NetNewsWire/Mac/AppDelegate.swift

804 lines
26 KiB
Swift
Raw Normal View History

2017-05-22 22:00:45 +02:00
//
// AppDelegate.swift
2018-08-29 07:18:24 +02:00
// NetNewsWire
2017-05-22 22:00:45 +02:00
//
2017-05-27 19:43:27 +02:00
// Created by Brent Simmons on 7/11/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
2017-05-22 22:00:45 +02:00
//
import AppKit
import UserNotifications
import Articles
2017-05-27 19:43:27 +02:00
import RSTree
2017-05-27 22:37:50 +02:00
import RSWeb
2017-09-18 02:12:42 +02:00
import Account
import RSCore
2020-07-29 12:11:57 +02:00
import RSCoreResources
import Secrets
// If we're not going to import Sparkle, provide dummy protocols to make it easy
// for AppDelegate to comply
#if MAC_APP_STORE || TEST
protocol SPUStandardUserDriverDelegate {}
protocol SPUUpdaterDelegate {}
#else
2019-10-22 23:42:17 +02:00
import Sparkle
#endif
2017-05-27 19:43:27 +02:00
var appDelegate: AppDelegate!
2017-05-22 22:00:45 +02:00
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate
{
2017-05-22 22:00:45 +02:00
private struct WindowRestorationIdentifiers {
static let mainWindow = "mainWindow"
}
var userNotificationManager: UserNotificationManager!
var faviconDownloader: FaviconDownloader!
2017-11-26 05:12:53 +01:00
var imageDownloader: ImageDownloader!
2017-11-26 22:16:32 +01:00
var authorAvatarDownloader: AuthorAvatarDownloader!
var webFeedIconDownloader: WebFeedIconDownloader!
2020-08-14 01:03:39 +02:00
var extensionContainersFile: ExtensionContainersFile!
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
var appName: String!
var refreshTimer: AccountRefreshTimer?
var syncTimer: ArticleStatusSyncTimer?
var shuttingDown = false {
didSet {
if shuttingDown {
refreshTimer?.shuttingDown = shuttingDown
refreshTimer?.invalidate()
syncTimer?.shuttingDown = shuttingDown
syncTimer?.invalidate()
}
}
}
@IBOutlet var debugMenuItem: NSMenuItem!
2018-01-28 00:11:02 +01:00
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
2019-09-09 00:09:26 +02:00
@IBOutlet var groupArticlesByFeedMenuItem: NSMenuItem!
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
2017-05-27 19:43:27 +02:00
var unreadCount = 0 {
didSet {
if unreadCount != oldValue {
CoalescingQueue.standard.add(self, #selector(updateDockBadge))
2017-11-19 22:57:42 +01:00
postUnreadCountDidChangeNotification()
}
2017-05-27 19:43:27 +02:00
}
}
private var mainWindowController: MainWindowController? {
var bestController: MainWindowController?
for candidateController in mainWindowControllers {
if let bestWindow = bestController?.window, let candidateWindow = candidateController.window {
if bestWindow.orderedIndex > candidateWindow.orderedIndex {
bestController = candidateController
}
} else {
bestController = candidateController
}
}
return bestController
}
private var mainWindowControllers = [MainWindowController]()
private var preferencesWindowController: NSWindowController?
private var addFeedController: AddFeedController?
private var addFolderWindowController: AddFolderWindowController?
private var importOPMLController: ImportOPMLWindowController?
private var exportOPMLController: ExportOPMLWindowController?
private var keyboardShortcutsWindowController: WebViewWindowController?
private var inspectorWindowController: InspectorWindowController?
private var crashReportWindowController: CrashReportWindowController? // For testing only
private let log = Log()
private let appNewsURLString = "https://nnw.ranchero.com/feed.json"
private let appMovementMonitor = RSAppMovementMonitor()
#if !MAC_APP_STORE && !TEST
private var softwareUpdater: SPUUpdater!
#endif
2017-05-27 19:43:27 +02:00
override init() {
NSWindow.allowsAutomaticWindowTabbing = false
super.init()
SecretsManager.provider = Secrets()
2020-01-10 21:00:22 +01:00
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
2020-04-16 22:06:56 +02:00
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(inspectableObjectsDidChange(_:)), name: .InspectableObjectsDidChange, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil)
appDelegate = self
}
// MARK: - API
func logMessage(_ message: String, type: LogItem.ItemType) {
2017-11-26 01:15:17 +01:00
#if DEBUG
2018-09-11 07:06:50 +02:00
if type == .debug {
2017-11-26 01:15:17 +01:00
print("logMessage: \(message) - \(type)")
2018-09-11 07:06:50 +02:00
}
2017-11-26 01:15:17 +01:00
#endif
let logItem = LogItem(type: type, message: message)
log.add(logItem)
}
func logDebugMessage(_ message: String) {
logMessage(message, type: .debug)
2017-05-27 19:43:27 +02:00
}
func showAddFolderSheetOnWindow(_ window: NSWindow) {
addFolderWindowController = AddFolderWindowController()
addFolderWindowController!.runSheetOnWindow(window)
}
2020-04-22 04:25:45 +02:00
func showAddWebFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) {
addFeedController = AddFeedController(hostWindow: window)
2020-04-22 04:25:45 +02:00
addFeedController?.showAddFeedSheet(.webFeed, urlString, name, account, folder)
}
// MARK: - NSApplicationDelegate
2019-10-18 07:11:35 +02:00
func applicationWillFinishLaunching(_ notification: Notification) {
installAppleEventHandlers()
CacheCleaner.purgeIfNecessary()
// Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir
let cacheFolder: String
if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path {
cacheFolder = userCacheFolder
}
else {
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier)
}
let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons")
let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder)
try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil)
faviconDownloader = FaviconDownloader(folder: faviconsFolder)
let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images")
let imagesFolderURL = URL(fileURLWithPath: imagesFolder)
try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil)
imageDownloader = ImageDownloader(folder: imagesFolder)
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
2020-03-04 23:40:40 +01:00
appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
}
2017-05-27 19:43:27 +02:00
func applicationDidFinishLaunching(_ note: Notification) {
#if MAC_APP_STORE || TEST
checkForUpdatesMenuItem.isHidden = true
#else
// Initialize Sparkle...
let hostBundle = Bundle.main
let updateDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: self)
self.softwareUpdater = SPUUpdater(hostBundle: hostBundle, applicationBundle: hostBundle, userDriver: updateDriver, delegate: self)
do {
try self.softwareUpdater.start()
}
catch {
NSLog("Failed to start software updater with error: \(error)")
}
#endif
AppDefaults.shared.registerDefaults()
let isFirstRun = AppDefaults.shared.isFirstRun
if isFirstRun {
logDebugMessage("Is first run.")
}
let localAccount = AccountManager.shared.defaultAccount
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
// Import feeds. Either old NNW 3 feeds or the default feeds.
if !NNW3ImportController.importSubscriptionsIfFileExists(account: localAccount) {
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
}
}
2017-05-27 19:43:27 +02:00
updateSortMenuItems()
2019-09-09 00:09:26 +02:00
updateGroupByFeedMenuItem()
if mainWindowController == nil {
let mainWindowController = createAndShowMainWindow()
mainWindowController.restoreStateFromUserDefaults()
}
2019-08-24 03:30:28 +02:00
if isFirstRun {
mainWindowController?.window?.center()
}
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
2017-11-26 22:16:32 +01:00
DispatchQueue.main.async {
self.unreadCount = AccountManager.shared.unreadCount
}
if InspectorWindowController.shouldOpenAtStartup {
self.toggleInspectorWindow(self)
}
2020-08-14 01:03:39 +02:00
extensionContainersFile = ExtensionContainersFile()
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
refreshTimer = AccountRefreshTimer()
syncTimer = ArticleStatusSyncTimer()
UNUserNotificationCenter.current().requestAuthorization(options:[.badge]) { (granted, error) in }
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
if settings.authorizationStatus == .authorized {
DispatchQueue.main.async {
NSApplication.shared.registerForRemoteNotifications()
}
}
}
UNUserNotificationCenter.current().delegate = self
userNotificationManager = UserNotificationManager()
if AppDefaults.shared.showDebugMenu {
2019-11-28 21:59:45 +01:00
refreshTimer!.update()
syncTimer!.update()
// The Web Inspector uses SPI and can never appear in a MAC_APP_STORE build.
#if MAC_APP_STORE
let debugMenu = debugMenuItem.submenu!
let toggleWebInspectorItemIndex = debugMenu.indexOfItem(withTarget: self, andAction: #selector(toggleWebInspectorEnabled(_:)))
if toggleWebInspectorItemIndex != -1 {
debugMenu.removeItem(at: toggleWebInspectorItemIndex)
}
#endif
} else {
debugMenuItem.menu?.removeItem(debugMenuItem)
DispatchQueue.main.async {
self.refreshTimer!.timedRefresh(nil)
self.syncTimer!.timedRefresh(nil)
}
2019-11-28 21:59:45 +01:00
}
#if !MAC_APP_STORE
DispatchQueue.main.async {
CrashReporter.check(appName: "NetNewsWire")
}
#endif
2017-05-27 19:43:27 +02:00
}
func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool {
guard let mainWindowController = mainWindowController else {
return false
}
mainWindowController.handle(userActivity)
return true
}
2017-05-27 19:43:27 +02:00
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
// https://github.com/brentsimmons/NetNewsWire/issues/522
// I couldnt reproduce the crashing bug, but it appears to happen on creating a main window
// and its views and view controllers. The check below is so that the app does nothing
// if the window doesnt already exist because it absolutely *should* exist already.
// And if the window exists, then maybe the views and view controllers are also already loaded?
// Well try this, and then see if we get more crash logs like this or not.
guard let mainWindowController = mainWindowController, mainWindowController.isWindowLoaded else {
return false
}
mainWindowController.showWindow(self)
2017-05-27 19:43:27 +02:00
return false
}
func applicationDidBecomeActive(_ notification: Notification) {
fireOldTimers()
}
2017-05-27 19:43:27 +02:00
func applicationDidResignActive(_ notification: Notification) {
ArticleStringFormatter.emptyCaches()
saveState()
}
func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
AccountManager.shared.receiveRemoteNotification(userInfo: userInfo)
}
func applicationWillTerminate(_ notification: Notification) {
shuttingDown = true
saveState()
2017-05-27 19:43:27 +02:00
}
// MARK: Notifications
@objc func unreadCountDidChange(_ note: Notification) {
if note.object is AccountManager {
unreadCount = AccountManager.shared.unreadCount
}
2017-05-27 19:43:27 +02:00
}
@objc func webFeedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
2017-11-26 22:16:32 +01:00
return
}
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
let _ = faviconDownloader.favicon(for: feed)
}
2017-11-26 22:16:32 +01:00
}
@objc func inspectableObjectsDidChange(_ note: Notification) {
guard let inspectorWindowController = inspectorWindowController, inspectorWindowController.isOpen else {
return
}
inspectorWindowController.objects = objectsForInspector()
}
@objc func userDefaultsDidChange(_ note: Notification) {
updateSortMenuItems()
2019-09-09 00:09:26 +02:00
updateGroupByFeedMenuItem()
refreshTimer?.update()
updateDockBadge()
}
@objc func didWakeNotification(_ note: Notification) {
fireOldTimers()
}
2017-05-27 19:43:27 +02:00
// MARK: Main Window
func createMainWindowController() -> MainWindowController {
let controller: MainWindowController
if #available(macOS 11.0, *) {
let storyboard = NSStoryboard(name: NSStoryboard.Name("MainWindow"), bundle: nil)
controller = storyboard.instantiateController(withIdentifier: "UnifiedWindowController") as! MainWindowController
} else {
controller = windowControllerWithName("MainWindow") as! MainWindowController
}
if !(mainWindowController?.isOpen ?? false) {
mainWindowControllers.removeAll()
}
mainWindowControllers.append(controller)
return controller
}
2017-05-27 19:43:27 +02:00
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
2018-12-09 21:32:33 +01:00
let storyboard = NSStoryboard(name: NSStoryboard.Name(storyboardName), bundle: nil)
2017-05-27 19:43:27 +02:00
return storyboard.instantiateInitialController()! as! NSWindowController
}
@discardableResult
func createAndShowMainWindow() -> MainWindowController {
let controller = createMainWindowController()
controller.showWindow(self)
if let window = controller.window {
window.restorationClass = Self.self
window.identifier = NSUserInterfaceItemIdentifier(rawValue: WindowRestorationIdentifiers.mainWindow)
}
return controller
}
2017-05-27 19:43:27 +02:00
func createAndShowMainWindowIfNecessary() {
2017-05-27 19:43:27 +02:00
if mainWindowController == nil {
createAndShowMainWindow()
} else {
mainWindowController?.showWindow(self)
2017-05-27 19:43:27 +02:00
}
}
func removeMainWindow(_ windowController: MainWindowController) {
guard mainWindowControllers.count > 1 else { return }
if let index = mainWindowControllers.firstIndex(of: windowController) {
mainWindowControllers.remove(at: index)
}
}
2017-05-27 19:43:27 +02:00
// MARK: NSUserInterfaceValidations
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if shuttingDown {
return false
}
2017-05-27 19:43:27 +02:00
let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
let isSpecialAccountAvailable = AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit })
2017-05-27 19:43:27 +02:00
if item.action == #selector(refreshAll(_:)) {
return !AccountManager.shared.refreshInProgress && !AccountManager.shared.activeAccounts.isEmpty
2017-05-27 19:43:27 +02:00
}
if item.action == #selector(importOPMLFromFile(_:)) {
return AccountManager.shared.activeAccounts.contains(where: { !$0.behaviors.contains(where: { $0 == .disallowOPMLImports }) })
}
2017-05-27 19:43:27 +02:00
if item.action == #selector(addAppNews(_:)) {
return !isDisplayingSheet && !AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) && !AccountManager.shared.activeAccounts.isEmpty
2017-05-27 19:43:27 +02:00
}
2018-01-28 00:13:45 +01:00
if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) {
return mainWindowController?.isOpen ?? false
}
2020-04-22 04:25:45 +02:00
if item.action == #selector(showAddWebFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty
}
2020-05-10 18:44:30 +02:00
if item.action == #selector(showAddRedditFeedWindow(_:)) {
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isRedditEnabled else {
2020-05-10 18:44:30 +02:00
return false
}
return ExtensionPointManager.shared.isRedditEnabled
}
if item.action == #selector(showAddTwitterFeedWindow(_:)) {
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isTwitterEnabled else {
return false
}
return ExtensionPointManager.shared.isTwitterEnabled
}
#if !MAC_APP_STORE
if item.action == #selector(toggleWebInspectorEnabled(_:)) {
(item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off
}
#endif
2017-05-27 19:43:27 +02:00
return true
}
// MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
2019-10-03 18:39:48 +02:00
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
mainWindowController?.handle(response)
completionHandler()
}
2017-05-27 19:43:27 +02:00
// MARK: Add Feed
2020-04-22 04:25:45 +02:00
func addWebFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
createAndShowMainWindowIfNecessary()
if mainWindowController!.isDisplayingSheet {
return
}
2017-05-27 19:43:27 +02:00
2020-04-22 04:25:45 +02:00
showAddWebFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder)
2017-05-27 19:43:27 +02:00
}
// MARK: - Dock Badge
@objc func updateDockBadge() {
let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : ""
NSApplication.shared.dockTile.badgeLabel = label
}
2017-09-23 21:17:14 +02:00
// MARK: - Actions
@IBAction func showPreferences(_ sender: Any?) {
2017-05-27 19:43:27 +02:00
if preferencesWindowController == nil {
preferencesWindowController = windowControllerWithName("Preferences")
}
preferencesWindowController!.showWindow(self)
}
@IBAction func newMainWindow(_ sender: Any?) {
2017-05-27 19:43:27 +02:00
createAndShowMainWindow()
}
@IBAction func showMainWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
mainWindowController?.window?.makeKey()
}
2017-05-27 19:43:27 +02:00
@IBAction func refreshAll(_ sender: Any?) {
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
2017-05-27 19:43:27 +02:00
}
2020-04-22 04:25:45 +02:00
@IBAction func showAddWebFeedWindow(_ sender: Any?) {
addWebFeed(nil)
}
2020-05-10 18:44:30 +02:00
@IBAction func showAddRedditFeedWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
addFeedController?.showAddFeedSheet(.redditFeed)
}
2020-04-22 04:25:45 +02:00
@IBAction func showAddTwitterFeedWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
addFeedController?.showAddFeedSheet(.twitterFeed)
2017-05-27 19:43:27 +02:00
}
@IBAction func showAddFolderWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
showAddFolderSheetOnWindow(mainWindowController!.window!)
2017-05-27 19:43:27 +02:00
}
@IBAction func showKeyboardShortcutsWindow(_ sender: Any?) {
if keyboardShortcutsWindowController == nil {
keyboardShortcutsWindowController = WebViewWindowController(title: NSLocalizedString("Keyboard Shortcuts", comment: "window title"))
let htmlFile = Bundle(for: type(of: self)).path(forResource: "KeyboardShortcuts", ofType: "html")!
keyboardShortcutsWindowController?.displayContents(of: htmlFile)
if let window = keyboardShortcutsWindowController?.window {
let point = NSPoint(x: 128, y: 64)
2019-03-04 21:39:30 +01:00
let size = NSSize(width: 620, height: 1100)
let minSize = NSSize(width: 400, height: 400)
window.setPointAndSizeAdjustingForScreen(point: point, size: size, minimumSize: minSize)
}
}
keyboardShortcutsWindowController!.showWindow(self)
}
@IBAction func toggleInspectorWindow(_ sender: Any?) {
if inspectorWindowController == nil {
inspectorWindowController = (windowControllerWithName("Inspector") as! InspectorWindowController)
}
if inspectorWindowController!.isOpen {
inspectorWindowController!.window!.performClose(self)
}
else {
inspectorWindowController!.objects = objectsForInspector()
inspectorWindowController!.showWindow(self)
}
}
@IBAction func importOPMLFromFile(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
if mainWindowController!.isDisplayingSheet {
return
2017-05-27 19:43:27 +02:00
}
importOPMLController = ImportOPMLWindowController()
importOPMLController?.runSheetOnWindow(mainWindowController!.window!)
}
@IBAction func importNNW3FromFile(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
if mainWindowController!.isDisplayingSheet {
return
}
NNW3ImportController.askUserToImportNNW3Subscriptions(window: mainWindowController!.window!)
2017-05-27 19:43:27 +02:00
}
2017-09-23 21:17:14 +02:00
@IBAction func exportOPML(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
if mainWindowController!.isDisplayingSheet {
return
2017-05-27 19:43:27 +02:00
}
exportOPMLController = ExportOPMLWindowController()
exportOPMLController?.runSheetOnWindow(mainWindowController!.window!)
2017-05-27 19:43:27 +02:00
}
2017-09-23 21:17:14 +02:00
@IBAction func addAppNews(_ sender: Any?) {
2017-09-23 21:17:14 +02:00
if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
2017-05-27 19:43:27 +02:00
return
}
2020-04-22 04:25:45 +02:00
addWebFeed(appNewsURLString, name: "NetNewsWire News")
2017-05-27 19:43:27 +02:00
}
2017-05-27 22:37:50 +02:00
@IBAction func openWebsite(_ sender: Any?) {
2017-05-27 22:37:50 +02:00
2018-08-29 07:18:24 +02:00
Browser.open("https://ranchero.com/netnewswire/", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openReleaseNotes(_ sender: Any?) {
Browser.open(URL.releaseNotes.absoluteString, inBackground: false)
}
2017-05-27 22:37:50 +02:00
@IBAction func openHowToSupport(_ sender: Any?) {
2020-07-26 11:46:48 +02:00
Browser.open("https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown", inBackground: false)
}
@IBAction func openRepository(_ sender: Any?) {
2017-05-27 22:37:50 +02:00
2018-08-29 07:18:24 +02:00
Browser.open("https://github.com/brentsimmons/NetNewsWire", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openBugTracker(_ sender: Any?) {
2017-05-27 22:37:50 +02:00
2018-08-29 07:18:24 +02:00
Browser.open("https://github.com/brentsimmons/NetNewsWire/issues", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openSlackGroup(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/slack", inBackground: false)
}
@IBAction func openTechnotes(_ sender: Any?) {
2020-07-26 11:46:48 +02:00
Browser.open("https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes", inBackground: false)
}
@IBAction func showHelp(_ sender: Any?) {
2017-05-27 22:37:50 +02:00
Browser.open("https://ranchero.com/netnewswire/help/mac/5.1/en/", inBackground: false)
2017-05-27 22:37:50 +02:00
}
2017-11-17 03:23:07 +01:00
@IBAction func donateToAppCampForGirls(_ sender: Any?) {
Browser.open("https://appcamp4girls.com/contribute/", inBackground: false)
}
2018-12-29 22:59:14 +01:00
@IBAction func showPrivacyPolicy(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/privacypolicy", inBackground: false)
}
@IBAction func gotoToday(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
mainWindowController!.gotoToday(sender)
}
@IBAction func gotoAllUnread(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
mainWindowController!.gotoAllUnread(sender)
}
@IBAction func gotoStarred(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
mainWindowController!.gotoStarred(sender)
}
2018-01-28 00:11:02 +01:00
@IBAction func sortByOldestArticleOnTop(_ sender: Any?) {
AppDefaults.shared.timelineSortDirection = .orderedAscending
2018-01-28 00:11:02 +01:00
}
@IBAction func sortByNewestArticleOnTop(_ sender: Any?) {
AppDefaults.shared.timelineSortDirection = .orderedDescending
2018-01-28 00:11:02 +01:00
}
2019-09-09 00:09:26 +02:00
@IBAction func groupByFeedToggled(_ sender: NSMenuItem) {
AppDefaults.shared.timelineGroupByFeed.toggle()
2019-09-09 00:09:26 +02:00
}
@IBAction func checkForUpdates(_ sender: Any?) {
#if !MAC_APP_STORE && !TEST
self.softwareUpdater.checkForUpdates()
#endif
}
2017-05-27 19:43:27 +02:00
}
// MARK: - Debug Menu
extension AppDelegate {
@IBAction func debugSearch(_ sender: Any?) {
AccountManager.shared.defaultAccount.debugRunSearch()
}
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
#if DEBUG
AccountManager.shared.activeAccounts.forEach{ $0.debugDropConditionalGetInfo() }
#endif
}
@IBAction func debugTestCrashReporterWindow(_ sender: Any?) {
#if DEBUG
crashReportWindowController = CrashReportWindowController(crashLogText: "This is a test crash log.")
crashReportWindowController!.testing = true
crashReportWindowController!.showWindow(self)
#endif
}
@IBAction func debugTestCrashReportSending(_ sender: Any?) {
#if DEBUG
CrashReporter.sendCrashLogText("This is a test. Hi, Brent.")
#endif
}
@IBAction func openApplicationSupportFolder(_ sender: Any?) {
#if DEBUG
guard let appSupport = Platform.dataSubfolder(forApplication: nil, folderName: "") else { return }
NSWorkspace.shared.open(URL(fileURLWithPath: appSupport))
#endif
}
@IBAction func toggleWebInspectorEnabled(_ sender: Any?) {
#if !MAC_APP_STORE
let newValue = !AppDefaults.shared.webInspectorEnabled
AppDefaults.shared.webInspectorEnabled = newValue
2019-09-17 20:58:45 +02:00
// An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window,
// and reset the default to a separate window when the preference is toggled off and on again in case the inspector is
// accidentally reattached.
AppDefaults.shared.webInspectorStartsAttached = false
NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue)
#endif
}
}
private extension AppDelegate {
2017-05-22 22:00:45 +02:00
func fireOldTimers() {
// Its possible theres a refresh timer set to go off in the past.
// In that case, refresh now and update the timer.
refreshTimer?.fireOldTimer()
syncTimer?.fireOldTimer()
}
func objectsForInspector() -> [Any]? {
guard let window = NSApplication.shared.mainWindow, let windowController = window.windowController as? MainWindowController else {
return nil
}
return windowController.selectedObjectsInSidebar()
}
func saveState() {
mainWindowController?.saveStateToUserDefaults()
inspectorWindowController?.saveState()
}
func updateSortMenuItems() {
let sortByNewestOnTop = AppDefaults.shared.timelineSortDirection == .orderedDescending
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
}
2019-09-09 00:09:26 +02:00
func updateGroupByFeedMenuItem() {
let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed
2019-09-09 00:09:26 +02:00
groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off
}
}
/*
the ScriptingAppDelegate protocol exposes a narrow set of accessors with
internal visibility which are very similar to some private vars.
These would be unnecessary if the similar accessors were marked internal rather than private,
but for now, we'll keep the stratification of visibility
*/
extension AppDelegate : ScriptingAppDelegate {
internal var scriptingMainWindowController: ScriptingMainWindowController? {
return mainWindowController
}
internal var scriptingCurrentArticle: Article? {
return self.scriptingMainWindowController?.scriptingCurrentArticle
}
internal var scriptingSelectedArticles: [Article] {
return self.scriptingMainWindowController?.scriptingSelectedArticles ?? []
}
}
extension AppDelegate: NSWindowRestoration {
@objc static func restoreWindow(withIdentifier identifier: NSUserInterfaceItemIdentifier, state: NSCoder, completionHandler: @escaping (NSWindow?, Error?) -> Void) {
var mainWindow: NSWindow? = nil
if identifier.rawValue == WindowRestorationIdentifiers.mainWindow {
mainWindow = appDelegate.createAndShowMainWindow().window
}
completionHandler(mainWindow, nil)
}
}