22 Commits
v1.5.5 ... main

Author SHA1 Message Date
relikd
b194a1427d feat: add svg artwork 2025-12-09 00:30:25 +01:00
relikd
ff34781fea ref: simplify regex icon 2025-12-09 00:28:29 +01:00
relikd
4edd4448ae ref: pixel-perfect rss icon alignment 2025-12-09 00:10:54 +01:00
relikd
33f907228b ref: simplify rss icon path 2025-12-08 23:31:09 +01:00
relikd
673e0d3d48 fix: quadratic curve 2025-12-08 23:30:50 +01:00
relikd
b3fdadb9f4 feat: feed group icon 2025-12-08 22:40:11 +01:00
relikd
9fc513254f fix: pixel-perfect group icon 2025-12-08 22:31:49 +01:00
relikd
881b9db02c ref: flip coordinate system 2025-12-08 21:43:24 +01:00
relikd
3a14c90f37 ref: split svgRect and svgRoundedRect 2025-12-08 21:36:49 +01:00
relikd
96884474ac ref: unread dot icon 2025-12-08 21:21:29 +01:00
relikd
82ae18c8a5 ref: pixel-perfect main menu icon (+feed icon) 2025-12-08 21:10:20 +01:00
relikd
6eddb57651 ref: svg rss icon 2025-12-08 21:09:38 +01:00
relikd
67d17599b5 ref: default rss icon 2025-12-08 19:05:27 +01:00
relikd
3507fd8e27 feat: appearance settings article icon 2025-12-08 19:04:53 +01:00
relikd
ca417f35b6 ref: rename drawing methods 2025-12-08 17:36:48 +01:00
relikd
6e5326f913 feat: new menubar icon for Appearance settings 2025-12-08 16:32:39 +01:00
relikd
1589b23aa9 fix: TinySVG rect scaling 2025-12-08 16:31:53 +01:00
relikd
e0cd04b882 feat: new group icon (svg) 2025-12-08 14:59:48 +01:00
relikd
6b4c38ec21 feat: TinySVG support for quadratic curves 2025-12-08 14:49:40 +01:00
relikd
e7208ae2ab fix: variable name 2025-12-08 14:13:09 +01:00
relikd
508377a823 fix: limit tooltip to 2000 characters 2025-12-05 22:24:29 +01:00
relikd
2185eb76fb fix: uniform menu titles 2025-12-05 14:11:48 +01:00
16 changed files with 302 additions and 237 deletions

View File

@@ -108,6 +108,13 @@ 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
``` ```
3. Each article menu item shows a summary tooltip (if the server provides one).
By default, the tooltip is limited to 2000 characters.
You can change the limit with this command:
```
defaults write de.relikd.baRSS tooltipCharacterLimit -int 500
```
3. Limit the number of displayed articles per feed menu. 3. Limit the number of displayed articles per feed menu.
**Note:** displayed unread count may be different than the unread items inside. 'Open all unread' will open hidden items too. **Note:** displayed unread count may be different than the unread items inside. 'Open all unread' will open hidden items too.
``` ```

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect y="14" width="16" height="1"/>
<rect y="10" width="16" height="1"/>
<rect x="9" y="6" width="7" height="1"/>
<rect x="9" y="2" width="7" height="1"/>
<rect x="1" y="1" width="7" height="7"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g fill="none" stroke="#000">
<path d="M3,13.5c-1.5,0-2.5-1-2.5-2.5V3.5c0-1.5.5-2,2-2h1.5c1.5,0,1.5,1,3,1h6c1.5,0,2.5,1,2.5,2.5v6c0,1.5-1,2.5-2.5,2.5H3Z"/>
<path d="M1.5,5h13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<!-- menu -->
<rect x="0" y="0" width="16" height="3"/>
<rect x="5" y="4" width="9" height="12"/>
<rect x="6" y="3" width="7" height="12" fill="#aaa"/>
<!-- entries -->
<rect x="6" y="12" width="6" height="1"/>
<rect x="6" y="9" width="6" height="1"/>
<rect x="6" y="6" width="6" height="1"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M18,19c-14,21-13,43,0,62l-7,4C-4,63-4,35,12,14l6,5Z"/>
<circle cx="31" cy="67" r="7"/>
<path d="M65,28l11-4,2,6-11,4,7,9-5,4-7-9-7,9-5-4,7-9-11-4,2-6,11,4v-11h6v11Z"/>
<path d="M82,81c14-21,13-43,0-62l7-5c16,22,15,50,0,71l-7-4Z"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="13" cy="87" r="13"/>
<path d="M0,35q65,0,65,65h-20q0,-45,-45,-45z"/>
<rect x="60" y="0" width="15" height="50"/>
<rect x="85" y="0" width="15" height="50"/>
</svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="13" cy="87" r="13"/>
<path d="M0,35q65,0,65,65h-20q0,-45,-45,-45z"/>
<path d="M0,0q100,0,100,100h-20q0,-80,-80,-80z"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -22,12 +22,16 @@ static NSString* const auxiliaryAppURL = @"https://github.com/relikd/URL-Scheme-
/// Default RSS icon (with border, with gradient, orange) /// Default RSS icon (with border, with gradient, orange)
static NSImageName const RSSImageDefaultRSSIcon = @"RSSImageDefaultRSSIcon"; static NSImageName const RSSImageDefaultRSSIcon = @"RSSImageDefaultRSSIcon";
/// Settings, global icon (menu bar, black) /// Settings, global statusbar icon (rss icon with neighbor icons)
static NSImageName const RSSImageSettingsGlobal = @"RSSImageSettingsGlobal"; static NSImageName const RSSImageSettingsGlobalIcon = @"RSSImageSettingsGlobalIcon";
/// Settings, global menu icon (menu bar, black)
static NSImageName const RSSImageSettingsGlobalMenu = @"RSSImageSettingsGlobalMenu";
/// Settings, group icon (folder, black) /// Settings, group icon (folder, black)
static NSImageName const RSSImageSettingsGroup = @"RSSImageSettingsGroup"; static NSImageName const RSSImageSettingsGroup = @"RSSImageSettingsGroup";
/// Settings, feed icon (RSS, no border, no gradient, black) /// Settings, feed icon (RSS, no border, no gradient, black)
static NSImageName const RSSImageSettingsFeed = @"RSSImageSettingsFeed"; static NSImageName const RSSImageSettingsFeed = @"RSSImageSettingsFeed";
/// Settings, article icon (RSS surrounded by text lines)
static NSImageName const RSSImageSettingsArticle = @"RSSImageSettingsArticle";
/// Menu bar, bar icon (RSS, with border, no gradient, orange) /// Menu bar, bar icon (RSS, with border, no gradient, orange)
static NSImageName const RSSImageMenuBarIconActive = @"RSSImageMenuBarIconActive"; static NSImageName const RSSImageMenuBarIconActive = @"RSSImageMenuBarIconActive";
/// Menu bar, bar icon (RSS, with border, no gradient, paused, orange) /// Menu bar, bar icon (RSS, with border, no gradient, paused, orange)

