barss: URL scheme + remove 'Fix cache' button
This commit is contained in:
@@ -21,6 +21,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- Associate OPML files (double click and right click actions in Finder)
|
||||
- Quick Look preview for OPML files
|
||||
- Sandboxing & hardened runtime environment
|
||||
- Config URL scheme `barss:` with `open/preferences` and `config/fixcache`
|
||||
|
||||
### Fixed
|
||||
- *Adding feed:* Show users any 5xx server error response and extracted failure reason
|
||||
@@ -43,6 +44,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- *Adding feed:* Refresh interval hotkeys set to: `⌘1` … `⌘6`
|
||||
- *Settings, Feeds:* Single add button for feeds, groups, and separators
|
||||
- *Settings, Feeds:* Always append new items at the end
|
||||
- *Settings, General*: Moved `Fix cache` button to `About` text section
|
||||
- *Status Bar Menu*: Show `(no title)` instead of `(error)`
|
||||
- *Status Bar Menu*: `Update all feeds` will show error alerts for broken URLs
|
||||
- *UI:* Interface builder files replaced with code equivalent
|
||||
@@ -106,4 +108,4 @@ Initial release
|
||||
[0.9.3]: https://github.com/relikd/baRSS/compare/v0.9.2...v0.9.3
|
||||
[0.9.2]: https://github.com/relikd/baRSS/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/relikd/baRSS/compare/v0.9...v0.9.1
|
||||
[0.9]: https://github.com/relikd/baRSS/compare/e1f36514a8aa2d5fb9a575b6eb19adc2ce4a04d9...v0.9
|
||||
[0.9]: https://github.com/relikd/baRSS/compare/2fecf33d3101b0e7888bafee9d3b0f8b9cee30c6...v0.9
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
// SOFTWARE.
|
||||
|
||||
@import Cocoa;
|
||||
@class BarStatusItem;
|
||||
@class BarStatusItem, Preferences;
|
||||
|
||||
@interface AppHook : NSApplication <NSApplicationDelegate>
|
||||
@property (readonly, strong) BarStatusItem *statusItem;
|
||||
@property (readonly, strong) NSPersistentContainer *persistentContainer;
|
||||
|
||||
- (void)openPreferences;
|
||||
- (Preferences*)openPreferences;
|
||||
@end
|
||||
|
||||
107
baRSS/AppHook.m
107
baRSS/AppHook.m
@@ -21,6 +21,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "AppHook.h"
|
||||
#import "Constants.h"
|
||||
#import "BarStatusItem.h"
|
||||
#import "WebFeed.h"
|
||||
#import "UpdateScheduler.h"
|
||||
@@ -46,7 +47,7 @@
|
||||
RegisterImageViewNames();
|
||||
_statusItem = [BarStatusItem new];
|
||||
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
|
||||
[appleEventManager setEventHandler:self andSelector:@selector(handleGetURLEvent:withReplyEvent:)
|
||||
[appleEventManager setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:)
|
||||
forEventClass:kInternetEventClass andEventID:kAEGetURL];
|
||||
[self migrateVersionUpdate];
|
||||
}
|
||||
@@ -64,30 +65,11 @@
|
||||
[UpdateScheduler unregisterNetworkChangeNotification];
|
||||
}
|
||||
|
||||
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
// feed://https://feeds.feedburner.com/simpledesktops
|
||||
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
NSString *scheme = [[[NSURL URLWithString:url] scheme] lowercaseString];
|
||||
url = [url substringFromIndex:scheme.length + 1]; // + ':'
|
||||
if (url.length >= 2 && [[url substringToIndex:2] isEqualToString:@"//"]) {
|
||||
url = [url substringFromIndex:2];
|
||||
}
|
||||
if ([scheme isEqualToString:@"feed"]) {
|
||||
[WebFeed autoDownloadAndParseURL:url addAnyway:NO modify:nil];
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle opml file imports
|
||||
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
|
||||
NSMutableArray<NSURL*> *urls = [NSMutableArray arrayWithCapacity:filenames.count];
|
||||
for (NSString *file in filenames) {
|
||||
NSURL *u = [NSURL fileURLWithPath:file];
|
||||
if (u) [urls addObject:u];
|
||||
}
|
||||
[self openPreferences];
|
||||
SettingsFeeds *sf = [(Preferences*)(self.prefWindow.window) selectFeedsTab];
|
||||
[sf importOpmlFiles:urls];
|
||||
[sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
|
||||
/// Called during application start. Perform any version dependent updates here
|
||||
- (void)migrateVersionUpdate {
|
||||
// Currently unused, but you'll be thankful to know the previous version number in the future
|
||||
[UserPrefs dbUpdateFileVersion];
|
||||
[UserPrefs dbUpdateAppVersion];
|
||||
}
|
||||
|
||||
|
||||
@@ -95,13 +77,14 @@
|
||||
|
||||
|
||||
/// Called whenever the user activates the preferences (either through menu click or hotkey).
|
||||
- (void)openPreferences {
|
||||
- (Preferences*)openPreferences {
|
||||
if (!self.prefWindow) {
|
||||
self.prefWindow = [[NSWindowController alloc] initWithWindow:[Preferences window]];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferencesClosed:) name:NSWindowWillCloseNotification object:self.prefWindow.window];
|
||||
}
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[self.prefWindow showWindow:nil];
|
||||
return (Preferences*)self.prefWindow.window;
|
||||
}
|
||||
|
||||
/// Callback method after user closes the preferences window.
|
||||
@@ -116,8 +99,7 @@
|
||||
if (self.prefWindow) {
|
||||
CGPoint screenPoint = self.prefWindow.window.frame.origin;
|
||||
[self.prefWindow close];
|
||||
[self openPreferences];
|
||||
[self.prefWindow.window setFrameOrigin:screenPoint];
|
||||
[[self openPreferences] setFrameOrigin:screenPoint];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,11 +166,64 @@
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
/// Called during application start. Perform any version dependent updates here
|
||||
- (void)migrateVersionUpdate {
|
||||
// Currently unused, but you'll be thankful to know the previous version number in the future
|
||||
[UserPrefs dbUpdateFileVersion];
|
||||
[UserPrefs dbUpdateAppVersion];
|
||||
|
||||
#pragma mark - Application Input (URLs and Files)
|
||||
|
||||
|
||||
/**
|
||||
Callback method fired on opml file import
|
||||
*/
|
||||
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
|
||||
NSMutableArray<NSURL*> *urls = [NSMutableArray arrayWithCapacity:filenames.count];
|
||||
for (NSString *file in filenames) {
|
||||
NSURL *u = [NSURL fileURLWithPath:file];
|
||||
if (u) [urls addObject:u];
|
||||
}
|
||||
SettingsFeeds *sf = [[self openPreferences] selectTab:1];
|
||||
[sf importOpmlFiles:urls];
|
||||
[sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
|
||||
}
|
||||
|
||||
/**
|
||||
Callback method fired when opened with an URL (@c feed: and @c barss: scheme)
|
||||
*/
|
||||
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
NSString *scheme = [[[NSURL URLWithString:url] scheme] lowercaseString];
|
||||
url = [url substringFromIndex:scheme.length + 1]; // + ':'
|
||||
if (url.length >= 2 && [[url substringToIndex:2] isEqualToString:@"//"]) {
|
||||
url = [url substringFromIndex:2];
|
||||
}
|
||||
if ([scheme isEqualToString:kURLSchemeFeed]) {
|
||||
[WebFeed autoDownloadAndParseURL:url addAnyway:NO modify:nil];
|
||||
} else if ([scheme isEqualToString:kURLSchemeBarss]) {
|
||||
NSMutableArray<NSString*> *comp = [[url pathComponents] mutableCopy];
|
||||
NSString *action = comp.firstObject;
|
||||
if (action) {
|
||||
[comp removeObjectAtIndex:0];
|
||||
[self handleConfigURLScheme:action parameters:comp];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Helper method for handling the @c barss: scheme (see below).
|
||||
@textblock
|
||||
barss:open/preferences[/0-4]
|
||||
barss:config/fixcache[/silent]
|
||||
@/textblock
|
||||
*/
|
||||
- (void)handleConfigURLScheme:(const NSString*)action parameters:(NSArray<NSString*>*)params {
|
||||
if ([action isEqualToString:kURLActionOpen]) {
|
||||
if ([params.firstObject isEqualToString:kURLParamPreferences]) {
|
||||
NSDecimalNumber *num = [NSDecimalNumber decimalNumberWithString:params.lastObject];
|
||||
[[self openPreferences] selectTab:num.unsignedIntegerValue];
|
||||
}
|
||||
} else if ([action isEqualToString:kURLActionConfig]) {
|
||||
if ([params.firstObject isEqualToString:kURLParamFixCache]) {
|
||||
[StoreCoordinator cleanupAndShowAlert:![params.lastObject isEqualToString:kURLParamSilent]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,12 +259,6 @@ static NSEventModifierFlags fnKeyFlags = NSEventModifierFlagShift | NSEventModif
|
||||
return;
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// if (key == NSEnterCharacter || key == NSCarriageReturnCharacter) {
|
||||
// if ([self sendAction:@selector(enterPressed:) to:nil from:self])
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
[super sendEvent:event];
|
||||
|
||||
@@ -104,6 +104,26 @@ static NSNotificationName const kNotificationTotalUnreadCountChanged = @"baRSS-n
|
||||
static NSNotificationName const kNotificationTotalUnreadCountReset = @"baRSS-notification-total-unread-count-reset";
|
||||
|
||||
|
||||
#pragma mark - URL Scheme constants
|
||||
|
||||
/// @c feed: URL scheme. Used for feed subscriptions.
|
||||
/// @note E.g., @c feed://https://feeds.feedburner.com/simpledesktops
|
||||
static NSString* const kURLSchemeFeed = @"feed";
|
||||
/// @c barss: URL scheme. Used for configuring the app.
|
||||
/// @note E.g., @c barss://open/preferences
|
||||
static NSString* const kURLSchemeBarss = @"barss";
|
||||
/// Use @c barss:open to display information
|
||||
static NSString* const kURLActionOpen = @"open";
|
||||
/// Use @c barss:config to perform configuration steps
|
||||
static NSString* const kURLActionConfig = @"config";
|
||||
/// Open preferences window with optional tab index. E.g., @c barss:open/preferences/1
|
||||
static NSString* const kURLParamPreferences = @"preferences";
|
||||
/// Run core data cleanup with optional silent parameter. E.g., @c barss:config/fixcache/silent
|
||||
static NSString* const kURLParamFixCache = @"fixcache";
|
||||
/// Disables error alerts and other interactive UI
|
||||
static NSString* const kURLParamSilent = @"silent";
|
||||
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,5 @@ static int const dbFileVersion = 1; // update in case database structure changes
|
||||
+ (NSArray<FeedArticle*>*)articlesAtPath:(nullable NSString*)path isFeed:(BOOL)feedFlag sorted:(BOOL)sortFlag unread:(BOOL)readFlag inContext:(NSManagedObjectContext*)moc limit:(NSUInteger)limit;
|
||||
|
||||
// Restore sound state
|
||||
+ (void)restoreFeedIndexPaths;
|
||||
+ (NSUInteger)deleteUnreferenced;
|
||||
+ (NSUInteger)deleteAllGroups;
|
||||
+ (void)cleanupAndShowAlert:(BOOL)flag;
|
||||
@end
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "StoreCoordinator.h"
|
||||
#import "Constants.h"
|
||||
#import "NSFetchRequest+Ext.h"
|
||||
#import "AppHook.h"
|
||||
#import "Feed+Ext.h"
|
||||
@@ -224,6 +225,19 @@
|
||||
|
||||
#pragma mark - Restore Sound State
|
||||
|
||||
+ (void)cleanupAndShowAlert:(BOOL)flag {
|
||||
NSUInteger deleted = [self deleteUnreferenced];
|
||||
[self restoreFeedIndexPaths];
|
||||
PostNotification(kNotificationTotalUnreadCountReset, nil);
|
||||
if (flag) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = NSLocalizedString(@"Database cleanup successful", nil);
|
||||
alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"Removed %lu unreferenced database entries.", nil), deleted];
|
||||
alert.alertStyle = NSAlertStyleInformational;
|
||||
[alert runModal];
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all @c Feed and re-calculate @c indexPath.
|
||||
+ (void)restoreFeedIndexPaths {
|
||||
NSManagedObjectContext *moc = [self getMainContext];
|
||||
@@ -252,13 +266,13 @@
|
||||
}
|
||||
|
||||
/// Delete all @c FeedGroup items.
|
||||
+ (NSUInteger)deleteAllGroups {
|
||||
NSManagedObjectContext *moc = [self getMainContext];
|
||||
NSUInteger deleted = [self batchDelete:FeedGroup.entity nullAttribute:nil inContext:moc];
|
||||
[self saveContext:moc andParent:YES];
|
||||
[moc reset];
|
||||
return deleted;
|
||||
}
|
||||
//+ (NSUInteger)deleteAllGroups {
|
||||
// NSManagedObjectContext *moc = [self getMainContext];
|
||||
// NSUInteger deleted = [self batchDelete:FeedGroup.entity nullAttribute:nil inContext:moc];
|
||||
// [self saveContext:moc andParent:YES];
|
||||
// [moc reset];
|
||||
// return deleted;
|
||||
//}
|
||||
|
||||
/**
|
||||
Perform batch delete on entities of type @c entity where @c column @c IS @c NULL. If @c column is @c nil, delete all rows.
|
||||
|
||||
@@ -58,9 +58,19 @@
|
||||
<string>feed</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>de.relikd.baRSS.url.config</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>barss</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>11016</string>
|
||||
<string>11155</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.news</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
[mas beginEditing];
|
||||
[self str:mas add:@"Programming\n" bold:YES];
|
||||
[self str:mas add:@"Oleg Geier\n\n" bold:NO];
|
||||
[self str:mas add:@"Source Code available\n" bold:YES];
|
||||
[self str:mas add:@"Source Code Available\n" bold:YES];
|
||||
[self str:mas add:@"github.com" link:@"https://github.com/relikd/baRSS"];
|
||||
[self str:mas add:@" (MIT License)\nor " bold:NO];
|
||||
[self str:mas add:@"gitlab.com" link:@"https://gitlab.com/relikd/baRSS"];
|
||||
@@ -68,6 +68,8 @@
|
||||
[self str:mas add:@"3rd-Party Libraries\n" bold:YES];
|
||||
[self str:mas add:@"RSXML" link:@"https://github.com/relikd/RSXML"];
|
||||
[self str:mas add:@" (MIT License)" bold:NO];
|
||||
[self str:mas add:@"\n\n\n\nOptions\n" bold:YES];
|
||||
[self str:mas add:@"Fix Cache" link:@"barss:config/fixcache"];
|
||||
[mas endEditing];
|
||||
return mas;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
// NSInteger ins = [[[moc.insertedObjects filteredSetUsingPredicate:pred] valueForKeyPath:@"@sum.unread"] integerValue];
|
||||
// NSLog(@"%ld, %ld", del, ins);
|
||||
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil];
|
||||
PostNotification(kNotificationTotalUnreadCountReset, nil);
|
||||
[self.dataStore rearrangeObjects]; // update ordering
|
||||
[UpdateScheduler scheduleNextFeed];
|
||||
}
|
||||
@@ -228,7 +228,7 @@
|
||||
[self restoreOrderingAndIndexPathStr:parentNodes];
|
||||
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
|
||||
[UpdateScheduler scheduleNextFeed];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil];
|
||||
PostNotification(kNotificationTotalUnreadCountReset, nil);
|
||||
}
|
||||
|
||||
- (void)openImportDialog {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
@import Cocoa;
|
||||
|
||||
@interface SettingsGeneral : NSViewController
|
||||
- (void)fixCache:(NSButton *)sender;
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender;
|
||||
- (void)changeDefaultRSSReader:(NSPopUpButton *)sender;
|
||||
@end
|
||||
|
||||
@@ -46,17 +46,6 @@
|
||||
|
||||
#pragma mark - UI interaction with IBAction
|
||||
|
||||
- (void)fixCache:(NSButton *)sender {
|
||||
NSUInteger deleted = [StoreCoordinator deleteUnreferenced];
|
||||
[StoreCoordinator restoreFeedIndexPaths];
|
||||
PostNotification(kNotificationTotalUnreadCountReset, nil);
|
||||
// show only if >0, but hey, this button will vanish anyway ...
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = [NSString stringWithFormat:@"Removed %lu unreferenced core data entries.", deleted];
|
||||
alert.alertStyle = NSAlertStyleInformational;
|
||||
[alert runModal];
|
||||
}
|
||||
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender {
|
||||
[UserPrefs setHttpApplication:sender.selectedItem.representedObject];
|
||||
}
|
||||
@@ -101,22 +90,12 @@
|
||||
@return Application name such as 'Safari' or 'baRSS'
|
||||
*/
|
||||
- (NSString*)applicationNameForBundleId:(NSString*)bundleID {
|
||||
CFStringRef bundleIDRef = CFBridgingRetain(bundleID);
|
||||
if (!bundleIDRef)
|
||||
return nil;
|
||||
CFArrayRef arr = LSCopyApplicationURLsForBundleIdentifier(bundleIDRef, NULL);
|
||||
CFRelease(bundleIDRef);
|
||||
if (!arr)
|
||||
return nil;
|
||||
CFDictionaryRef infoDict = NULL;
|
||||
if (CFArrayGetCount(arr) > 0)
|
||||
infoDict = CFBundleCopyInfoDictionaryForURL(CFArrayGetValueAtIndex(arr, 0));
|
||||
CFRelease(arr);
|
||||
if (!infoDict)
|
||||
return nil;
|
||||
NSString *name = CFDictionaryGetValue(infoDict, kCFBundleNameKey);
|
||||
CFRelease(infoDict);
|
||||
return name;
|
||||
NSArray<NSURL*> *urls = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier((__bridge CFStringRef)bundleID, NULL));
|
||||
if (urls.count > 0) {
|
||||
NSDictionary *info = CFBridgingRelease(CFBundleCopyInfoDictionaryForURL((CFURLRef)urls.firstObject));
|
||||
return info[(NSString*)kCFBundleNameKey];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,12 +105,7 @@
|
||||
@return Array of @c bundleIDs of installed applications supporting that url scheme.
|
||||
*/
|
||||
- (NSArray<NSString*>*)listOfBundleIdsForScheme:(NSString*)scheme {
|
||||
CFStringRef schemeRef = CFBridgingRetain(scheme);
|
||||
if (!schemeRef)
|
||||
return nil;
|
||||
CFArrayRef allHandlers = LSCopyAllHandlersForURLScheme(schemeRef);
|
||||
CFRelease(schemeRef);
|
||||
return (NSArray*)CFBridgingRelease(allHandlers);
|
||||
return CFBridgingRelease(LSCopyAllHandlersForURLScheme((__bridge CFStringRef _Nonnull)(scheme)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,12 +115,7 @@
|
||||
@return @c bundleID of default application
|
||||
*/
|
||||
- (NSString*)defaultBundleIdForScheme:(NSString*)scheme {
|
||||
CFStringRef schemeRef = CFBridgingRetain(scheme);
|
||||
if (!schemeRef)
|
||||
return nil;
|
||||
CFStringRef defaultHandler = LSCopyDefaultHandlerForURLScheme(schemeRef);
|
||||
CFRelease(schemeRef);
|
||||
return (NSString*)CFBridgingRelease(defaultHandler);
|
||||
return CFBridgingRelease(LSCopyDefaultHandlerForURLScheme((__bridge CFStringRef _Nonnull)(scheme)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,18 +126,12 @@
|
||||
*/
|
||||
- (BOOL)setDefaultRSSApplication:(NSString*)bundleID {
|
||||
// TODO: Does not work with sandboxing.
|
||||
CFStringRef bundleIDRef = CFBridgingRetain(bundleID);
|
||||
if (!bundleIDRef)
|
||||
return NO;
|
||||
CFStringRef schemeRef = CFBridgingRetain(@"feed");
|
||||
if (!schemeRef) {
|
||||
CFRelease(bundleIDRef);
|
||||
return NO;
|
||||
}
|
||||
OSStatus s = LSSetDefaultHandlerForURLScheme(schemeRef, bundleIDRef);
|
||||
CFRelease(schemeRef);
|
||||
CFRelease(bundleIDRef);
|
||||
OSStatus s = LSSetDefaultHandlerForURLScheme(CFSTR("feed"), (__bridge CFStringRef _Nonnull)(bundleID));
|
||||
return s == 0;
|
||||
}
|
||||
|
||||
// Rebuild Launch Services cache
|
||||
// https://eclecticlight.co/2017/08/11/launch-services-database-problems-correcting-and-rebuilding/
|
||||
// /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -v -apps u
|
||||
|
||||
@end
|
||||
|
||||
@@ -36,10 +36,6 @@
|
||||
|
||||
self.popupHttpApplication = [[self createPopup:x top: PAD_WIN + 1] action:@selector(changeHttpApplication:) target:controller];
|
||||
self.popupDefaultRSSReader = [[self createPopup:x top: YFromTop(self.popupHttpApplication) + PAD_M] action:@selector(changeDefaultRSSReader:) target:controller];
|
||||
|
||||
// Add fix cache button
|
||||
[[[[NSView button:NSLocalizedString(@"Fix Cache", nil)] action:@selector(fixCache:) target:controller]
|
||||
tooltip:NSLocalizedString(@"Will remove unreferenced feed entries", nil)] placeIn:self xRight:PAD_WIN y:PAD_WIN];
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// User Preferences Plist
|
||||
+ (BOOL)defaultYES:(NSString*)key;
|
||||
+ (BOOL)defaultNO:(NSString*)key;
|
||||
+ (NSUInteger)defaultUInt:(NSUInteger)defaultInt forKey:(NSString*)key;
|
||||
|
||||
+ (NSString*)getHttpApplication;
|
||||
+ (void)setHttpApplication:(NSString*)bundleID;
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
}
|
||||
|
||||
/// @return Return @c defaultInt if key is not set. Otherwise, return user defaults property from plist.
|
||||
+ (NSInteger)defaultInt:(NSInteger)defaultInt forKey:(NSString*)key {
|
||||
+ (NSUInteger)defaultUInt:(NSUInteger)defaultInt forKey:(NSString*)key {
|
||||
NSInteger ret = [[NSUserDefaults standardUserDefaults] integerForKey:key];
|
||||
if (ret > 0) return ret;
|
||||
if (ret > 0) return (NSUInteger)ret;
|
||||
return defaultInt;
|
||||
}
|
||||
|
||||
@@ -74,21 +74,15 @@
|
||||
|
||||
/// @return The limit on how many links should be opened at the same time, if user holds the option key.
|
||||
/// Default: @c 10
|
||||
+ (NSUInteger)openFewLinksLimit {
|
||||
return (NSUInteger)[self defaultInt:10 forKey:@"openFewLinksLimit"];
|
||||
}
|
||||
+ (NSUInteger)openFewLinksLimit { return [self defaultUInt:10 forKey:@"openFewLinksLimit"]; }
|
||||
|
||||
/// @return The limit on when to truncate article titles (Short names setting must be active).
|
||||
/// Default: @c 60
|
||||
+ (NSUInteger)shortArticleNamesLimit {
|
||||
return (NSUInteger)[self defaultInt:60 forKey:@"shortArticleNamesLimit"];
|
||||
}
|
||||
+ (NSUInteger)shortArticleNamesLimit { return [self defaultUInt:60 forKey:@"shortArticleNamesLimit"]; }
|
||||
|
||||
/// @return The maximum number of articles displayed per feed (Limit articles setting must be active).
|
||||
/// Default: @c 40
|
||||
+ (NSUInteger)articlesInMenuLimit {
|
||||
return (NSUInteger)[self defaultInt:40 forKey:@"articlesInMenuLimit"];
|
||||
}
|
||||
+ (NSUInteger)articlesInMenuLimit { return [self defaultUInt:40 forKey:@"articlesInMenuLimit"]; }
|
||||
|
||||
|
||||
#pragma mark - Application Info Plist
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
|
||||
@interface Preferences : NSWindow <NSWindowDelegate>
|
||||
+ (instancetype)window;
|
||||
- (SettingsFeeds*)selectFeedsTab;
|
||||
- (__kindof NSViewController*)selectTab:(NSUInteger)index;
|
||||
@end
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "SettingsFeeds.h"
|
||||
#import "SettingsAppearance.h"
|
||||
#import "SettingsAbout.h"
|
||||
#import "UserPrefs.h"
|
||||
|
||||
/// Managing individual tabs in application preferences
|
||||
@interface PrefTabs : NSTabViewController
|
||||
@@ -48,8 +49,7 @@
|
||||
flexibleWidth,
|
||||
TabItem(NSImageNameInfo, NSLocalizedString(@"About", nil), [SettingsAbout class]),
|
||||
];
|
||||
|
||||
[self switchToTab:[[NSUserDefaults standardUserDefaults] integerForKey:@"preferencesTab"]];
|
||||
[self switchToTab:[UserPrefs defaultUInt:0 forKey:@"preferencesTab"]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -63,9 +63,14 @@ NS_INLINE NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class cl
|
||||
}
|
||||
|
||||
/// Safely set selected index without out of bounds exception
|
||||
- (void)switchToTab:(NSInteger)index {
|
||||
if (index > 0 || (NSUInteger)index < self.tabViewItems.count)
|
||||
self.selectedTabViewItemIndex = index;
|
||||
- (__kindof NSViewController*)switchToTab:(NSUInteger)index {
|
||||
if (index < 0 || index >= self.tabViewItems.count)
|
||||
return nil;
|
||||
NSTabViewItem *tab = self.tabViewItems[index];
|
||||
if (tab.identifier == NSToolbarFlexibleSpaceItemIdentifier)
|
||||
return nil;
|
||||
self.selectedTabViewItemIndex = (NSInteger)index;
|
||||
return [tab viewController];
|
||||
}
|
||||
|
||||
/// Delegate method, store last selected tab to user preferences
|
||||
@@ -100,10 +105,9 @@ NS_INLINE NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class cl
|
||||
return w;
|
||||
}
|
||||
|
||||
- (SettingsFeeds*)selectFeedsTab {
|
||||
PrefTabs *pref = (PrefTabs*)self.contentViewController;
|
||||
[pref switchToTab:1];
|
||||
return (SettingsFeeds*)[pref.tabViewItems[1] viewController];
|
||||
/// Selects tab (if not flexible space or out of bounds) and returns associated view controller
|
||||
- (__kindof NSViewController*)selectTab:(NSUInteger)index {
|
||||
return [(PrefTabs*)self.contentViewController switchToTab:index];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
|
||||
Reference in New Issue
Block a user