User preferences are considered for most menu items

This commit is contained in:
relikd
2018-09-17 01:26:21 +02:00
parent ee74d651fe
commit 23c7645d36
11 changed files with 681 additions and 399 deletions

View File

@@ -7,8 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
541A90F221257D77002680A6 /* MenuItemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 541A90F121257D77002680A6 /* MenuItemInfo.m */; };
54209E942117325100F3B5EF /* DrawImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 54209E932117325100F3B5EF /* DrawImage.m */; }; 54209E942117325100F3B5EF /* DrawImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 54209E932117325100F3B5EF /* DrawImage.m */; };
543695D5214EFD9800DA979D /* NSMenuItem+Info.m in Sources */ = {isa = PBXBuildFile; fileRef = 543695D4214EFD9800DA979D /* NSMenuItem+Info.m */; };
544B011A2114B41200386E5C /* ModalSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* ModalSheet.m */; }; 544B011A2114B41200386E5C /* ModalSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* ModalSheet.m */; };
544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; }; 544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; };
544DCCB9212A2B4D002DBC46 /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML.framework */; }; 544DCCB9212A2B4D002DBC46 /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML.framework */; };
@@ -20,6 +20,7 @@
546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC44221189975007CC3A3 /* SettingsGeneral.xib */; }; 546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC44221189975007CC3A3 /* SettingsGeneral.xib */; };
546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC4462118A8E6007CC3A3 /* Preferences.xib */; }; 546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC4462118A8E6007CC3A3 /* Preferences.xib */; };
5477D34E21233C62002BA27F /* FeedConfig+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */; }; 5477D34E21233C62002BA27F /* FeedConfig+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */; };
5496B511214D6275003ED4ED /* UserPrefs.m in Sources */ = {isa = PBXBuildFile; fileRef = 5496B510214D6275003ED4ED /* UserPrefs.m */; };
54ACC28621061B3C0020715F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54ACC28521061B3C0020715F /* Assets.xcassets */; }; 54ACC28621061B3C0020715F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54ACC28521061B3C0020715F /* Assets.xcassets */; };
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; }; 54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; }; 54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; };
@@ -56,10 +57,10 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
541A90F021257D77002680A6 /* MenuItemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuItemInfo.h; sourceTree = "<group>"; };
541A90F121257D77002680A6 /* MenuItemInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuItemInfo.m; sourceTree = "<group>"; };
54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = "<group>"; }; 54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = "<group>"; };
54209E932117325100F3B5EF /* DrawImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawImage.m; sourceTree = "<group>"; }; 54209E932117325100F3B5EF /* DrawImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawImage.m; sourceTree = "<group>"; };
543695D3214EFD9800DA979D /* NSMenuItem+Info.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMenuItem+Info.h"; sourceTree = "<group>"; };
543695D4214EFD9800DA979D /* NSMenuItem+Info.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMenuItem+Info.m"; sourceTree = "<group>"; };
544B01182114B41200386E5C /* ModalSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalSheet.h; sourceTree = "<group>"; }; 544B01182114B41200386E5C /* ModalSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalSheet.h; sourceTree = "<group>"; };
544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = "<group>"; }; 544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = "<group>"; };
544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = "<group>"; }; 544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = "<group>"; };
@@ -75,6 +76,8 @@
546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; }; 546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
5477D34C21233C62002BA27F /* FeedConfig+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Ext.h"; sourceTree = "<group>"; }; 5477D34C21233C62002BA27F /* FeedConfig+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Ext.h"; sourceTree = "<group>"; };
5477D34D21233C62002BA27F /* FeedConfig+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedConfig+Ext.m"; sourceTree = "<group>"; }; 5477D34D21233C62002BA27F /* FeedConfig+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedConfig+Ext.m"; sourceTree = "<group>"; };
5496B50F214D6275003ED4ED /* UserPrefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPrefs.h; sourceTree = "<group>"; };
5496B510214D6275003ED4ED /* UserPrefs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserPrefs.m; sourceTree = "<group>"; };
54ACC27C21061B3B0020715F /* baRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = baRSS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54ACC27C21061B3B0020715F /* baRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = baRSS.app; sourceTree = BUILT_PRODUCTS_DIR; };
54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = "<group>"; }; 54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = "<group>"; };
54ACC28521061B3C0020715F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 54ACC28521061B3C0020715F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -108,8 +111,8 @@
541A90EF21257D4F002680A6 /* Status Bar Menu */ = { 541A90EF21257D4F002680A6 /* Status Bar Menu */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
541A90F021257D77002680A6 /* MenuItemInfo.h */, 543695D3214EFD9800DA979D /* NSMenuItem+Info.h */,
541A90F121257D77002680A6 /* MenuItemInfo.m */, 543695D4214EFD9800DA979D /* NSMenuItem+Info.m */,
54FE73D1212316CD003EAC65 /* BarMenu.h */, 54FE73D1212316CD003EAC65 /* BarMenu.h */,
54FE73D2212316CD003EAC65 /* BarMenu.m */, 54FE73D2212316CD003EAC65 /* BarMenu.m */,
); );
@@ -128,6 +131,8 @@
546FC44521189ADC007CC3A3 /* General Tab */ = { 546FC44521189ADC007CC3A3 /* General Tab */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5496B50F214D6275003ED4ED /* UserPrefs.h */,
5496B510214D6275003ED4ED /* UserPrefs.m */,
546FC44021189975007CC3A3 /* SettingsGeneral.h */, 546FC44021189975007CC3A3 /* SettingsGeneral.h */,
546FC44121189975007CC3A3 /* SettingsGeneral.m */, 546FC44121189975007CC3A3 /* SettingsGeneral.m */,
546FC44221189975007CC3A3 /* SettingsGeneral.xib */, 546FC44221189975007CC3A3 /* SettingsGeneral.xib */,
@@ -293,12 +298,13 @@
54ACC28C21061B3C0020715F /* main.m in Sources */, 54ACC28C21061B3C0020715F /* main.m in Sources */,
54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */, 54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */,
544B011A2114B41200386E5C /* ModalSheet.m in Sources */, 544B011A2114B41200386E5C /* ModalSheet.m in Sources */,
543695D5214EFD9800DA979D /* NSMenuItem+Info.m in Sources */,
54ACC29821061FBA0020715F /* Preferences.m in Sources */, 54ACC29821061FBA0020715F /* Preferences.m in Sources */,
5496B511214D6275003ED4ED /* UserPrefs.m in Sources */,
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */, 546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */, 54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */,
54209E942117325100F3B5EF /* DrawImage.m in Sources */, 54209E942117325100F3B5EF /* DrawImage.m in Sources */,
54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */, 54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */,
541A90F221257D77002680A6 /* MenuItemInfo.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>LSMultipleInstancesProhibited</key>
<true/>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key> <key>CFBundleName</key>

View File

@@ -21,6 +21,8 @@
// SOFTWARE. // SOFTWARE.
#import "SettingsGeneral.h" #import "SettingsGeneral.h"
#import "AppHook.h"
#import "BarMenu.h"
@implementation SettingsGeneral @implementation SettingsGeneral
@@ -28,6 +30,29 @@
[super viewDidLoad]; [super viewDidLoad];
} }
- (IBAction)checkmarkClicked:(NSButton*)sender {
// TODO: Could be optimized by updating only the relevant parts
[[(AppHook*)NSApp barMenu] rebuildMenu];
}
- (IBAction)changeMenuBarIconSetting:(NSButton*)sender {
[[(AppHook*)NSApp barMenu] updateBarIcon];
}
- (IBAction)changeMenuHeaderSetting:(NSButton*)sender {
BOOL recursive = YES;
NSString *bindingKey = [[sender infoForBinding:@"value"] valueForKey:NSObservedKeyPathKey];
if ([bindingKey containsString:@"values.global"]) {
recursive = NO; // item is in menu bar menu, no need to go recursive
}
[[(AppHook*)NSApp barMenu] updateMenuHeaders:recursive];
}
- (IBAction)changeMenuItemUpdateAllHidden:(NSButton*)sender {
BOOL checked = (sender.state == NSControlStateValueOn);
[[(AppHook*)NSApp barMenu] setItemUpdateAllHidden:!checked];
}
// TODO: show list of installed browsers and make menu choosable // TODO: show list of installed browsers and make menu choosable
@end @end

View File

@@ -34,101 +34,16 @@
</binding> </binding>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Z4-ko-oj2"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Eyy-w7-79K">
<rect key="frame" x="70" y="124" width="22" height="18"/> <rect key="frame" x="18" y="234" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="3cL-4f-90v"> <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Z56-ik-iHk">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedTickMark" id="xKL-Lh-tBL"> <action selector="changeMenuItemUpdateAllHidden:" target="-2" id="12D-ge-tzs"/>
<dictionary key="options"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.globalUpdateAll" id="FrQ-u0-lFo">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="36q-Dv-PLf">
<rect key="frame" x="18" y="49" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Menu bar icon:" id="Mkr-3d-gmN">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="usG-bX-431">
<rect key="frame" x="156" y="42" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="left" imageScaling="proportionallyDown" imageFrameStyle="grayBezel" image="NSBookmarksTemplate" id="QxY-0c-aDo"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2sG-NO-OJz">
<rect key="frame" x="18" y="22" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Open URL in:" id="vNb-i3-dvE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BcN-gW-jBg">
<rect key="frame" x="155" y="17" width="148" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Default" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="qW6-vv-pdE" id="R91-En-pHg">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="M0i-AE-1LS">
<items>
<menuItem title="Default" state="on" id="qW6-vv-pdE"/>
<menuItem title="Safari" id="RTt-ZJ-ZHv"/>
<menuItem title="other …" id="hSc-D3-fq8"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QYu-9A-RSx">
<rect key="frame" x="18" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="NHd-8Y-lgR">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalUnreadCount" id="dyl-pE-j2k">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iGf-ZI-o6t">
<rect key="frame" x="44" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="fhA-hO-eSv">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupUnreadCount" id="Mg5-xJ-L3n">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ezY-pZ-WN7">
<rect key="frame" x="70" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="PgG-Pm-s9M">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedUnreadCount" id="hnm-Q2-kbs">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/> <integer key="NSNullPlaceholder" value="1"/>
@@ -144,6 +59,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="Tte-Vw-oMq"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalOpenUnread" id="c20-0p-cPb"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.globalOpenUnread" id="c20-0p-cPb">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -160,6 +76,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="zRA-Ht-Qj1"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupOpenUnread" id="mCn-aE-DwT"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.groupOpenUnread" id="mCn-aE-DwT">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -176,6 +93,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="4sR-3H-A6H"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedOpenUnread" id="Qyh-BN-P74"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.feedOpenUnread" id="Qyh-BN-P74">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -184,22 +102,6 @@
</binding> </binding>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Eyy-w7-79K">
<rect key="frame" x="18" y="234" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Z56-ik-iHk">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalUpdateAll" id="FrQ-u0-lFo">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lAu-qx-vWl"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lAu-qx-vWl">
<rect key="frame" x="18" y="190" width="22" height="18"/> <rect key="frame" x="18" y="190" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
@@ -208,6 +110,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="gcu-x5-gUa"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkRead" id="uiO-3M-xfT"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkRead" id="uiO-3M-xfT">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -224,6 +127,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="rTt-3J-rkn"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkRead" id="YLZ-t8-Jbk"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkRead" id="YLZ-t8-Jbk">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -240,6 +144,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="2cM-mG-Lnw"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkRead" id="mYj-26-0OV"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkRead" id="mYj-26-0OV">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -256,6 +161,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="anc-id-9sf"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkUnread" id="drp-87-kfY"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkUnread" id="drp-87-kfY">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -272,6 +178,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="98j-A6-A2m"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkUnread" id="bJP-0I-l7t"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkUnread" id="bJP-0I-l7t">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -288,6 +195,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="changeMenuHeaderSetting:" target="-2" id="Muv-3Y-LU0"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkUnread" id="mRu-7M-3bu"> <binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkUnread" id="mRu-7M-3bu">
<dictionary key="options"> <dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -296,6 +204,74 @@
</binding> </binding>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QYu-9A-RSx">
<rect key="frame" x="18" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="NHd-8Y-lgR">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeMenuBarIconSetting:" target="-2" id="2Eq-PQ-ffr"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalUnreadCount" id="dyl-pE-j2k">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iGf-ZI-o6t">
<rect key="frame" x="44" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="fhA-hO-eSv">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkmarkClicked:" target="-2" id="PUq-gk-16h"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupUnreadCount" id="Mg5-xJ-L3n">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ezY-pZ-WN7">
<rect key="frame" x="70" y="146" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="PgG-Pm-s9M">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkmarkClicked:" target="-2" id="dfY-Sm-GHz"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedUnreadCount" id="hnm-Q2-kbs">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Z4-ko-oj2">
<rect key="frame" x="70" y="124" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="3cL-4f-90v">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkmarkClicked:" target="-2" id="hzW-x5-kBO"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedTickMark" id="xKL-Lh-tBL">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Fey-RP-7wF"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Fey-RP-7wF">
<rect key="frame" x="96" y="235" width="206" height="17"/> <rect key="frame" x="96" y="235" width="206" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@@ -332,6 +308,15 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tjr-Of-qZE">
<rect key="frame" x="96" y="147" width="206" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Number of unread items" id="JZc-eZ-uTe">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="K4q-A1-Fi9"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="K4q-A1-Fi9">
<rect key="frame" x="96" y="125" width="206" height="17"/> <rect key="frame" x="96" y="125" width="206" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@@ -341,6 +326,44 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="36q-Dv-PLf">
<rect key="frame" x="18" y="49" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Menu bar icon:" id="Mkr-3d-gmN">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="usG-bX-431">
<rect key="frame" x="156" y="42" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="left" imageScaling="proportionallyDown" imageFrameStyle="grayBezel" image="NSBookmarksTemplate" id="QxY-0c-aDo"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2sG-NO-OJz">
<rect key="frame" x="18" y="22" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Open URL in:" id="vNb-i3-dvE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BcN-gW-jBg">
<rect key="frame" x="155" y="17" width="148" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Default" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="qW6-vv-pdE" id="R91-En-pHg">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="M0i-AE-1LS">
<items>
<menuItem title="Default" state="on" id="qW6-vv-pdE"/>
<menuItem title="Safari" id="RTt-ZJ-ZHv"/>
<menuItem title="other …" id="hSc-D3-fq8"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<customView toolTip="Show in menu bar" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P4Y-i1-MGE" customClass="SettingsIconGlobal"> <customView toolTip="Show in menu bar" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P4Y-i1-MGE" customClass="SettingsIconGlobal">
<rect key="frame" x="20" y="260" width="18" height="18"/> <rect key="frame" x="20" y="260" width="18" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
@@ -377,15 +400,23 @@
</userDefinedRuntimeAttribute> </userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</customView> </customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tjr-Of-qZE"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Tec-F3-9cE">
<rect key="frame" x="96" y="147" width="206" height="17"/> <rect key="frame" x="201" y="48" width="46" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Number of unread items" id="JZc-eZ-uTe"> <buttonCell key="cell" type="check" title="Tint" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="19k-mc-RLe">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> </buttonCell>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <connections>
</textFieldCell> <action selector="changeMenuBarIconSetting:" target="-2" id="HYX-cD-a5Z"/>
</textField> <binding destination="iU7-KA-nY5" name="value" keyPath="values.tintMenuBarIcon" id="WON-AK-QIa">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
<integer key="NSNullPlaceholder" value="1"/>
</dictionary>
</binding>
</connections>
</button>
</subviews> </subviews>
<point key="canvasLocation" x="140" y="-155.5"/> <point key="canvasLocation" x="140" y="-155.5"/>
</customView> </customView>

View File

@@ -0,0 +1,28 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import <Foundation/Foundation.h>
@interface UserPrefs : NSObject
+ (BOOL)defaultYES:(NSString*)key;
+ (BOOL)defaultNO:(NSString*)key;
@end

View File

@@ -0,0 +1,38 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "UserPrefs.h"
@implementation UserPrefs
+ (BOOL)defaultYES:(NSString*)key {
if ([[NSUserDefaults standardUserDefaults] objectForKey:key] == NULL) {
return YES;
}
return [[NSUserDefaults standardUserDefaults] boolForKey:key];
}
+ (BOOL)defaultNO:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] boolForKey:key];
}
@end

View File

@@ -24,4 +24,7 @@
@interface BarMenu : NSObject <NSMenuDelegate> @interface BarMenu : NSObject <NSMenuDelegate>
- (void)rebuildMenu; - (void)rebuildMenu;
- (void)updateBarIcon;
- (void)updateMenuHeaders:(BOOL)recursive;
- (void)setItemUpdateAllHidden:(BOOL)hidden;
@end @end

View File

@@ -24,27 +24,11 @@
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "DrawImage.h" #import "DrawImage.h"
#import "Preferences.h" #import "Preferences.h"
#import "MenuItemInfo.h" #import "NSMenuItem+Info.h"
#import "UserPrefs.h"
@interface BarMenu() @interface BarMenu()
/// @c NSMenuItem options that are assigned to the @c tag attribute.
typedef NS_OPTIONS(NSInteger, MenuItemTag) {
/// Item visible at the very first menu level
ScopeGlobal = 2,
/// Item visible at each grouping, e.g., multiple feeds in one group
ScopeGroup = 4,
/// Item visible at the deepest menu level (@c FeedItem elements and header)
ScopeLocal = 8,
///
TagPreferences = (1 << 4),
TagPauseUpdates = (2 << 4),
TagUpdateFeed = (3 << 4),
TagMarkAllRead = (4 << 4),
TagMarkAllUnread = (5 << 4),
TagOpenAllUnread = (6 << 4),
};
@property (strong) NSStatusItem *barItem; @property (strong) NSStatusItem *barItem;
@property (strong) Preferences *prefWindow; @property (strong) Preferences *prefWindow;
@property (weak) NSMenu *mm; @property (weak) NSMenu *mm;
@@ -58,7 +42,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
self = [super init]; self = [super init];
self.barItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; self.barItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
self.barItem.highlightMode = YES; self.barItem.highlightMode = YES;
self.barItem.menu = [self generateMainMenu]; [self rebuildMenu];
// [self donothing]; // [self donothing];
return self; return self;
} }
@@ -77,12 +61,11 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
// TODO: remove debugging stuff // TODO: remove debugging stuff
- (void)printUnreadRecurisve:(NSMenu*)menu str:(NSString*)prefix { - (void)printUnreadRecurisve:(NSMenu*)menu str:(NSString*)prefix {
for (NSMenuItem *item in menu.itemArray) { for (NSMenuItem *item in menu.itemArray) {
MenuItemInfo *info = item.representedObject; if (![item hasReaderInfo]) continue;
if (!info) continue; id obj = [item requestCoreDataObject];
id obj = [StoreCoordinator objectWithID:info.objID];
if ([obj isKindOfClass:[FeedItem class]] && ([obj unread] > 0 || item.unreadCount > 0)) if ([obj isKindOfClass:[FeedItem class]] && ([obj unread] > 0 || item.unreadCount > 0))
NSLog(@"%@ %@ (%d == %d)", prefix, item.title, item.unreadCount, [obj unread]); NSLog(@"%@ %@ (%d == %d)", prefix, item.title, item.unreadCount, [obj unread]);
else if (item.hasUnread) else if ([item hasUnread])
NSLog(@"%@ %@ (%d)", prefix, item.title, item.unreadCount); NSLog(@"%@ %@ (%d)", prefix, item.title, item.unreadCount);
if (item.hasSubmenu) { if (item.hasSubmenu) {
[self printUnreadRecurisve:item.submenu str:[NSString stringWithFormat:@" %@", prefix]]; [self printUnreadRecurisve:item.submenu str:[NSString stringWithFormat:@" %@", prefix]];
@@ -95,14 +78,20 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
*/ */
- (void)updateBarIcon { - (void)updateBarIcon {
// TODO: Option: unread count in menubar, Option: highlight color, Option: icon choice // TODO: Option: unread count in menubar, Option: highlight color, Option: icon choice
if (self.unreadCountTotal > 0) { dispatch_async(dispatch_get_main_queue(), ^{
self.barItem.title = [NSString stringWithFormat:@"%d", self.unreadCountTotal]; if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"globalUnreadCount"]) {
self.barItem.image = [RSSIcon templateIcon:16 tint:[NSColor rssOrange]]; self.barItem.title = [NSString stringWithFormat:@"%d", self.unreadCountTotal];
} else { } else {
self.barItem.title = @""; self.barItem.title = @"";
self.barItem.image = [RSSIcon templateIcon:16 tint:nil]; }
self.barItem.image.template = YES;
} if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"tintMenuBarIcon"]) {
self.barItem.image = [RSSIcon templateIcon:16 tint:[NSColor rssOrange]];
} else {
self.barItem.image = [RSSIcon templateIcon:16 tint:nil];
self.barItem.image.template = YES;
}
});
// NSLog(@"==> %d", self.unreadCountTotal); // NSLog(@"==> %d", self.unreadCountTotal);
// [self printUnreadRecurisve:self.barItem.menu str:@""]; // [self printUnreadRecurisve:self.barItem.menu str:@""];
} }
@@ -118,7 +107,10 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
NSMenu *menu = [NSMenu new]; NSMenu *menu = [NSMenu new];
menu.autoenablesItems = NO; menu.autoenablesItems = NO;
[self addTitle:NSLocalizedString(@"Pause Updates", nil) selector:@selector(pauseUpdates:) toMenu:menu tag:TagPauseUpdates]; [self addTitle:NSLocalizedString(@"Pause Updates", nil) selector:@selector(pauseUpdates:) toMenu:menu tag:TagPauseUpdates];
[self addTitle:NSLocalizedString(@"Update all feeds", nil) selector:@selector(updateAllFeeds:) toMenu:menu tag:TagUpdateFeed]; NSMenuItem *updateAll = [self addTitle:NSLocalizedString(@"Update all feeds", nil) selector:@selector(updateAllFeeds:) toMenu:menu tag:TagUpdateFeed];
if ([UserPrefs defaultYES:@"globalUpdateAll"] == NO)
updateAll.hidden = YES;
[menu addItem:[NSMenuItem separatorItem]]; [menu addItem:[NSMenuItem separatorItem]];
[self defaultHeaderForMenu:menu scope:ScopeGlobal]; [self defaultHeaderForMenu:menu scope:ScopeGlobal];
@@ -128,11 +120,13 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
[menu addItem:[self menuItemForFeedConfig:fc unread:&_unreadCountTotal]]; [menu addItem:[self menuItemForFeedConfig:fc unread:&_unreadCountTotal]];
} }
} }
[self updateMenuHeaderEnabled:menu hasUnread:(self.unreadCountTotal > 0)];
[self updateBarIcon]; [self updateBarIcon];
[menu addItem:[NSMenuItem separatorItem]]; [menu addItem:[NSMenuItem separatorItem]];
[self addTitle:NSLocalizedString(@"Preferences", nil) selector:@selector(openPreferences) toMenu:menu tag:TagPreferences];
menu.itemArray.lastObject.keyEquivalent = @","; NSMenuItem *prefs = [self addTitle:NSLocalizedString(@"Preferences", nil) selector:@selector(openPreferences) toMenu:menu tag:TagPreferences];
prefs.keyEquivalent = @",";
[menu addItemWithTitle:NSLocalizedString(@"Quit", nil) action:@selector(terminate:) keyEquivalent:@"q"]; [menu addItemWithTitle:NSLocalizedString(@"Quit", nil) action:@selector(terminate:) keyEquivalent:@"q"];
return menu; return menu;
} }
@@ -148,7 +142,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
NSMenuItem *item; NSMenuItem *item;
if (config.typ == SEPARATOR) { if (config.typ == SEPARATOR) {
item = [NSMenuItem separatorItem]; item = [NSMenuItem separatorItem];
item.representedObject = [MenuItemInfo withID:config.objectID]; [item setReaderInfo:config.objectID unread:0];
return item; return item;
} }
int count = 0; int count = 0;
@@ -158,9 +152,10 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
item = [self groupItem:config unread:&count]; item = [self groupItem:config unread:&count];
} }
*unread += count; *unread += count;
item.representedObject = [MenuItemInfo withID:config.objectID]; [item setReaderInfo:config.objectID unread:0];
// !!!: fix that double count
[item markReadAndUpdateTitle:-count]; [item markReadAndUpdateTitle:-count];
[self updateMenuHeader:item.submenu hasUnread:(count > 0)]; [self updateMenuHeaderEnabled:item.submenu hasUnread:(count > 0)];
return item; return item;
} }
@@ -171,20 +166,25 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
@param unread Pointer to an int that will be incremented for each unread item. @param unread Pointer to an int that will be incremented for each unread item.
*/ */
- (NSMenuItem*)feedItem:(FeedConfig*)config unread:(int*)unread { - (NSMenuItem*)feedItem:(FeedConfig*)config unread:(int*)unread {
static NSImage *defaultRSSIcon;
if (!defaultRSSIcon)
defaultRSSIcon = [RSSIcon iconWithSize:16];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:@selector(openFeedURL:) keyEquivalent:@""]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:@selector(openFeedURL:) keyEquivalent:@""];
item.target = self; item.target = self;
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeLocal]; item.submenu = [self defaultHeaderForMenu:nil scope:ScopeFeed];
for (FeedItem *obj in config.feed.items) { for (FeedItem *obj in config.feed.items) {
if (obj.unread) ++(*unread); if (obj.unread) ++(*unread);
[item.submenu addItem:[self feedEntryItem:obj]]; [item.submenu addItem:[self feedEntryItem:obj]];
} }
item.toolTip = config.feed.subtitle; item.toolTip = config.feed.subtitle;
item.enabled = (config.feed.items.count > 0); item.enabled = (config.feed.items.count > 0);
item.image = defaultRSSIcon;
// set icon
dispatch_async(dispatch_get_main_queue(), ^{
static NSImage *defaultRSSIcon;
if (!defaultRSSIcon)
defaultRSSIcon = [RSSIcon iconWithSize:16];
item.image = defaultRSSIcon;
});
item.tag = ScopeFeed;
return item; return item;
} }
@@ -195,17 +195,21 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
@param unread Pointer to an int that will be incremented for each unread item. @param unread Pointer to an int that will be incremented for each unread item.
*/ */
- (NSMenuItem*)groupItem:(FeedConfig*)config unread:(int*)unread { - (NSMenuItem*)groupItem:(FeedConfig*)config unread:(int*)unread {
static NSImage *groupIcon;
if (!groupIcon) {
groupIcon = [NSImage imageNamed:NSImageNameFolder];
groupIcon.size = NSMakeSize(16, 16);
}
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
item.image = groupIcon;
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeGroup]; item.submenu = [self defaultHeaderForMenu:nil scope:ScopeGroup];
for (FeedConfig *obj in config.sortedChildren) { for (FeedConfig *obj in config.sortedChildren) {
[item.submenu addItem: [self menuItemForFeedConfig:obj unread:unread]]; [item.submenu addItem: [self menuItemForFeedConfig:obj unread:unread]];
} }
// set icon
dispatch_async(dispatch_get_main_queue(), ^{
static NSImage *groupIcon;
if (!groupIcon) {
groupIcon = [NSImage imageNamed:NSImageNameFolder];
groupIcon.size = NSMakeSize(16, 16);
}
item.image = groupIcon;
});
item.tag = ScopeGroup;
return item; return item;
} }
@@ -215,7 +219,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
- (NSMenuItem*)feedEntryItem:(FeedItem*)item { - (NSMenuItem*)feedEntryItem:(FeedItem*)item {
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""]; NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""];
mi.target = self; mi.target = self;
mi.representedObject = [MenuItemInfo withID:item.objectID unread:(item.unread ? 1 : 0)]; [mi setReaderInfo:item.objectID unread:(item.unread ? 1 : 0)];
//mi.toolTip = item.abstract; //mi.toolTip = item.abstract;
// TODO: Do regex during save, not during display. Its here for testing purposes ... // TODO: Do regex during save, not during display. Its here for testing purposes ...
if (item.abstract.length > 0) { if (item.abstract.length > 0) {
@@ -224,10 +228,38 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
} }
mi.enabled = (item.link.length > 0); mi.enabled = (item.link.length > 0);
mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff); mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff);
mi.tag = ScopeLocal; mi.tag = ScopeFeed;
return mi; return mi;
} }
/**
Helper function to insert a menu item with @c target @c = @c self
*/
- (NSMenuItem*)addTitle:(NSString*)title selector:(SEL)selector toMenu:(NSMenu*)menu tag:(MenuItemTag)tag {
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""];
item.target = self;
item.tag = tag;
[item applyUserSettingsDisplay];
[menu addItem:item];
return item;
}
/**
Helper function to copy an existing menu item and set the option key modifier
*/
- (NSMenuItem*)addAlternateItem:(NSMenuItem*)alternateParent withTitle:(NSString*)title toMenu:(NSMenu*)menu {
NSMenuItem *alt = [alternateParent copy];
alt.title = title;
alt.keyEquivalentModifierMask = NSEventModifierFlagOption;
if (!alt.hidden) // hidden will be ignored if alternate is YES
alt.alternate = YES;
[menu addItem:alt];
return alt;
}
#pragma mark - Default Menu Header Items
/** /**
Append header items to menu accoring to user preferences. Append header items to menu accoring to user preferences.
@@ -242,35 +274,46 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
menu = [NSMenu new]; menu = [NSMenu new];
menu.autoenablesItems = NO; menu.autoenablesItems = NO;
} }
// TODO: hide items according to preferences
NSMenuItem *item = [self addTitle:NSLocalizedString(@"Open all unread", nil) selector:@selector(openAllUnread:) toMenu:menu tag:TagOpenAllUnread | scope];
[self addAlternateItem:item withTitle:[NSString stringWithFormat:NSLocalizedString(@"Open a few unread (%d)", nil), 3] toMenu:menu];
[self addTitle:NSLocalizedString(@"Mark all read", nil) selector:@selector(markAllRead:) toMenu:menu tag:TagMarkAllRead | scope]; [self addTitle:NSLocalizedString(@"Mark all read", nil) selector:@selector(markAllRead:) toMenu:menu tag:TagMarkAllRead | scope];
[self addTitle:NSLocalizedString(@"Mark all unread", nil) selector:@selector(markAllUnread:) toMenu:menu tag:TagMarkAllUnread | scope]; [self addTitle:NSLocalizedString(@"Mark all unread", nil) selector:@selector(markAllUnread:) toMenu:menu tag:TagMarkAllUnread | scope];
[self addTitle:NSLocalizedString(@"Open all unread", nil) selector:@selector(openAllUnread:) toMenu:menu tag:TagOpenAllUnread | scope];
NSMenuItem *openSomeUrls = [menu.itemArray.lastObject copy];
openSomeUrls.title = [NSString stringWithFormat:NSLocalizedString(@"Open a few unread (%d)", nil), 3];
openSomeUrls.alternate = YES;
openSomeUrls.keyEquivalentModifierMask = NSEventModifierFlagOption;
[menu addItem:openSomeUrls];
[menu addItem:[NSMenuItem separatorItem]]; [menu addItem:[NSMenuItem separatorItem]];
return menu; return menu;
} }
- (void)updateMenuHeader:(NSMenu*)menu hasUnread:(BOOL)flag { - (void)setItemUpdateAllHidden:(BOOL)hidden {
// [menu itemWithTag:MenuItemTag_FeedMarkAllRead].enabled = flag; [self.barItem.menu itemWithTag:TagUpdateFeed].hidden = hidden;
// [menu itemWithTag:MenuItemTag_FeedMarkAllUnread].enabled = !flag;
// [menu itemWithTag:MenuItemTag_FeedOpenAllUnread].enabled = flag;
} }
/** - (void)updateMenuHeaders:(BOOL)recursive {
Helper function to insert a menu item with @c target @c = @c self [self updateMenuHeaderHidden:self.barItem.menu recursive:recursive];
*/ }
- (void)addTitle:(NSString*)title selector:(SEL)selector toMenu:(NSMenu*)menu tag:(MenuItemTag)tag {
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]; - (void)updateMenuHeaderHidden:(NSMenu*)menu recursive:(BOOL)flag {
item.target = self; for (NSMenuItem *item in menu.itemArray) {
item.tag = tag; [item applyUserSettingsDisplay];
[menu addItem:item]; if (flag && item.hasSubmenu) {
[self updateMenuHeaderHidden:item.submenu recursive:YES];
}
}
}
- (void)updateMenuHeaderEnabled:(NSMenu*)menu hasUnread:(BOOL)flag {
int stopAfter = 4; // 3 (+1 alternate)
for (NSMenuItem *item in menu.itemArray) {
switch (item.tag & TagMaskType) {
case TagMarkAllRead: item.enabled = flag; break;
case TagMarkAllUnread: item.enabled = !flag; break;
case TagOpenAllUnread: item.enabled = flag; break;
default: continue; // wrong tag, ignore
}
--stopAfter;
if (stopAfter < 0)
break; // break early after all header items have been processed
}
} }
@@ -330,7 +373,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
}]; }];
stopAfter = maxItemCount; stopAfter = maxItemCount;
int total = [sender siblingsDescendantItemInfo:^int(NSMenuItem *item, int count) { int total = [sender siblingsDescendantItemInfo:^int(NSMenuItem *item, int count) {
if (item.tag & ScopeLocal) { if (item.tag & ScopeFeed) {
if (stopAfter <= 0) return -1; if (stopAfter <= 0) return -1;
--stopAfter; --stopAfter;
} }
@@ -386,17 +429,16 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
@param sender A menu item containing either a @c FeedItem or a @c FeedConfig. @param sender A menu item containing either a @c FeedItem or a @c FeedConfig.
*/ */
- (void)openFeedURL:(NSMenuItem*)sender { - (void)openFeedURL:(NSMenuItem*)sender {
MenuItemInfo *info = sender.representedObject; if (!sender.hasReaderInfo)
if (![info isKindOfClass:[MenuItemInfo class]]) return; return;
id obj = [StoreCoordinator objectWithID:info.objID];
NSString *url = nil; NSString *url = nil;
id obj = [sender requestCoreDataObject];
if ([obj isKindOfClass:[FeedConfig class]]) { if ([obj isKindOfClass:[FeedConfig class]]) {
url = [[(FeedConfig*)obj feed] link]; url = [[(FeedConfig*)obj feed] link];
} else if ([obj isKindOfClass:[FeedItem class]]) { } else if ([obj isKindOfClass:[FeedItem class]]) {
FeedItem *feed = obj; FeedItem *feed = obj;
url = [feed link]; url = [feed link];
if (sender.hasUnread) { if ([sender hasUnread]) {
feed.unread = NO; feed.unread = NO;
[sender markReadAndUpdateTitle:1]; [sender markReadAndUpdateTitle:1];
[self updateAcestors:sender markRead:1]; [self updateAcestors:sender markRead:1];
@@ -421,22 +463,6 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
#pragma mark - Iterating over items and propagating unread count #pragma mark - Iterating over items and propagating unread count
/**
Perform a fetch request to the Core Data storage to retrieve the feed item associated with the @c representedObject.
@param sender The @c NSMenuItem that contains the Core Data reference.
@return Returns @c nil if the menu item has no @c representedObject or the contained class doesn't match.
*/
- (FeedConfig*)requestFeedConfigForMenuItem:(NSMenuItem*)sender {
MenuItemInfo *info = sender.representedObject;
if (![info isKindOfClass:[MenuItemInfo class]])
return nil;
id obj = [StoreCoordinator objectWithID:info.objID];
if (![obj isKindOfClass:[FeedConfig class]])
return nil;
return obj;
}
/** /**
Iterate over all feed items from siblings and contained children. Iterate over all feed items from siblings and contained children.
@@ -445,7 +471,9 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
*/ */
- (void)siblingsDescendantFeedConfigs:(NSMenuItem*)sender block:(FeedConfigRecursiveItemsBlock)block { - (void)siblingsDescendantFeedConfigs:(NSMenuItem*)sender block:(FeedConfigRecursiveItemsBlock)block {
if (sender.parentItem) { if (sender.parentItem) {
[[self requestFeedConfigForMenuItem:sender.parentItem] descendantFeedItems:block]; FeedConfig *obj = [sender requestCoreDataObject];
if ([obj isKindOfClass:[FeedConfig class]]) // important: this could be a FeedItem
[obj descendantFeedItems:block];
} else { } else {
// Sadly we can't just fetch the list of FeedItems since it is not ordered (in case open 10 at a time) // Sadly we can't just fetch the list of FeedItems since it is not ordered (in case open 10 at a time)
@autoreleasepool { @autoreleasepool {
@@ -480,8 +508,8 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
@return Unread count for parent element (total count if parent is @c nil) @return Unread count for parent element (total count if parent is @c nil)
*/ */
- (int)getAncestorUnreadCount:(NSMenuItem*)sender { - (int)getAncestorUnreadCount:(NSMenuItem*)sender {
if ([sender.parentItem.representedObject isKindOfClass:[MenuItemInfo class]]) if ([sender.parentItem hasReaderInfo])
return sender.parentItem.unreadCount; return [sender.parentItem unreadCount];
return self.unreadCountTotal; return self.unreadCountTotal;
} }

View File

@@ -1,168 +0,0 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "MenuItemInfo.h"
@interface MenuItemInfo()
/// internal counter used to sum the unread count of all sub items
@property (assign) int unreadCount;
/// internal flag whether unread count is displayed in parenthesis
@property (assign) BOOL countInTitle;
@end
@implementation MenuItemInfo
/// @return Info with unreadCount = 0
+ (instancetype)withID:(NSManagedObjectID*)oid {
return [MenuItemInfo withID:oid unread:0];
}
+ (instancetype)withID:(NSManagedObjectID*)oid unread:(int)count {
MenuItemInfo *info = [MenuItemInfo new];
info.objID = oid;
info.unreadCount = count;
return info;
}
/// @return @c YES if (unreadCount > 0)
- (BOOL)hasUnread {
return self.unreadCount > 0;
}
/// set: unreadCount -= count
- (void)markRead:(int)count {
if (count > self.unreadCount) {
NSLog(@"should never happen, trying to set an unread count below zero");
self.unreadCount = 0;
} else {
self.unreadCount -= count;
}
}
@end
@implementation NSMenuItem (MenuItemInfo)
/** Call represented object and check whether unread count > 0. */
- (BOOL)hasUnread {
return [self.representedObject unreadCount] > 0;
}
/** Call represented object and retrieve the unread count from info. */
- (int)unreadCount {
return [self.representedObject unreadCount];
}
/**
Update internal unread counter and append unread count to title.
@note Count may be negative to mark items as unread.
@warning Does not check if @c representedObject is set accordingly
@param count The amount by which the counter is adjusted.
If negative the items will be marked as unread.
*/
- (void)markReadAndUpdateTitle:(int)count {
if (count == 0) return; // 0 won't change anything
MenuItemInfo *info = self.representedObject;
if (!self.hasSubmenu) {
[info markRead:count];
self.state = (info.hasUnread ? NSControlStateValueOn : NSControlStateValueOff);
} else {
int countBefore = info.unreadCount;
[info markRead:count];
if (info.countInTitle) {
int digitsBefore = (int)log10f(countBefore) + 1;
NSInteger index = (NSInteger)self.title.length - digitsBefore - 3; // " (%d)"
if (index < 0) index = 0;
self.title = [self.title substringToIndex:(NSUInteger)index]; // remove old count
info.countInTitle = NO;
}
if (info.unreadCount > 0) {
self.title = [self.title stringByAppendingFormat:@" (%d)", info.unreadCount];
info.countInTitle = YES;
}
}
}
/**
Recursively propagate unread count to ancestor menu items.
@note Does not update the current item, only the ancestors.
@param count The amount by which the counter is adjusted.
If negative the items will be marked as unread.
*/
- (void)markAncestorsRead:(int)count {
NSMenuItem *parent = self.parentItem;
while (parent.representedObject) {
[parent markReadAndUpdateTitle:count];
parent = parent.parentItem;
}
}
/**
Recursively iterate over submenues and children. Count aggregated element edits.
@warning Block will be called for parent items, too. Consider this when using counters.
@param block Will be called for each @c NSMenuItem sub-element where @c representedObject is set to a @c MenuItemInfo.
Return -1 to stop processing early.
@param flag If set to @c YES, recursive calls will be skipped for submenus that contain soleily read elements.
@return The number of changed elements in total.
*/
- (int)descendantItemInfo:(MenuItemInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag {
MenuItemInfo *info = self.representedObject;
if (![info isKindOfClass:[MenuItemInfo class]]) return 0;
if (flag && !info.hasUnread) return 0;
if (self.isSeparatorItem) return 0;
int countItems = 1; // deepest entry, FeedItem
if (self.hasSubmenu) {
countItems = 0;
for (NSMenuItem *child in self.submenu.itemArray) {
int c = [child descendantItemInfo:block unreadEntriesOnly:flag];
if (c < 0) break;
countItems += c;
}
}
return block(self, countItems);
}
/**
Recursively iterate over siblings and all contained children. Count aggregated element edits.
@warning Block will be called for parent items, too. Consider this when using counters.
@param block Will be called for each @c NSMenuItem sub-element where @c representedObject is set to a @c MenuItemInfo.
Return -1 to stop processing early.
@param flag If set to @c YES, recursive calls will be skipped for submenus that contain soleily read elements.
@return The number of changed elements in total.
*/
- (int)siblingsDescendantItemInfo:(MenuItemInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag {
int markedTotal = 0;
for (NSMenuItem *sibling in self.menu.itemArray) {
int marked = [sibling descendantItemInfo:block unreadEntriesOnly:flag];
if (marked < 0) break;
markedTotal += marked;
}
return markedTotal;
}
@end

View File

@@ -22,28 +22,46 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface MenuItemInfo : NSObject /// @c NSMenuItem options that are assigned to the @c tag attribute.
@property (strong) NSManagedObjectID *objID; typedef NS_OPTIONS(NSInteger, MenuItemTag) {
+ (instancetype)withID:(NSManagedObjectID*)oid; /// Item visible at the very first menu level
+ (instancetype)withID:(NSManagedObjectID*)oid unread:(int)count; ScopeGlobal = 2,
@end /// Item visible at each grouping, e.g., multiple feeds in one group
ScopeGroup = 4,
/// Item visible at the deepest menu level (@c FeedItem elements and header)
ScopeFeed = 8,
///
TagPreferences = (1 << 4),
TagPauseUpdates = (2 << 4),
TagUpdateFeed = (3 << 4),
TagMarkAllRead = (4 << 4),
TagMarkAllUnread = (5 << 4),
TagOpenAllUnread = (6 << 4),
TagMaskScope = 0xF,
TagMaskType = 0xFFF0,
};
@interface NSMenuItem (MenuItemInfo) @interface NSMenuItem (Info)
/** /**
Iteration block for descendants of @c NSMenuItem. Iteration block for descendants of @c NSMenuItem.
@param count The number of sub-elements contained in that @c NSMenuItem. 1 for @c FeedItems at the deepest layer. @param count The number of sub-elements contained in that @c NSMenuItem. 1 for @c FeedItems at the deepest layer.
Otherwise the number of (updated) descendants. Otherwise the number of (updated) descendants.
@return Return how many elements are updated in this block execution. If none were changed return @c 0. @return Return how many elements are updated in this block execution. If none were changed return @c 0.
If execution should be stopped early, return @c -1. If execution should be stopped early, return @c -1.
*/ */
typedef int (^MenuItemInfoRecursiveBlock) (NSMenuItem *item, int count); typedef int (^ReaderInfoRecursiveBlock) (NSMenuItem *item, int count);
- (BOOL)hasUnread; - (BOOL)hasUnread;
- (int)unreadCount; - (int)unreadCount;
- (BOOL)hasReaderInfo;
- (void)setReaderInfo:(NSManagedObjectID*)oid unread:(int)count;
- (id)requestCoreDataObject;
- (void)applyUserSettingsDisplay;
- (void)markReadAndUpdateTitle:(int)count; - (void)markReadAndUpdateTitle:(int)count;
- (void)countInTitle:(BOOL)show;
- (void)markAncestorsRead:(int)count; - (void)markAncestorsRead:(int)count;
- (int)siblingsDescendantItemInfo:(MenuItemInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag; - (int)siblingsDescendantItemInfo:(ReaderInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag;
@end @end

View File

@@ -0,0 +1,275 @@
//
// The MIT License (MIT)
// Copyright (c) 2018 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "NSMenuItem+Info.h"
#import "UserPrefs.h"
#import "StoreCoordinator.h"
/// User preferences for displaying menu items
typedef NS_ENUM(char, DisplaySetting) {
/// User preference not available. @c NSMenuItem is not configurable (not a header item)
INVALID,
/// User preference to display this item
ALLOW,
/// User preference to hide this item
PROHIBIT
};
@interface ReaderInfo : NSObject
@property (strong) NSManagedObjectID *objID;
/// internal counter used to sum the unread count of all sub items
@property (assign) int unreadCount;
/// internal flag whether unread count is displayed in parenthesis
@property (assign) BOOL countInTitle;
@end
@implementation ReaderInfo
/// set: unreadCount -= count
- (void)markRead:(int)count {
if (count > self.unreadCount) {
NSLog(@"should never happen, trying to set an unread count below zero");
self.unreadCount = 0;
} else {
self.unreadCount -= count;
}
}
@end
// ################################################################
// #
// # NSMenuItem ReaderInfo Extension
// #
// ################################################################
@implementation NSMenuItem (Info)
/** Call represented object and check whether unread count > 0. */
- (BOOL)hasUnread {
return [(ReaderInfo*)self.representedObject unreadCount] > 0;
}
/** Call represented object and retrieve the unread count from info. */
- (int)unreadCount {
return [(ReaderInfo*)self.representedObject unreadCount];
}
/** Return @c YES if @c ReaderInfo is stored in @c representedObject. */
- (BOOL)hasReaderInfo {
return [self.representedObject isKindOfClass:[ReaderInfo class]];
}
/**
Save represented core data object in @c ReaderInfo.
@param oid Represented core data object id.
@param count Unread count for item.
*/
- (void)setReaderInfo:(NSManagedObjectID*)oid unread:(int)count {
ReaderInfo *info = [ReaderInfo new];
info.objID = oid;
info.unreadCount = count;
self.representedObject = info;
}
/**
Return represented core data object. Return @c nil if @c ReaderInfo is missing.
*/
- (id)requestCoreDataObject {
if (![self hasReaderInfo])
return nil;
return [StoreCoordinator objectWithID: [(ReaderInfo*)self.representedObject objID]];
}
/**
Check user preferences for preferred display style.
@return As per user settings return @c ALLOW or @c PROHIBIT. Will return @c INVALID for items that aren't configurable.
*/
- (DisplaySetting)allowsDisplay {
NSString *prefix;
switch (self.tag & TagMaskScope) {
case ScopeFeed: prefix = @"feed"; break;
case ScopeGroup: prefix = @"group"; break;
case ScopeGlobal: prefix = @"global"; break;
default: return INVALID; // no scope, not recognized menu item
}
NSString *postfix;
switch (self.tag & TagMaskType) {
case TagOpenAllUnread: postfix = @"OpenUnread"; break;
case TagMarkAllRead: postfix = @"MarkRead"; break;
case TagMarkAllUnread: postfix = @"MarkUnread"; break;
default: return INVALID; // wrong tag, ignore
}
if ([UserPrefs defaultYES:[prefix stringByAppendingString:postfix]])
return ALLOW;
return PROHIBIT;
}
/**
Set item @c hidden based on user preferences. Does nothing for items that aren't configurable in settings.
*/
- (void)applyUserSettingsDisplay {
switch ([self allowsDisplay]) {
case ALLOW:
self.hidden = NO;
if (self.keyEquivalentModifierMask == NSEventModifierFlagOption)
self.alternate = YES; // restore alternate flag
break;
case PROHIBIT:
if (self.isAlternate)
self.alternate = NO; // to allow hidden = YES, alternate flag needs to be NO
self.hidden = YES;
break;
case INVALID: break;
}
}
/**
Update internal unread counter and append unread count to title.
@note Count may be negative to mark items as unread.
@warning Does not check if @c representedObject is set accordingly
@param count The amount by which the counter is adjusted.
If negative the items will be marked as unread.
*/
- (void)markReadAndUpdateTitle:(int)count {
if (count == 0) return; // 0 won't change anything
ReaderInfo *info = self.representedObject;
if (!self.hasSubmenu) {
[info markRead:count];
self.state = ([self hasUnread] ? NSControlStateValueOn : NSControlStateValueOff);
} else {
int countBefore = info.unreadCount;
[info markRead:count];
if (info.countInTitle) {
[self removeUnreadCountFromTitle:countBefore];
info.countInTitle = NO;
}
[self addUnreadCountToTitle];
}
}
/**
Update title without changing internal unread count. Save to call multiple times.
@param show Whether to show or hide count
*/
- (void)countInTitle:(BOOL)show {
ReaderInfo *info = self.representedObject;
NSLog(@"%@", info);
return;
if (!show && info.countInTitle) {
[self removeUnreadCountFromTitle: info.unreadCount];
info.countInTitle = NO;
} else if (show && !info.countInTitle) {
[self addUnreadCountToTitle];
}
}
/**
Update title after unread count has changed
@param countBefore The count before the update
*/
- (void)removeUnreadCountFromTitle:(int)countBefore {
int digitsBefore = (int)log10f(countBefore) + 1;
NSInteger index = (NSInteger)self.title.length - digitsBefore - 3; // " (%d)"
if (index < 0) index = 0;
self.title = [self.title substringToIndex:(NSUInteger)index]; // remove old count
}
/**
Append count in parenthesis if thats allowed for the current scope (user settings)
*/
- (void)addUnreadCountToTitle {
ReaderInfo *info = self.representedObject;
if (info.unreadCount > 0 &&
(((self.tag & ScopeGroup) && [UserPrefs defaultYES:@"groupUnreadCount"]) ||
((self.tag & ScopeFeed) && [UserPrefs defaultYES:@"feedUnreadCount"])))
{
self.title = [self.title stringByAppendingFormat:@" (%d)", info.unreadCount];
info.countInTitle = YES;
}
}
/**
Recursively propagate unread count to ancestor menu items.
@note Does not update the current item, only the ancestors.
@param count The amount by which the counter is adjusted.
If negative the items will be marked as unread.
*/
- (void)markAncestorsRead:(int)count {
NSMenuItem *parent = self.parentItem;
while (parent.representedObject) {
[parent markReadAndUpdateTitle:count];
parent = parent.parentItem;
}
}
/**
Recursively iterate over submenues and children. Count aggregated element edits.
@warning Block will be called for parent items, too. Consider this when using counters.
@param block Will be called for each @c NSMenuItem sub-element where @c representedObject is set to a @c ReaderInfo.
Return -1 to stop processing early.
@param flag If set to @c YES, recursive calls will be skipped for submenus that contain soleily read elements.
@return The number of changed elements in total.
*/
- (int)descendantItemInfo:(ReaderInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag {
if (self.isSeparatorItem) return 0;
if (![self hasReaderInfo]) return 0;
if (flag && ![self hasUnread]) return 0;
int countItems = 1; // deepest entry, FeedItem
if (self.hasSubmenu) {
countItems = 0;
for (NSMenuItem *child in self.submenu.itemArray) {
int c = [child descendantItemInfo:block unreadEntriesOnly:flag];
if (c < 0) break;
countItems += c;
}
}
return block(self, countItems);
}
/**
Recursively iterate over siblings and all contained children. Count aggregated element edits.
@warning Block will be called for parent items, too. Consider this when using counters.
@param block Will be called for each @c NSMenuItem sub-element where @c representedObject is set to a @c ReaderInfo.
Return -1 to stop processing early.
@param flag If set to @c YES, recursive calls will be skipped for submenus that contain soleily read elements.
@return The number of changed elements in total.
*/
- (int)siblingsDescendantItemInfo:(ReaderInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag {
int markedTotal = 0;
for (NSMenuItem *sibling in self.menu.itemArray) {
int marked = [sibling descendantItemInfo:block unreadEntriesOnly:flag];
if (marked < 0) break;
markedTotal += marked;
}
return markedTotal;
}
@end