// // KMMainDocument.swift // PDF Reader Pro // // Created by wanjun on 2022/12/6. // import Cocoa import CoreFoundation @objc enum KMArchiveMask: Int { case diskImage = 1 case email = 2 } @objc enum KMExportOption: Int { case `default` = 0 case withoutNotes case withEmbeddedNotes } typealias KMMainDocumentCloudUploadHanddler = (@escaping(Bool, String)->()) -> () @objcMembers class KMMainDocument: CTTabContents { struct MDFlags { var exportOption: UInt32 // assuming this is a 2-bit field, change to appropriate data type var exportUsingPanel: UInt32 // assuming this is a 1-bit field, change to appropriate data type var gettingFileType: UInt32 // assuming this is a 1-bit field, change to appropriate data type var convertingNotes: UInt32 // assuming this is a 1-bit field, change to appropriate data type var needsPasswordToConvert: UInt32 // assuming this is a 1-bit field, change to appropriate data type } static let kLastExportedTypeKey = "SKLastExportedType" static let kLastExportedOptionKey = "SKLastExportedOption" static let kBundleDataFilename = "data" var mainViewController: KMMainViewController? var homeWindowController: KMHomeWindowController? var homeViewController: KMHomeViewController? var bookmarkSheetController: KMBookmarkSheetController? var bookmarkController: KMBookmarkController? var isNewCreated: Bool = false var closedByUserGestureFlag: Bool = false // 标记 closedByUserGesture 这个状态需要延后存储(如果需要) var cloud: Bool = false var cloudUploadHanddler: KMMainDocumentCloudUploadHanddler? var isUnlockFromKeychain: Bool = false private var _saveAsing = false var fileUpdateChecker: SKFileUpdateChecker? var mdFlags: MDFlags? var currentDocumentSetup: [String: Any] { get { var tempSetup: [String: Any] = [:] let tempMainSetup: [String: Any] = mainViewController?.currentSetup() ?? [:] let filePath = fileURL?.path ?? "" if filePath.count > 0{ tempSetup.updateValue(filePath, forKey: KMDocumentSetupFileNameKey) }else { return tempSetup } if let alias = SKAlias.init(url: fileURL){ if let data = alias.data{ tempSetup.updateValue(data as Any, forKey: KMDocumentSetupAliasKey) } } if tempSetup.count > 0 { tempSetup.merge(tempMainSetup) { (_, new) in new } } return tempSetup } set { } } private var _saveToURL: URL? var saveToURL: URL? { get { return self._saveToURL } } var exportAccessoryC: SKExportAccessoryController? var pdfData: Data? weak var watermarkSaveDelegate: AnyObject? private var _trackEvents = IndexSet() override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { if (self.isNewCreated) { // if let data = self.mainViewController, !data.isPDFDocumentEdited && !data.needSave && !self.isDocumentEdited { self._km_save(to: url, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) return // } } if (!self.needSaveWatermark()) { self._km_save(to: url, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) return } var openAccessoryView = self.watermarkSaveDelegate != nil if (openAccessoryView) { if let _browser = self.watermarkSaveDelegate as? KMBrowser, _browser.isCloseAllTabViewItem { openAccessoryView = false } } self._km_saveForWatermark(openAccessoryView: openAccessoryView) { [unowned self] in self.trackEvents() } callback: { [unowned self] needSave, params in if (needSave) { self._km_save(to: url, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) } else { // 水印保存 if (self.watermarkSaveDelegate == nil) { if let data = params.first as? KMResult, data == .cancel { if let shouldClose = params.last as? Bool, shouldClose { DispatchQueue.main.async { self.mainViewController?.browserWindowController?.browser.windowDidBeginToClose() } } } else { DispatchQueue.main.async { self.mainViewController?.browserWindowController?.browser.windowDidBeginToClose() } } return } if let data = params.first as? KMResult, data == .cancel { if var shouldClose = params.last as? Bool { if let _browser = self.watermarkSaveDelegate as? KMBrowser, _browser.isCloseAllTabViewItem { shouldClose = true } (self.watermarkSaveDelegate as? KMBrowser)?.document(self, shouldClose: shouldClose, contextInfo: nil) } } else { (self.watermarkSaveDelegate as? KMBrowser)?.document(self, shouldClose: true, contextInfo: nil) } self.watermarkSaveDelegate = nil } } } override func makeWindowControllers() { // Returns the storyboard that contains your document window. if ((self.fileURL?.path) != nil) { if !self.fileURL!.path.isPDFValid() { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return } } let mainWindow = NSApp.mainWindow var currentWindowController: KMBrowserWindowController? if mainWindow != nil { let windowController = mainWindow!.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } // if (currentWindowController == nil) && (self.fileURL != nil) { // let browser = KMBrowser.init() as KMBrowser // browser.addHomeTabContents() // browser.windowController = KMBrowserWindowController.init(browser: browser) // currentWindowController = browser.windowController as? KMBrowserWindowController // } if currentWindowController?.browser == nil && (self.fileURL != nil) { let browser: KMBrowser = KMBrowser.init() browser.windowController = KMBrowserWindowController.init(browser: browser) browser.addHomeTabContents() currentWindowController = browser.windowController as? KMBrowserWindowController browser.windowController.showWindow(self) } mainViewController = KMMainViewController.init() mainViewController?.myDocument = self self.mdFlags = MDFlags(exportOption: 0, exportUsingPanel: 0, gettingFileType: 0, convertingNotes: 0, needsPasswordToConvert: 0) if ((self.fileURL?.path) != nil) { let pdfDocument = CPDFDocument.init(url: URL(fileURLWithPath: self.fileURL!.path)) mainViewController?.document = pdfDocument } if mainViewController?.document == nil { return } self.view = mainViewController?.view if let url = self.fileURL { AppSandboxFileAccess().persistPermissionURL(url as URL) let bookmarkData = try?url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) if bookmarkData != nil { AppSandboxFileAccess().bookmarkPersistanceDelegate.setBookmarkData(bookmarkData! as Data, for: url as URL) AppSandboxFileAccess().bookmarkPersistanceDelegate.setBookmarkData(bookmarkData! as Data, for: NSURL(fileURLWithPath: url.path) as URL) } } if let currentBrowser = currentWindowController?.browser { let activeBrowser = currentBrowser.activeTabContents() let activeIndex = currentBrowser.activeTabIndex() let ishome = activeBrowser?.isHome ?? false let isfirstTab = (activeIndex == 0) if ishome && !isfirstTab { // 替换 【标签需要被替换】 self.addWindowController(currentWindowController!) self.mainViewController?.browserWindowController = currentWindowController // 替换 document currentWindowController?.browser.replaceTabContents(at: Int32(activeIndex), with: self) // 刷新标签 currentWindowController?.browser.updateTabState(at: Int32(activeIndex)) // 刷新 home icon if let tabStripController = currentWindowController?.tabStripController { if let view = tabStripController.view(at: UInt(activeIndex)) as? CTTabView { view.controller().isHome = self.isHome view.controller().isNewTab = self.isNewTab view.controller().updateUI() } } } else { if currentWindowController?.browser.tabCount() ?? 0 > 1 && (!KMMemberInfo.shared.isMemberAllFunction || KMPreference.shared.openDocumentType == .newWindow){ // 开启新窗口 let browser = KMBrowser.init() as KMBrowser browser.windowController = KMBrowserWindowController.init(browser: browser) browser.addHomeTabContents() browser.windowController.showWindow(self) browser.add(self, at: Int32()-1, inForeground: true) self.addWindowController(browser.windowController) self.mainViewController?.browserWindowController = browser.windowController as? KMBrowserWindowController }else { // 正常拼接到后面 self.addWindowController(currentWindowController!) self.mainViewController?.browserWindowController = currentWindowController currentWindowController?.browser.add(self, at: Int32()-1, inForeground: true) } } } } override func showWindows() { super.showWindows() self.setDataFromTmpData() } override func windowControllerDidLoadNib(_ aController: NSWindowController) { super.windowControllerDidLoadNib(aController) self.setDataFromTmpData() fileUpdateChecker = SKFileUpdateChecker.init(for: self) fileUpdateChecker?.isEnabled = true } override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) async throws { do { try await super.save(to: url, ofType: typeName, for: saveOperation) } catch let outError { NSApp.presentError(outError) } if saveOperation == .saveToOperation { if let data = self.mdFlags?.exportUsingPanel, data == 1 { if FileManager.default.fileExists(atPath: url.path) { let ws = NSWorkspace.shared ws.activateFileViewerSelecting([url]) } } } self.mdFlags?.exportUsingPanel = 0 self.mdFlags?.exportOption = UInt32(KMExportOption.default.rawValue) } override func write(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, originalContentsURL absoluteOriginalContentsURL: URL?) throws { try self._km_write(to: url, ofType: typeName, for: saveOperation, originalContentsURL: absoluteOriginalContentsURL) } override func canClose(withDelegate delegate: Any, shouldClose shouldCloseSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { let isPrompt = KMPreferenceManager.shared.closeFileIsPrompt() if (isPrompt) { super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo) if KMAdvertisementManager.manager.appClosedCount == 0 { KMAdvertisementManager.manager.appClosedCount = 1 } return } if (self.isNewCreated) { self.save(nil) } else if (self.isDocumentEdited) { self.save(nil) } else if (mainViewController != nil) { if self.mainViewController!.isPDFDocumentEdited || self.mainViewController!.needSave { self.save(nil) } } super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo) } override func saveAs(_ sender: Any?) { if (!self.needSaveWatermark()) { self._km_saveAs(sender) return } self._km_saveForWatermark { [weak self] needSave, _ in if (needSave) { self?._km_saveAs(sender) } } } override func saveTo(_ sender: Any?) { guard let pdfDoc = self.mainViewController?.listView.document else { NSSound.beep() return } if pdfDoc.allowsPrinting == false || pdfDoc.allowsCopying == false { Task { _ = await KMAlertTool.runModel(message: NSLocalizedString("This is a secured document. Editing is not permitted.", comment: "")) } return } let idx = (sender as? NSMenuItem)?.tag ?? 0 var typeName = KMPDFDocumentType if idx == 0 { typeName = KMPDFDocumentType } else if idx == 1 { typeName = KMPDFBundleDocumentType } else if idx == 2 { typeName = KMNotesDocumentType } else if idx == 3 { typeName = KMNotesTextDocumentType } else if idx == 4 { typeName = KMNotesRTFDocumentType } else if idx == 5 { typeName = KMNotesRTFDDocumentType } else if idx == 6 { typeName = KMNotesDocumentType } KMDataManager.ud_set(typeName, forKey: Self.kLastExportedTypeKey) super.saveTo(sender) } override func prepareSavePanel(_ savePanel: NSSavePanel) -> Bool { let success = super.prepareSavePanel(savePanel) let exportUsingPanel = self.mdFlags?.exportUsingPanel ?? 0 if success && exportUsingPanel > 0 { // *formatPopup = [[savePanel accessoryView] subviewOfClass:[NSPopUpButton class]]; var formatPopup: NSPopUpButton? let svs = savePanel.accessoryView?.subviews.first?.subviews ?? [] for sv in svs { if let data = sv as? NSPopUpButton { formatPopup = data break } } self._removeSavePanelOfFormatPopupItems(savePanel) if (formatPopup != nil) { let lastExportedType = KMDataManager.ud_string(forKey: Self.kLastExportedTypeKey) var lastExportedOption = KMDataManager.ud_integer(forKey: Self.kLastExportedOptionKey) if lastExportedOption == 0 { lastExportedOption = KMExportOption.withEmbeddedNotes.rawValue } // if (lastExportedType != nil) { // let idx = formatPopup?.indexOfItem(withRepresentedObject: lastExportedType) ?? -1 // let selectedIdx = formatPopup?.indexOfSelectedItem ?? 0 // if idx != -1 && idx != selectedIdx { // formatPopup?.selectItem(at: idx) // formatPopup?.sendAction(formatPopup?.action, to: formatPopup?.target) // [savePanel setAllowedFileTypes:[NSArray arrayWithObjects:[self fileNameExtensionForType:lastExportedType saveOperation:NSSaveToOperation], nil]]; // if let data = self.fileNameExtension(forType: lastExportedType!, saveOperation: .saveToOperation) { // savePanel.allowedFileTypes = [data] // } // } // } self.mdFlags?.exportOption = UInt32(lastExportedOption) // // self.exportAccessoryC = SKExportAccessoryController() // self.exportAccessoryC?.addFormatPopUpButton(formatPopup) // self.exportAccessoryC?.matrix.target = self // self.exportAccessoryC?.matrix.action = #selector(changeExportOption) // savePanel.accessoryView = self.exportAccessoryC?.view // self._updateExportAccessoryView() } } else if success { self._removeSavePanelOfFormatPopupItems(savePanel) } return success } override func runModalSavePanel(for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { self.mdFlags?.exportUsingPanel = saveOperation == .saveToOperation ? 1 : 0 self.mdFlags?.exportOption = UInt32(KMExportOption.default.rawValue) if (self.isNewCreated) { // if let data = self.mainViewController, !data.isPDFDocumentEdited && !data.needSave && !self.isDocumentEdited { self._km_runModalSavePanel(for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) return // } } if (!self.needSaveWatermark()) { self._km_runModalSavePanel(for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) return } self._km_saveForWatermark { [weak self] needSave, _ in if (needSave) { self?._km_runModalSavePanel(for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) } } } override func save(_ sender: Any?) { if (!self.needSaveWatermark()) { self._km_save(sender) return } self._km_saveForWatermark { [weak self] in self?.trackEvents() } callback: { [weak self] needSave, _ in if (needSave) { self?._km_save(sender) } } } func systemInteractionMode() -> KMInteractionMode { let mainWindow = NSApp.mainWindow if mainWindow != nil { let windowController = mainWindow!.windowController if windowController?.window?.screen?.isEqual(NSScreen.screens[0]) ?? false{ return mainViewController?.interactionMode ?? .normal } } return .normal } func saveForWatermark() { if (!self.needSaveWatermark()) { self._km_save(nil) return } self._km_saveForWatermark { [weak self] in self?.trackEvents() } callback: { [unowned self] needSave, params in if (needSave) { self._km_save(nil) } else { // 水印保存 if (self.watermarkSaveDelegate == nil) { if let data = params.first as? KMResult, data == .cancel { if let shouldClose = params.last as? Bool, shouldClose { DispatchQueue.main.async { self.mainViewController?.browserWindowController?.browser.windowDidBeginToClose() } } } else { DispatchQueue.main.async { self.mainViewController?.browserWindowController?.browser.windowDidBeginToClose() } } return } if let data = params.first as? KMResult, data == .cancel { if var shouldClose = params.last as? Bool { if let _browser = self.watermarkSaveDelegate as? KMBrowser, _browser.isCloseAllTabViewItem { shouldClose = true } (self.watermarkSaveDelegate as? KMBrowser)?.document(self, shouldClose: shouldClose, contextInfo: nil) } } else { (self.watermarkSaveDelegate as? KMBrowser)?.document(self, shouldClose: true, contextInfo: nil) } self.watermarkSaveDelegate = nil } } } override func read(from absoluteURL: URL, ofType typeName: String) throws { do { try super.read(from: absoluteURL, ofType: typeName) updateChangeCount(.changeCleared) } catch let outError { Swift.print(outError) } } override func read(from data: Data, ofType typeName: String) throws { // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure. // Alternatively, you could remove this method and override read(from:ofType:) instead. If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded. // let pdfDocument = CPDFDocument.init(data: data) self.pdfData = data // if pdfDocument == nil { // throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) // } } // MARK: Autosaving override func close() { mainViewController?.cancelMeasureType() if self.isActive { if browser != nil { var activeIndex = 0 let dex = browser.index(of: self) if dex == browser.tabCount() - 1 { activeIndex = Int(browser.tabCount()-2) } else { activeIndex = Int(dex + 1) } let activeContents = browser.tabContents(at: Int32(activeIndex)) activeContents?.addWindowController(browser.windowController) } } super.close() } // MARK: init override init() { super.init() // Add your subclass-specific initialization here. NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFListViewAnnotationsAttributeHasChangeNotification"), object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFViewDocumentChangedNotification"), object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFViewPageChangedNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFListViewDidAddAnnotationNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.CPDFViewPageChanged, object: nil) } override init?(baseTabContents baseContents: CTTabContents?) { super.init(baseTabContents: baseContents) if isHome { homeViewController = KMHomeViewController.init() homeViewController?.myDocument = self self.view = homeViewController?.view } } // MARK: Handling User Actions override var title: String? { get { if isHome { if (self.isNewTab) { return NSLocalizedString("New Tab", comment: "") } else { return NSLocalizedString("Home", comment: "") } } else { return fileURL?.lastPathComponent } } set { super.title = newValue } } func needSaveWatermark() -> Bool { if let need = self.mainViewController?.saveWatermarkFlag { return need } return false } func changeExportOption(_ sender: NSMatrix?) { self.mdFlags?.exportOption = UInt32(sender?.selectedCell()?.tag ?? 0) } // MARK: Private Methods func pdfChangedNotification(_ notification: Notification) -> Void { if !isHome { let mainViewController = mainViewController var document: CPDFDocument! let dic = notification.object as? NSDictionary if dic?["object"] is CPDFAnnotation { guard let annotation = dic?["object"] as? CPDFAnnotation else { return } document = annotation.page.document } else if dic?["object"] is CPDFListView { let pdflistView = notification.object as? CPDFListView document = pdflistView?.document } if mainViewController != nil { if document == mainViewController!.document { updateChangeCount(.changeDone) } } } } override func updateChangeCount(_ change: NSDocument.ChangeType) { let mainWindow = NSApp.mainWindow var currentWindowController: KMBrowserWindowController? if mainWindow != nil { let windowController = mainWindow!.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } if let currentBroser = currentWindowController?.browser { if self.isEqual(to: currentBroser.activeTabContents()) { super.updateChangeCount(change) return } } super.updateChangeCount(.changeCleared) } func uploadToCloud(_ callback: (@escaping(Bool, String)->())) { guard let handdler = self.cloudUploadHanddler else { return } handdler(callback) } func isPDFDocument() -> Bool { return true } func setDataFromTmpData() { guard let _document = self.mainViewController?.document else { return } // self.tryToUnlockDocument(document!) if (_document.permissionsStatus != .owner) { var password: NSString? = nil let fileId = self.fileId(for: _document) if (fileId.isEmpty) { return } self.getPassword(&password, fileId: fileId) if (password != nil) { self.isUnlockFromKeychain = true // document.unlock(withPassword: password! as String) self.mainViewController?.model.password = password as String? } } //如果已存在,开个存在页签 var selectDocument: KMMainDocument? = self if selectDocument != nil { if let browser_ = selectDocument?.browser { let currentIndex = browser_.tabStripModel.index(of: selectDocument) browser_.tabStripModel.selectTabContents(at: Int32(currentIndex), userGesture: true) if browser_.window.isVisible { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { browser_.window.orderFront(nil) } } else if browser_.window.isMiniaturized { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { browser_.window.orderFront(nil) } } } } } func tryToUnlockDocument(_ document: CPDFDocument) { if (document.permissionsStatus != .owner) { var password: NSString? = nil let fileId = self.fileId(for: document) if (fileId.isEmpty) { return } self.getPassword(&password, fileId: fileId) if (password != nil) { self.isUnlockFromKeychain = true document.unlock(withPassword: password! as String) } } } func km_updateChangeCount(_ change: NSDocument.ChangeType) { super.updateChangeCount(change) } func trackEvents() { km_synchronized(self) { for i in self._trackEvents { if let type = KMSubscribeWaterMarkType(rawValue: i) { KMTools.trackEvent(type: type) } } } self.clearTrackEvents() } func recordTrackEvent(type: KMSubscribeWaterMarkType) { if (type == .none) { return } km_synchronized(self) { self._trackEvents.insert(type.rawValue) } } func clearTrackEvents() { km_synchronized(self) { self._trackEvents.removeAll() } } @IBAction func saveArchive(_ sender: Any?) { guard let item = sender as? NSMenuItem else { NSSound.beep() return } guard let fileURL = self.fileURL else { NSSound.beep() return } let check = try?fileURL.checkResourceIsReachable() if check == false || self.isDocumentEdited { let msg = KMLocalizedString("You must save this file first", "Alert text when trying to create archive for unsaved document") let inf = KMLocalizedString("The document has unsaved changes, or has not previously been saved to disk.", "Informative text in alert dialog") Task { _ = await KMAlertTool.runModel(message: msg, informative: inf) } return } // NSString *ext = ([sender tag] | SKArchiveDiskImageMask) ? @"dmg" : @"tgz"; let idx = item.tag let ext = (idx == 1 || idx == 3) ? "dmg" : "tgz" let isEmail = (idx == 2 || idx == 3) if isEmail { // if (([sender tag] | SKArchiveEmailMask)) { let tmpDirURL = FileManager.default.uniqueChewableItemsDirectoryURL() let tmpFileURL = tmpDirURL.appendingPathComponent(fileURL.lastPathComponentReplacingPathExtension(ext)) self.newSaveArchive(to: tmpFileURL, email: true) } else { let sp = NSSavePanel() sp.allowedFileTypes = [ext] sp.canCreateDirectories = true sp.nameFieldStringValue = fileURL.lastPathComponentReplacingPathExtension(ext) sp.beginSheetModal(for: self.windowForSheet!) { result in if result == .OK { self.newSaveArchive(to: sp.url!, email: false) } } } } // func saveArchiveToURL(to fileURL: URL, email: Bool) { // NSTask *task = [[[NSTask alloc] init] autorelease]; // let task = Task() // if fileURL.pathExtension == "dmg" { // [task setLaunchPath:@""]; // task.launchPath = "/usr/bin/hdiutil" // [task setArguments:[NSArray arrayWithObjects:@"create", @"-srcfolder", [[self fileURL] path], @"-format", @"UDZO", @"-volname", [[fileURL lastPathComponent] stringByDeletingPathExtension], [fileURL path], nil]]; // } else { // [task setLaunchPath:@"/usr/bin/tar"]; // [task setArguments:[NSArray arrayWithObjects:@"-czf", [fileURL path], [[self fileURL] lastPathComponent], nil]]; // } // [task setCurrentDirectoryPath:[[[self fileURL] URLByDeletingLastPathComponent] path]]; // [task setStandardOutput:[NSFileHandle fileHandleWithNullDevice]]; // [task setStandardError:[NSFileHandle fileHandleWithNullDevice]]; // // SKAttachmentEmailer *emailer = nil; // if (email) // emailer = [SKAttachmentEmailer attachmentEmailerWithFileURL:fileURL subject:[self displayName] waitingForTask:task]; // // @try { // [task launch]; // } // @catch (id exception) { // [emailer taskFailed]; // } // } @IBAction func readNotes(_ sender: Any?) { KMPrint("readNotes") } @IBAction func convertNotes(_ sender: Any?) { KMPrint("convertNotes") } @IBAction func batchRemovePassWord(_ sender: Any?) { self.mainViewController?.clickChildTool(type: .secure, index: 2) } @IBAction func batchRemovPrivatySecurity(_ sender: Any?) { self.mainViewController?.removeOwnerPassword() } @IBAction func printPDFDocument(_ sender: Any?) { self.mainViewController?.saveDocument() KMPrintWindowController.showNewPrintWindowControll(inputDocument: self.mainViewController?.document, inputPageRange: KMPrintPageRange()) } @IBAction func performFindPanelAction(_ sender: Any?) { self.mainViewController?.toolbarController.showFindBar() } @IBAction func addBookmark(_ sender: Any?) { guard let item = sender as? NSMenuItem else { return } // let bookmarkSheetController = SKBookmarkSheetController(windowNibName: "BookmarkSheet") // NSWindow.currentWindow().beginSheet(bookmarkSheetController.window!) { resoponse in // // } // bookmarkSheetController.textField.stringValue = self.displayName // bookmarkSheetController.beginSheetModal(for: self.windowForSheet) { [unowned self] result in // if (result == NSApplication.ModalResponse.OK.rawValue) { // let label = bookmarkSheetController.textField.stringValue; // let folder: SKBookmark = bookmarkSheetController.selectedFolder ?? SKBookmarkController.shared().bookmarkRoot // var bookmark = SKBookmark() // switch (item.tag) { // case 0: // let mainViewController = self.mainViewController // let page = mainViewController?.listView.currentPage() // bookmark = SKBookmark.bookmark(with: self.fileURL, pageIndex: (page?.pageIndex())!, label: label) as! SKBookmark // case 1: // let setup = self.currentDocumntSetup() // bookmark = SKBookmark.bookmark(withSetup: setup, label: label) as! SKBookmark // // case 2: // let setups = NSApp.orderedDocuments.map { $0.value(forKey: "currentDocumentSetup") } // bookmark = SKBookmark.bookmarkSession(withSetups: setups as [Any], label: label) as! SKBookmark // // default: // break; // } // folder.mutableArrayValue(forKey: "children").add(bookmark) // } // } if item.tag == 3 { KMPrint("Edit Bookmark") bookmarkController = KMBookmarkController.showBookmarkController() } else if item.tag == 2 { KMPrint("session Bookmark") bookmarkSheetController = KMBookmarkSheetController.showBookmarkSheetController(type: .session) } else if item.tag == 0 { KMPrint("add Bookmark") bookmarkSheetController = KMBookmarkSheetController.showBookmarkSheetController(type: .bookmark) } bookmarkSheetController?.stringValue = self.displayName bookmarkSheetController?.cancelAction = { [weak self] controller, type in } // bookmarkSheetController?.doneAction = { [unowned self] controller, type, label in let folder = controller.selectedFolder var bookmark: KMBookmark? switch type { case .bookmark: let mainViewController = mainViewController if let page = mainViewController?.listView.currentPage() { let index: UInt = page.pageIndex() bookmark = KMBookmark.bookmark(url: self.fileURL!, pageIndex: index, label: label) } case .setup: break let setup = currentDocumentSetup bookmark = KMBookmark.bookmark(setup: setup, label: label) case .session: let setups = NSApp.orderedDocuments.compactMap { $0.value(forKey:"currentDocumentSetup") } bookmark = KMSessionBookmark.bookmarkSession(setups: setups as NSArray, label: label) default: break } if let bookmark = bookmark { folder?.children.append(bookmark) } KMBookmarkManager.manager.saveData() } } func currentDocumntSetup() -> [String: Any] { var setup: [String: Any] = [:] let data = SKAlias.init(url: fileURL).data if (data != nil) { setup.updateValue(data as Any, forKey: "_BDAlias") } else { setup.updateValue(fileURL?.path as Any, forKey: "fileName") } return setup; } @IBAction func showWindow(_ sender: Any?) { KMPrint("showWindow") } // MARK: - Private Methods private func _PDFBundleFileWrapper(for name: String) -> FileWrapper { var aName = name if name.isCaseInsensitiveEqual(Self.kBundleDataFilename) { aName = aName + "1" } // var data: Data? let fileWrapper = FileWrapper(directoryWithFileWrappers: [:]) let info = KMInfoWindowController.shared.info(for: self) // NSDictionary *options = [[self mainWindowController] presentationOptions]; // if (options) { // info = [[info mutableCopy] autorelease]; // [(NSMutableDictionary *)info setObject:options forKey:SKPresentationOptionsKey]; // } if let data = self.pdfData { fileWrapper.addRegularFile(withContents: data, preferredFilename: aName + ".pdf") } if let data = self.mainViewController?.document?.string()?.data(using: .utf8) { fileWrapper.addRegularFile(withContents: data, preferredFilename: Self.kBundleDataFilename + ".txt") } if let data = try?PropertyListSerialization.data(fromPropertyList: info, format: .xml, options: PropertyListSerialization.WriteOptions(0)) { fileWrapper.addRegularFile(withContents: data, preferredFilename: Self.kBundleDataFilename + ".plist") } // if ((data = [NSPropertyListSerialization dataWithPropertyList:info format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL])) // [fileWrapper addRegularFileWithContents:data preferredFilename:[BUNDLE_DATA_FILENAME stringByAppendingPathExtension:@"plist"]]; // if ([[self notes] count] > 0) { // if ((data = [self notesData])) // [fileWrapper addRegularFileWithContents:data preferredFilename:[name stringByAppendingPathExtension:@"skim"]]; // if ((data = [[self notesString] dataUsingEncoding:NSUTF8StringEncoding])) // [fileWrapper addRegularFileWithContents:data preferredFilename:[name stringByAppendingPathExtension:@"txt"]]; // if ((data = [self notesRTFData])) // [fileWrapper addRegularFileWithContents:data preferredFilename:[name stringByAppendingPathExtension:@"rtf"]]; // if ((data = [self notesFDFDataForFile:[name stringByAppendingPathExtension:@"pdf"] fileIDStrings:[[self pdfDocument] fileIDStrings]])) // [fileWrapper addRegularFileWithContents:data preferredFilename:[name stringByAppendingPathExtension:@"fdf"]]; // } return fileWrapper } private func _km_write(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, originalContentsURL absoluteOriginalContentsURL: URL?) throws { if typeName == KMPDFBundleDocumentType { // NSFileWrapper *fileWrapper = [self PDFBundleFileWrapperForName:[[absoluteURL lastPathComponent] stringByDeletingPathExtension]]; // if (fileWrapper) // didWrite = [fileWrapper writeToURL:absoluteURL options:0 originalContentsURL:nil error:&error]; // else // error = [NSError writeFileErrorWithLocalizedDescription:NSLocalizedString(@"Unable to write file", @"Error description")]; let fileWrapper = self._PDFBundleFileWrapper(for: url.deletingPathExtension().lastPathComponent) do { try fileWrapper.write(to: url, options: FileWrapper.WritingOptions(rawValue: 0), originalContentsURL: nil) } catch { NSApp.presentError(error) } return } var success = true if !self.isHome { if mainViewController != nil { mainViewController?.savePdfAlertView() if mainViewController?.document != nil { self.mainViewController?.commitEditingIfNeed() // if mainViewController!.document!.isEncrypted { // success = mainViewController!.document!.write(to: url) // } else { if (mainViewController!.needSave) { if let options = self.mainViewController?.secureOptions, !options.isEmpty { self.mainViewController!.document?.setDocumentAttributes(self.mainViewController?.documentAttribute) success = self.mainViewController!.document!.write(to: url, withOptions: options) } else if let flag = self.mainViewController?.removeSecureFlag, flag { success = self.mainViewController!.document!.writeDecrypt(to: url) } else { if(mainViewController?.hasEnterRedact() == true) { success = mainViewController?.redactController.redactPdfView.document?.write(to: url) ?? false } else { success = mainViewController!.document!.write(to: url) } } } else { if(mainViewController?.hasEnterRedact() == true) { success = mainViewController?.redactController.redactPdfView.document?.write(to: url) ?? false } else { success = mainViewController!.document!.write(to: url) } } // } self.mainViewController?.needSave = false self.mainViewController?.clearSecureOptions() self.mainViewController?.clearRemoveSecureFlag() } } } else { success = false } if (success && self._saveAsing) { if let tabView = self.browser?.windowController?.tabStripController?.activeTabView() as? CTTabView { tabView.controller()?.title = url.lastPathComponent } self._saveAsing = false } if success && isNewCreated && NSDocument.SaveOperationType.saveAsOperation == saveOperation { isNewCreated = false } if mainViewController != nil { mainViewController?.savePdfFinishAlertView() } } private func _km_saveForWatermark(openAccessoryView: Bool = true, subscribeDidClick: (()->Void)? = nil, callback:@escaping (_ needSave: Bool, _ param: Any...)->Void) { Task { @MainActor in if await (KMLightMemberManager.manager.canPayFunction() == false) { let _ = KMSubscribeWaterMarkWindowController.show(window: NSApp.mainWindow!, isContinue:false, type: .save) { if let _callback = subscribeDidClick { _callback() } } completion: { isSubscribeSuccess, isWaterMarkExport, isClose in if (isClose) { callback(false, KMResult.cancel, false) return } if (isSubscribeSuccess) { callback(true) return } if (isWaterMarkExport) { guard let _document = self.mainViewController?.document else { callback(false, KMResult.failure) return } // 提交文本编辑的内容 self.mainViewController?.commitEditingIfNeed() DispatchQueue.main.async { NSPanel.savePanel(NSApp.mainWindow!, openAccessoryView, panel:{ panel in if (!self.isNewCreated) { panel.directoryURL = _document.documentURL.deletingLastPathComponent() } panel.nameFieldStringValue = _document.documentURL.lastPathComponent }) { response, url, isOpen in if (response == .cancel) { callback(false, KMResult.cancel, true) return } guard let _url = KMTools.saveWatermarkDocument(document: _document, to: url!, secureOptions: self.mainViewController?.secureOptions, documentAttribute: self.mainViewController?.documentAttribute,removePWD: self.mainViewController!.removeSecureFlag) else { callback(false, KMResult.failure) return } callback(false, KMResult.success) if (isOpen) { NSDocumentController.shared.km_safe_openDocument(withContentsOf: _url, display: true) { _, _, _ in } } else { NSWorkspace.shared.activateFileViewerSelecting([_url]) } } } return } callback(false, KMResult.cancel, false) } return } callback(true) } } private func _km_save(_ sender: Any?) { super.save(sender) } private func _km_save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { self._saveToURL = url super.save(to: url, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) // self.mdFlags?.exportUsingPanel = 0 // self.mdFlags?.exportOption = UInt32(KMExportOption.default.rawValue) } private func _km_saveAs(_ sender: Any?) { super.saveAs(sender) self._saveAsing = true } private func _km_runModalSavePanel(for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { super.runModalSavePanel(for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo) } private func _updateExportAccessoryView() { let typeName = self.fileTypeFromLastRunSavePanel ?? "" let matrix = self.exportAccessoryC?.matrix matrix?.selectCell(withTag: Int(self.mdFlags?.exportOption ?? 0)) if self._canAttachNotesForType(typeName) { matrix?.isHidden = false let ws = NSWorkspace.shared let isLocked = self.mainViewController?.listView.document.isLocked ?? false let allowsPrinting = self.mainViewController?.listView.document.allowsPrinting ?? false if ws.type(typeName, conformsToType: KMPDFDocumentType) && isLocked == false && allowsPrinting { (matrix?.cell(withTag: KMExportOption.withEmbeddedNotes.rawValue))?.isEnabled = true } else { (matrix?.cell(withTag: KMExportOption.withEmbeddedNotes.rawValue))?.isEnabled = false if let data = self.mdFlags?.exportOption, data == KMExportOption.withEmbeddedNotes.rawValue { self.mdFlags?.exportOption = UInt32(KMExportOption.default.rawValue) matrix?.selectCell(withTag: KMExportOption.default.rawValue) } } } else { matrix?.isHidden = true } } private func _canAttachNotesForType(_ typeName: String) -> Bool { let ws = NSWorkspace.shared return ws.type(typeName, conformsToType: KMPDFDocumentType) || ws.type(typeName, conformsToType: KMPostScriptDocumentType) || ws.type(typeName, conformsToType: KMDVIDocumentType) || ws.type(typeName, conformsToType: KMXDVDocumentType) } private func _removeSavePanelOfFormatPopupItems(_ savePanel: NSSavePanel) { var formatPopup: NSPopUpButton? let svs = savePanel.accessoryView?.subviews.first?.subviews ?? [] for sv in svs { if let data = sv as? NSPopUpButton { formatPopup = data break } } if let item = formatPopup?.item(withTitle: "Notes as Text") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "Notes as FDF") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "Notes as RTFD") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: NSLocalizedString("Text", comment: "")) { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: NSLocalizedString("text", comment: "")) { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "PDF Reader Pro Edition Notes") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "XDV") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "DVI") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "Images") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } if let item = formatPopup?.item(withTitle: "Encapsulated PostScript") { formatPopup?.removeItem(at: formatPopup!.index(of: item)) } } // MARK: - Printing override func printOperation(withSettings printSettings: [NSPrintInfo.AttributeKey : Any]) throws -> NSPrintOperation { let printInfo = self.printInfo.copy() as! NSPrintInfo printInfo.dictionary().addEntries(from: printSettings) var printOperation: NSPrintOperation? if self.isHome { return NSPrintOperation() } let documentURL = self.mainViewController?.document?.documentURL if documentURL == nil { return NSPrintOperation() } guard let pdfDoc = PDFDocument(url: documentURL!) else { return NSPrintOperation() } if pdfDoc.responds(to: #selector(PDFDocument.printOperation(for:scalingMode:autoRotate:))) { printOperation = pdfDoc.printOperation(for: printInfo, scalingMode: .pageScaleNone, autoRotate: true) } else if pdfDoc.responds(to: #selector(PDFDocument.getPrintOperation(for:autoRotate:))) { printOperation = pdfDoc.getPrintOperation(for: printInfo, autoRotate: true) } // NSPrintProtected is a private key that disables the items in the PDF popup of the Print panel, and is set for encrypted documents if pdfDoc.isEncrypted { printOperation?.printInfo.dictionary().setValue(false, forKey: "NSPrintProtected") } let printPanel = printOperation?.printPanel printPanel?.options = [.showsCopies, .showsPageRange, .showsPaperSize, .showsOrientation, .showsScaling, .showsPreview] if printOperation == nil { throw NSError.printDocumentError(withLocalizedDescription: "") } return printOperation! } override func runModalPrintOperation(_ printOperation: NSPrintOperation, delegate: Any?, didRun didRunSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { printOperation.run() } } extension PDFDocument { @objc func getPrintOperation(for printInfo: NSPrintInfo, autoRotate: Bool) -> NSPrintOperation { // 在此处实现方法的具体逻辑 return NSPrintOperation() } } extension KMMainDocument { override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { if (menuItem.action == #selector(save(_ :))) { if (self.isHome) { return false } if (self.isDocumentEdited) { return self.isDocumentEdited } guard let mainVC = self.mainViewController else { return false } return mainVC.isPDFDocumentEdited || mainVC.needSave } else if (menuItem.action == #selector(saveAs(_ :))) { return !self.isHome } else if menuItem.action == #selector(batchRemovPrivatySecurity) { if self.isHome { return false } guard let doc = self.mainViewController?.listView?.document else { return false } let allowsPrinting = doc.allowsPrinting let allowsCopying = doc.allowsCopying if allowsCopying && allowsPrinting { return false } return true } else if menuItem.action == #selector(saveArchive) { return !self.isHome } else if (menuItem.action == #selector(saveTo(_ :))) { return !self.isHome } else if (menuItem.action == #selector(batchRemovePassWord)) { return !self.isHome } else if (menuItem.action == #selector(addBookmark)) { if menuItem.tag == 3 { return true }else { return !self.isHome } } return super.validateMenuItem(menuItem) } } extension NSDocument { @objc class func isDamage(url: URL) -> Bool { // 文件路径是否存在 if (FileManager.default.fileExists(atPath: url.path) == false) { return true } /// PDF 格式文件 if (url.pathExtension.lowercased() == "pdf") { let document = PDFDocument(url: url) if (document == nil) { return true } if (document!.isLocked) { // 加锁文件不在这里判断 return false } if (document!.pageCount <= 0) { return true } return false } // 支持的图片格式 let imageExts = ["jpg","cur","bmp","jpeg","gif","png","tiff","tif","ico","icns","tga","psd","eps","hdr","jp2","jpc","pict","sgi","heic"] let isImage = imageExts.contains(url.pathExtension.lowercased()) if (isImage == false) { // 其他格式目前返回没损坏,后续再补充(如果有需求) return false } // 图片格式 let image = NSImage(contentsOf: url) let data = image?.tiffRepresentation if (data == nil) { return true } let imageRep = NSBitmapImageRep(data: data!) imageRep!.size = image!.size var imageData: NSData? if (url.pathExtension.lowercased() == "png") { imageData = imageRep?.representation(using: .png, properties: [:]) as NSData? } else { imageData = imageRep?.representation(using: .jpeg, properties: [:]) as NSData? } if (imageData == nil) { return true } let path = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!) if (FileManager.default.fileExists(atPath: path!) == false) { try?FileManager.default.createDirectory(atPath: path!, withIntermediateDirectories: false) } var tagString: String = "" let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyMMddHHmmss" tagString.append(dateFormatter.string(from: Date())) tagString = tagString.appendingFormat("%04d", arc4random()%10000) let filePath = path?.appending("/\(tagString).png") if (imageData!.write(toFile: filePath!, atomically: true) == false) { return true } // 删除临时图片 try?FileManager.default.removeItem(atPath: filePath!) return false } @objc class func isDamage(url: URL, needAlertIfDamage need: Bool) -> Bool { let result = self.isDamage(url: url) if (result == false) { return false } if (need == false) { return true } let alert = NSAlert() alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return true } } // MARK: - // MARK: 保存密码 extension NSDocument { func savePasswordInKeychain(_ password: String, _ document: CPDFDocument) { if (document.isLocked || password.isEmpty) { return } let fileId = self.fileId(for: document) if (fileId.isEmpty) { return } // let status: SKPasswordStatus = let label = "PDF Reader Pro: \(self.displayName!)" SKKeychain.setPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId, label: label, comment: self.fileURL?.path) } func getPassword(_ password: AutoreleasingUnsafeMutablePointer, fileId: String) { let status = SKKeychain.getPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId) // if (status == .found) { // } } fileprivate func fileId(for document: CPDFDocument) -> String { return "\(document.documentURL.path.hash)" } private func passwordServiceName() -> String { return "PDF Reader Pro password" } } extension KMMainDocument: SKPDFSynchronizerDelegate { func synchronizer(_ synchronizer: SKPDFSynchronizer!, foundLine line: Int, inFile file: String!) { if FileManager.default.fileExists(atPath: file) { let defaults = UserDefaults.standard var editorPreset = defaults.string(forKey: SKTeXEditorPresetKey) ?? "" var editorCmd: String? var editorArgs: String? var cmdString: String? if !KMSyncPreferences.getTeXEditorCommand(command: &editorCmd, arguments: &editorArgs, forPreset: editorPreset) { editorCmd = defaults.string(forKey: SKTeXEditorCommandKey) editorArgs = defaults.string(forKey: SKTeXEditorArgumentsKey) } if var cmdString = editorArgs { if !editorCmd!.hasPrefix("/") { var searchPaths = ["/usr/bin", "/usr/local/bin"] var toolPath: String? let fm = FileManager.default if !(editorPreset.isEmpty) { if let path = NSWorkspace.shared.fullPath(forApplication: editorPreset) { if let appBundle = Bundle(path: path) { if let contentsPath = appBundle.path(forResource: "Contents", ofType: nil) { searchPaths.insert(contentsPath, at: 0) } if editorPreset != "BBEdit", let execPath = appBundle.executablePath { searchPaths.insert(execPath, at: 0) } if let resourcePath = appBundle.resourcePath { searchPaths.insert(resourcePath, at: 0) } if let sharedSupportPath = appBundle.sharedSupportPath { searchPaths.insert(sharedSupportPath, at: 0) } } } } else { let appSupportDirs = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) let appSupportPaths = appSupportDirs.map { $0.path } searchPaths.append(contentsOf: appSupportPaths) } for path in searchPaths { toolPath = (path as NSString).appendingPathComponent(editorCmd!) if fm.isExecutableFile(atPath: toolPath!) { editorCmd = toolPath break } toolPath = ((path as NSString).appendingPathComponent("bin") as NSString).appendingPathComponent(editorCmd!) if fm.isExecutableFile(atPath: toolPath!) { editorCmd = toolPath break } } } cmdString = cmdString.replacingOccurrences(of: "%line", with: "\(line + 1)") cmdString = cmdString.replacingOccurrences(of: "%file", with: file) cmdString = cmdString.replacingOccurrences(of: "%output", with: fileURL?.path ?? "") cmdString.insert(contentsOf: "\" ", at: cmdString.startIndex) cmdString.insert(contentsOf: editorCmd!, at: cmdString.startIndex) cmdString.insert("\"", at: cmdString.startIndex) let ws = NSWorkspace.shared if let theUTI = try? ws.type(ofFile: editorCmd!) { if ws.type(theUTI, conformsToType: "com.apple.applescript.script") || ws.type(theUTI, conformsToType: "com.apple.applescript.text") { cmdString.insert(contentsOf: "/usr/bin/osascript ", at: cmdString.startIndex) } } let task = Process() task.launchPath = "/bin/sh" task.currentDirectoryPath = (file as NSString).deletingLastPathComponent task.arguments = ["-c", cmdString] task.standardOutput = FileHandle.nullDevice task.standardError = FileHandle.nullDevice do { try task.run() } catch let error { Swift.print("command failed: \(cmdString ?? ""): \(error)") } } } } func synchronizer(_ synchronizer: SKPDFSynchronizer!, foundLocation point: NSPoint, atPageIndex pageIndex: UInt, options: Int) { guard let pdfDoc = self.mainViewController?.document else { return } if pageIndex < pdfDoc.pageCount { if let page = pdfDoc.page(at: pageIndex) { var adjustedPoint = point if options & SKPDFSynchronizerFlippedMask != 0 { let mediaBox = page.bounds(for: .mediaBox) adjustedPoint.y = NSMaxY(mediaBox) - adjustedPoint.y } self.mainViewController?.listView.displayLine(at: adjustedPoint, inPageAtIndex: Int(pageIndex), showReadingBar: options & SKPDFSynchronizerShowReadingBarMask != 0) } } } } extension KMMainDocument { }