source: trunk/macosx/TorrentCell.m @ 10437

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

#1869 Move the finished state to libtransmission. This setting is now remembered between launches. This also causes torrents that hit the seed ratio to not have this setting changed to unlimited until start.

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