From 4d90ed022f37aa53bc37de8b443ba56b96947687 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 26 Nov 2018 13:17:16 -0800 Subject: [PATCH] Make the default-aggregator popup work in the preferences window. Fix #444. --- NetNewsWire/Base.lproj/Preferences.storyboard | 13 +- .../GeneralPrefencesViewController.swift | 154 +++++++++++++++--- 2 files changed, 140 insertions(+), 27 deletions(-) diff --git a/NetNewsWire/Base.lproj/Preferences.storyboard b/NetNewsWire/Base.lproj/Preferences.storyboard index 070a4af6e..552f89b10 100644 --- a/NetNewsWire/Base.lproj/Preferences.storyboard +++ b/NetNewsWire/Base.lproj/Preferences.storyboard @@ -1,8 +1,8 @@ - + - + @@ -76,17 +76,18 @@ - + - - - + + + + diff --git a/NetNewsWire/Preferences/GeneralPrefencesViewController.swift b/NetNewsWire/Preferences/GeneralPrefencesViewController.swift index 59b8f9c25..4a47f7ad4 100644 --- a/NetNewsWire/Preferences/GeneralPrefencesViewController.swift +++ b/NetNewsWire/Preferences/GeneralPrefencesViewController.swift @@ -12,55 +12,167 @@ import RSCore final class GeneralPreferencesViewController: NSViewController { @IBOutlet var defaultRSSReaderPopup: NSPopUpButton! - static let feedURLScheme = "feed:" - static let feedsURLScheme = "feeds:" + private var rssReaderInfo = RSSReaderInfo() + + public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + commonInit() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } override func viewWillAppear() { - updateRSSReaderPopup() + super.viewWillAppear() + updateUI() + } + + // MARK: - Notifications + + @objc func applicationWillBecomeActive(_ note: Notification) { + updateUI() + } + + // MARK: - Actions + + @IBAction func rssReaderPopupDidChangeValue(_ sender: Any?) { + guard let menuItem = defaultRSSReaderPopup.selectedItem else { + return + } + guard let bundleID = menuItem.representedObject as? String else { + return + } + registerAppWithBundleID(bundleID) + updateUI() } } +// MARK: - Private + private extension GeneralPreferencesViewController { - func updateRSSReaderPopup() { - let rssReaders = fetchRSSReaders() - print(rssReaders) + func commonInit() { + NotificationCenter.default.addObserver(self, selector: #selector(applicationWillBecomeActive(_:)), name: NSApplication.willBecomeActiveNotification, object: nil) } - func fetchRSSReaders() -> Set { - let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: GeneralPreferencesViewController.feedURLScheme) - let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: GeneralPreferencesViewController.feedURLScheme) + func updateUI() { + rssReaderInfo = RSSReaderInfo() + updateRSSReaderPopup() + } - var allReaders = Set() - if let defaultRSSReaderBundleID = defaultRSSReaderBundleID, let defaultReader = RSSReader(bundleID: defaultRSSReaderBundleID, isDefaultReader: true) { - allReaders.insert(defaultReader) + func updateRSSReaderPopup() { + // Top item should always be: NetNewsWire (this app) + // Additional items should be sorted alphabetically. + // Any older versions of NetNewsWire should be listed as: NetNewsWire (old version) + + let menu = NSMenu(title: "RSS Readers") + + let netNewsWireBundleID = Bundle.main.bundleIdentifier! + let thisAppParentheticalComment = NSLocalizedString("(this app)", comment: "Preferences default RSS Reader popup") + let thisAppName = "NetNewsWire \(thisAppParentheticalComment)" + let netNewsWireMenuItem = NSMenuItem(title: thisAppName, action: nil, keyEquivalent: "") + netNewsWireMenuItem.representedObject = netNewsWireBundleID + menu.addItem(netNewsWireMenuItem) + + let readersToList = rssReaderInfo.rssReaders.filter { $0.bundleID != netNewsWireBundleID } + let sortedReaders = readersToList.sorted { (reader1, reader2) -> Bool in + return reader1.nameMinusAppSuffix.localizedStandardCompare(reader2.nameMinusAppSuffix) == .orderedAscending } - rssReaderBundleIDs.forEach { (bundleID) in - let isDefault = bundleID == defaultRSSReaderBundleID - if let reader = RSSReader(bundleID: bundleID, isDefaultReader: isDefault) { - allReaders.insert(reader) + + let oldVersionParentheticalComment = NSLocalizedString("(old version)", comment: "Preferences default RSS Reader popup") + for rssReader in sortedReaders { + var appName = rssReader.nameMinusAppSuffix + if appName.contains("NetNewsWire") { + appName = "\(appName) \(oldVersionParentheticalComment)" + } + let menuItem = NSMenuItem(title: appName, action: nil, keyEquivalent: "") + menuItem.representedObject = rssReader.bundleID + menu.addItem(menuItem) + } + + defaultRSSReaderPopup.menu = menu + + func insertAndSelectNoneMenuItem() { + let noneTitle = NSLocalizedString("None", comment: "Preferences default RSS Reader popup") + let menuItem = NSMenuItem(title: noneTitle, action: nil, keyEquivalent: "") + defaultRSSReaderPopup.menu!.insertItem(menuItem, at: 0) + defaultRSSReaderPopup.selectItem(at: 0) + } + + guard let defaultRSSReaderBundleID = rssReaderInfo.defaultRSSReaderBundleID else { + insertAndSelectNoneMenuItem() + return + } + + for menuItem in defaultRSSReaderPopup.menu!.items { + guard let bundleID = menuItem.representedObject as? String else { + continue + } + if bundleID == defaultRSSReaderBundleID { + defaultRSSReaderPopup.select(menuItem) + return } } - return allReaders + + insertAndSelectNoneMenuItem() + } + + func registerAppWithBundleID(_ bundleID: String) { + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID) + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID) } } -private final class RSSReader: Hashable { + +// MARK: - RSSReaderInfo + +private struct RSSReaderInfo { + + let defaultRSSReaderBundleID: String? + let rssReaders: Set + static let feedURLScheme = "feed:" + + init() { + let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme) + self.defaultRSSReaderBundleID = defaultRSSReaderBundleID + self.rssReaders = RSSReaderInfo.fetchRSSReaders(defaultRSSReaderBundleID) + } + + static func fetchRSSReaders(_ defaultRSSReaderBundleID: String?) -> Set { + let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme) + + var rssReaders = Set() + if let defaultRSSReaderBundleID = defaultRSSReaderBundleID, let defaultReader = RSSReader(bundleID: defaultRSSReaderBundleID) { + rssReaders.insert(defaultReader) + } + rssReaderBundleIDs.forEach { (bundleID) in + if let reader = RSSReader(bundleID: bundleID) { + rssReaders.insert(reader) + } + } + return rssReaders + } +} + + +// MARK: - RSSReader + +private struct RSSReader: Hashable { let bundleID: String let name: String let nameMinusAppSuffix: String let path: String - let isDefaultReader: Bool - init?(bundleID: String, isDefaultReader: Bool) { + init?(bundleID: String) { guard let path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleID) else { return nil } self.path = path self.bundleID = bundleID - self.isDefaultReader = isDefaultReader let name = (path as NSString).lastPathComponent self.name = name