From c4110cd160310efda71ce6eb2760ad78b4df9c73 Mon Sep 17 00:00:00 2001 From: relikd Date: Wed, 15 Aug 2018 19:41:52 +0200 Subject: [PATCH] Displaying items in the bar + FeedConfig type enum --- baRSS.xcodeproj/project.pbxproj | 26 ++- baRSS/AppDelegate.h | 1 - baRSS/AppDelegate.m | 27 +-- baRSS/{NewsController.h => BarMenu.h} | 4 +- baRSS/BarMenu.m | 196 ++++++++++++++++++ baRSS/Base.lproj/Main.xib | 42 +--- baRSS/FeedDownload.h | 27 +++ baRSS/{NewsController.m => FeedDownload.m} | 21 +- baRSS/Preferences/FeedConfig+Ext.h | 36 ++++ .../{FeedConfig+Print.h => FeedConfig+Ext.m} | 32 ++- baRSS/Preferences/Feeds Tab/ModalFeedEdit.m | 5 +- baRSS/Preferences/Feeds Tab/SettingsFeeds.m | 23 +- baRSS/Preferences/Preferences.m | 3 +- baRSS/StoreCoordinator.h | 5 +- baRSS/StoreCoordinator.m | 16 +- 15 files changed, 342 insertions(+), 122 deletions(-) rename baRSS/{NewsController.h => BarMenu.h} (88%) create mode 100644 baRSS/BarMenu.m create mode 100644 baRSS/FeedDownload.h rename baRSS/{NewsController.m => FeedDownload.m} (78%) create mode 100644 baRSS/Preferences/FeedConfig+Ext.h rename baRSS/Preferences/{FeedConfig+Print.h => FeedConfig+Ext.m} (72%) diff --git a/baRSS.xcodeproj/project.pbxproj b/baRSS.xcodeproj/project.pbxproj index 39a26ab..110d4a2 100644 --- a/baRSS.xcodeproj/project.pbxproj +++ b/baRSS.xcodeproj/project.pbxproj @@ -19,16 +19,18 @@ 546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */ = {isa = PBXBuildFile; fileRef = 546FC44121189975007CC3A3 /* SettingsGeneral.m */; }; 546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC44221189975007CC3A3 /* SettingsGeneral.xib */; }; 546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC4462118A8E6007CC3A3 /* Preferences.xib */; }; + 5477D34E21233C62002BA27F /* FeedConfig+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */; }; 54ACC28121061B3B0020715F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28021061B3B0020715F /* AppDelegate.m */; }; 54ACC28621061B3C0020715F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54ACC28521061B3C0020715F /* Assets.xcassets */; }; 54ACC28921061B3C0020715F /* Main.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54ACC28721061B3C0020715F /* Main.xib */; }; 54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; }; - 54ACC29521061E270020715F /* NewsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* NewsController.m */; }; + 54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; }; 54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.m */; }; 54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; }; 54E88321211B509D00064188 /* ModalFeedEdit.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54E8831F211B509D00064188 /* ModalFeedEdit.xib */; }; 54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28221061B3B0020715F /* DBv1.xcdatamodeld */; }; 54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */; }; + 54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FE73D2212316CD003EAC65 /* BarMenu.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -50,6 +52,8 @@ 546FC44121189975007CC3A3 /* SettingsGeneral.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneral.m; sourceTree = ""; }; 546FC44221189975007CC3A3 /* SettingsGeneral.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsGeneral.xib; sourceTree = ""; }; 546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = ""; }; + 5477D34C21233C62002BA27F /* FeedConfig+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Ext.h"; sourceTree = ""; }; + 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedConfig+Ext.m"; sourceTree = ""; }; 54ACC27C21061B3B0020715F /* baRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = baRSS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54ACC27F21061B3B0020715F /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 54ACC28021061B3B0020715F /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -58,16 +62,17 @@ 54ACC28821061B3C0020715F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Main.xib; sourceTree = ""; }; 54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 54ACC29321061E270020715F /* NewsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewsController.h; sourceTree = ""; }; - 54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = ""; }; + 54ACC29321061E270020715F /* FeedDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedDownload.h; sourceTree = ""; }; + 54ACC29421061E270020715F /* FeedDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedDownload.m; sourceTree = ""; }; 54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = ""; }; 54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = ""; }; 54E8831D211B509D00064188 /* ModalFeedEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalFeedEdit.h; sourceTree = ""; }; 54E8831E211B509D00064188 /* ModalFeedEdit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEdit.m; sourceTree = ""; }; 54E8831F211B509D00064188 /* ModalFeedEdit.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ModalFeedEdit.xib; sourceTree = ""; }; - 54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Print.h"; sourceTree = ""; }; 54FE73CE21220DEC003EAC65 /* StoreCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoreCoordinator.h; sourceTree = ""; }; 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoreCoordinator.m; sourceTree = ""; }; + 54FE73D1212316CD003EAC65 /* BarMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BarMenu.h; sourceTree = ""; }; + 54FE73D2212316CD003EAC65 /* BarMenu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BarMenu.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -103,7 +108,8 @@ 546FC44D2118B357007CC3A3 /* Preferences */ = { isa = PBXGroup; children = ( - 54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */, + 5477D34C21233C62002BA27F /* FeedConfig+Ext.h */, + 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */, 54ACC29621061FBA0020715F /* Preferences.h */, 54ACC29721061FBA0020715F /* Preferences.m */, 546FC4462118A8E6007CC3A3 /* Preferences.xib */, @@ -151,10 +157,12 @@ 544B011C2114EE9100386E5C /* AppHook.m */, 54ACC27F21061B3B0020715F /* AppDelegate.h */, 54ACC28021061B3B0020715F /* AppDelegate.m */, + 54FE73D1212316CD003EAC65 /* BarMenu.h */, + 54FE73D2212316CD003EAC65 /* BarMenu.m */, 54FE73CE21220DEC003EAC65 /* StoreCoordinator.h */, 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */, - 54ACC29321061E270020715F /* NewsController.h */, - 54ACC29421061E270020715F /* NewsController.m */, + 54ACC29321061E270020715F /* FeedDownload.h */, + 54ACC29421061E270020715F /* FeedDownload.m */, 54209E922117325100F3B5EF /* DrawImage.h */, 54209E932117325100F3B5EF /* DrawImage.m */, 546FC44D2118B357007CC3A3 /* Preferences */, @@ -267,9 +275,11 @@ 54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */, 544B011D2114EE9100386E5C /* AppHook.m in Sources */, 546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */, - 54ACC29521061E270020715F /* NewsController.m in Sources */, + 54ACC29521061E270020715F /* FeedDownload.m in Sources */, + 5477D34E21233C62002BA27F /* FeedConfig+Ext.m in Sources */, 54ACC28C21061B3C0020715F /* main.m in Sources */, 54ACC28121061B3B0020715F /* AppDelegate.m in Sources */, + 54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */, 544B011A2114B41200386E5C /* ModalSheet.m in Sources */, 54ACC29821061FBA0020715F /* Preferences.m in Sources */, 546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */, diff --git a/baRSS/AppDelegate.h b/baRSS/AppDelegate.h index 7c97dc3..c11cc79 100644 --- a/baRSS/AppDelegate.h +++ b/baRSS/AppDelegate.h @@ -26,6 +26,5 @@ @interface AppDelegate : NSObject @property (readonly, strong) NSPersistentContainer *persistentContainer; - (IBAction)saveAction:(id)sender; -- (void)preferencesClosed; @end diff --git a/baRSS/AppDelegate.m b/baRSS/AppDelegate.m index 2d6bacc..6c7061b 100644 --- a/baRSS/AppDelegate.m +++ b/baRSS/AppDelegate.m @@ -22,45 +22,24 @@ #import "AppDelegate.h" #import "PyHandler.h" -#import "DrawImage.h" -#import "Preferences.h" -#import "StoreCoordinator.h" +#import "BarMenu.h" @interface AppDelegate () -@property (weak) IBOutlet NSMenu *statusMenu; -@property (strong) NSStatusItem *statusItem; -@property (strong) Preferences *prefWindow; +@property (strong) BarMenu *barMenu; @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - self.statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; - self.statusItem.title = @"me"; - self.statusItem.menu = self.statusMenu; - self.statusItem.highlightMode = YES; - self.statusItem.image = [[RSSIcon templateIcon:16 tint:nil] image]; - self.statusItem.image.template = YES; + self.barMenu = [BarMenu new]; [PyHandler prepare]; printf("up and running\n"); -// [StoreCoordinator deleteUnreferencedFeeds]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { [PyHandler shutdown]; } -- (IBAction)openPreferences:(id)sender { - if (!self.prefWindow) - self.prefWindow = [[Preferences alloc] initWithWindowNibName:@"Preferences"]; - [NSApp activateIgnoringOtherApps:YES]; - [self.prefWindow showWindow:nil]; -} - -- (void)preferencesClosed { - self.prefWindow = nil; -} - #pragma mark - Core Data stack @synthesize persistentContainer = _persistentContainer; diff --git a/baRSS/NewsController.h b/baRSS/BarMenu.h similarity index 88% rename from baRSS/NewsController.h rename to baRSS/BarMenu.h index ce315a6..e48e519 100644 --- a/baRSS/NewsController.h +++ b/baRSS/BarMenu.h @@ -22,6 +22,6 @@ #import -@interface NewsController : NSObject -+ (void)downloadFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block; +@interface BarMenu : NSObject +//+ (instancetype)start; @end diff --git a/baRSS/BarMenu.m b/baRSS/BarMenu.m new file mode 100644 index 0000000..915f8d3 --- /dev/null +++ b/baRSS/BarMenu.m @@ -0,0 +1,196 @@ +// +// The MIT License (MIT) +// Copyright (c) 2018 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import "BarMenu.h" +#import "StoreCoordinator.h" +#import "DrawImage.h" +#import "Preferences.h" + +@interface BarMenu() +@property (strong) NSStatusItem *barItem; +@property (strong) Preferences *prefWindow; +@property (weak) NSMenu *mm; +@end + + +@implementation BarMenu + +- (instancetype)init { + self = [super init]; + self.barItem = [self statusItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferencesClosed) name:@"baRSSPreferencesClosed" object:nil]; +// [self donothing]; + return self; +} + +- (void)donothing { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.mm itemAtIndex:4].title = [NSString stringWithFormat:@"%@", [NSDate date]]; + }); + sleep(1); + [self performSelectorInBackground:@selector(donothing) withObject:nil]; +} + +-(void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (NSStatusItem*)statusItem { + NSStatusItem *item = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; + item.title = @"me"; + item.menu = self.mainMenu; + item.highlightMode = YES; + item.image = [[RSSIcon templateIcon:16 tint:nil] image]; + item.image.template = YES; + return item; +} + +- (void)openPreferences { + if (!self.prefWindow) + self.prefWindow = [[Preferences alloc] initWithWindowNibName:@"Preferences"]; + [NSApp activateIgnoringOtherApps:YES]; + [self.prefWindow showWindow:nil]; +} + +- (void)preferencesClosed { + self.prefWindow = nil; +} + +#pragma mark - Main Menu Item Actions + +- (void)pauseUpdates { + NSLog(@"1pause"); +} + +- (void)updateAllFeeds { + NSLog(@"1update all"); +} + +- (void)openAllUnread { + NSLog(@"1all unread"); +} + +- (void)openFeedURL:(NSMenuItem*)sender { + id obj = [StoreCoordinator objectWithID:sender.representedObject]; + NSString *url = nil; + if ([obj isKindOfClass:[FeedItem class]]) { + url = [(FeedItem*)obj link]; + } else if ([obj isKindOfClass:[FeedConfig class]]) { + url = [[(FeedConfig*)obj feed] link]; + } + if (!url || url.length == 0) return; + [[NSWorkspace sharedWorkspace] openURLs:@[[NSURL URLWithString:url]] withAppBundleIdentifier:@"com.apple.Safari" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifiers:nil]; +} + +#pragma mark - Menu Generator + +- (NSMenu*)mainMenu { + NSMenu *menu = [NSMenu new]; + menu.autoenablesItems = NO; +// self.mm = menu; + [self addTitle:@"Pause Updates" selector:@selector(pauseUpdates) key:@"" toMenu:menu]; + [self addTitle:@"Update all feeds" selector:@selector(updateAllFeeds) key:@"" toMenu:menu]; + [self addTitle:@"Open all unread" selector:@selector(openAllUnread) key:@"" toMenu:menu]; + [menu addItem:[NSMenuItem separatorItem]]; + + NSArray *items = [StoreCoordinator sortedFeedConfigItems]; + for (FeedConfig *fc in items) { + [menu addItem:[self menuItemForFeedConfig:fc]]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + [self addTitle:@"Preferences" selector:@selector(openPreferences) key:@"," toMenu:menu]; + [menu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; + return menu; +} + +- (void)addTitle:(NSString*)title selector:(SEL)selector key:(NSString*)key toMenu:(NSMenu*)menu { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key]; + item.target = self; + [menu addItem:item]; +} + +- (NSMenuItem*)menuItemForFeedConfig:(FeedConfig*)fc { + NSMenuItem *item; + if (fc.typ == SEPARATOR) { + item = [NSMenuItem separatorItem]; + } else { + item = [[NSMenuItem alloc] initWithTitle:fc.name action:nil keyEquivalent:@""]; + if (fc.typ == FEED) { + item.submenu = [self menuForFeed:fc.feed]; + item.action = @selector(openFeedURL:); + item.target = self; + item.toolTip = fc.feed.subtitle; + item.enabled = (fc.feed.link.length > 0); + static NSImage *defaultRSSIcon; + if (!defaultRSSIcon) + defaultRSSIcon = [[[RSSIcon iconWithSize:NSMakeSize(16, 16)] autoGradient] image]; + item.image = defaultRSSIcon; + } else { + item.submenu = [self menuForFeedConfig:fc]; + item.image = [NSImage imageNamed:NSImageNameFolder]; + item.image.size = NSMakeSize(16, 16); + } + } + item.representedObject = fc.objectID; + return item; +} + +- (NSMenu*)menuForFeedConfig:(FeedConfig*)parent { + NSMenu *menu = [NSMenu new]; + menu.autoenablesItems = NO; + // TODO: open unread for groups ... + for (FeedConfig *fc in parent.sortedChildren) { + [menu addItem:[self menuItemForFeedConfig:fc]]; + } + return menu; +} + +- (NSMenu*)menuForFeed:(Feed*)feed { + NSMenu *menu = [NSMenu new]; + menu.autoenablesItems = NO; + // TODO: open unread for feed only ... + for (FeedItem *entry in feed.items) { + [menu addItem:[self menuItemForFeedItem:entry]]; + } + return menu; +} + +- (NSMenuItem*)menuItemForFeedItem:(FeedItem*)item { + NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""]; + mi.target = self; + mi.representedObject = item.objectID; + mi.toolTip = item.subtitle; + mi.enabled = (item.link.length > 0); + return mi; +} + +//- (NSIndexPath*)indexPathForMenu:(NSMenu*)menu { +// NSMenu *parent = menu.supermenu; +// if (parent == nil) { +// return [NSIndexPath new]; +// } else { +// return [[self indexPathForMenu:parent] indexPathByAddingIndex:(NSUInteger)[parent indexOfItemWithSubmenu:menu]]; +// } +//} + +@end diff --git a/baRSS/Base.lproj/Main.xib b/baRSS/Base.lproj/Main.xib index 6c548be..8e44460 100644 --- a/baRSS/Base.lproj/Main.xib +++ b/baRSS/Base.lproj/Main.xib @@ -12,46 +12,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/baRSS/FeedDownload.h b/baRSS/FeedDownload.h new file mode 100644 index 0000000..794dbf4 --- /dev/null +++ b/baRSS/FeedDownload.h @@ -0,0 +1,27 @@ +// +// The MIT License (MIT) +// Copyright (c) 2018 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import + +@interface FeedDownload : NSObject ++ (void)getFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block; +@end diff --git a/baRSS/NewsController.m b/baRSS/FeedDownload.m similarity index 78% rename from baRSS/NewsController.m rename to baRSS/FeedDownload.m index 1ca879c..238d524 100644 --- a/baRSS/NewsController.m +++ b/baRSS/FeedDownload.m @@ -20,27 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#import "NewsController.h" +#import "FeedDownload.h" #import "PyHandler.h" -@interface NewsController () -@end +@implementation FeedDownload -@implementation NewsController - -- (IBAction)pauseUpdates:(NSMenuItem *)sender { - NSLog(@"pause"); -} - -- (IBAction)updateAllFeeds:(NSMenuItem *)sender { - NSLog(@"update all"); -} - -- (IBAction)openAllUnread:(NSMenuItem *)sender { - NSLog(@"all unread"); -} - -+ (void)downloadFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block { ++ (void)getFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block { [NSThread detachNewThreadWithBlock:^{ NSDictionary *dict = [PyHandler getFeed:url withEtag:nil andModified:nil]; NSError *err = nil; diff --git a/baRSS/Preferences/FeedConfig+Ext.h b/baRSS/Preferences/FeedConfig+Ext.h new file mode 100644 index 0000000..1f422ef --- /dev/null +++ b/baRSS/Preferences/FeedConfig+Ext.h @@ -0,0 +1,36 @@ +// +// The MIT License (MIT) +// Copyright (c) 2018 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import "FeedConfig+CoreDataClass.h" + +@interface FeedConfig (Ext) +typedef enum int16_t { + GROUP = 0, + FEED = 1, + SEPARATOR = 2 +} FeedConfigType; +@property (getter=typ, setter=setTyp:) FeedConfigType typ; +@property (readonly) NSArray *sortedChildren; + +- (NSString*)readableRefreshString; +- (NSString*)readableDescription; +@end diff --git a/baRSS/Preferences/FeedConfig+Print.h b/baRSS/Preferences/FeedConfig+Ext.m similarity index 72% rename from baRSS/Preferences/FeedConfig+Print.h rename to baRSS/Preferences/FeedConfig+Ext.m index 98ca3dd..cc49d8f 100644 --- a/baRSS/Preferences/FeedConfig+Print.h +++ b/baRSS/Preferences/FeedConfig+Ext.m @@ -20,21 +20,35 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#ifndef FeedConfig_Print_h -#define FeedConfig_Print_h +#import "FeedConfig+Ext.h" + +@implementation FeedConfig (Ext) + +- (FeedConfigType)typ { + return (FeedConfigType)self.type; +} + +- (void)setTyp:(FeedConfigType)typ { + self.type = typ; +} + +- (NSArray *)sortedChildren { + if (self.children.count == 0) + return nil; + return [self.children sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]]; +} -@implementation FeedConfig (Print) - (NSString*)readableRefreshString { return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]]; } + - (NSString*)readableDescription { - switch (self.type) { - case 0: return [NSString stringWithFormat:@"%@", self.name]; // Group - case 2: return @"-------------"; // Separator - default: + switch (self.typ) { + case SEPARATOR: return @"-------------"; + case GROUP: return [NSString stringWithFormat:@"%@", self.name]; + case FEED: return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.url, [self readableRefreshString]]; } } -@end -#endif /* FeedConfig_Print_h */ +@end diff --git a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m index 17fdfde..235964c 100644 --- a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m +++ b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m @@ -21,9 +21,8 @@ // SOFTWARE. #import "ModalFeedEdit.h" -#import "NewsController.h" +#import "FeedDownload.h" #import "StoreCoordinator.h" -#import "FeedConfig+CoreDataProperties.h" @interface ModalFeedEdit() @property (weak) IBOutlet NSTextField *url; @@ -140,7 +139,7 @@ self.feedError = nil; [self.spinnerURL startAnimation:nil]; [self.spinnerName startAnimation:nil]; - [NewsController downloadFeed:self.previousURL withBlock:^(NSDictionary *result, NSError *error) { + [FeedDownload getFeed:self.previousURL withBlock:^(NSDictionary *result, NSError *error) { self.feedResult = result; self.feedError = error; // warning indicator .hidden is bound to feedError // TODO: play error sound? diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m index 887b387..aa485c0 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m @@ -22,8 +22,7 @@ #import "SettingsFeeds.h" #import "AppDelegate.h" -#import "DBv1+CoreDataModel.h" -#import "FeedConfig+Print.h" +#import "FeedConfig+Ext.h" #import "ModalSheet.h" #import "ModalFeedEdit.h" #import "DrawImage.h" @@ -62,7 +61,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; [self.undoManager beginUndoGrouping]; FeedConfig *sp = [self insertSortedItemAtSelection]; sp.name = @"---"; - sp.type = 2; + sp.typ = SEPARATOR; [self.undoManager endUndoGrouping]; } @@ -93,8 +92,8 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; - (void)showModalForFeedConfig:(FeedConfig*)obj isGroupEdit:(BOOL)group { BOOL existingItem = [obj isKindOfClass:[FeedConfig class]]; if (existingItem) { - if (obj.type == 2) return; // Separator - group = (obj.type == 0); + if (obj.typ == SEPARATOR) return; + group = (obj.typ == GROUP); } self.modalController = (group ? [ModalGroupEdit new] : [ModalFeedEdit new]); self.modalController.representedObject = obj; @@ -104,7 +103,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; [self.undoManager beginUndoGrouping]; if (!existingItem) { // create new item FeedConfig *item = [self insertSortedItemAtSelection]; - item.type = (group ? 0 : 1); + item.typ = (group ? GROUP : FEED); self.modalController.representedObject = item; } [self.modalController updateRepresentedObject]; @@ -120,7 +119,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; FeedConfig *selected = [[[self.dataStore arrangedObjects] descendantNodeAtIndexPath:selectedIndex] representedObject]; NSUInteger lastIndex = selected.children.count; - BOOL groupSelected = (selected.type == 0); + BOOL groupSelected = (selected.typ == GROUP); if (!groupSelected) { lastIndex = (NSUInteger)selected.sortIndex + 1; // insert after selection @@ -217,7 +216,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id )info proposedItem:(id)item proposedChildIndex:(NSInteger)index { FeedConfig *fc = [(NSTreeNode*)item representedObject]; - if (index == -1 && fc.type != 0) { // if drag is on specific item and that item isnt a group + if (index == -1 && fc.typ != GROUP) { // if drag is on specific item and that item isnt a group return NSDragOperationNone; } @@ -238,8 +237,8 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item { FeedConfig *f = [(NSTreeNode*)item representedObject]; - BOOL isFeed = (f.type == 1); - BOOL isSeperator = (f.type == 2); + BOOL isFeed = (f.typ == FEED); + BOOL isSeperator = (f.typ == SEPARATOR); BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"]; NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed")); @@ -252,7 +251,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; return cellView; // the refresh cell is already skipped with the above if condition } else { cellView.textField.objectValue = f.name; - if (f.type == 0) { + if (f.typ == GROUP) { cellView.imageView.image = [NSImage imageNamed:NSImageNameFolder]; } else { // TODO: load icon @@ -283,7 +282,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; if (aSelector == @selector(copy:)) return YES; // can edit only if selection is not a separator - return (((FeedConfig*)self.dataStore.selectedNodes.firstObject.representedObject).type != 2); + return (((FeedConfig*)self.dataStore.selectedNodes.firstObject.representedObject).typ != SEPARATOR); } return [super respondsToSelector:aSelector]; } diff --git a/baRSS/Preferences/Preferences.m b/baRSS/Preferences/Preferences.m index 5e714be..cb0645f 100644 --- a/baRSS/Preferences/Preferences.m +++ b/baRSS/Preferences/Preferences.m @@ -23,7 +23,6 @@ #import "Preferences.h" #import "SettingsFeeds.h" #import "SettingsGeneral.h" -#import "AppDelegate.h" @interface Preferences () @@ -58,7 +57,7 @@ } - (void)windowWillClose:(NSNotification *)notification { - [(AppDelegate*)[NSApp delegate] preferencesClosed]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"baRSSPreferencesClosed" object:nil]; } @end diff --git a/baRSS/StoreCoordinator.h b/baRSS/StoreCoordinator.h index 8dbbbfd..ee78ec1 100644 --- a/baRSS/StoreCoordinator.h +++ b/baRSS/StoreCoordinator.h @@ -21,11 +21,14 @@ // SOFTWARE. #import +#import "DBv1+CoreDataModel.h" +#import "FeedConfig+Ext.h" -@class Feed; @interface StoreCoordinator : NSObject + (void)save; + (void)deleteUnreferencedFeeds; ++ (NSArray*)sortedFeedConfigItems; ++ (id)objectWithID:(NSManagedObjectID*)objID; + (Feed*)createFeedFromDictionary:(NSDictionary*)obj; @end diff --git a/baRSS/StoreCoordinator.m b/baRSS/StoreCoordinator.m index d6946fe..8b54696 100644 --- a/baRSS/StoreCoordinator.m +++ b/baRSS/StoreCoordinator.m @@ -22,7 +22,6 @@ #import "StoreCoordinator.h" #import "AppDelegate.h" -#import "DBv1+CoreDataModel.h" @implementation StoreCoordinator @@ -44,6 +43,21 @@ if (err) NSLog(@"%@", err); } ++ (NSArray*)sortedFeedConfigItems { + NSManagedObjectContext *moc = [self getContext]; + NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName: FeedConfig.entity.name]; + fr.predicate = [NSPredicate predicateWithFormat:@"parent = NULL"]; // %@", parent + fr.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]; + NSError *err; + NSArray *result = [moc executeFetchRequest:fr error:&err]; + if (err) NSLog(@"%@", err); + return result; +} + ++ (id)objectWithID:(NSManagedObjectID*)objID { + return [[self getContext] objectWithID:objID]; +} + + (Feed*)createFeedFromDictionary:(NSDictionary*)obj { NSManagedObjectContext *moc = [self getContext]; Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:moc];