- Moved modal sheet to its own ViewController
- Warning Indicator for non parsable URLs - Popover for error description - Modal Controller handles CoreData update - Bugfixes
This commit is contained in:
@@ -25,6 +25,8 @@
|
|||||||
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
|
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
|
||||||
54ACC29521061E270020715F /* NewsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* NewsController.m */; };
|
54ACC29521061E270020715F /* NewsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* NewsController.m */; };
|
||||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.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 */; };
|
54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28221061B3B0020715F /* DBv1.xcdatamodeld */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -59,6 +61,10 @@
|
|||||||
54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = "<group>"; };
|
54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = "<group>"; };
|
||||||
54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; };
|
54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; };
|
||||||
54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = "<group>"; };
|
54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = "<group>"; };
|
||||||
|
54E8831D211B509D00064188 /* ModalFeedEdit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalFeedEdit.h; sourceTree = "<group>"; };
|
||||||
|
54E8831E211B509D00064188 /* ModalFeedEdit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEdit.m; sourceTree = "<group>"; };
|
||||||
|
54E8831F211B509D00064188 /* ModalFeedEdit.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ModalFeedEdit.xib; sourceTree = "<group>"; };
|
||||||
|
54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Print.h"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -81,28 +87,27 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
546FC44521189ADC007CC3A3 /* Settings Tabs */ = {
|
546FC44521189ADC007CC3A3 /* General Tab */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
546FC44021189975007CC3A3 /* SettingsGeneral.h */,
|
546FC44021189975007CC3A3 /* SettingsGeneral.h */,
|
||||||
546FC44121189975007CC3A3 /* SettingsGeneral.m */,
|
546FC44121189975007CC3A3 /* SettingsGeneral.m */,
|
||||||
546FC44221189975007CC3A3 /* SettingsGeneral.xib */,
|
546FC44221189975007CC3A3 /* SettingsGeneral.xib */,
|
||||||
546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */,
|
|
||||||
546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */,
|
|
||||||
546FC43E21188C78007CC3A3 /* SettingsFeeds.xib */,
|
|
||||||
);
|
);
|
||||||
path = "Settings Tabs";
|
path = "General Tab";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
546FC44D2118B357007CC3A3 /* Preferences */ = {
|
546FC44D2118B357007CC3A3 /* Preferences */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */,
|
||||||
54ACC29621061FBA0020715F /* Preferences.h */,
|
54ACC29621061FBA0020715F /* Preferences.h */,
|
||||||
54ACC29721061FBA0020715F /* Preferences.m */,
|
54ACC29721061FBA0020715F /* Preferences.m */,
|
||||||
546FC4462118A8E6007CC3A3 /* Preferences.xib */,
|
546FC4462118A8E6007CC3A3 /* Preferences.xib */,
|
||||||
546FC44521189ADC007CC3A3 /* Settings Tabs */,
|
|
||||||
544B01182114B41200386E5C /* ModalSheet.h */,
|
544B01182114B41200386E5C /* ModalSheet.h */,
|
||||||
544B01192114B41200386E5C /* ModalSheet.m */,
|
544B01192114B41200386E5C /* ModalSheet.m */,
|
||||||
|
546FC44521189ADC007CC3A3 /* General Tab */,
|
||||||
|
54E88323211B542E00064188 /* Feeds Tab */,
|
||||||
);
|
);
|
||||||
path = Preferences;
|
path = Preferences;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -157,6 +162,19 @@
|
|||||||
path = baRSS;
|
path = baRSS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
54E88323211B542E00064188 /* Feeds Tab */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */,
|
||||||
|
546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */,
|
||||||
|
546FC43E21188C78007CC3A3 /* SettingsFeeds.xib */,
|
||||||
|
54E8831D211B509D00064188 /* ModalFeedEdit.h */,
|
||||||
|
54E8831E211B509D00064188 /* ModalFeedEdit.m */,
|
||||||
|
54E8831F211B509D00064188 /* ModalFeedEdit.xib */,
|
||||||
|
);
|
||||||
|
path = "Feeds Tab";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -224,6 +242,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */,
|
546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */,
|
||||||
|
54E88321211B509D00064188 /* ModalFeedEdit.xib in Resources */,
|
||||||
54ACC28621061B3C0020715F /* Assets.xcassets in Resources */,
|
54ACC28621061B3C0020715F /* Assets.xcassets in Resources */,
|
||||||
546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */,
|
546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */,
|
||||||
546FC43F21188C78007CC3A3 /* SettingsFeeds.xib in Resources */,
|
546FC43F21188C78007CC3A3 /* SettingsFeeds.xib in Resources */,
|
||||||
@@ -249,6 +268,7 @@
|
|||||||
544B011A2114B41200386E5C /* ModalSheet.m in Sources */,
|
544B011A2114B41200386E5C /* ModalSheet.m in Sources */,
|
||||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */,
|
54ACC29821061FBA0020715F /* Preferences.m in Sources */,
|
||||||
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
|
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
|
||||||
|
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */,
|
||||||
1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */,
|
1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */,
|
||||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */,
|
54209E942117325100F3B5EF /* DrawImage.m in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -117,7 +117,8 @@
|
|||||||
@implementation DrawSeparator
|
@implementation DrawSeparator
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
- (void)drawRect:(NSRect)dirtyRect {
|
||||||
NSGradient *grdnt = [[NSGradient alloc] initWithStartingColor:[NSColor darkGrayColor] endingColor:[[NSColor darkGrayColor] colorWithAlphaComponent:0.0]];
|
NSGradient *grdnt = [[NSGradient alloc] initWithStartingColor:[NSColor darkGrayColor] endingColor:[[NSColor darkGrayColor] colorWithAlphaComponent:0.0]];
|
||||||
NSBezierPath *rounded = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(1, self.bounds.size.height/2.0-1, self.bounds.size.width-2, 2) xRadius:1 yRadius:1];
|
NSRect separatorRect = NSMakeRect(1, self.frame.size.height / 2.0 - 1, self.frame.size.width - 2, 2);
|
||||||
|
NSBezierPath *rounded = [NSBezierPath bezierPathWithRoundedRect:separatorRect xRadius:1 yRadius:1];
|
||||||
[grdnt drawInBezierPath:rounded angle:0];
|
[grdnt drawInBezierPath:rounded angle:0];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -24,4 +24,5 @@
|
|||||||
|
|
||||||
@interface NewsController : NSObject
|
@interface NewsController : NSObject
|
||||||
|
|
||||||
|
+ (void)downloadFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -70,4 +70,15 @@
|
|||||||
NSLog(@"all unread");
|
NSLog(@"all unread");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (void)downloadFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block {
|
||||||
|
[NSThread detachNewThreadWithBlock:^{
|
||||||
|
NSDictionary *dict = [PyHandler getFeed:url withEtag:nil andModified:nil];
|
||||||
|
NSError *err = nil;
|
||||||
|
if (!dict || [dict[@"entries"] count] == 0 ) {
|
||||||
|
err = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotParseResponse userInfo:nil];
|
||||||
|
}
|
||||||
|
if (block) block(dict, err);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
40
baRSS/Preferences/FeedConfig+Print.h
Normal file
40
baRSS/Preferences/FeedConfig+Print.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef FeedConfig_Print_h
|
||||||
|
#define FeedConfig_Print_h
|
||||||
|
|
||||||
|
@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:
|
||||||
|
return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.url, [self readableRefreshString]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif /* FeedConfig_Print_h */
|
||||||
35
baRSS/Preferences/Feeds Tab/ModalFeedEdit.h
Normal file
35
baRSS/Preferences/Feeds Tab/ModalFeedEdit.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// 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 <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@protocol ModalFeedConfigEdit <NSObject>
|
||||||
|
- (void)updateRepresentedObject; // must call [item.managedObjectContext refreshAllObjects]
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface ModalFeedEdit : NSViewController <ModalFeedConfigEdit, NSTextFieldDelegate>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface ModalGroupEdit : NSViewController <ModalFeedConfigEdit>
|
||||||
|
@end
|
||||||
|
|
||||||
218
baRSS/Preferences/Feeds Tab/ModalFeedEdit.m
Normal file
218
baRSS/Preferences/Feeds Tab/ModalFeedEdit.m
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
//
|
||||||
|
// 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 "ModalFeedEdit.h"
|
||||||
|
#import "NewsController.h"
|
||||||
|
#import "FeedConfig+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@interface ModalFeedEdit()
|
||||||
|
@property (weak) IBOutlet NSTextField *url;
|
||||||
|
@property (weak) IBOutlet NSTextField *name;
|
||||||
|
@property (weak) IBOutlet NSTextField *refreshNum;
|
||||||
|
@property (weak) IBOutlet NSPopUpButton *refreshUnit;
|
||||||
|
@property (weak) IBOutlet NSProgressIndicator *spinnerURL;
|
||||||
|
@property (weak) IBOutlet NSProgressIndicator *spinnerName;
|
||||||
|
@property (weak) IBOutlet NSButton *warningIndicator;
|
||||||
|
@property (weak) IBOutlet NSPopover *warningPopover;
|
||||||
|
|
||||||
|
@property (copy) NSString *previousURL;
|
||||||
|
@property (strong) NSError *feedError;
|
||||||
|
@property (strong) NSDictionary *feedResult;
|
||||||
|
|
||||||
|
@property (assign) BOOL shouldEvaluate;
|
||||||
|
@property (assign) BOOL lateEvaluation;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ModalFeedEdit
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
self.previousURL = @"";
|
||||||
|
self.refreshNum.intValue = 30;
|
||||||
|
self.shouldEvaluate = NO;
|
||||||
|
|
||||||
|
FeedConfig *fc = [self feedConfigOrNil];
|
||||||
|
if (fc) {
|
||||||
|
self.url.objectValue = fc.url;
|
||||||
|
self.name.objectValue = fc.name;
|
||||||
|
self.refreshNum.intValue = fc.refreshNum;
|
||||||
|
NSInteger unitIndex = fc.refreshUnit;
|
||||||
|
if (unitIndex < 0 || unitIndex > self.refreshUnit.numberOfItems - 1)
|
||||||
|
unitIndex = self.refreshUnit.numberOfItems - 1;
|
||||||
|
[self.refreshUnit selectItemAtIndex:unitIndex];
|
||||||
|
|
||||||
|
self.previousURL = self.url.stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
FeedConfig *item = [self feedConfigOrNil];
|
||||||
|
if (self.shouldEvaluate && self.lateEvaluation && item) {
|
||||||
|
if (!item.name || [item.name isEqualToString:@""]) {
|
||||||
|
[self setTitleFromFeed];
|
||||||
|
if (![item.name isEqualToString: self.name.stringValue]) // only if result isnt empty as well
|
||||||
|
item.name = self.name.stringValue;
|
||||||
|
[item.managedObjectContext refreshAllObjects];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateRepresentedObject {
|
||||||
|
FeedConfig *item = [self feedConfigOrNil];
|
||||||
|
if (item) {
|
||||||
|
// if's to prevent unnecessary undo groups if nothing has changed
|
||||||
|
if (![item.name isEqualToString: self.name.stringValue])
|
||||||
|
item.name = self.name.stringValue;
|
||||||
|
if (![item.url isEqualToString:self.url.stringValue])
|
||||||
|
item.url = self.url.stringValue;
|
||||||
|
if (item.refreshNum != self.refreshNum.intValue)
|
||||||
|
item.refreshNum = self.refreshNum.intValue;
|
||||||
|
if (item.refreshUnit != self.refreshUnit.indexOfSelectedItem)
|
||||||
|
item.refreshUnit = (int16_t)self.refreshUnit.indexOfSelectedItem;
|
||||||
|
|
||||||
|
self.shouldEvaluate = YES;
|
||||||
|
self.lateEvaluation = NO;
|
||||||
|
// TODO: append feed result
|
||||||
|
NSLog(@"here i want to set it");
|
||||||
|
[item.managedObjectContext refreshAllObjects];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (FeedConfig*)feedConfigOrNil {
|
||||||
|
if ([self.representedObject isKindOfClass:[FeedConfig class]])
|
||||||
|
return self.representedObject;
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)urlHasChanged {
|
||||||
|
return ![self.previousURL isEqualToString:self.url.stringValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)controlTextDidChange:(NSNotification *)obj {
|
||||||
|
if (obj.object == self.url) {
|
||||||
|
self.warningIndicator.hidden = (!self.feedError || [self urlHasChanged]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
||||||
|
if (obj.object == self.url && [self urlHasChanged]) {
|
||||||
|
self.previousURL = self.url.stringValue;
|
||||||
|
self.feedResult = nil;
|
||||||
|
NSLog(@"setting result to nil");
|
||||||
|
self.feedError = nil;
|
||||||
|
[self.spinnerURL startAnimation:nil];
|
||||||
|
[self.spinnerName startAnimation:nil];
|
||||||
|
[NewsController downloadFeed:self.previousURL withBlock:^(NSDictionary *result, NSError *error) {
|
||||||
|
self.feedResult = result;
|
||||||
|
NSLog(@"got results back");
|
||||||
|
self.feedError = error; // warning indicator .hidden is bound to feedError
|
||||||
|
// TODO: play error sound?
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
self.lateEvaluation = YES; // stays YES if this block runs after updateRepresentedObject:
|
||||||
|
[self setTitleFromFeed];
|
||||||
|
[self.spinnerURL stopAnimation:nil];
|
||||||
|
[self.spinnerName stopAnimation:nil];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
// http://feeds.feedburner.com/simpledesktops
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setTitleFromFeed {
|
||||||
|
if ([self.name.stringValue isEqualToString:@""]) {
|
||||||
|
self.name.objectValue = self.feedResult[@"feed"][@"title"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)didClickWarningButton:(NSButton*)sender {
|
||||||
|
if (!self.feedError)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NSString *str = self.feedError.localizedDescription;
|
||||||
|
NSTextField *tf = self.warningPopover.contentViewController.view.subviews.firstObject;
|
||||||
|
tf.maximumNumberOfLines = 7;
|
||||||
|
tf.objectValue = str;
|
||||||
|
|
||||||
|
NSSize newSize = tf.fittingSize; // width is limited by the textfield's preferred width
|
||||||
|
newSize.width += 2 * tf.frame.origin.x; // the padding
|
||||||
|
newSize.height += 2 * tf.frame.origin.y;
|
||||||
|
|
||||||
|
[self.warningPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSRectEdgeMinY];
|
||||||
|
[self.warningPopover setContentSize:newSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - ModalGroupEdit
|
||||||
|
|
||||||
|
@implementation ModalGroupEdit
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
if ([self.representedObject isKindOfClass:[FeedConfig class]]) {
|
||||||
|
FeedConfig *fc = self.representedObject;
|
||||||
|
((NSTextField*)self.view).objectValue = fc.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- (void)loadView {
|
||||||
|
NSTextField *tf = [NSTextField textFieldWithString:@"New Group"];
|
||||||
|
tf.placeholderString = @"New Group";
|
||||||
|
tf.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
|
||||||
|
self.view = tf;
|
||||||
|
}
|
||||||
|
- (void)updateRepresentedObject {
|
||||||
|
if ([self.representedObject isKindOfClass:[FeedConfig class]]) {
|
||||||
|
FeedConfig *item = self.representedObject;
|
||||||
|
NSString *name = ((NSTextField*)self.view).stringValue;
|
||||||
|
if (![item.name isEqualToString: name]) {
|
||||||
|
item.name = name;
|
||||||
|
[item.managedObjectContext refreshAllObjects];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - StrictUIntFormatter
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
175
baRSS/Preferences/Feeds Tab/ModalFeedEdit.xib
Normal file
175
baRSS/Preferences/Feeds Tab/ModalFeedEdit.xib
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="ModalFeedEdit">
|
||||||
|
<connections>
|
||||||
|
<outlet property="name" destination="ab8-rr-HbK" id="J4T-Zl-KF3"/>
|
||||||
|
<outlet property="refreshNum" destination="cNl-ht-xws" id="3cA-TW-qi5"/>
|
||||||
|
<outlet property="refreshUnit" destination="TUi-VS-ge4" id="dr6-GW-gU0"/>
|
||||||
|
<outlet property="spinnerName" destination="Afo-pQ-8Qx" id="DVx-vd-Zer"/>
|
||||||
|
<outlet property="spinnerURL" destination="H0a-x4-o4X" id="MgB-RI-yP5"/>
|
||||||
|
<outlet property="url" destination="Asm-D9-ZfT" id="3gO-Xc-2KJ"/>
|
||||||
|
<outlet property="view" destination="i0K-k8-GMU" id="qcu-Oh-rOj"/>
|
||||||
|
<outlet property="warningIndicator" destination="LWE-Y8-ebl" id="j9x-OY-2th"/>
|
||||||
|
<outlet property="warningPopover" destination="stq-gJ-ra0" id="rJy-GV-PHk"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customView id="i0K-k8-GMU" userLabel="View">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="79"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MOX-a1-Yda" userLabel="URL Label">
|
||||||
|
<rect key="frame" x="-2" y="60" width="103" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="URL" id="6wE-lP-4xC">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField verticalHuggingPriority="750" fixedFrame="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Asm-D9-ZfT">
|
||||||
|
<rect key="frame" x="107" y="58" width="193" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="https://example.org/feed.rss" drawsBackground="YES" usesSingleLineMode="YES" id="0Sk-H2-VAC">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="-2" id="R3c-aF-If2"/>
|
||||||
|
</connections>
|
||||||
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kVL-HV-oxU" userLabel="Name Label">
|
||||||
|
<rect key="frame" x="-2" y="31" width="103" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name" id="2ls-F4-oUL">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ab8-rr-HbK">
|
||||||
|
<rect key="frame" x="107" y="29" width="193" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Example Title" drawsBackground="YES" usesSingleLineMode="YES" id="1ku-vp-T5y">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Tc-as-s1U" userLabel="Refresh Label">
|
||||||
|
<rect key="frame" x="-2" y="2" width="103" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Refresh" id="2IV-ec-RfH">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cNl-ht-xws">
|
||||||
|
<rect key="frame" x="107" y="0.0" width="85" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="30" drawsBackground="YES" usesSingleLineMode="YES" id="DqU-fT-cIf">
|
||||||
|
<customFormatter key="formatter" id="Lbd-r9-4bc" customClass="StrictUIntFormatter"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TUi-VS-ge4">
|
||||||
|
<rect key="frame" x="198" y="-3" width="125" height="26"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<popUpButtonCell key="cell" type="push" title="Minutes" bezelStyle="rounded" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" altersStateOfSelectedItem="NO" selectedItem="CsM-KR-zzs" id="O0p-Tc-KQ1">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="menu"/>
|
||||||
|
<menu key="menu" showsStateColumn="NO" autoenablesItems="NO" id="7hX-7Y-rtT">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Seconds" keyEquivalent="s" id="VD1-1h-Hdh">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Minutes" state="on" keyEquivalent="m" id="CsM-KR-zzs">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Hours" keyEquivalent="h" id="Nqd-L9-4V8">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Days" keyEquivalent="d" id="5c2-Mb-3aw">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Weeks" keyEquivalent="w" id="mJE-8n-iKF">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</popUpButtonCell>
|
||||||
|
</popUpButton>
|
||||||
|
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="H0a-x4-o4X">
|
||||||
|
<rect key="frame" x="304" y="60" width="16" height="16"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||||
|
</progressIndicator>
|
||||||
|
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="Afo-pQ-8Qx">
|
||||||
|
<rect key="frame" x="304" y="31" width="16" height="16"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||||
|
</progressIndicator>
|
||||||
|
<button hidden="YES" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LWE-Y8-ebl">
|
||||||
|
<rect key="frame" x="302" y="60" width="18" height="18"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||||
|
<buttonCell key="cell" type="roundRect" bezelStyle="roundedRect" image="NSCaution" imagePosition="only" alignment="center" refusesFirstResponder="YES" state="on" imageScaling="proportionallyDown" inset="2" id="FAw-6c-Vij">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="cellTitle"/>
|
||||||
|
<string key="keyEquivalent">i</string>
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="didClickWarningButton:" target="-2" id="wNa-Cc-jZb"/>
|
||||||
|
<binding destination="-2" name="hidden" keyPath="self.feedError" id="o3F-lJ-LPU">
|
||||||
|
<dictionary key="options">
|
||||||
|
<string key="NSValueTransformerName">NSIsNil</string>
|
||||||
|
</dictionary>
|
||||||
|
</binding>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<point key="canvasLocation" x="-137" y="586.5"/>
|
||||||
|
</customView>
|
||||||
|
<viewController id="xTH-2c-Ppt" userLabel="Popover View Controller">
|
||||||
|
<connections>
|
||||||
|
<outlet property="view" destination="bVj-RM-sjw" id="TP8-Eb-GVO"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<popover behavior="t" id="stq-gJ-ra0">
|
||||||
|
<connections>
|
||||||
|
<outlet property="contentViewController" destination="xTH-2c-Ppt" id="ODh-uM-ARs"/>
|
||||||
|
</connections>
|
||||||
|
</popover>
|
||||||
|
<customView id="bVj-RM-sjw" userLabel="Popover View">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="300" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textField wantsLayer="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nCT-Lc-wce">
|
||||||
|
<rect key="frame" x="2" y="2" width="296" height="40"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<textFieldCell key="cell" truncatesLastVisibleLine="YES" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" id="YJs-n4-Lxb">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="title">Couldn't load Feed
|
||||||
|
An additional line
|
||||||
|
and a third</string>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
<point key="canvasLocation" x="14" y="477"/>
|
||||||
|
</customView>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="NSCaution" width="32" height="32"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
@@ -21,17 +21,18 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
#import "SettingsFeeds.h"
|
#import "SettingsFeeds.h"
|
||||||
#import "DBv1+CoreDataModel.h"
|
|
||||||
#import "ModalSheet.h"
|
|
||||||
#import "DrawImage.h"
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
#import "DBv1+CoreDataModel.h"
|
||||||
|
#import "FeedConfig+Print.h"
|
||||||
|
#import "ModalSheet.h"
|
||||||
|
#import "ModalFeedEdit.h"
|
||||||
|
#import "DrawImage.h"
|
||||||
|
|
||||||
@interface SettingsFeeds ()
|
@interface SettingsFeeds ()
|
||||||
@property (weak) IBOutlet ModalFeedEdit *viewModalEditFeed;
|
|
||||||
@property (weak) IBOutlet ModalGroupEdit *viewModalEditGroup;
|
|
||||||
@property (weak) IBOutlet NSOutlineView *outlineView;
|
@property (weak) IBOutlet NSOutlineView *outlineView;
|
||||||
@property (weak) IBOutlet NSTreeController *dataStore;
|
@property (weak) IBOutlet NSTreeController *dataStore;
|
||||||
|
|
||||||
|
@property (strong) NSViewController<ModalFeedConfigEdit> *modalController;
|
||||||
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes;
|
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes;
|
||||||
@property (strong) NSUndoManager *undoManager;
|
@property (strong) NSUndoManager *undoManager;
|
||||||
@end
|
@end
|
||||||
@@ -89,40 +90,25 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
[self showModalForFeedConfig:self.dataStore.selectedObjects.firstObject isGroupEdit:YES]; // yes will be overwritten anyway
|
[self showModalForFeedConfig:self.dataStore.selectedObjects.firstObject isGroupEdit:YES]; // yes will be overwritten anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)showModalForFeedConfig:(FeedConfig*)obj isGroupEdit:(bool)group {
|
- (void)showModalForFeedConfig:(FeedConfig*)obj isGroupEdit:(BOOL)group {
|
||||||
bool existingItem = [obj isKindOfClass:[FeedConfig class]];
|
BOOL existingItem = [obj isKindOfClass:[FeedConfig class]];
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
if (obj.type == 2) return; // Separator
|
if (obj.type == 2) return; // Separator
|
||||||
group = (obj.type == 0);
|
group = (obj.type == 0);
|
||||||
if (group) [self.viewModalEditGroup setGroupName:obj.name];
|
|
||||||
else [self.viewModalEditFeed setURL:obj.url name:obj.name refreshNum:obj.refreshNum unit:obj.refreshUnit];
|
|
||||||
} else {
|
|
||||||
if (group) [self.viewModalEditGroup setDefaultValues];
|
|
||||||
else [self.viewModalEditFeed setDefaultValues];
|
|
||||||
}
|
}
|
||||||
NSView *content = (group ? self.viewModalEditGroup : self.viewModalEditFeed);
|
self.modalController = (group ? [ModalGroupEdit new] : [ModalFeedEdit new]);
|
||||||
[self.view.window beginSheet:[ModalSheet modalWithView:content] completionHandler:^(NSModalResponse returnCode) {
|
self.modalController.representedObject = obj;
|
||||||
|
|
||||||
|
[self.view.window beginSheet:[ModalSheet modalWithView:self.modalController.view] completionHandler:^(NSModalResponse returnCode) {
|
||||||
if (returnCode == NSModalResponseOK) {
|
if (returnCode == NSModalResponseOK) {
|
||||||
FeedConfig *item = obj;
|
|
||||||
if (!existingItem) { // create new item
|
if (!existingItem) { // create new item
|
||||||
item = [self insertSortedItemAtSelection];
|
FeedConfig *item = [self insertSortedItemAtSelection];
|
||||||
item.type = (group ? 0 : 1);
|
item.type = (group ? 0 : 1);
|
||||||
|
self.modalController.representedObject = item;
|
||||||
}
|
}
|
||||||
if (group) {
|
[self.modalController updateRepresentedObject];
|
||||||
if (![item.name isEqualToString: self.viewModalEditGroup.title.stringValue])
|
|
||||||
item.name = self.viewModalEditGroup.title.stringValue;
|
|
||||||
} else {
|
|
||||||
if (![item.name isEqualToString: self.viewModalEditFeed.title.stringValue])
|
|
||||||
item.name = self.viewModalEditFeed.title.stringValue;
|
|
||||||
if (![item.url isEqualToString:self.viewModalEditFeed.url.stringValue])
|
|
||||||
item.url = self.viewModalEditFeed.url.stringValue;
|
|
||||||
if (item.refreshNum != self.viewModalEditFeed.refreshNum.intValue)
|
|
||||||
item.refreshNum = self.viewModalEditFeed.refreshNum.intValue;
|
|
||||||
if (item.refreshUnit != self.viewModalEditFeed.refreshUnit.indexOfSelectedItem)
|
|
||||||
item.refreshUnit = (int16_t)self.viewModalEditFeed.refreshUnit.indexOfSelectedItem;
|
|
||||||
}
|
|
||||||
[self.dataStore rearrangeObjects];
|
|
||||||
}
|
}
|
||||||
|
self.modalController = nil;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +118,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
|
|
||||||
FeedConfig *selected = [[[self.dataStore arrangedObjects] descendantNodeAtIndexPath:selectedIndex] representedObject];
|
FeedConfig *selected = [[[self.dataStore arrangedObjects] descendantNodeAtIndexPath:selectedIndex] representedObject];
|
||||||
NSUInteger lastIndex = selected.children.count;
|
NSUInteger lastIndex = selected.children.count;
|
||||||
bool groupSelected = (selected.type == 0);
|
BOOL groupSelected = (selected.type == 0);
|
||||||
|
|
||||||
if (!groupSelected) {
|
if (!groupSelected) {
|
||||||
lastIndex = (NSUInteger)selected.sortIndex + 1; // insert after selection
|
lastIndex = (NSUInteger)selected.sortIndex + 1; // insert after selection
|
||||||
@@ -191,7 +177,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
if (!item || !dstChildren)
|
if (!item || !dstChildren)
|
||||||
dstChildren = [self.dataStore arrangedObjects].childNodes;
|
dstChildren = [self.dataStore arrangedObjects].childNodes;
|
||||||
|
|
||||||
bool isFolderDrag = (index == -1);
|
BOOL isFolderDrag = (index == -1);
|
||||||
NSUInteger insertIndex = (isFolderDrag ? dstChildren.count : (NSUInteger)index);
|
NSUInteger insertIndex = (isFolderDrag ? dstChildren.count : (NSUInteger)index);
|
||||||
// index where the items will be moved to, but not final since items above can vanish
|
// index where the items will be moved to, but not final since items above can vanish
|
||||||
NSIndexPath *dest = [item indexPath];
|
NSIndexPath *dest = [item indexPath];
|
||||||
@@ -250,16 +236,16 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
|
|
||||||
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
|
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
|
||||||
FeedConfig *f = [(NSTreeNode*)item representedObject];
|
FeedConfig *f = [(NSTreeNode*)item representedObject];
|
||||||
bool isFeed = (f.type == 1);
|
BOOL isFeed = (f.type == 1);
|
||||||
bool isSeperator = (f.type == 2);
|
BOOL isSeperator = (f.type == 2);
|
||||||
bool isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
||||||
|
|
||||||
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
||||||
// owner is nil to prohibit repeated awakeFromNib calls
|
// owner is nil to prohibit repeated awakeFromNib calls
|
||||||
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
||||||
|
|
||||||
if (isRefreshColumn) {
|
if (isRefreshColumn) {
|
||||||
cellView.textField.stringValue = (!isFeed ? @"" : [ModalFeedEdit stringForRefreshNum:f.refreshNum unit:f.refreshUnit]);
|
cellView.textField.stringValue = (!isFeed ? @"" : [f readableRefreshString]);
|
||||||
} else if (isSeperator) {
|
} else if (isSeperator) {
|
||||||
return cellView; // the refresh cell is already skipped with the above if condition
|
return cellView; // the refresh cell is already skipped with the above if condition
|
||||||
} else {
|
} else {
|
||||||
@@ -285,13 +271,17 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
|
|
||||||
|
|
||||||
- (BOOL)respondsToSelector:(SEL)aSelector {
|
- (BOOL)respondsToSelector:(SEL)aSelector {
|
||||||
if (aSelector == @selector(enterPressed:) || aSelector == @selector(copy:)) {
|
if (aSelector == @selector(undo:)) return [self.undoManager canUndo];
|
||||||
bool outlineHasFocus = [[self.view.window firstResponder] isKindOfClass:[NSOutlineView class]];
|
if (aSelector == @selector(redo:)) return [self.undoManager canRedo];
|
||||||
return outlineHasFocus && (self.dataStore.selectedNodes.count > 0);
|
if (aSelector == @selector(copy:) || aSelector == @selector(enterPressed:)) {
|
||||||
} else if (aSelector == @selector(undo:)) {
|
BOOL outlineHasFocus = [[self.view.window firstResponder] isKindOfClass:[NSOutlineView class]];
|
||||||
return [self.undoManager canUndo];
|
BOOL hasSelection = (self.dataStore.selectedNodes.count > 0);
|
||||||
} else if (aSelector == @selector(redo:)) {
|
if (!outlineHasFocus || !hasSelection)
|
||||||
return [self.undoManager canRedo];
|
return NO;
|
||||||
|
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 [super respondsToSelector:aSelector];
|
return [super respondsToSelector:aSelector];
|
||||||
}
|
}
|
||||||
@@ -312,32 +302,37 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
|
|
||||||
- (void)copy:(id)sender {
|
- (void)copy:(id)sender {
|
||||||
NSMutableString *str = [[NSMutableString alloc] init];
|
NSMutableString *str = [[NSMutableString alloc] init];
|
||||||
NSMutableArray<FeedConfig*> *items = [NSMutableArray arrayWithArray:self.dataStore.selectedObjects];
|
NSUInteger count = self.dataStore.selectedNodes.count;
|
||||||
while (items.count > 0) {
|
NSMutableArray<NSTreeNode*> *groups = [NSMutableArray arrayWithCapacity:count];
|
||||||
[self traverseChildren:items[0] appendString:str indentation:0 onSelection:items];
|
|
||||||
|
// filter out nodes that are already present in some selected parent node
|
||||||
|
for (NSTreeNode *node in self.dataStore.selectedNodes) {
|
||||||
|
BOOL skipItem = NO;
|
||||||
|
for (NSTreeNode *stored in groups) {
|
||||||
|
NSIndexPath *p = node.indexPath;
|
||||||
|
while (p.length > stored.indexPath.length)
|
||||||
|
p = [p indexPathByRemovingLastIndex];
|
||||||
|
if ([p isEqualTo:stored.indexPath]) {
|
||||||
|
skipItem = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!skipItem) {
|
||||||
|
[self traverseChildren:node appendString:str prefix:@""];
|
||||||
|
if (node.childNodes.count > 0)
|
||||||
|
[groups addObject:node];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[[NSPasteboard generalPasteboard] clearContents];
|
[[NSPasteboard generalPasteboard] clearContents];
|
||||||
[[NSPasteboard generalPasteboard] setString:str forType:NSPasteboardTypeString];
|
[[NSPasteboard generalPasteboard] setString:str forType:NSPasteboardTypeString];
|
||||||
NSLog(@"%@", str);
|
NSLog(@"%@", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)traverseChildren:(FeedConfig*)obj appendString:(NSMutableString*)str indentation:(int)indent onSelection:(NSMutableArray*)arr {
|
- (void)traverseChildren:(NSTreeNode*)obj appendString:(NSMutableString*)str prefix:(NSString*)prefix {
|
||||||
for (NSUInteger i = 0; i < arr.count; i++) {
|
[str appendFormat:@"%@%@\n", prefix, [obj.representedObject readableDescription]];
|
||||||
if (obj == arr[i]) {
|
prefix = [prefix stringByAppendingString:@" "];
|
||||||
[arr removeObjectAtIndex:i];
|
for (NSTreeNode *child in obj.childNodes) {
|
||||||
break;
|
[self traverseChildren:child appendString:str prefix:prefix];
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = indent; i > 0; i--) {
|
|
||||||
[str appendString:@" "];
|
|
||||||
}
|
|
||||||
switch (obj.type) {
|
|
||||||
case 0: [str appendFormat:@"%@:\n", obj.name]; break; // Group
|
|
||||||
case 2: [str appendString:@"-------------\n"]; break; // Separator
|
|
||||||
default: [str appendFormat:@"%@ (%@) - %@\n", obj.name, obj.url, [ModalFeedEdit stringForRefreshNum:obj.refreshNum unit:obj.refreshUnit]];
|
|
||||||
}
|
|
||||||
for (FeedConfig *child in obj.children) {
|
|
||||||
[self traverseChildren:child appendString:str indentation:indent + 1 onSelection:arr];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,8 +11,6 @@
|
|||||||
<outlet property="dataStore" destination="JPf-gH-wxm" id="9qy-D6-L4R"/>
|
<outlet property="dataStore" destination="JPf-gH-wxm" id="9qy-D6-L4R"/>
|
||||||
<outlet property="outlineView" destination="wP9-Vd-f79" id="nKf-fc-7Np"/>
|
<outlet property="outlineView" destination="wP9-Vd-f79" id="nKf-fc-7Np"/>
|
||||||
<outlet property="view" destination="zfc-Ie-Sdx" id="65R-bK-FDI"/>
|
<outlet property="view" destination="zfc-Ie-Sdx" id="65R-bK-FDI"/>
|
||||||
<outlet property="viewModalEditFeed" destination="0p5-Fv-ym4" id="C7K-zb-25M"/>
|
|
||||||
<outlet property="viewModalEditGroup" destination="ZZI-U3-ymX" id="G34-k8-YYo"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
@@ -221,120 +219,7 @@ CA
|
|||||||
</subviews>
|
</subviews>
|
||||||
<point key="canvasLocation" x="27" y="883"/>
|
<point key="canvasLocation" x="27" y="883"/>
|
||||||
</customView>
|
</customView>
|
||||||
<customView id="0p5-Fv-ym4" userLabel="Modal Edit Feed" customClass="ModalFeedEdit">
|
<viewController id="TaZ-4L-TdU" customClass="ModalFeedEdit"/>
|
||||||
<rect key="frame" x="0.0" y="0.0" width="320" height="79"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qGE-iP-bhd" userLabel="URL Label">
|
|
||||||
<rect key="frame" x="-2" y="60" width="103" height="17"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="URL" id="20W-UE-O54">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dBf-Ic-aFx">
|
|
||||||
<rect key="frame" x="107" y="58" width="213" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="https://example.org/feed.rss" drawsBackground="YES" usesSingleLineMode="YES" id="9uG-PC-ydS">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Gwc-hR-gQU" userLabel="Name Label">
|
|
||||||
<rect key="frame" x="-2" y="31" width="103" height="17"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name" id="ZZt-vJ-gqR">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Sif-B7-ReQ">
|
|
||||||
<rect key="frame" x="107" y="29" width="213" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Example Title" drawsBackground="YES" usesSingleLineMode="YES" id="B6j-EO-17H">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OtU-zo-1Fb" userLabel="Refresh Label">
|
|
||||||
<rect key="frame" x="-2" y="2" width="103" height="17"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Refresh" id="XDh-kX-OcM">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kGm-Pt-t1H">
|
|
||||||
<rect key="frame" x="107" y="0.0" width="85" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="30" drawsBackground="YES" usesSingleLineMode="YES" id="rm9-UV-YhG">
|
|
||||||
<customFormatter key="formatter" id="Ubj-jG-b6k" customClass="StrictUIntFormatter"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mB4-d4-lQz" userLabel="TimeUnit">
|
|
||||||
<rect key="frame" x="198" y="-3" width="125" height="26"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<popUpButtonCell key="cell" type="push" title="Seconds" bezelStyle="rounded" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" altersStateOfSelectedItem="NO" selectedItem="KZL-Pi-rjI" id="0ux-Qa-zbq">
|
|
||||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
|
||||||
<font key="font" metaFont="menu"/>
|
|
||||||
<menu key="menu" showsStateColumn="NO" autoenablesItems="NO" id="Snb-Bo-1GG">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Seconds" keyEquivalent="s" id="TnX-Hk-ZFr">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Minutes" keyEquivalent="m" id="KZL-Pi-rjI">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Hours" keyEquivalent="h" id="uwN-nk-aaQ">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Days" keyEquivalent="d" id="XCA-xq-97w">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Weeks" keyEquivalent="w" id="r9d-8y-QrY">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</popUpButtonCell>
|
|
||||||
</popUpButton>
|
|
||||||
</subviews>
|
|
||||||
<connections>
|
|
||||||
<outlet property="refreshNum" destination="kGm-Pt-t1H" id="OJY-yn-Mye"/>
|
|
||||||
<outlet property="refreshUnit" destination="mB4-d4-lQz" id="xsk-oZ-up1"/>
|
|
||||||
<outlet property="title" destination="Sif-B7-ReQ" id="vdX-oR-Lvv"/>
|
|
||||||
<outlet property="url" destination="dBf-Ic-aFx" id="DqH-R5-Sfe"/>
|
|
||||||
</connections>
|
|
||||||
<point key="canvasLocation" x="27" y="1131"/>
|
|
||||||
</customView>
|
|
||||||
<customView id="ZZI-U3-ymX" userLabel="Modal Edit Group" customClass="ModalGroupEdit">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="260" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zl1-tn-hla">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="260" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" title="Group Name" placeholderString="Group Name" drawsBackground="YES" usesSingleLineMode="YES" id="afG-YW-7CW">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
</textField>
|
|
||||||
</subviews>
|
|
||||||
<connections>
|
|
||||||
<outlet property="title" destination="zl1-tn-hla" id="R4x-bv-CHB"/>
|
|
||||||
</connections>
|
|
||||||
<point key="canvasLocation" x="27" y="1229"/>
|
|
||||||
</customView>
|
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="NSActionTemplate" width="14" height="14"/>
|
<image name="NSActionTemplate" width="14" height="14"/>
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
</popUpButtonCell>
|
</popUpButtonCell>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
</subviews>
|
</subviews>
|
||||||
<point key="canvasLocation" x="-20" y="903.5"/>
|
<point key="canvasLocation" x="91" y="-163"/>
|
||||||
</customView>
|
</customView>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
@@ -24,27 +24,5 @@
|
|||||||
|
|
||||||
@interface ModalSheet : NSPanel
|
@interface ModalSheet : NSPanel
|
||||||
+ (instancetype)modalWithView:(NSView*)content;
|
+ (instancetype)modalWithView:(NSView*)content;
|
||||||
|
- (void)setDoneEnabled:(BOOL)accept;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@interface ModalFeedEdit : NSView
|
|
||||||
@property (weak) IBOutlet NSTextField *url;
|
|
||||||
@property (weak) IBOutlet NSTextField *title;
|
|
||||||
@property (weak) IBOutlet NSTextField *refreshNum;
|
|
||||||
@property (weak) IBOutlet NSPopUpButton *refreshUnit;
|
|
||||||
- (void)setDefaultValues;
|
|
||||||
- (void)setURL:(NSString*)url name:(NSString*)name refreshNum:(int32_t)num unit:(int16_t)unit;
|
|
||||||
+ (NSString*)stringForRefreshNum:(int32_t)num unit:(int16_t)unit;
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@interface ModalGroupEdit : NSView
|
|
||||||
@property (weak) IBOutlet NSTextField *title;
|
|
||||||
- (void)setDefaultValues;
|
|
||||||
- (void)setGroupName:(NSString*)name;
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@interface StrictUIntFormatter : NSFormatter
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,15 @@
|
|||||||
|
|
||||||
#import "ModalSheet.h"
|
#import "ModalSheet.h"
|
||||||
|
|
||||||
#define BETWEEN(x,min,max) (x < min ? min : x > max ? max : x)
|
@interface ModalSheet()
|
||||||
|
@property (strong) NSButton *btnDone;
|
||||||
|
@end
|
||||||
#pragma mark - ModalSheet
|
|
||||||
|
|
||||||
@implementation ModalSheet
|
@implementation ModalSheet
|
||||||
|
|
||||||
- (void)didTapDoneButton:(id)sender { [self closeWithResponse:NSModalResponseOK]; }
|
- (void)didTapDoneButton:(id)sender { [self closeWithResponse:NSModalResponseOK]; }
|
||||||
- (void)didTapCancelButton:(id)sender { [self closeWithResponse:NSModalResponseAbort]; }
|
- (void)didTapCancelButton:(id)sender { [self closeWithResponse:NSModalResponseAbort]; }
|
||||||
|
- (void)setDoneEnabled:(BOOL)accept { self.btnDone.enabled = accept; }
|
||||||
|
|
||||||
- (void)closeWithResponse:(NSModalResponse)response {
|
- (void)closeWithResponse:(NSModalResponse)response {
|
||||||
// store modal view width and remove subviews to avoid _NSKeyboardFocusClipView issues
|
// store modal view width and remove subviews to avoid _NSKeyboardFocusClipView issues
|
||||||
@@ -46,27 +46,30 @@
|
|||||||
static const int padButtons = 12;
|
static const int padButtons = 12;
|
||||||
static const int minWidth = 320;
|
static const int minWidth = 320;
|
||||||
static const int maxWidth = 1200;
|
static const int maxWidth = 1200;
|
||||||
NSInteger prevWidth = [[NSUserDefaults standardUserDefaults] integerForKey:@"modalSheetWidth"];
|
|
||||||
|
|
||||||
NSRect cFrame = NSMakeRect(padWindow, padWindow, BETWEEN(prevWidth, minWidth, maxWidth), content.frame.size.height);
|
NSInteger prevWidth = [[NSUserDefaults standardUserDefaults] integerForKey:@"modalSheetWidth"];
|
||||||
|
if (prevWidth < minWidth) prevWidth = minWidth;
|
||||||
|
else if (prevWidth > maxWidth) prevWidth = maxWidth;
|
||||||
|
|
||||||
|
NSRect cFrame = NSMakeRect(padWindow, padWindow, prevWidth, content.frame.size.height);
|
||||||
NSRect wFrame = CGRectInset(cFrame, -padWindow, -padWindow);
|
NSRect wFrame = CGRectInset(cFrame, -padWindow, -padWindow);
|
||||||
|
|
||||||
NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
|
NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
|
||||||
ModalSheet *sheet = [[super alloc] initWithContentRect:wFrame styleMask:style backing:NSBackingStoreBuffered defer:NO];
|
ModalSheet *sheet = [[super alloc] initWithContentRect:wFrame styleMask:style backing:NSBackingStoreBuffered defer:NO];
|
||||||
|
|
||||||
// Respond buttons
|
// Respond buttons
|
||||||
NSButton *btnDone = [NSButton buttonWithTitle:NSLocalizedString(@"Done", nil) target:sheet action:@selector(didTapDoneButton:)];
|
sheet.btnDone = [NSButton buttonWithTitle:NSLocalizedString(@"Done", nil) target:sheet action:@selector(didTapDoneButton:)];
|
||||||
btnDone.keyEquivalent = @"\r"; // Enter / Return
|
sheet.btnDone.keyEquivalent = @"\r"; // Enter / Return
|
||||||
btnDone.autoresizingMask = NSViewMinXMargin | NSViewMaxYMargin;
|
sheet.btnDone.autoresizingMask = NSViewMinXMargin | NSViewMaxYMargin;
|
||||||
|
|
||||||
NSButton *btnCancel = [NSButton buttonWithTitle:NSLocalizedString(@"Cancel", nil) target:sheet action:@selector(didTapCancelButton:)];
|
NSButton *btnCancel = [NSButton buttonWithTitle:NSLocalizedString(@"Cancel", nil) target:sheet action:@selector(didTapCancelButton:)];
|
||||||
btnCancel.keyEquivalent = [NSString stringWithFormat:@"%c", 0x1b]; // ESC
|
btnCancel.keyEquivalent = [NSString stringWithFormat:@"%c", 0x1b]; // ESC
|
||||||
btnCancel.autoresizingMask = NSViewMinXMargin | NSViewMaxYMargin;
|
btnCancel.autoresizingMask = NSViewMinXMargin | NSViewMaxYMargin;
|
||||||
|
|
||||||
NSRect align = [btnDone alignmentRectForFrame:btnDone.frame];
|
NSRect align = [sheet.btnDone alignmentRectForFrame:sheet.btnDone.frame];
|
||||||
align.origin.x = wFrame.size.width - align.size.width - padWindow;
|
align.origin.x = wFrame.size.width - align.size.width - padWindow;
|
||||||
align.origin.y = padWindow;
|
align.origin.y = padWindow;
|
||||||
[btnDone setFrameOrigin:[btnDone frameForAlignmentRect:align].origin];
|
[sheet.btnDone setFrameOrigin:[sheet.btnDone frameForAlignmentRect:align].origin];
|
||||||
|
|
||||||
align.origin.x -= [btnCancel alignmentRectForFrame:btnCancel.frame].size.width + padButtons;
|
align.origin.x -= [btnCancel alignmentRectForFrame:btnCancel.frame].size.width + padButtons;
|
||||||
[btnCancel setFrameOrigin:[btnCancel frameForAlignmentRect:align].origin];
|
[btnCancel setFrameOrigin:[btnCancel frameForAlignmentRect:align].origin];
|
||||||
@@ -78,7 +81,7 @@
|
|||||||
// add all UI elements to the window view
|
// add all UI elements to the window view
|
||||||
content.frame = cFrame;
|
content.frame = cFrame;
|
||||||
[sheet.contentView addSubview:content];
|
[sheet.contentView addSubview:content];
|
||||||
[sheet.contentView addSubview:btnDone];
|
[sheet.contentView addSubview:sheet.btnDone];
|
||||||
[sheet.contentView addSubview:btnCancel];
|
[sheet.contentView addSubview:btnCancel];
|
||||||
|
|
||||||
// add respond buttons to the window height
|
// add respond buttons to the window height
|
||||||
@@ -93,58 +96,3 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - ModalFeedEdit
|
|
||||||
|
|
||||||
|
|
||||||
@implementation ModalFeedEdit
|
|
||||||
- (void)setDefaultValues {
|
|
||||||
self.url.stringValue = @"";
|
|
||||||
self.title.stringValue = @"";
|
|
||||||
self.refreshNum.intValue = 30;
|
|
||||||
[self.refreshUnit selectItemAtIndex:1];
|
|
||||||
}
|
|
||||||
- (void)setURL:(NSString*)url name:(NSString*)name refreshNum:(int32_t)num unit:(int16_t)unit {
|
|
||||||
self.url.objectValue = url;
|
|
||||||
self.title.objectValue = name;
|
|
||||||
self.refreshNum.intValue = num;
|
|
||||||
[self.refreshUnit selectItemAtIndex:BETWEEN(unit, 0, self.refreshUnit.numberOfItems - 1)];
|
|
||||||
}
|
|
||||||
+ (NSString*)stringForRefreshNum:(int32_t)num unit:(int16_t)unit {
|
|
||||||
return [NSString stringWithFormat:@"%d%c", num, [@"smhdw" characterAtIndex:(NSUInteger)BETWEEN(unit, 0, 4)]];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - ModalGroupEdit
|
|
||||||
|
|
||||||
|
|
||||||
@implementation ModalGroupEdit
|
|
||||||
- (void)setDefaultValues {
|
|
||||||
self.title.stringValue = @"New Group";
|
|
||||||
}
|
|
||||||
- (void)setGroupName:(NSString*)name {
|
|
||||||
self.title.objectValue = name;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - StrictUIntFormatter
|
|
||||||
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user