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
|
2017-09-17 21:22:15 +02:00
|
|
|
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
|
2017-11-14 03:33:23 +01:00
|
|
|
import RSCore
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2017-09-23 21:17:14 +02:00
|
|
|
let appName = "Evergreen"
|
2017-05-27 19:43:27 +02:00
|
|
|
var currentTheme: VSTheme!
|
2017-11-15 06:31:17 +01:00
|
|
|
var appDelegate: AppDelegate!
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
|
|
@NSApplicationMain
|
2017-05-29 21:04:22 +02:00
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
let windowControllers = NSMutableArray()
|
|
|
|
var preferencesWindowController: NSWindowController?
|
|
|
|
var mainWindowController: NSWindowController?
|
2017-11-14 22:18:25 +01:00
|
|
|
var readerWindows = [NSWindowController]()
|
2017-05-27 19:43:27 +02:00
|
|
|
var feedListWindowController: NSWindowController?
|
2017-11-12 20:57:51 +01:00
|
|
|
var dinosaursWindowController: DinosaursWindowController?
|
2017-05-27 19:43:27 +02:00
|
|
|
var addFeedController: AddFeedController?
|
|
|
|
var addFolderWindowController: AddFolderWindowController?
|
2017-11-14 03:33:23 +01:00
|
|
|
var keyboardShortcutsWindowController: WebViewWindowController?
|
2017-11-16 07:33:35 +01:00
|
|
|
var inspectorWindowController: InspectorWindowController?
|
2017-11-16 07:40:49 +01:00
|
|
|
var logWindowController: LogWindowController?
|
2017-11-17 03:23:07 +01:00
|
|
|
var panicButtonWindowController: PanicButtonWindowController?
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
let log = Log()
|
2017-05-27 19:43:27 +02:00
|
|
|
let themeLoader = VSThemeLoader()
|
2017-06-01 23:10:04 +02:00
|
|
|
private let appNewsURLString = "https://ranchero.com/evergreen/feed.json"
|
2017-10-06 03:01:18 +02:00
|
|
|
private let dockBadge = DockBadge()
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
var unreadCount = 0 {
|
|
|
|
didSet {
|
2017-10-06 03:12:58 +02:00
|
|
|
if unreadCount != oldValue {
|
|
|
|
dockBadge.update()
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override init() {
|
|
|
|
|
|
|
|
NSWindow.allowsAutomaticWindowTabbing = false
|
|
|
|
super.init()
|
2017-10-06 03:01:18 +02:00
|
|
|
dockBadge.appDelegate = self
|
2017-10-19 03:37:45 +02:00
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
2017-11-15 06:31:17 +01:00
|
|
|
appDelegate = self
|
|
|
|
}
|
|
|
|
|
2017-11-15 22:13:40 +01:00
|
|
|
// MARK: - API
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
func logMessage(_ message: String, type: LogItem.ItemType) {
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-11-15 22:13:40 +01:00
|
|
|
func showAddFolderSheetOnWindow(_ window: NSWindow) {
|
|
|
|
|
|
|
|
addFolderWindowController = AddFolderWindowController()
|
|
|
|
addFolderWindowController!.runSheetOnWindow(window)
|
|
|
|
}
|
|
|
|
|
2017-11-17 03:23:07 +01:00
|
|
|
func hitPanicButton(with window: NSWindow) {
|
|
|
|
|
|
|
|
panicButtonWindowController = PanicButtonWindowController()
|
|
|
|
panicButtonWindowController!.runSheetOnWindow(window)
|
|
|
|
}
|
|
|
|
|
2017-09-19 07:00:35 +02:00
|
|
|
// MARK: - NSApplicationDelegate
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
func applicationDidFinishLaunching(_ note: Notification) {
|
|
|
|
|
2017-09-23 21:17:14 +02:00
|
|
|
let isFirstRun = AppDefaults.shared.isFirstRun
|
2017-11-15 06:31:17 +01:00
|
|
|
logDebugMessage(isFirstRun ? "Is first run." : "Is not 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
|
2017-09-24 21:24:44 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
createAndShowMainWindow()
|
|
|
|
|
|
|
|
#if RELEASE
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.refreshAll(self)
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.getURL(_:_:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
|
2017-10-19 03:37:45 +02:00
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.unreadCount = AccountManager.shared.unreadCount
|
|
|
|
}
|
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()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2017-10-19 03:37:45 +02:00
|
|
|
@objc func unreadCountDidChange(_ note: Notification) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2017-10-19 03:37:45 +02:00
|
|
|
if note.object is AccountManager {
|
|
|
|
unreadCount = AccountManager.shared.unreadCount
|
|
|
|
}
|
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 {
|
2017-11-14 22:18:25 +01:00
|
|
|
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
|
|
|
|
2017-11-14 22:18:25 +01: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()
|
2017-11-15 22:13:40 +01:00
|
|
|
showAddFolderSheetOnWindow(mainWindowController!.window!)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showFeedList(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
if feedListWindowController == nil {
|
|
|
|
feedListWindowController = windowControllerWithName("FeedList")
|
|
|
|
}
|
|
|
|
feedListWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
|
2017-11-12 20:57:51 +01:00
|
|
|
@IBAction func showDinosaursWindow(_ sender: Any?) {
|
|
|
|
|
|
|
|
if dinosaursWindowController == nil {
|
|
|
|
dinosaursWindowController = DinosaursWindowController()
|
|
|
|
}
|
|
|
|
dinosaursWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
|
2017-11-14 03:33:23 +01: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)
|
|
|
|
}
|
|
|
|
keyboardShortcutsWindowController!.showWindow(self)
|
|
|
|
}
|
2017-11-16 07:33:35 +01:00
|
|
|
|
|
|
|
@IBAction func toggleInspectorWindow(_ sender: Any?) {
|
|
|
|
|
|
|
|
if inspectorWindowController == nil {
|
|
|
|
inspectorWindowController = InspectorWindowController()
|
|
|
|
}
|
|
|
|
|
|
|
|
if inspectorWindowController!.isOpen {
|
|
|
|
inspectorWindowController!.window!.performClose(self)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
inspectorWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-16 07:40:49 +01:00
|
|
|
@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 {
|
2017-10-05 22:28:39 +02:00
|
|
|
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 {
|
2017-09-24 21:24:44 +02:00
|
|
|
let opmlString = AccountManager.shared.localAccount.OPMLString(indentLevel: 0)
|
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) {
|
|
|
|
|
2017-10-06 03:12:58 +02:00
|
|
|
Browser.open("//ranchero.com/evergreen/", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openRepository(_ sender: AnyObject) {
|
|
|
|
|
2017-10-06 03:12:58 +02:00
|
|
|
Browser.open("https://github.com/brentsimmons/Evergreen", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openBugTracker(_ sender: AnyObject) {
|
|
|
|
|
2017-10-06 03:12:58 +02:00
|
|
|
Browser.open("https://github.com/brentsimmons/Evergreen/issues", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showHelp(_ sender: AnyObject) {
|
|
|
|
|
2017-10-06 03:12:58 +02:00
|
|
|
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
|
|
|
|
|
|
|
@IBAction func hitPanicButton(_ sender: Any?) {
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
hitPanicButton(with: mainWindowController!.window!)
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-14 22:18:25 +01:00
|
|
|
private extension AppDelegate {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-11-14 22:18:25 +01:00
|
|
|
func createReaderWindow() -> NSWindowController {
|
|
|
|
|
|
|
|
return windowControllerWithName("MainWindow")
|
|
|
|
}
|
|
|
|
}
|