Refactoring UserPrefs

This commit is contained in:
relikd
2019-10-03 12:13:38 +02:00
parent b25565c74f
commit ae18e93b6a
21 changed files with 223 additions and 255 deletions

View File

@@ -123,7 +123,7 @@ defaults write de.relikd.baRSS articlesInMenuLimit -int 40
5. You can change the appearance of colors throughout the application. E.g., The tint color of the menu bar icon and the color of the blue dot of unread articles.
```
defaults write de.relikd.baRSS colorStatusIconTint -string "#37F"
defaults write de.relikd.baRSS colorUnreadTickMark -string "#FBA33A"
defaults write de.relikd.baRSS colorUnreadIndicator -string "#FBA33A"
```

View File

@@ -407,10 +407,10 @@
54E9CF2F225913850023696F /* Helper */ = {
isa = PBXGroup;
children = (
54209E922117325100F3B5EF /* DrawImage.h */,
54209E932117325100F3B5EF /* DrawImage.m */,
5496B50F214D6275003ED4ED /* UserPrefs.h */,
5496B510214D6275003ED4ED /* UserPrefs.m */,
54209E922117325100F3B5EF /* DrawImage.h */,
54209E932117325100F3B5EF /* DrawImage.m */,
54910065233A4D4000858AE2 /* URLScheme.h */,
54910066233A4D4000858AE2 /* URLScheme.m */,
);
@@ -655,10 +655,7 @@
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -750,6 +747,10 @@
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"APP_NAME=\"\\@\\\"$(PRODUCT_NAME)\\\"\"",
"$(inherited)",
);
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
@@ -801,6 +802,10 @@
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"APP_NAME=\"\\@\\\"$(PRODUCT_NAME)\\\"\"",
"$(inherited)",
);
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;

View File

@@ -45,6 +45,7 @@
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
UserPrefsInit();
RegisterImageViewNames();
_statusItem = [BarStatusItem new];
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
@@ -70,11 +71,10 @@
[UpdateScheduler unregisterNetworkChangeNotification];
}
/// Called during application start. Perform any version dependent updates here
/// Called during application start. Perform any version migration updates here.
- (void)migrateVersionUpdate {
// Currently unused, but you'll be thankful to know the previous version number in the future
[UserPrefs dbUpdateFileVersion];
[UserPrefs dbUpdateAppVersion];
// Currently unused, but you'll be thankful in the future for a previously saved version number
[StoreCoordinator setOption:@"app-version" value: UserPrefsAppVersion()];
}

View File

