From f759f947c601b0ad82a25ee30cc3e1131e7ed8b4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 22 Sep 2020 19:27:36 -0500 Subject: [PATCH] Restore the ability to select the default RSS reader. Issue #2428 --- Mac/Base.lproj/Preferences.storyboard | 83 +++++++--- .../GeneralPrefencesViewController.swift | 154 +++++++++++++++++- 2 files changed, 209 insertions(+), 28 deletions(-) diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index ea5a4d76a..d9f000100 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -1,8 +1,8 @@ - + - + @@ -32,14 +32,14 @@ - + - + - + @@ -47,7 +47,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -90,7 +90,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -133,7 +133,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + @@ -188,13 +220,14 @@ + - + @@ -374,16 +407,16 @@ - + - + - + - + @@ -490,7 +523,7 @@ - + @@ -545,16 +578,16 @@ - + - + - + - + @@ -657,7 +690,7 @@ - + diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift index 32818e524..1ecfb3ff3 100644 --- a/Mac/Preferences/General/GeneralPrefencesViewController.swift +++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift @@ -15,9 +15,11 @@ final class GeneralPreferencesViewController: NSViewController { private var userNotificationSettings: UNNotificationSettings? + @IBOutlet var defaultRSSReaderPopup: NSPopUpButton! @IBOutlet var defaultBrowserPopup: NSPopUpButton! - @IBOutlet weak var showUnreadCountCheckbox: NSButton! - + @IBOutlet weak var showUnreadCountCheckbox: NSButton! + private var rssReaderInfo = RSSReaderInfo() + public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) commonInit() @@ -42,6 +44,17 @@ final class GeneralPreferencesViewController: NSViewController { // 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() + } + @IBAction func browserPopUpDidChangeValue(_ sender: Any?) { guard let menuItem = defaultBrowserPopup.selectedItem else { return @@ -102,10 +115,74 @@ private extension GeneralPreferencesViewController { } func updateUI() { + rssReaderInfo = RSSReaderInfo() updateBrowserPopup() + updateRSSReaderPopup() updateHideUnreadCountCheckbox() } + 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 + } + + 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 + } + } + + insertAndSelectNoneMenuItem() + } + + func registerAppWithBundleID(_ bundleID: String) { + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID) + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID) + } + func updateBrowserPopup() { let menu = defaultBrowserPopup.menu! let allBrowsers = MacWebBrowser.sortedBrowsers() @@ -161,8 +238,79 @@ private extension GeneralPreferencesViewController { updateAlert.addButton(withTitle: NSLocalizedString("Close", comment: "Close")) let modalResponse = updateAlert.runModal() if modalResponse == .alertFirstButtonReturn { - NSWorkspace.shared.open(URL(fileURLWithPath: "x-apple.systempreferences:com.apple.preference.notifications")) + NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.notifications")!) } } } + +// 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 + + init?(bundleID: String) { + guard let path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleID) else { + return nil + } + + self.path = path + self.bundleID = bundleID + + let name = (path as NSString).lastPathComponent + self.name = name + if name.hasSuffix(".app") { + self.nameMinusAppSuffix = name.stripping(suffix: ".app") + } + else { + self.nameMinusAppSuffix = name + } + } + + // MARK: - Hashable + + func hash(into hasher: inout Hasher) { + hasher.combine(bundleID) + } + + // MARK: - Equatable + + static func ==(lhs: RSSReader, rhs: RSSReader) -> Bool { + return lhs.bundleID == rhs.bundleID + } +}