// // GeneralPrefencesViewController.swift // NetNewsWire // // Created by Brent Simmons on 9/3/18. // Copyright © 2018 Ranchero Software. All rights reserved. // import AppKit import RSCore final class GeneralPreferencesViewController: NSViewController { @IBOutlet var defaultRSSReaderPopup: NSPopUpButton! 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() { 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 commonInit() { NotificationCenter.default.addObserver(self, selector: #selector(applicationWillBecomeActive(_:)), name: NSApplication.willBecomeActiveNotification, object: nil) } func updateUI() { rssReaderInfo = RSSReaderInfo() updateRSSReaderPopup() } 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) } } // 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.rs_string(byStrippingSuffix: ".app", caseSensitive: false) } 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 } }