@@ -64,6 +64,7 @@ static NSImageName const RSSImageMenuItemUnread = @"RSSImageMenuItemUnread";
/// Helper method calls @c (defaultCenter)postNotification:
static inline void PostNotification(NSNotificationName name, id obj) { [[NSNotificationCenter defaultCenter] postNotificationName:name object:obj]; }
/// Helper method calls @c (defaultCenter)addObserver:
static inline void RegisterNotification(NSNotificationName name, SEL action, id observer) { [[NSNotificationCenter defaultCenter] addObserver:observer selector:action name:name object:nil]; }
/**
@c notification.object is @c NSNumber of type @c NSUInteger.

View File

@@ -63,7 +63,7 @@
+ (void)didClickOnMenuItem:(NSMenuItem*)sender {
NSString *url = [StoreCoordinator urlForFeedWithIndexPath:sender.representedObject];
if (url && url.length > 0)
[UserPrefs openURLsWithPreferredBrowser:@[[NSURL URLWithString:url]]];
UserPrefsOpenURL(url);
}

View File

@@ -52,20 +52,20 @@
NSString *title = self.title;
if (!title) return @"";
// TODO: It should be enough to get user prefs once per menu build
if ([UserPrefs defaultNO:@"feedShortNames"]) {
NSUInteger limit = [UserPrefs shortArticleNamesLimit];
if (UserPrefsBool(Pref_feedTruncateTitle)) {
NSUInteger limit = UserPrefsUInt(Pref_shortArticleNamesLimit);
if (title.length > limit)
title = [NSString stringWithFormat:@"%@…", [title substringToIndex:limit-1]];
title = [[title substringToIndex:limit] stringByAppendingString:@"…"];
}
return title;
}
/// @return Fully initialized @c NSMenuItem with @c title, @c tooltip, @c tickmark, and @c action.
/// @return Fully initialized @c NSMenuItem with @c title, @c tooltip, @c unread-indicator, and @c action.
- (NSMenuItem*)newMenuItem {
NSMenuItem *item = [NSMenuItem new];
item.title = [self shortArticleName];
item.enabled = (self.link.length > 0);
item.state = (self.unread && [UserPrefs defaultYES:@"feedTickMark"] ? NSControlStateValueOn : NSControlStateValueOff);
item.state = (self.unread && UserPrefsBool(Pref_feedUnreadIndicator) ? NSControlStateValueOn : NSControlStateValueOff);
item.onStateImage = [NSImage imageNamed:RSSImageMenuItemUnread];
item.accessibilityLabel = (self.unread ? NSLocalizedString(@"article: unread", @"accessibility label, feed menu item") : NSLocalizedString(@"article: read", @"accessibility label, feed menu item"));
item.toolTip = (self.abstract ? self.abstract : self.body); // fall back to body (html)
@@ -83,7 +83,7 @@
NSString *url = fa.link;
BOOL success = NO;
if (url && url.length > 0 && !flipUnread) // flipUnread == change unread state
success = [UserPrefs openURLsWithPreferredBrowser:@[[NSURL URLWithString:url]]];
success = UserPrefsOpenURL(url);
if (flipUnread || (success && fa.unread)) {
fa.unread = !fa.unread;
[StoreCoordinator saveContext:moc andParent:YES];

View File

@@ -23,8 +23,6 @@
@import Cocoa;
#import "DBv1+CoreDataModel.h"
static int const dbFileVersion = 1; // update in case database structure changes
@interface StoreCoordinator : NSObject
// Managing contexts
+ (NSManagedObjectContext*)getMainContext;

View File

@@ -320,8 +320,8 @@ void RegisterImageViewNames(void) {
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; });
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; });
NSColor *c1 = [UserPrefs defaultColor:orange forKey:@"colorStatusIconTint"];
NSColor *c2 = [UserPrefs defaultColor:[NSColor systemBlueColor] forKey:@"colorUnreadTickMark"];
NSColor *c1 = UserPrefsColor(Pref_colorStatusIconTint, orange);
NSColor *c2 = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor systemBlueColor]);
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, YES); return YES; });
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, NO); return YES; });

View File

@@ -20,33 +20,91 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef UserPrefs_h
#define UserPrefs_h
@import Cocoa;
@interface UserPrefs : NSObject
// User Preferences Plist
+ (BOOL)defaultYES:(NSString*)key;
+ (BOOL)defaultNO:(NSString*)key;
+ (NSUInteger)defaultUInt:(NSUInteger)defaultInt forKey:(NSString*)key;
// ---------------------------------------------------------------
// | MARK: Constants
// ---------------------------------------------------------------
+ (NSString*)getHttpApplication;
+ (void)setHttpApplication:(NSString*)bundleID;
+ (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls;
// ------ Preferences window ------
/** default: @c 1 */ static NSString* const Pref_prefSelectedTab = @"prefSelectedTab";
/** default: @c nil */ static NSString* const Pref_prefWindowFrame = @"prefWindowFrame";
/** default: @c nil */ static NSString* const Pref_modalSheetWidth = @"modalSheetWidth";
// ------ General settings ------ (Preferences > General Tab) ------
/** default: @c nil */ static NSString* const Pref_defaultHttpApplication = @"defaultHttpApplication";
// ------ Appearance matrix ------ (Preferences > Appearance Tab) ------
/** default: @c YES */ static NSString* const Pref_globalTintMenuIcon = @"globalTintMenuBarIcon";
/** default: @c YES */ static NSString* const Pref_globalUpdateAll = @"globalUpdateAll";
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
/** default: @c YES */ static NSString* const Pref_globalMarkRead = @"globalMarkRead";
/** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread";
/** default: @c YES */ static NSString* const Pref_globalUnreadCount = @"globalUnreadCount";
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
/** default: @c YES */ static NSString* const Pref_groupMarkRead = @"groupMarkRead";
/** default: @c YES */ static NSString* const Pref_groupMarkUnread = @"groupMarkUnread";
/** default: @c YES */ static NSString* const Pref_groupUnreadCount = @"groupUnreadCount";
/** default: @c YES */ static NSString* const Pref_feedOpenUnread = @"feedOpenUnread";
/** default: @c YES */ static NSString* const Pref_feedMarkRead = @"feedMarkRead";
/** default: @c YES */ static NSString* const Pref_feedMarkUnread = @"feedMarkUnread";
/** default: @c YES */ static NSString* const Pref_feedUnreadCount = @"feedUnreadCount";
/** default: @c YES */ static NSString* const Pref_feedUnreadIndicator = @"feedUnreadIndicator";
/** default: @c NO */ static NSString* const Pref_feedTruncateTitle = @"feedTruncateTitle";
/** default: @c NO */ static NSString* const Pref_feedLimitArticles = @"feedLimitArticles";
// ------ Hidden preferences ------ only modifiable via `defaults write de.relikd.baRSS {KEY}` ------
/** default: @c 10 */ static NSString* const Pref_openFewLinksLimit = @"openFewLinksLimit";
/** default: @c 60 */ static NSString* const Pref_shortArticleNamesLimit = @"shortArticleNamesLimit";
/** default: @c 40 */ static NSString* const Pref_articlesInMenuLimit = @"articlesInMenuLimit";
/** default: @c nil */ static NSString* const Pref_colorStatusIconTint = @"colorStatusIconTint";
/** default: @c nil */ static NSString* const Pref_colorUnreadIndicator = @"colorUnreadIndicator";
// Hidden Plist Properties
+ (NSUInteger)openFewLinksLimit; // Change with: defaults write de.relikd.baRSS openFewLinksLimit -int 10
+ (NSUInteger)shortArticleNamesLimit; // Change with: defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
+ (NSUInteger)articlesInMenuLimit; // Change with: defaults write de.relikd.baRSS articlesInMenuLimit -int 40
+ (NSColor*)defaultColor:(NSColor*)defaultColor forKey:(NSString*)key; // Change with: defaults write de.relikd.baRSS {KEY} -string "#FBA33A"
// Application Info Plist
+ (NSString*)appName;
+ (NSString*)appVersion;
+ (NSString*)appVersionWithBuildNo;
// ---------------------------------------------------------------
// | MARK: - NSUserDefaults
// ---------------------------------------------------------------
// Core Data Properties
+ (BOOL)dbIsUnusedInitalState;
+ (BOOL)dbIsCurrentFileVersion;
+ (BOOL)dbIsCurrentAppVersion;
+ (void)dbUpdateFileVersion;
+ (void)dbUpdateAppVersion;
@end
void UserPrefsInit(void);
NSColor* UserPrefsColor(NSString *key, NSColor *defaultColor); // Change with: defaults write de.relikd.baRSS {KEY} -string "#FBA33A"
// ------ Getter ------
/// Helper method calls @c (standardUserDefaults)boolForKey:
static inline BOOL UserPrefsBool(NSString* const key) { return [[NSUserDefaults standardUserDefaults] boolForKey:key]; }
/// Helper method calls @c (standardUserDefaults)integerForKey:
static inline NSInteger UserPrefsInt(NSString* const key) { return [[NSUserDefaults standardUserDefaults] integerForKey:key]; }
/// Helper method calls @c (standardUserDefaults)integerForKey: @return @c (NSUInteger)result
static inline NSUInteger UserPrefsUInt(NSString* const key) { return (NSUInteger)[[NSUserDefaults standardUserDefaults] integerForKey:key]; }
/// Helper method calls @c (standardUserDefaults)stringForKey:
static inline NSString* UserPrefsString(NSString* const key) { return [[NSUserDefaults standardUserDefaults] stringForKey:key]; }
// ------ Setter ------
/// Helper method calls @c (standardUserDefaults)setObject:forKey:
static inline void UserPrefsSet(NSString* const key, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; }
/// Helper method calls @c (standardUserDefaults)setInteger:forKey:
static inline void UserPrefsSetInt(NSString* const key, NSInteger value) { [[NSUserDefaults standardUserDefaults] setInteger:value forKey:key]; }
/// Helper method calls @c (standardUserDefaults)setBool:forKey:
static inline void UserPrefsSetBool(NSString* const key, BOOL value) { [[NSUserDefaults standardUserDefaults] setBool:value forKey:key]; }
// ---------------------------------------------------------------
// | MARK: - NSBundle
// ---------------------------------------------------------------
/// Helper method calls @c (mainBundle)CFBundleShortVersionString
static inline NSString* UserPrefsAppVersion() { return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; }
// ---------------------------------------------------------------
// | MARK: - Open URLs
// ---------------------------------------------------------------
/**
Open web links in default browser or a browser the user selected in the preferences.
@param urls A list of @c NSURL objects that will be opened immediatelly in bulk.
@return @c YES if @c urls are opened successfully. @c NO on error.
*/
static inline BOOL UserPrefsOpenURLs(NSArray<NSURL*> *urls) {
return [[NSWorkspace sharedWorkspace] openURLs:urls withAppBundleIdentifier:UserPrefsString(Pref_defaultHttpApplication) options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifiers:nil];
}
/// Call @c UserPrefsOpenURLs() with single item array and convert string to @c NSURL
static inline BOOL UserPrefsOpenURL(NSString *url) { return UserPrefsOpenURLs(@[[NSURL URLWithString:url]]); }
#endif /* UserPrefs_h */

