Refactoring Part 1: Dynamic menus (stable)

This commit is contained in:
relikd
2018-11-24 14:18:06 +01:00
parent 080991ebc4
commit 6223d1a169
22 changed files with 1026 additions and 973 deletions

View File

@@ -1,52 +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 "FeedConfig+CoreDataClass.h"
@class FeedItem;
@interface FeedConfig (Ext)
/// Enum type to distinguish different @c FeedConfig types
typedef enum int16_t {
GROUP = 0,
FEED = 1,
SEPARATOR = 2
} FeedConfigType;
/**
Iteration block for descendants of @c FeedItem.
@param parent The parent @c FeedConfig where this @c FeedItem belongs to.
@param item Currently processed @c FeedItem.
@return Return @c YES to continue processing. Return @c NO to stop processing and exit early.
*/
typedef BOOL (^FeedConfigRecursiveItemsBlock) (FeedConfig *parent, FeedItem *item);
@property (getter=typ, setter=setTyp:) FeedConfigType typ;
@property (readonly) NSArray<FeedConfig*> *sortedChildren;
@property (readonly) NSIndexPath *indexPath;
- (BOOL)descendantFeedItems:(FeedConfigRecursiveItemsBlock)block;
- (void)calculateAndSetScheduled;
- (void)mergeChangesAndSave;
- (NSString*)readableRefreshString;
- (NSString*)readableDescription;
@end

View File

@@ -1,104 +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 "FeedConfig+Ext.h"
#import "Feed+CoreDataClass.h"
@implementation FeedConfig (Ext)
/// Enum tpye getter see @c FeedConfigType
- (FeedConfigType)typ { return (FeedConfigType)self.type; }
/// Enum type setter see @c FeedConfigType
- (void)setTyp:(FeedConfigType)typ { self.type = typ; }
/**
Sorted children array based on sort order provided in feed settings.
@return Sorted array of @c FeedConfig items.
*/
- (NSArray<FeedConfig *> *)sortedChildren {
if (self.children.count == 0)
return nil;
return [self.children sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
}
- (NSIndexPath *)indexPath {
if (self.parent == nil)
return [NSIndexPath indexPathWithIndex:(NSUInteger)self.sortIndex];
return [self.parent.indexPath indexPathByAddingIndex:(NSUInteger)self.sortIndex];
}
/**
Iterate over all descendant @c FeedItems in sub groups
@param block Will yield the current parent config and feed item. Return @c NO to cancel iteration.
@return Returns @c NO if the iteration was canceled early. Otherwise @c YES.
*/
- (BOOL)descendantFeedItems:(FeedConfigRecursiveItemsBlock)block {
if (self.children.count > 0) {
for (FeedConfig *config in self.sortedChildren) {
if ([config descendantFeedItems:block] == NO)
return NO;
}
} else if (self.feed.items.count > 0) {
for (FeedItem* item in self.feed.items) {
if (block(self, item) == NO)
return NO;
}
}
return YES;
}
/// @return Time interval respecting the selected unit. E.g., returns @c 180 for @c '3m'
- (NSTimeInterval)timeInterval {
static const int unit[] = {1, 60, 3600, 86400, 604800}; // smhdw
return self.refreshNum * unit[self.refreshUnit % 5];
}
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
- (void)calculateAndSetScheduled {
self.scheduled = [[NSDate date] dateByAddingTimeInterval:[self timeInterval]];
}
/// Update item with @c mergeChanges:YES and save the context
- (void)mergeChangesAndSave {
[self.managedObjectContext performBlockAndWait:^{
[self.managedObjectContext refreshObject:self mergeChanges:YES];
[self.managedObjectContext save:nil];
}];
}
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
- (NSString*)readableRefreshString {
return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]];
}
/// @return Simplified description of the feed object.
- (NSString*)readableDescription {
switch (self.typ) {
case SEPARATOR: return @"-------------";
case GROUP: return [NSString stringWithFormat:@"%@", self.name];
case FEED:
return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.url, [self readableRefreshString]];
}
}
@end

View File

