source: trunk/macosx/TorrentTableView.m @ 9487

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

the new way of determining the unwanted width of the progress bar wasn't cutting it, so use the old way

  • Property svn:keywords set to Date Rev Author Id
File size: 30.9 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 9487 2009-11-07 02:22:57Z 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];
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.