diff --git a/baRSS.xcodeproj/project.pbxproj b/baRSS.xcodeproj/project.pbxproj index 2358d45..95aaf52 100644 --- a/baRSS.xcodeproj/project.pbxproj +++ b/baRSS.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1968EF7567E06D2A5BB3481A /* PyHandler.m */; }; + 544B011A2114B41200386E5C /* FeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* FeedEdit.m */; }; + 544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; }; 544FBD4521064AEB008A260C /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544FBD4421064AEB008A260C /* Python.framework */; }; 544FBD4721064B2F008A260C /* getFeed.py in Resources */ = {isa = PBXBuildFile; fileRef = 544FBD4621064B2F008A260C /* getFeed.py */; }; 544FBD4921064DF0008A260C /* feedparser521.py in Resources */ = {isa = PBXBuildFile; fileRef = 544FBD4821064DF0008A260C /* feedparser521.py */; }; @@ -23,6 +25,10 @@ /* Begin PBXFileReference section */ 1968E7919BAA36F042FCB717 /* PyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyHandler.h; sourceTree = ""; }; 1968EF7567E06D2A5BB3481A /* PyHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PyHandler.m; sourceTree = ""; }; + 544B01182114B41200386E5C /* FeedEdit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedEdit.h; sourceTree = ""; }; + 544B01192114B41200386E5C /* FeedEdit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FeedEdit.m; sourceTree = ""; }; + 544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = ""; }; + 544B011C2114EE9100386E5C /* AppHook.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppHook.m; sourceTree = ""; }; 544FBD4421064AEB008A260C /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = System/Library/Frameworks/Python.framework; sourceTree = SDKROOT; }; 544FBD4621064B2F008A260C /* getFeed.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = getFeed.py; sourceTree = ""; usesTabs = 0; }; 544FBD4821064DF0008A260C /* feedparser521.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = feedparser521.py; sourceTree = ""; usesTabs = 0; }; @@ -92,12 +98,16 @@ isa = PBXGroup; children = ( 549369F421091E6D001AF895 /* python */, + 544B011B2114EE9100386E5C /* AppHook.h */, + 544B011C2114EE9100386E5C /* AppHook.m */, 54ACC27F21061B3B0020715F /* AppDelegate.h */, 54ACC28021061B3B0020715F /* AppDelegate.m */, 54ACC29321061E270020715F /* NewsController.h */, 54ACC29421061E270020715F /* NewsController.m */, 54ACC29621061FBA0020715F /* Preferences.h */, 54ACC29721061FBA0020715F /* Preferences.m */, + 544B01182114B41200386E5C /* FeedEdit.h */, + 544B01192114B41200386E5C /* FeedEdit.m */, 54ACC28521061B3C0020715F /* Assets.xcassets */, 54ACC28721061B3C0020715F /* Main.xib */, 54ACC28A21061B3C0020715F /* Info.plist */, @@ -188,9 +198,11 @@ buildActionMask = 2147483647; files = ( 54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */, + 544B011D2114EE9100386E5C /* AppHook.m in Sources */, 54ACC29521061E270020715F /* NewsController.m in Sources */, 54ACC28C21061B3C0020715F /* main.m in Sources */, 54ACC28121061B3B0020715F /* AppDelegate.m in Sources */, + 544B011A2114B41200386E5C /* FeedEdit.m in Sources */, 54ACC29821061FBA0020715F /* Preferences.m in Sources */, 1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */, ); diff --git a/baRSS/AppHook.h b/baRSS/AppHook.h new file mode 100644 index 0000000..4a6bdeb --- /dev/null +++ b/baRSS/AppHook.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 AppHook : NSApplication + +@end diff --git a/baRSS/AppHook.m b/baRSS/AppHook.m new file mode 100644 index 0000000..d31b1bc --- /dev/null +++ b/baRSS/AppHook.m @@ -0,0 +1,57 @@ +// +// 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 "AppHook.h" + +static NSEventModifierFlags fnKeyFlags = NSEventModifierFlagShift | NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagFunction; + +@implementation AppHook +- (void) sendEvent:(NSEvent *)event { + if ([event type] == NSEventTypeKeyDown) { + NSEventModifierFlags flags = (event.modifierFlags & fnKeyFlags); // ignore caps lock, etc. + unichar key = [event.characters characterAtIndex:0]; // charactersIgnoringModifiers + if (flags == NSEventModifierFlagCommand) { + switch (key) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + case 'z': if ([self sendAction:@selector(undo:) to:nil from:self]) return; break; +#pragma clang diagnostic pop + case 'x': if ([self sendAction:@selector(cut:) to:nil from:self]) return; break; + case 'c': if ([self sendAction:@selector(copy:) to:nil from:self]) return; break; + case 'v': if ([self sendAction:@selector(paste:) to:nil from:self]) return; break; + case 'a': if ([self sendAction:@selector(selectAll:) to:nil from:self]) return; break; + case 'q': if ([self sendAction:@selector(terminate:) to:nil from:self]) return; break; + case 'w': if ([self sendAction:@selector(performClose:) to:nil from:self]) return; break; + } + } else if (flags == (NSEventModifierFlagCommand | NSEventModifierFlagShift)) { + if (key == 'z') { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + if ([self sendAction:@selector(redo:) to:nil from:self]) + return; +#pragma clang diagnostic pop + } + } + } + [super sendEvent:event]; +} +@end diff --git a/baRSS/Base.lproj/Main.xib b/baRSS/Base.lproj/Main.xib index 474cdb7..9db1b58 100644 --- a/baRSS/Base.lproj/Main.xib +++ b/baRSS/Base.lproj/Main.xib @@ -19,22 +19,6 @@ - - - - - - - - - - - - - - - - @@ -70,10 +54,16 @@ - + + + + + + + + - - + @@ -84,12 +74,12 @@ - + - + @@ -98,6 +88,12 @@ + + + + + + @@ -430,6 +426,7 @@ + @@ -459,7 +456,7 @@ - + @@ -514,6 +511,131 @@ CA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/baRSS/FeedEdit.h b/baRSS/FeedEdit.h new file mode 100644 index 0000000..f23e9d7 --- /dev/null +++ b/baRSS/FeedEdit.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 FeedEdit : NSWindow + +@end diff --git a/baRSS/FeedEdit.m b/baRSS/FeedEdit.m new file mode 100644 index 0000000..0b4a900 --- /dev/null +++ b/baRSS/FeedEdit.m @@ -0,0 +1,58 @@ +// +// 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 "FeedEdit.h" + +@implementation FeedEdit + +- (IBAction)didTapDoneButton:(id)sender { + [self.parentWindow endSheet:self returnCode:NSModalResponseOK]; +} + +- (IBAction)didTapCancelButton:(id)sender { + [self.parentWindow endSheet:self returnCode:NSModalResponseAbort]; +} + +@end + + + +@interface StrictUIntFormatter : NSFormatter +@end + +@implementation StrictUIntFormatter +- (NSString *)stringForObjectValue:(id)obj { + return [NSString stringWithFormat:@"%d", [[NSString stringWithFormat:@"%@", obj] intValue]]; +} +- (BOOL)getObjectValue:(out id _Nullable __autoreleasing *)obj forString:(NSString *)string errorDescription:(out NSString *__autoreleasing _Nullable *)error { + *obj = [[NSNumber numberWithInt:[string intValue]] stringValue]; + return YES; +} +- (BOOL)isPartialStringValid:(NSString *__autoreleasing _Nonnull *)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString *__autoreleasing _Nullable *)error { + for (NSUInteger i = 0; i < [*partialStringPtr length]; i++) { + unichar c = [*partialStringPtr characterAtIndex:i]; + if (c < '0' || c > '9') + return NO; + } + return YES; +} +@end diff --git a/baRSS/Info.plist b/baRSS/Info.plist index efaf975..ead7ad7 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -29,6 +29,6 @@ NSMainNibFile Main NSPrincipalClass - NSApplication + AppHook diff --git a/baRSS/NewsController.m b/baRSS/NewsController.m index a91d933..28276b9 100644 --- a/baRSS/NewsController.m +++ b/baRSS/NewsController.m @@ -22,10 +22,11 @@ #import "NewsController.h" #import "PyHandler.h" -#import "AppDelegate.h" +#import "Preferences.h" #import "DBv1+CoreDataModel.h" @interface NewsController () +@property (weak) IBOutlet Preferences *preferencesWindow; @property (weak) IBOutlet NSOutlineView *outlineView; @property (strong) NSArray *currentlyDraggedNodes; @@ -98,8 +99,22 @@ static NSString *dragNodeType = @"baRSS-feed-type"; NSLog(@"all unread"); } +- (IBAction)presentModalFeedProperties:(id)sender { + self.preferencesWindow.feedDetailSheet.parentWindow = self.preferencesWindow; + [self.preferencesWindow beginSheet:self.preferencesWindow.feedDetailSheet completionHandler:^(NSModalResponse returnCode) { + NSLog(@"%ld", (long)returnCode); + }]; +// if ([sender isKindOfClass:[NSOutlineView class]]) { +// NSOutlineView *ov = sender; +// if (ov.clickedRow == -1) +// return; // ignore clicks on column headers and where no row was selected +// +// id vop = [ov itemAtRow:ov.clickedRow]; +// NSLog(@"%@", vop); +// } +} + - (IBAction)addFeed:(NSButton *)sender { - NSLog(@"add feed"); [self.managedObjectContext.undoManager beginUndoGrouping]; FeedConfig *nf = [self insertSortedItemAtSelection]; nf.type = 1; @@ -117,10 +132,9 @@ static NSString *dragNodeType = @"baRSS-feed-type"; } - (IBAction)addSeparator:(NSButton *)sender { - NSLog(@"add separator"); [self.managedObjectContext.undoManager beginUndoGrouping]; FeedConfig *sp = [self insertSortedItemAtSelection]; - sp.name = @"-------------"; + sp.name = @"---"; sp.type = 2; [self.managedObjectContext.undoManager endUndoGrouping]; } diff --git a/baRSS/Preferences.h b/baRSS/Preferences.h index 831cb5e..c11e8d9 100644 --- a/baRSS/Preferences.h +++ b/baRSS/Preferences.h @@ -22,6 +22,7 @@ #import -@interface Preferences : NSWindowController +@interface Preferences : NSWindow +@property (weak) IBOutlet NSWindow *feedDetailSheet; @end diff --git a/baRSS/Preferences.m b/baRSS/Preferences.m index 9bb664a..c1f66b9 100644 --- a/baRSS/Preferences.m +++ b/baRSS/Preferences.m @@ -24,59 +24,45 @@ #import "NewsController.h" @interface Preferences () -@property (weak) IBOutlet NSToolbar *toolbar; @property (weak) IBOutlet NSView *viewGeneral; @property (weak) IBOutlet NSView *viewFeeds; - @property (weak) IBOutlet NewsController *newsController; -@property (weak) IBOutlet NSOutlineView *feedsOutline; @end @implementation Preferences + - (void)awakeFromNib { [super awakeFromNib]; - if (self.window.contentView.subviews.count == 0) { - self.window.contentView = self.viewGeneral; + if (self.contentView.subviews.count == 0) { + self.contentView = self.viewGeneral; self.toolbar.selectedItemIdentifier = self.toolbar.items.firstObject.itemIdentifier; } } -- (IBAction)clickGeneral:(NSToolbarItem *)sender { - self.window.contentView = self.viewGeneral; +- (IBAction)tabGeneralClicked:(NSToolbarItem *)sender { + self.contentView = self.viewGeneral; } -- (IBAction)clickFeeds:(NSToolbarItem *)sender { - self.window.contentView = self.viewFeeds; +- (IBAction)tabFeedsClicked:(NSToolbarItem *)sender { + self.contentView = self.viewFeeds; } -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (void)keyDown:(NSEvent *)event { - if (event.modifierFlags & NSEventModifierFlagCommand) { - bool holdShift = event.modifierFlags & NSEventModifierFlagShift; - unichar key = [event.characters characterAtIndex:0]; - switch (key) { - case 'w': [self close]; break; - case 'q': [NSApplication.sharedApplication terminate:self]; break; - } - if (self.window.contentView == self.viewFeeds) { // these only apply for NSOutlineView - switch (key) { - case 'z': - if (holdShift) [self.newsController.managedObjectContext.undoManager redo]; - else [self.newsController.managedObjectContext.undoManager undo]; - [self.newsController rearrangeObjects]; // update the ordering - break; - case 'o': break; // open .opml file - case 's': break; // save data or backup .opml file - case 'c': // copy row entry - [self.newsController copyDescriptionOfSelectedItems]; - break; - case 'a': [self.feedsOutline selectAll:nil]; break; - } - } +- (void)undo:(id)sender { + if (self.contentView == self.viewFeeds) { + [self.newsController.managedObjectContext.undoManager undo]; + [self.newsController rearrangeObjects]; // update the ordering } } +- (void)redo:(id)sender { + if (self.contentView == self.viewFeeds) { + [self.newsController.managedObjectContext.undoManager redo]; + [self.newsController rearrangeObjects]; // update the ordering + } +} + +- (void)copy:(id)sender { + [self.newsController copyDescriptionOfSelectedItems]; +} + @end