@@ -109,22 +109,13 @@
item.refreshUnit = (int16_t)self.refreshUnit.indexOfSelectedItem;
if (self.shouldDeletePrevArticles) {
[StoreCoordinator overwriteConfig:item withFeed:self.feedResult];
[item.managedObjectContext performBlockAndWait:^{
// TODO: move to separate function and add icon download
if (!item.meta) {
item.meta = [[FeedMeta alloc] initWithEntity:FeedMeta.entity insertIntoManagedObjectContext:item.managedObjectContext];
}
item.meta.httpEtag = self.httpEtag;
item.meta.httpModified = self.httpDate;
}];
[item updateRSSFeed:self.feedResult];
[item setEtag:self.httpEtag modified:self.httpDate];
}
if ([item.managedObjectContext hasChanges]) {
self.objectIsModified = YES;
[item calculateAndSetScheduled];
[item.managedObjectContext performBlockAndWait:^{
[item.managedObjectContext refreshObject:item mergeChanges:YES];
}];
[item.managedObjectContext refreshObject:item mergeChanges:YES];
}
}
@@ -222,9 +213,7 @@
NSString *name = ((NSTextField*)self.view).stringValue;
if (![item.name isEqualToString: name]) {
item.name = name;
[item.managedObjectContext performBlockAndWait:^{
[item.managedObjectContext refreshObject:item mergeChanges:YES];
}];
[item.managedObjectContext refreshObject:item mergeChanges:YES];
[self.delegate modalDidUpdateFeedConfig:item];
}
}

View File

