2017-05-22 22:00:45 +02:00
//
// A p p D e l e g a t e . s w i f t
// E v e r g r e e n
//
2017-05-27 19:43:27 +02:00
// C r e a t e d b y B r e n t S i m m o n s o n 7 / 1 1 / 1 5 .
// C o p y r i g h t © 2 0 1 5 R a n c h e r o S o f t w a r e , L L C . A l l r i g h t s r e s e r v e d .
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-11-15 06:31:17 +01: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
2017-11-25 06:39:59 +01: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 !
2017-11-25 06:39:59 +01:00
var appName : String !
2017-11-19 21:46:29 +01:00
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-11-19 22:57:42 +01:00
postUnreadCountDidChangeNotification ( )
2017-10-06 03:12:58 +02:00
}
2017-05-27 19:43:27 +02:00
}
}
2017-11-25 06:39:59 +01: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 var panicButtonWindowController : PanicButtonWindowController ?
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 ( )
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: - A P I
2017-11-15 06:31:17 +01:00
func logMessage ( _ message : String , type : LogItem . ItemType ) {
2017-11-26 01:15:17 +01:00
#if DEBUG
print ( " logMessage: \( message ) - \( type ) " )
#endif
2017-11-15 06:31:17 +01:00
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-12-25 19:40:06 +01:00
// f u n c m a r k O l d e r A r t i c l e s A s R e a d ( w i t h w i n d o w : N S W i n d o w ) {
//
// p a n i c B u t t o n W i n d o w C o n t r o l l e r = P a n i c B u t t o n W i n d o w C o n t r o l l e r ( )
// p a n i c B u t t o n W i n d o w C o n t r o l l e r ! . r u n S h e e t O n W i n d o w ( w i n d o w )
// }
2017-11-17 03:23:07 +01:00
2017-11-20 01:28:26 +01:00
func markEverywhereAsRead ( with window : NSWindow ) {
let alert = NSAlert ( )
alert . messageText = NSLocalizedString ( " Mark All Articles as Read Everywhere? " , comment : " Mark Everywhere alert messageText " )
alert . informativeText = NSLocalizedString ( " This will mark every single article as read. All of them. The unread count will be zero. \n \n Note: this operation cannot be undone. " , comment : " Mark Everywhere informativeText. " )
alert . addButton ( withTitle : NSLocalizedString ( " Mark All Articles as Read " , comment : " Mark Everywhere alert button. " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Don’ t Mark as Read " , comment : " Mark Everywhere alert button. " ) )
alert . beginSheetModal ( for : window ) { ( returnCode ) in
if returnCode = = . alertFirstButtonReturn {
self . markEverywhereAsRead ( )
}
}
}
func markEverywhereAsRead ( ) {
AccountManager . shared . accounts . forEach { $0 . markEverywhereAsRead ( ) }
}
2017-09-19 07:00:35 +02:00
// MARK: - N S A p p l i c a t i o n D e l e g a t e
2017-05-27 19:43:27 +02:00
func applicationDidFinishLaunching ( _ note : Notification ) {
2017-11-25 06:39:59 +01:00
appName = Bundle . main . infoDictionary ! [ " CFBundleExecutable " ] ! as ! String
2017-09-23 21:17:14 +02:00
let isFirstRun = AppDefaults . shared . isFirstRun
2017-11-26 01:10:19 +01:00
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
2017-11-19 21:46:29 +01:00
2017-11-25 20:14:06 +01:00
let tempDirectory = NSTemporaryDirectory ( )
2017-11-25 22:47:26 +01:00
let cacheFolder = ( tempDirectory as NSString ) . appendingPathComponent ( " com.ranchero.evergreen " )
2017-11-26 05:12:53 +01:00
2017-11-25 22:47:26 +01:00
let faviconsFolder = ( cacheFolder as NSString ) . appendingPathComponent ( " Favicons " )
2017-11-25 20:14:06 +01:00
let faviconsFolderURL = URL ( fileURLWithPath : faviconsFolder )
try ! FileManager . default . createDirectory ( at : faviconsFolderURL , withIntermediateDirectories : true , attributes : nil )
2017-11-25 06:39:59 +01:00
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-12-16 20:16:32 +01:00
2017-05-27 19:43:27 +02:00
createAndShowMainWindow ( )
NSAppleEventManager . shared ( ) . setEventHandler ( self , andSelector : #selector ( AppDelegate . getURL ( _ : _ : ) ) , forEventClass : AEEventClass ( kInternetEventClass ) , andEventID : AEEventID ( kAEGetURL ) )
2017-10-19 03:37:45 +02:00
2017-11-26 22:16:32 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( feedSettingDidChange ( _ : ) ) , name : . FeedSettingDidChange , object : nil )
2017-10-19 03:37:45 +02:00
DispatchQueue . main . async {
self . unreadCount = AccountManager . shared . unreadCount
}
2017-12-25 19:23:12 +01:00
#if RELEASE
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 ( )
}
// MARK: G e t U R L A p p l e E v e n t
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: N o t i f i c a t i o n s
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
}
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 )
}
2017-05-27 19:43:27 +02:00
// MARK: M a i n W i n d o w
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: N S U s e r I n t e r f a c e V a l i d a t i o n s
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: A d d F e e d
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: - A c t i o n s
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-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 )
2017-12-21 02:23:46 +01:00
2017-12-22 20:13:20 +01:00
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 )
2017-12-21 02:23:46 +01:00
}
2017-11-14 03:33:23 +01:00
}
2017-12-21 02:23:46 +01:00
2017-11-14 03:33:23 +01:00
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-12-22 19:28:26 +01:00
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 ) {
2017-12-21 06:23:48 +01:00
Browser . open ( " https://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
}
2017-12-21 06:23:48 +01: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 ) {
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
2017-12-25 19:40:06 +01:00
// @ I B A c t i o n f u n c m a r k O l d e r A r t i c l e s A s R e a d ( _ s e n d e r : A n y ? ) {
//
// c r e a t e A n d S h o w M a i n W i n d o w ( )
// m a r k O l d e r A r t i c l e s A s R e a d ( w i t h : m a i n W i n d o w C o n t r o l l e r ! . w i n d o w ! )
// }
2017-11-20 01:28:26 +01:00
@IBAction func markEverywhereAsRead ( _ sender : Any ? ) {
createAndShowMainWindow ( )
markEverywhereAsRead ( with : mainWindowController ! . window ! )
}
2017-11-20 07:39:13 +01:00
@IBAction func debugDropConditionalGetInfo ( _ sender : Any ? ) {
#if DEBUG
2017-11-25 20:14:06 +01:00
AccountManager . shared . accounts . forEach { $0 . debugDropConditionalGetInfo ( ) }
2017-11-20 07:39:13 +01:00
#endif
}
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 " )
}
}