source: trunk/macosx/TorrentTableView.m @ 6274

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

groups are now stored as objects instead of dictionaries

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