View File

@@ -63,7 +63,14 @@
item.state = (self.unread && UserPrefsBool(Pref_feedUnreadIndicator) ? NSControlStateValueOn : NSControlStateValueOff); item.state = (self.unread && UserPrefsBool(Pref_feedUnreadIndicator) ? NSControlStateValueOn : NSControlStateValueOff);
item.onStateImage = [NSImage imageNamed:RSSImageMenuItemUnread]; 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.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) // truncate tooltip
NSUInteger limit = UserPrefsUInt(Pref_tooltipCharacterLimit);
if (limit > 0) {
NSString *tooltip = (self.abstract ? self.abstract : self.body); // fall back to body (html)
if (tooltip.length > limit)
tooltip = [[tooltip substringToIndex:limit] stringByAppendingString:@"…\n[…]"];
item.toolTip = tooltip;
}
item.representedObject = self.objectID; item.representedObject = self.objectID;
item.target = [self class]; item.target = [self class];
item.action = @selector(didClickOnMenuItem:); item.action = @selector(didClickOnMenuItem:);

View File

@@ -23,126 +23,18 @@ static inline const CGFloat ShorterSide(NSSize s) {
return (s.width < s.height ? s.width : s.height); return (s.width < s.height ? s.width : s.height);
} }
/// Perform @c CGAffineTransform with custom rotation point /// Flip coordinate system
// CGAffineTransform RotateAroundPoint(CGAffineTransform at, CGFloat angle, CGFloat x, CGFloat y) { //static void FlipCoordinateSystem(CGContextRef c, CGFloat height) {
// at = CGAffineTransformTranslate(at, x, y); // CGContextTranslateCTM(c, 0, height);
// at = CGAffineTransformRotate(at, angle); // CGContextScaleCTM(c, 1, -1);
// return CGAffineTransformTranslate(at, -x, -y);
//} //}
/// Scale and translate context to the center with respect to the new scale. If @c width @c != @c length align top left.
#pragma mark - CGPath Component Generators static void SetContentScale(CGContextRef c, CGSize size, CGFloat scale) {
const CGFloat s = ShorterSide(size);
CGFloat offset = s * (1 - scale) / 2;
/// Add circle with @c radius CGContextTranslateCTM(c, offset, size.height - s + offset); // top left alignment
static inline void PathAddCircle(CGMutablePathRef path, CGFloat radius) { CGContextScaleCTM(c, scale, scale);
CGPathAddArc(path, NULL, radius, radius, radius, 0, M_PI * 2, YES);
}
/// Add ring with @c radius and @c innerRadius
static inline void PathAddRing(CGMutablePathRef path, CGFloat radius, CGFloat innerRadius) {
CGPathAddArc(path, NULL, radius, radius, radius, 0, M_PI * 2, YES);
CGPathAddArc(path, NULL, radius, radius, innerRadius, 0, M_PI * -2, YES);
}
/// Add a single RSS icon radio wave
static inline void PathAddRSSArc(CGMutablePathRef path, CGFloat radius, CGFloat thickness) {
CGPathMoveToPoint(path, NULL, 0, radius + thickness);
CGPathAddArc(path, NULL, 0, 0, radius + thickness, M_PI_2, 0, YES);
CGPathAddLineToPoint(path, NULL, radius, 0);
CGPathAddArc(path, NULL, 0, 0, radius, 0, M_PI_2, NO);
CGPathCloseSubpath(path);
}
/// Add two vertical bars representing a pause icon
static inline void PathAddPauseIcon(CGMutablePathRef path, CGAffineTransform at, CGFloat size, CGFloat thickness) {
const CGFloat off = (size - 2 * thickness) / 4;
CGPathAddRect(path, &at, CGRectMake(off, 0, thickness, size));
CGPathAddRect(path, &at, CGRectMake(size/2 + off, 0, thickness, size));
}
/// Add X icon by applying a rotational affine transform and drawing a plus sign
// void PathAddXIcon(CGMutablePathRef path, CGAffineTransform at, CGFloat size, CGFloat thickness) {
// at = RotateAroundPoint(at, M_PI_4, size/2, size/2);
// const CGFloat p = size * 0.5 - thickness / 2;
// CGPathAddRect(path, &at, CGRectMake(0, p, size, thickness));
// CGPathAddRect(path, &at, CGRectMake(p, 0, thickness, p));
// CGPathAddRect(path, &at, CGRectMake(p, p + thickness, thickness, p));
//}
#pragma mark - Full Icon Path Generators
/// Create @c CGPath for global icon; a menu bar and an open menu below
static inline void AddGlobalIconPath(CGContextRef c, CGFloat size) {
CGMutablePathRef menu = CGPathCreateMutable();
CGPathAddRect(menu, NULL, CGRectMake(0, 0.8 * size, size, 0.2 * size));
CGPathAddRect(menu, NULL, CGRectMake(0.3 * size, 0, 0.55 * size, 0.75 * size));
CGPathAddRect(menu, NULL, CGRectMake(0.35 * size, 0.05 * size, 0.45 * size, 0.75 * size));
CGFloat entryHeight = 0.1 * size; // 0.075
for (int i = 0; i < 3; i++) { // 4
//CGPathAddRect(menu, NULL, CGRectMake(0.37 * size, (2 * i + 1) * entryHeight, 0.42 * size, entryHeight)); // uncomment path above
CGPathAddRect(menu, NULL, CGRectMake(0.35 * size, (2 * i + 1.5) * entryHeight, 0.4 * size, entryHeight * 0.8));
}
CGContextAddPath(c, menu);
CGPathRelease(menu);
}
/// Create @c CGPath for group icon; a folder symbol
static inline void AddGroupIconPath(CGContextRef c, CGFloat size, BOOL showBackground) {
const CGFloat r1 = size * 0.05; // corners
const CGFloat r2 = size * 0.08; // upper part, name tag
const CGFloat r3 = size * 0.15; // lower part, corners inside
const CGFloat posTop = 0.85 * size;
const CGFloat posMiddle = 0.6 * size - r3;
const CGFloat posBottom = 0.15 * size + r1;
const CGFloat posNameTag = 0.3 * size;
CGMutablePathRef upper = CGPathCreateMutable();
CGPathMoveToPoint(upper, NULL, 0, 0.5 * size);
CGPathAddLineToPoint(upper, NULL, 0, posTop - r1);
CGPathAddArc(upper, NULL, r1, posTop - r1, r1, M_PI, M_PI_2, YES);
CGPathAddArc(upper, NULL, posNameTag, posTop - r2, r2, M_PI_2, M_PI_4, YES);
CGPathAddArc(upper, NULL, posNameTag + 1.85 * r2, posTop, r2, M_PI + M_PI_4, -M_PI_2, NO);
CGPathAddArc(upper, NULL, size - r1, posTop - r1 - r2, r1, M_PI_2, 0, YES);
CGPathAddArc(upper, NULL, size - r1, posBottom, r1, 0, -M_PI_2, YES);
CGPathAddArc(upper, NULL, r1, posBottom, r1, -M_PI_2, M_PI, YES);
CGPathCloseSubpath(upper);
CGMutablePathRef lower = CGPathCreateMutable();
CGPathAddArc(lower, NULL, r3, posMiddle, r3, M_PI, M_PI_2, YES);
CGPathAddArc(lower, NULL, size - r3, posMiddle, r3, M_PI_2, 0, YES);
CGPathAddArc(lower, NULL, size - r1, posBottom, r1, 0, -M_PI_2, YES);
CGPathAddArc(lower, NULL, r1, posBottom, r1, -M_PI_2, M_PI, YES);
CGPathCloseSubpath(lower);
CGContextAddPath(c, upper);
if (showBackground)
CGContextEOFillPath(c);
CGContextAddPath(c, lower);
CGPathRelease(upper);
CGPathRelease(lower);
}
/**
Create @c CGPath for RSS icon; a circle in the lower left bottom and two radio waves going outwards.
@param connection If @c NO, draw only one radio wave and a pause icon in the upper right
*/
static inline void AddRSSIconPath(CGContextRef c, CGFloat size, BOOL connection) {
CGMutablePathRef bars = CGPathCreateMutable(); // the rss bars
PathAddCircle(bars, size * 0.125);
PathAddRSSArc(bars, size * 0.45, size * 0.2);
if (connection) {
PathAddRSSArc(bars, size * 0.8, size * 0.2);
} else {
CGAffineTransform at = CGAffineTransformMake(0.5, 0, 0, 0.5, size/2, size/2);
PathAddPauseIcon(bars, at, size, size * 0.3);
//PathAddXIcon(bars, at, size, size * 0.3);
}
CGContextAddPath(c, bars);
CGPathRelease(bars);
} }
@@ -168,130 +60,214 @@ static void DrawGradient(CGContextRef c, CGFloat size, NSColor *color) {
CFArrayRef colors = CFArrayCreate(NULL, cgColors, 3, NULL); CFArrayRef colors = CFArrayCreate(NULL, cgColors, 3, NULL);
CGGradientRef gradient = CGGradientCreateWithColors(NULL, colors, NULL); CGGradientRef gradient = CGGradientCreateWithColors(NULL, colors, NULL);
CGContextDrawLinearGradient(c, gradient, CGPointMake(0, size), CGPointMake(size, 0), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGContextDrawLinearGradient(c, gradient, CGPointMake(0, 0), CGPointMake(size, size), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient); CGGradientRelease(gradient);
CFRelease(colors); CFRelease(colors);
} }
#pragma mark - CGContext Drawing & Manipulation #pragma mark - RSS Icon (rounded corners)
/// Flip coordinate system /**
static void FlipCoordinateSystem(CGContextRef c, CGFloat height) { Create @c CGPath for RSS icon; a circle in the lower left bottom and two radio waves going outwards.
CGContextTranslateCTM(c, 0, height); @param connection If @c NO, draw only one radio wave and a pause icon in the upper right
CGContextScaleCTM(c, 1, -1); */
} static inline void AddRSSIconPath(CGContextRef c, CGFloat size, BOOL connection) {
svgCircle(c, size/100, 13, 87, 13, NO);
/// Scale and translate context to the center with respect to the new scale. If @c width @c != @c length align top left. svgPath(c, size/100, "M0,35q65,0,65,65h-20q0,-45,-45,-45z");
static void SetContentScale(CGContextRef c, CGSize size, CGFloat scale) { if (connection) {
const CGFloat s = ShorterSide(size); svgPath(c, size/100, "M0,0q100,0,100,100h-20q0,-80,-80,-80z");
CGFloat offset = s * (1 - scale) / 2; } else {
CGContextTranslateCTM(c, offset, size.height - s + offset); // top left alignment // pause icon
CGContextScaleCTM(c, scale, scale); svgRect(c, size/100, CGRectMake(60, 0, 15, 50));
} svgRect(c, size/100, CGRectMake(85, 0, 15, 50));
/// Helper method; set drawing color, add rounded background and prepare content scale
static void DrawRoundedFrame(CGContextRef c, CGRect r, CGColorRef color, BOOL background, CGFloat corner, CGFloat defaultScale, CGFloat scaling) {
CGContextSetFillColorWithColor(c, color);
CGContextSetStrokeColorWithColor(c, color);
CGFloat contentScale = defaultScale;
if (background) {
svgAddRect(c, 1, r, ShorterSide(r.size) * corner/2);
if (scaling != 0.0)
contentScale *= scaling;
} }
SetContentScale(c, r.size, contentScale);
} }
/// Draw monochrome RSS icon with rounded corners
#pragma mark - Easy Icon Drawing Methods static void RoundedRSS_Monochrome(CGRect r, BOOL connection) {
/// Draw global icon (menu bar)
static void DrawGlobalIcon(CGRect r, CGColorRef color, BOOL background) {
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
DrawRoundedFrame(c, r, color, background, 0.4, 1.0, 0.7);
AddGlobalIconPath(c, ShorterSide(r.size));
CGContextEOFillPath(c);
}
/// Draw group icon (folder)
static void DrawGroupIcon(CGRect r, CGColorRef color, BOOL background) {
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
const CGFloat s = ShorterSide(r.size);
const CGFloat l = s * 0.08; // line width
DrawRoundedFrame(c, r, color, background, 0.4, 1.0 - (l / s), 0.85);
CGContextSetLineWidth(c, l * (background ? 0.5 : 1.0));
AddGroupIconPath(c, s, background);
CGContextStrokePath(c);
}
/// Draw RSS icon (flat without gradient)
static void DrawRSSIcon(CGRect r, CGColorRef color, BOOL background, BOOL connection) {
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
DrawRoundedFrame(c, r, color, background, 0.4, 1.0, 0.7);
AddRSSIconPath(c, ShorterSide(r.size), connection);
CGContextEOFillPath(c);
}
/// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves)
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); CGContextSetFillColorWithColor(c, [NSColor menuBarIconColor].CGColor);
// background rounded rect
svgRoundedRect(c, 1, r, size * 0.4/2);
// RSS icon
SetContentScale(c, r.size, 11/16.0);
AddRSSIconPath(c, size, connection);
CGContextEOFillPath(c);
}
/// Draw RSS icon with orange gradient background
static void RoundedRSS_Gradient(CGRect r, NSColor *color) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGContextSetFillColorWithColor(c, NSColor.whiteColor.CGColor);
// background rounded rect
svgRoundedRect(c, 1, r, size * 0.4/2);
// Gradient // Gradient
CGContextSaveGState(c); CGContextSaveGState(c);
CGContextClip(c); CGContextClip(c);
DrawGradient(c, size, color); DrawGradient(c, size, color);
CGContextRestoreGState(c); CGContextRestoreGState(c);
// Bars // RSS icon
SetContentScale(c, r.size, 11/16.0);
AddRSSIconPath(c, size, YES); AddRSSIconPath(c, size, YES);
CGContextEOFillPath(c); CGContextEOFillPath(c);
} }
#pragma mark - Appearance Settings
/// Draw icon representing global `status bar icon` (rounded RSS icon with neighbor items)
static void Appearance_MenuBarIcon(CGRect r) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGContextSetFillColorWithColor(c, [NSColor controlTextColor].CGColor);
// menu bar
CGContextSetAlpha(c, .23);
const CGFloat barHeightInset = round(size*.06);
svgRect(c, 1, CGRectInset(r, 0, barHeightInset));
CGContextFillPath(c);
const CGFloat offset = round(size*.75);
const CGFloat iconInset = round(size*.2);
const CGFloat iconCorner = size*.12;
CGContextSetAlpha(c, .66);
// left neighbor
CGContextTranslateCTM(c, -offset, 0);
svgRoundedRect(c, 1, CGRectInset(r, iconInset, iconInset), iconCorner);
CGContextFillPath(c);
// right neighbor
CGContextTranslateCTM(c, +2*offset, 0);
svgRoundedRect(c, 1, CGRectInset(r, iconInset, iconInset), iconCorner);
CGContextFillPath(c);
// main icon
CGContextSetAlpha(c, 1);
CGContextTranslateCTM(c, -offset, 0);
svgRoundedRect(c, 1, CGRectInset(r, iconInset, iconInset), iconCorner);
SetContentScale(c, r.size, 7/16.0);
AddRSSIconPath(c, size, YES);
CGContextEOFillPath(c);
}
/// Draw icon representing `Main Menu` (menu bar)
static void Appearance_MainMenu(CGRect r) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGContextSetFillColorWithColor(c, [NSColor controlTextColor].CGColor);
// menu
svgRect(c, size/16, CGRectMake(0, 0, 16, 3));
svgRect(c, size/16, CGRectMake(5, 4, 9, 12));
svgRect(c, size/16, CGRectMake(6, 3, 7, 12));
// entries
svgRect(c, size/16, CGRectMake(6, 12, 6, 1));
svgRect(c, size/16, CGRectMake(6, 9, 6, 1));
svgRect(c, size/16, CGRectMake(6, 6, 6, 1));
CGContextEOFillPath(c);
}
/// Draw icon representing `FeedGroup` (folder)
static void Appearance_Group(CGRect r, BOOL withLine) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
// folder path
svgPath(c, size/16, "M3,13.5c-1.5,0-2.5-1-2.5-2.5V3.5c0-1.5.5-2,2-2h1.5c1.5,0,1.5,1,3,1h6c1.5,0,2.5,1,2.5,2.5v6c0,1.5-1,2.5-2.5,2.5H3Z");
// line
if (withLine) {
svgPath(c, size/16, "M1.5,5h13Z");
}
CGContextSetLineWidth(c, size * 1/16);
CGContextSetStrokeColorWithColor(c, [NSColor controlTextColor].CGColor);
CGContextStrokePath(c);
}
/// Draw icon representing `Feed` (group + RSS)
static void Appearance_Feed(CGRect r) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGContextSetFillColorWithColor(c, [NSColor controlTextColor].CGColor);
// folder
Appearance_Group(r, NO);
// rss icon
SetContentScale(c, r.size, 7/16.0);
AddRSSIconPath(c, size, YES);
CGContextFillPath(c);
}
/// Draw icon representing `Article` (RSS inside text document)
static void Appearance_Article(CGRect r) {
const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
CGContextSetFillColorWithColor(c, [NSColor controlTextColor].CGColor);
// text lines
svgRect(c, size/16, CGRectMake(0, 14, 16, 1));
svgRect(c, size/16, CGRectMake(0, 10, 16, 1));
svgRect(c, size/16, CGRectMake(9, 6, 7, 1));
svgRect(c, size/16, CGRectMake(9, 2, 7, 1));
// picture
//svgRect(c, size/16, CGRectMake(1, 1, 7, 7));
// RSS icon
CGContextTranslateCTM(c, size/16 * 1, size/16 * 1); // same offset as picture
CGContextScaleCTM(c, 7/16.0, 7/16.0); // same size as picture
AddRSSIconPath(c, size, YES);
CGContextEOFillPath(c);
}
#pragma mark - Other Icons
/// Draw unread icon (blue dot for unread menu item) /// Draw unread icon (blue dot for unread menu item)
static void DrawUnreadIcon(CGRect r, NSColor *color) { static void DrawUnreadIcon(CGRect r, NSColor *color) {
CGFloat size = ShorterSide(r.size) / 2.0; const CGFloat radius = 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.7); SetContentScale(c, r.size, 0.7);
CGContextTranslateCTM(c, 0, size * -0.15); // align with baseline of menu item text CGContextTranslateCTM(c, 0, radius * -0.15); // align with baseline of menu item text
// outer ring (opaque)
CGContextSetFillColorWithColor(c, color.CGColor); CGContextSetFillColorWithColor(c, color.CGColor);
PathAddRing(path, size, size * 0.7); CGPathAddArc(path, NULL, radius, radius, radius, 0, M_PI * 2, YES);
CGPathAddArc(path, NULL, radius, radius, radius*.7, 0, M_PI * -2, YES);
CGContextAddPath(c, path); CGContextAddPath(c, path);
CGContextEOFillPath(c); CGContextEOFillPath(c);
// inner circle (translucent)
CGContextSetFillColorWithColor(c, [color colorWithAlphaComponent:0.5].CGColor); CGContextSetFillColorWithColor(c, [color colorWithAlphaComponent:0.5].CGColor);
PathAddCircle(path, size); CGPathAddArc(path, NULL, radius, radius, radius, 0, M_PI * 2, YES);
CGContextAddPath(c, path); CGContextAddPath(c, path);
CGContextFillPath(c); CGContextFillPath(c);
CGPathRelease(path); CGPathRelease(path);
} }
/// Draw "(.*)" as vector path /// Draw `(.*)` as vector path
static void DrawRegexIcon(CGRect r) { static void DrawRegexIcon(CGRect r) {
const CGFloat size = ShorterSide(r.size); const CGFloat size = ShorterSide(r.size);
CGContextRef c = NSGraphicsContext.currentContext.CGContext; CGContextRef c = NSGraphicsContext.currentContext.CGContext;
svgAddRect(c, 1, r, .2 * size); // background
CGContextSetFillColorWithColor(c, NSColor.redColor.CGColor); CGContextSetFillColorWithColor(c, NSColor.redColor.CGColor);
svgRoundedRect(c, 1, r, size * 0.4/2);
CGContextFillPath(c); CGContextFillPath(c);
// SVG files use bottom-left corner coordinate system. Quartz uses top-left. // foreground
FlipCoordinateSystem(c, r.size.height);
SetContentScale(c, r.size, 0.8);
// "("
svgAddPath(c, size/1000, "m184 187c-140 205-134 432-1 622l-66 44c-159-221-151-499 0-708z");
// "."
svgAddCircle(c, size/1000, 315, 675, 70, NO);
// "*"
svgAddPath(c, size/1000, "m652 277 107-35 21 63-109 36 68 92-54 39-68-93-66 91-52-41 67-88-109-37 21-63 108 37v-113h66v112z");
// ")"
svgAddPath(c, size/1000, "m816 813c140-205 134-430 1-621l66-45c159 221 151 499 0 708z");
CGContextSetFillColorWithColor(c, NSColor.whiteColor.CGColor); CGContextSetFillColorWithColor(c, NSColor.whiteColor.CGColor);
SetContentScale(c, r.size, 25/32.0);
// "("
svgPath(c, size/100, "M18,19c-14,21-13,43,0,62l-7,4C-4,63-4,35,12,14l6,5Z");
// "."
svgCircle(c, size/100, 31, 67, 7, NO);
// "*"
svgPath(c, size/100, "M65,28l11-4,2,6-11,4,7,9-5,4-7-9-7,9-5-4,7-9-11-4,2-6,11,4v-11h6v11Z");
// ")"
svgPath(c, size/100, "M82,81c14-21,13-43,0-62l7-5c16,22,15,50,0,71l-7-4Z");
CGContextFillPath(c); CGContextFillPath(c);
} }
@@ -301,19 +277,25 @@ static void DrawRegexIcon(CGRect r) {
/// Add single image to @c ImageNamed cache and set accessibility description /// Add single image to @c ImageNamed cache and set accessibility description
static void Register(CGFloat size, NSImageName name, NSString *description, BOOL (^draw)(NSRect r)) { static void Register(CGFloat size, NSImageName name, NSString *description, BOOL (^draw)(NSRect r)) {
NSImage *img = [NSImage imageWithSize: NSMakeSize(size, size) flipped:NO drawingHandler:draw]; NSImage *img = [NSImage imageWithSize: NSMakeSize(size, size) flipped:YES drawingHandler:draw];
img.accessibilityDescription = description; img.accessibilityDescription = description;
img.name = name; img.name = name;
} }
/// 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) {
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, [NSColor rssOrange]); return YES; }); // Default feed icon (fallback icon if no favicon found)
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; }); Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"Default feed icon", nil), ^(NSRect r) { RoundedRSS_Gradient(r, [NSColor rssOrange]); return YES; });
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; }); // Menu bar icon
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor controlTextColor].CGColor, NO, YES); return YES; }); Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"Menu bar icon", nil), ^(NSRect r) { RoundedRSS_Monochrome(r, YES); return YES; });
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, YES); return YES; }); Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"Menu bar icon, paused", nil), ^(NSRect r) { RoundedRSS_Monochrome(r, NO); return YES; });
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, NO); return YES; }); // Appearance settings
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor unreadIndicatorColor]); return YES; }); Register(16, RSSImageSettingsGlobalIcon, NSLocalizedString(@"Global settings, menu bar icon", nil), ^(NSRect r) { Appearance_MenuBarIcon(r); return YES; });
Register(16, RSSImageSettingsGlobalMenu, NSLocalizedString(@"Global settings, main menu", nil), ^(NSRect r) { Appearance_MainMenu(r); return YES; });
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { Appearance_Group(r, YES); return YES; });
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { Appearance_Feed(r); return YES; });
Register(16, RSSImageSettingsArticle, NSLocalizedString(@"Article settings", nil), ^(NSRect r) { Appearance_Article(r); return YES; });
// Other settings
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread indicator", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor unreadIndicatorColor]); return YES; });
Register(32, RSSImageRegexIcon, NSLocalizedString(@"Regex icon", nil), ^(NSRect r) { DrawRegexIcon(r); return YES; }); Register(32, RSSImageRegexIcon, NSLocalizedString(@"Regex icon", nil), ^(NSRect r) { DrawRegexIcon(r); return YES; });
} }

