diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 505b3413e..c57c65938 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -21,7 +21,13 @@ 3C7A11980D0B2EE300B5701F /* getgateway.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C7A11920D0B2EE300B5701F /* getgateway.h */; }; 3C7A11990D0B2EE300B5701F /* natpmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C7A11930D0B2EE300B5701F /* natpmp.c */; }; 3C7A119A0D0B2EE300B5701F /* natpmp.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C7A11940D0B2EE300B5701F /* natpmp.h */; }; - 454BB0562941E8D800F99F38 /* GroupTextCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 454BB0542941E8D800F99F38 /* GroupTextCell.mm */; }; + 4521532B29AF891F009331B0 /* GroupCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4521532929AF891E009331B0 /* GroupCell.mm */; }; + 4521532E29AF9D61009331B0 /* TorrentCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4521532D29AF9D61009331B0 /* TorrentCell.mm */; }; + 4534164229B0EA8600F544C9 /* SmallTorrentCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4534164129B0EA8600F544C9 /* SmallTorrentCell.mm */; }; + 45489C5429AF6FB20098A812 /* TorrentCellActionButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45489C5029AF6FB10098A812 /* TorrentCellActionButton.mm */; }; + 45489C5629AF6FB20098A812 /* TorrentCellRevealButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45489C5229AF6FB10098A812 /* TorrentCellRevealButton.mm */; }; + 45489C5729AF6FB20098A812 /* TorrentCellControlButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45489C5329AF6FB10098A812 /* TorrentCellControlButton.mm */; }; + 4559D21629B32623004EFDF0 /* ProgressBarView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4559D21529B32623004EFDF0 /* ProgressBarView.mm */; }; 457AF8EB28604AFC00BCF74F /* Toolbar.mm in Sources */ = {isa = PBXBuildFile; fileRef = 457AF8EA28604AFC00BCF74F /* Toolbar.mm */; }; 45A7D3292843B54D00F0C32A /* GroupPopUpButtonCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45A7D3282843B54D00F0C32A /* GroupPopUpButtonCell.mm */; }; 45A7D32C2843B55F00F0C32A /* PriorityPopUpButtonCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 45A7D32B2843B55F00F0C32A /* PriorityPopUpButtonCell.mm */; }; @@ -46,7 +52,6 @@ 4D80185910BBC0B0008A4AF2 /* magnet-metainfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */; }; 4D80185A10BBC0B0008A4AF2 /* magnet-metainfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D80185810BBC0B0008A4AF2 /* magnet-metainfo.h */; }; 4D9A2BF009E16D21002D0FF9 /* libtransmission.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D18389709DEC0030047D688 /* libtransmission.a */; }; - 4DCCBB3E09C3D71100D3CABF /* TorrentCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DCCBB3C09C3D71100D3CABF /* TorrentCell.mm */; }; 4DE5CC9D0980656F00BE280E /* NSStringAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DE5CC9C0980656F00BE280E /* NSStringAdditions.mm */; }; 4DE5CCA70980735700BE280E /* Badger.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DE5CCA60980735700BE280E /* Badger.mm */; }; 4DE5CCCB0981D9BE00BE280E /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4DE5CCCA0981D9BE00BE280E /* Defaults.plist */; }; @@ -642,8 +647,21 @@ 3C7A11920D0B2EE300B5701F /* getgateway.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = getgateway.h; sourceTree = ""; }; 3C7A11930D0B2EE300B5701F /* natpmp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = natpmp.c; sourceTree = ""; }; 3C7A11940D0B2EE300B5701F /* natpmp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = natpmp.h; sourceTree = ""; }; - 454BB0542941E8D800F99F38 /* GroupTextCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GroupTextCell.mm; sourceTree = ""; }; - 454BB0552941E8D800F99F38 /* GroupTextCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupTextCell.h; sourceTree = ""; }; + 4521532929AF891E009331B0 /* GroupCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GroupCell.mm; sourceTree = ""; }; + 4521532A29AF891F009331B0 /* GroupCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupCell.h; sourceTree = ""; }; + 4521532C29AF9D60009331B0 /* TorrentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TorrentCell.h; sourceTree = ""; }; + 4521532D29AF9D61009331B0 /* TorrentCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCell.mm; sourceTree = ""; }; + 4534164029B0EA8500F544C9 /* SmallTorrentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmallTorrentCell.h; sourceTree = ""; }; + 4534164129B0EA8600F544C9 /* SmallTorrentCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SmallTorrentCell.mm; sourceTree = ""; }; + 45489C4529AE70F00098A812 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 45489C4729AE79FC0098A812 /* TorrentCellActionButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TorrentCellActionButton.h; sourceTree = ""; }; + 45489C4A29AF10D00098A812 /* TorrentCellRevealButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TorrentCellRevealButton.h; sourceTree = ""; }; + 45489C4B29AF10D10098A812 /* TorrentCellControlButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TorrentCellControlButton.h; sourceTree = ""; }; + 45489C5029AF6FB10098A812 /* TorrentCellActionButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCellActionButton.mm; sourceTree = ""; }; + 45489C5229AF6FB10098A812 /* TorrentCellRevealButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCellRevealButton.mm; sourceTree = ""; }; + 45489C5329AF6FB10098A812 /* TorrentCellControlButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCellControlButton.mm; sourceTree = ""; }; + 4559D21429B32622004EFDF0 /* ProgressBarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProgressBarView.h; sourceTree = ""; }; + 4559D21529B32623004EFDF0 /* ProgressBarView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProgressBarView.mm; sourceTree = ""; }; 455C0939287767270003A078 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/PrefsWindow.strings; sourceTree = ""; }; 455C093A287767290003A078 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/PrefsWindow.strings; sourceTree = ""; }; 455C093B2877672C0003A078 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/PrefsWindow.strings; sourceTree = ""; }; @@ -771,8 +789,6 @@ 4D8017E910BBC073008A4AF2 /* torrent-magnet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "torrent-magnet.h"; sourceTree = ""; }; 4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "magnet-metainfo.cc"; sourceTree = ""; }; 4D80185810BBC0B0008A4AF2 /* magnet-metainfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "magnet-metainfo.h"; sourceTree = ""; }; - 4DCCBB3C09C3D71100D3CABF /* TorrentCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCell.mm; sourceTree = ""; }; - 4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TorrentCell.h; sourceTree = ""; }; 4DDBB71909E16BAE00284745 /* transmissioncli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = transmissioncli; sourceTree = BUILT_PRODUCTS_DIR; }; 4DE5CC9B0980656F00BE280E /* NSStringAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSStringAdditions.h; sourceTree = ""; }; 4DE5CC9C0980656F00BE280E /* NSStringAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSStringAdditions.mm; sourceTree = ""; }; @@ -1457,10 +1473,20 @@ A27F0F320E19AD9800B2DB97 /* TorrentGroup.mm */, 4D364D9E091FBB2C00377D12 /* TorrentTableView.h */, 4D364D9F091FBB2C00377D12 /* TorrentTableView.mm */, - 4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */, - 4DCCBB3C09C3D71100D3CABF /* TorrentCell.mm */, - 454BB0552941E8D800F99F38 /* GroupTextCell.h */, - 454BB0542941E8D800F99F38 /* GroupTextCell.mm */, + 4521532C29AF9D60009331B0 /* TorrentCell.h */, + 4521532D29AF9D61009331B0 /* TorrentCell.mm */, + 4534164029B0EA8500F544C9 /* SmallTorrentCell.h */, + 4534164129B0EA8600F544C9 /* SmallTorrentCell.mm */, + 4521532A29AF891F009331B0 /* GroupCell.h */, + 4521532929AF891E009331B0 /* GroupCell.mm */, + 4559D21429B32622004EFDF0 /* ProgressBarView.h */, + 4559D21529B32623004EFDF0 /* ProgressBarView.mm */, + 45489C4729AE79FC0098A812 /* TorrentCellActionButton.h */, + 45489C5029AF6FB10098A812 /* TorrentCellActionButton.mm */, + 45489C4B29AF10D10098A812 /* TorrentCellControlButton.h */, + 45489C5329AF6FB10098A812 /* TorrentCellControlButton.mm */, + 45489C4A29AF10D00098A812 /* TorrentCellRevealButton.h */, + 45489C5229AF6FB10098A812 /* TorrentCellRevealButton.mm */, A21A9BE0106D86A800F1C3C1 /* TrackerNode.h */, A21A9BE1106D86A800F1C3C1 /* TrackerNode.mm */, A2725B6C0DE5C4F5003445E7 /* FileListNode.h */, @@ -2797,7 +2823,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = "The Transmission Project"; TargetAttributes = { 8D1107260486CEB800E47090 = { @@ -3127,13 +3153,14 @@ A225A4C0187E369C00CDE823 /* ShareToolbarItem.mm in Sources */, A2D77453154CC72B00A62B93 /* WebSeedTableView.mm in Sources */, 8D11072D0486CEB800E47090 /* main.mm in Sources */, + 45489C5629AF6FB20098A812 /* TorrentCellRevealButton.mm in Sources */, 4DF0C5AB0899190500DD8943 /* Controller.mm in Sources */, 4D118E1A08CB46B20033958F /* PrefsController.mm in Sources */, + 4521532E29AF9D61009331B0 /* TorrentCell.mm in Sources */, 4D364DA0091FBB2C00377D12 /* TorrentTableView.mm in Sources */, 4DE5CC9D0980656F00BE280E /* NSStringAdditions.mm in Sources */, 4DE5CCA70980735700BE280E /* Badger.mm in Sources */, 4DFBC2DF09C0970D00D5C571 /* Torrent.mm in Sources */, - 4DCCBB3E09C3D71100D3CABF /* TorrentCell.mm in Sources */, A200B9200A22798F007BBB1E /* InfoWindowController.mm in Sources */, A2AF1C390A3D0F6200F1575D /* FileOutlineView.mm in Sources */, A2710E770A86796000CE4F7D /* PrefsWindow.mm in Sources */, @@ -3153,12 +3180,14 @@ A2085DDC0C53BC74000BC3B7 /* AboutWindowController.mm in Sources */, A21282A80CA6C66800EAEE0F /* StatusBarView.mm in Sources */, A257C1820CAD3003004E121C /* PeerTableView.mm in Sources */, + 45489C5429AF6FB20098A812 /* TorrentCellActionButton.mm in Sources */, A2A6321B0CD9751700E3DA60 /* BadgeView.mm in Sources */, A2ED7D8F0CEF431B00970975 /* FilterButton.mm in Sources */, 45A7D32C2843B55F00F0C32A /* PriorityPopUpButtonCell.mm in Sources */, A25892640CF1F7E800CCCDDF /* StatsWindowController.mm in Sources */, A2C89D600CFCBF57004CC2BC /* ButtonToolbarItem.mm in Sources */, A219798B0D07B78400438EA7 /* GroupToolbarItem.mm in Sources */, + 4521532B29AF891F009331B0 /* GroupCell.mm in Sources */, C841A28129197724009F18E8 /* NSKeyedUnarchiverAdditions.mm in Sources */, A22180980D148A71007D09ED /* GroupsPrefsController.mm in Sources */, A26AF21A0D2DA35A00FF7140 /* FileOutlineController.mm in Sources */, @@ -3167,9 +3196,11 @@ 45A7D3292843B54D00F0C32A /* GroupPopUpButtonCell.mm in Sources */, A2D307A40D9EC6870051FD27 /* BlocklistDownloader.mm in Sources */, A2725B6E0DE5C4F5003445E7 /* FileListNode.mm in Sources */, + 4559D21629B32623004EFDF0 /* ProgressBarView.mm in Sources */, A2725D5D0DE7507C003445E7 /* TrackerTableView.mm in Sources */, A28F4F770E085BDC003A3882 /* ColorTextField.mm in Sources */, A27F0F330E19AD9800B2DB97 /* TorrentGroup.mm in Sources */, + 45489C5729AF6FB20098A812 /* TorrentCellControlButton.mm in Sources */, C809AEE7291ECFD000BFDBE1 /* NSDataAdditions.mm in Sources */, A222E9870E6B21D9009FB003 /* BlocklistDownloaderViewController.mm in Sources */, A222EA7B0E6C32C4009FB003 /* BlocklistScheduler.mm in Sources */, @@ -3192,10 +3223,10 @@ A2E57BA713109E6B00A7DAB1 /* FilterBarController.mm in Sources */, A2B5B4E91880665E0071A66A /* ShareTorrentFileHelper.mm in Sources */, A22BAE281388040500FB022F /* NSMutableArrayAdditions.mm in Sources */, - 454BB0562941E8D800F99F38 /* GroupTextCell.mm in Sources */, A2966E8713DAF74C007B52DF /* GlobalOptionsPopoverViewController.mm in Sources */, A234EA541453563B000F3E97 /* NSImageAdditions.mm in Sources */, A2AB883E16A399A6008FAD50 /* VDKQueue.mm in Sources */, + 4534164229B0EA8600F544C9 /* SmallTorrentCell.mm in Sources */, A2451E6916ACE4EB00586E0E /* FileRenameSheetController.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/macosx/Base.lproj/MainMenu.xib b/macosx/Base.lproj/MainMenu.xib index fbb2cc106..0ea595860 100644 --- a/macosx/Base.lproj/MainMenu.xib +++ b/macosx/Base.lproj/MainMenu.xib @@ -1,8 +1,7 @@ - + - - + @@ -17,88 +16,341 @@ - + - + - + - + - + - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -126,10 +378,10 @@ - + - + @@ -1017,13 +1269,20 @@ CA + + + + + + - + + YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T diff --git a/macosx/CMakeLists.txt b/macosx/CMakeLists.txt index 5cef802d1..74335c170 100644 --- a/macosx/CMakeLists.txt +++ b/macosx/CMakeLists.txt @@ -90,14 +90,14 @@ target_sources(${TR_NAME}-mac FilterButton.mm GlobalOptionsPopoverViewController.h GlobalOptionsPopoverViewController.mm + GroupCell.h + GroupCell.mm GroupPopUpButtonCell.h GroupPopUpButtonCell.mm GroupsController.h GroupsController.mm GroupsPrefsController.h GroupsPrefsController.mm - GroupTextCell.h - GroupTextCell.mm GroupToolbarItem.h GroupToolbarItem.mm InfoActivityViewController.h @@ -148,12 +148,16 @@ target_sources(${TR_NAME}-mac PrefsWindow.mm PriorityPopUpButtonCell.h PriorityPopUpButtonCell.mm + ProgressBarView.h + ProgressBarView.mm ProgressGradients.h ProgressGradients.mm ShareToolbarItem.h ShareToolbarItem.mm ShareTorrentFileHelper.h ShareTorrentFileHelper.mm + SmallTorrentCell.h + SmallTorrentCell.mm SparkleProxy.mm StatsWindowController.h StatsWindowController.mm @@ -167,6 +171,12 @@ target_sources(${TR_NAME}-mac Torrent.mm TorrentCell.h TorrentCell.mm + TorrentCellActionButton.h + TorrentCellActionButton.mm + TorrentCellControlButton.h + TorrentCellControlButton.mm + TorrentCellRevealButton.h + TorrentCellRevealButton.mm TorrentGroup.h TorrentGroup.mm TorrentTableView.h diff --git a/macosx/Controller.mm b/macosx/Controller.mm index e1701dc1d..44e848fed 100644 --- a/macosx/Controller.mm +++ b/macosx/Controller.mm @@ -625,11 +625,10 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool //set table size BOOL const small = [self.fDefaults boolForKey:@"SmallView"]; - if (small) - { - self.fTableView.rowHeight = kRowHeightSmall; - } - self.fTableView.usesAlternatingRowBackgroundColors = !small; + self.fTableView.rowHeight = small ? kRowHeightSmall : kRowHeightRegular; + self.fTableView.usesAutomaticRowHeights = NO; + self.fTableView.floatsGroupRows = YES; + //self.fTableView.usesAlternatingRowBackgroundColors = !small; [self.fWindow setContentBorderThickness:NSMinY(self.fTableView.enclosingScrollView.frame) forEdge:NSMinYEdge]; self.fWindow.movableByWindowBackground = YES; @@ -2317,6 +2316,8 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool { [self.fInfoController updateInfoStats]; } + + [self.fTableView reloadVisibleRows]; } //badge dock @@ -3239,10 +3240,6 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool //set all groups as expanded [self.fTableView removeAllCollapsedGroups]; -//since we're not doing this the right way (boo buggy animation), we need to remember selected values -#warning when Lion-only and using views instead of cells, this likely won't be needed - NSArray* selectedValues = self.fTableView.selectedValues; - beganUpdates = YES; [self.fTableView beginUpdates]; @@ -3286,11 +3283,6 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool for (TorrentGroup* group in self.fDisplayedTorrents) [self.fTableView expandItem:group]; } - - if (selectedValues) - { - [self.fTableView selectValues:selectedValues]; - } } //sort the torrents (won't sort the groups, though) @@ -3599,66 +3591,6 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool return ![item isKindOfClass:[Torrent class]]; } -- (id)outlineView:(NSOutlineView*)outlineView objectValueForTableColumn:(NSTableColumn*)tableColumn byItem:(id)item -{ - if ([item isKindOfClass:[Torrent class]]) - { - if (tableColumn) - { - return nil; - } - return ((Torrent*)item).hashString; - } - else - { - NSString* ident = tableColumn.identifier; - TorrentGroup* group = (TorrentGroup*)item; - if ([ident isEqualToString:@"Group"]) - { - NSInteger groupIndex = group.groupIndex; - return groupIndex != -1 ? [GroupsController.groups nameForIndex:groupIndex] : NSLocalizedString(@"No Group", "Group table row"); - } - else if ([ident isEqualToString:@"Color"]) - { - NSInteger groupIndex = group.groupIndex; - return [GroupsController.groups imageForIndex:groupIndex]; - } - else if ([ident isEqualToString:@"DL Image"]) - { - NSImage* image = [NSImage imageNamed:@"DownArrowGroupTemplate"]; - image.accessibilityDescription = NSLocalizedString(@"DL", "Torrent -> status image"); - return image; - } - else if ([ident isEqualToString:@"UL Image"]) - { - if ([self.fDefaults boolForKey:@"DisplayGroupRowRatio"]) - { - NSImage* image = [NSImage imageNamed:@"YingYangGroupTemplate"]; - image.accessibilityDescription = NSLocalizedString(@"Ratio", "Torrent -> status image"); - return image; - } - else - { - NSImage* image = [NSImage imageNamed:@"UpArrowGroupTemplate"]; - image.accessibilityDescription = NSLocalizedString(@"UL", "Torrent -> status image"); - return image; - } - } - else - { - if ([self.fDefaults boolForKey:@"DisplayGroupRowRatio"]) - { - return [NSString stringForRatio:group.ratio]; - } - else - { - CGFloat rate = [ident isEqualToString:@"UL"] ? group.uploadRate : group.downloadRate; - return [NSString stringForSpeed:rate]; - } - } - } -} - - (BOOL)outlineView:(NSOutlineView*)outlineView writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pasteboard { //only allow reordering of rows if sorting by order @@ -3980,7 +3912,7 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool BOOL makeSmall = ![self.fDefaults boolForKey:@"SmallView"]; [self.fDefaults setBool:makeSmall forKey:@"SmallView"]; - self.fTableView.usesAlternatingRowBackgroundColors = !makeSmall; + //self.fTableView.usesAlternatingRowBackgroundColors = !makeSmall; self.fTableView.rowHeight = makeSmall ? kRowHeightSmall : kRowHeightRegular; @@ -5319,6 +5251,12 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool height = (kGroupSeparatorHeight + self.fTableView.intercellSpacing.height) * groups + (self.fTableView.rowHeight + self.fTableView.intercellSpacing.height) * (self.fTableView.numberOfRows - groups); + + //account for group padding... + if (groups > 1) + { + height += (groups - 1) * 20; + } } else { diff --git a/macosx/GroupCell.h b/macosx/GroupCell.h new file mode 100644 index 000000000..be22c046f --- /dev/null +++ b/macosx/GroupCell.h @@ -0,0 +1,18 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import +#import "TorrentTableView.h" + +@interface GroupCell : NSTableCellView + +@property(nonatomic) IBOutlet NSImageView* fGroupIndicatorView; +@property(nonatomic) IBOutlet NSTextField* fGroupTitleField; + +@property(nonatomic) IBOutlet NSImageView* fGroupDownloadView; +@property(nonatomic) IBOutlet NSImageView* fGroupUploadAndRatioView; +@property(nonatomic) IBOutlet NSTextField* fGroupDownloadField; +@property(nonatomic) IBOutlet NSTextField* fGroupUploadAndRatioField; + +@end diff --git a/macosx/GroupCell.mm b/macosx/GroupCell.mm new file mode 100644 index 000000000..2c8ddd454 --- /dev/null +++ b/macosx/GroupCell.mm @@ -0,0 +1,9 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "GroupCell.h" + +@implementation GroupCell + +@end diff --git a/macosx/GroupTextCell.h b/macosx/GroupTextCell.h deleted file mode 100644 index 8b505c34a..000000000 --- a/macosx/GroupTextCell.h +++ /dev/null @@ -1,11 +0,0 @@ -// This file Copyright © 2022-2023 Transmission authors and contributors. -// It may be used under the MIT (SPDX: MIT) license. -// License text can be found in the licenses/ folder. - -#import - -@interface GroupTextCell : NSTextFieldCell - -@property(nonatomic) BOOL selected; - -@end diff --git a/macosx/GroupTextCell.mm b/macosx/GroupTextCell.mm deleted file mode 100644 index 138b05817..000000000 --- a/macosx/GroupTextCell.mm +++ /dev/null @@ -1,35 +0,0 @@ -// This file Copyright © 2022-2023 Transmission authors and contributors. -// It may be used under the MIT (SPDX: MIT) license. -// License text can be found in the licenses/ folder. - -#import "GroupTextCell.h" -#import "TorrentGroup.h" -#import "TorrentTableView.h" - -@implementation GroupTextCell - -//vertically align text -- (NSRect)titleRectForBounds:(NSRect)theRect -{ - NSRect titleFrame = [super titleRectForBounds:theRect]; - NSSize titleSize = [[self attributedStringValue] size]; - titleFrame.origin.y = NSMidY(theRect) - (CGFloat)1.0 - titleSize.height * (CGFloat)0.5; - titleFrame.origin.x = theRect.origin.x; - return titleFrame; -} - -- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView -{ - //set font size and color - NSRect titleRect = [self titleRectForBounds:cellFrame]; - NSMutableAttributedString* string = [[self attributedStringValue] mutableCopy]; - NSDictionary* attributes = @{ - NSFontAttributeName : [NSFont boldSystemFontOfSize:11.0], - NSForegroundColorAttributeName : self.selected ? [NSColor labelColor] : [NSColor secondaryLabelColor] - }; - - [string addAttributes:attributes range:NSMakeRange(0, string.length)]; - [string drawInRect:titleRect]; -} - -@end diff --git a/macosx/ProgressBarView.h b/macosx/ProgressBarView.h new file mode 100644 index 000000000..882622694 --- /dev/null +++ b/macosx/ProgressBarView.h @@ -0,0 +1,14 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@class TorrentTableView; +@class Torrent; + +@interface ProgressBarView : NSView + +- (void)drawBarInRect:(NSRect)barRect forTableView:(TorrentTableView*)tableView withTorrent:(Torrent*)torrent; + +@end diff --git a/macosx/ProgressBarView.mm b/macosx/ProgressBarView.mm new file mode 100644 index 000000000..e2983d897 --- /dev/null +++ b/macosx/ProgressBarView.mm @@ -0,0 +1,203 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "ProgressBarView.h" +#import "ProgressGradients.h" +#import "TorrentTableView.h" +#import "Torrent.h" + +static CGFloat const kPiecesTotalPercent = 0.6; +static NSInteger const kMaxPieces = 18 * 18; + +@interface ProgressBarView () + +@property(nonatomic, readonly) NSUserDefaults* fDefaults; + +@property(nonatomic, readonly) NSColor* fBarBorderColor; +@property(nonatomic, readonly) NSColor* fBluePieceColor; +@property(nonatomic, readonly) NSColor* fBarMinimalBorderColor; + +@end + +@implementation ProgressBarView + +- (instancetype)init +{ + if ((self = [super init])) + { + _fDefaults = NSUserDefaults.standardUserDefaults; + + _fBluePieceColor = [NSColor colorWithCalibratedRed:0.0 green:0.4 blue:0.8 alpha:1.0]; + _fBarBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.2]; + _fBarMinimalBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.015]; + } + return self; +} + +- (void)drawBarInRect:(NSRect)barRect forTableView:(TorrentTableView*)tableView withTorrent:(Torrent*)torrent +{ + BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; + + CGFloat const piecesBarPercent = tableView.piecesBarPercent; + if (piecesBarPercent > 0.0) + { + NSRect piecesBarRect, regularBarRect; + NSDivideRect(barRect, &piecesBarRect, ®ularBarRect, floor(NSHeight(barRect) * kPiecesTotalPercent * piecesBarPercent), NSMaxYEdge); + + [self drawRegularBar:regularBarRect forTorrent:torrent]; + [self drawPiecesBar:piecesBarRect forTorrent:torrent]; + } + else + { + torrent.previousFinishedPieces = nil; + + [self drawRegularBar:barRect forTorrent:torrent]; + } + + NSColor* borderColor = minimal ? self.fBarMinimalBorderColor : self.fBarBorderColor; + [borderColor set]; + [NSBezierPath strokeRect:NSInsetRect(barRect, 0.5, 0.5)]; +} + +- (void)drawRegularBar:(NSRect)barRect forTorrent:(Torrent*)torrent +{ + NSRect haveRect, missingRect; + NSDivideRect(barRect, &haveRect, &missingRect, round(torrent.progress * NSWidth(barRect)), NSMinXEdge); + + if (!NSIsEmptyRect(haveRect)) + { + if (torrent.active) + { + if (torrent.checking) + { + [ProgressGradients.progressYellowGradient drawInRect:haveRect angle:90]; + } + else if (torrent.seeding) + { + NSRect ratioHaveRect, ratioRemainingRect; + NSDivideRect(haveRect, &ratioHaveRect, &ratioRemainingRect, round(torrent.progressStopRatio * NSWidth(haveRect)), NSMinXEdge); + + [ProgressGradients.progressGreenGradient drawInRect:ratioHaveRect angle:90]; + [ProgressGradients.progressLightGreenGradient drawInRect:ratioRemainingRect angle:90]; + } + else + { + [ProgressGradients.progressBlueGradient drawInRect:haveRect angle:90]; + } + } + else + { + if (torrent.waitingToStart) + { + if (torrent.allDownloaded) + { + [ProgressGradients.progressDarkGreenGradient drawInRect:haveRect angle:90]; + } + else + { + [ProgressGradients.progressDarkBlueGradient drawInRect:haveRect angle:90]; + } + } + else + { + [ProgressGradients.progressGrayGradient drawInRect:haveRect angle:90]; + } + } + } + + if (!torrent.allDownloaded) + { + CGFloat const widthRemaining = round(NSWidth(barRect) * torrent.progressLeft); + + NSRect wantedRect; + NSDivideRect(missingRect, &wantedRect, &missingRect, widthRemaining, NSMinXEdge); + + //not-available section + if (torrent.active && !torrent.checking && torrent.availableDesired < 1.0 && [self.fDefaults boolForKey:@"DisplayProgressBarAvailable"]) + { + NSRect unavailableRect; + NSDivideRect(wantedRect, &wantedRect, &unavailableRect, round(NSWidth(wantedRect) * torrent.availableDesired), NSMinXEdge); + + [ProgressGradients.progressRedGradient drawInRect:unavailableRect angle:90]; + } + + //remaining section + [ProgressGradients.progressWhiteGradient drawInRect:wantedRect angle:90]; + } + + //unwanted section + if (!NSIsEmptyRect(missingRect)) + { + if (!torrent.magnet) + { + [ProgressGradients.progressLightGrayGradient drawInRect:missingRect angle:90]; + } + else + { + [ProgressGradients.progressRedGradient drawInRect:missingRect angle:90]; + } + } +} + +- (void)drawPiecesBar:(NSRect)barRect forTorrent:(Torrent*)torrent +{ + //fill an all-white bar for magnet links + if (torrent.magnet) + { + [[NSColor colorWithCalibratedWhite:1.0 alpha:[self.fDefaults boolForKey:@"SmallView"] ? 0.25 : 1.0] set]; + NSRectFillUsingOperation(barRect, NSCompositingOperationSourceOver); + return; + } + + NSInteger pieceCount = MIN(torrent.pieceCount, kMaxPieces); + float* piecesPercent = static_cast(malloc(pieceCount * sizeof(float))); + [torrent getAmountFinished:piecesPercent size:pieceCount]; + + NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:pieceCount pixelsHigh:1 + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bytesPerRow:0 + bitsPerPixel:0]; + + NSIndexSet* previousFinishedIndexes = torrent.previousFinishedPieces; + NSMutableIndexSet* finishedIndexes = [NSMutableIndexSet indexSet]; + + for (NSInteger i = 0; i < pieceCount; i++) + { + NSColor* pieceColor; + if (piecesPercent[i] == 1.0f) + { + if (previousFinishedIndexes && ![previousFinishedIndexes containsIndex:i]) + { + pieceColor = NSColor.orangeColor; + } + else + { + pieceColor = self.fBluePieceColor; + } + [finishedIndexes addIndex:i]; + } + else + { + pieceColor = [NSColor.whiteColor blendedColorWithFraction:piecesPercent[i] ofColor:self.fBluePieceColor]; + } + + //it's faster to just set color instead of checking previous color + [bitmap setColor:pieceColor atX:i y:0]; + } + + free(piecesPercent); + + torrent.previousFinishedPieces = finishedIndexes.count > 0 ? finishedIndexes : nil; //don't bother saving if none are complete + + //actually draw image + [bitmap drawInRect:barRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver + fraction:([self.fDefaults boolForKey:@"SmallView"] ? 0.25 : 1.0)respectFlipped:YES + hints:nil]; +} + +@end diff --git a/macosx/SmallTorrentCell.h b/macosx/SmallTorrentCell.h new file mode 100644 index 000000000..3af100116 --- /dev/null +++ b/macosx/SmallTorrentCell.h @@ -0,0 +1,25 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@class TorrentTableView; + +@interface SmallTorrentCell : NSTableCellView + +@property(nonatomic) IBOutlet NSButton* fActionButton; +@property(nonatomic) IBOutlet NSButton* fControlButton; +@property(nonatomic) IBOutlet NSButton* fRevealButton; + +@property(nonatomic) IBOutlet NSImageView* fIconView; +@property(nonatomic) IBOutlet NSImageView* fGroupIndicatorView; + +@property(nonatomic) IBOutlet NSTextField* fTorrentTitleField; +@property(nonatomic) IBOutlet NSTextField* fTorrentStatusField; + +@property(nonatomic) IBOutlet NSView* fTorrentProgressBarView; + +@property(nonatomic) TorrentTableView* fTorrentTableView; + +@end diff --git a/macosx/SmallTorrentCell.mm b/macosx/SmallTorrentCell.mm new file mode 100644 index 000000000..2078c9415 --- /dev/null +++ b/macosx/SmallTorrentCell.mm @@ -0,0 +1,98 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "SmallTorrentCell.h" +#import "ProgressBarView.h" +#import "ProgressGradients.h" +#import "TorrentTableView.h" +#import "Torrent.h" + +@interface SmallTorrentCell () +@property(nonatomic) NSTrackingArea* fTrackingArea; +@end + +@implementation SmallTorrentCell + +//draw progress bar +- (void)drawRect:(NSRect)dirtyRect +{ + if (self.fTorrentTableView) + { + NSRect barRect = self.fTorrentProgressBarView.frame; + ProgressBarView* progressBar = [[ProgressBarView alloc] init]; + Torrent* torrent = (Torrent*)self.objectValue; + + [progressBar drawBarInRect:barRect forTableView:self.fTorrentTableView withTorrent:torrent]; + } + + [super drawRect:dirtyRect]; +} + +//otherwise progress bar is inverted +- (BOOL)isFlipped +{ + return YES; +} + +//show fControlButton and fRevealButton +- (void)mouseEntered:(NSEvent*)event +{ + [super mouseEntered:event]; + + NSPoint mouseLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + if (NSPointInRect(mouseLocation, self.fTrackingArea.rect)) + { + [self.fTorrentTableView hoverEventBeganForView:self]; + } +} + +- (void)mouseExited:(NSEvent*)event +{ + [super mouseExited:event]; + + NSPoint mouseLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + if (!NSPointInRect(mouseLocation, self.fTrackingArea.rect)) + { + [self.fTorrentTableView hoverEventEndedForView:self]; + } +} + +- (void)mouseUp:(NSEvent*)event +{ + [super mouseUp:event]; + [self updateTrackingAreas]; +} + +- (void)updateTrackingAreas +{ + if (self.fTrackingArea != nil) + { + [self removeTrackingArea:self.fTrackingArea]; + } + + //tracking rect should not be entire row, but start at fGroupDownloadView + NSRect titleRect = self.fTorrentTitleField.frame; + CGFloat maxX = NSMaxX(titleRect); + NSRect rect = self.bounds; + rect.origin.x = maxX; + + NSTrackingAreaOptions opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow); + self.fTrackingArea = [[NSTrackingArea alloc] initWithRect:rect options:opts owner:self userInfo:nil]; + [self addTrackingArea:self.fTrackingArea]; + + //check to see if mouse is already within rect + NSPoint mouseLocation = [self.window mouseLocationOutsideOfEventStream]; + mouseLocation = [self.superview convertPoint:mouseLocation fromView:nil]; + + if (NSPointInRect(mouseLocation, rect)) + { + [self mouseEntered:[[NSEvent alloc] init]]; + } + else + { + [self mouseExited:[[NSEvent alloc] init]]; + } +} + +@end diff --git a/macosx/TorrentCell.h b/macosx/TorrentCell.h index 1788a1bd3..ab6caf4ae 100644 --- a/macosx/TorrentCell.h +++ b/macosx/TorrentCell.h @@ -3,20 +3,23 @@ // License text can be found in the licenses/ folder. #import +#import "TorrentTableView.h" -@interface TorrentCell : NSActionCell +@interface TorrentCell : NSTableCellView -@property(nonatomic) BOOL hover; -@property(nonatomic) BOOL hoverControl; -@property(nonatomic) BOOL hoverReveal; -@property(nonatomic) BOOL hoverAction; +@property(nonatomic) IBOutlet NSButton* fActionButton; +@property(nonatomic) IBOutlet NSButton* fControlButton; +@property(nonatomic) IBOutlet NSButton* fRevealButton; -- (NSRect)iconRectForBounds:(NSRect)bounds; -- (NSRect)actionRectForBounds:(NSRect)bounds; +@property(nonatomic) IBOutlet NSImageView* fIconView; +@property(nonatomic) IBOutlet NSImageView* fGroupIndicatorView; -- (void)addTrackingAreasForView:(NSView*)controlView - inRect:(NSRect)cellFrame - withUserInfo:(NSDictionary*)userInfo - mouseLocation:(NSPoint)mouseLocation; +@property(nonatomic) IBOutlet NSTextField* fTorrentTitleField; +@property(nonatomic) IBOutlet NSTextField* fTorrentProgressField; +@property(nonatomic) IBOutlet NSTextField* fTorrentStatusField; + +@property(nonatomic) IBOutlet NSView* fTorrentProgressBarView; + +@property(nonatomic) TorrentTableView* fTorrentTableView; @end diff --git a/macosx/TorrentCell.mm b/macosx/TorrentCell.mm index bc5d70e1a..92944e3cb 100644 --- a/macosx/TorrentCell.mm +++ b/macosx/TorrentCell.mm @@ -3,919 +3,31 @@ // License text can be found in the licenses/ folder. #import "TorrentCell.h" -#import "FileNameCell.h" -#import "GroupsController.h" -#import "NSImageAdditions.h" -#import "NSStringAdditions.h" +#import "ProgressBarView.h" #import "ProgressGradients.h" #import "Torrent.h" -#import "TorrentTableView.h" - -static CGFloat const kBarHeight = 12.0; - -static CGFloat const kImageSizeRegular = 32.0; -static CGFloat const kImageSizeMin = 16.0; -static CGFloat const kErrorImageSize = 20.0; - -static CGFloat const kGroupImageSizeRegular = 10.0; -static CGFloat const kGroupImageSizeMin = 6.0; -static CGFloat const kGroupPaddingRegular = 22.0; -static CGFloat const kGroupPaddingMin = 14.0; - -static CGFloat const kNormalButtonWidth = 14.0; -static CGFloat const kActionButtonWidth = 16.0; - -static CGFloat const kPriorityIconSize = 12.0; - -//ends up being larger than font height -static CGFloat const kHeightTitle = 16.0; -static CGFloat const kHeightStatus = 12.0; - -static CGFloat const kPaddingHorizontal = 5.0; -static CGFloat const kPaddingEdgeMax = 12.0; -static CGFloat const kPaddingBetweenButtons = 3.0; -static CGFloat const kPaddingBetweenImageAndTitle = kPaddingHorizontal + 1.0; -static CGFloat const kPaddingBetweenImageAndBar = kPaddingHorizontal; -static CGFloat const kPaddingBetweenTitleAndPriority = 6.0; -static CGFloat const kPaddingAboveTitle = 4.0; -static CGFloat const kPaddingBetweenTitleAndMinStatus = 3.0; -static CGFloat const kPaddingBetweenTitleAndProgress = 1.0; -static CGFloat const kPaddingBetweenProgressAndBar = 2.0; -static CGFloat const kPaddingBetweenBarAndStatus = 2.0; -static CGFloat const kPaddingBetweenBarAndEdgeMin = 3.0; -static CGFloat const kPaddingExpansionFrame = 2.0; - -static CGFloat const kPiecesTotalPercent = 0.6; - -static NSInteger const kMaxPieces = 18 * 18; - -static NSMutableParagraphStyle* sParagraphStyle() -{ - NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy]; - paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle; - return paragraphStyle; -} -static NSDictionary* const kTitleAttributes = @{ - NSFontAttributeName : [NSFont messageFontOfSize:12.0], - NSParagraphStyleAttributeName : sParagraphStyle(), - NSForegroundColorAttributeName : NSColor.labelColor -}; -static NSDictionary* const kStatusAttributes = @{ - NSFontAttributeName : [NSFont messageFontOfSize:10.0], - NSParagraphStyleAttributeName : sParagraphStyle(), - NSForegroundColorAttributeName : NSColor.secondaryLabelColor -}; -static NSDictionary* const kTitleEmphasizedAttributes = @{ - NSFontAttributeName : [NSFont messageFontOfSize:12.0], - NSParagraphStyleAttributeName : sParagraphStyle(), - NSForegroundColorAttributeName : NSColor.whiteColor -}; -static NSDictionary* const kStatusEmphasizedAttributes = @{ - NSFontAttributeName : [NSFont messageFontOfSize:10.0], - NSParagraphStyleAttributeName : sParagraphStyle(), - NSForegroundColorAttributeName : NSColor.whiteColor -}; - -@interface TorrentCell () - -@property(nonatomic) BOOL fTracking; -@property(nonatomic) BOOL fMouseDownControlButton; -@property(nonatomic) BOOL fMouseDownRevealButton; - -@property(nonatomic, readonly) NSColor* fBarBorderColor; -@property(nonatomic, readonly) NSColor* fBluePieceColor; -@property(nonatomic, readonly) NSColor* fBarMinimalBorderColor; - -@end @implementation TorrentCell -//only called once and the main table is always needed, so don't worry about releasing -- (instancetype)init +//draw progress bar +- (void)drawRect:(NSRect)dirtyRect { - if ((self = [super init])) + if (self.fTorrentTableView) { - _fBluePieceColor = [NSColor colorWithCalibratedRed:0.0 green:0.4 blue:0.8 alpha:1.0]; - _fBarBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.2]; - _fBarMinimalBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.015]; - } - return self; -} + NSRect barRect = self.fTorrentProgressBarView.frame; + ProgressBarView* progressBar = [[ProgressBarView alloc] init]; + Torrent* torrent = (Torrent*)self.objectValue; -- (id)copyWithZone:(NSZone*)zone -{ - TorrentCell* copy = [super copyWithZone:zone]; - copy->_fBluePieceColor = _fBluePieceColor; - copy->_fBarBorderColor = _fBarBorderColor; - copy->_fBarMinimalBorderColor = _fBarMinimalBorderColor; - [copy setRepresentedObject:self.representedObject]; - return copy; -} - -- (NSUserDefaults*)fDefaults -{ - return NSUserDefaults.standardUserDefaults; -} - -- (NSRect)iconRectForBounds:(NSRect)bounds -{ - BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; - CGFloat const imageSize = minimal ? kImageSizeMin : kImageSizeRegular; - CGFloat const padding = minimal ? kGroupPaddingMin : kGroupPaddingRegular; - - return NSMakeRect(NSMinX(bounds) + (padding * 0.5) + kPaddingHorizontal, ceil(NSMidY(bounds) - imageSize * 0.5), imageSize, imageSize); -} - -- (NSRect)actionRectForBounds:(NSRect)bounds -{ - NSRect iconRect = [self iconRectForBounds:bounds]; - NSRect actionRect = [self actionButtonRectForBounds:iconRect]; - - return actionRect; -} - -- (NSCellHitResult)hitTestForEvent:(NSEvent*)event inRect:(NSRect)cellFrame ofView:(NSView*)controlView -{ - NSPoint point = [controlView convertPoint:event.locationInWindow fromView:nil]; - - if (NSMouseInRect(point, [self controlButtonRectForBounds:cellFrame], controlView.flipped) || - NSMouseInRect(point, [self revealButtonRectForBounds:cellFrame], controlView.flipped)) - { - return NSCellHitContentArea | NSCellHitTrackableArea; + [progressBar drawBarInRect:barRect forTableView:self.fTorrentTableView withTorrent:torrent]; } - return NSCellHitContentArea; + [super drawRect:dirtyRect]; } -+ (BOOL)prefersTrackingUntilMouseUp +//otherwise progress bar is inverted +- (BOOL)isFlipped { return YES; } -- (BOOL)trackMouse:(NSEvent*)event inRect:(NSRect)cellFrame ofView:(NSView*)controlView untilMouseUp:(BOOL)flag -{ - self.fTracking = YES; - - self.controlView = controlView; - - NSPoint point = [controlView convertPoint:event.locationInWindow fromView:nil]; - - NSRect const controlRect = [self controlButtonRectForBounds:cellFrame]; - BOOL const checkControl = NSMouseInRect(point, controlRect, controlView.flipped); - - NSRect const revealRect = [self revealButtonRectForBounds:cellFrame]; - BOOL const checkReveal = NSMouseInRect(point, revealRect, controlView.flipped); - - [(TorrentTableView*)controlView removeTrackingAreas]; - - while (event.type != NSEventTypeLeftMouseUp) - { - point = [controlView convertPoint:event.locationInWindow fromView:nil]; - - if (checkControl) - { - BOOL const inControlButton = NSMouseInRect(point, controlRect, controlView.flipped); - if (self.fMouseDownControlButton != inControlButton) - { - self.fMouseDownControlButton = inControlButton; - [controlView setNeedsDisplayInRect:cellFrame]; - } - } - else if (checkReveal) - { - BOOL const inRevealButton = NSMouseInRect(point, revealRect, controlView.flipped); - if (self.fMouseDownRevealButton != inRevealButton) - { - self.fMouseDownRevealButton = inRevealButton; - [controlView setNeedsDisplayInRect:cellFrame]; - } - } - - //send events to where necessary - if (event.type == NSEventTypeMouseEntered || event.type == NSEventTypeMouseExited) - { - [NSApp sendEvent:event]; - } - event = [controlView.window nextEventMatchingMask:(NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged | - NSEventMaskMouseEntered | NSEventMaskMouseExited)]; - } - - self.fTracking = NO; - - if (self.fMouseDownControlButton) - { - self.fMouseDownControlButton = NO; - - [(TorrentTableView*)controlView toggleControlForTorrent:self.representedObject]; - } - else if (self.fMouseDownRevealButton) - { - self.fMouseDownRevealButton = NO; - [controlView setNeedsDisplayInRect:cellFrame]; - - NSString* location = ((Torrent*)self.representedObject).dataLocation; - if (location) - { - NSURL* file = [NSURL fileURLWithPath:location]; - [NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:@[ file ]]; - } - } - - [controlView updateTrackingAreas]; - - return YES; -} - -- (void)addTrackingAreasForView:(NSView*)controlView - inRect:(NSRect)cellFrame - withUserInfo:(NSDictionary*)userInfo - mouseLocation:(NSPoint)mouseLocation -{ - NSTrackingAreaOptions const options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways; - - //whole row - if ([self.fDefaults boolForKey:@"SmallView"]) - { - NSTrackingAreaOptions rowOptions = options; - if (NSMouseInRect(mouseLocation, cellFrame, controlView.flipped)) - { - rowOptions |= NSTrackingAssumeInside; - ((TorrentTableView*)controlView).hoverRow = [userInfo[@"Row"] integerValue]; - } - - NSMutableDictionary* rowInfo = [userInfo mutableCopy]; - rowInfo[@"Type"] = @"Row"; - NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect:cellFrame options:rowOptions owner:controlView userInfo:rowInfo]; - [controlView addTrackingArea:area]; - } - - //control button - NSRect controlButtonRect = [self controlButtonRectForBounds:cellFrame]; - NSTrackingAreaOptions controlOptions = options; - if (NSMouseInRect(mouseLocation, controlButtonRect, controlView.flipped)) - { - controlOptions |= NSTrackingAssumeInside; - ((TorrentTableView*)controlView).controlButtonHoverRow = [userInfo[@"Row"] integerValue]; - } - - NSMutableDictionary* controlInfo = [userInfo mutableCopy]; - controlInfo[@"Type"] = @"Control"; - NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect:controlButtonRect options:controlOptions owner:controlView - userInfo:controlInfo]; - [controlView addTrackingArea:area]; - - //reveal button - NSRect revealButtonRect = [self revealButtonRectForBounds:cellFrame]; - NSTrackingAreaOptions revealOptions = options; - if (NSMouseInRect(mouseLocation, revealButtonRect, controlView.flipped)) - { - revealOptions |= NSTrackingAssumeInside; - ((TorrentTableView*)controlView).revealButtonHoverRow = [userInfo[@"Row"] integerValue]; - } - - NSMutableDictionary* revealInfo = [userInfo mutableCopy]; - revealInfo[@"Type"] = @"Reveal"; - area = [[NSTrackingArea alloc] initWithRect:revealButtonRect options:revealOptions owner:controlView userInfo:revealInfo]; - [controlView addTrackingArea:area]; - - //action button - NSRect actionButtonRect = [self actionRectForBounds:cellFrame]; - NSTrackingAreaOptions actionOptions = options; - if (NSMouseInRect(mouseLocation, actionButtonRect, controlView.flipped)) - { - actionOptions |= NSTrackingAssumeInside; - ((TorrentTableView*)controlView).actionButtonHoverRow = [userInfo[@"Row"] integerValue]; - } - - NSMutableDictionary* actionInfo = [userInfo mutableCopy]; - actionInfo[@"Type"] = @"Action"; - area = [[NSTrackingArea alloc] initWithRect:actionButtonRect options:actionOptions owner:controlView userInfo:actionInfo]; - [controlView addTrackingArea:area]; -} - -- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView -{ - Torrent* torrent = self.representedObject; - NSAssert(torrent != nil, @"can't have a TorrentCell without a Torrent"); - - BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; - - //bar - [self drawBar:minimal ? [self barRectMinForBounds:cellFrame] : [self barRectRegForBounds:cellFrame]]; - - //group coloring - NSRect const parentRect = [self iconRectForBounds:cellFrame]; - NSRect iconRect = NSMakeRect(parentRect.origin.x, parentRect.origin.y, parentRect.size.width, parentRect.size.height); - - NSInteger const groupValue = torrent.groupValue; - if (groupValue != -1) - { - NSRect groupRect = [self groupIconRectForBounds:iconRect]; - NSColor* groupColor = [GroupsController.groups colorForIndex:groupValue]; - NSImage* icon = [NSImage discIconWithColor:groupColor insetFactor:0]; - [icon drawInRect:groupRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0f]; - } - - BOOL const error = torrent.anyErrorOrWarning; - - //icon - if (!minimal || !(!self.fTracking && self.hoverAction)) //don't show in minimal mode when hovered over - { - NSImage* icon = (minimal && error) ? [NSImage imageNamed:NSImageNameCaution] : torrent.icon; - [icon drawInRect:iconRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES - hints:nil]; - } - - //error badge - if (error && !minimal) - { - NSImage* errorImage = [NSImage imageNamed:NSImageNameCaution]; - NSRect const errorRect = NSMakeRect(NSMaxX(iconRect) - kErrorImageSize, NSMaxY(iconRect) - kErrorImageSize, kErrorImageSize, kErrorImageSize); - [errorImage drawInRect:errorRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 - respectFlipped:YES - hints:nil]; - } - - AttributesStyle style = self.backgroundStyle == NSBackgroundStyleEmphasized ? AttributesStyleEmphasized : AttributesStyleNormal; - - CGFloat titleRightBound; - //minimal status - if (minimal) - { - NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString style:style]; - NSRect minimalStatusRect = [self rectForMinimalStatusWithStringSize:[minimalString size] inBounds:cellFrame]; - - if (!self.hover) - { - [minimalString drawInRect:minimalStatusRect]; - } - - titleRightBound = NSMinX(minimalStatusRect); - } - //progress - else - { - NSAttributedString* progressString = [self attributedStatusString:torrent.progressString style:style]; - NSRect progressRect = [self rectForProgressWithStringInBounds:cellFrame]; - - [progressString drawInRect:progressRect]; - titleRightBound = NSMaxX(cellFrame); - } - - if (!minimal || self.hover) - { - //control button - NSString* controlImageSuffix; - if (self.fMouseDownControlButton) - { - controlImageSuffix = @"On"; - } - else if (!self.fTracking && self.hoverControl) - { - controlImageSuffix = @"Hover"; - } - else - { - controlImageSuffix = @"Off"; - } - - NSImage* controlImage; - if (torrent.active) - { - controlImage = [NSImage imageNamed:[@"Pause" stringByAppendingString:controlImageSuffix]]; - } - else - { - if (NSApp.currentEvent.modifierFlags & NSEventModifierFlagOption) - { - controlImage = [NSImage imageNamed:[@"ResumeNoWait" stringByAppendingString:controlImageSuffix]]; - } - else if (torrent.waitingToStart) - { - controlImage = [NSImage imageNamed:[@"Pause" stringByAppendingString:controlImageSuffix]]; - } - else - { - controlImage = [NSImage imageNamed:[@"Resume" stringByAppendingString:controlImageSuffix]]; - } - } - - NSRect const controlRect = [self controlButtonRectForBounds:cellFrame]; - [controlImage drawInRect:controlRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 - respectFlipped:YES - hints:nil]; - if (minimal) - { - titleRightBound = MIN(titleRightBound, NSMinX(controlRect)); - } - - //reveal button - NSString* revealImageString; - if (self.fMouseDownRevealButton) - { - revealImageString = @"RevealOn"; - } - else if (!self.fTracking && self.hoverReveal) - { - revealImageString = @"RevealHover"; - } - else - { - revealImageString = @"RevealOff"; - } - - NSImage* revealImage = [NSImage imageNamed:revealImageString]; - [revealImage drawInRect:[self revealButtonRectForBounds:cellFrame] fromRect:NSZeroRect - operation:NSCompositingOperationSourceOver - fraction:1.0 - respectFlipped:YES - hints:nil]; - - //action button -#warning image should use new gear - if (!self.fTracking && self.hoverAction) - { - NSImage* actionImage = [NSImage imageNamed:@"ActionHover"]; - [actionImage drawInRect:[self actionButtonRectForBounds:iconRect] fromRect:NSZeroRect - operation:NSCompositingOperationSourceOver - fraction:1.0 - respectFlipped:YES - hints:nil]; - } - } - - //title - NSAttributedString* titleString = [self attributedTitleWithStyle:style]; - NSRect titleRect = [self rectForTitleWithStringSize:[titleString size] withRightBound:titleRightBound inBounds:cellFrame - minimal:minimal]; - [titleString drawInRect:titleRect]; - - //priority icon - if (torrent.priority != TR_PRI_NORMAL) - { - NSRect const priorityRect = NSMakeRect( - NSMaxX(titleRect) + kPaddingBetweenTitleAndPriority, - NSMidY(titleRect) - kPriorityIconSize * 0.5, - kPriorityIconSize, - kPriorityIconSize); - - NSColor* priorityColor = self.backgroundStyle == NSBackgroundStyleEmphasized ? NSColor.whiteColor : NSColor.labelColor; - - NSImage* priorityImage = [[NSImage imageNamed:(torrent.priority == TR_PRI_HIGH ? @"PriorityHighTemplate" : @"PriorityLowTemplate")] - imageWithColor:priorityColor]; - [priorityImage drawInRect:priorityRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 - respectFlipped:YES - hints:nil]; - } - - //status - if (!minimal) - { - NSAttributedString* statusString = [self attributedStatusString:self.statusString style:style]; - [statusString drawInRect:[self rectForStatusWithStringInBounds:cellFrame]]; - } -} - -- (NSRect)expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView*)view -{ - BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; - - //this code needs to match the code in drawInteriorWithFrame:withView: - CGFloat titleRightBound; - if (minimal) - { - NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString style:AttributesStyleNormal]; - NSRect minimalStatusRect = [self rectForMinimalStatusWithStringSize:[minimalString size] inBounds:cellFrame]; - - titleRightBound = NSMinX(minimalStatusRect); - - if (self.hover) - { - NSRect const controlRect = [self controlButtonRectForBounds:cellFrame]; - titleRightBound = MIN(titleRightBound, NSMinX(controlRect)); - } - } - else - { - titleRightBound = NSMaxX(cellFrame); - } - - NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal]; - NSSize titleStringSize = [titleString size]; - NSRect realRect = [self rectForTitleWithStringSize:titleStringSize withRightBound:titleRightBound inBounds:cellFrame - minimal:minimal]; - - NSAssert(titleStringSize.width >= NSWidth(realRect), @"Full rect width should not be less than the used title rect width!"); - - if (titleStringSize.width > NSWidth(realRect) && - NSMouseInRect([view convertPoint:view.window.mouseLocationOutsideOfEventStream fromView:nil], realRect, view.flipped)) - { - realRect.size.width = titleStringSize.width; - return NSInsetRect(realRect, -kPaddingExpansionFrame, -kPaddingExpansionFrame); - } - - return NSZeroRect; -} - -- (void)drawWithExpansionFrame:(NSRect)cellFrame inView:(NSView*)view -{ - cellFrame.origin.x += kPaddingExpansionFrame; - cellFrame.origin.y += kPaddingExpansionFrame; - - NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal]; - [titleString drawInRect:cellFrame]; -} - -#pragma mark - Private - -- (void)drawBar:(NSRect)barRect -{ - BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; - - CGFloat const piecesBarPercent = ((TorrentTableView*)self.controlView).piecesBarPercent; - if (piecesBarPercent > 0.0) - { - NSRect piecesBarRect, regularBarRect; - NSDivideRect(barRect, &piecesBarRect, ®ularBarRect, floor(NSHeight(barRect) * kPiecesTotalPercent * piecesBarPercent), NSMaxYEdge); - - [self drawRegularBar:regularBarRect]; - [self drawPiecesBar:piecesBarRect]; - } - else - { - ((Torrent*)self.representedObject).previousFinishedPieces = nil; - - [self drawRegularBar:barRect]; - } - - NSColor* borderColor = minimal ? self.fBarMinimalBorderColor : self.fBarBorderColor; - [borderColor set]; - [NSBezierPath strokeRect:NSInsetRect(barRect, 0.5, 0.5)]; -} - -- (void)drawRegularBar:(NSRect)barRect -{ - Torrent* torrent = self.representedObject; - - NSRect haveRect, missingRect; - NSDivideRect(barRect, &haveRect, &missingRect, round(torrent.progress * NSWidth(barRect)), NSMinXEdge); - - if (!NSIsEmptyRect(haveRect)) - { - if (torrent.active) - { - if (torrent.checking) - { - [ProgressGradients.progressYellowGradient drawInRect:haveRect angle:90]; - } - else if (torrent.seeding) - { - NSRect ratioHaveRect, ratioRemainingRect; - NSDivideRect(haveRect, &ratioHaveRect, &ratioRemainingRect, round(torrent.progressStopRatio * NSWidth(haveRect)), NSMinXEdge); - - [ProgressGradients.progressGreenGradient drawInRect:ratioHaveRect angle:90]; - [ProgressGradients.progressLightGreenGradient drawInRect:ratioRemainingRect angle:90]; - } - else - { - [ProgressGradients.progressBlueGradient drawInRect:haveRect angle:90]; - } - } - else - { - if (torrent.waitingToStart) - { - if (torrent.allDownloaded) - { - [ProgressGradients.progressDarkGreenGradient drawInRect:haveRect angle:90]; - } - else - { - [ProgressGradients.progressDarkBlueGradient drawInRect:haveRect angle:90]; - } - } - else - { - [ProgressGradients.progressGrayGradient drawInRect:haveRect angle:90]; - } - } - } - - if (!torrent.allDownloaded) - { - CGFloat const widthRemaining = round(NSWidth(barRect) * torrent.progressLeft); - - NSRect wantedRect; - NSDivideRect(missingRect, &wantedRect, &missingRect, widthRemaining, NSMinXEdge); - - //not-available section - if (torrent.active && !torrent.checking && torrent.availableDesired < 1.0 && [self.fDefaults boolForKey:@"DisplayProgressBarAvailable"]) - { - NSRect unavailableRect; - NSDivideRect(wantedRect, &wantedRect, &unavailableRect, round(NSWidth(wantedRect) * torrent.availableDesired), NSMinXEdge); - - [ProgressGradients.progressRedGradient drawInRect:unavailableRect angle:90]; - } - - //remaining section - [ProgressGradients.progressWhiteGradient drawInRect:wantedRect angle:90]; - } - - //unwanted section - if (!NSIsEmptyRect(missingRect)) - { - if (!torrent.magnet) - { - [ProgressGradients.progressLightGrayGradient drawInRect:missingRect angle:90]; - } - else - { - [ProgressGradients.progressRedGradient drawInRect:missingRect angle:90]; - } - } -} - -- (void)drawPiecesBar:(NSRect)barRect -{ - Torrent* torrent = self.representedObject; - - //fill an all-white bar for magnet links - if (torrent.magnet) - { - [[NSColor colorWithCalibratedWhite:1.0 alpha:[self.fDefaults boolForKey:@"SmallView"] ? 0.25 : 1.0] set]; - NSRectFillUsingOperation(barRect, NSCompositingOperationSourceOver); - return; - } - - NSInteger pieceCount = MIN(torrent.pieceCount, kMaxPieces); - float* piecesPercent = static_cast(malloc(pieceCount * sizeof(float))); - [torrent getAmountFinished:piecesPercent size:pieceCount]; - - NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:pieceCount pixelsHigh:1 - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSCalibratedRGBColorSpace - bytesPerRow:0 - bitsPerPixel:0]; - - NSIndexSet* previousFinishedIndexes = torrent.previousFinishedPieces; - NSMutableIndexSet* finishedIndexes = [NSMutableIndexSet indexSet]; - - for (NSInteger i = 0; i < pieceCount; i++) - { - NSColor* pieceColor; - if (piecesPercent[i] == 1.0f) - { - if (previousFinishedIndexes && ![previousFinishedIndexes containsIndex:i]) - { - pieceColor = NSColor.orangeColor; - } - else - { - pieceColor = self.fBluePieceColor; - } - [finishedIndexes addIndex:i]; - } - else - { - pieceColor = [NSColor.whiteColor blendedColorWithFraction:piecesPercent[i] ofColor:self.fBluePieceColor]; - } - - //it's faster to just set color instead of checking previous color - // faster and non-broken alternative to `[bitmap setColor:pieceColor atX:i y:0]` - unsigned char* data = bitmap.bitmapData + (i << 2); - data[0] = pieceColor.redComponent * 255; - data[1] = pieceColor.greenComponent * 255; - data[2] = pieceColor.blueComponent * 255; - data[3] = pieceColor.alphaComponent * 255; - } - - free(piecesPercent); - - torrent.previousFinishedPieces = finishedIndexes.count > 0 ? finishedIndexes : nil; //don't bother saving if none are complete - - //actually draw image - [bitmap drawInRect:barRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver - fraction:[self.fDefaults boolForKey:@"SmallView"] ? 0.25 : 1.0 - respectFlipped:YES - hints:nil]; -} - -- (NSRect)rectForMinimalStatusWithStringSize:(NSSize)stringSize inBounds:(NSRect)bounds -{ - NSRect result; - result.size = stringSize; - - result.origin.x = NSMaxX(bounds) - (kPaddingHorizontal + NSWidth(result) + kPaddingEdgeMax); - result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5); - - return result; -} - -- (NSRect)rectForTitleWithStringSize:(NSSize)stringSize - withRightBound:(CGFloat)rightBound - inBounds:(NSRect)bounds - minimal:(BOOL)minimal -{ - NSRect result; - result.origin.x = NSMinX(bounds) + kPaddingHorizontal + (minimal ? kImageSizeMin : kImageSizeRegular) + kPaddingBetweenImageAndTitle; - result.size.height = kHeightTitle; - - if (minimal) - { - result.origin.x += kGroupPaddingMin; - result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5); - result.size.width = rightBound - NSMinX(result) - kPaddingBetweenTitleAndMinStatus; - } - else - { - result.origin.x += kGroupPaddingRegular; - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle; - result.size.width = rightBound - NSMinX(result) - kPaddingHorizontal - kPaddingEdgeMax; - } - - if (((Torrent*)self.representedObject).priority != TR_PRI_NORMAL) - { - result.size.width -= kPriorityIconSize + kPaddingBetweenTitleAndPriority; - } - result.size.width = MIN(NSWidth(result), stringSize.width); - - return result; -} - -- (NSRect)rectForProgressWithStringInBounds:(NSRect)bounds -{ - NSRect result; - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle + kHeightTitle + kPaddingBetweenTitleAndProgress; - result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kGroupPaddingRegular + kImageSizeRegular + kPaddingBetweenImageAndTitle; - - result.size.height = kHeightStatus; - result.size.width = NSMaxX(bounds) - NSMinX(result) - kPaddingHorizontal; - - return result; -} - -- (NSRect)rectForStatusWithStringInBounds:(NSRect)bounds -{ - NSRect result; - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle + kHeightTitle + kPaddingBetweenTitleAndProgress + kHeightStatus + - kPaddingBetweenProgressAndBar + kBarHeight + kPaddingBetweenBarAndStatus; - result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kGroupPaddingRegular + kImageSizeRegular + kPaddingBetweenImageAndTitle; - - result.size.height = kHeightStatus; - result.size.width = NSMaxX(bounds) - NSMinX(result) - kPaddingHorizontal; - - return result; -} - -- (NSRect)barRectRegForBounds:(NSRect)bounds -{ - NSRect result; - result.size.height = kBarHeight; - result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kGroupPaddingRegular + kImageSizeRegular + kPaddingBetweenImageAndBar; - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle + kHeightTitle + kPaddingBetweenTitleAndProgress + kHeightStatus + - kPaddingBetweenProgressAndBar; - - result.size.width = floor( - NSMaxX(bounds) - NSMinX(result) - kPaddingHorizontal - 2.0 * (kPaddingBetweenButtons + kNormalButtonWidth + kPaddingEdgeMax)); - - return result; -} - -- (NSRect)barRectMinForBounds:(NSRect)bounds -{ - NSRect result; - result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kImageSizeMin + kGroupPaddingMin + kPaddingBetweenImageAndBar; - result.origin.y = NSMinY(bounds) + kPaddingBetweenBarAndEdgeMin; - result.size.height = NSHeight(bounds) - 2.0 * kPaddingBetweenBarAndEdgeMin; - result.size.width = NSMaxX(bounds) - NSMinX(result) - kPaddingBetweenBarAndEdgeMin - kPaddingEdgeMax; - - return result; -} - -- (NSRect)controlButtonRectForBounds:(NSRect)bounds -{ - NSRect result; - result.size.height = kNormalButtonWidth; - result.size.width = kNormalButtonWidth; - result.origin.x = NSMaxX(bounds) - (kPaddingHorizontal + kNormalButtonWidth + kPaddingBetweenButtons + kNormalButtonWidth + kPaddingEdgeMax); - - if (![self.fDefaults boolForKey:@"SmallView"]) - { - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle + kHeightTitle - (kNormalButtonWidth - kBarHeight) * 0.5 + - kPaddingBetweenTitleAndProgress + kHeightStatus + kPaddingBetweenProgressAndBar; - } - else - { - result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5); - } - - return result; -} - -- (NSRect)revealButtonRectForBounds:(NSRect)bounds -{ - NSRect result; - result.size.height = kNormalButtonWidth; - result.size.width = kNormalButtonWidth; - result.origin.x = NSMaxX(bounds) - (kPaddingHorizontal + kNormalButtonWidth + kPaddingEdgeMax); - - if (![self.fDefaults boolForKey:@"SmallView"]) - { - result.origin.y = NSMinY(bounds) + kPaddingAboveTitle + kHeightTitle - (kNormalButtonWidth - kBarHeight) * 0.5 + - kPaddingBetweenTitleAndProgress + kHeightStatus + kPaddingBetweenProgressAndBar; - } - else - { - result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5); - } - - return result; -} - -- (NSRect)actionButtonRectForBounds:(NSRect)bounds -{ - return NSMakeRect(NSMidX(bounds) - kActionButtonWidth * 0.5, NSMidY(bounds) - kActionButtonWidth * 0.5, kActionButtonWidth, kActionButtonWidth); -} - -- (NSRect)groupIconRectForBounds:(NSRect)bounds -{ - BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; - CGFloat const imageSize = minimal ? kGroupImageSizeMin : kGroupImageSizeRegular; - CGFloat const padding = minimal ? kGroupPaddingMin + 2 : kGroupPaddingRegular + 1.5; - - return NSMakeRect(NSMinX(bounds) - padding * 0.5, NSMidY(bounds) - imageSize * 0.5, imageSize, imageSize); -} - -- (NSAttributedString*)attributedTitleWithStyle:(AttributesStyle)style -{ - NSString* title = ((Torrent*)self.representedObject).name; - return [[NSAttributedString alloc] initWithString:title - attributes:style == AttributesStyleEmphasized ? kTitleEmphasizedAttributes : kTitleAttributes]; -} - -- (NSAttributedString*)attributedStatusString:(NSString*)string style:(AttributesStyle)style -{ - return [[NSAttributedString alloc] initWithString:string - attributes:style == AttributesStyleEmphasized ? kStatusEmphasizedAttributes : kStatusAttributes]; -} - -- (NSString*)buttonString -{ - if (self.fMouseDownRevealButton || (!self.fTracking && self.hoverReveal)) - { - return NSLocalizedString(@"Show the data file in Finder", "Torrent cell -> button info"); - } - else if (self.fMouseDownControlButton || (!self.fTracking && self.hoverControl)) - { - Torrent* torrent = self.representedObject; - if (torrent.active) - return NSLocalizedString(@"Pause the transfer", "Torrent Table -> tooltip"); - else - { - if (NSApp.currentEvent.modifierFlags & NSEventModifierFlagOption) - { - return NSLocalizedString(@"Resume the transfer right away", "Torrent cell -> button info"); - } - else if (torrent.waitingToStart) - { - return NSLocalizedString(@"Stop waiting to start", "Torrent cell -> button info"); - } - else - { - return NSLocalizedString(@"Resume the transfer", "Torrent cell -> button info"); - } - } - } - else if (!self.fTracking && self.hoverAction) - { - return NSLocalizedString(@"Change transfer settings", "Torrent Table -> tooltip"); - } - else - { - return nil; - } -} - -- (NSString*)statusString -{ - NSString* buttonString; - if ((buttonString = self.buttonString)) - { - return buttonString; - } - else - { - return ((Torrent*)self.representedObject).statusString; - } -} - -- (NSString*)minimalStatusString -{ - Torrent* torrent = self.representedObject; - return [self.fDefaults boolForKey:@"DisplaySmallStatusRegular"] ? torrent.shortStatusString : torrent.remainingTimeString; -} - @end diff --git a/macosx/TorrentCellActionButton.h b/macosx/TorrentCellActionButton.h new file mode 100644 index 000000000..d557e1789 --- /dev/null +++ b/macosx/TorrentCellActionButton.h @@ -0,0 +1,8 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@interface TorrentCellActionButton : NSButton +@end diff --git a/macosx/TorrentCellActionButton.mm b/macosx/TorrentCellActionButton.mm new file mode 100644 index 000000000..77357a3ff --- /dev/null +++ b/macosx/TorrentCellActionButton.mm @@ -0,0 +1,87 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "TorrentCellActionButton.h" +#import "TorrentTableView.h" +#import "Torrent.h" + +@interface TorrentCellActionButton () +@property(nonatomic) NSTrackingArea* fTrackingArea; +@property(nonatomic) NSImage* fImage; +@property(nonatomic) NSImage* fAlternativeImage; +@property(nonatomic) TorrentTableView* torrentTableView; +@property(nonatomic) NSUserDefaults* fDefaults; +@end + +@implementation TorrentCellActionButton + +- (void)awakeFromNib +{ + self.fDefaults = NSUserDefaults.standardUserDefaults; + self.fImage = self.image; + + // hide image by default and show only on hover + self.fAlternativeImage = [[NSImage alloc] init]; + self.image = self.fAlternativeImage; + + // disable button click highlighting + [self.cell setHighlightsBy:NSNoCellMask]; +} + +- (void)setupTorrentTableView +{ + if (!self.torrentTableView) + { + self.torrentTableView = (TorrentTableView*)[[[self superview] superview] superview]; + } +} + +- (void)mouseEntered:(NSEvent*)event +{ + [super mouseEntered:event]; + + self.image = self.fImage; + + [self setupTorrentTableView]; + [self.torrentTableView hoverEventBeganForView:self]; +} + +- (void)mouseExited:(NSEvent*)event +{ + [super mouseExited:event]; + + self.image = self.fAlternativeImage; + + [self setupTorrentTableView]; + [self.torrentTableView hoverEventEndedForView:self]; +} + +- (void)mouseDown:(NSEvent*)event +{ + //when filterbar is shown, we need to remove focus otherwise action fails + [self.window makeFirstResponder:self.torrentTableView]; + + [super mouseDown:event]; + + BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; + if (!minimal) + { + [self setupTorrentTableView]; + [self.torrentTableView hoverEventEndedForView:self]; + } +} + +- (void)updateTrackingAreas +{ + if (self.fTrackingArea != nil) + { + [self removeTrackingArea:self.fTrackingArea]; + } + + NSTrackingAreaOptions opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); + self.fTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:opts owner:self userInfo:nil]; + [self addTrackingArea:self.fTrackingArea]; +} + +@end diff --git a/macosx/TorrentCellControlButton.h b/macosx/TorrentCellControlButton.h new file mode 100644 index 000000000..13d1be13e --- /dev/null +++ b/macosx/TorrentCellControlButton.h @@ -0,0 +1,11 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@interface TorrentCellControlButton : NSButton + +- (void)resetImage; + +@end diff --git a/macosx/TorrentCellControlButton.mm b/macosx/TorrentCellControlButton.mm new file mode 100644 index 000000000..cc4fc34d0 --- /dev/null +++ b/macosx/TorrentCellControlButton.mm @@ -0,0 +1,107 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "TorrentCellControlButton.h" +#import "TorrentTableView.h" +#import "Torrent.h" + +@interface TorrentCellControlButton () +@property(nonatomic) NSTrackingArea* fTrackingArea; +@property(nonatomic, copy) NSString* controlImageSuffix; +@property(nonatomic) TorrentTableView* torrentTableView; +@end + +@implementation TorrentCellControlButton + +- (void)awakeFromNib +{ + self.controlImageSuffix = @"Off"; + [self updateImage]; +} + +- (void)resetImage +{ + self.controlImageSuffix = @"Off"; + [self updateImage]; +} + +- (void)setupTorrentTableView +{ + if (!self.torrentTableView) + { + self.torrentTableView = (TorrentTableView*)[[[self superview] superview] superview]; + } +} + +- (void)mouseEntered:(NSEvent*)event +{ + [super mouseEntered:event]; + self.controlImageSuffix = @"Hover"; + [self updateImage]; + + [self.torrentTableView hoverEventBeganForView:self]; +} + +- (void)mouseExited:(NSEvent*)event +{ + [super mouseExited:event]; + self.controlImageSuffix = @"Off"; + [self updateImage]; + + [self.torrentTableView hoverEventEndedForView:self]; +} + +- (void)mouseDown:(NSEvent*)event +{ + //when filterbar is shown, we need to remove focus otherwise action fails + [self.window makeFirstResponder:self.torrentTableView]; + + [super mouseDown:event]; + self.controlImageSuffix = @"On"; + [self updateImage]; + + [self.torrentTableView hoverEventEndedForView:self]; +} + +- (void)updateImage +{ + [self setupTorrentTableView]; + + NSImage* controlImage; + Torrent* torrent = [self.torrentTableView itemAtRow:[self.torrentTableView rowForView:self]]; + if (torrent.active) + { + controlImage = [NSImage imageNamed:[@"Pause" stringByAppendingString:self.controlImageSuffix]]; + } + else + { + if (NSApp.currentEvent.modifierFlags & NSEventModifierFlagOption) + { + controlImage = [NSImage imageNamed:[@"ResumeNoWait" stringByAppendingString:self.controlImageSuffix]]; + } + else if (torrent.waitingToStart) + { + controlImage = [NSImage imageNamed:[@"Pause" stringByAppendingString:self.controlImageSuffix]]; + } + else + { + controlImage = [NSImage imageNamed:[@"Resume" stringByAppendingString:self.controlImageSuffix]]; + } + } + self.image = controlImage; +} + +- (void)updateTrackingAreas +{ + if (self.fTrackingArea != nil) + { + [self removeTrackingArea:self.fTrackingArea]; + } + + NSTrackingAreaOptions opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); + self.fTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:opts owner:self userInfo:nil]; + [self addTrackingArea:self.fTrackingArea]; +} + +@end diff --git a/macosx/TorrentCellRevealButton.h b/macosx/TorrentCellRevealButton.h new file mode 100644 index 000000000..515f44e8d --- /dev/null +++ b/macosx/TorrentCellRevealButton.h @@ -0,0 +1,8 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@interface TorrentCellRevealButton : NSButton +@end diff --git a/macosx/TorrentCellRevealButton.mm b/macosx/TorrentCellRevealButton.mm new file mode 100644 index 000000000..e3f1f1cf3 --- /dev/null +++ b/macosx/TorrentCellRevealButton.mm @@ -0,0 +1,79 @@ +// This file Copyright © 2006-2023 Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "TorrentCellRevealButton.h" +#import "TorrentTableView.h" + +@interface TorrentCellRevealButton () +@property(nonatomic) NSTrackingArea* fTrackingArea; +@property(nonatomic, copy) NSString* revealImageString; +@property(nonatomic) TorrentTableView* torrentTableView; +@end + +@implementation TorrentCellRevealButton + +- (void)awakeFromNib +{ + self.revealImageString = @"RevealOff"; + [self updateImage]; +} + +- (void)setupTorrentTableView +{ + if (!self.torrentTableView) + { + self.torrentTableView = (TorrentTableView*)[[[self superview] superview] superview]; + } +} + +- (void)mouseEntered:(NSEvent*)event +{ + [super mouseEntered:event]; + self.revealImageString = @"RevealHover"; + [self updateImage]; + + [self.torrentTableView hoverEventBeganForView:self]; +} + +- (void)mouseExited:(NSEvent*)event +{ + [super mouseExited:event]; + self.revealImageString = @"RevealOff"; + [self updateImage]; + + [self.torrentTableView hoverEventEndedForView:self]; +} + +- (void)mouseDown:(NSEvent*)event +{ + //when filterbar is shown, we need to remove focus otherwise action fails + [self.window makeFirstResponder:self.torrentTableView]; + + [super mouseDown:event]; + self.revealImageString = @"RevealOn"; + [self updateImage]; +} + +- (void)updateImage +{ + [self setupTorrentTableView]; + + NSImage* revealImage = [NSImage imageNamed:self.revealImageString]; + self.image = revealImage; + [self setNeedsDisplay:YES]; +} + +- (void)updateTrackingAreas +{ + if (self.fTrackingArea != nil) + { + [self removeTrackingArea:self.fTrackingArea]; + } + + NSTrackingAreaOptions opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); + self.fTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:opts owner:self userInfo:nil]; + [self addTrackingArea:self.fTrackingArea]; +} + +@end diff --git a/macosx/TorrentTableView.h b/macosx/TorrentTableView.h index c3a8c9380..ad1fb2bf6 100644 --- a/macosx/TorrentTableView.h +++ b/macosx/TorrentTableView.h @@ -10,19 +10,15 @@ extern const CGFloat kGroupSeparatorHeight; @interface TorrentTableView : NSOutlineView +- (void)reloadVisibleRows; + - (BOOL)isGroupCollapsed:(NSInteger)value; - (void)removeCollapsedGroup:(NSInteger)value; - (void)removeAllCollapsedGroups; - (void)saveCollapsedGroups; -- (void)removeTrackingAreas; -@property(nonatomic) NSInteger hoverRow; -@property(nonatomic) NSInteger controlButtonHoverRow; -@property(nonatomic) NSInteger revealButtonHoverRow; -@property(nonatomic) NSInteger actionButtonHoverRow; +- (void)restoreSelectionIndexes; -- (void)selectValues:(NSArray*)values; -@property(nonatomic, readonly) NSArray* selectedValues; @property(nonatomic, readonly) NSArray* selectedTorrents; - (NSRect)iconRectForRow:(NSInteger)row; @@ -30,9 +26,14 @@ extern const CGFloat kGroupSeparatorHeight; - (void)copy:(id)sender; - (void)paste:(id)sender; -- (void)toggleControlForTorrent:(Torrent*)torrent; +- (void)hoverEventBeganForView:(id)view; +- (void)hoverEventEndedForView:(id)view; -- (void)displayTorrentActionPopoverForEvent:(NSEvent*)event; +- (void)toggleGroupRowRatio; + +- (IBAction)toggleControlForTorrent:(id)sender; + +- (IBAction)displayTorrentActionPopover:(id)sender; - (IBAction)setQuickLimitMode:(id)sender; - (void)setQuickLimit:(id)sender; diff --git a/macosx/TorrentTableView.mm b/macosx/TorrentTableView.mm index b88604772..ca6419d19 100644 --- a/macosx/TorrentTableView.mm +++ b/macosx/TorrentTableView.mm @@ -12,12 +12,19 @@ #import "NSStringAdditions.h" #import "Torrent.h" #import "TorrentCell.h" +#import "SmallTorrentCell.h" +#import "GroupCell.h" #import "TorrentGroup.h" -#import "GroupTextCell.h" +#import "GroupsController.h" +#import "NSImageAdditions.h" +#import "TorrentCellActionButton.h" +#import "TorrentCellControlButton.h" +#import "TorrentCellRevealButton.h" CGFloat const kGroupSeparatorHeight = 18.0; static NSInteger const kMaxGroup = 999999; +static CGFloat const kErrorImageSize = 20.0; //eliminate when Lion-only typedef NS_ENUM(NSUInteger, ActionMenuTag) { @@ -38,9 +45,6 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; @property(nonatomic) IBOutlet Controller* fController; -@property(nonatomic) TorrentCell* fTorrentCell; -@property(nonatomic) GroupTextCell* fGroupTextCell; - @property(nonatomic, readonly) NSUserDefaults* fDefaults; @property(nonatomic, readonly) NSMutableIndexSet* fCollapsedGroups; @@ -48,7 +52,7 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; @property(nonatomic) IBOutlet NSMenu* fContextRow; @property(nonatomic) IBOutlet NSMenu* fContextNoRow; -@property(nonatomic) NSArray* fSelectedValues; +@property(nonatomic) NSIndexSet* fSelectedRowIndexes; @property(nonatomic) IBOutlet NSMenu* fActionMenu; @property(nonatomic) IBOutlet NSMenu* fUploadMenu; @@ -64,6 +68,8 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; @property(nonatomic) BOOL fActionPopoverShown; @property(nonatomic) NSView* fPositioningView; +@property(nonatomic) NSDictionary* fHoverEventDict; + @end @implementation TorrentTableView @@ -74,9 +80,6 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; { _fDefaults = NSUserDefaults.standardUserDefaults; - _fTorrentCell = [[TorrentCell alloc] init]; - _fGroupTextCell = [[GroupTextCell alloc] init]; - NSData* groupData; if ((groupData = [_fDefaults dataForKey:@"CollapsedGroupIndexes"])) { @@ -93,14 +96,10 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; _fCollapsedGroups = [[NSMutableIndexSet alloc] init]; } - _hoverRow = -1; - _controlButtonHoverRow = -1; - _revealButtonHoverRow = -1; - _actionButtonHoverRow = -1; - _fActionPopoverShown = NO; self.delegate = self; + self.indentationPerLevel = 0; _piecesBarPercent = [_fDefaults boolForKey:@"PiecesBar"] ? 1.0 : 0.0; @@ -120,19 +119,74 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; - (void)awakeFromNib { - //set group columns to show ratio, needs to be in awakeFromNib to size columns correctly - [self setGroupStatusColumns]; - - //disable highlight color and set manually in drawRow - [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone]; - - [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(refreshTorrentTable) name:@"RefreshTorrentTable" - object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(setNeedsDisplay) name:@"RefreshTorrentTable" object:nil]; } -- (void)refreshTorrentTable +//make sure we don't lose selection on manual reloads +- (void)reloadData { - self.needsDisplay = YES; + [super reloadData]; + [self restoreSelectionIndexes]; +} + +- (void)reloadVisibleRows +{ + NSRect visibleRect = self.visibleRect; + NSRange range = [self rowsInRect:visibleRect]; + + //since we use floating group rows, we need some magic to find visible group rows + if ([self.fDefaults boolForKey:@"SortByGroup"]) + { + NSInteger location = range.location; + NSInteger length = range.length; + NSRange fullRange = NSMakeRange(0, length + location); + NSIndexSet* fullIndexSet = [NSIndexSet indexSetWithIndexesInRange:fullRange]; + NSMutableIndexSet* visibleIndexSet = [[NSMutableIndexSet alloc] init]; + + [fullIndexSet enumerateIndexesUsingBlock:^(NSUInteger row, BOOL* stop) { + id rowView = [self rowViewAtRow:row makeIfNecessary:NO]; + if ([rowView isGroupRowStyle]) + { + [visibleIndexSet addIndex:row]; + } + else if (NSIntersectsRect(visibleRect, [self rectOfRow:row])) + { + [visibleIndexSet addIndex:row]; + } + }]; + + [self reloadDataForRowIndexes:visibleIndexSet columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } + else + { + [self reloadDataForRowIndexes:[NSIndexSet indexSetWithIndexesInRange:range] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } +} + +- (void)reloadDataForRowIndexes:(NSIndexSet*)rowIndexes columnIndexes:(NSIndexSet*)columnIndexes +{ + [super reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes]; + + //redraw fControlButton + BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; + [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger row, BOOL* stop) { + id rowView = [self rowViewAtRow:row makeIfNecessary:NO]; + if (![rowView isGroupRowStyle]) + { + if (minimal) + { + SmallTorrentCell* smallCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + [(TorrentCellControlButton*)smallCell.fControlButton resetImage]; + } + else + { + TorrentCell* torrentCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + [(TorrentCellControlButton*)torrentCell.fControlButton resetImage]; + } + } + }]; + + [self restoreSelectionIndexes]; } - (BOOL)isGroupCollapsed:(NSInteger)value @@ -168,9 +222,11 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; - (BOOL)outlineView:(NSOutlineView*)outlineView isGroupItem:(id)item { - //return no and style the groupItem cell manually in willDisplayCell - //otherwise we get unwanted padding before each group header - return NO; + if ([item isKindOfClass:[Torrent class]]) + { + return NO; + } + return YES; } - (CGFloat)outlineView:(NSOutlineView*)outlineView heightOfRowByItem:(id)item @@ -178,119 +234,207 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; return [item isKindOfClass:[Torrent class]] ? self.rowHeight : kGroupSeparatorHeight; } -- (NSCell*)outlineView:(NSOutlineView*)outlineView dataCellForTableColumn:(NSTableColumn*)tableColumn item:(id)item -{ - BOOL const group = ![item isKindOfClass:[Torrent class]]; - if (!tableColumn) - { - return !group ? self.fTorrentCell : nil; - } - else - { - NSString* ident = tableColumn.identifier; - NSArray* imageColumns = @[ @"Color", @"DL Image", @"UL Image" ]; - if (![imageColumns containsObject:ident]) - { - return group ? self.fGroupTextCell : nil; - } - else - { - return group ? [tableColumn dataCellForRow:[self rowForItem:item]] : nil; - } - } -} - -- (void)outlineView:(NSOutlineView*)outlineView - willDisplayCell:(id)cell - forTableColumn:(NSTableColumn*)tableColumn - item:(id)item +- (NSView*)outlineView:(NSOutlineView*)outlineView viewForTableColumn:(NSTableColumn*)tableColumn item:(id)item { if ([item isKindOfClass:[Torrent class]]) { - if (!tableColumn) + Torrent* torrent = (Torrent*)item; + BOOL const minimal = [self.fDefaults boolForKey:@"SmallView"]; + BOOL const error = torrent.anyErrorOrWarning; + + if (minimal) { - TorrentCell* torrentCell = cell; - torrentCell.representedObject = item; + SmallTorrentCell* smallCell = [outlineView makeViewWithIdentifier:@"SmallTorrentCell" owner:self]; + smallCell.fTorrentTableView = self; - NSInteger const row = [self rowForItem:item]; - torrentCell.hover = (row == self.hoverRow); - torrentCell.hoverControl = (row == self.controlButtonHoverRow); - torrentCell.hoverReveal = (row == self.revealButtonHoverRow); - torrentCell.hoverAction = (row == self.actionButtonHoverRow); + //set this so that we can draw bar in smallCell drawRect + smallCell.objectValue = torrent; - // if cell is selected, set backgroundStyle - // then can provide alternate font color in TorrentCell - drawInteriorWithFrame - NSIndexSet* selectedRowIndexes = self.selectedRowIndexes; - if ([selectedRowIndexes containsIndex:row]) + smallCell.fTorrentTitleField.stringValue = torrent.name; + + //set torrent icon + smallCell.fIconView.image = error ? [NSImage imageNamed:NSImageNameCaution] : torrent.icon; + + smallCell.fTorrentStatusField.stringValue = [self.fDefaults boolForKey:@"DisplaySmallStatusRegular"] ? + torrent.shortStatusString : + torrent.remainingTimeString; + ; + smallCell.fActionButton.action = @selector(displayTorrentActionPopover:); + + NSInteger const groupValue = torrent.groupValue; + NSImage* groupImage; + if (groupValue != -1) { - torrentCell.backgroundStyle = NSBackgroundStyleEmphasized; + if (![self.fDefaults boolForKey:@"SortByGroup"]) + { + NSColor* groupColor = [GroupsController.groups colorForIndex:groupValue]; + groupImage = [NSImage discIconWithColor:groupColor insetFactor:0]; + } } - } - } - else - { - if ([cell isKindOfClass:[GroupTextCell class]]) - { - GroupTextCell* groupCell = cell; - // if cell is selected, set selected flag so we can style the title in GroupTextCell drawInteriorWithFrame - NSInteger const row = [self rowForItem:item]; - NSIndexSet* selectedRowIndexes = self.selectedRowIndexes; - groupCell.selected = [selectedRowIndexes containsIndex:row]; - } - } -} + smallCell.fGroupIndicatorView.image = groupImage; -//we override row highlighting because we are custom drawing the group rows -//see isGroupItem -- (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect -{ - NSColor* highlightColor = nil; + smallCell.fControlButton.action = @selector(toggleControlForTorrent:); + smallCell.fRevealButton.action = @selector(revealTorrentFile:); - id item = [self itemAtRow:row]; + if (self.fHoverEventDict) + { + NSInteger row = [self rowForItem:item]; + NSInteger hoverRow = [self.fHoverEventDict[@"row"] integerValue]; - //we only highlight torrent cells - if ([item isKindOfClass:[Torrent class]]) - { - //use system highlight color when Transmission is active - if (self == [self.window firstResponder] && [self.window isMainWindow] && [self.window isKeyWindow]) - { - highlightColor = [NSColor alternateSelectedControlColor]; + if (row == hoverRow) + { + smallCell.fTorrentStatusField.hidden = YES; + smallCell.fControlButton.hidden = NO; + smallCell.fRevealButton.hidden = NO; + } + } + else + { + smallCell.fTorrentStatusField.hidden = NO; + smallCell.fControlButton.hidden = YES; + smallCell.fRevealButton.hidden = YES; + } + + //redraw buttons + [smallCell.fControlButton display]; + [smallCell.fRevealButton display]; + + return smallCell; } else { - highlightColor = [NSColor disabledControlTextColor]; + TorrentCell* torrentCell = [outlineView makeViewWithIdentifier:@"TorrentCell" owner:self]; + torrentCell.fTorrentTableView = self; + + //set this so that we can draw bar in torrentCell drawRect + torrentCell.objectValue = torrent; + + torrentCell.fTorrentTitleField.stringValue = torrent.name; + torrentCell.fTorrentProgressField.stringValue = torrent.progressString; + + //set torrent icon + NSImage* fileImage = torrent.icon; + + //error badge + if (error) + { + NSRect frame = torrentCell.fIconView.frame; + NSImage* resultImage = [[NSImage alloc] initWithSize:NSMakeSize(frame.size.height, frame.size.width)]; + [resultImage lockFocus]; + + //draw fileImage + [fileImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; + + //overlay error badge + NSImage* errorImage = [NSImage imageNamed:NSImageNameCaution]; + NSRect const errorRect = NSMakeRect(frame.origin.x, 0, kErrorImageSize, kErrorImageSize); + [errorImage drawInRect:errorRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 + respectFlipped:YES + hints:nil]; + + [resultImage unlockFocus]; + + torrentCell.fIconView.image = resultImage; + } + else + { + torrentCell.fIconView.image = fileImage; + } + + NSString* status; + if (self.fHoverEventDict) + { + NSInteger row = [self rowForItem:item]; + NSInteger hoverRow = [self.fHoverEventDict[@"row"] integerValue]; + + if (row == hoverRow) + { + status = self.fHoverEventDict[@"string"]; + } + } + torrentCell.fTorrentStatusField.stringValue = status ? status : torrent.statusString; + torrentCell.fActionButton.action = @selector(displayTorrentActionPopover:); + + NSInteger const groupValue = torrent.groupValue; + NSImage* groupImage; + if (groupValue != -1) + { + if (![self.fDefaults boolForKey:@"SortByGroup"]) + { + NSColor* groupColor = [GroupsController.groups colorForIndex:groupValue]; + groupImage = [NSImage discIconWithColor:groupColor insetFactor:0]; + } + } + + torrentCell.fGroupIndicatorView.image = groupImage; + + torrentCell.fControlButton.action = @selector(toggleControlForTorrent:); + torrentCell.fRevealButton.action = @selector(revealTorrentFile:); + + //redraw buttons + [torrentCell.fControlButton display]; + [torrentCell.fRevealButton display]; + + return torrentCell; } - - NSIndexSet* selectedRowIndexes = [self selectedRowIndexes]; - if ([selectedRowIndexes containsIndex:row]) - { - [highlightColor setFill]; - NSRectFill([self rectOfRow:row]); - } - } - - [super drawRow:row clipRect:clipRect]; -} - -- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row -{ - if (column == -1) - { - return [self rectOfRow:row]; } else { - NSRect rect = [super frameOfCellAtColumn:column row:row]; + TorrentGroup* group = (TorrentGroup*)item; + GroupCell* groupCell = [outlineView makeViewWithIdentifier:@"GroupCell" owner:self]; - //adjust placement for proper vertical alignment - if (column == [self columnWithIdentifier:@"Group"]) + NSInteger groupIndex = group.groupIndex; + + NSColor* groupColor = groupIndex != -1 ? [GroupsController.groups colorForIndex:groupIndex] : + [NSColor colorWithWhite:1.0 alpha:0]; + groupCell.fGroupIndicatorView.image = [NSImage discIconWithColor:groupColor insetFactor:0]; + + NSString* groupName = groupIndex != -1 ? [GroupsController.groups nameForIndex:groupIndex] : + NSLocalizedString(@"No Group", "Group table row"); + + NSInteger row = [self rowForItem:item]; + if ([self isRowSelected:row]) { - rect.size.height -= 1.0f; + NSMutableAttributedString* string = [[NSMutableAttributedString alloc] initWithString:groupName]; + NSDictionary* attributes = @{ + NSFontAttributeName : [NSFont boldSystemFontOfSize:11.0], + NSForegroundColorAttributeName : [NSColor labelColor] + }; + + [string addAttributes:attributes range:NSMakeRange(0, string.length)]; + groupCell.fGroupTitleField.attributedStringValue = string; + } + else + { + groupCell.fGroupTitleField.stringValue = groupName; } - return rect; + groupCell.fGroupDownloadField.stringValue = [NSString stringForSpeed:group.downloadRate]; + groupCell.fGroupDownloadView.image = [NSImage imageNamed:@"DownArrowGroupTemplate"]; + + BOOL displayGroupRowRatio = [self.fDefaults boolForKey:@"DisplayGroupRowRatio"]; + groupCell.fGroupDownloadField.hidden = displayGroupRowRatio; + groupCell.fGroupDownloadView.hidden = displayGroupRowRatio; + + if (displayGroupRowRatio) + { + groupCell.fGroupUploadAndRatioView.image = [NSImage imageNamed:@"YingYangGroupTemplate"]; + groupCell.fGroupUploadAndRatioView.image.accessibilityDescription = NSLocalizedString(@"Ratio", "Torrent -> status image"); + + groupCell.fGroupUploadAndRatioField.stringValue = [NSString stringForRatio:group.ratio]; + } + else + { + groupCell.fGroupUploadAndRatioView.image = [NSImage imageNamed:@"UpArrowGroupTemplate"]; + groupCell.fGroupUploadAndRatioView.image.accessibilityDescription = NSLocalizedString(@"UL", "Torrent -> status image"); + + groupCell.fGroupUploadAndRatioField.stringValue = [NSString stringForSpeed:group.uploadRate]; + } + + return groupCell; } + return nil; } - (NSString*)outlineView:(NSOutlineView*)outlineView typeSelectStringForTableColumn:(NSTableColumn*)tableColumn item:(id)item @@ -342,160 +486,10 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; } } -- (void)updateTrackingAreas +- (void)outlineViewSelectionDidChange:(NSNotification*)notification { - [super updateTrackingAreas]; - [self removeTrackingAreas]; - - NSRange const rows = [self rowsInRect:self.visibleRect]; - if (rows.length == 0) - { - return; - } - - NSPoint mouseLocation = [self convertPoint:self.window.mouseLocationOutsideOfEventStream fromView:nil]; - for (NSUInteger row = rows.location; row < NSMaxRange(rows); row++) - { - if (![[self itemAtRow:row] isKindOfClass:[Torrent class]]) - { - continue; - } - - NSDictionary* userInfo = @{ @"Row" : @(row) }; - TorrentCell* cell = (TorrentCell*)[self preparedCellAtColumn:-1 row:row]; - [cell addTrackingAreasForView:self inRect:[self rectOfRow:row] withUserInfo:userInfo mouseLocation:mouseLocation]; - } -} - -- (void)removeTrackingAreas -{ - _hoverRow = -1; - _controlButtonHoverRow = -1; - _revealButtonHoverRow = -1; - _actionButtonHoverRow = -1; - - for (NSTrackingArea* area in self.trackingAreas) - { - if (area.owner == self && area.userInfo[@"Row"]) - { - [self removeTrackingArea:area]; - } - } -} - -- (void)setHoverRow:(NSInteger)row -{ - NSAssert([self.fDefaults boolForKey:@"SmallView"], @"cannot set a hover row when not in compact view"); - - _hoverRow = row; - if (row >= 0) - { - [self setNeedsDisplayInRect:[self rectOfRow:row]]; - } -} - -- (void)setControlButtonHoverRow:(NSInteger)row -{ - _controlButtonHoverRow = row; - if (row >= 0) - { - [self setNeedsDisplayInRect:[self rectOfRow:row]]; - } -} - -- (void)setRevealButtonHoverRow:(NSInteger)row -{ - _revealButtonHoverRow = row; - if (row >= 0) - { - [self setNeedsDisplayInRect:[self rectOfRow:row]]; - } -} - -- (void)setActionButtonHoverRow:(NSInteger)row -{ - _actionButtonHoverRow = row; - if (row >= 0) - { - [self setNeedsDisplayInRect:[self rectOfRow:row]]; - } -} - -- (void)mouseEntered:(NSEvent*)event -{ - NSDictionary* dict = (NSDictionary*)event.userData; - - NSNumber* row; - if ((row = dict[@"Row"])) - { - NSInteger rowVal = row.integerValue; - NSString* type = dict[@"Type"]; - if ([type isEqualToString:@"Action"]) - { - _actionButtonHoverRow = rowVal; - } - else if ([type isEqualToString:@"Control"]) - { - _controlButtonHoverRow = rowVal; - } - else if ([type isEqualToString:@"Reveal"]) - { - _revealButtonHoverRow = rowVal; - } - else - { - _hoverRow = rowVal; - if (![self.fDefaults boolForKey:@"SmallView"]) - { - return; - } - } - - [self setNeedsDisplayInRect:[self rectOfRow:rowVal]]; - } -} - -- (void)mouseExited:(NSEvent*)event -{ - NSDictionary* dict = (NSDictionary*)event.userData; - - NSNumber* row; - if ((row = dict[@"Row"])) - { - NSString* type = dict[@"Type"]; - if ([type isEqualToString:@"Action"]) - { - _actionButtonHoverRow = -1; - } - else if ([type isEqualToString:@"Control"]) - { - _controlButtonHoverRow = -1; - } - else if ([type isEqualToString:@"Reveal"]) - { - _revealButtonHoverRow = -1; - } - else - { - _hoverRow = -1; - if (![self.fDefaults boolForKey:@"SmallView"]) - { - return; - } - } - - [self setNeedsDisplayInRect:[self rectOfRow:row.integerValue]]; - } -} - -- (void)outlineViewSelectionIsChanging:(NSNotification*)notification -{ -#warning eliminate when view-based? - //if pushing a button, don't change the selected rows - if (self.fSelectedValues) - { - [self selectValues:self.fSelectedValues]; - } + self.fSelectedRowIndexes = self.selectedRowIndexes; + [self reloadVisibleRows]; } - (void)outlineViewItemDidExpand:(NSNotification*)notification @@ -532,42 +526,16 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; NSPoint point = [self convertPoint:event.locationInWindow fromView:nil]; NSInteger const row = [self rowAtPoint:point]; - //check to toggle group status before anything else - if ([self pointInGroupStatusRect:point]) - { - [self.fDefaults setBool:![self.fDefaults boolForKey:@"DisplayGroupRowRatio"] forKey:@"DisplayGroupRowRatio"]; - [self setGroupStatusColumns]; - - return; - } - - BOOL const pushed = row != -1 && - (self.actionButtonHoverRow == row || self.revealButtonHoverRow == row || self.controlButtonHoverRow == row); - - //if pushing a button, don't change the selected rows - if (pushed) - { - self.fSelectedValues = self.selectedValues; - } - [super mouseDown:event]; - self.fSelectedValues = nil; - - //avoid weird behavior when showing menu by doing this after mouse down - if (row != -1 && self.actionButtonHoverRow == row) + id item = nil; + if (row != -1) { -#warning maybe make appear on mouse down - [self displayTorrentActionPopoverForEvent:event]; + item = [self itemAtRow:row]; } - else if (!pushed && event.clickCount == 2) //double click - { - id item = nil; - if (row != -1) - { - item = [self itemAtRow:row]; - } + if (event.clickCount == 2) //double click + { if (!item || [item isKindOfClass:[Torrent class]]) { [self.fController showInfo:nil]; @@ -584,52 +552,12 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; } } } -} - -- (void)selectValues:(NSArray*)values -{ - NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; - - for (id item in values) + else if ([self pointInGroupStatusRect:point]) { - if ([item isKindOfClass:[Torrent class]]) - { - NSInteger const index = [self rowForItem:item]; - if (index != -1) - { - [indexSet addIndex:index]; - } - } - else - { - TorrentGroup* group = (TorrentGroup*)item; - NSInteger const groupIndex = group.groupIndex; - for (NSInteger i = 0; i < self.numberOfRows; i++) - { - id tableItem = [self itemAtRow:i]; - if ([tableItem isKindOfClass:[TorrentGroup class]] && groupIndex == ((TorrentGroup*)tableItem).groupIndex) - { - [indexSet addIndex:i]; - break; - } - } - } + //we check for this here rather than in the GroupCell + //as using floating group rows causes all sorts of weirdness... + [self toggleGroupRowRatio]; } - - [self selectRowIndexes:indexSet byExtendingSelection:NO]; -} - -- (NSArray*)selectedValues -{ - NSIndexSet* selectedIndexes = self.selectedRowIndexes; - NSMutableArray* values = [NSMutableArray arrayWithCapacity:selectedIndexes.count]; - - for (NSUInteger i = selectedIndexes.firstIndex; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex:i]) - { - [values addObject:[self itemAtRow:i]]; - } - - return values; } - (NSArray*)selectedTorrents @@ -676,6 +604,11 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; } } +- (void)restoreSelectionIndexes +{ + [self selectRowIndexes:self.fSelectedRowIndexes byExtendingSelection:NO]; +} + //make sure that the pause buttons become orange when holding down the option key - (void)flagsChanged:(NSEvent*)event { @@ -708,7 +641,24 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; - (NSRect)iconRectForRow:(NSInteger)row { - return [self.fTorrentCell iconRectForBounds:[self rectOfRow:row]]; + BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; + NSRect rect; + + if (minimal) + { + SmallTorrentCell* smallCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + rect = smallCell.fActionButton.frame; + } + else + { + TorrentCell* torrentCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + rect = torrentCell.fIconView.frame; + } + + NSRect rowRect = [self rectOfRow:row]; + rect.origin.y += rowRect.origin.y; + rect.origin.x += self.intercellSpacing.width; + return rect; } - (BOOL)acceptsFirstResponder @@ -799,8 +749,101 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; return YES; } -- (void)toggleControlForTorrent:(Torrent*)torrent +- (void)hoverEventBeganForView:(id)view { + NSInteger row = [self rowForView:view]; + Torrent* torrent = [self itemAtRow:row]; + + BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; + if (minimal) + { + if ([view isKindOfClass:[SmallTorrentCell class]]) + { + self.fHoverEventDict = @{ @"row" : [NSNumber numberWithInteger:row] }; + } + else if ([view isKindOfClass:[TorrentCellActionButton class]]) + { + SmallTorrentCell* smallCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + smallCell.fIconView.hidden = YES; + } + } + else + { + NSString* statusString; + if ([view isKindOfClass:[TorrentCellRevealButton class]]) + { + statusString = NSLocalizedString(@"Show the data file in Finder", "Torrent cell -> button info"); + } + else if ([view isKindOfClass:[TorrentCellControlButton class]]) + { + if (torrent.active) + statusString = NSLocalizedString(@"Pause the transfer", "Torrent Table -> tooltip"); + else + { + if (NSApp.currentEvent.modifierFlags & NSEventModifierFlagOption) + { + statusString = NSLocalizedString(@"Resume the transfer right away", "Torrent cell -> button info"); + } + else if (torrent.waitingToStart) + { + statusString = NSLocalizedString(@"Stop waiting to start", "Torrent cell -> button info"); + } + else + { + statusString = NSLocalizedString(@"Resume the transfer", "Torrent cell -> button info"); + } + } + } + else if ([view isKindOfClass:[TorrentCellActionButton class]]) + { + statusString = NSLocalizedString(@"Change transfer settings", "Torrent Table -> tooltip"); + } + + if (statusString) + { + self.fHoverEventDict = @{ @"string" : statusString, @"row" : [NSNumber numberWithInteger:row] }; + } + } + + [self reloadVisibleRows]; +} + +- (void)hoverEventEndedForView:(id)view +{ + NSInteger row = [self rowForView:[view superview]]; + + BOOL update = YES; + BOOL minimal = [self.fDefaults boolForKey:@"SmallView"]; + if (minimal) + { + if (minimal && ![view isKindOfClass:[SmallTorrentCell class]]) + { + if ([view isKindOfClass:[TorrentCellActionButton class]]) + { + SmallTorrentCell* smallCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + smallCell.fIconView.hidden = NO; + } + update = NO; + } + } + + if (update) + { + self.fHoverEventDict = nil; + [self reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } +} + +- (void)toggleGroupRowRatio +{ + BOOL displayGroupRowRatio = [self.fDefaults boolForKey:@"DisplayGroupRowRatio"]; + [self.fDefaults setBool:!displayGroupRowRatio forKey:@"DisplayGroupRowRatio"]; + [self reloadVisibleRows]; +} + +- (IBAction)toggleControlForTorrent:(id)sender +{ + Torrent* torrent = [self itemAtRow:[self rowForView:[sender superview]]]; if (torrent.active) { [self.fController stopTorrents:@[ torrent ]]; @@ -822,22 +865,26 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; } } -- (void)displayTorrentActionPopoverForEvent:(NSEvent*)event +- (IBAction)revealTorrentFile:(id)sender { - NSInteger const row = [self rowAtPoint:[self convertPoint:event.locationInWindow fromView:nil]]; - if (row < 0) + Torrent* torrent = [self itemAtRow:[self rowForView:[sender superview]]]; + NSString* location = torrent.dataLocation; + if (location) { - return; + NSURL* file = [NSURL fileURLWithPath:location]; + [NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:@[ file ]]; } +} - NSRect const rect = [self.fTorrentCell actionRectForBounds:[self rectOfRow:row]]; - +- (IBAction)displayTorrentActionPopover:(id)sender +{ if (self.fActionPopoverShown) { return; } - Torrent* torrent = [self itemAtRow:row]; + Torrent* torrent = [self itemAtRow:[self rowForView:[sender superview]]]; + NSRect rect = [sender bounds]; NSPopover* popover = [[NSPopover alloc] init]; popover.behavior = NSPopoverBehaviorTransient; @@ -845,7 +892,7 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; popover.contentViewController = infoViewController; popover.delegate = self; - [popover showRelativeToRect:rect ofView:self preferredEdge:NSMaxYEdge]; + [popover showRelativeToRect:rect ofView:sender preferredEdge:NSMaxYEdge]; [infoViewController setInfoForTorrents:@[ torrent ]]; [infoViewController updateInfo]; @@ -862,7 +909,7 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; } else { - [popover showRelativeToRect:rect ofView:self preferredEdge:NSMaxYEdge]; + [popover showRelativeToRect:rect ofView:sender preferredEdge:NSMaxYEdge]; } } @@ -1112,22 +1159,17 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; - (BOOL)pointInGroupStatusRect:(NSPoint)point { NSInteger row = [self rowAtPoint:point]; - if (row < 0 || [[self itemAtRow:row] isKindOfClass:[Torrent class]]) + if (![[self itemAtRow:row] isKindOfClass:[TorrentGroup class]]) { return NO; } - NSString* ident = (self.tableColumns[[self columnAtPoint:point]]).identifier; - return [ident isEqualToString:@"UL"] || [ident isEqualToString:@"UL Image"] || [ident isEqualToString:@"DL"] || - [ident isEqualToString:@"DL Image"]; -} + //check if click is within the status/ratio rect + GroupCell* groupCell = [self viewAtColumn:0 row:row makeIfNecessary:NO]; + NSRect titleRect = groupCell.fGroupTitleField.frame; + CGFloat maxX = NSMaxX(titleRect); -- (void)setGroupStatusColumns -{ - BOOL const ratio = [self.fDefaults boolForKey:@"DisplayGroupRowRatio"]; - - [self tableColumnWithIdentifier:@"DL"].hidden = ratio; - [self tableColumnWithIdentifier:@"DL Image"].hidden = ratio; + return point.x > maxX; } @end