User preferences are considered for most menu items
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
543695D5214EFD9800DA979D /* NSMenuItem+Info.m in Sources */ = {isa = PBXBuildFile; fileRef = 543695D4214EFD9800DA979D /* NSMenuItem+Info.m */; };
|
||||
544B011A2114B41200386E5C /* ModalSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* ModalSheet.m */; };
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; };
|
||||
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 */; };
|
||||
546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC4462118A8E6007CC3A3 /* Preferences.xib */; };
|
||||
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 */; };
|
||||
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
|
||||
54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; };
|
||||
@@ -56,10 +57,10 @@
|
||||
/* End PBXCopyFilesBuildPhase 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -75,6 +76,8 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@@ -108,8 +111,8 @@
|
||||
541A90EF21257D4F002680A6 /* Status Bar Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
541A90F021257D77002680A6 /* MenuItemInfo.h */,
|
||||
541A90F121257D77002680A6 /* MenuItemInfo.m */,
|
||||
543695D3214EFD9800DA979D /* NSMenuItem+Info.h */,
|
||||
543695D4214EFD9800DA979D /* NSMenuItem+Info.m */,
|
||||
54FE73D1212316CD003EAC65 /* BarMenu.h */,
|
||||
54FE73D2212316CD003EAC65 /* BarMenu.m */,
|
||||
);
|
||||
@@ -128,6 +131,8 @@
|
||||
546FC44521189ADC007CC3A3 /* General Tab */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5496B50F214D6275003ED4ED /* UserPrefs.h */,
|
||||
5496B510214D6275003ED4ED /* UserPrefs.m */,
|
||||
546FC44021189975007CC3A3 /* SettingsGeneral.h */,
|
||||
546FC44121189975007CC3A3 /* SettingsGeneral.m */,
|
||||
546FC44221189975007CC3A3 /* SettingsGeneral.xib */,
|
||||
@@ -293,12 +298,13 @@
|
||||
54ACC28C21061B3C0020715F /* main.m in Sources */,
|
||||
54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */,
|
||||
544B011A2114B41200386E5C /* ModalSheet.m in Sources */,
|
||||
543695D5214EFD9800DA979D /* NSMenuItem+Info.m in Sources */,
|
||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */,
|
||||
5496B511214D6275003ED4ED /* UserPrefs.m in Sources */,
|
||||
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
|
||||
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */,
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */,
|
||||
54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */,
|
||||
541A90F221257D77002680A6 /* MenuItemInfo.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSMultipleInstancesProhibited</key>
|
||||
<true/>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "SettingsGeneral.h"
|
||||
#import "AppHook.h"
|
||||
#import "BarMenu.h"
|
||||
|
||||
@implementation SettingsGeneral
|
||||
|
||||
@@ -28,6 +30,29 @@
|
||||
[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
|
||||
|
||||
@end
|
||||
|
||||
@@ -34,101 +34,16 @@
|
||||
</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">
|
||||
<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.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="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">
|
||||
<action selector="changeMenuItemUpdateAllHidden:" target="-2" id="12D-ge-tzs"/>
|
||||
<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"/>
|
||||
@@ -144,6 +59,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="Tte-Vw-oMq"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalOpenUnread" id="c20-0p-cPb">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -160,6 +76,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="zRA-Ht-Qj1"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupOpenUnread" id="mCn-aE-DwT">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -176,6 +93,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="4sR-3H-A6H"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedOpenUnread" id="Qyh-BN-P74">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -184,22 +102,6 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</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">
|
||||
<rect key="frame" x="18" y="190" width="22" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
@@ -208,6 +110,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="gcu-x5-gUa"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkRead" id="uiO-3M-xfT">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -224,6 +127,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="rTt-3J-rkn"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkRead" id="YLZ-t8-Jbk">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -240,6 +144,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="2cM-mG-Lnw"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkRead" id="mYj-26-0OV">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -256,6 +161,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="anc-id-9sf"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalMarkUnread" id="drp-87-kfY">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -272,6 +178,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="98j-A6-A2m"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.groupMarkUnread" id="bJP-0I-l7t">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -288,6 +195,7 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuHeaderSetting:" target="-2" id="Muv-3Y-LU0"/>
|
||||
<binding destination="iU7-KA-nY5" name="value" keyPath="values.feedMarkUnread" id="mRu-7M-3bu">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
@@ -296,6 +204,74 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</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">
|
||||
<rect key="frame" x="96" y="235" width="206" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
@@ -332,6 +308,15 @@
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</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">
|
||||
<rect key="frame" x="96" y="125" width="206" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
@@ -341,6 +326,44 @@
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</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">
|
||||
<rect key="frame" x="20" y="260" width="18" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
@@ -377,15 +400,23 @@
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</customView>
|
||||
<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">
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Tec-F3-9cE">
|
||||
<rect key="frame" x="201" y="48" width="46" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeMenuBarIconSetting:" target="-2" id="HYX-cD-a5Z"/>
|
||||
<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>
|
||||
<point key="canvasLocation" x="140" y="-155.5"/>
|
||||
</customView>
|
||||
|
||||
28
baRSS/Preferences/General Tab/UserPrefs.h
Normal file
28
baRSS/Preferences/General Tab/UserPrefs.h
Normal 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
|
||||
38
baRSS/Preferences/General Tab/UserPrefs.m
Normal file
38
baRSS/Preferences/General Tab/UserPrefs.m
Normal 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
|
||||
@@ -24,4 +24,7 @@
|
||||
|
||||
@interface BarMenu : NSObject <NSMenuDelegate>
|
||||
- (void)rebuildMenu;
|
||||
- (void)updateBarIcon;
|
||||
- (void)updateMenuHeaders:(BOOL)recursive;
|
||||
- (void)setItemUpdateAllHidden:(BOOL)hidden;
|
||||
@end
|
||||
|
||||
@@ -24,27 +24,11 @@
|
||||
#import "StoreCoordinator.h"
|
||||
#import "DrawImage.h"
|
||||
#import "Preferences.h"
|
||||
#import "MenuItemInfo.h"
|
||||
#import "NSMenuItem+Info.h"
|
||||
#import "UserPrefs.h"
|
||||
|
||||
|
||||
@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) Preferences *prefWindow;
|
||||
@property (weak) NSMenu *mm;
|
||||
@@ -58,7 +42,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
self = [super init];
|
||||
self.barItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
|
||||
self.barItem.highlightMode = YES;
|
||||
self.barItem.menu = [self generateMainMenu];
|
||||
[self rebuildMenu];
|
||||
// [self donothing];
|
||||
return self;
|
||||
}
|
||||
@@ -77,12 +61,11 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
// TODO: remove debugging stuff
|
||||
- (void)printUnreadRecurisve:(NSMenu*)menu str:(NSString*)prefix {
|
||||
for (NSMenuItem *item in menu.itemArray) {
|
||||
MenuItemInfo *info = item.representedObject;
|
||||
if (!info) continue;
|
||||
id obj = [StoreCoordinator objectWithID:info.objID];
|
||||
if (![item hasReaderInfo]) continue;
|
||||
id obj = [item requestCoreDataObject];
|
||||
if ([obj isKindOfClass:[FeedItem class]] && ([obj unread] > 0 || item.unreadCount > 0))
|
||||
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);
|
||||
if (item.hasSubmenu) {
|
||||
[self printUnreadRecurisve:item.submenu str:[NSString stringWithFormat:@" %@", prefix]];
|
||||
@@ -95,14 +78,20 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
*/
|
||||
- (void)updateBarIcon {
|
||||
// TODO: Option: unread count in menubar, Option: highlight color, Option: icon choice
|
||||
if (self.unreadCountTotal > 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"globalUnreadCount"]) {
|
||||
self.barItem.title = [NSString stringWithFormat:@"%d", self.unreadCountTotal];
|
||||
self.barItem.image = [RSSIcon templateIcon:16 tint:[NSColor rssOrange]];
|
||||
} else {
|
||||
self.barItem.title = @"";
|
||||
}
|
||||
|
||||
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);
|
||||
// [self printUnreadRecurisve:self.barItem.menu str:@""];
|
||||
}
|
||||
@@ -118,7 +107,10 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
NSMenu *menu = [NSMenu new];
|
||||
menu.autoenablesItems = NO;
|
||||
[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]];
|
||||
[self defaultHeaderForMenu:menu scope:ScopeGlobal];
|
||||
|
||||
@@ -128,11 +120,13 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
[menu addItem:[self menuItemForFeedConfig:fc unread:&_unreadCountTotal]];
|
||||
}
|
||||
}
|
||||
[self updateMenuHeaderEnabled:menu hasUnread:(self.unreadCountTotal > 0)];
|
||||
[self updateBarIcon];
|
||||
|
||||
[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"];
|
||||
return menu;
|
||||
}
|
||||
@@ -148,7 +142,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
NSMenuItem *item;
|
||||
if (config.typ == SEPARATOR) {
|
||||
item = [NSMenuItem separatorItem];
|
||||
item.representedObject = [MenuItemInfo withID:config.objectID];
|
||||
[item setReaderInfo:config.objectID unread:0];
|
||||
return item;
|
||||
}
|
||||
int count = 0;
|
||||
@@ -158,9 +152,10 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
item = [self groupItem:config unread:&count];
|
||||
}
|
||||
*unread += count;
|
||||
item.representedObject = [MenuItemInfo withID:config.objectID];
|
||||
[item setReaderInfo:config.objectID unread:0];
|
||||
// !!!: fix that double count
|
||||
[item markReadAndUpdateTitle:-count];
|
||||
[self updateMenuHeader:item.submenu hasUnread:(count > 0)];
|
||||
[self updateMenuHeaderEnabled:item.submenu hasUnread:(count > 0)];
|
||||
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.
|
||||
*/
|
||||
- (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:@""];
|
||||
item.target = self;
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeLocal];
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeFeed];
|
||||
for (FeedItem *obj in config.feed.items) {
|
||||
if (obj.unread) ++(*unread);
|
||||
[item.submenu addItem:[self feedEntryItem:obj]];
|
||||
}
|
||||
item.toolTip = config.feed.subtitle;
|
||||
item.enabled = (config.feed.items.count > 0);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -195,17 +195,21 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
@param unread Pointer to an int that will be incremented for each unread item.
|
||||
*/
|
||||
- (NSMenuItem*)groupItem:(FeedConfig*)config unread:(int*)unread {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeGroup];
|
||||
for (FeedConfig *obj in config.sortedChildren) {
|
||||
[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);
|
||||
}
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
|
||||
item.image = groupIcon;
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeGroup];
|
||||
for (FeedConfig *obj in config.sortedChildren) {
|
||||
[item.submenu addItem: [self menuItemForFeedConfig:obj unread:unread]];
|
||||
}
|
||||
});
|
||||
item.tag = ScopeGroup;
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -215,7 +219,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
- (NSMenuItem*)feedEntryItem:(FeedItem*)item {
|
||||
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""];
|
||||
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;
|
||||
// TODO: Do regex during save, not during display. Its here for testing purposes ...
|
||||
if (item.abstract.length > 0) {
|
||||
@@ -224,10 +228,38 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
}
|
||||
mi.enabled = (item.link.length > 0);
|
||||
mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff);
|
||||
mi.tag = ScopeLocal;
|
||||
mi.tag = ScopeFeed;
|
||||
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.
|
||||
@@ -242,35 +274,46 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
menu = [NSMenu new];
|
||||
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 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]];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (void)updateMenuHeader:(NSMenu*)menu hasUnread:(BOOL)flag {
|
||||
// [menu itemWithTag:MenuItemTag_FeedMarkAllRead].enabled = flag;
|
||||
// [menu itemWithTag:MenuItemTag_FeedMarkAllUnread].enabled = !flag;
|
||||
// [menu itemWithTag:MenuItemTag_FeedOpenAllUnread].enabled = flag;
|
||||
- (void)setItemUpdateAllHidden:(BOOL)hidden {
|
||||
[self.barItem.menu itemWithTag:TagUpdateFeed].hidden = hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
Helper function to insert a menu item with @c target @c = @c self
|
||||
*/
|
||||
- (void)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;
|
||||
[menu addItem:item];
|
||||
- (void)updateMenuHeaders:(BOOL)recursive {
|
||||
[self updateMenuHeaderHidden:self.barItem.menu recursive:recursive];
|
||||
}
|
||||
|
||||
- (void)updateMenuHeaderHidden:(NSMenu*)menu recursive:(BOOL)flag {
|
||||
for (NSMenuItem *item in menu.itemArray) {
|
||||
[item applyUserSettingsDisplay];
|
||||
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;
|
||||
int total = [sender siblingsDescendantItemInfo:^int(NSMenuItem *item, int count) {
|
||||
if (item.tag & ScopeLocal) {
|
||||
if (item.tag & ScopeFeed) {
|
||||
if (stopAfter <= 0) return -1;
|
||||
--stopAfter;
|
||||
}
|
||||
@@ -386,17 +429,16 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
@param sender A menu item containing either a @c FeedItem or a @c FeedConfig.
|
||||
*/
|
||||
- (void)openFeedURL:(NSMenuItem*)sender {
|
||||
MenuItemInfo *info = sender.representedObject;
|
||||
if (![info isKindOfClass:[MenuItemInfo class]]) return;
|
||||
|
||||
id obj = [StoreCoordinator objectWithID:info.objID];
|
||||
if (!sender.hasReaderInfo)
|
||||
return;
|
||||
NSString *url = nil;
|
||||
id obj = [sender requestCoreDataObject];
|
||||
if ([obj isKindOfClass:[FeedConfig class]]) {
|
||||
url = [[(FeedConfig*)obj feed] link];
|
||||
} else if ([obj isKindOfClass:[FeedItem class]]) {
|
||||
FeedItem *feed = obj;
|
||||
url = [feed link];
|
||||
if (sender.hasUnread) {
|
||||
if ([sender hasUnread]) {
|
||||
feed.unread = NO;
|
||||
[sender markReadAndUpdateTitle:1];
|
||||
[self updateAcestors:sender markRead:1];
|
||||
@@ -421,22 +463,6 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
#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.
|
||||
|
||||
@@ -445,7 +471,9 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
*/
|
||||
- (void)siblingsDescendantFeedConfigs:(NSMenuItem*)sender block:(FeedConfigRecursiveItemsBlock)block {
|
||||
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 {
|
||||
// Sadly we can't just fetch the list of FeedItems since it is not ordered (in case open 10 at a time)
|
||||
@autoreleasepool {
|
||||
@@ -480,8 +508,8 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
@return Unread count for parent element (total count if parent is @c nil)
|
||||
*/
|
||||
- (int)getAncestorUnreadCount:(NSMenuItem*)sender {
|
||||
if ([sender.parentItem.representedObject isKindOfClass:[MenuItemInfo class]])
|
||||
return sender.parentItem.unreadCount;
|
||||
if ([sender.parentItem hasReaderInfo])
|
||||
return [sender.parentItem unreadCount];
|
||||
return self.unreadCountTotal;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -22,14 +22,28 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface MenuItemInfo : NSObject
|
||||
@property (strong) NSManagedObjectID *objID;
|
||||
+ (instancetype)withID:(NSManagedObjectID*)oid;
|
||||
+ (instancetype)withID:(NSManagedObjectID*)oid unread:(int)count;
|
||||
@end
|
||||
/// @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)
|
||||
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.
|
||||
|
||||
@@ -38,12 +52,16 @@
|
||||
@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.
|
||||
*/
|
||||
typedef int (^MenuItemInfoRecursiveBlock) (NSMenuItem *item, int count);
|
||||
typedef int (^ReaderInfoRecursiveBlock) (NSMenuItem *item, int count);
|
||||
|
||||
- (BOOL)hasUnread;
|
||||
- (int)unreadCount;
|
||||
- (BOOL)hasReaderInfo;
|
||||
- (void)setReaderInfo:(NSManagedObjectID*)oid unread:(int)count;
|
||||
- (id)requestCoreDataObject;
|
||||
- (void)applyUserSettingsDisplay;
|
||||
- (void)markReadAndUpdateTitle:(int)count;
|
||||
- (void)countInTitle:(BOOL)show;
|
||||
- (void)markAncestorsRead:(int)count;
|
||||
- (int)siblingsDescendantItemInfo:(MenuItemInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag;
|
||||
- (int)siblingsDescendantItemInfo:(ReaderInfoRecursiveBlock)block unreadEntriesOnly:(BOOL)flag;
|
||||
@end
|
||||
|
||||
275
baRSS/Status Bar Menu/NSMenuItem+Info.m
Normal file
275
baRSS/Status Bar Menu/NSMenuItem+Info.m
Normal 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
|
||||
Reference in New Issue
Block a user