source: trunk/macosx/TorrentTableView.m @ 8227

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

fix #1921 Selected collapsed groups do not pause/resume together

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