Menu generation refactored
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 54209E932117325100F3B5EF /* DrawImage.m */; };
|
||||
543695D5214EFD9800DA979D /* NSMenuItem+Info.m in Sources */ = {isa = PBXBuildFile; fileRef = 543695D4214EFD9800DA979D /* NSMenuItem+Info.m */; };
|
||||
543695D8214F1F2700DA979D /* NSMenuItem+Generate.m in Sources */ = {isa = PBXBuildFile; fileRef = 543695D7214F1F2700DA979D /* NSMenuItem+Generate.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 */; };
|
||||
@@ -61,6 +62,8 @@
|
||||
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>"; };
|
||||
543695D6214F1F2700DA979D /* NSMenuItem+Generate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMenuItem+Generate.h"; sourceTree = "<group>"; };
|
||||
543695D7214F1F2700DA979D /* NSMenuItem+Generate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMenuItem+Generate.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>"; };
|
||||
@@ -113,6 +116,8 @@
|
||||
children = (
|
||||
543695D3214EFD9800DA979D /* NSMenuItem+Info.h */,
|
||||
543695D4214EFD9800DA979D /* NSMenuItem+Info.m */,
|
||||
543695D6214F1F2700DA979D /* NSMenuItem+Generate.h */,
|
||||
543695D7214F1F2700DA979D /* NSMenuItem+Generate.m */,
|
||||
54FE73D1212316CD003EAC65 /* BarMenu.h */,
|
||||
54FE73D2212316CD003EAC65 /* BarMenu.m */,
|
||||
);
|
||||
@@ -290,6 +295,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
543695D8214F1F2700DA979D /* NSMenuItem+Generate.m in Sources */,
|
||||
54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */,
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */,
|
||||
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "DrawImage.h"
|
||||
#import "Preferences.h"
|
||||
#import "NSMenuItem+Info.h"
|
||||
#import "NSMenuItem+Generate.h"
|
||||
#import "UserPrefs.h"
|
||||
|
||||
|
||||
@@ -117,7 +118,7 @@
|
||||
self.unreadCountTotal = 0;
|
||||
@autoreleasepool {
|
||||
for (FeedConfig *fc in [StoreCoordinator sortedFeedConfigItems]) {
|
||||
[menu addItem:[self menuItemForFeedConfig:fc unread:&_unreadCountTotal]];
|
||||
[menu addItem:[self generateMenuItem:fc unread:&_unreadCountTotal]];
|
||||
}
|
||||
}
|
||||
[self updateMenuHeaderEnabled:menu hasUnread:(self.unreadCountTotal > 0)];
|
||||
@@ -132,104 +133,56 @@
|
||||
}
|
||||
|
||||
/**
|
||||
Create and return a new @c NSMenuItem from the objects attributes.
|
||||
Generate menu item with all its sub-menus. @c FeedConfig type is evaluated automatically.
|
||||
|
||||
@param config @c FeedConfig object that represents a superior feed element.
|
||||
@param unread Pointer to an int that will be incremented for each unread item.
|
||||
@return Return a fully configured Separator item OR group item OR feed item. (but not @c FeedItem item)
|
||||
@param unread Pointer to an unread count. Will be incremented while traversing through sub-menus.
|
||||
*/
|
||||
- (NSMenuItem*)menuItemForFeedConfig:(FeedConfig*)config unread:(int*)unread {
|
||||
NSMenuItem *item;
|
||||
if (config.typ == SEPARATOR) {
|
||||
item = [NSMenuItem separatorItem];
|
||||
[item setReaderInfo:config.objectID unread:0];
|
||||
- (NSMenuItem*)generateMenuItem:(FeedConfig*)config unread:(int*)unread {
|
||||
NSMenuItem *item = [NSMenuItem feedConfig:config];
|
||||
int count = 0;
|
||||
if (item.tag == ScopeFeed) {
|
||||
count += [self setSubmenuForFeedScope:item config:config];
|
||||
} else if (item.tag == ScopeGroup) {
|
||||
[self setSubmenuForGroupScope:item config:config unread:&count];
|
||||
} else { // Separator item
|
||||
return item;
|
||||
}
|
||||
int count = 0;
|
||||
if (config.typ == FEED) {
|
||||
item = [self feedItem:config unread:&count];
|
||||
} else if (config.typ == GROUP) {
|
||||
item = [self groupItem:config unread:&count];
|
||||
}
|
||||
*unread += count;
|
||||
[item setReaderInfo:config.objectID unread:0];
|
||||
// !!!: fix that double count
|
||||
[item markReadAndUpdateTitle:-count];
|
||||
[self updateMenuHeaderEnabled:item.submenu hasUnread:(count > 0)];
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
Create and return a new @c NSMenuItem from the objects attributes.
|
||||
|
||||
@param config @c FeedConfig object that represents a superior feed element.
|
||||
@param unread Pointer to an int that will be incremented for each unread item.
|
||||
*/
|
||||
- (NSMenuItem*)feedItem:(FeedConfig*)config unread:(int*)unread {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:@selector(openFeedURL:) keyEquivalent:@""];
|
||||
item.target = self;
|
||||
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;
|
||||
}
|
||||
Set subitems for a @c FeedConfig group item. Namely various @c FeedConfig and @c FeedItem items.
|
||||
|
||||
/**
|
||||
Create and return a new @c NSMenuItem from the objects attributes.
|
||||
|
||||
@param config @c FeedConfig object that represents a group item.
|
||||
@param unread Pointer to an int that will be incremented for each unread item.
|
||||
@param item The item where the menu will be appended.
|
||||
@param config A @c FeedConfig group item.
|
||||
@param unread Pointer to an unread count. Will be incremented while traversing through sub-menus.
|
||||
*/
|
||||
- (NSMenuItem*)groupItem:(FeedConfig*)config unread:(int*)unread {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
|
||||
- (void)setSubmenuForGroupScope:(NSMenuItem*)item config:(FeedConfig*)config unread:(int*)unread {
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeGroup];
|
||||
for (FeedConfig *obj in config.sortedChildren) {
|
||||
[item.submenu addItem: [self menuItemForFeedConfig:obj unread:unread]];
|
||||
[item.submenu addItem: [self generateMenuItem: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;
|
||||
}
|
||||
|
||||
/**
|
||||
Create and return a new @c NSMenuItem from @c FeedItem attributes.
|
||||
Set subitems for a @c FeedConfig feed item. Namely its @c FeedItem items.
|
||||
|
||||
@param item The item where the menu will be appended.
|
||||
@param config For which item the menu should be generated. Attribute @c feed should be populated.
|
||||
@return Unread count for feed.
|
||||
*/
|
||||
- (NSMenuItem*)feedEntryItem:(FeedItem*)item {
|
||||
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""];
|
||||
mi.target = self;
|
||||
[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) {
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"<[^>]*>" options:kNilOptions error:nil];
|
||||
mi.toolTip = [regex stringByReplacingMatchesInString:item.abstract options:kNilOptions range:NSMakeRange(0, item.abstract.length) withTemplate:@""];
|
||||
- (int)setSubmenuForFeedScope:(NSMenuItem*)item config:(FeedConfig*)config {
|
||||
item.submenu = [self defaultHeaderForMenu:nil scope:ScopeFeed];
|
||||
int count = 0;
|
||||
for (FeedItem *obj in config.feed.items) {
|
||||
if (obj.unread) ++count;
|
||||
[item.submenu addItem:[[NSMenuItem feedItem:obj] setAction:@selector(openFeedURL:) target:self]];
|
||||
}
|
||||
mi.enabled = (item.link.length > 0);
|
||||
mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff);
|
||||
mi.tag = ScopeFeed;
|
||||
return mi;
|
||||
[item setAction:@selector(openFeedURL:) target:self];
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,19 +197,6 @@
|
||||
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
|
||||
|
||||
@@ -276,7 +216,7 @@
|
||||
}
|
||||
|
||||
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];
|
||||
[menu addItem:[item alternateWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Open a few unread (%d)", nil), 3]]];
|
||||
[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];
|
||||
|
||||
@@ -471,7 +411,7 @@
|
||||
*/
|
||||
- (void)siblingsDescendantFeedConfigs:(NSMenuItem*)sender block:(FeedConfigRecursiveItemsBlock)block {
|
||||
if (sender.parentItem) {
|
||||
FeedConfig *obj = [sender requestCoreDataObject];
|
||||
FeedConfig *obj = [sender.parentItem requestCoreDataObject];
|
||||
if ([obj isKindOfClass:[FeedConfig class]]) // important: this could be a FeedItem
|
||||
[obj descendantFeedItems:block];
|
||||
} else {
|
||||
|
||||
33
baRSS/Status Bar Menu/NSMenuItem+Generate.h
Normal file
33
baRSS/Status Bar Menu/NSMenuItem+Generate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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 <Cocoa/Cocoa.h>
|
||||
|
||||
@class FeedConfig, FeedItem;
|
||||
|
||||
@interface NSMenuItem (Generate)
|
||||
+ (NSMenuItem*)feedConfig:(FeedConfig*)config;
|
||||
+ (NSMenuItem*)feedItem:(FeedItem*)item;
|
||||
- (NSMenuItem*)alternateWithTitle:(NSString*)title;
|
||||
|
||||
- (NSMenuItem*)setAction:(nullable SEL)action target:(nullable id)target;
|
||||
@end
|
||||
127
baRSS/Status Bar Menu/NSMenuItem+Generate.m
Normal file
127
baRSS/Status Bar Menu/NSMenuItem+Generate.m
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// 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+Generate.h"
|
||||
#import "NSMenuItem+Info.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "DrawImage.h"
|
||||
|
||||
@implementation NSMenuItem (Feed)
|
||||
/**
|
||||
Generate a new @c NSMenuItem based on the type stored in @c FeedConfig.
|
||||
|
||||
@param config @c FeedConfig object that represents a superior feed element.
|
||||
@return Return a fully configured Separator item OR group item OR feed item. (but not @c FeedItem item)
|
||||
*/
|
||||
+ (NSMenuItem*)feedConfig:(FeedConfig*)config {
|
||||
NSMenuItem *item;
|
||||
switch (config.typ) {
|
||||
case SEPARATOR: item = [NSMenuItem separatorItem]; break;
|
||||
case GROUP: item = [self feedConfigItemGroup:config]; break;
|
||||
case FEED: item = [self feedConfigItemFeed:config]; break;
|
||||
}
|
||||
[item setReaderInfo:config.objectID unread:0];
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
Generate a new @c NSMenuItem from a @c FeedConfig feed item.
|
||||
|
||||
@param config @c FeedConfig object that represents a superior feed element.
|
||||
*/
|
||||
+ (NSMenuItem*)feedConfigItemFeed:(FeedConfig*)config {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
|
||||
item.toolTip = config.feed.subtitle;
|
||||
item.enabled = (config.feed.items.count > 0);
|
||||
item.tag = ScopeFeed;
|
||||
// set icon
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
static NSImage *defaultRSSIcon;
|
||||
if (!defaultRSSIcon)
|
||||
defaultRSSIcon = [RSSIcon iconWithSize:16];
|
||||
item.image = defaultRSSIcon;
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
Generate a new @c NSMenuItem from a @c FeedConfig group item
|
||||
|
||||
@param config @c FeedConfig object that represents a group item.
|
||||
*/
|
||||
+ (NSMenuItem*)feedConfigItemGroup:(FeedConfig*)config {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:config.name action:nil keyEquivalent:@""];
|
||||
item.tag = ScopeGroup;
|
||||
// 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;
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
Generate new @c NSMenuItem based on the attributes of a @c FeedItem.
|
||||
*/
|
||||
+ (NSMenuItem*)feedItem:(FeedItem*)item {
|
||||
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:nil keyEquivalent:@""];
|
||||
[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) {
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"<[^>]*>" options:kNilOptions error:nil];
|
||||
mi.toolTip = [regex stringByReplacingMatchesInString:item.abstract options:kNilOptions range:NSMakeRange(0, item.abstract.length) withTemplate:@""];
|
||||
}
|
||||
mi.enabled = (item.link.length > 0);
|
||||
mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff);
|
||||
mi.tag = ScopeFeed;
|
||||
return mi;
|
||||
}
|
||||
|
||||
/**
|
||||
Create a copy of an existing menu item and set it's option key modifier.
|
||||
*/
|
||||
- (NSMenuItem*)alternateWithTitle:(NSString*)title {
|
||||
NSMenuItem *alt = [self copy];
|
||||
alt.title = title;
|
||||
alt.keyEquivalentModifierMask = NSEventModifierFlagOption;
|
||||
if (!alt.hidden) // hidden will be ignored if alternate is YES
|
||||
alt.alternate = YES;
|
||||
return alt;
|
||||
}
|
||||
|
||||
/**
|
||||
Set @c action and @c target attributes.
|
||||
|
||||
@return Return @c self instance. Intended for method chains.
|
||||
*/
|
||||
- (NSMenuItem*)setAction:(SEL)action target:(id)target {
|
||||
self.action = action;
|
||||
self.target = target;
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user