source: trunk/macosx/TorrentTableView.m @ 9283

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

move some #imports out of header files

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