View File

@@ -1,5 +1,6 @@
@import Cocoa; @import Cocoa;
void svgAddPath(CGContextRef context, CGFloat scale, const char * path); void svgPath(CGContextRef context, CGFloat scale, const char * path);
void svgAddCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise); void svgCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise);
void svgAddRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius); void svgRoundedRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius);
void svgRect(CGContextRef context, CGFloat scale, CGRect rect);

View File

@@ -64,10 +64,16 @@ static void finishOp(CGMutablePathRef path, struct SVGState *state) {
state->y = state->num[1]; state->y = state->num[1];
CGPathAddLineToPoint(path, NULL, state->x * state->scale, state->y * state->scale); CGPathAddLineToPoint(path, NULL, state->x * state->scale, state->y * state->scale);
} else if (op == 'Q' && state->iNum == 4) {
state->x = state->num[2];
state->y = state->num[3];
CGPathAddQuadCurveToPoint(path, NULL, state->num[0] * state->scale, state->num[1] * state->scale, state->x * state->scale, state->y * state->scale);
} else if (op == 'C' && state->iNum == 6) { } else if (op == 'C' && state->iNum == 6) {
state->x = state->num[4]; state->x = state->num[4];
state->y = state->num[5]; state->y = state->num[5];
CGPathAddCurveToPoint(path, NULL, state->num[0] * state->scale, state->num[1] * state->scale, state->num[2] * state->scale, state->num[3] * state->scale, state->x * state->scale, state->y * state->scale); CGPathAddCurveToPoint(path, NULL, state->num[0] * state->scale, state->num[1] * state->scale, state->num[2] * state->scale, state->num[3] * state->scale, state->x * state->scale, state->y * state->scale);
} else { } else {
NSLog(@"Unsupported SVG operation %c %d", state->op, state->iNum); NSLog(@"Unsupported SVG operation %c %d", state->op, state->iNum);
} }
@@ -124,6 +130,8 @@ static void tinySVG_parse(const char * code, CGFloat scale, CGMutablePathRef pat
finishOp(path, &state); finishOp(path, &state);
} else if (state.iNum == 2 && strchr("MmLl", state.op) != NULL) { } else if (state.iNum == 2 && strchr("MmLl", state.op) != NULL) {
finishOp(path, &state); finishOp(path, &state);
} else if (state.iNum == 4 && strchr("Qq", state.op) != NULL) {
finishOp(path, &state);
} else if (state.iNum == 6 && strchr("Cc", state.op) != NULL) { } else if (state.iNum == 6 && strchr("Cc", state.op) != NULL) {
finishOp(path, &state); finishOp(path, &state);
} }
@@ -138,11 +146,17 @@ static void tinySVG_parse(const char * code, CGFloat scale, CGMutablePathRef pat
} }
} }
/// Helper method to scale `rect` according to svg size.
static inline CGRect scaledRect(CGRect rect, CGFloat scale) {
if (scale == 1.0) { return rect; }
return CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale);
}
# pragma mark - External API # pragma mark - External API
/// calls @c tinySVG_path and handles @c CGPath creation and release. /// calls @c tinySVG_path and handles @c CGPath creation and release.
void svgAddPath(CGContextRef context, CGFloat scale, const char * code) { void svgPath(CGContextRef context, CGFloat scale, const char * code) {
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
tinySVG_parse(code, scale, path); tinySVG_parse(code, scale, path);
CGContextAddPath(context, path); CGContextAddPath(context, path);
@@ -150,22 +164,24 @@ void svgAddPath(CGContextRef context, CGFloat scale, const char * code) {
} }
/// calls @c CGPathAddArc with full circle /// calls @c CGPathAddArc with full circle
void svgAddCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise) { void svgCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise) {
// No `CGContextAddArc` because that doesnt work well with overlapping counter-clockwise
CGMutablePathRef tmp = CGPathCreateMutable(); CGMutablePathRef tmp = CGPathCreateMutable();
CGPathAddArc(tmp, NULL, x * scale, y * scale, radius * scale, 0, M_PI * 2, clockwise); CGPathAddArc(tmp, NULL, x * scale, y * scale, radius * scale, 0, M_PI * 2, clockwise);
CGContextAddPath(context, tmp); CGContextAddPath(context, tmp);
CGPathRelease(tmp); CGPathRelease(tmp);
} }
/// Calls @c CGContextAddRect or @c CGPathAddRoundedRect (optional). /// Calls @c CGPathAddRoundedRect
/// @param cornerRadius Use @c <=0 for no corners. Use half of @c min(w,h) for a full circle. /// @param cornerRadius Use half of @c min(w,h) for a full circle.
void svgAddRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius) { void svgRoundedRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius) {
if (cornerRadius > 0) {
CGMutablePathRef tmp = CGPathCreateMutable(); CGMutablePathRef tmp = CGPathCreateMutable();
CGPathAddRoundedRect(tmp, NULL, rect, cornerRadius * scale, cornerRadius * scale); CGPathAddRoundedRect(tmp, NULL, scaledRect(rect, scale), cornerRadius * scale, cornerRadius * scale);
CGContextAddPath(context, tmp); CGContextAddPath(context, tmp);
CGPathRelease(tmp); CGPathRelease(tmp);
} else { }
CGContextAddRect(context, rect);
} /// Calls @c CGContextAddRect
void svgRect(CGContextRef context, CGFloat scale, CGRect rect) {
CGContextAddRect(context, scaledRect(rect, scale));
} }

