Use an accessory view instead of an intermediate sheet for OPML export

This commit is contained in:
Nate Weaver
2019-10-20 21:47:05 -05:00
parent 35e7d23d8a
commit 7e98869080
7 changed files with 223 additions and 226 deletions

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15400" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15400"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ExportOPMLAccessoryViewController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="accountPopUpButton" destination="bK0-BJ-siH" id="jce-mY-Lnu"/>
<outlet property="view" destination="c22-O7-iKe" id="F7r-Bu-0DA"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="481" height="127"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="lED-IQ-gZC">
<rect key="frame" x="67" y="20" width="347" height="87"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="56R-DT-OD3">
<rect key="frame" x="-2" y="3" width="63" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Account:" id="qLW-sa-J1x">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bK0-BJ-siH" userLabel="Account Popup">
<rect key="frame" x="65" y="-3" width="285" height="25"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="9GQ-mj-Tcp" id="oI2-rj-A5N">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="J4i-mi-ts0">
<items>
<menuItem title="Item 1" state="on" id="9GQ-mj-Tcp"/>
<menuItem title="Item 2" id="ngU-na-xcU"/>
<menuItem title="Item 3" id="20Y-AP-vVO"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="accountSelected:" target="-2" id="lQN-hm-7Cz"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="xQI-ze-ZFc">
<rect key="frame" x="-2" y="39" width="351" height="48"/>
<textFieldCell key="cell" selectable="YES" id="q1a-Rk-oL6">
<font key="font" metaFont="system"/>
<string key="title">Choose the account with the subscriptions youd like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import.</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="xQI-ze-ZFc" secondAttribute="trailing" id="6MA-XB-o9x"/>
<constraint firstItem="xQI-ze-ZFc" firstAttribute="leading" secondItem="lED-IQ-gZC" secondAttribute="leading" id="8x9-3N-8Jg"/>
<constraint firstItem="bK0-BJ-siH" firstAttribute="leading" secondItem="56R-DT-OD3" secondAttribute="trailing" constant="8" symbolic="YES" id="AW6-Od-DFn"/>
<constraint firstItem="56R-DT-OD3" firstAttribute="top" secondItem="xQI-ze-ZFc" secondAttribute="bottom" constant="20" id="MLg-wk-R1I"/>
<constraint firstAttribute="trailing" secondItem="bK0-BJ-siH" secondAttribute="trailing" id="MVl-Gh-JDy"/>
<constraint firstItem="bK0-BJ-siH" firstAttribute="firstBaseline" secondItem="56R-DT-OD3" secondAttribute="firstBaseline" id="Njd-pZ-Q9q"/>
<constraint firstItem="56R-DT-OD3" firstAttribute="leading" secondItem="lED-IQ-gZC" secondAttribute="leading" id="O48-xY-l3w"/>
<constraint firstItem="xQI-ze-ZFc" firstAttribute="top" secondItem="lED-IQ-gZC" secondAttribute="top" id="TYW-C0-9VM"/>
<constraint firstAttribute="bottom" secondItem="bK0-BJ-siH" secondAttribute="bottom" id="Uas-t1-rR1"/>
<constraint firstAttribute="width" constant="347" id="cOZ-Ot-MMd"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="lED-IQ-gZC" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="c22-O7-iKe" secondAttribute="leading" constant="20" symbolic="YES" id="0F9-Th-uQH"/>
<constraint firstItem="lED-IQ-gZC" firstAttribute="centerX" secondItem="c22-O7-iKe" secondAttribute="centerX" id="3jU-jT-k8m"/>
<constraint firstItem="lED-IQ-gZC" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" constant="20" id="908-Vf-gWh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xQI-ze-ZFc" secondAttribute="trailing" id="9c9-5Z-2j3"/>
<constraint firstAttribute="bottom" secondItem="lED-IQ-gZC" secondAttribute="bottom" constant="20" id="ppg-6J-6ti"/>
</constraints>
<point key="canvasLocation" x="139" y="194"/>
</customView>
</objects>
</document>

View File