View File

@@ -21,72 +21,36 @@
// SOFTWARE.
#import "UserPrefs.h"
#import "StoreCoordinator.h"
#import "NSString+Ext.h"
#import "NSString+Ext.h" // hexColor
@implementation UserPrefs
#pragma mark - User Preferences Plist
/// @return @c YES if key is not set. Otherwise, return user defaults property from plist.
+ (BOOL)defaultYES:(NSString*)key {
if ([[NSUserDefaults standardUserDefaults] objectForKey:key] == NULL) {
return YES;
}
return [[NSUserDefaults standardUserDefaults] boolForKey:key];
/// Helper method for @c UserPrefsInit()
static inline void defaultsAppend(NSMutableDictionary *defs, id value, NSArray<NSString*>* keys) {
for (NSString *key in keys)
[defs setObject:value forKey:key];
}
/// @return @c NO if key is not set. Otherwise, return user defaults property from plist.
+ (BOOL)defaultNO:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] boolForKey:key];
/// Helper method calls @c (standardUserDefaults)registerDefaults:
void UserPrefsInit(void) {
NSMutableDictionary *defs = [NSMutableDictionary dictionary];
defaultsAppend(defs, @YES, @[Pref_globalTintMenuIcon,
Pref_globalUpdateAll,
Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread,
Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead,
Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread,
Pref_globalUnreadCount, Pref_groupUnreadCount, Pref_feedUnreadCount,
Pref_feedUnreadIndicator]);
defaultsAppend(defs, @NO, @[Pref_feedTruncateTitle,
Pref_feedLimitArticles]);
// Display limits & truncation ( defaults write de.relikd.baRSS {KEY} -int 10 )
[defs setObject:[NSNumber numberWithUnsignedInteger:10] forKey:Pref_openFewLinksLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:60] forKey:Pref_shortArticleNamesLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:40] forKey:Pref_articlesInMenuLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:1] forKey:Pref_prefSelectedTab]; // feed tab
[[NSUserDefaults standardUserDefaults] registerDefaults:defs];
}
/// @return Return @c defaultInt if key is not set. Otherwise, return user defaults property from plist.
+ (NSUInteger)defaultUInt:(NSUInteger)defaultInt forKey:(NSString*)key {
NSInteger ret = [[NSUserDefaults standardUserDefaults] integerForKey:key];
if (ret > 0) return (NSUInteger)ret;
return defaultInt;
}
/// @return User configured custom browser. Or @c nil if not set yet. (which will fallback to default browser)
+ (NSString*)getHttpApplication {
return [[NSUserDefaults standardUserDefaults] stringForKey:@"defaultHttpApplication"];
}
/// Store custom browser bundle id to user defaults.
+ (void)setHttpApplication:(NSString*)bundleID {
[[NSUserDefaults standardUserDefaults] setObject:bundleID forKey:@"defaultHttpApplication"];
}
/**
Open web links in default browser or a browser the user selected in the preferences.
@param urls A list of @c NSURL objects that will be opened immediatelly in bulk.
@return @c YES if @c urls are opened successfully. @c NO on error.
*/
+ (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls {
if (urls.count == 0) return NO;
return [[NSWorkspace sharedWorkspace] openURLs:urls withAppBundleIdentifier:[self getHttpApplication] options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifiers:nil];
}
#pragma mark - Hidden Plist Properties -
/// @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 [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 [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 [self defaultUInt:40 forKey:@"articlesInMenuLimit"]; }
/// @return Returns @c defaultColor if defaults value couldn't be parsed or wasn't modified by user.
+ (NSColor*)defaultColor:(NSColor*)defaultColor forKey:(NSString*)key {
/// @return User set value. If it wasn't modified or couldn't be parsed return @c defaultColor
NSColor* UserPrefsColor(NSString *key, NSColor *defaultColor) {
NSString *colorStr = [[NSUserDefaults standardUserDefaults] stringForKey:key];
if (colorStr) {
NSColor *color = [colorStr hexColor];
@@ -96,62 +60,3 @@
}
return defaultColor;
}
#pragma mark - Application Info Plist
/// @return The application name, e.g., 'baRSS' or 'baRSS Beta'
+ (NSString*)appName {
return [[NSBundle mainBundle] infoDictionary][(NSString*)kCFBundleNameKey];
}
/// @return The application version number, e.g., '0.9.4'
+ (NSString*)appVersion {
return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
}
/// @return The application version number including build number, e.g., '0.9.4 (9906)'
+ (NSString*)appVersionWithBuildNo {
NSString *buildNo = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
return [[self appVersion] stringByAppendingFormat:@" (%@)", buildNo];
}
#pragma mark - Core Data Properties -
/// Helper method that retrieves and transforms option value to int
+ (int)dbIntForKey:(NSString*)key defaultsTo:(int)otherwise {
NSString *str = [StoreCoordinator optionForKey:key];
if (!str) return otherwise;
int num = [NSDecimalNumber decimalNumberWithString:str].intValue;
return isnan(num) ? otherwise : num;
}
/// Check whether the database was just initialized (first install)
+ (BOOL)dbIsUnusedInitalState {
return [StoreCoordinator optionForKey:@"db-version"] == nil;
}
/// Check whether the stored database version is up to date
+ (BOOL)dbIsCurrentFileVersion {
return [self dbIntForKey:@"db-version" defaultsTo:-1] == dbFileVersion;
}
/// Write current database version to core data
+ (void)dbUpdateFileVersion {
[StoreCoordinator setOption:@"db-version" value:[NSString stringWithFormat:@"%d", dbFileVersion]];
}
/// Check whether the stored application version is up to date
+ (BOOL)dbIsCurrentAppVersion {
return [[StoreCoordinator optionForKey:@"app-version"] isEqualToString:[self appVersion]];
}
/// Write current application version to core data
+ (void)dbUpdateAppVersion {
[StoreCoordinator setOption:@"app-version" value:[self appVersion]];
}
@end

View File

@@ -70,7 +70,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>13931</string>
<string>14336</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.news</string>
<key>LSMinimumSystemVersion</key>

View File

@@ -21,7 +21,6 @@
// SOFTWARE.
#import "NSURL+Ext.h"
#import "UserPrefs.h" // appName in +faviconsCacheURL
#import "NSError+Ext.h"
@implementation NSURL (Ext)
@@ -36,7 +35,7 @@
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
path = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
path = [path URLByAppendingPathComponent:[UserPrefs appName] isDirectory:YES];
path = [path URLByAppendingPathComponent:APP_NAME isDirectory:YES];
});
return path;
}

View File

@@ -22,25 +22,21 @@
#import "SettingsAboutView.h"
#import "NSView+Ext.h"
#import "UserPrefs.h"
@implementation SettingsAboutView
- (instancetype)init {
self = [super initWithFrame: NSZeroRect];
NSString *name = [UserPrefs appName];
NSString *version = [NSString stringWithFormat:NSLocalizedString(@"Version %@", nil),
#if DEBUG
[UserPrefs appVersionWithBuildNo]
#else
[UserPrefs appVersion]
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *version = [NSString stringWithFormat:NSLocalizedString(@"Version %@", nil), info[@"CFBundleShortVersionString"]];
#if DEBUG // append build number, e.g., '0.9.4 (9906)'
version = [version stringByAppendingFormat:@" (%@)", info[@"CFBundleVersion"]];
#endif
];
// Application icon image (top-centered)
NSImageView *logo = [[NSView imageView:NSImageNameApplicationIcon size:64] placeIn:self x:CENTER yTop:PAD_M];
// Add app name
NSTextField *lblN = [[[[NSView label:name] large] bold] placeIn:self x:CENTER yTop: YFromTop(logo) + PAD_M];
NSTextField *lblN = [[[[NSView label:APP_NAME] large] bold] placeIn:self x:CENTER yTop: YFromTop(logo) + PAD_M];
// Add version info
NSTextField *lblV = [[[[NSView label:version] small] selectable] placeIn:self x:CENTER yTop: YFromTop(lblN) + PAD_S];

View File

@@ -24,6 +24,7 @@
#import "SettingsAppearanceView.h"
#import "AppHook.h"
#import "BarStatusItem.h"
#import "UserPrefs.h"
@implementation SettingsAppearance
@@ -41,10 +42,9 @@
/// Sync new value with UserDefaults and update status bar icon
- (void)didSelectCheckbox:(NSButton*)sender {
BOOL state = (sender.state == NSControlStateValueOn);
[[NSUserDefaults standardUserDefaults] setBool:state forKey:sender.identifier];
if ([sender.identifier isEqualToString:@"globalUnreadCount"] ||
[sender.identifier isEqualToString:@"globalTintMenuBarIcon"]) {
NSString *pref = sender.identifier;
UserPrefsSetBool(pref, (sender.state == NSControlStateValueOn));
if (pref == Pref_globalUnreadCount || pref == Pref_globalTintMenuIcon) { // == because static string
[[(AppHook*)NSApp statusItem] updateBarIcon];
}
}

View File

@@ -22,67 +22,64 @@
#import "SettingsAppearanceView.h"
#import "NSView+Ext.h"
#import "Constants.h"
#import "UserPrefs.h"
#import "Constants.h" // column icons
#import "UserPrefs.h" // preference constants & UserPrefsBool()
@interface SettingsAppearanceView()
@property (assign) NSUInteger row;
@property (assign) CGFloat y;
@end
/***/ static CGFloat const IconSize = 18;
/***/ static CGFloat const colWidth = (IconSize + PAD_M); // checkbox column width
/***/ static CGFloat const X__ = PAD_WIN + 0 * colWidth;
/***/ static CGFloat const _X_ = PAD_WIN + 1 * colWidth;
/***/ static CGFloat const __X = PAD_WIN + 2 * colWidth;
@implementation SettingsAppearanceView
- (instancetype)init {
self = [super initWithFrame: NSZeroRect];
self.row = 0;
// Insert matrix header (the three icons)
[self head:0 img:RSSImageSettingsGlobal tooltip:NSLocalizedString(@"Show in menu bar", nil)];
[self head:1 img:RSSImageSettingsGroup tooltip:NSLocalizedString(@"Show in group menu", nil)];
[self head:2 img:RSSImageSettingsFeed tooltip:NSLocalizedString(@"Show in feed menu", nil)];
// Generate checkbox matrix (checkbox state, X: default ON, O: default OFF, blank: hidden)
[self entry:"X " label:NSLocalizedString(@"Tint menu bar icon on unread", nil)];
[self entry:"X " label:NSLocalizedString(@"Update all feeds", nil)];
[self entry:"XXX" label:NSLocalizedString(@"Open all unread", nil)];
[self entry:"XXX" label:NSLocalizedString(@"Mark all read", nil)];
[self entry:"XXX" label:NSLocalizedString(@"Mark all unread", nil)];
[self entry:"XXX" label:NSLocalizedString(@"Number of unread items", nil)];
[self entry:" X" label:NSLocalizedString(@"Tick mark unread items", nil)];
[[self entry:" O" label:NSLocalizedString(@"Short article names", nil)] tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)];
[[self entry:" O" label:NSLocalizedString(@"Limit number of articles", nil)] tooltip:NSLocalizedString(@"Display at most 40 articles in feed menu", nil)];
// Insert matrix header (icons above checkbox matrix)
ColumnIcon(self, X__, RSSImageSettingsGlobal, NSLocalizedString(@"Show in menu bar", nil));
ColumnIcon(self, _X_, RSSImageSettingsGroup, NSLocalizedString(@"Show in group menu", nil));
ColumnIcon(self, __X, RSSImageSettingsFeed, NSLocalizedString(@"Show in feed menu", nil));
// Generate checkbox matrix
self.y = PAD_WIN + IconSize + PAD_S;
[self entry:NSLocalizedString(@"Tint menu bar icon on unread", nil) c1:Pref_globalTintMenuIcon c2:nil c3:nil];
[self entry:NSLocalizedString(@"Update all feeds", nil) c1:Pref_globalUpdateAll c2:nil c3:nil];
[self entry:NSLocalizedString(@"Open all unread", nil) c1:Pref_globalOpenUnread c2:Pref_groupOpenUnread c3:Pref_feedOpenUnread];
[self entry:NSLocalizedString(@"Mark all read", nil) c1:Pref_globalMarkRead c2:Pref_groupMarkRead c3:Pref_feedMarkRead];
[self entry:NSLocalizedString(@"Mark all unread", nil) c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread];
[self entry:NSLocalizedString(@"Number of unread articles", nil) c1:Pref_globalUnreadCount c2:Pref_groupUnreadCount c3:Pref_feedUnreadCount];
[self entry:NSLocalizedString(@"Indicator for unread articles", nil) c1:nil c2:nil c3:Pref_feedUnreadIndicator];
[[self entry:NSLocalizedString(@"Truncate article title", nil) c1:nil c2:nil c3:Pref_feedTruncateTitle]
tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)];
[[self entry:NSLocalizedString(@"Limit number of articles", nil) c1:nil c2:nil c3:Pref_feedLimitArticles]
tooltip:NSLocalizedString(@"Display at most 40 articles in feed menu", nil)];
return self;
}
/// Helper method for matrix table header icons
- (void)head:(int)x img:(NSImageName)img tooltip:(NSString*)ttip {
[[[NSView imageView:img size:IconSize] tooltip:ttip] placeIn:self x:PAD_WIN + x * colWidth yTop:PAD_WIN];
static inline void ColumnIcon(id this, CGFloat x, const NSImageName img, NSString *ttip) {
[[[NSView imageView:img size:IconSize] placeIn:this x:x yTop:PAD_WIN] tooltip:ttip];
}
/// Helper method for generating a checkbox
static inline NSButton* Checkbox(id this, CGFloat x, CGFloat y, NSString *key) {
NSButton *check = [[NSView checkbox: UserPrefsBool(key)] placeIn:this x:x yTop:y];
check.identifier = key;
return check;
}
/// Create new entry with 1-3 checkboxes and a descriptive label
- (NSTextField*)entry:(char*)m label:(NSString*)text {
static char* const scope[] = { "global", "group", "feed" };
static char* const ident[] = { "TintMenuBarIcon", "UpdateAll", "OpenUnread", "MarkRead", "MarkUnread", "UnreadCount", "TickMark", "ShortNames", "LimitArticles" };
CGFloat y = PAD_WIN + IconSize + PAD_S + self.row * (PAD_S + HEIGHT_LABEL);
// Add checkboxes: row 0 - 8, col 0 - 2
for (NSUInteger col = 0; col < 3; col++) {
NSString *key = [NSString stringWithFormat:@"%s%s", scope[col], ident[self.row]];
BOOL state;
switch (m[col]) {
case 'X': state = [UserPrefs defaultYES:key]; break;
case 'O': state = [UserPrefs defaultNO: key]; break;
default: continue; // ignore blanks
}
NSButton *check = [[NSView checkbox:state] placeIn:self x:PAD_WIN + col * colWidth + 2 yTop:y + 2]; // 2px checkbox offset
check.identifier = key;
check.accessibilityLabel = [text stringByAppendingFormat:@" (%s)", scope[col]]; // TODO: localize: global, group, feed
}
self.row += 1;
// Add label
return [[[NSView label:text] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN];
- (NSTextField*)entry:(NSString*)label c1:(NSString*)pref1 c2:(NSString*)pref2 c3:(NSString*)pref3 {
CGFloat y = self.y;
self.y += (PAD_S + HEIGHT_LABEL);
// TODO: localize: global, group, feed
if (pref1) Checkbox(self, X__ + 2, y + 2, pref1).accessibilityLabel = [label stringByAppendingString:@" (global)"];
if (pref2) Checkbox(self, _X_ + 2, y + 2, pref2).accessibilityLabel = [label stringByAppendingString:@" (group)"];
if (pref3) Checkbox(self, __X + 2, y + 2, pref3).accessibilityLabel = [label stringByAppendingString:@" (feed)"];
return [[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN];
}
@end

View File

@@ -44,7 +44,7 @@
[pop addItemWithTitle: [self applicationNameForBundleId:bundleID]];
pop.lastItem.representedObject = bundleID;
}
[pop selectItemAtIndex:[pop indexOfItemWithRepresentedObject:[UserPrefs getHttpApplication]]];
[pop selectItemAtIndex:[pop indexOfItemWithRepresentedObject:UserPrefsString(Pref_defaultHttpApplication)]];
// Default RSS Reader application
NSString *feedBundleId = CFBridgingRelease(LSCopyDefaultHandlerForURLScheme(CFSTR("feed")));
self.view.defaultReader.objectValue = [self applicationNameForBundleId:feedBundleId];
@@ -65,7 +65,7 @@
// Callback method fired when user selects a different item from popup list
- (void)changeHttpApplication:(NSPopUpButton *)sender {
[UserPrefs setHttpApplication:sender.selectedItem.representedObject];
UserPrefsSet(Pref_defaultHttpApplication, sender.selectedItem.representedObject);
}
// Callback method from round help button right of default feed reader text

View File

@@ -21,6 +21,7 @@
// SOFTWARE.
#import "ModalSheet.h"
#import "UserPrefs.h"
#import "NSView+Ext.h"
@interface ModalSheet()
@@ -35,7 +36,7 @@
static NSInteger const maxWidth = 1200;
static CGFloat const contentOffsetY = PAD_WIN + HEIGHT_BUTTON + PAD_L;
NSInteger w = [[NSUserDefaults standardUserDefaults] integerForKey:@"modalSheetWidth"];
NSInteger w = UserPrefsInt(Pref_modalSheetWidth);
if (w < minWidth) w = minWidth;
else if (w > maxWidth) w = maxWidth;
@@ -90,8 +91,9 @@
return;
}
// Save modal view width for next time
CGFloat w = NSWidth(self.contentView.frame) - 2 * PAD_WIN;
[[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)w forKey:@"modalSheetWidth"];
NSInteger width = (NSInteger)(NSWidth(self.contentView.frame) - 2 * PAD_WIN);
if (UserPrefsInt(Pref_modalSheetWidth) != width)
UserPrefsSetInt(Pref_modalSheetWidth, width);
// Remove subviews to avoid _NSKeyboardFocusClipView issues
[self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.sheetParent endSheet:self returnCode:(successful ? NSModalResponseOK : NSModalResponseCancel)];

View File

@@ -49,13 +49,13 @@
flexibleWidth,
TabItem(NSImageNameInfo, NSLocalizedString(@"About", nil), [SettingsAbout class]),
];
[self switchToTab:[UserPrefs defaultUInt:0 forKey:@"preferencesTab"]];
[self switchToTab: UserPrefsUInt(Pref_prefSelectedTab)];
}
return self;
}
/// Helper method to generate tab item with image, label, and controller.
static NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class class) {
static inline NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class class) {
NSTabViewItem *item = [NSTabViewItem tabViewItemWithViewController: [class new]];
item.image = [NSImage imageNamed:imageName];
item.label = text;
@@ -76,10 +76,9 @@ static NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class class
/// Delegate method, store last selected tab to user preferences
- (void)tabView:(NSTabView*)tabView didSelectTabViewItem:(nullable NSTabViewItem*)tabViewItem {
[super tabView:tabView didSelectTabViewItem:tabViewItem];
NSInteger prevIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"preferencesTab"];
NSInteger newIndex = self.selectedTabViewItemIndex;
if (prevIndex != newIndex)
[[NSUserDefaults standardUserDefaults] setInteger:newIndex forKey:@"preferencesTab"];
if (UserPrefsInt(Pref_prefSelectedTab) != newIndex)
UserPrefsSetInt(Pref_prefSelectedTab, newIndex);
}
@end
@@ -95,7 +94,7 @@ static NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class class
w.title = [NSString stringWithFormat:NSLocalizedString(@"%@ Preferences", nil), NSProcessInfo.processInfo.processName];
w.contentViewController = [PrefTabs new];
w.delegate = w;
NSWindowPersistableFrameDescriptor prevFrame = [[NSUserDefaults standardUserDefaults] stringForKey:@"prefWindow"];
NSWindowPersistableFrameDescriptor prevFrame = UserPrefsString(Pref_prefWindowFrame);
if (!prevFrame) {
[w setContentSize:NSMakeSize(320, 327)];
[w center];
@@ -111,7 +110,7 @@ static NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Class class
}
- (void)windowWillClose:(NSNotification *)notification {
[[NSUserDefaults standardUserDefaults] setObject:self.stringWithSavedFrame forKey:@"prefWindow"];
UserPrefsSet(Pref_prefWindowFrame, self.stringWithSavedFrame);
}
/// Do not respond to Cmd-Z and Cmd-Shift-Z. Will be handled in subview controllers.

View File

@@ -105,14 +105,14 @@
/// Generate items for @c FeedArticles menu.
- (void)setArticles:(NSArray<FeedArticle*>*)sortedList forMenu:(NSMenu*)menu {
[menu insertDefaultHeader];
NSUInteger mc = 0;
if ([UserPrefs defaultNO:@"feedLimitArticles"]) {
mc = [UserPrefs articlesInMenuLimit];
}
NSInteger mc = NSIntegerMax;
if (UserPrefsBool(Pref_feedLimitArticles))
mc = UserPrefsInt(Pref_articlesInMenuLimit);
for (FeedArticle *fa in sortedList) {
[menu addItem:[fa newMenuItem]];
if (--mc == 0) // if mc==0 then unsigned int will underflow and turn into INT_MAX
if (--mc < 0) // mc == 0 will first decrement to -1, then evaluate
break;
[menu addItem:[fa newMenuItem]];
}
UnreadTotal *uct = self.unreadMap[menu.titleIndexPath];
[menu setHeaderHasUnread:(uct.unread > 0) hasRead:(uct.unread < uct.total)];

View File

@@ -118,18 +118,18 @@
- (void)updateBarIcon {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL hasNet = [UpdateScheduler allowNetworkConnection];
BOOL tint = (self.unreadCountTotal > 0 && hasNet && [UserPrefs defaultYES:@"globalTintMenuBarIcon"]);
BOOL tint = (self.unreadCountTotal > 0 && hasNet && UserPrefsBool(Pref_globalTintMenuIcon));
self.statusItem.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)];
self.statusItem.image.template = !tint;
BOOL showCount = (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"globalUnreadCount"]);
BOOL showCount = (self.unreadCountTotal > 0 && UserPrefsBool(Pref_globalUnreadCount));
self.statusItem.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @"");
});
}
/// Show popover with a brief notice that baRSS is running in the menu bar
- (void)showWelcomeMessage {
NSString *title = [NSString stringWithFormat:NSLocalizedString(@"Welcome to %@", nil), [UserPrefs appName]];
NSString *title = [NSString stringWithFormat:NSLocalizedString(@"Welcome to %@", nil), APP_NAME];
NSString *message = NSLocalizedString(@"There's no application window.\nEverything is up there.", nil);
NSTextField *head = [[NSView label:title] bold];
NSTextField *body = [[NSView label:message] small];
@@ -172,7 +172,7 @@
if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume Updates", nil);
// 'Update all feeds' item
if ([UserPrefs defaultYES:@"globalUpdateAll"]) {
if (UserPrefsBool(Pref_globalUpdateAll)) {
NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""];
updateAll.target = self;
updateAll.enabled = [UpdateScheduler allowNetworkConnection];

View File

@@ -88,7 +88,8 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
self.autoenablesItems = NO;
NSMenuItem *itm = [self addItemIfAllowed:TagOpenAllUnread title:NSLocalizedString(@"Open all unread", nil)];
if (itm) {
[self addItem:[itm alternateWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Open a few unread (%lu)", nil), [UserPrefs openFewLinksLimit]]]];
NSString *altTitle = [NSString stringWithFormat:NSLocalizedString(@"Open a few unread (%lu)", nil), UserPrefsUInt(Pref_openFewLinksLimit)];
[self addItem:[itm alternateWithTitle:altTitle]];
}
[self addItemIfAllowed:TagMarkAllRead title:NSLocalizedString(@"Mark all read", nil)];
[self addItemIfAllowed:TagMarkAllUnread title:NSLocalizedString(@"Mark all unread", nil)];
@@ -151,10 +152,16 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
/// Check user preferences for preferred display style.
- (BOOL)allowDisplayOfHeaderItem:(MenuItemTag)tag {
static char* const A[] = {"", "global", "feed", "group"};
static char* const B[] = {"", "MarkRead", "MarkUnread", "OpenUnread"};
int idx = (self.isMainMenu ? 1 : (self.isFeedMenu ? 2 : 3));
return [UserPrefs defaultYES:[NSString stringWithFormat:@"%s%s", A[idx], B[tag & 3]]]; // first 2 bits
static NSString* const mr[] = {Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead};
static NSString* const mu[] = {Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread};
static NSString* const ou[] = {Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread};
int i = (self.isMainMenu ? 0 : (self.isFeedMenu ? 2 : 1));
switch (tag) {
case TagMarkAllRead: return UserPrefsBool(mr[i]);
case TagMarkAllUnread: return UserPrefsBool(mu[i]);
case TagOpenAllUnread: return UserPrefsBool(ou[i]);
default: return NO;
}
}
/// Check user preferences if item should be displayed in menu. If so, add it to the menu and set callback to @c self.
@@ -176,7 +183,7 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
NSUInteger limit = 0;
if (sender.tag == TagOpenAllUnread) {
if (sender.isAlternate)
limit = [UserPrefs openFewLinksLimit];
limit = UserPrefsUInt(Pref_openFewLinksLimit);
openLinks = YES;
} else if (sender.tag != TagMarkAllRead && sender.tag != TagMarkAllUnread) {
return; // other menu item clicked. abort and return.
@@ -200,7 +207,8 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
if (fa.link.length > 0)
[urls addObject:[NSURL URLWithString:fa.link]];
}
success = [UserPrefs openURLsWithPreferredBrowser:urls];
if (urls.count > 0)
success = UserPrefsOpenURLs(urls);
}
// if success == NO, do not modify unread state
if (!openLinks || success) {
@@ -242,7 +250,7 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
if (loc != NSNotFound)
self.title = [self.title substringToIndex:loc];
}
if (count > 0 && [UserPrefs defaultYES:(self.submenu.isFeedMenu ? @"feedUnreadCount" : @"groupUnreadCount")]) {
if (count > 0 && UserPrefsBool(self.submenu.isFeedMenu ? Pref_feedUnreadCount : Pref_groupUnreadCount)) {
self.tag = TagTitleCountVisible; // apply new mask
self.title = [self.title stringByAppendingFormat:@" (%ld)", count];
}