source: trunk/macosx/TorrentTableView.m @ 6119

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

the pieces bar "slides" using NSAnimation instead of a timer

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