@@ -0,0 +1,58 @@
//
// ExportOPMLAccessoryViewController.swift
// NetNewsWire
//
// Created by Nate Weaver on 2019-10-20.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import AppKit
import Account
class ExportOPMLAccessoryViewController: NSViewController {
@IBOutlet weak var accountPopUpButton: NSPopUpButton!
var selectedAccount: Account? {
accountPopUpButton.selectedItem?.representedObject as? Account
}
init() {
super.init(nibName: "ExportOPMLAccessoryView", bundle: nil)
}
// MARK: - NSViewController
required init?(coder: NSCoder) {
preconditionFailure("ExportOPMLAccessoryView.init(coder) not implemented by design.")
}
override func viewDidLoad() {
accountPopUpButton.removeAllItems()
let menu = NSMenu()
accountPopUpButton.menu = menu
for oneAccount in AccountManager.shared.sortedAccounts {
let oneMenuItem = NSMenuItem()
oneMenuItem.title = oneAccount.nameForDisplay
oneMenuItem.representedObject = oneAccount
menu.addItem(oneMenuItem)
if oneAccount.accountID == AppDefaults.exportOPMLAccountID {
accountPopUpButton.select(oneMenuItem)
}
}
}
@IBAction func accountSelected(_ popUpButton: NSPopUpButton) {
NotificationCenter.default.post(name: .ExportOPMLSelectedAccountDidChange, object: self)
}
}
extension Notification.Name {
static let ExportOPMLSelectedAccountDidChange = Notification.Name(rawValue: "SelectedAccountDidChange")
}

View File

@@ -0,0 +1,62 @@
//
// ExportOPMLController.swift
// NetNewsWire
//
// Created by Maurice Parker on 5/1/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import AppKit
import Account
struct ExportOPMLController {
func runSheetOnWindow(_ hostWindow: NSWindow) {
let accessoryViewController = ExportOPMLAccessoryViewController()
let panel = NSSavePanel()
panel.allowedFileTypes = ["opml"]
panel.allowsOtherFileTypes = false
panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML")
panel.title = NSLocalizedString("Export OPML", comment: "Export OPML")
panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML")
panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML")
panel.isExtensionHidden = false
panel.accessoryView = accessoryViewController.view
let observer = NotificationCenter.default.addObserver(forName: .ExportOPMLSelectedAccountDidChange, object: nil, queue: OperationQueue.main) { notification in
self.updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController)
}
updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true)
panel.beginSheetModal(for: hostWindow) { result in
if result == NSApplication.ModalResponse.OK, let url = panel.url {
DispatchQueue.main.async {
guard let account = accessoryViewController.selectedAccount else { return }
let filename = url.lastPathComponent
let opmlString = OPMLExporter.OPMLString(with: account, title: filename)
do {
try opmlString.write(to: url, atomically: true, encoding: .utf8)
}
catch let error as NSError {
NSApplication.shared.presentError(error)
}
}
}
NotificationCenter.default.removeObserver(observer)
}
}
private func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) {
if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { return }
guard let account = accessoryViewController.selectedAccount else { return }
let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces)
panel.nameFieldStringValue = "Subscriptions-\(accountName).opml"
}
}

View File

