source: trunk/macosx/TorrentTableView.m @ 5282

Last change on this file since 5282 was 5282, checked in by livings124, 14 years ago

speed up check for enough remaining space by first determining if there is enough space to download what's needed ignoring preallocation

  • Property svn:keywords set to Date Rev Author Id
File size: 29.3 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 5282 2008-03-18 03:20:27Z livings124 $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "TorrentTableView.h"
26#import "TorrentCell.h"
27#import "Torrent.h"
28#import "NSApplicationAdditions.h"
29
30#define MAX_GROUP (INT_MAX-10)
31
32#define ACTION_MENU_GLOBAL_TAG 101
33#define ACTION_MENU_UNLIMITED_TAG 102
34#define ACTION_MENU_LIMIT_TAG 103
35
36#define PIECE_CHANGE 0.1
37#define PIECE_TIME 0.005
38
39#define GROUP_SPEED_IMAGE_COLUMN_WIDTH 8.0
40#define GROUP_RATIO_IMAGE_COLUMN_WIDTH 10.0
41
42@interface TorrentTableView (Private)
43
44- (BOOL) pointInControlRect: (NSPoint) point;
45- (BOOL) pointInRevealRect: (NSPoint) point;
46- (BOOL) pointInActionRect: (NSPoint) point;
47
48- (BOOL) pointInProgressRect: (NSPoint) point;
49- (BOOL) pointInMinimalStatusRect: (NSPoint) point;
50
51- (BOOL) pointInGroupStatusRect: (NSPoint) point;
52
53- (void) setGroupStatusColumns;
54
55- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files;
56
57- (void) resizePiecesBarIncrement;
58
59@end
60
61@implementation TorrentTableView
62
63- (id) initWithCoder: (NSCoder *) decoder
64{
65    if ((self = [super initWithCoder: decoder]))
66    {
67        fDefaults = [NSUserDefaults standardUserDefaults];
68       
69        fTorrentCell = [[TorrentCell alloc] init];
70       
71        if (![NSApp isOnLeopardOrBetter])
72        {
73            NSTableColumn * groupColumn = [self tableColumnWithIdentifier: @"Group"];
74            [self setOutlineTableColumn: groupColumn];
75           
76            //remove all unnecessary columns
77            int i;
78            for (i = [[self tableColumns] count]-1; i >= 0; i--)
79            {
80                NSTableColumn * column = [[self tableColumns] objectAtIndex: i];
81                if (column != groupColumn)
82                    [self removeTableColumn: column];
83            }
84           
85            [self sizeLastColumnToFit];
86           
87            [groupColumn setDataCell: fTorrentCell];
88        }
89       
90        NSData * groupData = [fDefaults dataForKey: @"CollapsedGroups"];
91        if (groupData)
92            fCollapsedGroups = [[NSUnarchiver unarchiveObjectWithData: groupData] mutableCopy];
93        else
94            fCollapsedGroups = [[NSMutableIndexSet alloc] init];
95       
96        fMouseControlRow = -1;
97        fMouseRevealRow = -1;
98        fMouseActionRow = -1;
99        fActionPushedRow = -1;
100       
101        [self setDelegate: self];
102       
103        fPiecesBarPercent = [fDefaults boolForKey: @"PiecesBar"] ? 1.0 : 0.0;
104    }
105   
106    return self;
107}
108
109- (void) dealloc
110{
111    [fCollapsedGroups release];
112   
113    [fPiecesBarTimer invalidate];
114    [fMenuTorrent release];
115   
116    [fSelectedValues release];
117   
118    [fTorrentCell release];
119   
120    [super dealloc];
121}
122
123- (void) awakeFromNib
124{
125    //set group columns to show ratio, needs to be in awakeFromNib to size columns correctly
126    [self setGroupStatusColumns];
127}
128
129- (BOOL) isGroupCollapsed: (int) value
130{
131    if (value < 0)
132        value = MAX_GROUP;
133   
134    return [fCollapsedGroups containsIndex: value];
135}
136
137- (void) removeCollapsedGroup: (int) value
138{
139    if (value < 0)
140        value = MAX_GROUP;
141   
142    [fCollapsedGroups removeIndex: value];
143}
144
145- (void) removeAllCollapsedGroups
146{
147    [fCollapsedGroups removeAllIndexes];
148}
149
150- (void) saveCollapsedGroups
151{
152    [fDefaults setObject: [NSArchiver archivedDataWithRootObject: fCollapsedGroups] forKey: @"CollapsedGroups"];
153}
154
155- (BOOL) outlineView: (NSOutlineView *) outlineView isGroupItem: (id) item
156{
157    return ![item isKindOfClass: [Torrent class]];
158}
159
160- (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
161{
162    return [item isKindOfClass: [Torrent class]] ? [self rowHeight] : GROUP_SEPARATOR_HEIGHT;
163}
164
165- (NSCell *) outlineView: (NSOutlineView *) outlineView dataCellForTableColumn: (NSTableColumn *) tableColumn item: (id) item
166{
167    BOOL group = ![item isKindOfClass: [Torrent class]];
168    if (!tableColumn)
169        return !group ? fTorrentCell : nil;
170    else
171        return group ? [tableColumn dataCellForRow: [self rowForItem: item]] : nil;
172}
173
174- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
175    item: (id) item
176{
177    if ([item isKindOfClass: [Torrent class]])
178    {
179        [cell setRepresentedObject: item];
180       
181        int row = [self rowForItem: item];
182        [cell setControlHover: row == fMouseControlRow];
183        [cell setRevealHover: row == fMouseRevealRow];
184        [cell setActionHover: row == fMouseActionRow];
185        [cell setActionPushed: row == fActionPushedRow];
186    }
187    else
188    {
189        if ([[tableColumn identifier] isEqualToString: @"UL Image"] || [[tableColumn identifier] isEqualToString: @"DL Image"])
190        {
191            //ensure arrows are white only when selected
192            NSImage * image = [cell image];
193            BOOL template = [cell backgroundStyle] == NSBackgroundStyleLowered;
194            if ([image isTemplate] != template)
195            {
196                [image setTemplate: template];
197                [cell setImage: nil];
198                [cell setImage: image];
199            }
200        }
201    }
202}
203
204- (NSRect) frameOfCellAtColumn: (NSInteger) column row: (NSInteger) row
205{
206    if (column == -1 || ![NSApp isOnLeopardOrBetter])
207        return [self rectOfRow: row];
208    else
209    {
210        NSRect rect = [super frameOfCellAtColumn: column row: row];
211       
212        //adjust placement for proper vertical alignment
213        if (column == [self columnWithIdentifier: @"Group"])
214            rect.size.height -= 1.0;
215       
216        return rect;
217    }
218}
219
220- (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item
221{
222    return [item isKindOfClass: [Torrent class]] ? [item name]
223            : [[self preparedCellAtColumn: [self columnWithIdentifier: @"Group"] row: [self rowForItem: item]] stringValue];
224}
225
226- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
227                tableColumn: (NSTableColumn *) column item: (id) item mouseLocation: (NSPoint) mouseLocation
228{
229    NSString * ident = [column identifier];
230    if ([ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"])
231        return NSLocalizedString(@"Download speed", "Torrent table -> group row -> tooltip");
232    else if ([ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"])
233        return [fDefaults boolForKey: @"DisplayGroupRowRatio"] ? NSLocalizedString(@"Ratio", "Torrent table -> group row -> tooltip")
234                : NSLocalizedString(@"Upload speed", "Torrent table -> group row -> tooltip");
235    else if (ident)
236    {
237        int count = [[item objectForKey: @"Torrents"] count];
238        if (count == 1)
239            return NSLocalizedString(@"1 transfer", "Torrent table -> group row -> tooltip");
240        else
241            return [NSString stringWithFormat: NSLocalizedString(@"%d transfers", "Torrent table -> group row -> tooltip"), count];
242    }
243    else
244        return nil;
245}
246
247- (void) updateTrackingAreas
248{
249    [super updateTrackingAreas];
250    [self removeButtonTrackingAreas];
251   
252    NSRange rows = [self rowsInRect: [self visibleRect]];
253    if (rows.length == 0)
254        return;
255   
256    NSPoint mouseLocation = [self convertPoint: [[self window] convertScreenToBase: [NSEvent mouseLocation]] fromView: nil];
257   
258    NSUInteger row;
259    for (row = rows.location; row < NSMaxRange(rows); row++)
260    {
261        if (![[self itemAtRow: row] isKindOfClass: [Torrent class]])
262            continue;
263       
264        NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: row] forKey: @"Row"];
265        TorrentCell * cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
266        [cell addTrackingAreasForView: self inRect: [self rectOfRow: row] withUserInfo: userInfo
267                mouseLocation: mouseLocation];
268    }
269}
270
271- (void) removeButtonTrackingAreas
272{
273    fMouseControlRow = -1;
274    fMouseRevealRow = -1;
275    fMouseActionRow = -1;
276   
277    if (![NSApp isOnLeopardOrBetter])
278        return;
279   
280    NSEnumerator * enumerator = [[self trackingAreas] objectEnumerator];
281    NSTrackingArea * area;
282    while ((area = [enumerator nextObject]))
283    {
284        if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
285            [self removeTrackingArea: area];
286    }
287}
288
289- (void) setControlButtonHover: (int) row
290{
291    fMouseControlRow = row;
292    if (row >= 0)
293        [self setNeedsDisplayInRect: [self rectOfRow: row]];
294}
295
296- (void) setRevealButtonHover: (int) row
297{
298    fMouseRevealRow = row;
299    if (row >= 0)
300        [self setNeedsDisplayInRect: [self rectOfRow: row]];
301}
302
303- (void) setActionButtonHover: (int) row
304{
305    fMouseActionRow = row;
306    if (row >= 0)
307        [self setNeedsDisplayInRect: [self rectOfRow: row]];
308}
309
310- (void) mouseEntered: (NSEvent *) event
311{
312    NSDictionary * dict = (NSDictionary *)[event userData];
313   
314    NSNumber * row;
315    if ((row = [dict objectForKey: @"Row"]))
316    {
317        int rowVal = [row intValue];
318        NSString * type = [dict objectForKey: @"Type"];
319        if ([type isEqualToString: @"Action"])
320            fMouseActionRow = rowVal;
321        else if ([type isEqualToString: @"Control"])
322            fMouseControlRow = rowVal;
323        else
324            fMouseRevealRow = rowVal;
325       
326        [self setNeedsDisplayInRect: [self rectOfRow: rowVal]];
327    }
328}
329
330- (void) mouseExited: (NSEvent *) event
331{
332    NSDictionary * dict = (NSDictionary *)[event userData];
333   
334    NSNumber * row;
335    if ((row = [dict objectForKey: @"Row"]))
336    {
337        NSString * type = [dict objectForKey: @"Type"];
338        if ([type isEqualToString: @"Action"])
339            fMouseActionRow = -1;
340        else if ([type isEqualToString: @"Control"])
341            fMouseControlRow = -1;
342        else
343            fMouseRevealRow = -1;
344       
345        [self setNeedsDisplayInRect: [self rectOfRow: [row intValue]]];
346    }
347}
348
349- (void) outlineViewSelectionIsChanging: (NSNotification *) notification
350{
351    if (fSelectedValues)
352        [self selectValues: fSelectedValues];
353}
354
355- (void) outlineViewItemDidExpand: (NSNotification *) notification
356{
357    int value = [[[[notification userInfo] objectForKey: @"NSObject"] objectForKey: @"Group"] intValue];
358    [fCollapsedGroups removeIndex: value >= 0 ? value : MAX_GROUP];
359   
360    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
361}
362
363- (void) outlineViewItemDidCollapse: (NSNotification *) notification
364{
365    int value = [[[[notification userInfo] objectForKey: @"NSObject"] objectForKey: @"Group"] intValue];
366    [fCollapsedGroups addIndex: value >= 0 ? value : MAX_GROUP];
367   
368    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
369}
370
371- (void) mouseDown: (NSEvent *) event
372{
373    NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
374   
375    //check to toggle group status before anything else
376    if ([self pointInGroupStatusRect: point])
377    {
378        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
379        [self setGroupStatusColumns];
380       
381        return;
382    }
383   
384    BOOL pushed = [self pointInControlRect: point] || [self pointInRevealRect: point] || [self pointInActionRect: point]
385                    || [self pointInProgressRect: point] || [self pointInMinimalStatusRect: point];
386   
387    //if pushing a button, don't change the selected rows
388    if (pushed)
389        fSelectedValues = [[self selectedValues] retain];
390   
391    [super mouseDown: event];
392   
393    [fSelectedValues release];
394    fSelectedValues = nil;
395   
396    //avoid weird behavior when showing menu by doing this after mouse down
397    if ([self pointInActionRect: point])
398    {
399        int row = [self rowAtPoint: point];
400       
401        fActionPushedRow = row;
402        [self setNeedsDisplayInRect: [self rectOfRow: row]]; //ensure button is pushed down
403       
404        [self displayTorrentMenuForEvent: event];
405       
406        fActionPushedRow = -1;
407        [self setNeedsDisplayInRect: [self rectOfRow: row]];
408    }
409    else if (!pushed && [event clickCount] == 2) //double click
410    {
411        id item = [self itemAtRow: [self rowAtPoint: point]];
412       
413        if ([item isKindOfClass: [Torrent class]])
414            [fController showInfo: nil];
415        else
416        {
417            if ([self isItemExpanded: item])
418                [self collapseItem: item];
419            else
420                [self expandItem: item];
421        }
422    }
423    else;
424}
425
426- (void) selectValues: (NSArray *) values
427{
428    NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
429   
430    NSEnumerator * enumerator = [values objectEnumerator];
431    id item;
432    while ((item = [enumerator nextObject]))
433    {
434        if ([item isKindOfClass: [Torrent class]])
435        {
436            NSUInteger index = [self rowForItem: item];
437            if (index != -1)
438                [indexSet addIndex: index];
439        }
440        else
441        {
442            NSNumber * group = [item objectForKey: @"Group"];
443            int i;
444            for (i = 0; i < [self numberOfRows]; i++)
445            {
446                id tableItem = [self itemAtRow: i];
447                if (![tableItem isKindOfClass: [Torrent class]] && [group isEqualToNumber: [tableItem objectForKey: @"Group"]])
448                {
449                    [indexSet addIndex: i];
450                    break;
451                }
452            }
453        }
454    }
455   
456    [self selectRowIndexes: indexSet byExtendingSelection: NO];
457}
458
459- (NSArray *) selectedValues
460{
461    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
462    NSMutableArray * values = [NSMutableArray arrayWithCapacity: [selectedIndexes count]];
463   
464    NSUInteger i;
465    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
466        [values addObject: [self itemAtRow: i]];
467   
468    return values;
469}
470
471- (NSArray *) selectedTorrents
472{
473    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
474    NSMutableArray * torrents = [NSMutableArray array];
475   
476    NSUInteger i;
477    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
478    {
479        id item = [self itemAtRow: i];
480        if ([item isKindOfClass: [Torrent class]])
481        {
482            if (![torrents containsObject: item])
483                [torrents addObject: item];
484        }
485        else
486            [torrents addObjectsFromArray: [item objectForKey: @"Torrents"]];
487    }
488   
489    return torrents;
490}
491
492- (NSMenu *) menuForEvent: (NSEvent *) event
493{
494    int row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
495    if (row >= 0)
496    {
497        if (![self isRowSelected: row])
498            [self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
499        return fContextRow;
500    }
501    else
502    {
503        [self deselectAll: self];
504        return fContextNoRow;
505    }
506}
507
508//make sure that the pause buttons become orange when holding down the option key
509- (void) flagsChanged: (NSEvent *) event
510{
511    [self display];
512    [super flagsChanged: event];
513}
514
515//option-command-f will focus the filter bar's search field
516- (void) keyDown: (NSEvent *) event
517{
518    if ([[event charactersIgnoringModifiers] isEqualToString: @"f"] && [event modifierFlags] & NSAlternateKeyMask
519        && [event modifierFlags] & NSCommandKeyMask)
520        [fController focusFilterField];
521    else
522        [super keyDown: event];
523}
524
525- (void) toggleControlForTorrent: (Torrent *) torrent
526{
527    if ([torrent isActive])
528        [fController stopTorrents: [NSArray arrayWithObject: torrent]];
529    else
530    {
531        if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
532            [fController resumeTorrentsNoWait: [NSArray arrayWithObject: torrent]];
533        else if ([torrent waitingToStart])
534            [fController stopTorrents: [NSArray arrayWithObject: torrent]];
535        else
536            [fController resumeTorrents: [NSArray arrayWithObject: torrent]];
537    }
538}
539
540- (void) displayTorrentMenuForEvent: (NSEvent *) event
541{
542    int row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
543    if (row < 0)
544        return;
545   
546    NSInteger numberOfNonFileItems = [fActionMenu numberOfItems];
547   
548    //update file action menu
549    fMenuTorrent = [[self itemAtRow: row] retain];
550    [self createFileMenu: fActionMenu forFiles: [fMenuTorrent fileList]];
551   
552    //place menu below button
553    NSRect rect = [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
554    NSPoint location = rect.origin;
555    location.y += rect.size.height + 5.0;
556    location = [self convertPoint: location toView: nil];
557   
558    NSEvent * newEvent = [NSEvent mouseEventWithType: [event type] location: location
559        modifierFlags: [event modifierFlags] timestamp: [event timestamp] windowNumber: [event windowNumber]
560        context: [event context] eventNumber: [event eventNumber] clickCount: [event clickCount] pressure: [event pressure]];
561   
562    [NSMenu popUpContextMenu: fActionMenu withEvent: newEvent forView: self];
563   
564    NSInteger i;
565    for (i = [fActionMenu numberOfItems]-1; i >= numberOfNonFileItems; i--)
566        [fActionMenu removeItemAtIndex: i];
567   
568    [fMenuTorrent release];
569    fMenuTorrent = nil;
570}
571
572- (void) menuNeedsUpdate: (NSMenu *) menu
573{
574    //this method seems to be called when it shouldn't be
575    if (!fMenuTorrent || ![menu supermenu])
576        return;
577   
578    if (menu == fUploadMenu || menu == fDownloadMenu)
579    {
580        NSMenuItem * item;
581        if ([menu numberOfItems] == 4)
582        {
583            const int speedLimitActionValue[] = { 0, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, -1 };
584           
585            int i;
586            for (i = 0; speedLimitActionValue[i] != -1; i++)
587            {
588                item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
589                        "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimit:)
590                        keyEquivalent: @""];
591                [item setTarget: self];
592                [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
593                [menu addItem: item];
594                [item release];
595            }
596        }
597       
598        BOOL upload = menu == fUploadMenu;
599        int mode = [fMenuTorrent speedMode: upload];
600       
601        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
602        [item setState: mode == TR_SPEEDLIMIT_SINGLE ? NSOnState : NSOffState];
603        [item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
604                            "torrent action menu -> upload/download limit"), [fMenuTorrent speedLimit: upload]]];
605       
606        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
607        [item setState: mode == TR_SPEEDLIMIT_UNLIMITED ? NSOnState : NSOffState];
608       
609        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
610        [item setState: mode == TR_SPEEDLIMIT_GLOBAL ? NSOnState : NSOffState];
611    }
612    else if (menu == fRatioMenu)
613    {
614        NSMenuItem * item;
615        if ([menu numberOfItems] == 4)
616        {
617            const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1.0 };
618           
619            int i;
620            for (i = 0; ratioLimitActionValue[i] != -1.0; i++)
621            {
622                item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"%.2f", ratioLimitActionValue[i]]
623                        action: @selector(setQuickRatio:) keyEquivalent: @""];
624                [item setTarget: self];
625                [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
626                [menu addItem: item];
627                [item release];
628            }
629        }
630       
631        int mode = [fMenuTorrent ratioSetting];
632       
633        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
634        [item setState: mode == NSOnState ? NSOnState : NSOffState];
635        [item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)", "torrent action menu -> ratio stop"),
636                            [fMenuTorrent ratioLimit]]];
637       
638        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
639        [item setState: mode == NSOffState ? NSOnState : NSOffState];
640       
641        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
642        [item setState: mode == NSMixedState ? NSOnState : NSOffState];
643    }
644    else  //assume the menu is part of the file list
645    {
646        if ([menu numberOfItems] > 0)
647            return;
648       
649        NSMenu * supermenu = [menu supermenu];
650        [self createFileMenu: menu forFiles: [[[supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: menu]]
651                                                    representedObject] objectForKey: @"Children"]];
652    }
653}
654
655- (void) setQuickLimitMode: (id) sender
656{
657    int mode;
658    switch ([sender tag])
659    {
660        case ACTION_MENU_UNLIMITED_TAG:
661            mode = TR_SPEEDLIMIT_UNLIMITED;
662            break;
663        case ACTION_MENU_LIMIT_TAG:
664            mode = TR_SPEEDLIMIT_SINGLE;
665            break;
666        case ACTION_MENU_GLOBAL_TAG:
667            mode = TR_SPEEDLIMIT_GLOBAL;
668            break;
669        default:
670            return;
671    }
672   
673    [fMenuTorrent setSpeedMode: mode upload: [sender menu] == fUploadMenu];
674   
675    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
676}
677
678- (void) setQuickLimit: (id) sender
679{
680    BOOL upload = [sender menu] == fUploadMenu;
681    [fMenuTorrent setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: upload];
682    [fMenuTorrent setSpeedLimit: [[sender representedObject] intValue] upload: upload];
683   
684    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
685}
686
687- (void) setQuickRatioMode: (id) sender
688{
689    int mode;
690    switch ([sender tag])
691    {
692        case ACTION_MENU_UNLIMITED_TAG:
693            mode = NSOffState;
694            break;
695        case ACTION_MENU_LIMIT_TAG:
696            mode = NSOnState;
697            break;
698        case ACTION_MENU_GLOBAL_TAG:
699            mode = NSMixedState;
700            break;
701        default:
702            return;
703    }
704   
705    [fMenuTorrent setRatioSetting: mode];
706   
707    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
708}
709
710- (void) setQuickRatio: (id) sender
711{
712    [fMenuTorrent setRatioSetting: NSOnState];
713    [fMenuTorrent setRatioLimit: [[sender representedObject] floatValue]];
714   
715    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
716}
717
718- (void) checkFile: (id) sender
719{
720    NSIndexSet * indexSet = [[sender representedObject] objectForKey: @"Indexes"];
721    [fMenuTorrent setFileCheckState: [sender state] != NSOnState ? NSOnState : NSOffState forIndexes: indexSet];
722   
723    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
724}
725
726- (void) togglePiecesBar
727{
728    [self resizePiecesBarIncrement];
729   
730    if (!fPiecesBarTimer)
731    {
732        fPiecesBarTimer = [NSTimer scheduledTimerWithTimeInterval: PIECE_TIME target: self
733                            selector: @selector(resizePiecesBarIncrement) userInfo: nil repeats: YES];
734        [[NSRunLoop currentRunLoop] addTimer: fPiecesBarTimer forMode: NSModalPanelRunLoopMode];
735        [[NSRunLoop currentRunLoop] addTimer: fPiecesBarTimer forMode: NSEventTrackingRunLoopMode];
736    }
737}
738
739- (float) piecesBarPercent
740{
741    return fPiecesBarPercent;
742}
743
744@end
745
746@implementation TorrentTableView (Private)
747
748- (BOOL) pointInControlRect: (NSPoint) point
749{
750    int row = [self rowAtPoint: point];
751    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
752        return NO;
753   
754    return NSPointInRect(point, [fTorrentCell controlButtonRectForBounds: [self rectOfRow: row]]);
755}
756
757- (BOOL) pointInRevealRect: (NSPoint) point
758{
759    int row = [self rowAtPoint: point];
760    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
761        return NO;
762   
763    return NSPointInRect(point, [fTorrentCell revealButtonRectForBounds: [self rectOfRow: row]]);
764}
765
766- (BOOL) pointInActionRect: (NSPoint) point
767{
768    int row = [self rowAtPoint: point];
769    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
770        return NO;
771   
772    return NSPointInRect(point, [fTorrentCell iconRectForBounds: [self rectOfRow: row]]);
773}
774
775- (BOOL) pointInProgressRect: (NSPoint) point
776{
777    int row = [self rowAtPoint: point];
778    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]] || [fDefaults boolForKey: @"SmallView"])
779        return NO;
780   
781    TorrentCell * cell;
782    if ([NSApp isOnLeopardOrBetter])
783        cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
784    else
785    {
786        cell = fTorrentCell;
787        [cell setRepresentedObject: [self itemAtRow: row]];
788    }
789    return NSPointInRect(point, [cell progressRectForBounds: [self rectOfRow: row]]);
790}
791
792- (BOOL) pointInMinimalStatusRect: (NSPoint) point
793{
794    int row = [self rowAtPoint: point];
795    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]] || ![fDefaults boolForKey: @"SmallView"])
796        return NO;
797   
798    TorrentCell * cell;
799    if ([NSApp isOnLeopardOrBetter])
800        cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
801    else
802    {
803        cell = fTorrentCell;
804        [cell setRepresentedObject: [self itemAtRow: row]];
805    }
806    return NSPointInRect(point, [cell minimalStatusRectForBounds: [self rectOfRow: row]]);
807}
808
809- (BOOL) pointInGroupStatusRect: (NSPoint) point
810{
811    int row = [self rowAtPoint: point];
812    if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
813        return NO;
814   
815    NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
816    return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
817            || [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
818}
819
820- (void) setGroupStatusColumns
821{
822    BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
823   
824    [[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
825    [[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
826   
827    //change size of image column
828    NSTableColumn * ulImageTableColumn = [self tableColumnWithIdentifier: @"UL Image"];
829    float oldWidth = [ulImageTableColumn width], newWidth = ratio ? GROUP_RATIO_IMAGE_COLUMN_WIDTH : GROUP_SPEED_IMAGE_COLUMN_WIDTH;
830    if (oldWidth != newWidth)
831    {
832        [ulImageTableColumn setWidth: newWidth];
833       
834        NSTableColumn * groupTableColumn = [self tableColumnWithIdentifier: @"Group"];
835        [groupTableColumn setWidth: [groupTableColumn width] - (newWidth - oldWidth)];
836    }
837}
838
839- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files
840{
841    NSEnumerator * enumerator = [files objectEnumerator];
842    NSDictionary * dict;
843    while ((dict = [enumerator nextObject]))
844    {
845        NSString * name = [dict objectForKey: @"Name"];
846       
847        NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: name action: @selector(checkFile:) keyEquivalent: @""];
848       
849        NSImage * icon;
850        if (![[dict objectForKey: @"IsFolder"] boolValue])
851            icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
852        else
853        {
854            NSMenu * itemMenu = [[NSMenu alloc] initWithTitle: name];
855            [itemMenu setAutoenablesItems: NO];
856            [item setSubmenu: itemMenu];
857            [itemMenu setDelegate: self];
858            [itemMenu release];
859           
860            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')];
861        }
862       
863        [item setRepresentedObject: dict];
864       
865        [icon setScalesWhenResized: YES];
866        [icon setSize: NSMakeSize(16.0, 16.0)];
867        [item setImage: icon];
868       
869        NSIndexSet * indexSet = [dict objectForKey: @"Indexes"];
870        [item setState: [fMenuTorrent checkForFiles: indexSet]];
871        [item setEnabled: [fMenuTorrent canChangeDownloadCheckForFiles: indexSet]];
872       
873        [menu addItem: item];
874        [item release];
875    }
876}
877
878- (void) resizePiecesBarIncrement
879{
880    BOOL done;
881    if ([fDefaults boolForKey: @"PiecesBar"])
882    {
883        fPiecesBarPercent = MIN(fPiecesBarPercent + PIECE_CHANGE, 1.0);
884        done = fPiecesBarPercent == 1.0;
885    }
886    else
887    {
888        fPiecesBarPercent = MAX(fPiecesBarPercent - PIECE_CHANGE, 0.0);
889        done = fPiecesBarPercent == 0.0;
890    }
891   
892    if (done)
893    {
894        [fPiecesBarTimer invalidate];
895        fPiecesBarTimer = nil;
896    }
897   
898    [self reloadData];
899}
900
901@end
Note: See TracBrowser for help on using the repository browser.