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