236 lines
9.3 KiB
Objective-C
236 lines
9.3 KiB
Objective-C
#import "SettingsFeedsView.h"
|
|
#import "StoreCoordinator.h"
|
|
#import "FeedGroup+Ext.h"
|
|
#import "DrawImage.h"
|
|
#import "SettingsFeeds.h"
|
|
#import "NSDate+Ext.h"
|
|
#import "NSView+Ext.h"
|
|
|
|
@interface SettingsFeedsView()
|
|
@property (weak) SettingsFeeds *controller;
|
|
@end
|
|
|
|
@implementation SettingsFeedsView
|
|
|
|
- (instancetype)initWithController:(SettingsFeeds*)delegate {
|
|
self = [super initWithFrame:NSMakeRect(0, 0, 201, 327)];
|
|
if (self) {
|
|
self.controller = delegate; // make sure its first
|
|
self.outline = [self generateOutlineView]; // uses self.controller
|
|
[self wrapContent:self.outline inScrollView:NSMakeRect(0, 20, NSWidth(self.frame), NSHeight(self.frame) - 20)];
|
|
self.outline.menu = [self generateCommandsMenu];
|
|
[self.outline.menu.itemArray makeObjectsPerformSelector:@selector(setTarget:) withObject:delegate];
|
|
CGFloat x = [self generateButtons]; // uses self.controller and self.outline
|
|
// Setup status text field ('Next update in X min.' or 'Updating X feeds ...')
|
|
self.status = [[[[[[NSView label:@""] small] gray] textCenter] placeIn:self x:x + PAD_L y:3.5] sizeToRight:PAD_L];
|
|
self.spinner = [[NSView activitySpinner] placeIn:self xRight:2 y:2];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
Setup @c self.outline
|
|
@note Requires @c self.controller
|
|
*/
|
|
- (NSOutlineView*)generateOutlineView {
|
|
// Generate outline view
|
|
NSOutlineView *o = [[NSOutlineView alloc] init];
|
|
o.columnAutoresizingStyle = NSTableViewFirstColumnOnlyAutoresizingStyle;
|
|
o.usesAlternatingRowBackgroundColors = YES;
|
|
o.allowsMultipleSelection = YES;
|
|
o.allowsColumnReordering = NO;
|
|
o.allowsColumnSelection = NO;
|
|
o.allowsEmptySelection = YES;
|
|
//o.intercellSpacing = NSMakeSize(3, 2);
|
|
o.rowHeight = 18;
|
|
|
|
[self setOutlineColumns:o];
|
|
|
|
// Setup action and bindings
|
|
SettingsFeeds *sf = self.controller;
|
|
o.target = sf;
|
|
o.doubleAction = @selector(doubleClickOutlineView:);
|
|
|
|
[o bind:NSContentBinding toObject:sf.dataStore withKeyPath:@"arrangedObjects" options:nil]; // @{NSAlwaysPresentsApplicationModalAlertsBindingOption:@YES}
|
|
[o bind:NSSelectionIndexPathsBinding toObject:sf.dataStore withKeyPath:@"selectionIndexPaths" options:nil];
|
|
return o;
|
|
}
|
|
|
|
/// Generate table columns 'Name' and 'Refresh'
|
|
- (void)setOutlineColumns:(NSOutlineView*)outline {
|
|
NSTableColumn *colName = [[NSTableColumn alloc] initWithIdentifier:CustomCellName];
|
|
colName.title = NSLocalizedString(@"Name", nil);
|
|
colName.width = 201;
|
|
colName.maxWidth = 10000;
|
|
colName.resizingMask = NSTableColumnAutoresizingMask;
|
|
[outline addTableColumn:colName];
|
|
|
|
NSTableColumn *colRefresh = [[NSTableColumn alloc] initWithIdentifier:CustomCellRefresh];
|
|
colRefresh.title = NSLocalizedString(@"Refresh", nil);
|
|
colRefresh.width = 60;
|
|
colRefresh.resizingMask = NSTableColumnNoResizing;
|
|
[outline addTableColumn:colRefresh];
|
|
|
|
for (NSTableColumn *col in outline.tableColumns) {
|
|
col.headerCell.title = [NSString stringWithFormat:@" %@", col.title];
|
|
NSDictionary *attr = @{ NSFontAttributeName: [NSFont systemFontOfSize:NSFont.smallSystemFontSize weight:NSFontWeightMedium] };
|
|
col.headerCell.attributedStringValue = [[NSAttributedString alloc] initWithString:col.title attributes:attr];
|
|
}
|
|
outline.outlineTableColumn = colName;
|
|
}
|
|
|
|
/// Setup right click menu (also used for hotkeys).
|
|
- (NSMenu*)generateCommandsMenu {
|
|
NSMenu *m = [[NSMenu alloc] initWithTitle:@""];
|
|
[m addItemWithTitle:NSLocalizedString(@"Edit Item", nil) action:@selector(editSelectedItem) keyEquivalent:[NSString stringWithFormat:@"%c", NSCarriageReturnCharacter]].keyEquivalentModifierMask = 0;
|
|
[m addItemWithTitle:NSLocalizedString(@"Delete Item(s)", nil) action:@selector(remove:) keyEquivalent:[NSString stringWithFormat:@"%c", NSBackspaceCharacter]];
|
|
[m addItem:[NSMenuItem separatorItem]]; // index: 2
|
|
[m addItemWithTitle:NSLocalizedString(@"New Feed", nil) action:@selector(addFeed) keyEquivalent:@"n"];
|
|
[m addItemWithTitle:NSLocalizedString(@"New Group", nil) action:@selector(addGroup) keyEquivalent:@"g"];
|
|
[m addItemWithTitle:NSLocalizedString(@"New Separator", nil) action:@selector(addSeparator) keyEquivalent:@""];
|
|
[m addItem:[NSMenuItem separatorItem]]; // index: 6
|
|
[m addItemWithTitle:NSLocalizedString(@"Import Feeds …", nil) action:@selector(openImportDialog) keyEquivalent:@"o"];
|
|
[m addItemWithTitle:NSLocalizedString(@"Export Feeds …", nil) action:@selector(openExportDialog) keyEquivalent:@"s"];
|
|
[m addItem:[NSMenuItem separatorItem]]; // index: 9
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
|
[m addItemWithTitle:NSLocalizedString(@"Undo", nil) action:@selector(undo:) keyEquivalent:@"z"];
|
|
[m addItemWithTitle:NSLocalizedString(@"Redo", nil) action:@selector(redo:) keyEquivalent:@"Z"];
|
|
#pragma clang diagnostic pop
|
|
return m;
|
|
}
|
|
|
|
/**
|
|
Setup the bottom button bar. (e.g., add, remove, edit, export, import, etc.)
|
|
@note Requires @c self.controller and @c self.outline
|
|
|
|
@return Max x-value of last button frame
|
|
*/
|
|
- (CGFloat)generateButtons {
|
|
NSButton *add = [[NSView buttonImageSquare:NSImageNameAddTemplate] tooltip:NSLocalizedString(@"Add new item", nil)];
|
|
NSButton *del = [[NSView buttonImageSquare:NSImageNameRemoveTemplate] tooltip:NSLocalizedString(@"Delete selected items", nil)];
|
|
NSButton *share = [[NSView buttonImageSquare:NSImageNameShareTemplate] tooltip:NSLocalizedString(@"Import or export data", nil)];
|
|
|
|
[self button:add copyActions:3 to:5];
|
|
[self button:del copyActions:1 to:1];
|
|
[self button:share copyActions:7 to:8]; // TODO: Add menus for online sync? email export? etc.
|
|
|
|
[add placeIn:self x:0 y:0];
|
|
[del placeIn:self x:24 y:0];
|
|
[share placeIn:self x:2 * 24 + PAD_L y:0];
|
|
|
|
NSTreeController *tc = self.controller.dataStore;
|
|
[add bind:NSEnabledBinding toObject:tc withKeyPath:@"canInsert" options:nil];
|
|
[del bind:NSEnabledBinding toObject:tc withKeyPath:@"canRemove" options:nil];
|
|
return NSMaxX(share.frame);
|
|
}
|
|
|
|
/**
|
|
Duplicate right click menu actions to button
|
|
@note Requires @c self.outline
|
|
*/
|
|
- (void)button:(NSButton*)btn copyActions:(NSInteger)start to:(NSInteger)end {
|
|
if (start < 0 || start > end || end >= self.outline.menu.numberOfItems) {
|
|
NSAssert(NO, @"Invalid index, can't copy command menu items.");
|
|
return;
|
|
}
|
|
if (start == end) {
|
|
// copy menu item action to button action
|
|
NSMenuItem *source = [self.outline.menu itemAtIndex:start];
|
|
[btn action:source.action target:source.target];
|
|
btn.keyEquivalent = source.keyEquivalent;
|
|
btn.keyEquivalentModifierMask = source.keyEquivalentModifierMask;
|
|
} else {
|
|
// create drop down menu with all options
|
|
btn.menu = [[NSMenu alloc] initWithTitle:@""];
|
|
[btn action:@selector(openButtonMenu:) target:self];
|
|
for (NSInteger i = start; i <= end; i++) {
|
|
[btn.menu addItem:[[self.outline.menu itemAtIndex:i] copy]];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Show drop down menu even for left click.
|
|
- (void)openButtonMenu:(NSButton*)sender {
|
|
//[NSMenu popUpContextMenu:sender.menu withEvent:[NSApp currentEvent] forView:sender];
|
|
[sender.menu popUpMenuPositioningItem:nil atLocation:NSMakePoint(0, NSHeight(sender.frame)) inView:sender];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark - Custom Outline View Cells -
|
|
|
|
|
|
/**
|
|
First outline view column, with textfield and feed icon
|
|
*/
|
|
@implementation NameColumnCell
|
|
/// Identifier for cell with @c .imageView (feed icon) and @c .textField (feed title)
|
|
NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell";
|
|
|
|
- (instancetype)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
|
self.identifier = CustomCellName;
|
|
self.imageView = [[NSView imageView:nil size:16] placeIn:self x:1 yTop:1];
|
|
self.imageView.accessibilityLabel = NSLocalizedString(@"Feed icon", nil);
|
|
self.textField = [[[NSView label:@""] placeIn:self x:25 yTop:0] sizeToRight:0];
|
|
self.textField.accessibilityLabel = NSLocalizedString(@"Feed title", nil);
|
|
return self;
|
|
}
|
|
|
|
- (void)setObjectValue:(FeedGroup*)fg {
|
|
self.textField.objectValue = fg.anyName;
|
|
self.imageView.image = fg.iconImage16;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/**
|
|
Second outline view column, either refresh string or empty
|
|
*/
|
|
@implementation RefreshColumnCell
|
|
/// Identifier for cell with @c .textField (refresh string or empty)
|
|
NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell";
|
|
|
|
- (instancetype)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
|
self.identifier = CustomCellRefresh;
|
|
self.textField = [[[[NSView label:@""] textRight] placeIn:self x:0 yTop:0] sizeToRight:0];
|
|
self.textField.accessibilityTitle = @" "; // otherwise groups and separators will say 'text'
|
|
return self;
|
|
}
|
|
|
|
- (void)setObjectValue:(FeedGroup*)fg {
|
|
NSString *str = @"";
|
|
if (fg.type == FEED) {
|
|
int32_t refresh = fg.feed.meta.refresh;
|
|
str = (refresh <= 0 ? @"∞" : [NSDate intStringForInterval:refresh]); // ∞ ƒ Ø
|
|
}
|
|
self.textField.objectValue = str;
|
|
self.textField.textColor = (str.length > 1 ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]);
|
|
self.textField.accessibilityLabel = (str.length > 1 ? NSLocalizedString(@"Refresh interval", nil) : nil);
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/**
|
|
First outline view column, separator line
|
|
*/
|
|
@implementation SeparatorColumnCell
|
|
/// Identifier for cell with line separator
|
|
NSUserInterfaceItemIdentifier const CustomCellSeparator = @"SeparatorColumnCell";
|
|
|
|
- (instancetype)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
|
self.identifier = CustomCellSeparator;
|
|
[[[[DrawSeparator alloc] initWithFrame:self.frame] placeIn:self x:0 y:0] sizableWidthAndHeight];
|
|
return self;
|
|
}
|
|
|
|
- (void)setObjectValue:(FeedGroup*)fg { /* do nothing */ }
|
|
|
|
@end
|