source: trunk/macosx/TorrentTableView.m @ 6044

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

minor efficiency improvements

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