source: trunk/macosx/TorrentTableView.m @ 9134

Last change on this file since 9134 was 9134, checked in by livings124, 13 years ago

remove unnecessary alloca

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