source: trunk/macosx/TorrentTableView.m @ 6265

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

trivial changes to sorting by state and other miscellaneous changes

  • Property svn:keywords set to Date Rev Author Id
File size: 31.6 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 6265 2008-06-30 02:22:29Z 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 "FileListNode.h"
29#import "QuickLookController.h"
30#import "NSApplicationAdditions.h"
31
32#define MAX_GROUP (INT_MAX-10)
33
34#define ACTION_MENU_GLOBAL_TAG 101
35#define ACTION_MENU_UNLIMITED_TAG 102
36#define ACTION_MENU_LIMIT_TAG 103
37
38#define GROUP_SPEED_IMAGE_COLUMN_WIDTH 8.0
39#define GROUP_RATIO_IMAGE_COLUMN_WIDTH 10.0
40
41#define TOGGLE_PROGRESS_SECONDS 0.175
42
43@interface TorrentTableView (Private)
44
45- (BOOL) pointInControlRect: (NSPoint) point;
46- (BOOL) pointInRevealRect: (NSPoint) point;
47- (BOOL) pointInActionRect: (NSPoint) point;
48
49- (BOOL) pointInGroupStatusRect: (NSPoint) point;
50
51- (void) setGroupStatusColumns;
52
53- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files;
54
55@end
56
57@implementation TorrentTableView
58
59- (id) initWithCoder: (NSCoder *) decoder
60{
61    if ((self = [super initWithCoder: decoder]))
62    {
63        fDefaults = [NSUserDefaults standardUserDefaults];
64       
65        fTorrentCell = [[TorrentCell alloc] init];
66       
67        if (![NSApp isOnLeopardOrBetter])
68        {
69            NSTableColumn * groupColumn = [self tableColumnWithIdentifier: @"Group"];
70            [self setOutlineTableColumn: groupColumn];
71           
72            //remove all unnecessary columns
73            int i;
74            for (i = [[self tableColumns] count]-1; i >= 0; i--)
75            {
76                NSTableColumn * column = [[self tableColumns] objectAtIndex: i];
77                if (column != groupColumn)
78                    [self removeTableColumn: column];
79            }
80           
81            [self sizeLastColumnToFit];
82           
83            [groupColumn setDataCell: fTorrentCell];
84        }
85       
86        NSData * groupData = [fDefaults dataForKey: @"CollapsedGroups"];
87        if (groupData)
88            fCollapsedGroups = [[NSUnarchiver unarchiveObjectWithData: groupData] mutableCopy];
89        else
90            fCollapsedGroups = [[NSMutableIndexSet alloc] init];
91       
92        fMouseControlRow = -1;
93        fMouseRevealRow = -1;
94        fMouseActionRow = -1;
95        fActionPushedRow = -1;
96       
97        [self setDelegate: self];
98       
99        fPiecesBarPercent = [fDefaults boolForKey: @"PiecesBar"] ? 1.0 : 0.0;
100    }
101   
102    return self;
103}
104
105- (void) dealloc
106{
107    [fCollapsedGroups release];
108   
109    [fPiecesBarAnimation release];
110    [fMenuTorrent release];
111   
112    [fSelectedValues release];
113   
114    [fTorrentCell release];
115   
116    [super dealloc];
117}
118
119- (void) awakeFromNib
120{
121    //set group columns to show ratio, needs to be in awakeFromNib to size columns correctly
122    [self setGroupStatusColumns];
123}
124
125- (BOOL) isGroupCollapsed: (int) value
126{
127    if (value == -1)
128        value = MAX_GROUP;
129   
130    return [fCollapsedGroups containsIndex: value];
131}
132
133- (void) removeCollapsedGroup: (int) value
134{
135    if (value == -1)
136        value = MAX_GROUP;
137   
138    [fCollapsedGroups removeIndex: value];
139}
140
141- (void) removeAllCollapsedGroups
142{
143    [fCollapsedGroups removeAllIndexes];
144}
145
146- (void) saveCollapsedGroups
147{
148    [fDefaults setObject: [NSArchiver archivedDataWithRootObject: fCollapsedGroups] forKey: @"CollapsedGroups"];
149}
150
151- (BOOL) outlineView: (NSOutlineView *) outlineView isGroupItem: (id) item
152{
153    return ![item isKindOfClass: [Torrent class]];
154}
155
156- (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
157{
158    return [item isKindOfClass: [Torrent class]] ? [self rowHeight] : GROUP_SEPARATOR_HEIGHT;
159}
160
161- (NSCell *) outlineView: (NSOutlineView *) outlineView dataCellForTableColumn: (NSTableColumn *) tableColumn item: (id) item
162{
163    BOOL group = ![item isKindOfClass: [Torrent class]];
164    if (!tableColumn)
165        return !group ? fTorrentCell : nil;
166    else
167        return group ? [tableColumn dataCellForRow: [self rowForItem: item]] : nil;
168}
169
170- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
171    item: (id) item
172{
173    if ([item isKindOfClass: [Torrent class]])
174    {
175        [cell setRepresentedObject: item];
176       
177        int row = [self rowForItem: item];
178        if ([NSApp isOnLeopardOrBetter])
179        {
180            [cell setControlHover: row == fMouseControlRow];
181            [cell setRevealHover: row == fMouseRevealRow];
182            [cell setActionHover: row == fMouseActionRow];
183        }
184        [cell setActionPushed: row == fActionPushedRow];
185    }
186    else
187    {
188        NSString * ident = [tableColumn identifier];
189        if ([ident isEqualToString: @"UL Image"] || [ident 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 mouseLocation: mouseLocation];
267    }
268}
269
270- (void) removeButtonTrackingAreas
271{
272    fMouseControlRow = -1;
273    fMouseRevealRow = -1;
274    fMouseActionRow = -1;
275   
276    if (![NSApp isOnLeopardOrBetter])
277        return;
278   
279    NSEnumerator * enumerator = [[self trackingAreas] objectEnumerator];
280    NSTrackingArea * area;
281    while ((area = [enumerator nextObject]))
282    {
283        if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
284            [self removeTrackingArea: area];
285    }
286}
287
288- (void) setControlButtonHover: (int) row
289{
290    fMouseControlRow = row;
291    if (row >= 0)
292        [self setNeedsDisplayInRect: [self rectOfRow: row]];
293}
294
295- (void) setRevealButtonHover: (int) row
296{
297    fMouseRevealRow = row;
298    if (row >= 0)
299        [self setNeedsDisplayInRect: [self rectOfRow: row]];
300}
301
302- (void) setActionButtonHover: (int) row
303{
304    fMouseActionRow = row;
305    if (row >= 0)
306        [self setNeedsDisplayInRect: [self rectOfRow: row]];
307}
308
309//when Leopard-only, use these variables instead of pointInActionRect:, etc.
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    if (value < 0)
359        value = MAX_GROUP;
360   
361    if ([fCollapsedGroups containsIndex: value])
362    {
363        [fCollapsedGroups removeIndex: value];
364        [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
365    }
366}
367
368- (void) outlineViewItemDidCollapse: (NSNotification *) notification
369{
370    int value = [[[[notification userInfo] objectForKey: @"NSObject"] objectForKey: @"Group"] intValue];
371    if (value < 0)
372        value = MAX_GROUP;
373   
374    [fCollapsedGroups addIndex: value];
375    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
376}
377
378- (void) mouseDown: (NSEvent *) event
379{
380    NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
381   
382    //check to toggle group status before anything else
383    if ([self pointInGroupStatusRect: point])
384    {
385        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
386        [self setGroupStatusColumns];
387       
388        return;
389    }
390   
391    BOOL pushed = [self pointInControlRect: point] || [self pointInRevealRect: point] || [self pointInActionRect: point];
392   
393    //if pushing a button, don't change the selected rows
394    if (pushed)
395        fSelectedValues = [[self selectedValues] retain];
396   
397    [super mouseDown: event];
398   
399    [fSelectedValues release];
400    fSelectedValues = nil;
401   
402    //avoid weird behavior when showing menu by doing this after mouse down
403    if ([self pointInActionRect: point])
404    {
405        int row = [self rowAtPoint: point];
406       
407        fActionPushedRow = row;
408        [self setNeedsDisplayInRect: [self rectOfRow: row]]; //ensure button is pushed down
409       
410        [self displayTorrentMenuForEvent: event];
411       
412        fActionPushedRow = -1;
413        [self setNeedsDisplayInRect: [self rectOfRow: row]];
414    }
415    else if (!pushed && [event clickCount] == 2) //double click
416    {
417        id item = nil;
418        int row = [self rowAtPoint: point];
419        if (row != -1)
420            item = [self itemAtRow: row];
421       
422        if (!item || [item isKindOfClass: [Torrent class]])
423            [fController showInfo: nil];
424        else
425        {
426            if ([self isItemExpanded: item])
427                [self collapseItem: item];
428            else
429                [self expandItem: item];
430        }
431    }
432    else;
433}
434
435- (void) selectValues: (NSArray *) values
436{
437    NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
438   
439    NSEnumerator * enumerator = [values objectEnumerator];
440    id item;
441    while ((item = [enumerator nextObject]))
442    {
443        if ([item isKindOfClass: [Torrent class]])
444        {
445            NSUInteger index = [self rowForItem: item];
446            if (index != -1)
447                [indexSet addIndex: index];
448        }
449        else
450        {
451            NSNumber * group = [item objectForKey: @"Group"];
452            int i;
453            for (i = 0; i < [self numberOfRows]; i++)
454            {
455                if ([indexSet containsIndex: i])
456                    continue;
457               
458                id tableItem = [self itemAtRow: i];
459                if (![tableItem isKindOfClass: [Torrent class]] && [group isEqualToNumber: [tableItem objectForKey: @"Group"]])
460                {
461                    [indexSet addIndex: i];
462                    break;
463                }
464            }
465        }
466    }
467   
468    [self selectRowIndexes: indexSet byExtendingSelection: NO];
469}
470
471- (NSArray *) selectedValues
472{
473    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
474    NSMutableArray * values = [NSMutableArray arrayWithCapacity: [selectedIndexes count]];
475   
476    NSUInteger i;
477    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
478        [values addObject: [self itemAtRow: i]];
479   
480    return values;
481}
482
483- (NSArray *) selectedTorrents
484{
485    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
486    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [selectedIndexes count]]; //take a shot at guessing capacity
487   
488    NSUInteger i;
489    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
490    {
491        id item = [self itemAtRow: i];
492        if ([item isKindOfClass: [Torrent class]])
493            [torrents addObject: item];
494        else
495        {
496            NSArray * groupTorrents = [item objectForKey: @"Torrents"];
497            [torrents addObjectsFromArray: groupTorrents];
498            i += [groupTorrents count];
499        }
500    }
501   
502    return torrents;
503}
504
505- (NSMenu *) menuForEvent: (NSEvent *) event
506{
507    int row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
508    if (row >= 0)
509    {
510        if (![self isRowSelected: row])
511            [self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
512        return fContextRow;
513    }
514    else
515    {
516        [self deselectAll: self];
517        return fContextNoRow;
518    }
519}
520
521//make sure that the pause buttons become orange when holding down the option key
522- (void) flagsChanged: (NSEvent *) event
523{
524    [self display];
525    [super flagsChanged: event];
526}
527
528//option-command-f will focus the filter bar's search field
529- (void) keyDown: (NSEvent *) event
530{
531    unichar firstChar = [[event charactersIgnoringModifiers] characterAtIndex: 0];
532   
533    if (firstChar == 'f' && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
534        [fController focusFilterField];
535    else
536    {
537        //handle quicklook
538        if (firstChar == ' ')
539            [[QuickLookController quickLook] toggleQuickLook];
540        else if (firstChar == NSRightArrowFunctionKey)
541            [[QuickLookController quickLook] pressRight];
542        else if (firstChar == NSLeftArrowFunctionKey)
543            [[QuickLookController quickLook] pressLeft];
544        else;
545       
546        [super keyDown: event];
547    }
548}
549
550- (NSRect) iconRectForRow: (int) row
551{
552    return [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
553}
554
555- (void) paste: (id) sender
556{
557    NSURL * url;
558    if ((url = [NSURL URLFromPasteboard: [NSPasteboard generalPasteboard]]))
559        [fController openURL: url];
560}
561
562- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
563{
564    SEL action = [menuItem action];
565   
566    if (action == @selector(paste:))
567        return [[[NSPasteboard generalPasteboard] types] containsObject: NSURLPboardType];
568   
569    return YES;
570}
571
572- (void) toggleControlForTorrent: (Torrent *) torrent
573{
574    if ([torrent isActive])
575        [fController stopTorrents: [NSArray arrayWithObject: torrent]];
576    else
577    {
578        if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
579            [fController resumeTorrentsNoWait: [NSArray arrayWithObject: torrent]];
580        else if ([torrent waitingToStart])
581            [fController stopTorrents: [NSArray arrayWithObject: torrent]];
582        else
583            [fController resumeTorrents: [NSArray arrayWithObject: torrent]];
584    }
585}
586
587- (void) displayTorrentMenuForEvent: (NSEvent *) event
588{
589    int row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
590    if (row < 0)
591        return;
592   
593    NSInteger numberOfNonFileItems = [fActionMenu numberOfItems];
594   
595    //update file action menu
596    fMenuTorrent = [[self itemAtRow: row] retain];
597    [self createFileMenu: fActionMenu forFiles: [fMenuTorrent fileList]];
598   
599    //place menu below button
600    NSRect rect = [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
601    NSPoint location = rect.origin;
602    location.y += rect.size.height + 5.0;
603    location = [self convertPoint: location toView: nil];
604   
605    NSEvent * newEvent = [NSEvent mouseEventWithType: [event type] location: location
606        modifierFlags: [event modifierFlags] timestamp: [event timestamp] windowNumber: [event windowNumber]
607        context: [event context] eventNumber: [event eventNumber] clickCount: [event clickCount] pressure: [event pressure]];
608   
609    [NSMenu popUpContextMenu: fActionMenu withEvent: newEvent forView: self];
610   
611    NSInteger i;
612    for (i = [fActionMenu numberOfItems]-1; i >= numberOfNonFileItems; i--)
613        [fActionMenu removeItemAtIndex: i];
614   
615    [fMenuTorrent release];
616    fMenuTorrent = nil;
617}
618
619- (void) menuNeedsUpdate: (NSMenu *) menu
620{
621    //this method seems to be called when it shouldn't be
622    if (!fMenuTorrent || ![menu supermenu])
623        return;
624   
625    if (menu == fUploadMenu || menu == fDownloadMenu)
626    {
627        NSMenuItem * item;
628        if ([menu numberOfItems] == 4)
629        {
630            const int speedLimitActionValue[] = { 0, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, -1 };
631           
632            int i;
633            for (i = 0; speedLimitActionValue[i] != -1; i++)
634            {
635                item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
636                        "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimit:)
637                        keyEquivalent: @""];
638                [item setTarget: self];
639                [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
640                [menu addItem: item];
641                [item release];
642            }
643        }
644       
645        BOOL upload = menu == fUploadMenu;
646        int mode = [fMenuTorrent speedMode: upload];
647       
648        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
649        [item setState: mode == TR_SPEEDLIMIT_SINGLE ? NSOnState : NSOffState];
650        [item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
651                            "torrent action menu -> upload/download limit"), [fMenuTorrent speedLimit: upload]]];
652       
653        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
654        [item setState: mode == TR_SPEEDLIMIT_UNLIMITED ? NSOnState : NSOffState];
655       
656        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
657        [item setState: mode == TR_SPEEDLIMIT_GLOBAL ? NSOnState : NSOffState];
658    }
659    else if (menu == fRatioMenu)
660    {
661        NSMenuItem * item;
662        if ([menu numberOfItems] == 4)
663        {
664            const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1.0 };
665           
666            int i;
667            for (i = 0; ratioLimitActionValue[i] != -1.0; i++)
668            {
669                item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
670                        action: @selector(setQuickRatio:) keyEquivalent: @""];
671                [item setTarget: self];
672                [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
673                [menu addItem: item];
674                [item release];
675            }
676        }
677       
678        int mode = [fMenuTorrent ratioSetting];
679       
680        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
681        [item setState: mode == NSOnState ? NSOnState : NSOffState];
682        [item setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
683            "torrent action menu -> ratio stop"), [fMenuTorrent ratioLimit]]];
684       
685        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
686        [item setState: mode == NSOffState ? NSOnState : NSOffState];
687       
688        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
689        [item setState: mode == NSMixedState ? NSOnState : NSOffState];
690    }
691    else //assume the menu is part of the file list
692    {
693        if ([menu numberOfItems] > 0)
694            return;
695       
696        NSMenu * supermenu = [menu supermenu];
697        [self createFileMenu: menu forFiles: [(FileListNode *)[[supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: menu]]
698                                                representedObject] children]];
699    }
700}
701
702//alternating rows - first row after group row is white
703- (void) highlightSelectionInClipRect: (NSRect) clipRect
704{
705    NSColor * altColor = [[NSColor controlAlternatingRowBackgroundColors] objectAtIndex: 1];
706    [altColor set];
707   
708    NSRect visibleRect = clipRect;
709    NSRange rows = [self rowsInRect: visibleRect];
710    BOOL start = YES;
711    int i;
712   
713    if (rows.length > 0)
714    {
715        //determine what the first row color should be
716        if ([[self itemAtRow: rows.location] isKindOfClass: [Torrent class]])
717        {
718            for (i = rows.location-1; i>=0; i--)
719            {
720                if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
721                    break;
722                start = !start;
723            }
724        }
725        else
726        {
727            rows.location++;
728            rows.length--;
729        }
730       
731        for (i = rows.location; i < NSMaxRange(rows); i++)
732        {
733            if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
734            {
735                start = YES;
736                continue;
737            }
738           
739            if (!start && ![self isRowSelected: i])
740                NSRectFill([self rectOfRow: i]);
741           
742            start = !start;
743        }
744       
745        float newY = NSMaxY([self rectOfRow: i-1]);
746        visibleRect.size.height -= newY - visibleRect.origin.y;
747        visibleRect.origin.y = newY;
748    }
749   
750    //remaining visible rows continue alternating
751    float height = [self rowHeight] + [self intercellSpacing].height;
752    int numberOfRects = ceil(visibleRect.size.height / height);
753    visibleRect.size.height = height;
754   
755    for (i=0; i<numberOfRects; i++)
756    {
757        if (!start)
758            NSRectFill(visibleRect);
759       
760        start = !start;
761        visibleRect.origin.y += height;
762    }
763   
764    [super highlightSelectionInClipRect: clipRect];
765}
766
767- (void) setQuickLimitMode: (id) sender
768{
769    int mode;
770    switch ([sender tag])
771    {
772        case ACTION_MENU_UNLIMITED_TAG:
773            mode = TR_SPEEDLIMIT_UNLIMITED;
774            break;
775        case ACTION_MENU_LIMIT_TAG:
776            mode = TR_SPEEDLIMIT_SINGLE;
777            break;
778        case ACTION_MENU_GLOBAL_TAG:
779            mode = TR_SPEEDLIMIT_GLOBAL;
780            break;
781        default:
782            return;
783    }
784   
785    [fMenuTorrent setSpeedMode: mode upload: [sender menu] == fUploadMenu];
786   
787    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
788}
789
790- (void) setQuickLimit: (id) sender
791{
792    BOOL upload = [sender menu] == fUploadMenu;
793    [fMenuTorrent setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: upload];
794    [fMenuTorrent setSpeedLimit: [[sender representedObject] intValue] upload: upload];
795   
796    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
797}
798
799- (void) setQuickRatioMode: (id) sender
800{
801    int mode;
802    switch ([sender tag])
803    {
804        case ACTION_MENU_UNLIMITED_TAG:
805            mode = NSOffState;
806            break;
807        case ACTION_MENU_LIMIT_TAG:
808            mode = NSOnState;
809            break;
810        case ACTION_MENU_GLOBAL_TAG:
811            mode = NSMixedState;
812            break;
813        default:
814            return;
815    }
816   
817    [fMenuTorrent setRatioSetting: mode];
818   
819    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
820}
821
822- (void) setQuickRatio: (id) sender
823{
824    [fMenuTorrent setRatioSetting: NSOnState];
825    [fMenuTorrent setRatioLimit: [[sender representedObject] floatValue]];
826   
827    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
828}
829
830- (void) checkFile: (id) sender
831{
832    NSIndexSet * indexSet = [(FileListNode *)[sender representedObject] indexes];
833    [fMenuTorrent setFileCheckState: [sender state] != NSOnState ? NSOnState : NSOffState forIndexes: indexSet];
834   
835    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
836}
837
838- (void) moveDataFile: (id) sender
839{
840    [fController moveDataFiles: [NSArray arrayWithObject: fMenuTorrent]];
841}
842
843- (void) togglePiecesBar
844{
845    //stop previous animation
846    if (fPiecesBarAnimation)
847        [fPiecesBarAnimation release];
848   
849    NSMutableArray * progressMarks = [NSMutableArray arrayWithCapacity: 16];
850    NSAnimationProgress i;
851    for (i = 0.0625; i <= 1.0; i += 0.0625)
852        [progressMarks addObject: [NSNumber numberWithFloat: i]];
853   
854    fPiecesBarAnimation = [[NSAnimation alloc] initWithDuration: TOGGLE_PROGRESS_SECONDS animationCurve: NSAnimationEaseIn];
855    [fPiecesBarAnimation setAnimationBlockingMode: NSAnimationNonblocking];
856    [fPiecesBarAnimation setProgressMarks: progressMarks];
857    [fPiecesBarAnimation setDelegate: self];
858   
859    [fPiecesBarAnimation startAnimation];
860}
861
862- (void) animationDidEnd: (NSAnimation *) animation
863{
864    if (animation == fPiecesBarAnimation)
865    {
866        [fPiecesBarAnimation release];
867        fPiecesBarAnimation = nil;
868    }
869}
870
871- (void) animation: (NSAnimation *) animation didReachProgressMark: (NSAnimationProgress) progress
872{
873    if (animation == fPiecesBarAnimation)
874    {
875        if ([fDefaults boolForKey: @"PiecesBar"])
876            fPiecesBarPercent = progress;
877        else
878            fPiecesBarPercent = 1.0 - progress;
879       
880        [self reloadData];
881    }
882}
883
884- (float) piecesBarPercent
885{
886    return fPiecesBarPercent;
887}
888
889@end
890
891@implementation TorrentTableView (Private)
892
893- (BOOL) pointInControlRect: (NSPoint) point
894{
895    int row = [self rowAtPoint: point];
896    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
897        return NO;
898   
899    return NSPointInRect(point, [fTorrentCell controlButtonRectForBounds: [self rectOfRow: row]]);
900}
901
902- (BOOL) pointInRevealRect: (NSPoint) point
903{
904    int row = [self rowAtPoint: point];
905    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
906        return NO;
907   
908    return NSPointInRect(point, [fTorrentCell revealButtonRectForBounds: [self rectOfRow: row]]);
909}
910
911- (BOOL) pointInActionRect: (NSPoint) point
912{
913    int row = [self rowAtPoint: point];
914    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
915        return NO;
916   
917    return NSPointInRect(point, [fTorrentCell iconRectForBounds: [self rectOfRow: row]]);
918}
919
920- (BOOL) pointInGroupStatusRect: (NSPoint) point
921{
922    int row = [self rowAtPoint: point];
923    if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
924        return NO;
925   
926    NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
927    return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
928            || [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
929}
930
931- (void) setGroupStatusColumns
932{
933    BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
934   
935    [[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
936    [[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
937   
938    //change size of image column
939    NSTableColumn * ulImageTableColumn = [self tableColumnWithIdentifier: @"UL Image"];
940    float oldWidth = [ulImageTableColumn width], newWidth = ratio ? GROUP_RATIO_IMAGE_COLUMN_WIDTH : GROUP_SPEED_IMAGE_COLUMN_WIDTH;
941    if (oldWidth != newWidth)
942    {
943        [ulImageTableColumn setWidth: newWidth];
944       
945        NSTableColumn * groupTableColumn = [self tableColumnWithIdentifier: @"Group"];
946        [groupTableColumn setWidth: [groupTableColumn width] - (newWidth - oldWidth)];
947    }
948}
949
950- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files
951{
952    NSEnumerator * enumerator = [files objectEnumerator];
953    FileListNode * node;
954    while ((node = [enumerator nextObject]))
955    {
956        NSString * name = [node name];
957       
958        NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: name action: @selector(checkFile:) keyEquivalent: @""];
959       
960        NSImage * icon;
961        if (![node isFolder])
962            icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
963        else
964        {
965            NSMenu * itemMenu = [[NSMenu alloc] initWithTitle: name];
966            [itemMenu setAutoenablesItems: NO];
967            [item setSubmenu: itemMenu];
968            [itemMenu setDelegate: self];
969            [itemMenu release];
970           
971            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')];
972        }
973       
974        [item setRepresentedObject: node];
975       
976        [icon setScalesWhenResized: YES];
977        [icon setSize: NSMakeSize(16.0, 16.0)];
978        [item setImage: icon];
979       
980        NSIndexSet * indexSet = [node indexes];
981        [item setState: [fMenuTorrent checkForFiles: indexSet]];
982        [item setEnabled: [fMenuTorrent canChangeDownloadCheckForFiles: indexSet]];
983       
984        [menu addItem: item];
985        [item release];
986    }
987}
988
989@end
Note: See TracBrowser for help on using the repository browser.