diff --git a/NetNewsWire/AppDefaults.swift b/NetNewsWire/AppDefaults.swift index 44a19f7a5..3d9150059 100644 --- a/NetNewsWire/AppDefaults.swift +++ b/NetNewsWire/AppDefaults.swift @@ -53,7 +53,6 @@ struct AppDefaults { static let timelineSortDirection = "timelineSortDirection" static let detailFontSize = "detailFontSize" static let openInBrowserInBackground = "openInBrowserInBackground" - static let mainWindowWidths = "mainWindowWidths" static let refreshInterval = "refreshInterval" // Hidden prefs @@ -120,15 +119,6 @@ struct AppDefaults { } } - static var mainWindowWidths: [Int]? { - get { - return UserDefaults.standard.object(forKey: Key.mainWindowWidths) as? [Int] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.mainWindowWidths) - } - } - static var refreshInterval: RefreshInterval { get { let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) @@ -143,6 +133,18 @@ struct AppDefaults { let defaults: [String : Any] = [Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, "NSScrollViewShouldScrollUnderTitlebar": false, Key.refreshInterval: RefreshInterval.everyHour.rawValue] UserDefaults.standard.register(defaults: defaults) + + // It seems that registering a default for NSQuitAlwaysKeepsWindows to true + // is not good enough to get the system to respect it, so we have to literally + // set it as the default to get it to take effect. This overrides a system-wide + // setting in the System Preferences, which is ostensibly meant to "close windows" + // in an app, but has the side-effect of also not preserving or restoring any state + // for the window. Since we've switched to using the standard state preservation and + // restoration mechanisms, and because it seems highly unlikely any user would object + // to NetNewsWire preserving this state, we'll force the preference on. If this becomes + // an issue, this could be changed to proactively look for whether the default has been + // set _by the user_ to false, and respect that default if it is so-set. + UserDefaults.standard.set(true, forKey: "NSQuitAlwaysKeepsWindows") } static func actualFontSize(for fontSize: FontSize) -> CGFloat { diff --git a/NetNewsWire/AppDelegate.swift b/NetNewsWire/AppDelegate.swift index 6450e268e..d6bdf761d 100644 --- a/NetNewsWire/AppDelegate.swift +++ b/NetNewsWire/AppDelegate.swift @@ -576,7 +576,6 @@ private extension AppDelegate { func saveState() { inspectorWindowController?.saveState() - mainWindowController?.saveState() } func updateSortMenuItems() { diff --git a/NetNewsWire/MainWindow/Detail/styleSheet.css b/NetNewsWire/MainWindow/Detail/styleSheet.css index 509b015c5..d315bb69b 100644 --- a/NetNewsWire/MainWindow/Detail/styleSheet.css +++ b/NetNewsWire/MainWindow/Detail/styleSheet.css @@ -51,6 +51,10 @@ body.light .articleDateline, body.light .articleDateLine.a:link, body.light .art color: rgba(0, 0, 0, 0.3); } +body.light code, body.light pre { + color: #666; +} + .light > .systemMessage { color: #cbcbcb; } @@ -81,6 +85,10 @@ body.dark .articleDateline, body.dark .articleDateLine.a:link, body.dark .articl color: #d2d2d2; } +body.dark code, body.dark pre { + color: #b2b2b2; +} + .dark > .systemMessage { color: #5f5f5f; } @@ -120,7 +128,6 @@ h1 { code, pre { font-family: "SF Mono", Menlo, "Courier New", Courier, monospace; font-size: 14px; - color: #666; } pre { white-space: pre-wrap; diff --git a/NetNewsWire/MainWindow/MainWindowController.swift b/NetNewsWire/MainWindow/MainWindowController.swift index f3740c69b..3cd37ed66 100644 --- a/NetNewsWire/MainWindow/MainWindowController.swift +++ b/NetNewsWire/MainWindow/MainWindowController.swift @@ -15,12 +15,11 @@ enum TimelineSourceMode { case regular, search } -class MainWindowController : NSWindowController, NSUserInterfaceValidations { +class MainWindowController : NSWindowController, NSUserInterfaceValidations, NSWindowDelegate { @IBOutlet var toolbarDelegate: MainWindowToolbarDelegate? private var sharingServicePickerDelegate: NSSharingServicePickerDelegate? - private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow") static var didPositionWindowOnFirstRun = false private var currentFeedOrFolder: AnyObject? { @@ -52,7 +51,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { window?.titleVisibility = .hidden } - window?.setFrameUsingName(windowAutosaveName, force: true) if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun { if let window = window { @@ -65,7 +63,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness) - restoreSplitViewState() sidebarViewController = splitViewController?.splitViewItems[0].viewController as? SidebarViewController sidebarViewController!.delegate = self @@ -75,8 +72,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { detailViewController = splitViewController?.splitViewItems[2].viewController as? DetailViewController - NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: NSApplication.willTerminateNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidBegin, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) @@ -91,12 +86,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { // MARK: - API - func saveState() { - - saveSplitViewState() - } - - func selectedObjectsInSidebar() -> [AnyObject]? { return sidebarViewController?.selectedObjects @@ -104,10 +93,18 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { // MARK: - Notifications - @objc func applicationWillTerminate(_ note: Notification) { + func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) { - saveState() - window?.saveFrame(usingName: windowAutosaveName) + saveSplitViewState(to: state) + } + + func window(_ window: NSWindow, didDecodeRestorableState state: NSCoder) { + + restoreSplitViewState(from: state) + + // Make sure the timeline view is first responder if possible, to start out viewing + // whatever preserved selection might have been restored + makeTimelineViewFirstResponder() } @objc func refreshProgressDidChange(_ note: Notification) { @@ -403,7 +400,9 @@ extension MainWindowController : ScriptingMainWindowController { // MARK: - Private private extension MainWindowController { - + + static let mainWindowWidthsStateKey = "mainWindowWidths" + var splitViewController: NSSplitViewController? { guard let viewController = contentViewController else { return nil @@ -582,7 +581,7 @@ private extension MainWindowController { } - func saveSplitViewState() { + func saveSplitViewState(to coder: NSCoder) { // TODO: Update this for multiple windows. @@ -591,16 +590,25 @@ private extension MainWindowController { } let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) } - if AppDefaults.mainWindowWidths != widths { - AppDefaults.mainWindowWidths = widths - } + coder.encode(widths, forKey: MainWindowController.mainWindowWidthsStateKey) + } - func restoreSplitViewState() { + func arrayOfIntFromCoder(_ coder: NSCoder, withKey: String) -> [Int]? { + let decodedFloats: [Int]? + do { + decodedFloats = try coder.decodeTopLevelObject(forKey: MainWindowController.mainWindowWidthsStateKey) as? [Int]? ?? nil + } + catch { + decodedFloats = nil + } + return decodedFloats + } + + func restoreSplitViewState(from coder: NSCoder) { // TODO: Update this for multiple windows. - - guard let splitView = splitViewController?.splitView, let widths = AppDefaults.mainWindowWidths, widths.count == 3, let window = window else { + guard let splitView = splitViewController?.splitView, let widths = arrayOfIntFromCoder(coder, withKey: MainWindowController.mainWindowWidthsStateKey), widths.count == 3, let window = window else { return } @@ -617,5 +625,6 @@ private extension MainWindowController { splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0) splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1) } + } diff --git a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift index 88a337465..c312ea91a 100644 --- a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift +++ b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift @@ -76,6 +76,26 @@ protocol SidebarDelegate: class { } } + // MARK: State Restoration + + private static let stateRestorationSelectedRowIndexes = "selectedRowIndexes" + + override func encodeRestorableState(with coder: NSCoder) { + + super.encodeRestorableState(with: coder) + + coder.encode(outlineView.selectedRowIndexes, forKey: SidebarViewController.stateRestorationSelectedRowIndexes) + } + + override func restoreState(with coder: NSCoder) { + + super.restoreState(with: coder) + + if let restoredRowIndexes = coder.decodeObject(of: [NSIndexSet.self], forKey: SidebarViewController.stateRestorationSelectedRowIndexes) as? IndexSet { + outlineView.selectRowIndexes(restoredRowIndexes, byExtendingSelection: false) + } + } + // MARK: - Notifications @objc func unreadCountDidChange(_ note: Notification) { @@ -283,6 +303,7 @@ protocol SidebarDelegate: class { func outlineViewSelectionDidChange(_ notification: Notification) { selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects) + self.invalidateRestorableState() } //MARK: - Node Manipulation diff --git a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift index 9feadf570..3f6c02fad 100644 --- a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift +++ b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift @@ -154,6 +154,26 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner { sharingServiceDelegate = SharingServiceDelegate(self.view.window) } + // MARK: State Restoration + + private static let stateRestorationSelectedArticles = "selectedArticles" + + override func encodeRestorableState(with coder: NSCoder) { + + super.encodeRestorableState(with: coder) + + coder.encode(self.selectedArticleIDs(), forKey: TimelineViewController.stateRestorationSelectedArticles) + } + + override func restoreState(with coder: NSCoder) { + + super.restoreState(with: coder) + + if let restoredArticleIDs = (try? coder.decodeTopLevelObject(forKey: TimelineViewController.stateRestorationSelectedArticles)) as? [String] { + self.restoreSelection(restoredArticleIDs) + } + } + // MARK: Appearance Change private func fontSizeDidChange() { @@ -627,6 +647,8 @@ extension TimelineViewController: NSTableViewDelegate { } postTimelineSelectionDidChangeNotification(selectedArticles) + + self.invalidateRestorableState() } private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) { diff --git a/Technotes/HelpBook.opml b/Technotes/HelpBook.opml new file mode 100644 index 000000000..b26f00e3a --- /dev/null +++ b/Technotes/HelpBook.opml @@ -0,0 +1,51 @@ + + + + HelpBook + Sun, 17 Feb 2019 22:26:12 GMT + 24,27 + 10 + 207 + 46 + 983 + 910 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +