Custom colors via defaults plist
This commit is contained in:
@@ -24,6 +24,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
|||||||
- *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
|
- *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
|
||||||
- *UI:* Accessibility hints for most UI elements
|
- *UI:* Accessibility hints for most UI elements
|
||||||
- *UI*: Show welcome message upon first usage (empty db)
|
- *UI*: Show welcome message upon first usage (empty db)
|
||||||
|
- *UI*: Custom colors via user defaults plist (bar icon tint & unread indicator)
|
||||||
- Welcome message also adds Github releases feed
|
- Welcome message also adds Github releases feed
|
||||||
- Config URL scheme `barss:` with `open/preferences`, `config/fixcache`, and `backup/show`
|
- Config URL scheme `barss:` with `open/preferences`, `config/fixcache`, and `backup/show`
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -95,25 +95,36 @@ If you prefer the optimized release version go to `Product > Archive`.
|
|||||||
Hidden options
|
Hidden options
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
1) When holding down the option key, the menu will show an item to open only a few unread items at a time.
|
This listing contains of options that have no UI that can be configured.
|
||||||
|
Most likely, you wouldn't ever stumble upon these if not reading this chapter.
|
||||||
|
**Note:** To reset an option run `defaults delete de.relikd.baRSS {KEY}`, where `{KEY}` is an option from below.
|
||||||
|
|
||||||
|
|
||||||
|
1. If you hold down the option key and click on an article item, you can mark a single item (un-)read.
|
||||||
|
|
||||||
|
2. When holding down the option key, the menu will show an item to open only a few unread items at a time.
|
||||||
This number can be changed with the following Terminal command (default: 10):
|
This number can be changed with the following Terminal command (default: 10):
|
||||||
|
```
|
||||||
|
defaults write de.relikd.baRSS openFewLinksLimit -int 10
|
||||||
|
```
|
||||||
|
|
||||||
```defaults write de.relikd.baRSS openFewLinksLimit -int 10```
|
3. In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default).
|
||||||
|
|
||||||
|
|
||||||
2) If you hold down the option key and click on an article item, you can mark a single item (un-)read.
|
|
||||||
|
|
||||||
|
|
||||||
3) In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default).
|
|
||||||
With this Terminal command you can customize this limit:
|
With this Terminal command you can customize this limit:
|
||||||
|
```
|
||||||
|
defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
|
||||||
|
```
|
||||||
|
|
||||||
```defaults write de.relikd.baRSS shortArticleNamesLimit -int 50```
|
4. Limit the number of displayed articles per feed menu.
|
||||||
|
|
||||||
|
|
||||||
4) Limit the number of displayed articles per feed menu.
|
|
||||||
**Note:** displayed unread count may be different than the unread items inside ('Open unread' will open hidden items too).
|
**Note:** displayed unread count may be different than the unread items inside ('Open unread' will open hidden items too).
|
||||||
|
```
|
||||||
|
defaults write de.relikd.baRSS articlesInMenuLimit -int 40
|
||||||
|
```
|
||||||
|
|
||||||
```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"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,6 @@
|
|||||||
|
|
||||||
@import Cocoa;
|
@import Cocoa;
|
||||||
|
|
||||||
@interface NSColor (RandomColor)
|
|
||||||
/// just for testing purposes
|
|
||||||
+ (NSColor*)randomColor;
|
|
||||||
/// RGB color with (251, 163, 58)
|
|
||||||
+ (NSColor*)rssOrange;
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
/// Draw separator line in @c NSOutlineView
|
/// Draw separator line in @c NSOutlineView
|
||||||
IB_DESIGNABLE
|
IB_DESIGNABLE
|
||||||
@interface DrawSeparator : NSView
|
@interface DrawSeparator : NSView
|
||||||
|
|||||||
@@ -22,20 +22,7 @@
|
|||||||
|
|
||||||
#import "DrawImage.h"
|
#import "DrawImage.h"
|
||||||
#import "Constants.h"
|
#import "Constants.h"
|
||||||
|
#import "UserPrefs.h"
|
||||||
@implementation NSColor (RandomColor)
|
|
||||||
/// @return Color with random R, G, B values for testing purposes
|
|
||||||
+ (NSColor*)randomColor {
|
|
||||||
return [NSColor colorWithRed:(arc4random()%50+20)/100.0
|
|
||||||
green:(arc4random()%50+20)/100.0
|
|
||||||
blue:(arc4random()%50+20)/100.0
|
|
||||||
alpha:1];
|
|
||||||
}
|
|
||||||
/// @return Orange color that is typically used for RSS
|
|
||||||
+ (NSColor*)rssOrange {
|
|
||||||
return [NSColor colorWithCalibratedRed:251/255.0 green:163/255.0 blue:58/255.0 alpha:1.0];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@implementation DrawSeparator
|
@implementation DrawSeparator
|
||||||
@@ -278,14 +265,14 @@ static void DrawRSSIcon(CGRect r, CGColorRef color, BOOL background, BOOL connec
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves)
|
/// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves)
|
||||||
static void DrawRSSGradientIcon(CGRect r) {
|
static void DrawRSSGradientIcon(CGRect r, NSColor *color) {
|
||||||
const CGFloat size = ShorterSide(r.size);
|
const CGFloat size = ShorterSide(r.size);
|
||||||
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
|
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
|
||||||
DrawRoundedFrame(c, r, NSColor.whiteColor.CGColor, YES, 0.4, 1.0, 0.7);
|
DrawRoundedFrame(c, r, NSColor.whiteColor.CGColor, YES, 0.4, 1.0, 0.7);
|
||||||
// Gradient
|
// Gradient
|
||||||
CGContextSaveGState(c);
|
CGContextSaveGState(c);
|
||||||
CGContextClip(c);
|
CGContextClip(c);
|
||||||
DrawGradient(c, size, [NSColor rssOrange]);
|
DrawGradient(c, size, color);
|
||||||
CGContextRestoreGState(c);
|
CGContextRestoreGState(c);
|
||||||
// Bars
|
// Bars
|
||||||
AddRSSIconPath(c, size, YES);
|
AddRSSIconPath(c, size, YES);
|
||||||
@@ -297,7 +284,8 @@ static void DrawUnreadIcon(CGRect r, NSColor *color) {
|
|||||||
CGFloat size = ShorterSide(r.size) / 2.0;
|
CGFloat size = ShorterSide(r.size) / 2.0;
|
||||||
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
|
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
SetContentScale(c, r.size, 0.8);
|
SetContentScale(c, r.size, 0.7);
|
||||||
|
CGContextTranslateCTM(c, 0, size * -0.15); // align with baseline of menu item text
|
||||||
|
|
||||||
CGContextSetFillColorWithColor(c, color.CGColor);
|
CGContextSetFillColorWithColor(c, color.CGColor);
|
||||||
PathAddRing(path, size, size * 0.7);
|
PathAddRing(path, size, size * 0.7);
|
||||||
@@ -325,12 +313,17 @@ static void Register(CGFloat size, NSImageName name, NSString *description, BOOL
|
|||||||
/// Register all icons that require custom drawing in @c ImageNamed cache
|
/// Register all icons that require custom drawing in @c ImageNamed cache
|
||||||
void RegisterImageViewNames(void) {
|
void RegisterImageViewNames(void) {
|
||||||
const CGColorRef black = [NSColor controlTextColor].CGColor;
|
const CGColorRef black = [NSColor controlTextColor].CGColor;
|
||||||
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r); return YES; });
|
NSColor* const orange = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A
|
||||||
|
|
||||||
|
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, orange); return YES; });
|
||||||
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; });
|
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; });
|
||||||
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; });
|
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; });
|
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; });
|
||||||
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, YES); return YES; });
|
|
||||||
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, NO); return YES; });
|
NSColor *c1 = [UserPrefs defaultColor:orange forKey:@"colorStatusIconTint"];
|
||||||
Register(12, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor systemBlueColor]); return YES; });
|
NSColor *c2 = [UserPrefs defaultColor:[NSColor systemBlueColor] forKey:@"colorUnreadTickMark"];
|
||||||
// TODO: user selected color for rss bar icon & unread dot
|
|
||||||
|
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; });
|
||||||
|
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, c2); return YES; });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@
|
|||||||
+ (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls;
|
+ (BOOL)openURLsWithPreferredBrowser:(NSArray<NSURL*>*)urls;
|
||||||
|
|
||||||
// Hidden Plist Properties
|
// Hidden Plist Properties
|
||||||
+ (NSUInteger)openFewLinksLimit; // Change with: 'defaults write de.relikd.baRSS openFewLinksLimit -int 10'
|
+ (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)shortArticleNamesLimit; // Change with: defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
|
||||||
+ (NSUInteger)articlesInMenuLimit; // Change with: 'defaults write de.relikd.baRSS articlesInMenuLimit -int 40'
|
+ (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
|
// Application Info Plist
|
||||||
+ (NSString*)appName;
|
+ (NSString*)appName;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#import "UserPrefs.h"
|
#import "UserPrefs.h"
|
||||||
#import "StoreCoordinator.h"
|
#import "StoreCoordinator.h"
|
||||||
|
#import "NSString+Ext.h"
|
||||||
|
|
||||||
@implementation UserPrefs
|
@implementation UserPrefs
|
||||||
|
|
||||||
@@ -84,6 +85,18 @@
|
|||||||
/// Default: @c 40
|
/// Default: @c 40
|
||||||
+ (NSUInteger)articlesInMenuLimit { return [self defaultUInt:40 forKey:@"articlesInMenuLimit"]; }
|
+ (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 {
|
||||||
|
NSString *colorStr = [[NSUserDefaults standardUserDefaults] stringForKey:key];
|
||||||
|
if (colorStr) {
|
||||||
|
NSColor *color = [colorStr hexColor];
|
||||||
|
if (color) return color;
|
||||||
|
NSLog(@"Error reading defaults '%@'. Hex color '%@' is invalid. It should be of the form #RBG or #RRGGBB.", key, colorStr);
|
||||||
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
|
||||||
|
}
|
||||||
|
return defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Application Info Plist
|
#pragma mark - Application Info Plist
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>13723</string>
|
<string>13931</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.news</string>
|
<string>public.app-category.news</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|||||||
@@ -28,8 +28,9 @@
|
|||||||
@interface NSError (Ext)
|
@interface NSError (Ext)
|
||||||
// Generators
|
// Generators
|
||||||
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
|
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
|
||||||
+ (instancetype)canceledByUser;
|
|
||||||
+ (instancetype)feedURLNotFound:(NSURL*)url;
|
+ (instancetype)feedURLNotFound:(NSURL*)url;
|
||||||
|
+ (instancetype)canceledByUser;
|
||||||
|
//+ (instancetype)formattingError:(NSString*)description;
|
||||||
// User notification
|
// User notification
|
||||||
- (BOOL)inCaseLog:(nullable const char*)title;
|
- (BOOL)inCaseLog:(nullable const char*)title;
|
||||||
- (BOOL)inCasePresent:(NSApplication*)app;
|
- (BOOL)inCasePresent:(NSApplication*)app;
|
||||||
|
|||||||
@@ -115,17 +115,23 @@ static const char* CodeDescription(NSInteger code) {
|
|||||||
return [self errorWithDomain:NSURLErrorDomain code:errCode userInfo:info];
|
return [self errorWithDomain:NSURLErrorDomain code:errCode userInfo:info];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate @c NSError for user canceled operation. With title "Operation canceled.".
|
|
||||||
+ (instancetype)canceledByUser {
|
|
||||||
NSDictionary *info = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Operation canceled.", nil) };
|
|
||||||
return [self errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:info];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate @c NSError for webpages that don't contain feed urls.
|
/// Generate @c NSError for webpages that don't contain feed urls.
|
||||||
+ (instancetype)feedURLNotFound:(NSURL*)url {
|
+ (instancetype)feedURLNotFound:(NSURL*)url {
|
||||||
return RSXMLMakeErrorWrongParser(RSXMLErrorExpectingFeed, RSXMLErrorExpectingHTML, url);
|
return RSXMLMakeErrorWrongParser(RSXMLErrorExpectingFeed, RSXMLErrorExpectingHTML, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate @c NSError for user canceled operation. With title "Operation was canceled."
|
||||||
|
+ (instancetype)canceledByUser {
|
||||||
|
return [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Generate @c NSError for invalid or malformed input. With title "The value is invalid."
|
||||||
|
+ (instancetype)formattingError:(NSString*)description {
|
||||||
|
NSDictionary *info = nil;
|
||||||
|
if (description) info = @{ NSLocalizedRecoverySuggestionErrorKey: description };
|
||||||
|
return [NSError errorWithDomain:NSCocoaErrorDomain code:NSFormattingError userInfo:info];
|
||||||
|
}*/
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// | MARK: - User notification
|
// | MARK: - User notification
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|||||||
@@ -22,7 +22,11 @@
|
|||||||
|
|
||||||
@import Cocoa;
|
@import Cocoa;
|
||||||
|
|
||||||
@interface NSString (Ext)
|
@interface NSString (PlainHTML)
|
||||||
+ (NSString*)plainTextFromHTMLData:(NSData*)data;
|
+ (NSString*)plainTextFromHTMLData:(NSData*)data;
|
||||||
- (nonnull NSString*)htmlToPlainText;
|
- (nonnull NSString*)htmlToPlainText;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface NSString (HexColor)
|
||||||
|
- (nullable NSColor*)hexColor;
|
||||||
|
@end
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#import "NSString+Ext.h"
|
#import "NSString+Ext.h"
|
||||||
|
|
||||||
@implementation NSString (Ext)
|
@implementation NSString (PlainHTML)
|
||||||
|
|
||||||
/// Init string with @c NSUTF8StringEncoding and call @c htmlToPlainText
|
/// Init string with @c NSUTF8StringEncoding and call @c htmlToPlainText
|
||||||
+ (NSString*)plainTextFromHTMLData:(NSData*)data {
|
+ (NSString*)plainTextFromHTMLData:(NSData*)data {
|
||||||
@@ -112,3 +112,51 @@ static inline BOOL CLOSE(NSString *tag, NSString *match) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@implementation NSString (HexColor)
|
||||||
|
|
||||||
|
/**
|
||||||
|
Color from hex string with format: @c #[0x|0X]([A]RGB|[AA]RRGGBB)
|
||||||
|
|
||||||
|
@return @c nil if string is not properly formatted.
|
||||||
|
*/
|
||||||
|
- (nullable NSColor*)hexColor {
|
||||||
|
if ([self characterAtIndex:0] != '#') // must start with '#'
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
NSScanner *scanner = [NSScanner scannerWithString:self];
|
||||||
|
scanner.scanLocation = 1;
|
||||||
|
unsigned int value;
|
||||||
|
if (![scanner scanHexInt:&value])
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
NSUInteger len = scanner.scanLocation - 1; // -'#'
|
||||||
|
if (len > 1 && ([self characterAtIndex:2] == 'x' || [self characterAtIndex:3] == 'X'))
|
||||||
|
len -= 2; // ignore '0x'RRGGBB
|
||||||
|
|
||||||
|
unsigned int r = 0, g = 0, b = 0, a = 255;
|
||||||
|
switch (len) {
|
||||||
|
case 4: // #ARGB
|
||||||
|
// ignore alpha for now
|
||||||
|
// a = (value >> 8) & 0xF0; a = a | (a >> 4);
|
||||||
|
case 3: // #RGB
|
||||||
|
r = (value >> 4) & 0xF0; r = r | (r >> 4);
|
||||||
|
g = (value) & 0xF0; g = g | (g >> 4);
|
||||||
|
b = (value) & 0x0F; b = b | (b << 4);
|
||||||
|
break;
|
||||||
|
case 8: // #AARRGGBB
|
||||||
|
// a = (value >> 24) & 0xFF;
|
||||||
|
case 6: // #RRGGBB
|
||||||
|
r = (value >> 16) & 0xFF;
|
||||||
|
g = (value >> 8) & 0xFF;
|
||||||
|
b = (value) & 0xFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return [NSColor colorWithCalibratedRed:r/255.f green:g/255.f blue:b/255.f alpha:a/255.f];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user