View File

@@ -39,6 +39,7 @@
// ------ Hidden preferences ------ only modifiable via `defaults write de.relikd.baRSS {KEY}` ------ // ------ Hidden preferences ------ only modifiable via `defaults write de.relikd.baRSS {KEY}` ------
/** default: @c 10 */ static NSString* const Pref_openFewLinksLimit = @"openFewLinksLimit"; /** default: @c 10 */ static NSString* const Pref_openFewLinksLimit = @"openFewLinksLimit";
/** default: @c 60 */ static NSString* const Pref_shortArticleNamesLimit = @"shortArticleNamesLimit"; /** default: @c 60 */ static NSString* const Pref_shortArticleNamesLimit = @"shortArticleNamesLimit";
/** default: @c 2k */ static NSString* const Pref_tooltipCharacterLimit = @"tooltipCharacterLimit";
/** default: @c 40 */ static NSString* const Pref_articlesInMenuLimit = @"articlesInMenuLimit"; /** 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_colorStatusIconTint = @"colorStatusIconTint";
/** default: @c nil */ static NSString* const Pref_colorUnreadIndicator = @"colorUnreadIndicator"; /** default: @c nil */ static NSString* const Pref_colorUnreadIndicator = @"colorUnreadIndicator";

View File

@@ -29,6 +29,7 @@ void UserPrefsInit(void) {
// Display limits & truncation ( defaults write de.relikd.baRSS {KEY} -int 10 ) // Display limits & truncation ( defaults write de.relikd.baRSS {KEY} -int 10 )
[defs setObject:[NSNumber numberWithUnsignedInteger:10] forKey:Pref_openFewLinksLimit]; [defs setObject:[NSNumber numberWithUnsignedInteger:10] forKey:Pref_openFewLinksLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:60] forKey:Pref_shortArticleNamesLimit]; [defs setObject:[NSNumber numberWithUnsignedInteger:60] forKey:Pref_shortArticleNamesLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:2000] forKey:Pref_tooltipCharacterLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:40] forKey:Pref_articlesInMenuLimit]; [defs setObject:[NSNumber numberWithUnsignedInteger:40] forKey:Pref_articlesInMenuLimit];
[defs setObject:[NSNumber numberWithUnsignedInteger:1] forKey:Pref_prefSelectedTab]; // feed tab [defs setObject:[NSNumber numberWithUnsignedInteger:1] forKey:Pref_prefSelectedTab]; // feed tab
[[NSUserDefaults standardUserDefaults] registerDefaults:defs]; [[NSUserDefaults standardUserDefaults] registerDefaults:defs];

