This commit is contained in:
Brent Simmons 2019-02-18 13:33:01 -08:00
commit e0514cb665
7 changed files with 146 additions and 35 deletions

View File

@ -53,7 +53,6 @@ struct AppDefaults {
static let timelineSortDirection = "timelineSortDirection" static let timelineSortDirection = "timelineSortDirection"
static let detailFontSize = "detailFontSize" static let detailFontSize = "detailFontSize"
static let openInBrowserInBackground = "openInBrowserInBackground" static let openInBrowserInBackground = "openInBrowserInBackground"
static let mainWindowWidths = "mainWindowWidths"
static let refreshInterval = "refreshInterval" static let refreshInterval = "refreshInterval"
// Hidden prefs // 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 { static var refreshInterval: RefreshInterval {
get { get {
let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) 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] 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) 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 { static func actualFontSize(for fontSize: FontSize) -> CGFloat {

View File

@ -576,7 +576,6 @@ private extension AppDelegate {
func saveState() { func saveState() {
inspectorWindowController?.saveState() inspectorWindowController?.saveState()
mainWindowController?.saveState()
} }
func updateSortMenuItems() { func updateSortMenuItems() {

View File

@ -51,6 +51,10 @@ body.light .articleDateline, body.light .articleDateLine.a:link, body.light .art
color: rgba(0, 0, 0, 0.3); color: rgba(0, 0, 0, 0.3);
} }
body.light code, body.light pre {
color: #666;
}
.light > .systemMessage { .light > .systemMessage {
color: #cbcbcb; color: #cbcbcb;
} }
@ -81,6 +85,10 @@ body.dark .articleDateline, body.dark .articleDateLine.a:link, body.dark .articl
color: #d2d2d2; color: #d2d2d2;
} }
body.dark code, body.dark pre {
color: #b2b2b2;
}
.dark > .systemMessage { .dark > .systemMessage {
color: #5f5f5f; color: #5f5f5f;
} }
@ -120,7 +128,6 @@ h1 {
code, pre { code, pre {
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace; font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
font-size: 14px; font-size: 14px;
color: #666;
} }
pre { pre {
white-space: pre-wrap; white-space: pre-wrap;

View File

@ -15,12 +15,11 @@ enum TimelineSourceMode {
case regular, search case regular, search
} }
class MainWindowController : NSWindowController, NSUserInterfaceValidations { class MainWindowController : NSWindowController, NSUserInterfaceValidations, NSWindowDelegate {
@IBOutlet var toolbarDelegate: MainWindowToolbarDelegate? @IBOutlet var toolbarDelegate: MainWindowToolbarDelegate?
private var sharingServicePickerDelegate: NSSharingServicePickerDelegate? private var sharingServicePickerDelegate: NSSharingServicePickerDelegate?
private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow")
static var didPositionWindowOnFirstRun = false static var didPositionWindowOnFirstRun = false
private var currentFeedOrFolder: AnyObject? { private var currentFeedOrFolder: AnyObject? {
@ -52,7 +51,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
window?.titleVisibility = .hidden window?.titleVisibility = .hidden
} }
window?.setFrameUsingName(windowAutosaveName, force: true)
if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun { if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun {
if let window = window { if let window = window {
@ -65,7 +63,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
} }
detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness) detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness)
restoreSplitViewState()
sidebarViewController = splitViewController?.splitViewItems[0].viewController as? SidebarViewController sidebarViewController = splitViewController?.splitViewItems[0].viewController as? SidebarViewController
sidebarViewController!.delegate = self sidebarViewController!.delegate = self
@ -75,8 +72,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
detailViewController = splitViewController?.splitViewItems[2].viewController as? DetailViewController 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: .AccountRefreshDidBegin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
@ -91,12 +86,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
// MARK: - API // MARK: - API
func saveState() {
saveSplitViewState()
}
func selectedObjectsInSidebar() -> [AnyObject]? { func selectedObjectsInSidebar() -> [AnyObject]? {
return sidebarViewController?.selectedObjects return sidebarViewController?.selectedObjects
@ -104,10 +93,18 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
// MARK: - Notifications // MARK: - Notifications
@objc func applicationWillTerminate(_ note: Notification) { func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) {
saveState() saveSplitViewState(to: state)
window?.saveFrame(usingName: windowAutosaveName) }
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) { @objc func refreshProgressDidChange(_ note: Notification) {
@ -403,7 +400,9 @@ extension MainWindowController : ScriptingMainWindowController {
// MARK: - Private // MARK: - Private
private extension MainWindowController { private extension MainWindowController {
static let mainWindowWidthsStateKey = "mainWindowWidths"
var splitViewController: NSSplitViewController? { var splitViewController: NSSplitViewController? {
guard let viewController = contentViewController else { guard let viewController = contentViewController else {
return nil return nil
@ -582,7 +581,7 @@ private extension MainWindowController {
} }
func saveSplitViewState() { func saveSplitViewState(to coder: NSCoder) {
// TODO: Update this for multiple windows. // TODO: Update this for multiple windows.
@ -591,16 +590,25 @@ private extension MainWindowController {
} }
let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) } let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
if AppDefaults.mainWindowWidths != widths { coder.encode(widths, forKey: MainWindowController.mainWindowWidthsStateKey)
AppDefaults.mainWindowWidths = widths
}
} }
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. // TODO: Update this for multiple windows.
guard let splitView = splitViewController?.splitView, let widths = arrayOfIntFromCoder(coder, withKey: MainWindowController.mainWindowWidthsStateKey), widths.count == 3, let window = window else {
guard let splitView = splitViewController?.splitView, let widths = AppDefaults.mainWindowWidths, widths.count == 3, let window = window else {
return return
} }
@ -617,5 +625,6 @@ private extension MainWindowController {
splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0) splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0)
splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1) splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1)
} }
} }

View File

@ -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 // MARK: - Notifications
@objc func unreadCountDidChange(_ note: Notification) { @objc func unreadCountDidChange(_ note: Notification) {
@ -283,6 +303,7 @@ protocol SidebarDelegate: class {
func outlineViewSelectionDidChange(_ notification: Notification) { func outlineViewSelectionDidChange(_ notification: Notification) {
selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects) selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects)
self.invalidateRestorableState()
} }
//MARK: - Node Manipulation //MARK: - Node Manipulation

View File

@ -154,6 +154,26 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
sharingServiceDelegate = SharingServiceDelegate(self.view.window) 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 // MARK: Appearance Change
private func fontSizeDidChange() { private func fontSizeDidChange() {
@ -627,6 +647,8 @@ extension TimelineViewController: NSTableViewDelegate {
} }
postTimelineSelectionDidChangeNotification(selectedArticles) postTimelineSelectionDidChangeNotification(selectedArticles)
self.invalidateRestorableState()
} }
private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) { private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {

51
Technotes/HelpBook.opml Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head><!-- <editor scale-mac="1.5">
<sidebar width="203"/>
<column name="text" width="907"/>
</editor> -->
<title>HelpBook</title>
<dateCreated>Sun, 17 Feb 2019 22:26:12 GMT</dateCreated>
<expansionState>24,27</expansionState>
<vertScrollState>10</vertScrollState>
<windowTop>207</windowTop>
<windowLeft>46</windowLeft>
<windowRight>983</windowRight>
<windowBottom>910</windowBottom>
</head>
<body>
<outline text="How to add feeds"/>
<outline text="Installing Safari extension"/>
<outline text="How to go through articles"/>
<outline text="What is a feed?"/>
<outline text="Keyboard shortcuts"/>
<outline text="Contributing - Slack group, GitHub"/>
<outline text="Getting NetNewsWire news"/>
<outline text="Privacy"/>
<outline text="About the On My Mac account"/>
<outline text="Adding a syncing account"/>
<outline text="Importing subscriptions from another app - OPML"/>
<outline text="Exporting subscriptions to use in a another app - OPML"/>
<outline text="Sharing with other apps, particularly blogging apps"/>
<outline text="Adding microblog feeds"/>
<outline text="Renaming feeds and folders"/>
<outline text="Getting and copying feed information"/>
<outline text="OPML file on disk"/>
<outline text="Other app data on disk"/>
<outline text="Searching"/>
<outline text="Customizing the toolbar"/>
<outline text="Hidden prefs"/>
<outline text="Updating NetNewsWire"/>
<outline text="Sorting the timeline"/>
<outline text="Smart feeds - Today, All Unread, Starred"/>
<outline text="Philosophy of saving articles">
<outline text="NetNewsWire is not a personal backup of the internet"/>
<outline text="Articles automatically marked as read"/>
</outline>
<outline text="Notes for feed publishers">
<outline text="Supported formats"/>
<outline text="Conditional GET"/>
<outline text="Where app gets favicons and images"/>
</outline>
</body>
</opml>