source: trunk/macosx/TorrentTableView.m @ 9674

Last change on this file since 9674 was 9674, checked in by livings124, 13 years ago

fix pasting a url into the torrent window

  • Property svn:keywords set to Date Rev Author Id
File size: 30.9 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 9674 2009-12-05 15:48:26Z livings124 $
3 *
4 * Copyright (c) 2005-2009 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 "Controller.h"
27#import "FileListNode.h"
28#import "NSApplicationAdditions.h"
29#import "Torrent.h"
30#import "TorrentCell.h"
31#import "TorrentGroup.h"
32
33#define MAX_GROUP 999999
34
35#define ACTION_MENU_GLOBAL_TAG 101
36#define ACTION_MENU_UNLIMITED_TAG 102
37#define ACTION_MENU_LIMIT_TAG 103
38
39#define ACTION_MENU_PRIORITY_HIGH_TAG 101
40#define ACTION_MENU_PRIORITY_NORMAL_TAG 102
41#define ACTION_MENU_PRIORITY_LOW_TAG 103
42
43#define TOGGLE_PROGRESS_SECONDS 0.175
44
45@interface TorrentTableView (Private)
46
47- (BOOL) pointInGroupStatusRect: (NSPoint) point;
48
49- (void) setGroupStatusColumns;
50
51- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files;
52
53@end
54
55@implementation TorrentTableView
56
57- (id) initWithCoder: (NSCoder *) decoder
58{
59    if ((self = [super initWithCoder: decoder]))
60    {
61        fDefaults = [NSUserDefaults standardUserDefaults];
62       
63        fTorrentCell = [[TorrentCell alloc] init];
64       
65        NSData * groupData = [fDefaults dataForKey: @"CollapsedGroups"];
66        if (groupData)
67            fCollapsedGroups = [[NSUnarchiver unarchiveObjectWithData: groupData] mutableCopy];
68        else
69            fCollapsedGroups = [[NSMutableIndexSet alloc] init];
70       
71        fMouseControlRow = -1;
72        fMouseRevealRow = -1;
73        fMouseActionRow = -1;
74        fActionPushedRow = -1;
75       
76        [self setDelegate: self];
77       
78        fPiecesBarPercent = [fDefaults boolForKey: @"PiecesBar"] ? 1.0f : 0.0f;
79    }
80   
81    return self;
82}
83
84- (void) dealloc
85{
86    [fCollapsedGroups release];
87   
88    [fPiecesBarAnimation release];
89    [fMenuTorrent release];
90   
91    [fSelectedValues release];
92   
93    [fTorrentCell release];
94   
95    [super dealloc];
96}
97
98- (void) awakeFromNib
99{
100    //set group columns to show ratio, needs to be in awakeFromNib to size columns correctly
101    [self setGroupStatusColumns];
102}
103
104- (BOOL) isGroupCollapsed: (NSInteger) value
105{
106    if (value == -1)
107        value = MAX_GROUP;
108   
109    return [fCollapsedGroups containsIndex: value];
110}
111
112- (void) removeCollapsedGroup: (NSInteger) value
113{
114    if (value == -1)
115        value = MAX_GROUP;
116   
117    [fCollapsedGroups removeIndex: value];
118}
119
120- (void) removeAllCollapsedGroups
121{
122    [fCollapsedGroups removeAllIndexes];
123}
124
125- (void) saveCollapsedGroups
126{
127    [fDefaults setObject: [NSArchiver archivedDataWithRootObject: fCollapsedGroups] forKey: @"CollapsedGroups"];
128}
129
130- (BOOL) outlineView: (NSOutlineView *) outlineView isGroupItem: (id) item
131{
132    return ![item isKindOfClass: [Torrent class]];
133}
134
135- (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
136{
137    return [item isKindOfClass: [Torrent class]] ? [self rowHeight] : GROUP_SEPARATOR_HEIGHT;
138}
139
140- (NSCell *) outlineView: (NSOutlineView *) outlineView dataCellForTableColumn: (NSTableColumn *) tableColumn item: (id) item
141{
142    const BOOL group = ![item isKindOfClass: [Torrent class]];
143    if (!tableColumn)
144        return !group ? fTorrentCell : nil;
145    else
146        return group ? [tableColumn dataCellForRow: [self rowForItem: item]] : nil;
147}
148
149- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
150    item: (id) item
151{
152    if ([item isKindOfClass: [Torrent class]])
153    {
154        [cell setRepresentedObject: item];
155       
156        const NSInteger row = [self rowForItem: item];
157        [cell setControlHover: row == fMouseControlRow];
158        [cell setRevealHover: row == fMouseRevealRow];
159        [cell setActionHover: row == fMouseActionRow];
160        [cell setActionPushed: row == fActionPushedRow];
161    }
162    else
163    {
164        NSString * ident = [tableColumn identifier];
165        if ([ident isEqualToString: @"UL Image"] || [ident isEqualToString: @"DL Image"])
166        {
167            //ensure arrows are white only when selected
168            if ([NSApp isOnSnowLeopardOrBetter])
169                [[cell image] setTemplate: [cell backgroundStyle] == NSBackgroundStyleLowered];
170            else
171            {
172                NSImage * image = [cell image];
173                const BOOL template = [cell backgroundStyle] == NSBackgroundStyleLowered;
174                if ([image isTemplate] != template)
175                {
176                    [image setTemplate: template];
177                    [cell setImage: nil];
178                    [cell setImage: image];
179                }
180            }
181        }
182    }
183}
184
185- (NSRect) frameOfCellAtColumn: (NSInteger) column row: (NSInteger) row
186{
187    if (column == -1)
188        return [self rectOfRow: row];
189    else
190    {
191        NSRect rect = [super frameOfCellAtColumn: column row: row];
192       
193        //adjust placement for proper vertical alignment
194        if (column == [self columnWithIdentifier: @"Group"])
195            rect.size.height -= 1.0f;
196       
197        return rect;
198    }
199}
200
201- (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item
202{
203    return [item isKindOfClass: [Torrent class]] ? [item name]
204            : [[self preparedCellAtColumn: [self columnWithIdentifier: @"Group"] row: [self rowForItem: item]] stringValue];
205}
206
207- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
208                tableColumn: (NSTableColumn *) column item: (id) item mouseLocation: (NSPoint) mouseLocation
209{
210    NSString * ident = [column identifier];
211    if ([ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"])
212        return NSLocalizedString(@"Download speed", "Torrent table -> group row -> tooltip");
213    else if ([ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"])
214        return [fDefaults boolForKey: @"DisplayGroupRowRatio"] ? NSLocalizedString(@"Ratio", "Torrent table -> group row -> tooltip")
215                : NSLocalizedString(@"Upload speed", "Torrent table -> group row -> tooltip");
216    else if (ident)
217    {
218        NSInteger count = [[item torrents] count];
219        if (count == 1)
220            return NSLocalizedString(@"1 transfer", "Torrent table -> group row -> tooltip");
221        else
222            return [NSString stringWithFormat: NSLocalizedString(@"%d transfers", "Torrent table -> group row -> tooltip"), count];
223    }
224    else
225        return nil;
226}
227
228- (void) updateTrackingAreas
229{
230    [super updateTrackingAreas];
231    [self removeButtonTrackingAreas];
232   
233    NSRange rows = [self rowsInRect: [self visibleRect]];
234    if (rows.length == 0)
235        return;
236   
237    NSPoint mouseLocation = [self convertPoint: [[self window] convertScreenToBase: [NSEvent mouseLocation]] fromView: nil];
238    for (NSUInteger row = rows.location; row < NSMaxRange(rows); row++)
239    {
240        if (![[self itemAtRow: row] isKindOfClass: [Torrent class]])
241            continue;
242       
243        NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: row] forKey: @"Row"];
244        TorrentCell * cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
245        [cell addTrackingAreasForView: self inRect: [self rectOfRow: row] withUserInfo: userInfo mouseLocation: mouseLocation];
246    }
247}
248
249- (void) removeButtonTrackingAreas
250{
251    fMouseControlRow = -1;
252    fMouseRevealRow = -1;
253    fMouseActionRow = -1;
254   
255    for (NSTrackingArea * area in [self trackingAreas])
256    {
257        if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
258            [self removeTrackingArea: area];
259    }
260}
261
262- (void) setControlButtonHover: (NSInteger) row
263{
264    fMouseControlRow = row;
265    if (row >= 0)
266        [self setNeedsDisplayInRect: [self rectOfRow: row]];
267}
268
269- (void) setRevealButtonHover: (NSInteger) row
270{
271    fMouseRevealRow = row;
272    if (row >= 0)
273        [self setNeedsDisplayInRect: [self rectOfRow: row]];
274}
275
276- (void) setActionButtonHover: (NSInteger) row
277{
278    fMouseActionRow = row;
279    if (row >= 0)
280        [self setNeedsDisplayInRect: [self rectOfRow: row]];
281}
282
283- (void) mouseEntered: (NSEvent *) event
284{
285    NSDictionary * dict = (NSDictionary *)[event userData];
286   
287    NSNumber * row;
288    if ((row = [dict objectForKey: @"Row"]))
289    {
290        NSInteger rowVal = [row intValue];
291        NSString * type = [dict objectForKey: @"Type"];
292        if ([type isEqualToString: @"Action"])
293            fMouseActionRow = rowVal;
294        else if ([type isEqualToString: @"Control"])
295            fMouseControlRow = rowVal;
296        else
297            fMouseRevealRow = rowVal;
298       
299        [self setNeedsDisplayInRect: [self rectOfRow: rowVal]];
300    }
301}
302
303- (void) mouseExited: (NSEvent *) event
304{
305    NSDictionary * dict = (NSDictionary *)[event userData];
306   
307    NSNumber * row;
308    if ((row = [dict objectForKey: @"Row"]))
309    {
310        NSString * type = [dict objectForKey: @"Type"];
311        if ([type isEqualToString: @"Action"])
312            fMouseActionRow = -1;
313        else if ([type isEqualToString: @"Control"])
314            fMouseControlRow = -1;
315        else
316            fMouseRevealRow = -1;
317       
318        [self setNeedsDisplayInRect: [self rectOfRow: [row intValue]]];
319    }
320}
321
322- (void) outlineViewSelectionIsChanging: (NSNotification *) notification
323{
324    if (fSelectedValues)
325        [self selectValues: fSelectedValues];
326}
327
328- (void) outlineViewItemDidExpand: (NSNotification *) notification
329{
330    NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
331    if (value < 0)
332        value = MAX_GROUP;
333   
334    if ([fCollapsedGroups containsIndex: value])
335    {
336        [fCollapsedGroups removeIndex: value];
337        [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
338    }
339}
340
341- (void) outlineViewItemDidCollapse: (NSNotification *) notification
342{
343    NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
344    if (value < 0)
345        value = MAX_GROUP;
346   
347    [fCollapsedGroups addIndex: value];
348    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
349}
350
351- (void) mouseDown: (NSEvent *) event
352{
353    NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
354    const NSInteger row = [self rowAtPoint: point];
355   
356    //check to toggle group status before anything else
357    if ([self pointInGroupStatusRect: point])
358    {
359        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
360        [self setGroupStatusColumns];
361       
362        return;
363    }
364   
365    const BOOL pushed = row != -1 && (fMouseActionRow == row || fMouseRevealRow == row || fMouseControlRow == row);
366   
367    //if pushing a button, don't change the selected rows
368    if (pushed)
369        fSelectedValues = [[self selectedValues] retain];
370   
371    [super mouseDown: event];
372   
373    [fSelectedValues release];
374    fSelectedValues = nil;
375   
376    //avoid weird behavior when showing menu by doing this after mouse down
377    if (row != -1 && fMouseActionRow == row)
378    {
379        fActionPushedRow = row;
380        [self setNeedsDisplayInRect: [self rectOfRow: row]]; //ensure button is pushed down
381       
382        [self displayTorrentMenuForEvent: event];
383       
384        fActionPushedRow = -1;
385        [self setNeedsDisplayInRect: [self rectOfRow: row]];
386    }
387    else if (!pushed && [event clickCount] == 2) //double click
388    {
389        id item = nil;
390        if (row != -1)
391            item = [self itemAtRow: row];
392       
393        if (!item || [item isKindOfClass: [Torrent class]])
394            [fController showInfo: nil];
395        else
396        {
397            if ([self isItemExpanded: item])
398                [self collapseItem: item];
399            else
400                [self expandItem: item];
401        }
402    }
403    else;
404}
405
406- (void) selectValues: (NSArray *) values
407{
408    NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
409   
410    for (id item in values)
411    {
412        if ([item isKindOfClass: [Torrent class]])
413        {
414            NSInteger index = [self rowForItem: item];
415            if (index != -1)
416                [indexSet addIndex: index];
417        }
418        else
419        {
420            NSInteger group = [item groupIndex];
421            for (NSInteger i = 0; i < [self numberOfRows]; i++)
422            {
423                if ([indexSet containsIndex: i])
424                    continue;
425               
426                id tableItem = [self itemAtRow: i];
427                if (![tableItem isKindOfClass: [Torrent class]] && group == [tableItem groupIndex])
428                {
429                    [indexSet addIndex: i];
430                    break;
431                }
432            }
433        }
434    }
435   
436    [self selectRowIndexes: indexSet byExtendingSelection: NO];
437}
438
439- (NSArray *) selectedValues
440{
441    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
442    NSMutableArray * values = [NSMutableArray arrayWithCapacity: [selectedIndexes count]];
443   
444    for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
445        [values addObject: [self itemAtRow: i]];
446   
447    return values;
448}
449
450- (NSArray *) selectedTorrents
451{
452    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
453    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [selectedIndexes count]]; //take a shot at guessing capacity
454   
455    for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
456    {
457        id item = [self itemAtRow: i];
458        if ([item isKindOfClass: [Torrent class]])
459            [torrents addObject: item];
460        else
461        {
462            NSArray * groupTorrents = [item torrents];
463            [torrents addObjectsFromArray: groupTorrents];
464            if ([self isItemExpanded: item])
465                i +=[groupTorrents count];
466        }
467    }
468   
469    return torrents;
470}
471
472- (NSMenu *) menuForEvent: (NSEvent *) event
473{
474    NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
475    if (row >= 0)
476    {
477        if (![self isRowSelected: row])
478            [self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
479        return fContextRow;
480    }
481    else
482    {
483        [self deselectAll: self];
484        return fContextNoRow;
485    }
486}
487
488//make sure that the pause buttons become orange when holding down the option key
489- (void) flagsChanged: (NSEvent *) event
490{
491    [self display];
492    [super flagsChanged: event];
493}
494
495//option-command-f will focus the filter bar's search field
496- (void) keyDown: (NSEvent *) event
497{
498    const unichar firstChar = [[event charactersIgnoringModifiers] characterAtIndex: 0];
499   
500    if (firstChar == 'f' && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
501        [fController focusFilterField];
502    else if (firstChar == ' ')
503        [fController toggleQuickLook: nil];
504    else
505        [super keyDown: event];
506}
507
508- (NSRect) iconRectForRow: (NSInteger) row
509{
510    return [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
511}
512
513#warning catch string urls?
514- (void) paste: (id) sender
515{
516    NSURL * url;
517    if ((url = [NSURL URLFromPasteboard: [NSPasteboard generalPasteboard]]))
518        [fController openURL: [url absoluteString]];
519}
520
521- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
522{
523    SEL action = [menuItem action];
524   
525    if (action == @selector(paste:))
526        return [[[NSPasteboard generalPasteboard] types] containsObject: NSURLPboardType];
527   
528    return YES;
529}
530
531- (void) toggleControlForTorrent: (Torrent *) torrent
532{
533    if ([torrent isActive])
534        [fController stopTorrents: [NSArray arrayWithObject: torrent]];
535    else
536    {
537        if (([NSApp isOnSnowLeopardOrBetter] ? [NSEvent modifierFlags] : [[NSApp currentEvent] modifierFlags]) & NSAlternateKeyMask)
538            [fController resumeTorrentsNoWait: [NSArray arrayWithObject: torrent]];
539        else if ([torrent waitingToStart])
540            [fController stopTorrents: [NSArray arrayWithObject: torrent]];
541        else
542            [fController resumeTorrents: [NSArray arrayWithObject: torrent]];
543    }
544}
545
546- (void) displayTorrentMenuForEvent: (NSEvent *) event
547{
548    const NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
549    if (row < 0)
550        return;
551   
552    const NSInteger numberOfNonFileItems = [fActionMenu numberOfItems];
553   
554    //update file action menu
555    fMenuTorrent = [[self itemAtRow: row] retain];
556   
557    //show/hide the file divider
558    const BOOL isFolder = [fMenuTorrent isFolder];
559    [[fActionMenu itemAtIndex: numberOfNonFileItems-1] setHidden: !isFolder];
560   
561    if (isFolder)
562        [self createFileMenu: fActionMenu forFiles: [fMenuTorrent fileList]];
563   
564    //update global limit check
565    [fGlobalLimitItem setState: [fMenuTorrent usesGlobalSpeedLimit] ? NSOnState : NSOffState];
566   
567    //place menu below button
568    NSRect rect = [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
569    NSPoint location = rect.origin;
570    location.y += rect.size.height + 5.0;
571   
572    if ([NSApp isOnSnowLeopardOrBetter])
573    {
574        location = [self convertPoint: location toView: self];
575        [fActionMenu popUpMenuPositioningItem: nil atLocation: location inView: self];
576    }
577    else
578    {
579        location = [self convertPoint: location toView: nil];
580        NSEvent * newEvent = [NSEvent mouseEventWithType: [event type] location: location
581            modifierFlags: [event modifierFlags] timestamp: [event timestamp] windowNumber: [event windowNumber]
582            context: [event context] eventNumber: [event eventNumber] clickCount: [event clickCount] pressure: [event pressure]];
583       
584        [NSMenu popUpContextMenu: fActionMenu withEvent: newEvent forView: self];
585    }
586   
587    for (NSInteger i = [fActionMenu numberOfItems]-1; i >= numberOfNonFileItems; i--)
588        [fActionMenu removeItemAtIndex: i];
589   
590    [fMenuTorrent release];
591    fMenuTorrent = nil;
592}
593
594- (void) menuNeedsUpdate: (NSMenu *) menu
595{
596    //this method seems to be called when it shouldn't be
597    if (!fMenuTorrent || ![menu supermenu])
598        return;
599   
600    if (menu == fUploadMenu || menu == fDownloadMenu)
601    {
602        NSMenuItem * item;
603        if ([menu numberOfItems] == 3)
604        {
605            const NSInteger speedLimitActionValue[] = { 0, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500,
606                                                        750, 1000, 1500, 2000, -1 };
607           
608            for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
609            {
610                item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
611                        "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimit:)
612                        keyEquivalent: @""];
613                [item setTarget: self];
614                [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
615                [menu addItem: item];
616                [item release];
617            }
618        }
619       
620        const BOOL upload = menu == fUploadMenu;
621        const BOOL limit = [fMenuTorrent usesSpeedLimit: upload];
622       
623        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
624        [item setState: limit ? NSOnState : NSOffState];
625        [item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
626                            "torrent action menu -> upload/download limit"), [fMenuTorrent speedLimit: upload]]];
627       
628        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
629        [item setState: !limit ? NSOnState : NSOffState];
630    }
631    else if (menu == fRatioMenu)
632    {
633        NSMenuItem * item;
634        if ([menu numberOfItems] == 4)
635        {
636            const CGFloat ratioLimitActionValue[] = { 0.25f, 0.5f, 0.75f, 1.0f, 1.5f, 2.0f, 3.0f, -1.0f };
637           
638            for (NSInteger i = 0; ratioLimitActionValue[i] != -1.0f; i++)
639            {
640                item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
641                        action: @selector(setQuickRatio:) keyEquivalent: @""];
642                [item setTarget: self];
643                [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
644                [menu addItem: item];
645                [item release];
646            }
647        }
648       
649        const tr_ratiolimit mode = [fMenuTorrent ratioSetting];
650       
651        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
652        [item setState: mode == TR_RATIOLIMIT_SINGLE ? NSOnState : NSOffState];
653        [item setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
654            "torrent action menu -> ratio stop"), [fMenuTorrent ratioLimit]]];
655       
656        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
657        [item setState: mode == TR_RATIOLIMIT_UNLIMITED ? NSOnState : NSOffState];
658       
659        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
660        [item setState: mode == TR_RATIOLIMIT_GLOBAL ? NSOnState : NSOffState];
661    }
662    else if (menu == fPriorityMenu)
663    {
664        const tr_priority_t priority = [fMenuTorrent priority];
665       
666        NSMenuItem * item = [menu itemWithTag: ACTION_MENU_PRIORITY_HIGH_TAG];
667        [item setState: priority == TR_PRI_HIGH ? NSOnState : NSOffState];
668       
669        item = [menu itemWithTag: ACTION_MENU_PRIORITY_NORMAL_TAG];
670        [item setState: priority == TR_PRI_NORMAL ? NSOnState : NSOffState];
671       
672        item = [menu itemWithTag: ACTION_MENU_PRIORITY_LOW_TAG];
673        [item setState: priority == TR_PRI_LOW ? NSOnState : NSOffState];
674    }
675    else //assume the menu is part of the file list
676    {
677        if ([menu numberOfItems] > 0)
678            return;
679       
680        NSMenu * supermenu = [menu supermenu];
681        [self createFileMenu: menu forFiles: [(FileListNode *)[[supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: menu]]
682                                                representedObject] children]];
683    }
684}
685
686//alternating rows - first row after group row is white
687- (void) highlightSelectionInClipRect: (NSRect) clipRect
688{
689    NSRect visibleRect = clipRect;
690    NSRange rows = [self rowsInRect: visibleRect];
691    BOOL start = YES;
692   
693    const CGFloat totalRowHeight = [self rowHeight] + [self intercellSpacing].height;
694   
695    NSRect gridRects[(NSInteger)(ceil(visibleRect.size.height / totalRowHeight / 2.0)) + 1]; //add one if partial rows at top and bottom
696    NSInteger rectNum = 0;
697   
698    if (rows.length > 0)
699    {
700        //determine what the first row color should be
701        if ([[self itemAtRow: rows.location] isKindOfClass: [Torrent class]])
702        {
703            for (NSInteger i = rows.location-1; i>=0; i--)
704            {
705                if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
706                    break;
707                start = !start;
708            }
709        }
710        else
711        {
712            rows.location++;
713            rows.length--;
714        }
715       
716        NSInteger i;
717        for (i = rows.location; i < NSMaxRange(rows); i++)
718        {
719            if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
720            {
721                start = YES;
722                continue;
723            }
724           
725            if (!start && ![self isRowSelected: i])
726                gridRects[rectNum++] = [self rectOfRow: i];
727           
728            start = !start;
729        }
730       
731        const CGFloat newY = NSMaxY([self rectOfRow: i-1]);
732        visibleRect.size.height -= newY - visibleRect.origin.y;
733        visibleRect.origin.y = newY;
734    }
735   
736    const NSInteger numberBlankRows = ceil(visibleRect.size.height / totalRowHeight);
737   
738    //remaining visible rows continue alternating
739    visibleRect.size.height = totalRowHeight;
740    if (start)
741        visibleRect.origin.y += totalRowHeight;
742   
743    for (NSInteger i = start ? 1 : 0; i < numberBlankRows; i += 2)
744    {
745        gridRects[rectNum++] = visibleRect;
746        visibleRect.origin.y += 2.0 * totalRowHeight;
747    }
748   
749    NSAssert([[NSColor controlAlternatingRowBackgroundColors] count] >= 2, @"There should be 2 alternating row colors");
750   
751    [[[NSColor controlAlternatingRowBackgroundColors] objectAtIndex: 1] set];
752    NSRectFillList(gridRects, rectNum);
753   
754    [super highlightSelectionInClipRect: clipRect];
755}
756
757- (void) setQuickLimitMode: (id) sender
758{
759    const BOOL limit = [sender tag] == ACTION_MENU_LIMIT_TAG;
760    [fMenuTorrent setUseSpeedLimit: limit upload: [sender menu] == fUploadMenu];
761   
762    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
763}
764
765- (void) setQuickLimit: (id) sender
766{
767    const BOOL upload = [sender menu] == fUploadMenu;
768    [fMenuTorrent setUseSpeedLimit: YES upload: upload];
769    [fMenuTorrent setSpeedLimit: [[sender representedObject] intValue] upload: upload];
770   
771    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
772}
773
774- (void) setGlobalLimit: (id) sender
775{
776    [fMenuTorrent setUseGlobalSpeedLimit: [sender state] != NSOnState];
777   
778    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
779}
780
781- (void) setQuickRatioMode: (id) sender
782{
783    tr_ratiolimit mode;
784    switch ([sender tag])
785    {
786        case ACTION_MENU_UNLIMITED_TAG:
787            mode = TR_RATIOLIMIT_UNLIMITED;
788            break;
789        case ACTION_MENU_LIMIT_TAG:
790            mode = TR_RATIOLIMIT_SINGLE;
791            break;
792        case ACTION_MENU_GLOBAL_TAG:
793            mode = TR_RATIOLIMIT_GLOBAL;
794            break;
795        default:
796            return;
797    }
798   
799    [fMenuTorrent setRatioSetting: mode];
800   
801    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
802}
803
804- (void) setQuickRatio: (id) sender
805{
806    [fMenuTorrent setRatioSetting: TR_RATIOLIMIT_SINGLE];
807    [fMenuTorrent setRatioLimit: [[sender representedObject] floatValue]];
808   
809    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
810}
811
812- (void) setPriority: (id) sender
813{
814    tr_priority_t priority;
815    switch ([sender tag])
816    {
817        case ACTION_MENU_PRIORITY_HIGH_TAG:
818            priority = TR_PRI_HIGH;
819            break;
820        case ACTION_MENU_PRIORITY_NORMAL_TAG:
821            priority = TR_PRI_NORMAL;
822            break;
823        case ACTION_MENU_PRIORITY_LOW_TAG:
824            priority = TR_PRI_LOW;
825            break;
826        default:
827            NSAssert1(NO, @"Unknown priority: %d", [sender tag]);
828    }
829   
830    [fMenuTorrent setPriority: priority];
831   
832    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
833}
834
835- (void) checkFile: (id) sender
836{
837    NSIndexSet * indexSet = [(FileListNode *)[sender representedObject] indexes];
838    [fMenuTorrent setFileCheckState: [sender state] != NSOnState ? NSOnState : NSOffState forIndexes: indexSet];
839   
840    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
841}
842
843- (void) togglePiecesBar
844{
845    //stop previous animation
846    if (fPiecesBarAnimation)
847        [fPiecesBarAnimation release];
848   
849    NSMutableArray * progressMarks = [NSMutableArray arrayWithCapacity: 16];
850    for (NSAnimationProgress i = 0.0625f; i <= 1.0f; i += 0.0625f)
851        [progressMarks addObject: [NSNumber numberWithFloat: i]];
852   
853    fPiecesBarAnimation = [[NSAnimation alloc] initWithDuration: TOGGLE_PROGRESS_SECONDS animationCurve: NSAnimationEaseIn];
854    [fPiecesBarAnimation setAnimationBlockingMode: NSAnimationNonblocking];
855    [fPiecesBarAnimation setProgressMarks: progressMarks];
856    [fPiecesBarAnimation setDelegate: self];
857   
858    [fPiecesBarAnimation startAnimation];
859}
860
861- (void) animationDidEnd: (NSAnimation *) animation
862{
863    if (animation == fPiecesBarAnimation)
864    {
865        [fPiecesBarAnimation release];
866        fPiecesBarAnimation = nil;
867    }
868}
869
870- (void) animation: (NSAnimation *) animation didReachProgressMark: (NSAnimationProgress) progress
871{
872    if (animation == fPiecesBarAnimation)
873    {
874        if ([fDefaults boolForKey: @"PiecesBar"])
875            fPiecesBarPercent = progress;
876        else
877            fPiecesBarPercent = 1.0f - progress;
878       
879        [self reloadData];
880    }
881}
882
883- (CGFloat) piecesBarPercent
884{
885    return fPiecesBarPercent;
886}
887
888@end
889
890@implementation TorrentTableView (Private)
891
892- (BOOL) pointInGroupStatusRect: (NSPoint) point
893{
894    NSInteger row = [self rowAtPoint: point];
895    if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
896        return NO;
897   
898    NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
899    return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
900            || [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
901}
902
903- (void) setGroupStatusColumns
904{
905    const BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
906   
907    [[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
908    [[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
909}
910
911- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files
912{
913    for (FileListNode * node in files)
914    {
915        NSString * name = [node name];
916       
917        NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: name action: @selector(checkFile:) keyEquivalent: @""];
918       
919        NSImage * icon;
920        if (![node isFolder])
921            icon = [node icon];
922        else
923        {
924            NSMenu * itemMenu = [[NSMenu alloc] initWithTitle: name];
925            [itemMenu setAutoenablesItems: NO];
926            [item setSubmenu: itemMenu];
927            [itemMenu setDelegate: self];
928            [itemMenu release];
929           
930            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')];
931        }
932       
933        [item setRepresentedObject: node];
934       
935        [icon setSize: NSMakeSize(16.0, 16.0)];
936        [item setImage: icon];
937       
938        NSIndexSet * indexSet = [node indexes];
939        [item setState: [fMenuTorrent checkForFiles: indexSet]];
940        [item setEnabled: [fMenuTorrent canChangeDownloadCheckForFiles: indexSet]];
941       
942        [menu addItem: item];
943        [item release];
944    }
945}
946
947@end
Note: See TracBrowser for help on using the repository browser.