// // KMPreferenceController.swift // PDF Reader Pro // // Created by tangchao on 2023/11/7. // import Cocoa class KMPreferenceWindow: NSWindow { // override func responds(to aSelector: Selector!) -> Bool { // return aSelector != #selector(toggleToolbarShow) && aSelector != #selector(runToolbarCustomizationPalette) && super.responds(to: aSelector) // } } private let SKPreferencesToolbarIdentifier = "SKPreferencesToolbarIdentifier" private let SKPreferenceWindowFrameAutosaveName = "SKPreferenceWindow" private let BOTTOM_MARGIN = 27.0 private let SKLastSelectedPreferencePaneKey = "SKLastSelectedPreferencePane" private let NIBNAME_KEY = "nibName" @objc protocol KMPreferencePane: NSObjectProtocol { @objc optional func defaultsDidRevert() @objc optional func reloadData() } @objcMembers class KMPreferenceController: NSWindowController { @IBOutlet var resetBtn: NSButton! @IBOutlet var resetAllBtn: NSButton! var preferencePanes: [KMPreferencePane]? var history: [NSViewController]? var historyIndex: Int = 0 var currentPane: KMPreferencePane? private let INITIALUSERDEFAULTS_KEY = "InitialUserDefaults" private let RESETTABLEKEYS_KEY = "ResettableKeys" /* @interface SKPreferenceController : NSWindowController { NSArray *resetButtons; } @property (nonatomic, retain) IBOutlet NSArray *resetButtons; + (id)sharedPrefenceController; - (IBAction)resetAll:(id)sender; - (IBAction)resetCurrent:(id)sender; - (IBAction)doGoToNextPage:(id)sender; - (IBAction)doGoToPreviousPage:(id)sender; - (IBAction)doGoToFirstPage:(id)sender; - (IBAction)doGoToLastPage:(id)sender; - (IBAction)doGoBack:(id)sender; - (IBAction)doGoForward:(id)sender; - (IBAction)changeFont:(id)sender; - (IBAction)changeAttributes:(id)sender; - (void)selectPaneWithIdentifier:(NSString *)itemIdentifier; @end */ deinit { KMPrint("KMPreferenceController deinit.") // currentPane = nil; } static let shared = KMPreferenceController(windowNibName: "PreferenceWindow") override func windowDidLoad() { super.windowDidLoad() self.preferencePanes = [KMGeneralPreferences(), KMDisplayPreferences(), KMNotesPreferences(), KMSyncPreferences()] self.history = [] self.historyIndex = 0 if #available(macOS 11.0,*) { self.window?.toolbarStyle = .expanded } resetBtn.title = KMLocalizedString("Reset") resetAllBtn.title = KMLocalizedString("Reset All") let window = self.window let toolbar = NSToolbar(identifier: SKPreferencesToolbarIdentifier) toolbar.allowsUserCustomization = false toolbar.autosavesConfiguration = false toolbar.isVisible = true toolbar.delegate = self window?.toolbar = toolbar window?.showsToolbarButton = false window?.contentView?.wantsLayer = true // we want to restore the top of the window, while without the force it restores the bottom position without the size window?.setFrameUsingName(SKPreferenceWindowFrameAutosaveName, force: true) self.windowFrameAutosaveName = SKPreferenceWindowFrameAutosaveName // [NSGraphicsContext SKAutoSizeButtons:resetButtons rightAlign:false]; var width = 0.0 var frame: NSRect = .zero var pane: NSViewController? var view: NSView? for pane in self.preferencePanes ?? [] { let _pane = pane as? NSViewController width = fmax(width, NSWidth(_pane?.view.frame ?? .zero)) } for pane in self.preferencePanes ?? [] { let _pane = pane as? NSViewController view = _pane?.view frame = view?.frame ?? .zero if let data = view?.autoresizingMask.contains(.width), data { frame.size.width = width } else { frame.origin.x = floor(0.5 * (width - NSWidth(frame))) } frame.origin.y = BOTTOM_MARGIN view?.frame = frame } self.currentPane = self._preferencePane(forItemIdentifier: UserDefaults.standard.string(forKey: SKLastSelectedPreferencePaneKey) ?? "") ?? self.preferencePanes?.first toolbar.selectedItemIdentifier = NSToolbarItem.Identifier((self.currentPane as? NSViewController)?.nibName ?? "") if let _currentPane = self.currentPane as? NSViewController { window?.title = _currentPane.title ?? "" self.history?.append(_currentPane) } view = (self.currentPane as? NSViewController)?.view frame = window?.frame ?? .zero frame.size.width = width frame = KMShrinkRect(rect: frame, amount: NSHeight(window?.contentView?.frame ?? .zero) - NSMaxY(view?.frame ?? .zero), edge: .minY) window?.setFrame(frame, display: false) if let data = view { window?.contentView?.addSubview(data) } NotificationCenter.default.addObserver(self, selector: #selector(preferenceDidChangeNotification), name: KMPreferenceManager.didChangeNotification, object: nil) self.resetBtn.toolTip = NSLocalizedString("Revert all currently shown preferences to their original values", comment: "") self.resetAllBtn.toolTip = NSLocalizedString("Revert all preferences to their original values", comment: "") } @IBAction func changeFont(_ sender: AnyObject?) { self.window?.contentView?.activeFontWell()?.changeFontFromFontManager(sender) } /* - (void)selectPaneWithIdentifier:(NSString *)itemIdentifier { [self selectPane:[self preferencePaneForItemIdentifier:itemIdentifier]]; } #pragma mark Actions - (IBAction)doGoToNextPage:(id)sender { NSUInteger itemIndex = [preferencePanes indexOfObject:currentPane]; if (itemIndex != NSNotFound && ++itemIndex < [preferencePanes count]) [self selectPane:[preferencePanes objectAtIndex:itemIndex]]; } - (IBAction)doGoToPreviousPage:(id)sender { NSUInteger itemIndex = [preferencePanes indexOfObject:currentPane]; if (itemIndex != NSNotFound && itemIndex-- > 0) [self selectPane:[preferencePanes objectAtIndex:itemIndex]]; } - (IBAction)doGoToFirstPage:(id)sender { [self selectPane:[preferencePanes objectAtIndex:0]]; } - (IBAction)doGoToLastPage:(id)sender { [self selectPane:[preferencePanes lastObject]]; } - (IBAction)doGoBack:(id)sender { if (historyIndex > 0) { historyIndex--; [self selectPane:nil]; } } - (IBAction)doGoForward:(id)sender { if (historyIndex + 1 < [history count]) { historyIndex++; [self selectPane:nil]; } } - (IBAction)changeAttributes:(id)sender { // [[[[self window] contentView] activeFontWell] changeAttributesFromFontManager:sender]; } - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if ([menuItem action] == @selector(doGoToNextPage:) || [menuItem action] == @selector(doGoToLastPage:)) return [currentPane isEqual:[preferencePanes lastObject]] == NO; else if ([menuItem action] == @selector(doGoToPreviousPage:) || [menuItem action] == @selector(doGoToFirstPage:)) return [currentPane isEqual:[preferencePanes objectAtIndex:0]] == NO; else if ([menuItem action] == @selector(doGoBack:)) return historyIndex > 0; else if ([menuItem action] == @selector(doGoForward:)) return historyIndex + 1 < [history count]; return YES; } #pragma mark Toolbar @end */ @IBAction func resetCurrent(_ sender: AnyObject?) { if (self.currentPane == nil) { return } let label = (self.currentPane as? NSViewController)?.title ?? "" let alert = NSAlert() alert.messageText = String(format: KMLocalizedString("Reset %@ preferences to their original values?"), label) alert.informativeText = String(format: KMLocalizedString("Choosing Reset will restore all settings in this pane to the state they were in when PDF Reader Pro Edition was first installed."), label) alert.addButton(withTitle: KMLocalizedString("Reset")) alert.addButton(withTitle: KMLocalizedString("Cancel")) alert.beginSheetModal(for: self.window!, modalDelegate: self, didEnd: #selector(_resetCurrentSheetDidEnd), contextInfo: nil) } @IBAction func resetAll(_ sender: AnyObject?) { let alert = NSAlert() alert.messageText = KMLocalizedString("Reset all preferences to their original values?") alert.informativeText = KMLocalizedString("Choosing Reset will restore all settings to the state they were in when PDF Reader Pro Edition was first installed.") alert.addButton(withTitle: KMLocalizedString("Reset")) alert.addButton(withTitle: KMLocalizedString("Cancel")) let response = alert.runModal() if (response == .alertFirstButtonReturn) { NSUserDefaultsController.shared.revertToInitialValues(forKeys: nil) for pane in self.preferencePanes ?? [] { pane.defaultsDidRevert?() } KMPreference.shared.resetAllData() } } @objc private func preferenceDidChangeNotification(sender: Notification) { self.currentPane?.reloadData?() } } // MARK: - Private Methods extension KMPreferenceController { private func _preferencePane(forItemIdentifier itemIdent: String) -> KMPreferencePane? { self._panelControlSelectColor(itemIdent) for pane in self.preferencePanes ?? [] { let _pane = pane as? NSViewController if _pane?.nibName == itemIdent { return pane } } return nil } private func _panelControlSelectColor(_ identifier: String) { for item in self.window?.toolbar?.items ?? [] { if item.itemIdentifier.rawValue == identifier { if item.itemIdentifier.rawValue == "GeneralPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsGeneralSel") } else if item.itemIdentifier.rawValue == "DisplayPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsViewSel") } else if item.itemIdentifier.rawValue == "NotesPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsAnnotationSel") } else if item.itemIdentifier.rawValue == "SyncPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsSynchronizeSel") } else if item.itemIdentifier.rawValue == "DropboxPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsDropboxSel") } } else { if item.itemIdentifier.rawValue == "GeneralPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsGeneralNor") } else if item.itemIdentifier.rawValue == "DisplayPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsViewNor") } else if item.itemIdentifier.rawValue == "NotesPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsAnnotationNor") } else if item.itemIdentifier.rawValue == "SyncPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsSynchronizeNor") } else if item.itemIdentifier.rawValue == "DropboxPreferences" { item.image = NSImage(named: "KMImageNameElseSettingsDropboxNor") } } } } @objc private func _selectPaneAction(_ sender: AnyObject?) { let _item = sender as? NSToolbarItem self._panelControlSelectColor(_item?.itemIdentifier.rawValue ?? "") if let pane = self._preferencePane(forItemIdentifier: _item?.itemIdentifier.rawValue ?? "") { self._selectPane(pane) } } private func _selectPane(_ pane: KMPreferencePane) { var _pane = pane as? NSViewController let _currentPane = self.currentPane as? NSViewController if _pane != _currentPane { if let data = _pane { self.historyIndex += 1 if let cnt = self.history?.count, cnt > self.historyIndex { let range = self.historyIndex ..< cnt-self.historyIndex self.history?.removeSubrange(range) } self.history?.append(data) } else { _pane = self.history?[self.historyIndex] } let window = self.window let contentView = window?.contentView let oldView = _currentPane?.view let view = _pane?.view var frame = window?.frame ?? .zero frame = KMShrinkRect(rect: frame, amount: NSHeight(contentView?.frame ?? .zero) - NSMaxY(view?.frame ?? .zero), edge: .minY) // make sure edits are committed _currentPane?.commitEditing() NSUserDefaultsController.shared.commitEditing() self.currentPane = pane window?.title = (self.currentPane as? NSViewController)?.title ?? "" if let data = (self.currentPane as? NSViewController)?.nibName { UserDefaults.standard.setValue(data, forKey: SKLastSelectedPreferencePaneKey) window?.toolbar?.selectedItemIdentifier = NSToolbarItem.Identifier(data) } if UserDefaults.standard.bool(forKey: SKDisableAnimationsKey) { if oldView != nil && view != nil { contentView?.replaceSubview(oldView!, with: view!) } window?.setFrame(frame, display: true) } else { let duration = window?.animationResizeTime(frame) ?? 0 contentView?.displayIfNeeded() NSAnimationContext.runAnimationGroup { context in context.duration = duration if oldView != nil && view != nil { contentView?.animator().replaceSubview(oldView!, with: view!) } window?.animator().setFrame(frame, display: true) } } } } @objc private func _resetCurrentSheetDidEnd(_ alert: NSAlert, _ returnCode: Int, _ contextInfo: Any) { if (returnCode == NSApplication.ModalResponse.alertFirstButtonReturn.rawValue) { if let initialUserDefaultsURL = Bundle.main.url(forResource: INITIALUSERDEFAULTS_KEY, withExtension: "plist") { if let _currentPane = self.currentPane as? NSViewController { let resettableKeys = (NSDictionary(contentsOf: initialUserDefaultsURL)?.object(forKey: RESETTABLEKEYS_KEY) as? NSDictionary)?.object(forKey: _currentPane.nibName ?? "") if let data = resettableKeys as? [String] { NSUserDefaultsController.shared.revertToInitialValues(forKeys: data) } self.currentPane?.defaultsDidRevert?() if _currentPane is KMGeneralPreferences { KMPreference.shared.resetData(.general) } else if _currentPane is KMDisplayPreferences { KMPreference.shared.resetData(.display) } else if _currentPane is KMNotesPreferences { KMPreference.shared.resetData(.markup) } } } } } } extension KMPreferenceController: NSToolbarDelegate { func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { let pane = self._preferencePane(forItemIdentifier: itemIdentifier.rawValue) as? NSViewController let item = NSToolbarItem(itemIdentifier: itemIdentifier) item.label = pane?.title ?? "" var ima: NSImage? if itemIdentifier.rawValue == "GeneralPreferences" { ima = NSImage(named: "KMImageNameElseSettingsGeneralNor") } else if itemIdentifier.rawValue == "DisplayPreferences" { ima = NSImage(named: "KMImageNameElseSettingsViewNor") } else if itemIdentifier.rawValue == "NotesPreferences" { ima = NSImage(named: "KMImageNameElseSettingsAnnotationNor") } else if itemIdentifier.rawValue == "SyncPreferences" { ima = NSImage(named: "KMImageNameElseSettingsSynchronizeNor") } else if itemIdentifier.rawValue == "DropboxPreferences" { ima = NSImage(named: "KMImageNameElseSettingsDropboxNor") } item.image = ima item.target = self item.action = #selector(_selectPaneAction) return item; } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { var items: [NSToolbarItem.Identifier] = [] for item in self.preferencePanes ?? [] { if let name = (item as? NSViewController)?.nibName { items.append(NSToolbarItem.Identifier(rawValue: name)) } } return items } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { return self.toolbarDefaultItemIdentifiers(toolbar) } func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { return self.toolbarDefaultItemIdentifiers(toolbar) } } extension KMPreferenceController: NSWindowDelegate { func windowDidResignMain(_ notification: Notification) { self.window?.contentView?.deactivateWellSubcontrols() } func windowWillClose(_ notification: Notification) { (self.currentPane as? NSViewController)?.commitEditing() NSUserDefaultsController.shared.commitEditing() } }