@@ -21,7 +21,6 @@
// SOFTWARE.
#import "SettingsFeeds.h"
#import "AppHook.h"
#import "BarMenu.h"
#import "ModalSheet.h"
#import "ModalFeedEdit.h"
@@ -47,27 +46,16 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
[self.outlineView registerForDraggedTypes:[NSArray arrayWithObject:dragNodeType]];
[self.dataStore setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[childContext setParentContext:[(AppHook*)NSApp persistentContainer].viewContext];
// childContext.automaticallyMergesChangesFromParent = YES;
NSUndoManager *um = [[NSUndoManager alloc] init];
um.groupsByEvent = NO;
um.levelsOfUndo = 30;
childContext.undoManager = um;
self.undoManager = [[NSUndoManager alloc] init];
self.undoManager.groupsByEvent = NO;
self.undoManager.levelsOfUndo = 30;
self.dataStore.managedObjectContext = childContext;
self.undoManager = self.dataStore.managedObjectContext.undoManager;
self.dataStore.managedObjectContext = [StoreCoordinator createChildContext];
self.dataStore.managedObjectContext.undoManager = self.undoManager;
}
- (void)saveAndRebuildMenu {
[self.dataStore.managedObjectContext performBlock:^{
[StoreCoordinator saveContext:self.dataStore.managedObjectContext];
[[(AppHook*)NSApp barMenu] rebuildMenu]; // updating individual items was way to complicated ...
[self.dataStore.managedObjectContext.parentContext performBlock:^{
[StoreCoordinator saveContext:self.dataStore.managedObjectContext.parentContext];
}];
}];
- (void)saveChanges {
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
}
- (IBAction)addFeed:(id)sender {
@@ -84,7 +72,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
sp.name = @"---";
sp.typ = SEPARATOR;
[self.undoManager endUndoGrouping];
[self saveAndRebuildMenu];
[self saveChanges];
}
- (IBAction)remove:(id)sender {
@@ -93,7 +81,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
[self incrementIndicesBy:-1 forSubsequentNodes:path];
[self.dataStore remove:sender];
[self.undoManager endUndoGrouping];
[self saveAndRebuildMenu];
[self saveChanges];
}
- (IBAction)doubleClickOutlineView:(NSOutlineView*)sender {
@@ -139,7 +127,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
}
- (void)modalDidUpdateFeedConfig:(FeedConfig*)config {
[self saveAndRebuildMenu];
[self saveChanges]; // TODO: adjust total count
}
- (FeedConfig*)insertSortedItemAtSelection {
@@ -178,7 +166,8 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
root = [root descendantNodeAtIndexPath:parentPath];
for (NSUInteger i = [path indexAtPosition:path.length - 1]; i < root.childNodes.count; i++) {
((FeedConfig*)[root.childNodes[i] representedObject]).sortIndex += val;
FeedConfig *conf = [root.childNodes[i] representedObject];
conf.sortIndex += val;
}
}
@@ -197,7 +186,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation {
[self.undoManager endUndoGrouping];
if (self.dataStore.managedObjectContext.hasChanges) {
[self saveAndRebuildMenu];
[self saveChanges];
} else {
[self.undoManager disableUndoRegistration];
[self.undoManager undoNestedGroup];
@@ -228,6 +217,11 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
--updateIndex;
}
}
for (NSUInteger i = self.currentlyDraggedNodes.count; i > 0; i--) { // sorted that way to handle children first
FeedConfig *fc = [self.currentlyDraggedNodes[i - 1] representedObject];
[fc.managedObjectContext refreshObject:fc mergeChanges:YES]; // make sure unreadCount is correct
[fc markUnread:-fc.unreadCount ancestorsOnly:YES];
}
// decrement sort indices at source
for (NSTreeNode *node in self.currentlyDraggedNodes)
@@ -243,6 +237,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
for (NSUInteger i = 0; i < self.currentlyDraggedNodes.count; i++) {
FeedConfig *fc = [self.currentlyDraggedNodes[i] representedObject];
fc.sortIndex = (int32_t)(updateIndex + i);
[fc markUnread:fc.unreadCount ancestorsOnly:YES];
}
return YES;
}
@@ -322,14 +317,16 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
- (void)undo:(id)sender {
[self.undoManager undo];
[StoreCoordinator restoreUnreadCount];
[self saveChanges];
[self.dataStore rearrangeObjects]; // update ordering
[self saveAndRebuildMenu];
}
- (void)redo:(id)sender {
[self.undoManager redo];
[StoreCoordinator restoreUnreadCount];
[self saveChanges];
[self.dataStore rearrangeObjects]; // update ordering
[self saveAndRebuildMenu];
}
- (void)enterPressed:(id)sender {

View File

@@ -24,6 +24,7 @@
#import "AppHook.h"
#import "BarMenu.h"
#import "UserPrefs.h"
#import "StoreCoordinator.h"
#import <ServiceManagement/ServiceManagement.h>
@@ -57,6 +58,15 @@
CFRelease(helperIdentifier);
}
- (IBAction)fixCache:(NSButton *)sender {
[StoreCoordinator deleteUnreferencedFeeds];
[StoreCoordinator restoreUnreadCount];
}
- (IBAction)changeMenuBarIconSetting:(NSButton*)sender {
[[(AppHook*)NSApp barMenu] updateBarIcon];
}
- (IBAction)changeHttpApplication:(NSPopUpButton *)sender {
[UserPrefs setHttpApplication:sender.selectedItem.representedObject];
}
@@ -68,31 +78,6 @@
}
}
// TODO: add self to login items
- (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)changeMenuItemUpdateAll:(NSButton*)sender {
BOOL checked = (sender.state == NSControlStateValueOn);
[[(AppHook*)NSApp barMenu] setItemUpdateAllHidden:!checked];
}
#pragma mark - Helper methods
/**

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -45,7 +45,6 @@
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeMenuItemUpdateAll:" target="-2" id="Zb8-Oi-JVr"/>
<binding destination="iU7-KA-nY5" name="value" keyPath="values.globalUpdateAll" id="FrQ-u0-lFo">
<dictionary key="options">
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
@@ -62,7 +61,6 @@
<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"/>
@@ -79,7 +77,6 @@
<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"/>
@@ -96,7 +93,6 @@
<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"/>
@@ -113,7 +109,6 @@
<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"/>
@@ -130,7 +125,6 @@
<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"/>
@@ -147,7 +141,6 @@
<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"/>
@@ -164,7 +157,6 @@
<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"/>
@@ -181,7 +173,6 @@
<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"/>
@@ -198,7 +189,6 @@
<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"/>
@@ -232,7 +222,6 @@
<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"/>
@@ -249,7 +238,6 @@
<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"/>
@@ -266,7 +254,6 @@
<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"/>
@@ -446,6 +433,17 @@
<action selector="changeDefaultRSSReader:" target="-2" id="ul1-1K-oJb"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QwE-M7-q2R">
<rect key="frame" x="206" y="279" width="100" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Fix Cache" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ady-2s-Ggm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="fixCache:" target="-2" id="gbM-hA-UVF"/>
</connections>
</button>
</subviews>
<point key="canvasLocation" x="140" y="-155.5"/>
</customView>