NetNewsWire/Evergreen/AppDelegate.swift

469 lines
13 KiB
Swift
Raw Normal View History

2017-05-22 22:00:45 +02:00
//
// AppDelegate.swift
// Evergreen
//
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 Cocoa
2017-05-27 19:43:27 +02:00
import DB5
import Data
2017-05-27 19:43:27 +02:00
import RSTextDrawing
import RSTree
2017-05-27 22:37:50 +02:00
import RSWeb
2017-09-18 02:12:42 +02:00
import Account
import RSCore
2017-05-27 19:43:27 +02:00
var appDelegate: AppDelegate!
2017-05-22 22:00:45 +02:00
@NSApplicationMain
2017-11-19 22:57:42 +01:00
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UnreadCountProvider {
2017-05-22 22:00:45 +02:00
var currentTheme: VSTheme!
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!
2017-11-27 04:57:45 +01:00
var feedIconDownloader: FeedIconDownloader!
var appName: String!
@IBOutlet var debugMenuItem: NSMenuItem!
lazy var genericFeedImage: NSImage? = {
let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns"
let image = NSImage(contentsOfFile: path)
return image
}()
lazy var sendToCommands: [SendToCommand] = {
return [SendToMicroBlogCommand(), SendToMarsEditCommand()]
}()
2017-05-27 19:43:27 +02:00
var unreadCount = 0 {
didSet {
if unreadCount != oldValue {
dockBadge.update()
2017-11-19 22:57:42 +01:00
postUnreadCountDidChangeNotification()
}
2017-05-27 19:43:27 +02:00
}
}
private let windowControllers = NSMutableArray()
private var preferencesWindowController: NSWindowController?
private var mainWindowController: NSWindowController?
private var readerWindows = [NSWindowController]()
private var feedListWindowController: NSWindowController?
private var addFeedController: AddFeedController?
private var addFolderWindowController: AddFolderWindowController?
private var keyboardShortcutsWindowController: WebViewWindowController?
private var inspectorWindowController: InspectorWindowController?
private var logWindowController: LogWindowController?
private let log = Log()
private let themeLoader = VSThemeLoader()
private let appNewsURLString = "https://ranchero.com/evergreen/feed.json"
private let dockBadge = DockBadge()
2017-05-27 19:43:27 +02:00
override init() {
NSWindow.allowsAutomaticWindowTabbing = false
super.init()
dockBadge.appDelegate = self
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(sidebarSelectionDidChange(_:)), name: .SidebarSelectionDidChange, object: nil)
appDelegate = self
}
// MARK: - API
func logMessage(_ message: String, type: LogItem.ItemType) {
2017-11-26 01:15:17 +01:00
#if DEBUG
print("logMessage: \(message) - \(type)")
#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)
}
// MARK: - NSApplicationDelegate
2017-05-27 19:43:27 +02:00
func applicationDidFinishLaunching(_ note: Notification) {
appName = Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String
2017-09-23 21:17:14 +02:00
let isFirstRun = AppDefaults.shared.isFirstRun
if isFirstRun {
logDebugMessage("Is first run.")
}
2017-09-24 21:24:44 +02:00
let localAccount = AccountManager.shared.localAccount
2017-09-27 06:43:40 +02:00
DefaultFeedsImporter.importIfNeeded(isFirstRun, account: localAccount)
2017-05-27 19:43:27 +02:00
currentTheme = themeLoader.defaultTheme
let tempDirectory = NSTemporaryDirectory()
let cacheFolder = (tempDirectory as NSString).appendingPathComponent("com.ranchero.evergreen")
2017-11-26 05:12:53 +01:00
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)
2017-11-26 05:12:53 +01:00
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)
2017-11-26 22:16:32 +01:00
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
2017-11-27 04:57:45 +01:00
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader)
2017-05-27 19:43:27 +02:00
createAndShowMainWindow()
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.getURL(_:_:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
2017-11-26 22:16:32 +01:00
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
DispatchQueue.main.async {
self.unreadCount = AccountManager.shared.unreadCount
}
if InspectorWindowController.shouldOpenAtStartup {
self.toggleInspectorWindow(self)
}
#if RELEASE
debugMenuItem.menu?.removeItem(debugMenuItem)
DispatchQueue.main.async {
self.refreshAll(self)
}
#endif
2017-05-27 19:43:27 +02:00
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
if (!flag) {
createAndShowMainWindow()
}
return false
}
func applicationDidResignActive(_ notification: Notification) {
RSSingleLineRenderer.emptyCache()
RSMultiLineRenderer.emptyCache()
TimelineCellData.emptyCache()
timelineEmptyCaches()
saveState()
}
func applicationWillTerminate(_ notification: Notification) {
saveState()
2017-05-27 19:43:27 +02:00
}
// MARK: GetURL Apple Event
2017-09-18 02:12:42 +02:00
@objc func getURL(_ event: NSAppleEventDescriptor, _ withReplyEvent: NSAppleEventDescriptor) {
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
guard let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else {
return
}
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
let normalizedURLString = urlString.rs_normalizedURL()
if !normalizedURLString.rs_stringMayBeURL() {
return
}
DispatchQueue.main.async {
self.addFeed(normalizedURLString)
}
2017-05-22 22:00:45 +02:00
}
2017-05-27 19:43:27 +02:00
// MARK: Notifications
@objc func unreadCountDidChange(_ note: Notification) {
2017-05-27 19:43:27 +02:00
if note.object is AccountManager {
unreadCount = AccountManager.shared.unreadCount
}
2017-05-27 19:43:27 +02:00
}
2017-11-26 22:16:32 +01:00
@objc func feedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed else {
return
}
let _ = faviconDownloader.favicon(for: feed)
}
@objc func sidebarSelectionDidChange(_ note: Notification) {
guard let inspectorWindowController = inspectorWindowController, inspectorWindowController.isOpen else {
return
}
inspectorWindowController.objects = objectsForInspector()
}
2017-05-27 19:43:27 +02:00
// MARK: Main Window
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
2017-09-18 02:12:42 +02:00
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: storyboardName), bundle: nil)
2017-05-27 19:43:27 +02:00
return storyboard.instantiateInitialController()! as! NSWindowController
}
func createAndShowMainWindow() {
if mainWindowController == nil {
mainWindowController = createReaderWindow()
2017-05-27 19:43:27 +02:00
}
mainWindowController!.showWindow(self)
}
// MARK: NSUserInterfaceValidations
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if item.action == #selector(refreshAll(_:)) {
2017-09-23 21:17:14 +02:00
return !AccountManager.shared.refreshInProgress
2017-05-27 19:43:27 +02:00
}
if item.action == #selector(addAppNews(_:)) {
2017-09-23 21:17:14 +02:00
return !AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString)
2017-05-27 19:43:27 +02:00
}
return true
}
// MARK: Add Feed
func addFeed(_ urlString: String?, _ name: String? = nil) {
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
createAndShowMainWindow()
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
addFeedController?.showAddFeedSheet(urlString, name)
}
2017-09-23 21:17:14 +02:00
// MARK: - Actions
2017-05-27 19:43:27 +02:00
@IBAction func newReaderWindow(_ sender: Any?) {
let readerWindow = createReaderWindow()
readerWindows += [readerWindow]
readerWindow.showWindow(self)
}
2017-05-27 19:43:27 +02:00
@IBAction func showPreferences(_ sender: AnyObject) {
if preferencesWindowController == nil {
preferencesWindowController = windowControllerWithName("Preferences")
}
preferencesWindowController!.showWindow(self)
}
@IBAction func showMainWindow(_ sender: AnyObject) {
createAndShowMainWindow()
}
@IBAction func refreshAll(_ sender: AnyObject) {
2017-09-24 21:24:44 +02:00
AccountManager.shared.refreshAll()
2017-05-27 19:43:27 +02:00
}
@IBAction func showAddFeedWindow(_ sender: AnyObject) {
addFeed(nil)
}
@IBAction func showAddFolderWindow(_ sender: AnyObject) {
createAndShowMainWindow()
showAddFolderSheetOnWindow(mainWindowController!.window!)
2017-05-27 19:43:27 +02:00
}
@IBAction func showFeedList(_ sender: AnyObject) {
if feedListWindowController == nil {
feedListWindowController = windowControllerWithName("FeedList")
2017-05-27 19:43:27 +02:00
}
feedListWindowController!.showWindow(self)
}
@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)
let size = NSSize(width: 620, height: 1000)
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 showLogWindow(_ sender: Any?) {
if logWindowController == nil {
logWindowController = LogWindowController(title: "Errors", log: log)
}
logWindowController!.showWindow(self)
}
2017-05-27 19:43:27 +02:00
@IBAction func importOPMLFromFile(_ sender: AnyObject) {
let panel = NSOpenPanel()
panel.canDownloadUbiquitousContents = true
panel.canResolveUbiquitousConflicts = true
panel.canChooseFiles = true
panel.allowsMultipleSelection = false
panel.canChooseDirectories = false
panel.resolvesAliases = true
panel.allowedFileTypes = ["opml"]
panel.allowsOtherFileTypes = false
let result = panel.runModal()
2017-09-23 21:17:14 +02:00
if result == NSApplication.ModalResponse.OK, let url = panel.url {
DispatchQueue.main.async {
do {
try OPMLImporter.parseAndImport(fileURL: url, account: AccountManager.shared.localAccount)
}
catch let error as NSError {
NSApplication.shared.presentError(error)
}
2017-05-27 19:43:27 +02:00
}
}
}
2017-09-23 21:17:14 +02:00
2017-05-27 20:33:31 +02:00
@IBAction func importOPMLFromURL(_ sender: AnyObject) {
}
2017-05-27 19:43:27 +02:00
@IBAction func exportOPML(_ sender: AnyObject) {
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.nameFieldStringValue = "MySubscriptions.opml"
let result = panel.runModal()
2017-10-07 21:00:47 +02:00
if result == NSApplication.ModalResponse.OK, let url = panel.url {
2017-09-23 21:17:14 +02:00
DispatchQueue.main.async {
let filename = url.lastPathComponent
let opmlString = OPMLExporter.OPMLString(with: AccountManager.shared.localAccount, title: filename)
2017-09-23 21:17:14 +02:00
do {
try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8)
}
catch let error as NSError {
NSApplication.shared.presentError(error)
2017-05-27 19:43:27 +02:00
}
}
}
}
2017-09-23 21:17:14 +02:00
2017-05-27 19:43:27 +02:00
@IBAction func addAppNews(_ sender: AnyObject) {
2017-09-23 21:17:14 +02:00
if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
2017-05-27 19:43:27 +02:00
return
}
addFeed(appNewsURLString, "Evergreen News")
}
2017-05-27 22:37:50 +02:00
@IBAction func openWebsite(_ sender: AnyObject) {
Browser.open("https://ranchero.com/evergreen/", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openRepository(_ sender: AnyObject) {
Browser.open("https://github.com/brentsimmons/Evergreen", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openBugTracker(_ sender: AnyObject) {
Browser.open("https://github.com/brentsimmons/Evergreen/issues", inBackground: false)
2017-05-27 22:37:50 +02:00
}
@IBAction func openTechnotes(_ sender: Any?) {
Browser.open("https://github.com/brentsimmons/Evergreen/tree/master/Technotes", inBackground: false)
}
2017-05-27 22:37:50 +02:00
@IBAction func showHelp(_ sender: AnyObject) {
Browser.open("https://ranchero.com/evergreen/help/1.0/", inBackground: false)
2017-05-27 22:37:50 +02:00
}
2017-11-17 03:23:07 +01:00
2017-11-20 07:39:13 +01:00
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
#if DEBUG
AccountManager.shared.accounts.forEach{ $0.debugDropConditionalGetInfo() }
2017-11-20 07:39:13 +01:00
#endif
}
2017-05-27 19:43:27 +02:00
}
private extension AppDelegate {
2017-05-22 22:00:45 +02:00
func createReaderWindow() -> NSWindowController {
return windowControllerWithName("MainWindow")
}
func objectsForInspector() -> [Any]? {
guard let window = NSApplication.shared.mainWindow, let windowController = window.windowController as? MainWindowController else {
return nil
}
return windowController.selectedObjectsInSidebar()
}
func saveState() {
if let inspectorWindowController = inspectorWindowController {
inspectorWindowController.saveState()
}
}
}