// This file Copyright © 2007-2022 Transmission authors and contributors. // It may be used under the MIT (SPDX: MIT) license. // License text can be found in the licenses/ folder. #import "GroupsPrefsController.h" #import "GroupsController.h" #import "ExpandedPathToPathTransformer.h" #import "ExpandedPathToIconTransformer.h" #import "NSApplicationAdditions.h" #define GROUP_TABLE_VIEW_DATA_TYPE @"GroupTableViewDataType" #define ADD_TAG 0 #define REMOVE_TAG 1 @interface GroupsPrefsController () @property(nonatomic) IBOutlet NSWindow* groupRulesSheetWindow; @property(nonatomic, weak) IBOutlet NSPredicateEditor* ruleEditor; @property(nonatomic, weak) IBOutlet NSLayoutConstraint* ruleEditorHeightConstraint; @end @interface GroupsPrefsController () - (void)updateSelectedGroup; - (void)refreshCustomLocationWithSingleGroup; @end @implementation GroupsPrefsController @synthesize groupRulesSheetWindow; @synthesize ruleEditor; @synthesize ruleEditorHeightConstraint; - (void)awakeFromNib { [fTableView registerForDraggedTypes:@[ GROUP_TABLE_VIEW_DATA_TYPE ]]; [fSelectedColorView addObserver:self forKeyPath:@"color" options:0 context:NULL]; [self updateSelectedGroup]; } - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableview { return GroupsController.groups.numberOfGroups; } - (id)tableView:(NSTableView*)tableView objectValueForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row { GroupsController* groupsController = GroupsController.groups; NSInteger groupsIndex = [groupsController indexForRow:row]; NSString* identifier = tableColumn.identifier; if ([identifier isEqualToString:@"Color"]) { return [groupsController imageForIndex:groupsIndex]; } else { return [groupsController nameForIndex:groupsIndex]; } } - (void)tableViewSelectionDidChange:(NSNotification*)notification { [self updateSelectedGroup]; } - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { if (object == fSelectedColorView && fTableView.numberOfSelectedRows == 1) { NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; [GroupsController.groups setColor:fSelectedColorView.color forIndex:index]; fTableView.needsDisplay = YES; } } - (void)controlTextDidEndEditing:(NSNotification*)notification { if (notification.object == fSelectedColorNameField) { NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; [GroupsController.groups setName:fSelectedColorNameField.stringValue forIndex:index]; fTableView.needsDisplay = YES; } } - (BOOL)tableView:(NSTableView*)tableView writeRowsWithIndexes:(NSIndexSet*)rowIndexes toPasteboard:(NSPasteboard*)pboard { [pboard declareTypes:@[ GROUP_TABLE_VIEW_DATA_TYPE ] owner:self]; [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:rowIndexes] forType:GROUP_TABLE_VIEW_DATA_TYPE]; return YES; } - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation { NSPasteboard* pasteboard = info.draggingPasteboard; if ([pasteboard.types containsObject:GROUP_TABLE_VIEW_DATA_TYPE]) { [fTableView setDropRow:row dropOperation:NSTableViewDropAbove]; return NSDragOperationGeneric; } return NSDragOperationNone; } - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id)info row:(NSInteger)newRow dropOperation:(NSTableViewDropOperation)operation { NSPasteboard* pasteboard = info.draggingPasteboard; if ([pasteboard.types containsObject:GROUP_TABLE_VIEW_DATA_TYPE]) { NSIndexSet* indexes = [NSKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:GROUP_TABLE_VIEW_DATA_TYPE]]; NSInteger oldRow = indexes.firstIndex; if (oldRow < newRow) { newRow--; } [fTableView beginUpdates]; [GroupsController.groups moveGroupAtRow:oldRow toRow:newRow]; [fTableView moveRowAtIndex:oldRow toIndex:newRow]; [fTableView endUpdates]; } return YES; } - (void)addRemoveGroup:(id)sender { if (NSColorPanel.sharedColorPanelExists) { [NSColorPanel.sharedColorPanel close]; } NSInteger row; switch ([[sender cell] tagForSegment:[sender selectedSegment]]) { case ADD_TAG: [fTableView beginUpdates]; [GroupsController.groups addNewGroup]; row = fTableView.numberOfRows; [fTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] withAnimation:NSTableViewAnimationSlideUp]; [fTableView endUpdates]; [fTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; [fTableView scrollRowToVisible:row]; [fSelectedColorNameField.window makeFirstResponder:fSelectedColorNameField]; break; case REMOVE_TAG: row = fTableView.selectedRow; [fTableView beginUpdates]; [GroupsController.groups removeGroupWithRowIndex:row]; [fTableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] withAnimation:NSTableViewAnimationSlideUp]; [fTableView endUpdates]; if (fTableView.numberOfRows > 0) { if (row == fTableView.numberOfRows) { --row; } [fTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; [fTableView scrollRowToVisible:row]; } break; } [self updateSelectedGroup]; } - (void)customDownloadLocationSheetShow:(id)sender { NSOpenPanel* panel = [NSOpenPanel openPanel]; panel.prompt = NSLocalizedString(@"Select", "Preferences -> Open panel prompt"); panel.allowsMultipleSelection = NO; panel.canChooseFiles = NO; panel.canChooseDirectories = YES; panel.canCreateDirectories = YES; [panel beginSheetModalForWindow:fCustomLocationPopUp.window completionHandler:^(NSInteger result) { NSInteger const index = [GroupsController.groups indexForRow:fTableView.selectedRow]; if (result == NSModalResponseOK) { NSString* path = panel.URLs[0].path; [GroupsController.groups setCustomDownloadLocation:path forIndex:index]; [GroupsController.groups setUsesCustomDownloadLocation:YES forIndex:index]; } else { if (![GroupsController.groups customDownloadLocationForIndex:index]) { [GroupsController.groups setUsesCustomDownloadLocation:NO forIndex:index]; } } [self refreshCustomLocationWithSingleGroup]; [fCustomLocationPopUp selectItemAtIndex:0]; }]; } - (IBAction)toggleUseCustomDownloadLocation:(id)sender { NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; if (fCustomLocationEnableCheck.state == NSControlStateValueOn) { if ([GroupsController.groups customDownloadLocationForIndex:index]) { [GroupsController.groups setUsesCustomDownloadLocation:YES forIndex:index]; } else { [self customDownloadLocationSheetShow:nil]; } } else { [GroupsController.groups setUsesCustomDownloadLocation:NO forIndex:index]; } fCustomLocationPopUp.enabled = (fCustomLocationEnableCheck.state == NSControlStateValueOn); } #pragma mark - #pragma mark Rule editor - (IBAction)toggleUseAutoAssignRules:(id)sender { NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; if (fAutoAssignRulesEnableCheck.state == NSControlStateValueOn) { if ([GroupsController.groups autoAssignRulesForIndex:index]) { [GroupsController.groups setUsesAutoAssignRules:YES forIndex:index]; } else { [self orderFrontRulesSheet:nil]; } } else { [GroupsController.groups setUsesAutoAssignRules:NO forIndex:index]; } fAutoAssignRulesEditButton.enabled = fAutoAssignRulesEnableCheck.state == NSControlStateValueOn; } - (IBAction)orderFrontRulesSheet:(id)sender { if (!self.groupRulesSheetWindow) { [NSBundle.mainBundle loadNibNamed:@"GroupRules" owner:self topLevelObjects:NULL]; } NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; NSPredicate* predicate = [GroupsController.groups autoAssignRulesForIndex:index]; self.ruleEditor.objectValue = predicate; if (self.ruleEditor.numberOfRows == 0) { [self.ruleEditor addRow:nil]; } [fTableView.window beginSheet:self.groupRulesSheetWindow completionHandler:nil]; } - (IBAction)cancelRules:(id)sender { [self.groupRulesSheetWindow orderOut:nil]; [NSApp endSheet:self.groupRulesSheetWindow]; NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; if (![GroupsController.groups autoAssignRulesForIndex:index]) { [GroupsController.groups setUsesAutoAssignRules:NO forIndex:index]; fAutoAssignRulesEnableCheck.state = NO; fAutoAssignRulesEditButton.enabled = NO; } } - (IBAction)saveRules:(id)sender { [self.groupRulesSheetWindow orderOut:nil]; [NSApp endSheet:self.groupRulesSheetWindow]; NSInteger index = [GroupsController.groups indexForRow:fTableView.selectedRow]; [GroupsController.groups setUsesAutoAssignRules:YES forIndex:index]; NSPredicate* predicate = self.ruleEditor.objectValue; [GroupsController.groups setAutoAssignRules:predicate forIndex:index]; fAutoAssignRulesEnableCheck.state = [GroupsController.groups usesAutoAssignRulesForIndex:index]; fAutoAssignRulesEditButton.enabled = fAutoAssignRulesEnableCheck.state == NSControlStateValueOn; } - (void)ruleEditorRowsDidChange:(NSNotification*)notification { NSScrollView* ruleEditorScrollView = self.ruleEditor.enclosingScrollView; CGFloat const rowHeight = self.ruleEditor.rowHeight; CGFloat const bordersHeight = ruleEditorScrollView.frame.size.height - ruleEditorScrollView.contentSize.height; CGFloat const requiredRowCount = self.ruleEditor.numberOfRows; CGFloat const maxVisibleRowCount = (long)((NSHeight(self.ruleEditor.window.screen.visibleFrame) * 2 / 3) / rowHeight); self.ruleEditorHeightConstraint.constant = MIN(requiredRowCount, maxVisibleRowCount) * rowHeight + bordersHeight; ruleEditorScrollView.hasVerticalScroller = requiredRowCount > maxVisibleRowCount; } - (void)updateSelectedGroup { [fAddRemoveControl setEnabled:fTableView.numberOfSelectedRows > 0 forSegment:REMOVE_TAG]; if (fTableView.numberOfSelectedRows == 1) { NSInteger const index = [GroupsController.groups indexForRow:fTableView.selectedRow]; fSelectedColorView.color = [GroupsController.groups colorForIndex:index]; fSelectedColorView.enabled = YES; fSelectedColorNameField.stringValue = [GroupsController.groups nameForIndex:index]; fSelectedColorNameField.enabled = YES; [self refreshCustomLocationWithSingleGroup]; fAutoAssignRulesEnableCheck.state = [GroupsController.groups usesAutoAssignRulesForIndex:index]; fAutoAssignRulesEnableCheck.enabled = YES; fAutoAssignRulesEditButton.enabled = (fAutoAssignRulesEnableCheck.state == NSControlStateValueOn); } else { fSelectedColorView.color = NSColor.whiteColor; fSelectedColorView.enabled = NO; fSelectedColorNameField.stringValue = @""; fSelectedColorNameField.enabled = NO; fCustomLocationEnableCheck.enabled = NO; fCustomLocationPopUp.enabled = NO; fAutoAssignRulesEnableCheck.enabled = NO; fAutoAssignRulesEditButton.enabled = NO; } } - (void)refreshCustomLocationWithSingleGroup { NSInteger const index = [GroupsController.groups indexForRow:fTableView.selectedRow]; BOOL const hasCustomLocation = [GroupsController.groups usesCustomDownloadLocationForIndex:index]; fCustomLocationEnableCheck.state = hasCustomLocation; fCustomLocationEnableCheck.enabled = YES; fCustomLocationPopUp.enabled = hasCustomLocation; NSString* location = [GroupsController.groups customDownloadLocationForIndex:index]; if (location) { ExpandedPathToPathTransformer* pathTransformer = [[ExpandedPathToPathTransformer alloc] init]; [fCustomLocationPopUp itemAtIndex:0].title = [pathTransformer transformedValue:location]; ExpandedPathToIconTransformer* iconTransformer = [[ExpandedPathToIconTransformer alloc] init]; [fCustomLocationPopUp itemAtIndex:0].image = [iconTransformer transformedValue:location]; } else { [fCustomLocationPopUp itemAtIndex:0].title = @""; [fCustomLocationPopUp itemAtIndex:0].image = nil; } } @end