source: trunk/macosx/TorrentTableView.m @ 5935

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

Option to only show the add window when manually adding transfers

  • Property svn:keywords set to Date Rev Author Id
File size: 31.2 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 5935 2008-05-26 21:39: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 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        if ([[tableColumn identifier] isEqualToString: @"UL Image"] || [[tableColumn identifier] isEqualToString: @"DL Image"])
192        {
193            //ensure arrows are white only when selected
194            NSImage * image = [cell image];
195            BOOL template = [cell backgroundStyle] == NSBackgroundStyleLowered;
196            if ([image isTemplate] != template)
197            {
198                [image setTemplate: template];
199                [cell setImage: nil];
200                [cell setImage: image];
201            }
202        }
203    }
204}
205
206- (NSRect) frameOfCellAtColumn: (NSInteger) column row: (NSInteger) row
207{
208    if (column == -1 || ![NSApp isOnLeopardOrBetter])
209        return [self rectOfRow: row];
210    else
211    {
212        NSRect rect = [super frameOfCellAtColumn: column row: row];
213       
214        //adjust placement for proper vertical alignment
215        if (column == [self columnWithIdentifier: @"Group"])
216            rect.size.height -= 1.0;
217       
218        return rect;
219    }
220}
221
222- (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item
223{
224    return [item isKindOfClass: [Torrent class]] ? [item name]
225            : [[self preparedCellAtColumn: [self columnWithIdentifier: @"Group"] row: [self rowForItem: item]] stringValue];
226}
227
228- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
229                tableColumn: (NSTableColumn *) column item: (id) item mouseLocation: (NSPoint) mouseLocation
230{
231    NSString * ident = [column identifier];
232    if ([ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"])
233        return NSLocalizedString(@"Download speed", "Torrent table -> group row -> tooltip");
234    else if ([ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"])
235        return [fDefaults boolForKey: @"DisplayGroupRowRatio"] ? NSLocalizedString(@"Ratio", "Torrent table -> group row -> tooltip")
236                : NSLocalizedString(@"Upload speed", "Torrent table -> group row -> tooltip");
237    else if (ident)
238    {
239        int count = [[item objectForKey: @"Torrents"] count];
240        if (count == 1)
241            return NSLocalizedString(@"1 transfer", "Torrent table -> group row -> tooltip");
242        else
243            return [NSString stringWithFormat: NSLocalizedString(@"%d transfers", "Torrent table -> group row -> tooltip"), count];
244    }
245    else
246        return nil;
247}
248
249- (void) updateTrackingAreas
250{
251    [super updateTrackingAreas];
252    [self removeButtonTrackingAreas];
253   
254    NSRange rows = [self rowsInRect: [self visibleRect]];
255    if (rows.length == 0)
256        return;
257   
258    NSPoint mouseLocation = [self convertPoint: [[self window] convertScreenToBase: [NSEvent mouseLocation]] fromView: nil];
259   
260    NSUInteger row;
261    for (row = rows.location; row < NSMaxRange(rows); row++)
262    {
263        if (![[self itemAtRow: row] isKindOfClass: [Torrent class]])
264            continue;
265       
266        NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: row] forKey: @"Row"];
267        TorrentCell * cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
268        [cell addTrackingAreasForView: self inRect: [self rectOfRow: row] withUserInfo: userInfo
269                mouseLocation: mouseLocation];
270    }
271}
272
273- (void) removeButtonTrackingAreas
274{
275    fMouseControlRow = -1;
276    fMouseRevealRow = -1;
277    fMouseActionRow = -1;
278   
279    if (![NSApp isOnLeopardOrBetter])
280        return;
281   
282    NSEnumerator * enumerator = [[self trackingAreas] objectEnumerator];
283    NSTrackingArea * area;
284    while ((area = [enumerator nextObject]))
285    {
286        if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
287            [self removeTrackingArea: area];
288    }
289}
290
291- (void) setControlButtonHover: (int) row
292{
293    fMouseControlRow = row;
294    if (row >= 0)
295        [self setNeedsDisplayInRect: [self rectOfRow: row]];
296}
297
298- (void) setRevealButtonHover: (int) row
299{
300    fMouseRevealRow = row;
301    if (row >= 0)
302        [self setNeedsDisplayInRect: [self rectOfRow: row]];
303}
304
305- (void) setActionButtonHover: (int) row
306{
307    fMouseActionRow = row;
308    if (row >= 0)
309        [self setNeedsDisplayInRect: [self rectOfRow: row]];
310}
311
312- (void) mouseEntered: (NSEvent *) event
313{
314    NSDictionary * dict = (NSDictionary *)[event userData];
315   
316    NSNumber * row;
317    if ((row = [dict objectForKey: @"Row"]))
318    {
319        int rowVal = [row intValue];
320        NSString * type = [dict objectForKey: @"Type"];
321        if ([type isEqualToString: @"Action"])
322            fMouseActionRow = rowVal;
323        else if ([type isEqualToString: @"Control"])
324            fMouseControlRow = rowVal;
325        else
326            fMouseRevealRow = rowVal;
327       
328        [self setNeedsDisplayInRect: [self rectOfRow: rowVal]];
329    }
330}
331
332- (void) mouseExited: (NSEvent *) event
333{
334    NSDictionary * dict = (NSDictionary *)[event userData];
335   
336    NSNumber * row;
337    if ((row = [dict objectForKey: @"Row"]))
338    {
339        NSString * type = [dict objectForKey: @"Type"];
340        if ([type isEqualToString: @"Action"])
341            fMouseActionRow = -1;
342        else if ([type isEqualToString: @"Control"])
343            fMouseControlRow = -1;
344        else
345            fMouseRevealRow = -1;
346       
347        [self setNeedsDisplayInRect: [self rectOfRow: [row intValue]]];
348    }
349}
350
351- (void) outlineViewSelectionIsChanging: (NSNotification *) notification
352{
353    if (fSelectedValues)
354        [self selectValues: fSelectedValues];
355}
356
357- (void) outlineViewItemDidExpand: (NSNotification *) notification
358{
359    int value = [[[[notification userInfo] objectForKey: @"NSObject"] objectForKey: @"Group"] intValue];
360    if (value < 0)
361        value = MAX_GROUP;
362   
363    if ([fCollapsedGroups containsIndex: value])
364    {
365        [fCollapsedGroups removeIndex: value];
366        [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
367    }
368}
369
370- (void) outlineViewItemDidCollapse: (NSNotification *) notification
371{
372    int value = [[[[notification userInfo] objectForKey: @"NSObject"] objectForKey: @"Group"] intValue];
373    if (value < 0)
374        value = MAX_GROUP;
375   
376    [fCollapsedGroups addIndex: value];
377    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
378}
379
380- (void) mouseDown: (NSEvent *) event
381{
382    NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
383   
384    //check to toggle group status before anything else
385    if ([self pointInGroupStatusRect: point])
386    {
387        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
388        [self setGroupStatusColumns];
389       
390        return;
391    }
392   
393    BOOL pushed = [self pointInControlRect: point] || [self pointInRevealRect: point] || [self pointInActionRect: point];
394   
395    //if pushing a button, don't change the selected rows
396    if (pushed)
397        fSelectedValues = [[self selectedValues] retain];
398   
399    [super mouseDown: event];
400   
401    [fSelectedValues release];
402    fSelectedValues = nil;
403   
404    //avoid weird behavior when showing menu by doing this after mouse down
405    if ([self pointInActionRect: point])
406    {
407        int row = [self rowAtPoint: point];
408       
409        fActionPushedRow = row;
410        [self setNeedsDisplayInRect: [self rectOfRow: row]]; //ensure button is pushed down
411       
412        [self displayTorrentMenuForEvent: event];
413       
414        fActionPushedRow = -1;
415        [self setNeedsDisplayInRect: [self rectOfRow: row]];
416    }
417    else if (!pushed && [event clickCount] == 2) //double click
418    {
419        id item = nil;
420        int row = [self rowAtPoint: point];
421        if (row != -1)
422            item = [self itemAtRow: row];
423       
424        if (!item || [item isKindOfClass: [Torrent class]])
425            [fController showInfo: nil];
426        else
427        {
428            if ([self isItemExpanded: item])
429                [self collapseItem: item];
430            else
431                [self expandItem: item];
432        }
433    }
434    else;
435}
436
437- (void) selectValues: (NSArray *) values
438{
439    NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
440   
441    NSEnumerator * enumerator = [values objectEnumerator];
442    id item;
443    while ((item = [enumerator nextObject]))
444    {
445        if ([item isKindOfClass: [Torrent class]])
446        {
447            NSUInteger index = [self rowForItem: item];
448            if (index != -1)
449                [indexSet addIndex: index];
450        }
451        else
452        {
453            NSNumber * group = [item objectForKey: @"Group"];
454            int i;
455            for (i = 0; i < [self numberOfRows]; i++)
456            {
457                id tableItem = [self itemAtRow: i];
458                if (![tableItem isKindOfClass: [Torrent class]] && [group isEqualToNumber: [tableItem objectForKey: @"Group"]])
459                {
460                    [indexSet addIndex: i];
461                    break;
462                }
463            }
464        }
465    }
466   
467    [self selectRowIndexes: indexSet byExtendingSelection: NO];
468}
469
470- (NSArray *) selectedValues
471{
472    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
473    NSMutableArray * values = [NSMutableArray arrayWithCapacity: [selectedIndexes count]];
474   
475    NSUInteger i;
476    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
477        [values addObject: [self itemAtRow: i]];
478   
479    return values;
480}
481
482- (NSArray *) selectedTorrents
483{
484    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
485    NSMutableArray * torrents = [NSMutableArray array];
486   
487    NSUInteger i;
488    for (i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
489    {
490        id item = [self itemAtRow: i];
491        if ([item isKindOfClass: [Torrent class]])
492        {
493            if (![torrents containsObject: item])
494                [torrents addObject: item];
495        }
496        else
497            [torrents addObjectsFromArray: [item objectForKey: @"Torrents"]];
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    [self resizePiecesBarIncrement];
845   
846    if (!fPiecesBarTimer)
847    {
848        fPiecesBarTimer = [NSTimer scheduledTimerWithTimeInterval: PIECE_TIME target: self
849                            selector: @selector(resizePiecesBarIncrement) userInfo: nil repeats: YES];
850        [[NSRunLoop currentRunLoop] addTimer: fPiecesBarTimer forMode: NSModalPanelRunLoopMode];
851        [[NSRunLoop currentRunLoop] addTimer: fPiecesBarTimer forMode: NSEventTrackingRunLoopMode];
852    }
853}
854
855- (float) piecesBarPercent
856{
857    return fPiecesBarPercent;
858}
859
860@end
861
862@implementation TorrentTableView (Private)
863
864- (BOOL) pointInControlRect: (NSPoint) point
865{
866    int row = [self rowAtPoint: point];
867    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
868        return NO;
869   
870    return NSPointInRect(point, [fTorrentCell controlButtonRectForBounds: [self rectOfRow: row]]);
871}
872
873- (BOOL) pointInRevealRect: (NSPoint) point
874{
875    int row = [self rowAtPoint: point];
876    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
877        return NO;
878   
879    return NSPointInRect(point, [fTorrentCell revealButtonRectForBounds: [self rectOfRow: row]]);
880}
881
882- (BOOL) pointInActionRect: (NSPoint) point
883{
884    int row = [self rowAtPoint: point];
885    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
886        return NO;
887   
888    return NSPointInRect(point, [fTorrentCell iconRectForBounds: [self rectOfRow: row]]);
889}
890
891- (BOOL) pointInGroupStatusRect: (NSPoint) point
892{
893    int row = [self rowAtPoint: point];
894    if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
895        return NO;
896   
897    NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
898    return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
899            || [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
900}
901
902- (void) setGroupStatusColumns
903{
904    BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
905   
906    [[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
907    [[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
908   
909    //change size of image column
910    NSTableColumn * ulImageTableColumn = [self tableColumnWithIdentifier: @"UL Image"];
911    float oldWidth = [ulImageTableColumn width], newWidth = ratio ? GROUP_RATIO_IMAGE_COLUMN_WIDTH : GROUP_SPEED_IMAGE_COLUMN_WIDTH;
912    if (oldWidth != newWidth)
913    {
914        [ulImageTableColumn setWidth: newWidth];
915       
916        NSTableColumn * groupTableColumn = [self tableColumnWithIdentifier: @"Group"];
917        [groupTableColumn setWidth: [groupTableColumn width] - (newWidth - oldWidth)];
918    }
919}
920
921- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files
922{
923    NSEnumerator * enumerator = [files objectEnumerator];
924    FileListNode * node;
925    while ((node = [enumerator nextObject]))
926    {
927        NSString * name = [node name];
928       
929        NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: name action: @selector(checkFile:) keyEquivalent: @""];
930       
931        NSImage * icon;
932        if (![node isFolder])
933            icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
934        else
935        {
936            NSMenu * itemMenu = [[NSMenu alloc] initWithTitle: name];
937            [itemMenu setAutoenablesItems: NO];
938            [item setSubmenu: itemMenu];
939            [itemMenu setDelegate: self];
940            [itemMenu release];
941           
942            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')];
943        }
944       
945        [item setRepresentedObject: node];
946       
947        [icon setScalesWhenResized: YES];
948        [icon setSize: NSMakeSize(16.0, 16.0)];
949        [item setImage: icon];
950       
951        NSIndexSet * indexSet = [node indexes];
952        [item setState: [fMenuTorrent checkForFiles: indexSet]];
953        [item setEnabled: [fMenuTorrent canChangeDownloadCheckForFiles: indexSet]];
954       
955        [menu addItem: item];
956        [item release];
957    }
958}
959
960- (void) resizePiecesBarIncrement
961{
962    BOOL done;
963    if ([fDefaults boolForKey: @"PiecesBar"])
964    {
965        fPiecesBarPercent = MIN(fPiecesBarPercent + PIECE_CHANGE, 1.0);
966        done = fPiecesBarPercent == 1.0;
967    }
968    else
969    {
970        fPiecesBarPercent = MAX(fPiecesBarPercent - PIECE_CHANGE, 0.0);
971        done = fPiecesBarPercent == 0.0;
972    }
973   
974    if (done)
975    {
976        [fPiecesBarTimer invalidate];
977        fPiecesBarTimer = nil;
978    }
979   
980    [self reloadData];
981}
982
983@end
Note: See TracBrowser for help on using the repository browser.