source: trunk/macosx/TorrentCell.m @ 12849

Last change on this file since 12849 was 12849, checked in by livings124, 10 years ago

add a bit more spacing within the torrent cell, and make the code more easily changeable in the future

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