View File

@@ -18,7 +18,7 @@
- (instancetype)init { - (instancetype)init {
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)]; self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
// Insert matrix header (icons above checkbox matrix) // Insert matrix header (icons above checkbox matrix)
ColumnIcon(self, X__, RSSImageSettingsGlobal); ColumnIcon(self, X__, RSSImageSettingsGlobalMenu);
ColumnIcon(self, _X_, RSSImageSettingsGroup); ColumnIcon(self, _X_, RSSImageSettingsGroup);
ColumnIcon(self, __X, RSSImageSettingsFeed); ColumnIcon(self, __X, RSSImageSettingsFeed);
// Generate checkbox matrix // Generate checkbox matrix
@@ -37,7 +37,7 @@
c2:nil c2tt:nil c2:nil c2tt:nil
c3:nil c3tt:nil]; c3:nil c3tt:nil];
[self entry:NSLocalizedString(@"Toggle “Show Hidden Articles”", nil) [self entry:NSLocalizedString(@"Toggle “Show hidden articles”", nil)
help:NSLocalizedString(@"Show button in main menu to quickly toggle whether hidden articles should be shown. See option “Show only unread”.", nil) help:NSLocalizedString(@"Show button in main menu to quickly toggle whether hidden articles should be shown. See option “Show only unread”.", nil)
tip:nil tip:nil
c1:Pref_globalToggleHidden c1tt:NSLocalizedString(@"in main menu", nil) c1:Pref_globalToggleHidden c1tt:NSLocalizedString(@"in main menu", nil)
@@ -132,7 +132,7 @@ static inline NSButton* Checkbox(id this, CGFloat x, CGFloat y, NSString *key) {
if (pref3) [Checkbox(self, __X + 2, y + 2, pref3) tooltip:ttip3].accessibilityLabel = [label stringByAppendingString:@" (feed)"]; if (pref3) [Checkbox(self, __X + 2, y + 2, pref3) tooltip:ttip3].accessibilityLabel = [label stringByAppendingString:@" (feed)"];
if (extraTip != nil) { if (extraTip != nil) {
label = [label stringByAppendingString:@" *"]; label = [label stringByAppendingString:@" *"];
ttip = [ttip stringByAppendingFormat:@"\n\nTip: %@", extraTip]; ttip = [ttip stringByAppendingFormat:@"\n\n* Tip: %@", extraTip];
} }
return [[[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN] tooltip:ttip]; return [[[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN] tooltip:ttip];
} }

View File

@@ -176,14 +176,14 @@
- (void)insertMainMenuHeader:(NSMenu*)menu { - (void)insertMainMenuHeader:(NSMenu*)menu {
// 'Pause Updates' item // 'Pause Updates' item
NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause Updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""]; NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""];
pause.target = self; pause.target = self;
if ([UpdateScheduler isPaused]) if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume Updates", nil); pause.title = NSLocalizedString(@"Resume updates", nil);
// 'show hidden articles' item // 'show hidden articles' item
if (UserPrefsBool(Pref_globalToggleHidden)) { if (UserPrefsBool(Pref_globalToggleHidden)) {
NSMenuItem *toggleHidden = [menu addItemWithTitle:NSLocalizedString(@"Show Hidden Articles", nil) action:@selector(toggleHiddenArticles) keyEquivalent:@"h"]; NSMenuItem *toggleHidden = [menu addItemWithTitle:NSLocalizedString(@"Show hidden articles", nil) action:@selector(toggleHiddenArticles) keyEquivalent:@"h"];
toggleHidden.target = self; toggleHidden.target = self;
toggleHidden.enabled = !self.holdingOptKey && (UserPrefsBool(Pref_groupUnreadOnly) || UserPrefsBool(Pref_feedUnreadOnly)); toggleHidden.enabled = !self.holdingOptKey && (UserPrefsBool(Pref_groupUnreadOnly) || UserPrefsBool(Pref_feedUnreadOnly));
[toggleHidden setState:self.barMenu.showHidden ? NSControlStateValueOn : NSControlStateValueOff]; [toggleHidden setState:self.barMenu.showHidden ? NSControlStateValueOn : NSControlStateValueOff];