Make the default-aggregator popup work in the preferences window. Fix #444.

This commit is contained in:
Brent Simmons 2018-11-26 13:17:16 -08:00
parent 03d615f8ef
commit 4d90ed022f
2 changed files with 140 additions and 27 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.13.2"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -76,17 +76,18 @@
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cSu-T2-Jby">
<rect key="frame" x="198" y="45" width="212" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="This doesnt work yet" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="bEy-Qx-Grl" id="Dyk-WN-XOo">
<popUpButtonCell key="cell" type="push" title="NetNewsWire" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="bEy-Qx-Grl" id="Dyk-WN-XOo" userLabel="Popup">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8PF-kj-u99">
<items>
<menuItem title="This doesnt work yet" state="on" id="bEy-Qx-Grl"/>
<menuItem title="Item 2" id="ZB8-D2-BS4"/>
<menuItem title="Item 3" id="aSx-R2-4eg"/>
<menuItem title="NetNewsWire" state="on" id="bEy-Qx-Grl"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="rssReaderPopupDidChangeValue:" target="iuH-lz-18x" id="pyX-Bp-ws0"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wsb-Lr-8Q7">
<rect key="frame" x="118" y="20" width="76" height="17"/>

View File

@ -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<RSSReader> {
let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: GeneralPreferencesViewController.feedURLScheme)
let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: GeneralPreferencesViewController.feedURLScheme)
func updateUI() {
rssReaderInfo = RSSReaderInfo()
updateRSSReaderPopup()
}
var allReaders = Set<RSSReader>()
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<RSSReader>
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<RSSReader> {
let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme)
var rssReaders = Set<RSSReader>()
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