// // SKPresentationOptionsSheetController.swift // PDF Reader Pro // // Created by liujiajie on 2024/1/17. // import Cocoa typealias closePresentationControllerCallBack = (_ vc: SKPresentationOptionsSheetController) -> () let RIGHTARROW_CHARACTER: unichar = 0x2192 let PAGE_COLUMNID = "page" let IMAGE_COLUMNID = "image" let TRANSITIONSTYLE_KEY = "transitionStyle" let DURATION_KEY = "duration" let SHOULDRESTRICT_KEY = "shouldRestrict" let PROPERTIES_KEY = "properties" let CONTENTOBJECT_BINDINGNAME = "contentObject" let MAX_PAGE_COLUMN_WIDTH: CGFloat = 100.0 let TABLE_OFFSET: CGFloat = 8.0 //var SKTransitionPropertiesObservationContext = UnsafeMutableRawPointer(mutating: "SKTransitionPropertiesObservationContext") var SKPDFViewTransitionsObservationContext: CChar? class SKPresentationOptionsSheetController: KMBaseWindowController { @IBOutlet var notesDocumentPopUpButton: NSPopUpButton! @IBOutlet var tableView: KMBotaTableView! @IBOutlet var separateCheckButton: NSButton! @IBOutlet var boxes, transitionLabels, transitionControls, buttons: NSArray! @IBOutlet var effectPopUpButton: NSPopUpButton! @IBOutlet var effectLabel: NSTextField! @IBOutlet var durationLabel: NSTextField! @IBOutlet var durationSlider: NSSlider! @IBOutlet var durationTF: NSTextField! @IBOutlet var extentLabel: NSTextField! @IBOutlet var screeBtn: NSButtonCell! @IBOutlet var pageBtn: NSButtonCell! @IBOutlet var synchoronizBox: NSBox! @IBOutlet var cancelBtn: NSButton! @IBOutlet var okBtn: NSButton! @IBOutlet var pageTransitionBox: NSBox! var closeCallBack: closePresentationControllerCallBack? var separate = false var transition: KMTransitionInfo = KMTransitionInfo() private var transitions_: NSArray? var transitions: NSArray? { get { return self.transitions_ } set{ if self.transitions_ != newValue { self.transitions_ = newValue if let data = self.transitions as? [KMTransitionInfo] { stopObservingTransitions(infos: data) startObservingTransitions(data) } } } } var currentTransitions: NSArray? { get{ return self.separate ? self.transitions : [transition as Any] } } var pageTransitions: NSArray? { get{ if self.separate && self.transitions?.count ?? 0 > 0 { return transforArr()//[transitions?.value(forKey: PROPERTIES_KEY) as Any] } return nil } } var notesDocument: NSDocument? { get{ _ = self.window return self.notesDocumentPopUpButton.selectedItem?.representedObject as? NSDocument } } var isScrolling: Bool { get{ let scroller = self.tableView?.enclosingScrollView?.verticalScroller as? KMScroller return scroller?.isScrolling ?? false } } var undoRedoManager = UndoManager() weak var controller: KMMainViewController? weak var transitionController: SKTransitionController? private weak var proxyDelegate_: SKTransitionControllerDelegate? override var windowNibName: NSNib.Name?{ return "TransitionSheet" } override init(window: NSWindow?) { super.init(window: window) } required init?(coder: NSCoder) { super.init(coder: coder) } deinit { NSLog("===SKPresentationOptionsSheetController==") } override func windowDidLoad() { super.windowDidLoad() let count: Int = SKTransitionController.transitionNames().count for i in 0.. 0.0 { SKResizeWindow(self.window, dw) SKShiftAndResizeViews(self.boxes as? [Any], -dw, dw) SKShiftAndResizeView(self.separateCheckButton, -dw, 0.0) } // collapse the table self.window?.setFrame(NSInsetRect(window?.frame ?? .zero, 0.5 * (NSWidth(tableView.enclosingScrollView?.frame ?? .zero) + TABLE_OFFSET), 0.0), display: false) self.tableView.registerForDraggedTypes(KMTransitionInfo.readableTypes(for: NSPasteboard(name: .drag))) self.tableView.delegate = self self.tableView.dataSource = self self.tableView.botaDelegate = self self.tableView.setTypeSelectHelper(SKTypeSelectHelper(matchOption: .SKFullStringMatch)) self.tableView.hasImageToolTips = true self.tableView.backgroundColor = NSColor(calibratedRed: 0.839216, green: 0.866667, blue: 0.898039, alpha: 1) if (transitionController?.pageTransitions != nil && transitionController?.pageTransitions.count ?? 0 > 0) { self.undoRedoManager.disableUndoRegistration() self.separate = true self.undoRedoManager.enableUndoRegistration() } // set the current notes document and observe changes for the popup self.handleDocumentsDidChangeNotification(notification: nil) let docIndex = self.notesDocumentPopUpButton.indexOfItem(withRepresentedObject: self.controller?.myDocument/*presentationNotesDocument*/) self.notesDocumentPopUpButton.selectItem(at: docIndex > 0 ? docIndex : 0) NotificationCenter.default.addObserver(self, selector: #selector(handleDocumentsDidChangeNotification(notification:)), name: NSNotification.Name("SKDocumentDidShowNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleDocumentsDidChangeNotification(notification:)), name: NSNotification.Name("SKDocumentControllerDidRemoveDocumentNotification"), object: nil) } override func initDefaultValue() { super.initDefaultValue() self.effectPopUpButton.target = self self.effectPopUpButton.action = #selector(effectButtonAction) self.durationTF.delegate = self self.screeBtn.target = self self.screeBtn.action = #selector(extentAction) self.pageBtn.target = self self.pageBtn.action = #selector(extentAction) } func stopObservingTransitions(infos: [KMTransitionInfo]) { } func startObservingTransitions(_ infos: [KMTransitionInfo]) { } func setValue(_ value: Any?, forKey key: String, ofTransition info: KMTransitionInfo) { info.setValue(value, forKey: key) } // MARK: - Private private func makeTransitions() { if transitions != nil { return } let tableColumn = self.tableView?.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: PAGE_COLUMNID)) let cell = tableColumn?.dataCell var labelWidth: CGFloat = 0.0 var array = [Any]() let dictionary: NSDictionary? = self.transition.properties let arr: NSArray? = self.transitionController?.pageTransitions as NSArray? let ptEnum = arr?.objectEnumerator() var tn: KMThumbnail? = nil labelWidth = min(labelWidth, MAX_PAGE_COLUMN_WIDTH) tableColumn?.minWidth = labelWidth tableColumn?.maxWidth = labelWidth tableColumn?.width = labelWidth var frame: NSRect = self.tableView?.enclosingScrollView?.frame ?? .zero let wi: CGFloat = 61//tableColumn?.value(forKeyPath: "@sum.width") as! CGFloat frame.size.width = 19.0 + wi self.tableView?.enclosingScrollView?.frame = frame self.transitions = array as NSArray } @objc dynamic func changePageTransitionType(sep: Bool) { self.separate = sep self.separateCheckButton.state = sep ? .on : .off let window = self.window let isVisible = window?.isVisible ?? false var frame = window?.frame ?? NSRect.zero let scrollView = tableView?.enclosingScrollView var extraWidth: CGFloat let firstResponder = window?.firstResponder var editor: NSTextView? = nil if let textView = firstResponder as? NSTextView { editor = textView if textView.isFieldEditor { } } if let editor = editor, window?.firstResponder != editor { window?.makeFirstResponder(firstResponder) } if sep { makeTransitions() extraWidth = (scrollView?.frame.width ?? 0) + TABLE_OFFSET frame.size.width += extraWidth frame.origin.x -= floor(0.5 * extraWidth) window?.setFrame(frame, display: isVisible, animate: isVisible) scrollView?.isHidden = false } else { scrollView?.isHidden = true extraWidth = (scrollView?.frame.width ?? 0) + TABLE_OFFSET frame.size.width -= extraWidth frame.origin.x += floor(0.5 * extraWidth) window?.setFrame(frame, display: isVisible, animate: isVisible) } (self.undoRedoManager.prepare(withInvocationTarget: self) as AnyObject).changePageTransitionType(sep: sep == false) } func selectedInfos() -> [KMTransitionInfo]? { if self.separate == false { return [self.transition] } else { guard let trans = self.transitions else { return nil } let ris = self.tableView.selectedRowIndexes var infos: [KMTransitionInfo] = [] for ri in ris { if ri >= trans.count { continue } if let info = trans.object(at: ri) as? KMTransitionInfo { infos.append(info) } } return infos } } private func _updateInfo(key: String, value: Any) { if key == KMTransitionStyleName { let _value = value as? SKAnimationTransitionStyle ?? .noTransition if self.separate == false { self.transition.transitionStyle = _value } else { guard let trans = self.transitions else { return } let ris = self.tableView.selectedRowIndexes for ri in ris { if ri >= trans.count { continue } let info = trans.object(at: ri) as? KMTransitionInfo info?.transitionStyle = _value } } } else if key == KMDurationName { let _value = value as? Float ?? 0 if self.separate == false { self.transition.duration = _value } else { guard let trans = self.transitions else { return } let ris = self.tableView.selectedRowIndexes for ri in ris { if ri >= trans.count { continue } let info = trans.object(at: ri) as? KMTransitionInfo info?.duration = _value } } } else if key == KMShouldRestrictName { let _value = value as? Bool ?? false if self.separate == false { self.transition.shouldRestrict = _value } else { guard let trans = self.transitions else { return } let ris = self.tableView.selectedRowIndexes for ri in ris { if ri >= trans.count { continue } let info = trans.object(at: ri) as? KMTransitionInfo info?.shouldRestrict = _value } } } } // Actions @objc func effectButtonAction(sender: NSPopUpButton) { let index = sender.indexOfSelectedItem let names = SKTransitionController.transitionNames() as? [String] let name = names?.safe_element(for: index) as? String ?? "" let type = SKTransitionController.style(forName: name) guard let infos = self.selectedInfos(), infos.isEmpty == false else { NSSound.beep() return } for info in infos { if info.transitionStyle != type { // undo self.undo_effectAction(info: info, key: KMTransitionStyleName, value: type, popup: nil) } } } @objc dynamic func undo_effectAction(info: KMTransitionInfo, key: String, value: Any, popup: NSPopUpButton?) { if key == KMTransitionStyleName { let oldValue = info.transitionStyle (self.undoRedoManager.prepare(withInvocationTarget: self) as AnyObject).undo_effectAction(info: info, key: key, value: oldValue, popup: self.effectPopUpButton) info.transitionStyle = value as? SKAnimationTransitionStyle ?? .noTransition popup?.selectItem(at: Int(info.transitionStyle.rawValue)) } } @objc func extentAction(sender: NSMatrix) { let value = sender.selectedTag() == 1 ? true : false self.undoRedoManager.registerUndo(withTarget: self) { target in if value { target.screeBtn.performClick(nil) } else { target.pageBtn.performClick(nil) } } self._updateInfo(key: KMShouldRestrictName, value: value) } func transforArr() -> NSArray { let arr = NSMutableArray() for i in 0..<(transitions?.count ?? 0) { if let item = transitions?[i] as? KMTransitionInfo { let dic = NSMutableDictionary.init() dic.setValue(item.duration, forKey: "duration") dic.setValue(item.shouldRestrict, forKey: "shouldRestrict") dic.setValue(item.properties?[SKStyleNameKey], forKey: "styleName") arr.add(dic) } } return arr } @IBAction func pageTransitionAction(_ sender: NSButton) { self.separate = sender.state == .on self.changePageTransitionType(sep: self.separate) Task { @MainActor in } } @IBAction func changeDurationSlider(_ sender: NSSlider) { guard let infos = self.selectedInfos(), infos.isEmpty == false else { NSSound.beep() return } for info in infos { if info.duration != sender.floatValue { // undo self.undo_sliderAction(info: info, key: KMDurationName, value: sender.floatValue, slider: nil, textF: self.durationTF) } } } @objc func undo_sliderAction(info: KMTransitionInfo, key: String, value: Any, slider: NSSlider?, textF: NSTextField?) { if key == KMDurationName { let oldValue = info.duration (self.undoRedoManager.prepare(withInvocationTarget: self) as AnyObject).undo_sliderAction(info: info, key: key, value: oldValue, slider: self.durationSlider, textF: self.durationTF) let duration = value as? Float ?? 0 slider?.floatValue = duration textF?.floatValue = duration } } @IBAction func cancelAction(_ sender: Any) { if let handle = self.closeCallBack { handle(self) } } @IBAction func okAction(_ sender: Any) { if let handle = self.closeCallBack { handle(self) } if self.undoRedoManager.canUndo { self.transitionController?.transitionStyle = transition.transitionStyle self.transitionController?.duration = CGFloat(transition.duration) self.transitionController?.shouldRestrict = transition.shouldRestrict self.transitionController?.pageTransitions = pageTransitions as? [Any] (self.controller?.undoManager)?.setActionName(NSLocalizedString("Change Transitions", comment: "Undo action name")) } } // Noti Actions @objc private func handleDocumentsDidChangeNotification(notification: Notification?) { let currentDoc = self.notesDocumentPopUpButton.selectedItem?.representedObject as? NSDocument while self.notesDocumentPopUpButton.numberOfItems > 1 { self.notesDocumentPopUpButton.removeItem(at: self.notesDocumentPopUpButton.numberOfItems - 1) } let document = self.controller?.myDocument let documents = NSMutableArray() let pageCount = self.controller?.document?.pageCount ?? 0 for doc in NSDocumentController.shared.documents { guard let mainDoc = doc as? KMMainDocument else { continue } if mainDoc.isHome { continue } if mainDoc.isEqual(to: document) { continue } if let pdfDoc = mainDoc.mainViewController?.document { if pdfDoc.pageCount == pageCount { documents.add(doc) } } } let sortDescriptor = NSSortDescriptor(key: "displayName", ascending: true) documents.sort(using: [sortDescriptor]) for doc in documents { self.notesDocumentPopUpButton.addItem(withTitle: (doc as AnyObject).displayName) self.notesDocumentPopUpButton.lastItem?.representedObject = doc } let docIndex = self.notesDocumentPopUpButton.indexOfItem(withRepresentedObject: currentDoc) self.notesDocumentPopUpButton.selectItem(at: docIndex == -1 ? 0 : docIndex) } } // MARK: - NSTextFieldDelegate extension SKPresentationOptionsSheetController: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { if self.durationTF.isEqual(to: obj.object) { } } func controlTextDidEndEditing(_ obj: Notification) { if self.durationTF.isEqual(to: obj.object) { if checkString(str: self.durationTF.stringValue) { self.durationSlider.floatValue = self.durationTF.floatValue // 更新数据 self._updateInfo(key: KMDurationName, value: self.durationTF.floatValue) }else{ self.durationTF.stringValue = "1.0" } } } func checkString(str: String) -> Bool { let reg = "^[1-9]d*.d*|0.d*[1-9]d*|0?.0+|0$" let pre = NSPredicate(format: "SELF MATCHES %@", reg) if pre.evaluate(with: str) { return true } return false } } // MARK: - NSWindowDelegate extension SKPresentationOptionsSheetController: NSWindowDelegate { func windowWillReturnUndoManager(_ window: NSWindow) -> UndoManager? { return self.undoRedoManager } } // MARK: - SKTransitionControllerDelegate extension SKPresentationOptionsSheetController: SKTransitionControllerDelegate { func transitionController(_ controller: SKTransitionController!, valueDidChanged info: [AnyHashable : Any]!) { // 消息转发 self.proxyDelegate_?.transitionController(controller, valueDidChanged: info) } } // MARK: - NSTableViewDelegate, NSTableViewDataSource extension SKPresentationOptionsSheetController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return self.transitions?.count ?? 0 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let identifier = tableColumn?.identifier let info = transitions?[row] as? KMTransitionInfo var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("KMPresentTableViewCell"), owner: self) if cell == nil { cell = KMPresentTableViewCell(type: .text) } let myCellView: KMPresentTableViewCell = cell! as! KMPresentTableViewCell myCellView.label?.stringValue = info?.label ?? "" let page = self.controller?.listView.document.page(at: UInt(info?.thumbnail?.pageIndex ?? 0)) let image = page?.thumbnail(of: page?.bounds.size ?? .zero) myCellView.iv?.image = image return myCellView; } func tableViewSelectionDidChange(_ notification: Notification) { if self.tableView.isEqual(to: notification.object) == false { return } let ris = self.tableView.selectedRowIndexes if ris.isEmpty { return } var info: KMTransitionInfo? if ris.count == 1 { info = self.transitions?[ris.first ?? 0] as? KMTransitionInfo self.pageTransitionBox.title = KMLocalizedString("Page Transition") + (info?.label ?? "") } else { self.pageTransitionBox.title = KMLocalizedString("Page Transition") info = self.transition } self.effectPopUpButton.selectItem(at: Int(info?.transitionStyle.rawValue ?? 0)) let durationV = info?.duration ?? 0 self.durationSlider.animator().floatValue = durationV self.durationTF.floatValue = durationV let shouldRestrict = info?.shouldRestrict ?? false if shouldRestrict { // 1 self.pageBtn.state = .on self.screeBtn.state = .off } else { self.screeBtn.state = .on self.pageBtn.state = .off } } func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { if rowIndexes.count == 1 { if let data = transitions?[rowIndexes.first ?? 0] as? NSPasteboardWriting { pboard.clearContents() pboard.writeObjects([data]) } return true } else { return false } } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { return NSDragOperation(rawValue: 0) } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { return false } } extension SKPresentationOptionsSheetController: KMBotaTableViewDelegate { func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? { return controller?.document?.page(at: UInt(rowIndex)) } func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) { if let data = transitions?[rowIndexes.first ?? 0] as? NSPasteboardWriting { let pboard = NSPasteboard.general pboard.clearContents() pboard.writeObjects([data]) } } func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool { return false } func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard) { } func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool { return false } func tableView(_ aTableView: NSTableView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray { return [] } }