source: trunk/macosx/TorrentCell.m @ 9311

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

"Reveal the data file in Finder" -> "Show the data file in Finder"

  • Property svn:keywords set to Date Rev Author Id
File size: 30.6 KB
Line 
1/******************************************************************************
2 * $Id: TorrentCell.m 9311 2009-10-15 23:41:28Z livings124 $
3 *
4 * Copyright (c) 2006-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 "TorrentCell.h"
26#import "GroupsController.h"
27#import "NSApplicationAdditions.h"
28#import "NSStringAdditions.h"
29#import "ProgressGradients.h"
30#import "Torrent.h"
31#import "TorrentTableView.h"
32
33#define BAR_HEIGHT 12.0f
34
35#define IMAGE_SIZE_REG 32.0f
36#define IMAGE_SIZE_MIN 16.0f
37#define ERROR_IMAGE_SIZE 20.0f
38
39#define NORMAL_BUTTON_WIDTH 14.0f
40#define ACTION_BUTTON_WIDTH 16.0f
41
42#define PRIORITY_ICON_WIDTH 14.0f
43#define PRIORITY_ICON_HEIGHT 14.0f
44
45//ends up being larger than font height
46#define HEIGHT_TITLE 16.0f
47#define HEIGHT_STATUS 12.0f
48
49#define PADDING_HORIZONTAL 3.0f
50#define PADDING_BETWEEN_IMAGE_AND_TITLE 5.0f
51#define PADDING_BETWEEN_IMAGE_AND_BAR 7.0f
52#define PADDING_BETWEEN_TITLE_AND_PRIORITY 4.0f
53#define PADDING_ABOVE_TITLE 4.0f
54#define PADDING_ABOVE_MIN_STATUS 4.0f
55#define PADDING_BETWEEN_TITLE_AND_MIN_STATUS 2.0f
56#define PADDING_BETWEEN_TITLE_AND_PROGRESS 1.0f
57#define PADDING_BETWEEN_PROGRESS_AND_BAR 2.0f
58#define PADDING_BETWEEN_TITLE_AND_BAR_MIN 3.0f
59#define PADDING_BETWEEN_BAR_AND_STATUS 2.0f
60
61#define PIECES_TOTAL_PERCENT 0.6f
62
63#define MAX_PIECES (18*18)
64
65@interface TorrentCell (Private)
66
67- (void) drawBar: (NSRect) barRect;
68- (void) drawRegularBar: (NSRect) barRect;
69- (void) drawPiecesBar: (NSRect) barRect;
70
71- (NSRect) rectForMinimalStatusWithString: (NSAttributedString *) string inBounds: (NSRect) bounds;
72- (NSRect) rectForTitleWithString: (NSAttributedString *) string basedOnMinimalStatusRect: (NSRect) statusRect
73            inBounds: (NSRect) bounds;
74- (NSRect) rectForProgressWithString: (NSAttributedString *) string inBounds: (NSRect) bounds;
75- (NSRect) rectForStatusWithString: (NSAttributedString *) string inBounds: (NSRect) bounds;
76
77- (NSAttributedString *) attributedTitle;
78- (NSAttributedString *) attributedStatusString: (NSString *) string;
79
80- (NSString *) buttonString;
81- (NSString *) statusString;
82- (NSString *) minimalStatusString;
83
84- (void) drawImage: (NSImage *) image inRect: (NSRect) rect; //use until 10.5 dropped
85
86@end
87
88@implementation TorrentCell
89
90//only called once and the main table is always needed, so don't worry about releasing
91- (id) init
92{
93    if ((self = [super init]))
94        {
95        fDefaults = [NSUserDefaults standardUserDefaults];
96       
97        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
98        [paragraphStyle setLineBreakMode: NSLineBreakByTruncatingTail];
99   
100        fTitleAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
101                                [NSFont messageFontOfSize: 12.0f], NSFontAttributeName,
102                                paragraphStyle, NSParagraphStyleAttributeName, nil];
103        fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
104                                [NSFont messageFontOfSize: 9.0f], NSFontAttributeName,
105                                paragraphStyle, NSParagraphStyleAttributeName, nil];
106        [paragraphStyle release];
107       
108        fBluePieceColor = [[NSColor colorWithCalibratedRed: 0.0f green: 0.4f blue: 0.8f alpha: 1.0f] retain];
109        fBarBorderColor = [[NSColor colorWithCalibratedWhite: 0.0f alpha: 0.2f] retain];
110       
111        fHighPriorityImage = [[NSImage imageNamed: @"PriorityHigh.png"] copy];
112        [fHighPriorityImage setFlipped: YES];
113       
114        fLowPriorityImage = [[NSImage imageNamed: @"PriorityLow.png"] copy];
115        [fLowPriorityImage setFlipped: YES];
116    }
117        return self;
118}
119
120- (NSRect) iconRectForBounds: (NSRect) bounds
121{
122    CGFloat imageSize = [fDefaults boolForKey: @"SmallView"] ? IMAGE_SIZE_MIN : IMAGE_SIZE_REG;
123   
124    NSRect result = bounds;
125    result.origin.x += PADDING_HORIZONTAL;
126    result.origin.y += floorf((result.size.height - imageSize) * 0.5f);
127    result.size = NSMakeSize(imageSize, imageSize);
128   
129    return result;
130}
131
132- (NSRect) titleRectForBounds: (NSRect) bounds
133{
134    return [self rectForTitleWithString: [self attributedTitle]
135            basedOnMinimalStatusRect: [self minimalStatusRectForBounds: bounds] inBounds: bounds];
136}
137
138- (NSRect) minimalStatusRectForBounds: (NSRect) bounds
139{
140    if (![fDefaults boolForKey: @"SmallView"])
141        return NSZeroRect;
142   
143    return [self rectForMinimalStatusWithString: [self attributedStatusString: [self minimalStatusString]]
144            inBounds: bounds];
145}
146
147- (NSRect) progressRectForBounds: (NSRect) bounds
148{
149    if ([fDefaults boolForKey: @"SmallView"])
150        return NSZeroRect;
151   
152    return [self rectForProgressWithString: [self attributedStatusString: [[self representedObject] progressString]]
153            inBounds: bounds];
154}
155
156- (NSRect) barRectForBounds: (NSRect) bounds
157{
158    BOOL minimal = [fDefaults boolForKey: @"SmallView"];
159   
160    NSRect result = bounds;
161    result.size.height = BAR_HEIGHT;
162    result.origin.x += (minimal ? IMAGE_SIZE_MIN : IMAGE_SIZE_REG) + PADDING_BETWEEN_IMAGE_AND_BAR;
163   
164    result.origin.y += PADDING_ABOVE_TITLE + HEIGHT_TITLE;
165    if (minimal)
166        result.origin.y += PADDING_BETWEEN_TITLE_AND_BAR_MIN;
167    else
168        result.origin.y += PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
169   
170    result.size.width = round(NSMaxX(bounds) - result.origin.x - PADDING_HORIZONTAL - 2.0f * (PADDING_HORIZONTAL + NORMAL_BUTTON_WIDTH));
171   
172    return result;
173}
174
175- (NSRect) statusRectForBounds: (NSRect) bounds
176{
177    if ([fDefaults boolForKey: @"SmallView"])
178        return NSZeroRect;
179   
180    return [self rectForStatusWithString: [self attributedStatusString: [self statusString]] inBounds: bounds];
181}
182
183- (NSRect) controlButtonRectForBounds: (NSRect) bounds
184{
185    NSRect result = bounds;
186    result.size.height = NORMAL_BUTTON_WIDTH;
187    result.size.width = NORMAL_BUTTON_WIDTH;
188    result.origin.x = NSMaxX(bounds) - 2.0f * (PADDING_HORIZONTAL + NORMAL_BUTTON_WIDTH);
189   
190    result.origin.y += PADDING_ABOVE_TITLE + HEIGHT_TITLE - (NORMAL_BUTTON_WIDTH - BAR_HEIGHT) * 0.5f;
191    if ([fDefaults boolForKey: @"SmallView"])
192        result.origin.y += PADDING_BETWEEN_TITLE_AND_BAR_MIN;
193    else
194        result.origin.y += PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
195   
196    return result;
197}
198
199- (NSRect) revealButtonRectForBounds: (NSRect) bounds
200{
201    NSRect result = bounds;
202    result.size.height = NORMAL_BUTTON_WIDTH;
203    result.size.width = NORMAL_BUTTON_WIDTH;
204    result.origin.x = NSMaxX(bounds) - (PADDING_HORIZONTAL + NORMAL_BUTTON_WIDTH);
205   
206    result.origin.y += PADDING_ABOVE_TITLE + HEIGHT_TITLE - (NORMAL_BUTTON_WIDTH - BAR_HEIGHT) * 0.5f;
207    if ([fDefaults boolForKey: @"SmallView"])
208        result.origin.y += PADDING_BETWEEN_TITLE_AND_BAR_MIN;
209    else
210        result.origin.y += PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
211   
212    return result;
213}
214
215- (NSRect) actionButtonRectForBounds: (NSRect) bounds
216{
217    NSRect result = [self iconRectForBounds: bounds];
218    if (![fDefaults boolForKey: @"SmallView"])
219    {
220        result.origin.x += (result.size.width - ACTION_BUTTON_WIDTH) * 0.5f;
221        result.origin.y += (result.size.height - ACTION_BUTTON_WIDTH) * 0.5f;
222        result.size.width = ACTION_BUTTON_WIDTH;
223        result.size.height = ACTION_BUTTON_WIDTH;
224    }
225   
226    return result;
227}
228
229- (NSUInteger) hitTestForEvent: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView
230{
231    NSPoint point = [controlView convertPoint: [event locationInWindow] fromView: nil];
232   
233    if (NSMouseInRect(point, [self controlButtonRectForBounds: cellFrame], [controlView isFlipped])
234        || NSMouseInRect(point, [self revealButtonRectForBounds: cellFrame], [controlView isFlipped]))
235        return NSCellHitContentArea | NSCellHitTrackableArea;
236   
237    return NSCellHitContentArea;
238}
239
240+ (BOOL) prefersTrackingUntilMouseUp
241{
242    return YES;
243}
244
245- (BOOL) trackMouse: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView untilMouseUp: (BOOL) flag
246{
247    fTracking = YES;
248   
249    [self setControlView: controlView];
250   
251    NSPoint point = [controlView convertPoint: [event locationInWindow] fromView: nil];
252   
253    NSRect controlRect= [self controlButtonRectForBounds: cellFrame];
254    BOOL checkControl = NSMouseInRect(point, controlRect, [controlView isFlipped]);
255   
256    NSRect revealRect = [self revealButtonRectForBounds: cellFrame];
257    BOOL checkReveal = NSMouseInRect(point, revealRect, [controlView isFlipped]);
258   
259    [(TorrentTableView *)controlView removeButtonTrackingAreas];
260   
261    while ([event type] != NSLeftMouseUp)
262    {
263        point = [controlView convertPoint: [event locationInWindow] fromView: nil];
264       
265        if (checkControl)
266        {
267            BOOL inControlButton = NSMouseInRect(point, controlRect, [controlView isFlipped]);
268            if (fMouseDownControlButton != inControlButton)
269            {
270                fMouseDownControlButton = inControlButton;
271                [controlView setNeedsDisplayInRect: cellFrame];
272            }
273        }
274        else if (checkReveal)
275        {
276            BOOL inRevealButton = NSMouseInRect(point, revealRect, [controlView isFlipped]);
277            if (fMouseDownRevealButton != inRevealButton)
278            {
279                fMouseDownRevealButton = inRevealButton;
280                [controlView setNeedsDisplayInRect: cellFrame];
281            }
282        }
283        else;
284       
285        //send events to where necessary
286        if ([event type] == NSMouseEntered || [event type] == NSMouseExited)
287            [NSApp sendEvent: event];
288        event = [[controlView window] nextEventMatchingMask:
289                    (NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSMouseEnteredMask | NSMouseExitedMask)];
290    }
291   
292    fTracking = NO;
293
294    if (fMouseDownControlButton)
295    {
296        fMouseDownControlButton = NO;
297       
298        [(TorrentTableView *)controlView toggleControlForTorrent: [self representedObject]];
299    }
300    else if (fMouseDownRevealButton)
301    {
302        fMouseDownRevealButton = NO;
303        [controlView setNeedsDisplayInRect: cellFrame];
304       
305        if ([NSApp isOnSnowLeopardOrBetter])
306        {
307            NSURL * file = [NSURL fileURLWithPath: [[self representedObject] dataLocation]];
308            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
309        }
310        else
311            [[NSWorkspace sharedWorkspace] selectFile: [[self representedObject] dataLocation] inFileViewerRootedAtPath: nil];
312    }
313    else;
314   
315    [controlView updateTrackingAreas];
316   
317    return YES;
318}
319
320- (void) addTrackingAreasForView: (NSView *) controlView inRect: (NSRect) cellFrame withUserInfo: (NSDictionary *) userInfo
321            mouseLocation: (NSPoint) mouseLocation
322{
323    NSTrackingAreaOptions options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
324   
325    //control button
326    NSRect controlButtonRect = [self controlButtonRectForBounds: cellFrame];
327    NSTrackingAreaOptions controlOptions = options;
328    if (NSMouseInRect(mouseLocation, controlButtonRect, [controlView isFlipped]))
329    {
330        controlOptions |= NSTrackingAssumeInside;
331        [(TorrentTableView *)controlView setControlButtonHover: [[userInfo objectForKey: @"Row"] intValue]];
332    }
333   
334    NSMutableDictionary * controlInfo = [userInfo mutableCopy];
335    [controlInfo setObject: @"Control" forKey: @"Type"];
336    NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect: controlButtonRect options: controlOptions owner: controlView
337                                userInfo: controlInfo];
338    [controlView addTrackingArea: area];
339    [controlInfo release];
340    [area release];
341   
342    //reveal button
343    NSRect revealButtonRect = [self revealButtonRectForBounds: cellFrame];
344    NSTrackingAreaOptions revealOptions = options;
345    if (NSMouseInRect(mouseLocation, revealButtonRect, [controlView isFlipped]))
346    {
347        revealOptions |= NSTrackingAssumeInside;
348        [(TorrentTableView *)controlView setRevealButtonHover: [[userInfo objectForKey: @"Row"] intValue]];
349    }
350   
351    NSMutableDictionary * revealInfo = [userInfo mutableCopy];
352    [revealInfo setObject: @"Reveal" forKey: @"Type"];
353    area = [[NSTrackingArea alloc] initWithRect: revealButtonRect options: revealOptions owner: controlView userInfo: revealInfo];
354    [controlView addTrackingArea: area];
355    [revealInfo release];
356    [area release];
357   
358    //action button
359    NSRect actionButtonRect = [self iconRectForBounds: cellFrame]; //use the whole icon
360    NSTrackingAreaOptions actionOptions = options;
361    if (NSMouseInRect(mouseLocation, actionButtonRect, [controlView isFlipped]))
362    {
363        actionOptions |= NSTrackingAssumeInside;
364        [(TorrentTableView *)controlView setActionButtonHover: [[userInfo objectForKey: @"Row"] intValue]];
365    }
366   
367    NSMutableDictionary * actionInfo = [userInfo mutableCopy];
368    [actionInfo setObject: @"Action" forKey: @"Type"];
369    area = [[NSTrackingArea alloc] initWithRect: actionButtonRect options: actionOptions owner: controlView userInfo: actionInfo];
370    [controlView addTrackingArea: area];
371    [actionInfo release];
372    [area release];
373}
374
375- (void) setControlHover: (BOOL) hover
376{
377    fHoverControl = hover;
378}
379
380- (void) setRevealHover: (BOOL) hover
381{
382    fHoverReveal = hover;
383}
384
385- (void) setActionHover: (BOOL) hover
386{
387    fHoverAction = hover;
388}
389
390- (void) setActionPushed: (BOOL) pushed
391{
392    fMouseDownActionButton = pushed;
393}
394
395- (void) drawInteriorWithFrame: (NSRect) cellFrame inView: (NSView *) controlView
396{
397    Torrent * torrent = [self representedObject];
398   
399    const BOOL minimal = [fDefaults boolForKey: @"SmallView"];
400   
401    //group coloring
402    NSRect iconRect = [self iconRectForBounds: cellFrame];
403   
404    NSInteger groupValue = [torrent groupValue];
405    if (groupValue != -1)
406    {
407        NSRect groupRect = NSInsetRect(iconRect, -1.0f, -2.0f);
408        if (!minimal)
409        {
410            groupRect.size.height--;
411            groupRect.origin.y--;
412        }
413        const CGFloat radius = minimal ? 3.0f : 6.0f;
414       
415        NSColor * groupColor = [[GroupsController groups] colorForIndex: groupValue],
416                * darkGroupColor = [groupColor blendedColorWithFraction: 0.2f ofColor: [NSColor whiteColor]];
417       
418        //border
419        NSBezierPath * bp = [NSBezierPath bezierPathWithRoundedRect: groupRect xRadius: radius yRadius: radius];
420        [darkGroupColor set];
421        [bp setLineWidth: 2.0f];
422        [bp stroke];
423       
424        //inside
425        bp = [NSBezierPath bezierPathWithRoundedRect: groupRect xRadius: radius yRadius: radius];
426        NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [groupColor blendedColorWithFraction: 0.7f
427                                    ofColor: [NSColor whiteColor]] endingColor: darkGroupColor];
428        [gradient drawInBezierPath: bp angle: 90.0f];
429        [gradient release];
430    }
431   
432    const BOOL error = [torrent isErrorOrWarning];
433   
434    //icon
435    if (!minimal || !(!fTracking && fHoverAction)) //don't show in minimal mode when hovered over
436    {
437        NSImage * icon = (minimal && error) ? [NSImage imageNamed: [NSApp isOnSnowLeopardOrBetter] ? NSImageNameCaution : @"Error.png"]
438                                            : [torrent icon];
439        [self drawImage: icon inRect: iconRect];
440    }
441   
442    //error badge
443    if (error && !minimal)
444    {
445        NSRect errorRect = NSMakeRect(NSMaxX(iconRect) - ERROR_IMAGE_SIZE, NSMaxY(iconRect) - ERROR_IMAGE_SIZE,
446                                        ERROR_IMAGE_SIZE, ERROR_IMAGE_SIZE);
447        [self drawImage: [NSImage imageNamed: [NSApp isOnSnowLeopardOrBetter] ? NSImageNameCaution : @"Error.png"] inRect: errorRect];
448    }
449   
450    //text color
451    NSColor * titleColor, * statusColor;
452    if ([self backgroundStyle] == NSBackgroundStyleDark)
453        titleColor = statusColor = [NSColor whiteColor];
454    else
455    {
456        titleColor = [NSColor controlTextColor];
457        statusColor = [NSColor darkGrayColor];
458    }
459   
460    [fTitleAttributes setObject: titleColor forKey: NSForegroundColorAttributeName];
461    [fStatusAttributes setObject: statusColor forKey: NSForegroundColorAttributeName];
462   
463    //minimal status
464    NSRect minimalStatusRect;
465    if (minimal)
466    {
467        NSAttributedString * minimalString = [self attributedStatusString: [self minimalStatusString]];
468        minimalStatusRect = [self rectForMinimalStatusWithString: minimalString inBounds: cellFrame];
469       
470        [minimalString drawInRect: minimalStatusRect];
471    }
472   
473    //title
474    NSAttributedString * titleString = [self attributedTitle];
475    NSRect titleRect = [self rectForTitleWithString: titleString basedOnMinimalStatusRect: minimalStatusRect inBounds: cellFrame];
476    [titleString drawInRect: titleRect];
477   
478    //priority icon
479    if ([torrent priority] != TR_PRI_NORMAL)
480    {
481        NSImage * priorityImage = [torrent priority] == TR_PRI_HIGH ? fHighPriorityImage : fLowPriorityImage;
482       
483        NSRect priorityRect = NSMakeRect(NSMaxX(titleRect) + PADDING_BETWEEN_TITLE_AND_PRIORITY,
484                                titleRect.origin.y - (PRIORITY_ICON_HEIGHT - titleRect.size.height) / 2.0,
485                                PRIORITY_ICON_WIDTH, PRIORITY_ICON_HEIGHT);
486        [priorityImage drawInRect: priorityRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
487    }
488   
489    //progress
490    if (!minimal)
491    {
492        NSAttributedString * progressString = [self attributedStatusString: [torrent progressString]];
493        NSRect progressRect = [self rectForProgressWithString: progressString inBounds: cellFrame];
494       
495        [progressString drawInRect: progressRect];
496    }
497   
498    //bar
499    [self drawBar: [self barRectForBounds: cellFrame]];
500   
501    //control button
502    NSString * controlImageSuffix;
503    if (fMouseDownControlButton)
504        controlImageSuffix = @"On.png";
505    else if (!fTracking && fHoverControl)
506        controlImageSuffix = @"Hover.png";
507    else
508        controlImageSuffix = @"Off.png";
509   
510    NSImage * controlImage;
511    if ([torrent isActive])
512        controlImage = [NSImage imageNamed: [@"Pause" stringByAppendingString: controlImageSuffix]];
513    else
514    {
515        if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
516            controlImage = [NSImage imageNamed: [@"ResumeNoWait" stringByAppendingString: controlImageSuffix]];
517        else if ([torrent waitingToStart])
518            controlImage = [NSImage imageNamed: [@"Pause" stringByAppendingString: controlImageSuffix]];
519        else
520            controlImage = [NSImage imageNamed: [@"Resume" stringByAppendingString: controlImageSuffix]];
521    }
522   
523    [self drawImage: controlImage inRect: [self controlButtonRectForBounds: cellFrame]];
524   
525    //reveal button
526    NSString * revealImageString;
527    if (fMouseDownRevealButton)
528        revealImageString = @"RevealOn.png";
529    else if (!fTracking && fHoverReveal)
530        revealImageString = @"RevealHover.png";
531    else
532        revealImageString = @"RevealOff.png";
533   
534    NSImage * revealImage = [NSImage imageNamed: revealImageString];
535    [self drawImage: revealImage inRect: [self revealButtonRectForBounds: cellFrame]];
536   
537    //action button
538    NSString * actionImageString;
539    if (fMouseDownActionButton)
540        actionImageString = @"ActionOn.png";
541    else if (!fTracking && fHoverAction)
542        actionImageString = @"ActionHover.png";
543    else
544        actionImageString = nil;
545   
546    if (actionImageString)
547    {
548        NSImage * actionImage = [NSImage imageNamed: actionImageString];
549        [self drawImage: actionImage inRect: [self actionButtonRectForBounds: cellFrame]];
550    }
551   
552    //status
553    if (!minimal)
554    {
555        NSAttributedString * statusString = [self attributedStatusString: [self statusString]];
556        [statusString drawInRect: [self rectForStatusWithString: statusString inBounds: cellFrame]];
557    }
558}
559
560@end
561
562@implementation TorrentCell (Private)
563
564- (void) drawBar: (NSRect) barRect
565{
566    const CGFloat piecesBarPercent = [(TorrentTableView *)[self controlView] piecesBarPercent];
567    if (piecesBarPercent > 0.0)
568    {
569        NSRect piecesBarRect, regularBarRect;
570        NSDivideRect(barRect, &piecesBarRect, &regularBarRect, floorf(NSHeight(barRect) * PIECES_TOTAL_PERCENT * piecesBarPercent),
571                    NSMaxYEdge);
572       
573        [self drawRegularBar: regularBarRect];
574        [self drawPiecesBar: piecesBarRect];
575    }
576    else
577    {
578        [[self representedObject] setPreviousFinishedPieces: nil];
579       
580        [self drawRegularBar: barRect];
581    }
582   
583    [fBarBorderColor set];
584    [NSBezierPath strokeRect: NSInsetRect(barRect, 0.5, 0.5)];
585}
586
587- (void) drawRegularBar: (NSRect) barRect
588{
589    Torrent * torrent = [self representedObject];
590   
591    NSRect haveRect, missingRect;
592    NSDivideRect(barRect, &haveRect, &missingRect, floorf([torrent progress] * NSWidth(barRect)), NSMinXEdge);
593   
594    if (!NSIsEmptyRect(haveRect))
595    {
596        if ([torrent isActive])
597        {
598            if ([torrent isChecking])
599                [[ProgressGradients progressYellowGradient] drawInRect: haveRect angle: 90];
600            else if ([torrent isSeeding])
601            {
602                NSRect ratioHaveRect, ratioRemainingRect;
603                NSDivideRect(haveRect, &ratioHaveRect, &ratioRemainingRect, floorf([torrent progressStopRatio] * NSWidth(haveRect)),
604                            NSMinXEdge);
605               
606                [[ProgressGradients progressGreenGradient] drawInRect: ratioHaveRect angle: 90];
607                [[ProgressGradients progressLightGreenGradient] drawInRect: ratioRemainingRect angle: 90];
608            }
609            else
610                [[ProgressGradients progressBlueGradient] drawInRect: haveRect angle: 90];
611        }
612        else
613        {
614            if ([torrent waitingToStart])
615            {
616                if ([torrent allDownloaded])
617                    [[ProgressGradients progressDarkGreenGradient] drawInRect: haveRect angle: 90];
618                else
619                    [[ProgressGradients progressDarkBlueGradient] drawInRect: haveRect angle: 90];
620            }
621            else
622                [[ProgressGradients progressGrayGradient] drawInRect: haveRect angle: 90];
623        }
624    }
625   
626    if (!NSIsEmptyRect(missingRect))
627    {
628        if (![torrent allDownloaded])
629        {
630            //the ratio of total progress to total width equals ratio of progress of amount wanted to wanted width
631            const CGFloat widthRemaining = floorf(NSWidth(barRect) * (1.0 - [torrent progressDone]) / [torrent progress]);
632           
633            NSRect wantedRect;
634            NSDivideRect(missingRect, &wantedRect, &missingRect, widthRemaining, NSMinXEdge);
635           
636            //not-available section
637            if ([torrent isActive] && ![torrent isChecking] && [fDefaults boolForKey: @"DisplayProgressBarAvailable"]
638                && [torrent availableDesired] > 0.0)
639            {
640                NSRect unavailableRect;
641                NSDivideRect(wantedRect, &wantedRect, &unavailableRect, floorf([torrent availableDesired] * NSWidth(wantedRect)),
642                            NSMinXEdge);
643               
644                [[ProgressGradients progressRedGradient] drawInRect: unavailableRect angle: 90];
645            }
646           
647            //remaining section
648            [[ProgressGradients progressWhiteGradient] drawInRect: wantedRect angle: 90];
649        }
650       
651        //unwanted section
652        [[ProgressGradients progressLightGrayGradient] drawInRect: missingRect angle: 90];
653    }
654}
655
656- (void) drawPiecesBar: (NSRect) barRect
657{
658    Torrent * torrent = [self representedObject];
659   
660    NSInteger pieceCount = MIN([torrent pieceCount], MAX_PIECES);
661    float * piecesPercent = malloc(pieceCount * sizeof(float));
662    [torrent getAmountFinished: piecesPercent size: pieceCount];
663   
664    NSBitmapImageRep * bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
665                                    pixelsWide: pieceCount pixelsHigh: 1 bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
666                                    isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
667   
668    NSIndexSet * previousFinishedIndexes = [torrent previousFinishedPieces];
669    NSMutableIndexSet * finishedIndexes = [NSMutableIndexSet indexSet];
670   
671    for (NSInteger i = 0; i < pieceCount; i++)
672    {
673        NSColor * pieceColor;
674        if (piecesPercent[i] == 1.0f)
675        {
676            if (previousFinishedIndexes && ![previousFinishedIndexes containsIndex: i])
677                pieceColor = [NSColor orangeColor];
678            else
679                pieceColor = fBluePieceColor;
680            [finishedIndexes addIndex: i];
681        }
682        else
683            pieceColor = [[NSColor whiteColor] blendedColorWithFraction: piecesPercent[i] ofColor: fBluePieceColor];
684       
685        //it's faster to just set color instead of checking previous color
686        [bitmap setColor: pieceColor atX: i y: 0];
687    }
688   
689    free(piecesPercent);
690   
691    [torrent setPreviousFinishedPieces: [finishedIndexes count] > 0 ? finishedIndexes : nil]; //don't bother saving if none are complete
692   
693    //actually draw image
694    [bitmap drawInRect: barRect];
695    [bitmap release];
696}
697
698- (NSRect) rectForMinimalStatusWithString: (NSAttributedString *) string inBounds: (NSRect) bounds
699{
700    NSRect result;
701    result.size = [string size];
702   
703    result.origin.x = NSMaxX(bounds) - (NSWidth(result) + PADDING_HORIZONTAL);
704    result.origin.y = NSMinY(bounds) + PADDING_ABOVE_MIN_STATUS;
705   
706    return result;
707}
708
709- (NSRect) rectForTitleWithString: (NSAttributedString *) string basedOnMinimalStatusRect: (NSRect) statusRect
710            inBounds: (NSRect) bounds
711{
712    const BOOL minimal = [fDefaults boolForKey: @"SmallView"];
713   
714    NSRect result;
715    result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE;
716    result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL
717                        + (minimal ? IMAGE_SIZE_MIN : IMAGE_SIZE_REG) + PADDING_BETWEEN_IMAGE_AND_TITLE;
718   
719    result.size = [string size];
720    result.size.width = MIN(result.size.width, NSMaxX(bounds) - result.origin.x - PADDING_HORIZONTAL
721                - (minimal ? PADDING_BETWEEN_TITLE_AND_MIN_STATUS + statusRect.size.width : 0.0)
722                - ([[self representedObject] priority] != TR_PRI_NORMAL ? PRIORITY_ICON_WIDTH + PADDING_BETWEEN_TITLE_AND_PRIORITY: 0.0));
723   
724    return result;
725}
726
727- (NSRect) rectForProgressWithString: (NSAttributedString *) string inBounds: (NSRect) bounds
728{
729    NSRect result;
730    result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE + PADDING_BETWEEN_TITLE_AND_PROGRESS;
731    result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL + IMAGE_SIZE_REG + PADDING_BETWEEN_IMAGE_AND_TITLE;
732   
733    result.size = [string size];
734    result.size.width = MIN(result.size.width, NSMaxX(bounds) - result.origin.x - PADDING_HORIZONTAL);
735   
736    return result;
737}
738
739- (NSRect) rectForStatusWithString: (NSAttributedString *) string inBounds: (NSRect) bounds
740{
741    NSRect result;
742    result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE + PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS
743                        + PADDING_BETWEEN_PROGRESS_AND_BAR + BAR_HEIGHT + PADDING_BETWEEN_BAR_AND_STATUS;
744    result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL + IMAGE_SIZE_REG + PADDING_BETWEEN_IMAGE_AND_TITLE;
745   
746    result.size = [string size];
747    result.size.width = MIN(result.size.width, NSMaxX(bounds) - result.origin.x - PADDING_HORIZONTAL);
748   
749    return result;
750}
751
752- (NSAttributedString *) attributedTitle
753{
754    NSString * title = [[self representedObject] name];
755    return [[[NSAttributedString alloc] initWithString: title attributes: fTitleAttributes] autorelease];
756}
757
758- (NSAttributedString *) attributedStatusString: (NSString *) string
759{
760    return [[[NSAttributedString alloc] initWithString: string attributes: fStatusAttributes] autorelease];
761}
762
763- (NSString *) buttonString
764{
765    if (fMouseDownRevealButton || (!fTracking && fHoverReveal))
766        return NSLocalizedString(@"Show the data file in Finder", "Torrent cell -> button info");
767    else if (fMouseDownControlButton || (!fTracking && fHoverControl))
768    {
769        Torrent * torrent = [self representedObject];
770        if ([torrent isActive])
771            return NSLocalizedString(@"Pause the transfer", "Torrent Table -> tooltip");
772        else
773        {
774            if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
775                return NSLocalizedString(@"Resume the transfer right away", "Torrent cell -> button info");
776            else if ([torrent waitingToStart])
777                return NSLocalizedString(@"Stop waiting to start", "Torrent cell -> button info");
778            else
779                return NSLocalizedString(@"Resume the transfer", "Torrent cell -> button info");
780        }
781    }
782    else if (!fTracking && fHoverAction)
783        return NSLocalizedString(@"Change transfer settings", "Torrent Table -> tooltip");
784    else
785        return nil;
786}
787
788- (NSString *) statusString
789{
790    NSString * buttonString;
791    if ((buttonString = [self buttonString]))
792        return buttonString;
793    else
794        return [[self representedObject] statusString];
795}
796
797- (NSString *) minimalStatusString
798{
799    NSString * buttonString;
800    if ((buttonString = [self buttonString]))
801        return buttonString;
802    else
803    {
804        Torrent * torrent = [self representedObject];
805        return [fDefaults boolForKey: @"DisplaySmallStatusRegular"] ? [torrent shortStatusString] : [torrent remainingTimeString];
806    }
807}
808
809- (void) drawImage: (NSImage *) image inRect: (NSRect) rect
810{
811    if ([NSApp isOnSnowLeopardOrBetter])
812        [image drawInRect: rect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
813    else
814    {
815        [image setFlipped: YES];
816        [image drawInRect: rect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
817    }
818}
819
820@end
Note: See TracBrowser for help on using the repository browser.