@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ExportOPMLWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="accountPopUpButton" destination="bbC-2g-e3k" id="sK9-0j-WyF"/>
<outlet property="window" destination="QvC-M9-y7g" id="Z1E-pd-3QE"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="355" height="180"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" wantsLayer="YES" misplaced="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="391" height="171"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="mvx-54-DH0">
<rect key="frame" x="18" y="100" width="355" height="51"/>
<textFieldCell key="cell" selectable="YES" id="7Ap-KG-Lc7">
<font key="font" metaFont="system"/>
<string key="title">Choose the account with the subscriptions youd like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import.</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xJz-HU-L4i">
<rect key="frame" x="18" y="63" width="63" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Account:" id="Dao-jI-G6i">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bbC-2g-e3k" userLabel="Account Popup">
<rect key="frame" x="85" y="58" width="289" height="25"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="MJb-Bf-UJG" id="xZd-AP-nuM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="TP8-jd-M1D">
<items>
<menuItem title="Item 1" state="on" id="MJb-Bf-UJG"/>
<menuItem title="Item 2" id="7U3-Y5-qeM"/>
<menuItem title="Item 3" id="Uii-dd-siy"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eZ4-Ej-Hks">
<rect key="frame" x="229" y="13" width="148" height="32"/>
<buttonCell key="cell" type="push" title="Export as OPML…" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bRz-cx-bmm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="exportOPML:" target="-2" id="Lcs-Xm-HfK"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PPB-R8-A9a">
<rect key="frame" x="81" y="13" width="148" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6lK-bV-Vwd">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="oun-pb-pQs"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="mvx-54-DH0" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" symbolic="YES" id="6SB-1X-jS4"/>
<constraint firstItem="xJz-HU-L4i" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" symbolic="YES" id="8TB-fT-THB"/>
<constraint firstItem="PPB-R8-A9a" firstAttribute="leading" secondItem="bbC-2g-e3k" secondAttribute="leading" id="9zJ-1D-4cz"/>
<constraint firstAttribute="bottom" secondItem="eZ4-Ej-Hks" secondAttribute="bottom" constant="20" symbolic="YES" id="ES8-CX-Cvn"/>
<constraint firstItem="bbC-2g-e3k" firstAttribute="leading" secondItem="xJz-HU-L4i" secondAttribute="trailing" constant="8" symbolic="YES" id="IAs-Eg-vxH"/>
<constraint firstItem="eZ4-Ej-Hks" firstAttribute="top" secondItem="bbC-2g-e3k" secondAttribute="bottom" constant="20" symbolic="YES" id="KLL-J1-sDN"/>
<constraint firstItem="eZ4-Ej-Hks" firstAttribute="leading" secondItem="PPB-R8-A9a" secondAttribute="trailing" constant="12" symbolic="YES" id="Lfs-g3-crb"/>
<constraint firstItem="PPB-R8-A9a" firstAttribute="centerY" secondItem="eZ4-Ej-Hks" secondAttribute="centerY" id="OVP-4p-nXp"/>
<constraint firstAttribute="trailing" secondItem="eZ4-Ej-Hks" secondAttribute="trailing" constant="20" symbolic="YES" id="RDU-0d-O0Q"/>
<constraint firstItem="xJz-HU-L4i" firstAttribute="top" secondItem="mvx-54-DH0" secondAttribute="bottom" constant="20" id="Rxj-lF-b8R"/>
<constraint firstItem="bbC-2g-e3k" firstAttribute="firstBaseline" secondItem="xJz-HU-L4i" secondAttribute="firstBaseline" id="TjR-gN-xrt"/>
<constraint firstAttribute="trailing" secondItem="mvx-54-DH0" secondAttribute="trailing" constant="20" symbolic="YES" id="VBk-HZ-2xv"/>
<constraint firstItem="eZ4-Ej-Hks" firstAttribute="width" secondItem="PPB-R8-A9a" secondAttribute="width" id="WOT-NI-xD8"/>
<constraint firstItem="mvx-54-DH0" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" symbolic="YES" id="l2i-jP-HX1"/>
<constraint firstAttribute="trailing" secondItem="bbC-2g-e3k" secondAttribute="trailing" constant="20" symbolic="YES" id="uZb-X0-fUh"/>
</constraints>
</view>
<point key="canvasLocation" x="81.5" y="102"/>
</window>
</objects>
</document>

View File

@@ -1,106 +0,0 @@
//
// ExportOPMLWindowController.swift
// NetNewsWire
//
// Created by Maurice Parker on 5/1/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import AppKit
import Account
class ExportOPMLWindowController: NSWindowController {
@IBOutlet weak var accountPopUpButton: NSPopUpButton!
private weak var hostWindow: NSWindow?
convenience init() {
self.init(windowNibName: NSNib.Name("ExportOPMLSheet"))
}
override func windowDidLoad() {
accountPopUpButton.removeAllItems()
let menu = NSMenu()
accountPopUpButton.menu = menu
for oneAccount in AccountManager.shared.sortedAccounts {
let oneMenuItem = NSMenuItem()
oneMenuItem.title = oneAccount.nameForDisplay
oneMenuItem.representedObject = oneAccount
menu.addItem(oneMenuItem)
if oneAccount.accountID == AppDefaults.exportOPMLAccountID {
accountPopUpButton.select(oneMenuItem)
}
}
}
// MARK: API
func runSheetOnWindow(_ hostWindow: NSWindow) {
self.hostWindow = hostWindow
if AccountManager.shared.accounts.count == 1 {
let account = AccountManager.shared.accounts.first!
exportOPML(account: account)
} else {
hostWindow.beginSheet(window!)
}
}
// MARK: Actions
@IBAction func cancel(_ sender: Any) {
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel)
}
@IBAction func exportOPML(_ sender: Any) {
guard let menuItem = accountPopUpButton.selectedItem else {
return
}
let account = menuItem.representedObject as! Account
AppDefaults.exportOPMLAccountID = account.accountID
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK)
exportOPML(account: account)
}
func exportOPML(account: Account) {
let panel = NSSavePanel()
panel.allowedFileTypes = ["opml"]
panel.allowsOtherFileTypes = false
panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML")
panel.title = NSLocalizedString("Export OPML", comment: "Export OPML")
panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML")
panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML")
panel.isExtensionHidden = false
let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces)
panel.nameFieldStringValue = "Subscriptions-\(accountName).opml"
panel.beginSheetModal(for: hostWindow!) { result in
if result == NSApplication.ModalResponse.OK, let url = panel.url {
DispatchQueue.main.async {
let filename = url.lastPathComponent
let opmlString = OPMLExporter.OPMLString(with: account, title: filename)
do {
try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8)
}
catch let error as NSError {
NSApplication.shared.presentError(error)
}
}
}
}
}
}