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