source: trunk/macosx/TorrentTableView.m @ 7247

Last change on this file since 7247 was 7247, checked in by livings124, 12 years ago

#1550 Not being able to deselect all transfers by clicking in the empty field

  • Property svn:keywords set to Date Rev Author Id
File size: 31.8 KB
Line 
1/******************************************************************************
2 * $Id: TorrentTableView.m 7247 2008-12-03 00:29:45Z 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 999999
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.0f
40#define GROUP_RATIO_IMAGE_COLUMN_WIDTH 10.0f
41
42#define TOGGLE_PROGRESS_SECONDS 0.175
43
44@interface TorrentTableView (Private)
45
46//unused on Leopard
47- (BOOL) pointInControlRect: (NSPoint) point;
48- (BOOL) pointInRevealRect: (NSPoint) point;
49- (BOOL) pointInActionRect: (NSPoint) point;
50
51- (BOOL) pointInGroupStatusRect: (NSPoint) point;
52
53- (void) setGroupStatusColumns;
54
55- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files;
56
57@end
58
59@implementation TorrentTableView
60
61- (id) initWithCoder: (NSCoder *) decoder
62{
63    if ((self = [super initWithCoder: decoder]))
64    {
65        fDefaults = [NSUserDefaults standardUserDefaults];
66       
67        fTorrentCell = [[TorrentCell alloc] init];
68       
69        if (![NSApp isOnLeopardOrBetter])
70        {
71            NSTableColumn * groupColumn = [self tableColumnWithIdentifier: @"Group"];
72            [self setOutlineTableColumn: groupColumn];
73           
74            //remove all unnecessary columns
75            for (NSInteger 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.0f : 0.0f;
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: (NSInteger) value
127{
128    if (value == -1)
129        value = MAX_GROUP;
130   
131    return [fCollapsedGroups containsIndex: value];
132}
133
134- (void) removeCollapsedGroup: (NSInteger) 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        NSInteger 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.0f;
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        NSInteger 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    for (NSUInteger row = rows.location; row < NSMaxRange(rows); row++)
259    {
260        if (![[self itemAtRow: row] isKindOfClass: [Torrent class]])
261            continue;
262       
263        NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: row] forKey: @"Row"];
264        TorrentCell * cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
265        [cell addTrackingAreasForView: self inRect: [self rectOfRow: row] withUserInfo: userInfo mouseLocation: mouseLocation];
266    }
267}
268
269- (void) removeButtonTrackingAreas
270{
271    fMouseControlRow = -1;
272    fMouseRevealRow = -1;
273    fMouseActionRow = -1;
274   
275    if (![NSApp isOnLeopardOrBetter])
276        return;
277   
278    NSEnumerator * enumerator = [[self trackingAreas] objectEnumerator];
279    NSTrackingArea * area;
280    while ((area = [enumerator nextObject]))
281    {
282        if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
283            [self removeTrackingArea: area];
284    }
285}
286
287- (void) setControlButtonHover: (NSInteger) row
288{
289    fMouseControlRow = row;
290    if (row >= 0)
291        [self setNeedsDisplayInRect: [self rectOfRow: row]];
292}
293
294- (void) setRevealButtonHover: (NSInteger) row
295{
296    fMouseRevealRow = row;
297    if (row >= 0)
298        [self setNeedsDisplayInRect: [self rectOfRow: row]];
299}
300
301- (void) setActionButtonHover: (NSInteger) row
302{
303    fMouseActionRow = row;
304    if (row >= 0)
305        [self setNeedsDisplayInRect: [self rectOfRow: row]];
306}
307
308- (void) mouseEntered: (NSEvent *) event
309{
310    NSDictionary * dict = (NSDictionary *)[event userData];
311   
312    NSNumber * row;
313    if ((row = [dict objectForKey: @"Row"]))
314    {
315        NSInteger rowVal = [row intValue];
316        NSString * type = [dict objectForKey: @"Type"];
317        if ([type isEqualToString: @"Action"])
318            fMouseActionRow = rowVal;
319        else if ([type isEqualToString: @"Control"])
320            fMouseControlRow = rowVal;
321        else
322            fMouseRevealRow = rowVal;
323       
324        [self setNeedsDisplayInRect: [self rectOfRow: rowVal]];
325    }
326}
327
328- (void) mouseExited: (NSEvent *) event
329{
330    NSDictionary * dict = (NSDictionary *)[event userData];
331   
332    NSNumber * row;
333    if ((row = [dict objectForKey: @"Row"]))
334    {
335        NSString * type = [dict objectForKey: @"Type"];
336        if ([type isEqualToString: @"Action"])
337            fMouseActionRow = -1;
338        else if ([type isEqualToString: @"Control"])
339            fMouseControlRow = -1;
340        else
341            fMouseRevealRow = -1;
342       
343        [self setNeedsDisplayInRect: [self rectOfRow: [row intValue]]];
344    }
345}
346
347- (void) outlineViewSelectionIsChanging: (NSNotification *) notification
348{
349    if (fSelectedValues)
350        [self selectValues: fSelectedValues];
351}
352
353- (void) outlineViewItemDidExpand: (NSNotification *) notification
354{
355    NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
356    if (value < 0)
357        value = MAX_GROUP;
358   
359    if ([fCollapsedGroups containsIndex: value])
360    {
361        [fCollapsedGroups removeIndex: value];
362        [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
363    }
364}
365
366- (void) outlineViewItemDidCollapse: (NSNotification *) notification
367{
368    NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
369    if (value < 0)
370        value = MAX_GROUP;
371   
372    [fCollapsedGroups addIndex: value];
373    [[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
374}
375
376- (void) mouseDown: (NSEvent *) event
377{
378    NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
379    const NSInteger row = [self rowAtPoint: point];
380   
381    //check to toggle group status before anything else
382    if ([self pointInGroupStatusRect: point])
383    {
384        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
385        [self setGroupStatusColumns];
386       
387        return;
388    }
389   
390    BOOL pushed;
391    if ([NSApp isOnLeopardOrBetter])
392        pushed = row != -1 && (fMouseActionRow == row || fMouseRevealRow == row || fMouseControlRow == row);
393    else
394        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 ([NSApp isOnLeopardOrBetter] ? row != -1 && fMouseActionRow == row : [self pointInActionRect: 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        if (row != -1)
420            item = [self itemAtRow: row];
421       
422        if (!item || [item isKindOfClass: [Torrent class]])
423            [fController showInfo: nil];
424        else
425        {
426            if ([self isItemExpanded: item])
427                [self collapseItem: item];
428            else
429                [self expandItem: item];
430        }
431    }
432    else;
433}
434
435- (void) selectValues: (NSArray *) values
436{
437    NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
438   
439    NSEnumerator * enumerator = [values objectEnumerator];
440    id item;
441    while ((item = [enumerator nextObject]))
442    {
443        if ([item isKindOfClass: [Torrent class]])
444        {
445            NSInteger index = [self rowForItem: item];
446            if (index != -1)
447                [indexSet addIndex: index];
448        }
449        else
450        {
451            NSInteger group = [item groupIndex];
452            for (NSInteger i = 0; i < [self numberOfRows]; i++)
453            {
454                if ([indexSet containsIndex: i])
455                    continue;
456               
457                id tableItem = [self itemAtRow: i];
458                if (![tableItem isKindOfClass: [Torrent class]] && group == [tableItem groupIndex])
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    for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
476        [values addObject: [self itemAtRow: i]];
477   
478    return values;
479}
480
481- (NSArray *) selectedTorrents
482{
483    NSIndexSet * selectedIndexes = [self selectedRowIndexes];
484    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [selectedIndexes count]]; //take a shot at guessing capacity
485   
486    for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
487    {
488        id item = [self itemAtRow: i];
489        if ([item isKindOfClass: [Torrent class]])
490            [torrents addObject: item];
491        else
492        {
493            NSArray * groupTorrents = [item torrents];
494            [torrents addObjectsFromArray: groupTorrents];
495            i += [groupTorrents count];
496        }
497    }
498   
499    return torrents;
500}
501
502- (NSMenu *) menuForEvent: (NSEvent *) event
503{
504    NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
505    if (row >= 0)
506    {
507        if (![self isRowSelected: row])
508            [self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
509        return fContextRow;
510    }
511    else
512    {
513        [self deselectAll: self];
514        return fContextNoRow;
515    }
516}
517
518//make sure that the pause buttons become orange when holding down the option key
519- (void) flagsChanged: (NSEvent *) event
520{
521    [self display];
522    [super flagsChanged: event];
523}
524
525//option-command-f will focus the filter bar's search field
526- (void) keyDown: (NSEvent *) event
527{
528    const unichar firstChar = [[event charactersIgnoringModifiers] characterAtIndex: 0];
529   
530    if (firstChar == 'f' && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
531        [fController focusFilterField];
532    else
533    {
534        //handle quicklook
535        if (firstChar == ' ')
536            [[QuickLookController quickLook] toggleQuickLook];
537        else if (firstChar == NSRightArrowFunctionKey)
538            [[QuickLookController quickLook] pressRight];
539        else if (firstChar == NSLeftArrowFunctionKey)
540            [[QuickLookController quickLook] pressLeft];
541        else;
542       
543        [super keyDown: event];
544    }
545}
546
547- (NSRect) iconRectForRow: (NSInteger) row
548{
549    return [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
550}
551
552- (void) paste: (id) sender
553{
554    NSURL * url;
555    if ((url = [NSURL URLFromPasteboard: [NSPasteboard generalPasteboard]]))
556        [fController openURL: url];
557}
558
559- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
560{
561    SEL action = [menuItem action];
562   
563    if (action == @selector(paste:))
564        return [[[NSPasteboard generalPasteboard] types] containsObject: NSURLPboardType];
565   
566    return YES;
567}
568
569- (void) toggleControlForTorrent: (Torrent *) torrent
570{
571    if ([torrent isActive])
572        [fController stopTorrents: [NSArray arrayWithObject: torrent]];
573    else
574    {
575        if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
576            [fController resumeTorrentsNoWait: [NSArray arrayWithObject: torrent]];
577        else if ([torrent waitingToStart])
578            [fController stopTorrents: [NSArray arrayWithObject: torrent]];
579        else
580            [fController resumeTorrents: [NSArray arrayWithObject: torrent]];
581    }
582}
583
584- (void) displayTorrentMenuForEvent: (NSEvent *) event
585{
586    NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
587    if (row < 0)
588        return;
589   
590    NSInteger numberOfNonFileItems = [fActionMenu numberOfItems];
591   
592    //update file action menu
593    fMenuTorrent = [[self itemAtRow: row] retain];
594    [self createFileMenu: fActionMenu forFiles: [fMenuTorrent fileList]];
595   
596    //place menu below button
597    NSRect rect = [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
598    NSPoint location = rect.origin;
599    location.y += rect.size.height + 5.0f;
600    location = [self convertPoint: location toView: nil];
601   
602    NSEvent * newEvent = [NSEvent mouseEventWithType: [event type] location: location
603        modifierFlags: [event modifierFlags] timestamp: [event timestamp] windowNumber: [event windowNumber]
604        context: [event context] eventNumber: [event eventNumber] clickCount: [event clickCount] pressure: [event pressure]];
605   
606    [NSMenu popUpContextMenu: fActionMenu withEvent: newEvent forView: self];
607   
608    for (NSInteger i = [fActionMenu numberOfItems]-1; i >= numberOfNonFileItems; i--)
609        [fActionMenu removeItemAtIndex: i];
610   
611    [fMenuTorrent release];
612    fMenuTorrent = nil;
613}
614
615- (void) menuNeedsUpdate: (NSMenu *) menu
616{
617    //this method seems to be called when it shouldn't be
618    if (!fMenuTorrent || ![menu supermenu])
619        return;
620   
621    if (menu == fUploadMenu || menu == fDownloadMenu)
622    {
623        NSMenuItem * item;
624        if ([menu numberOfItems] == 4)
625        {
626            const NSInteger speedLimitActionValue[] = { 0, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, -1 };
627           
628            for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
629            {
630                item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
631                        "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimit:)
632                        keyEquivalent: @""];
633                [item setTarget: self];
634                [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
635                [menu addItem: item];
636                [item release];
637            }
638        }
639       
640        BOOL upload = menu == fUploadMenu;
641        NSInteger mode = [fMenuTorrent speedMode: upload];
642       
643        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
644        [item setState: mode == TR_SPEEDLIMIT_SINGLE ? NSOnState : NSOffState];
645        [item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
646                            "torrent action menu -> upload/download limit"), [fMenuTorrent speedLimit: upload]]];
647       
648        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
649        [item setState: mode == TR_SPEEDLIMIT_UNLIMITED ? NSOnState : NSOffState];
650       
651        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
652        [item setState: mode == TR_SPEEDLIMIT_GLOBAL ? NSOnState : NSOffState];
653    }
654    else if (menu == fRatioMenu)
655    {
656        NSMenuItem * item;
657        if ([menu numberOfItems] == 4)
658        {
659            const CGFloat ratioLimitActionValue[] = { 0.25f, 0.5f, 0.75f, 1.0f, 1.5f, 2.0f, 3.0f, -1.0f };
660           
661            for (NSInteger i = 0; ratioLimitActionValue[i] != -1.0f; i++)
662            {
663                item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
664                        action: @selector(setQuickRatio:) keyEquivalent: @""];
665                [item setTarget: self];
666                [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
667                [menu addItem: item];
668                [item release];
669            }
670        }
671       
672        NSInteger mode = [fMenuTorrent ratioSetting];
673       
674        item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
675        [item setState: mode == NSOnState ? NSOnState : NSOffState];
676        [item setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
677            "torrent action menu -> ratio stop"), [fMenuTorrent ratioLimit]]];
678       
679        item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
680        [item setState: mode == NSOffState ? NSOnState : NSOffState];
681       
682        item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
683        [item setState: mode == NSMixedState ? NSOnState : NSOffState];
684    }
685    else //assume the menu is part of the file list
686    {
687        if ([menu numberOfItems] > 0)
688            return;
689       
690        NSMenu * supermenu = [menu supermenu];
691        [self createFileMenu: menu forFiles: [(FileListNode *)[[supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: menu]]
692                                                representedObject] children]];
693    }
694}
695
696//alternating rows - first row after group row is white
697- (void) highlightSelectionInClipRect: (NSRect) clipRect
698{
699    NSColor * altColor = [[NSColor controlAlternatingRowBackgroundColors] objectAtIndex: 1];
700    [altColor set];
701   
702    NSRect visibleRect = clipRect;
703    NSRange rows = [self rowsInRect: visibleRect];
704    BOOL start = YES;
705   
706    if (rows.length > 0)
707    {
708        //determine what the first row color should be
709        if ([[self itemAtRow: rows.location] isKindOfClass: [Torrent class]])
710        {
711            for (NSInteger i = rows.location-1; i>=0; i--)
712            {
713                if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
714                    break;
715                start = !start;
716            }
717        }
718        else
719        {
720            rows.location++;
721            rows.length--;
722        }
723       
724        NSInteger i;
725        for (i = rows.location; i < NSMaxRange(rows); i++)
726        {
727            if (![[self itemAtRow: i] isKindOfClass: [Torrent class]])
728            {
729                start = YES;
730                continue;
731            }
732           
733            if (!start && ![self isRowSelected: i])
734                NSRectFill([self rectOfRow: i]);
735           
736            start = !start;
737        }
738       
739        CGFloat newY = NSMaxY([self rectOfRow: i-1]);
740        visibleRect.size.height -= newY - visibleRect.origin.y;
741        visibleRect.origin.y = newY;
742    }
743       
744    //remaining visible rows continue alternating
745    const CGFloat height = [self rowHeight] + [self intercellSpacing].height;
746    const NSInteger numberOfRects = ceil(visibleRect.size.height / height);
747   
748    visibleRect.size.height = height;
749    if (start)
750        visibleRect.origin.y += height;
751   
752    for (NSInteger i = start ? 1 : 0; i < numberOfRects; i += 2)
753    {
754        NSRectFill(visibleRect);
755        visibleRect.origin.y += 2.0 * height;
756    }
757   
758    [super highlightSelectionInClipRect: clipRect];
759}
760
761- (void) setQuickLimitMode: (id) sender
762{
763    NSInteger mode;
764    switch ([sender tag])
765    {
766        case ACTION_MENU_UNLIMITED_TAG:
767            mode = TR_SPEEDLIMIT_UNLIMITED;
768            break;
769        case ACTION_MENU_LIMIT_TAG:
770            mode = TR_SPEEDLIMIT_SINGLE;
771            break;
772        case ACTION_MENU_GLOBAL_TAG:
773            mode = TR_SPEEDLIMIT_GLOBAL;
774            break;
775        default:
776            return;
777    }
778   
779    [fMenuTorrent setSpeedMode: mode upload: [sender menu] == fUploadMenu];
780   
781    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
782}
783
784- (void) setQuickLimit: (id) sender
785{
786    BOOL upload = [sender menu] == fUploadMenu;
787    [fMenuTorrent setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: upload];
788    [fMenuTorrent setSpeedLimit: [[sender representedObject] intValue] upload: upload];
789   
790    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
791}
792
793- (void) setQuickRatioMode: (id) sender
794{
795    NSInteger mode;
796    switch ([sender tag])
797    {
798        case ACTION_MENU_UNLIMITED_TAG:
799            mode = NSOffState;
800            break;
801        case ACTION_MENU_LIMIT_TAG:
802            mode = NSOnState;
803            break;
804        case ACTION_MENU_GLOBAL_TAG:
805            mode = NSMixedState;
806            break;
807        default:
808            return;
809    }
810   
811    [fMenuTorrent setRatioSetting: mode];
812   
813    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
814}
815
816- (void) setQuickRatio: (id) sender
817{
818    [fMenuTorrent setRatioSetting: NSOnState];
819    [fMenuTorrent setRatioLimit: [[sender representedObject] floatValue]];
820   
821    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
822}
823
824- (void) checkFile: (id) sender
825{
826    NSIndexSet * indexSet = [(FileListNode *)[sender representedObject] indexes];
827    [fMenuTorrent setFileCheckState: [sender state] != NSOnState ? NSOnState : NSOffState forIndexes: indexSet];
828   
829    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
830}
831
832- (void) moveDataFile: (id) sender
833{
834    [fController moveDataFiles: [NSArray arrayWithObject: fMenuTorrent]];
835}
836
837- (void) togglePiecesBar
838{
839    //stop previous animation
840    if (fPiecesBarAnimation)
841        [fPiecesBarAnimation release];
842   
843    NSMutableArray * progressMarks = [NSMutableArray arrayWithCapacity: 16];
844    for (NSAnimationProgress i = 0.0625f; i <= 1.0f; i += 0.0625f)
845        [progressMarks addObject: [NSNumber numberWithFloat: i]];
846   
847    fPiecesBarAnimation = [[NSAnimation alloc] initWithDuration: TOGGLE_PROGRESS_SECONDS animationCurve: NSAnimationEaseIn];
848    [fPiecesBarAnimation setAnimationBlockingMode: NSAnimationNonblocking];
849    [fPiecesBarAnimation setProgressMarks: progressMarks];
850    [fPiecesBarAnimation setDelegate: self];
851   
852    [fPiecesBarAnimation startAnimation];
853}
854
855- (void) animationDidEnd: (NSAnimation *) animation
856{
857    if (animation == fPiecesBarAnimation)
858    {
859        [fPiecesBarAnimation release];
860        fPiecesBarAnimation = nil;
861    }
862}
863
864- (void) animation: (NSAnimation *) animation didReachProgressMark: (NSAnimationProgress) progress
865{
866    if (animation == fPiecesBarAnimation)
867    {
868        if ([fDefaults boolForKey: @"PiecesBar"])
869            fPiecesBarPercent = progress;
870        else
871            fPiecesBarPercent = 1.0f - progress;
872       
873        [self reloadData];
874    }
875}
876
877- (CGFloat) piecesBarPercent
878{
879    return fPiecesBarPercent;
880}
881
882@end
883
884@implementation TorrentTableView (Private)
885
886- (BOOL) pointInControlRect: (NSPoint) point
887{
888    NSInteger row = [self rowAtPoint: point];
889    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
890        return NO;
891   
892    return NSPointInRect(point, [fTorrentCell controlButtonRectForBounds: [self rectOfRow: row]]);
893}
894
895- (BOOL) pointInRevealRect: (NSPoint) point
896{
897    NSInteger row = [self rowAtPoint: point];
898    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
899        return NO;
900   
901    return NSPointInRect(point, [fTorrentCell revealButtonRectForBounds: [self rectOfRow: row]]);
902}
903
904- (BOOL) pointInActionRect: (NSPoint) point
905{
906    NSInteger row = [self rowAtPoint: point];
907    if (row < 0 || ![[self itemAtRow: row] isKindOfClass: [Torrent class]])
908        return NO;
909   
910    return NSPointInRect(point, [fTorrentCell iconRectForBounds: [self rectOfRow: row]]);
911}
912
913- (BOOL) pointInGroupStatusRect: (NSPoint) point
914{
915    NSInteger row = [self rowAtPoint: point];
916    if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
917        return NO;
918   
919    NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
920    return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
921            || [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
922}
923
924- (void) setGroupStatusColumns
925{
926    BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
927   
928    [[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
929    [[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
930   
931    //change size of image column
932    NSTableColumn * ulImageTableColumn = [self tableColumnWithIdentifier: @"UL Image"];
933    CGFloat oldWidth = [ulImageTableColumn width], newWidth = ratio ? GROUP_RATIO_IMAGE_COLUMN_WIDTH : GROUP_SPEED_IMAGE_COLUMN_WIDTH;
934    if (oldWidth != newWidth)
935    {
936        [ulImageTableColumn setWidth: newWidth];
937       
938        NSTableColumn * groupTableColumn = [self tableColumnWithIdentifier: @"Group"];
939        [groupTableColumn setWidth: [groupTableColumn width] - (newWidth - oldWidth)];
940    }
941}
942
943- (void) createFileMenu: (NSMenu *) menu forFiles: (NSArray *) files
944{
945    NSEnumerator * enumerator = [files objectEnumerator];
946    FileListNode * node;
947    while ((node = [enumerator nextObject]))
948    {
949        NSString * name = [node name];
950       
951        NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: name action: @selector(checkFile:) keyEquivalent: @""];
952       
953        NSImage * icon;
954        if (![node isFolder])
955            icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
956        else
957        {
958            NSMenu * itemMenu = [[NSMenu alloc] initWithTitle: name];
959            [itemMenu setAutoenablesItems: NO];
960            [item setSubmenu: itemMenu];
961            [itemMenu setDelegate: self];
962            [itemMenu release];
963           
964            icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')];
965        }
966       
967        [item setRepresentedObject: node];
968       
969        [icon setScalesWhenResized: YES];
970        [icon setSize: NSMakeSize(16.0, 16.0)];
971        [item setImage: icon];
972       
973        NSIndexSet * indexSet = [node indexes];
974        [item setState: [fMenuTorrent checkForFiles: indexSet]];
975        [item setEnabled: [fMenuTorrent canChangeDownloadCheckForFiles: indexSet]];
976       
977        [menu addItem: item];
978        [item release];
979    }
980}
981
982@end
Note: See TracBrowser for help on using the repository browser.