diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index a5662c1be..a93982a53 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; + 847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; }; 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; @@ -544,6 +545,7 @@ 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = ""; }; 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextualMenuDelegate.swift; sourceTree = ""; }; 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = ""; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = ""; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = ""; }; @@ -924,6 +926,7 @@ 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */, 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */, 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */, + 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */, 844B5B6A1FEA224000C7C76A /* Keyboard */, 845A29251FC928C7007B49E3 /* Cell */, 84A37CB3201ECD610087C5AF /* Renaming */, @@ -1832,6 +1835,7 @@ 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */, 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */, + 847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */, 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */, 844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */, 84DAEE321F870B390058304B /* DockBadge.swift in Sources */, diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard index a2232733d..bb942f01b 100644 --- a/Evergreen/Base.lproj/MainWindow.storyboard +++ b/Evergreen/Base.lproj/MainWindow.storyboard @@ -359,6 +359,7 @@ + @@ -441,11 +442,28 @@ + + + + + + + + + + + + + + + + + @@ -456,6 +474,11 @@ + + + + + @@ -682,12 +705,12 @@ - - - - - - - + + + + + + + diff --git a/Evergreen/MainWindow/MainWindowController.swift b/Evergreen/MainWindow/MainWindowController.swift index d412a31b2..5a73960dd 100644 --- a/Evergreen/MainWindow/MainWindowController.swift +++ b/Evergreen/MainWindow/MainWindowController.swift @@ -13,7 +13,7 @@ import Account private let kWindowFrameKey = "MainWindow" class MainWindowController : NSWindowController, NSUserInterfaceValidations { - + var isOpen: Bool { return isWindowLoaded && window!.isVisible } @@ -38,9 +38,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { static var didPositionWindowOnFirstRun = false - override func windowDidLoad() { - - super.windowDidLoad() + override func windowDidLoad() { + + super.windowDidLoad() if !AppDefaults.shared.showTitleOnMainWindow { window?.titleVisibility = .hidden @@ -82,12 +82,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { return sidebarViewController?.selectedObjects } - // MARK: Notifications - + // MARK: Notifications + @objc func applicationWillTerminate(_ note: Notification) { - + window?.saveFrame(usingName: windowAutosaveName) - } + } @objc func appNavigationKeyPressed(_ note: Notification) { @@ -152,7 +152,22 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { if item.action == #selector(toolbarShowShareMenu(_:)) { return canShowShareMenu() } - + + if item.action == #selector(toggleSidebar(_:)) { + + guard let splitViewItem = sidebarSplitViewItem else { + return false + } + + let sidebarIsShowing = !splitViewItem.isCollapsed + if let menuItem = item as? NSMenuItem { + let title = sidebarIsShowing ? NSLocalizedString("Hide Sidebar", comment: "Menu item") : NSLocalizedString("Show Sidebar", comment: "Menu item") + menuItem.title = title + } + + return true + } + return true } @@ -171,10 +186,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } - @IBAction func showAddFolderWindow(_ sender: Any) { + @IBAction func showAddFolderWindow(_ sender: Any) { - appDelegate.showAddFolderSheetOnWindow(window!) - } + appDelegate.showAddFolderSheetOnWindow(window!) + } @IBAction func showAddFeedWindow(_ sender: Any) { @@ -412,59 +427,47 @@ extension MainWindowController : ScriptingMainWindowController { private extension MainWindowController { var splitViewController: NSSplitViewController? { - get { - guard let viewController = contentViewController else { - return nil - } - return viewController.childViewControllers.first as? NSSplitViewController + guard let viewController = contentViewController else { + return nil } + return viewController.childViewControllers.first as? NSSplitViewController } var sidebarViewController: SidebarViewController? { - get { - return splitViewController?.splitViewItems[0].viewController as? SidebarViewController - } + return splitViewController?.splitViewItems[0].viewController as? SidebarViewController } var timelineViewController: TimelineViewController? { - get { - return splitViewController?.splitViewItems[1].viewController as? TimelineViewController - } + return splitViewController?.splitViewItems[1].viewController as? TimelineViewController } - + + var sidebarSplitViewItem: NSSplitViewItem? { + return splitViewController?.splitViewItems[0] + } + var detailSplitViewItem: NSSplitViewItem? { - get { - return splitViewController?.splitViewItems[2] - } + return splitViewController?.splitViewItems[2] } var detailViewController: DetailViewController? { - get { - return splitViewController?.splitViewItems[2].viewController as? DetailViewController - } + return splitViewController?.splitViewItems[2].viewController as? DetailViewController } var selectedArticles: [Article]? { - get { - return timelineViewController?.selectedArticles - } + return timelineViewController?.selectedArticles } - + var oneSelectedArticle: Article? { - get { - if let articles = selectedArticles { - return articles.count == 1 ? articles[0] : nil - } - return nil + if let articles = selectedArticles { + return articles.count == 1 ? articles[0] : nil } + return nil } - + var currentLink: String? { - get { - return oneSelectedArticle?.preferredLink - } + return oneSelectedArticle?.preferredLink } - + func canGoToNextUnread() -> Bool { guard let timelineViewController = timelineViewController, let sidebarViewController = sidebarViewController else { diff --git a/Evergreen/MainWindow/Sidebar/SidebarContextualMenuDelegate.swift b/Evergreen/MainWindow/Sidebar/SidebarContextualMenuDelegate.swift new file mode 100644 index 000000000..1c2944646 --- /dev/null +++ b/Evergreen/MainWindow/Sidebar/SidebarContextualMenuDelegate.swift @@ -0,0 +1,34 @@ +// +// SidebarContextualMenuDelegate.swift +// Evergreen +// +// Created by Brent Simmons on 2/7/18. +// Copyright © 2018 Ranchero Software. All rights reserved. +// + +import AppKit + +@objc final class SidebarContextualMenuDelegate: NSObject, NSMenuDelegate { + + @IBOutlet weak var sidebarViewController: SidebarViewController? + + public func menuNeedsUpdate(_ menu: NSMenu) { + + guard let sidebarViewController = sidebarViewController else { + return + } + + menu.removeAllItems() + + guard let contextualMenu = sidebarViewController.contextualMenuForClickedRows() else { + return + } + + let items = contextualMenu.items + contextualMenu.removeAllItems() + for menuItem in items { + menu.addItem(menuItem) + } + } +} + diff --git a/Evergreen/MainWindow/Sidebar/SidebarGearMenuDelegate.swift b/Evergreen/MainWindow/Sidebar/SidebarGearMenuDelegate.swift index 1d33f3e37..a363f1804 100644 --- a/Evergreen/MainWindow/Sidebar/SidebarGearMenuDelegate.swift +++ b/Evergreen/MainWindow/Sidebar/SidebarGearMenuDelegate.swift @@ -14,10 +14,6 @@ import AppKit public func menuNeedsUpdate(_ menu: NSMenu) { - guard let sidebarViewController = sidebarViewController else { - return - } - // Save the first item, since it’s the gear icon itself. guard let gearMenuItem = menu.item(at: 0) else { assertionFailure("Expected sidebar gear menu to have at least one item.") @@ -26,6 +22,9 @@ import AppKit menu.removeAllItems() menu.addItem(gearMenuItem) + guard let sidebarViewController = sidebarViewController else { + return + } guard let contextualMenu = sidebarViewController.contextualMenuForSelectedObjects() else { return } diff --git a/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift b/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift index b154d410d..2ed394dd7 100644 --- a/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift +++ b/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift @@ -12,19 +12,9 @@ import RSTree class SidebarOutlineView : NSOutlineView { - weak var sidebarViewController: SidebarViewController? @IBOutlet var keyboardDelegate: KeyboardDelegate! - //MARK: NSResponder - - override func keyDown(with event: NSEvent) { - - if keyboardDelegate.keydown(event, in: self) { - return - } - - super.keyDown(with: event) - } + // MARK: NSTableView override func frameOfCell(atColumn column: Int, row: Int) -> NSRect { @@ -45,6 +35,8 @@ class SidebarOutlineView : NSOutlineView { return frame } + // MARK: NSView + override func viewWillStartLiveResize() { if let scrollView = self.enclosingScrollView { @@ -60,4 +52,15 @@ class SidebarOutlineView : NSOutlineView { } super.viewDidEndLiveResize() } + + // MARK: NSResponder + + override func keyDown(with event: NSEvent) { + + if keyboardDelegate.keydown(event, in: self) { + return + } + + super.keyDown(with: event) + } } diff --git a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift index 788051ae5..3afc39974 100644 --- a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift +++ b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift @@ -16,6 +16,7 @@ import RSCore @IBOutlet var outlineView: SidebarOutlineView! @IBOutlet var gearMenuDelegate: SidebarGearMenuDelegate! + @IBOutlet var contextualMenuDelegate: SidebarContextualMenuDelegate! let treeControllerDelegate = SidebarTreeControllerDelegate() lazy var treeController: TreeController = { @@ -37,7 +38,6 @@ import RSCore sidebarCellAppearance = SidebarCellAppearance(theme: appDelegate.currentTheme, fontSize: AppDefaults.shared.sidebarFontSize) - outlineView.sidebarViewController = self outlineView.setDraggingSourceOperationMask(.move, forLocal: true) outlineView.setDraggingSourceOperationMask(.copy, forLocal: false) @@ -200,6 +200,22 @@ import RSCore return menu(for: selectedObjects) } + func contextualMenuForClickedRows() -> NSMenu? { + + let row = outlineView.clickedRow + guard row != -1, let node = nodeForRow(row) else { + return nil + } + + if outlineView.selectedRowIndexes.contains(row) { + // If the clickedRow is part of the selected rows, then do a contextual menu for all the selected rows. + return contextualMenuForSelectedObjects() + } + + let object = node.representedObject + return menu(for: [object]) + } + // MARK: NSOutlineViewDelegate func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift index 2d5243c75..afcf64d16 100644 --- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift +++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift @@ -36,7 +36,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { didSet { if showFeedNames != oldValue { updateShowAvatars() - tableView.rowHeight = currentRowHeight + updateTableViewRowHeight() } } } @@ -147,7 +147,9 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize) cellAppearanceWithAvatar = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: true, fontSize: fontSize) updateRowHeights() - tableView.reloadData() + performBlockAndRestoreSelection { + tableView.reloadData() + } } // MARK: - API @@ -416,6 +418,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { rowHeightWithFeedName = calculateRowHeight(showingFeedNames: true) rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false) + updateTableViewRowHeight() } } @@ -587,6 +590,11 @@ private extension TimelineViewController { } } + func updateTableViewRowHeight() { + + tableView.rowHeight = currentRowHeight + } + func updateShowAvatars() { if showFeedNames { @@ -617,17 +625,32 @@ private extension TimelineViewController { func sortDirectionDidChange() { - let selectedArticleIDs = selectedArticles.articleIDs() + performBlockAndRestoreSelection { + let unsortedArticles = Set(articles) + updateArticles(with: unsortedArticles) + } + } - let unsortedArticles = Set(articles) - updateArticles(with: unsortedArticles) + func selectedArticleIDs() -> [String] { - selectArticles(selectedArticleIDs) + return selectedArticles.articleIDs() + } + + func restoreSelection(_ articleIDs: [String]) { + + selectArticles(articleIDs) if tableView.selectedRow != -1 { tableView.scrollRowToVisible(tableView.selectedRow) } } + func performBlockAndRestoreSelection(_ block: (() -> Void)) { + + let savedSelection = selectedArticleIDs() + block() + restoreSelection(savedSelection) + } + // MARK: Fetching Articles func fetchArticles() { @@ -672,15 +695,13 @@ private extension TimelineViewController { return } - let selectedArticleIDs = selectedArticles.articleIDs() - - var unsortedArticles = fetchUnsortedArticles(for: representedObjects) - unsortedArticles.formUnion(Set(articles)) - updateArticles(with: unsortedArticles) - - selectArticles(selectedArticleIDs) + performBlockAndRestoreSelection { + var unsortedArticles = fetchUnsortedArticles(for: representedObjects) + unsortedArticles.formUnion(Set(articles)) + updateArticles(with: unsortedArticles) + } } - + func selectArticles(_ articleIDs: [String]) { let indexesToSelect = articles.indexesForArticleIDs(Set(articleIDs))