// // KMBaseViewController.swift // PDF Master // // Created by tangchao on 2023/5/5. // import Cocoa // 基类 [抽象类] class KMBaseViewController: NSViewController { // 是否需要菜单 var needMenu = false { didSet { if (self.needMenu) { self.addMenu(to: self.view) } else { self.removeMenu(to: self.view) } } } // 是否能使用高级功能 var canUseAdvanced = false // 是否能使用付费功能 var canPayFunction = false deinit { Swift.debugPrint(self.className + " 已释放") self.removeNotifations() } override func viewDidLoad() { super.viewDidLoad() if (self.needMenu) { self.addMenu(to: self.view) } else { self.removeMenu(to: self.view) } self.addNotifations() self._reloadAdvancedData() } // Advanced @objc fileprivate func _advancedDidChange() { self._reloadAdvancedData() } fileprivate func _reloadAdvancedData() { Task { @MainActor in if await (KMLightMemberManager.manager.canUseAdvanced() == false) { self.canUseAdvanced = false } else { self.canUseAdvanced = true } if await (KMLightMemberManager.manager.canPayFunction() == false) { self.canPayFunction = false } else { self.canPayFunction = true } } } // Noti func addNotifations() { NotificationCenter.default.addObserver(self, selector: #selector(_advancedDidChange), name: NSNotification.Name(rawValue: ADVANCED_NOTIFICATION), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_advancedDidChange), name: NSNotification.Name(rawValue: USER_INFO_CHANGE_NOTIFICATION), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_advancedDidChange), name: NSNotification.Name(rawValue: USER_LOGINSTATE_NOTIFICATION), object: nil) } func removeNotifations() { NotificationCenter.default.removeObserver(self) } func km_add_office_multi(fileUrls: [URL], completionBlock:@escaping ([String])->Void) -> Void { var fileUrlStrings: [String] = [] let dispatchGroup = Dispatch.DispatchGroup() for (index, fileUrl) in fileUrls.enumerated() { let filePath = fileUrl.path let folderPath = "convertToPDF_\(index).pdf" let savePath = folderPath.kUrlToPDFFolderPath() if (savePath == nil) { continue } dispatchGroup.enter() KMConvertPDFManagerOC.convertFile(filePath, savePath: savePath!) { success, errorDic in if errorDic != nil || !success || !FileManager.default.fileExists(atPath: savePath!) { dispatchGroup.leave() if FileManager.default.fileExists(atPath: savePath!) { try?FileManager.default.removeItem(atPath: savePath!) } let alert = NSAlert.init() alert.alertStyle = .critical var infoString = "" if errorDic != nil { for key in (errorDic! as Dictionary).keys { infoString = infoString.appendingFormat("%@\n", errorDic![key] as! CVarArg) } } alert.informativeText = NSLocalizedString("Please install Microsoft Office to create PDFs from Office files", comment: "") alert.messageText = NSLocalizedString("Failed to Create PDF", comment: "") alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) alert.runModal() return } if !savePath!.isPDFValid() { dispatchGroup.leave() 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 } fileUrlStrings.append(savePath!) dispatchGroup.leave() } } dispatchGroup.notify(queue: DispatchQueue.main) { completionBlock(fileUrlStrings) } } // MARK: - Open Password Files private var lockedFiles: [URL] = [] func km_open_pdf_multi(type: KMPasswordInputWindowType = .open, progressBlock: ((_ index: Int, _ params: Any...)->Void)? = nil, completionBlock:@escaping ([CPDFDocument])->Void) { NSPanel.km_open_pdf_multi_success(self.view.window!, panel: nil) { urls in self.km_add_pdf_multi(fileUrls: urls, type: type, progressBlock: progressBlock, completionBlock: completionBlock) } } func km_open_file_multi(type: KMPasswordInputWindowType = .open, progressBlock: ((_ index: Int, _ params: Any...)->Void)? = nil, completionBlock:@escaping ([CPDFDocument])->Void) { NSPanel.km_open_multi_success(self.view.window!) { panel in var array: [String] = [] for fileType in KMConvertPDFManagerOC.supportFileType() { if let string = fileType as? String { array.append(string) } } panel.allowedFileTypes = KMTools.pdfExtensions + array } completion: { urls in self.km_add_file_multi(fileUrls: urls, type: type, progressBlock: progressBlock, completionBlock: completionBlock) } } func km_add_pdf_multi(fileUrlStrings: [String] ,type: KMPasswordInputWindowType = .open, progressBlock: ((_ index: Int, _ params: Any...)->Void)? = nil, completionBlock:@escaping ([CPDFDocument])->Void) { var urls: [URL] = [] for string in fileUrlStrings { urls.append(URL(fileURLWithPath: string)) } self.km_add_pdf_multi(fileUrls: urls, type: type, progressBlock: progressBlock, completionBlock: completionBlock) } func km_add_pdf_multi(fileUrls: [URL] ,type: KMPasswordInputWindowType = .open, progressBlock: ((_ index: Int, _ params: Any...)->Void)? = nil, completionBlock:@escaping ([CPDFDocument])->Void) { var results: [CPDFDocument] = [] self.lockedFiles.removeAll() var index = 0 for url in fileUrls { let document = CPDFDocument(url: url) if (document!.isLocked) { self.lockedFiles.append(url) continue } if let _document = document { results.append(_document) } index += 1 if let _callback = progressBlock { _callback(index, ((document != nil) ? document : CPDFDocument()) as Any, url) } } if (self.lockedFiles.count == 0) { completionBlock(results) return } // if let _callback = progressBlock { // _callback(0, results) // } self._openPasswordWindow_loop(fileUrl: self.lockedFiles.first!, type: type) { params in index += 1 if (params.count <= 2) { // 参数错误 if let _callback = progressBlock { // 回调进度 _callback(index) } return } let fileUrl = params[0] as! URL let result = params[1] as! KMPasswordInputWindowResult let password = params[2] as? String if (result == .cancel) { if let _callback = progressBlock { // 回调进度 _callback(index, CPDFDocument() as Any, fileUrl, result) } return } let document = CPDFDocument(url: fileUrl) if let _password = password { // 将文档进行解密 document?.unlock(withPassword: _password) } if let _callback = progressBlock { // 回调进度 _callback(index, document as Any, fileUrl, result, password as Any) } // 将文档加入返回数据 if let _document = document { results.append(_document) } } completionBlock: { completionBlock(results) } } func km_add_file_multi(fileUrls: [URL] ,type: KMPasswordInputWindowType = .open, progressBlock: ((_ index: Int, _ params: Any...)->Void)? = nil, completionBlock:@escaping ([CPDFDocument])->Void) { var pdfUrls: [URL] = [] var imageUrls: [URL] = [] var officeUrls: [URL] = [] for url in fileUrls { let type = url.pathExtension.lowercased() if (KMTools.isPDFType(type)) { pdfUrls.append(url) } if (KMTools.isImageType(type)) { imageUrls.append(url) } if (KMTools.isOfficeType(type)) { officeUrls.append(url) } } if (officeUrls.count == 0) { self.km_add_pdf_multi(fileUrls: pdfUrls, type: type, progressBlock: progressBlock) { documents in var index = documents.count var _documents: [CPDFDocument] = [] for imageUrl in imageUrls { index += 1 let document = CPDFDocument() let image = NSImage(contentsOfFile: imageUrl.path) // document?.insertPage(image!.size, withImage: imageUrl.path, at: 0) document?.km_insertPage(image!.size, withImage: imageUrl.path, at: 0) _documents.append(document!) if let _callback = progressBlock { // 回调进度 _callback(index, document as Any, imageUrl) } } completionBlock(documents + _documents) } return } self.km_add_office_multi(fileUrls: officeUrls) { [unowned self] fileUrlStrings in var officeDocuments: [CPDFDocument] = [] var index = 0 for fileUrlString in fileUrlStrings { index += 1 let document = CPDFDocument(url: URL(fileURLWithPath: fileUrlString)) officeDocuments.append(document!) if let _callback = progressBlock { // 回调进度 _callback(index, document as Any, URL(fileURLWithPath: fileUrlString)) } } self.km_add_pdf_multi(fileUrls: pdfUrls) { documents in var index = documents.count + officeDocuments.count var _documents: [CPDFDocument] = [] for imageUrl in imageUrls { index += 1 let document = CPDFDocument() let image = NSImage(contentsOfFile: imageUrl.path) // document?.insertPage(image!.size, withImage: imageUrl.path, at: 0) document?.km_insertPage(image!.size, withImage: imageUrl.path, at: 0) _documents.append(document!) if let _callback = progressBlock { // 回调进度 _callback(index, document as Any, imageUrl) } } completionBlock(officeDocuments + documents + _documents) } } } // MARK: - ProgressBlock Params Fetch func fetchProgressBlockParamsForDocument(params: Any...) -> CPDFDocument? { return params.first as? CPDFDocument } func fetchProgressBlockParamsForFileUrl(params: Any...) -> URL? { if (params.count < 2) { return nil } return params[1] as? URL } func fetchProgressBlockParamsForResult(params: Any...) -> KMPasswordInputWindowResult? { if (params.count <= 2) { return nil } return params[2] as? KMPasswordInputWindowResult } func fetchProgressBlockParamsForPassword(params: Any...) -> String? { if (params.count <= 2) { return nil } return params.last as? String } func fetchProgressBlockParamsIsPasswordFile(params: Any...) -> Bool { if (params.count <= 2) { return false } return true } // MARK: - Open Password Window // 留意: // -会直接弹密码弹窗,不会判断文档是否加密 // -在使用前最好判断下文件是否已加密 func openPasswordWindow(fileUrlString: String, type: KMPasswordInputWindowType = .open, completionBlock:@escaping (KMPasswordInputWindowResult, String?)->Void) { self.openPasswordWindow(fileUrl: URL(fileURLWithPath: fileUrlString), type: type, completionBlock: completionBlock) } func openPasswordWindow(fileUrl: URL, type: KMPasswordInputWindowType = .open, completionBlock:@escaping (KMPasswordInputWindowResult, String?)->Void) { KMPasswordInputWindow.openWindow(window: self.view.window!, type: type, url: fileUrl, callback: completionBlock) } func openPasswordWindow_success(fileUrlString: String, type: KMPasswordInputWindowType = .open, completionBlock:@escaping (String)->Void) { self.openPasswordWindow_success(fileUrl: URL(fileURLWithPath: fileUrlString), type: type, completionBlock: completionBlock) } func openPasswordWindow_success(fileUrl: URL, type: KMPasswordInputWindowType = .open, completionBlock:@escaping (String)->Void) { KMPasswordInputWindow.success_openWindow(window: self.view.window!, url: fileUrl, callback: completionBlock) } fileprivate func _openPasswordWindow_loop(fileUrl: URL, type: KMPasswordInputWindowType, progressBlock: ((_ params: Any...)->Void)?, completionBlock:@escaping ()->Void) { KMPasswordInputWindow.openWindow(window: self.view.window!, type: type, url: fileUrl) { [weak self] result, password in // 将结果返回 if let _callback = progressBlock { _callback(fileUrl, result, password as Any) } // 进行下一个 self?.lockedFiles.removeFirst() if let _fileUrl = self?.lockedFiles.first { self?._openPasswordWindow_loop(fileUrl: _fileUrl, type: type, progressBlock: progressBlock, completionBlock: completionBlock) } else { completionBlock() } } } // MARK: - Progress Window var progressC: SKProgressController? func showProgressWindow(message: String = "") { if (self.progressC != nil) { self.hiddenProgressWindow() } let progressC = SKProgressController() progressC.showClose = false progressC.message = message progressC.window?.backgroundColor = NSColor.km_init(hex: "#36383B") progressC.window?.contentView?.wantsLayer = true progressC.window?.contentView?.layer?.backgroundColor = NSColor.km_init(hex: "#36383B").cgColor progressC.progressField.textColor = NSColor.white self.progressC = progressC self.view.window?.beginSheet(progressC.window!) } func hiddenProgressWindow() { if let _progressC = self.progressC { if let _window = _progressC.window { self.view.window?.endSheet(_window) } self.progressC = nil } } // MARK: - Menu Add & Remove public func addMenu(to view: NSView?) { if let menuView = view { self.addMenu(to: menuView) return } self.addMenu(to: self.view) } public func removeMenu(to view: NSView?) { if let menuView = view { self.removeMenu(to: menuView) return } self.removeMenu(to: self.view) } private func addMenu(to view: NSView) { // 先移除 self.removeMenu(to: view) let menu = NSMenu() menu.delegate = self view.menu = menu } private func removeMenu(to view: NSView) { view.menu?.delegate = nil view.menu = nil } // MARK: - Document isDocumentEdited public func setDocumentEditedState(window: NSWindow? = nil) { var _win = window if (_win == nil) { _win = self.view.window } guard let _window = _win else { return } guard let _document = NSDocumentController.shared.document(for: _window) else { return } self.setDocumentEditedState(document: _document) } public func setDocumentEditedState(url: URL? = nil) { if let _url = url { KMTools.setDocumentEditedState(url: _url) } else { self.setDocumentEditedState(window: self.view.window) } } public func setDocumentEditedState(document: NSDocument) { km_synchronized(document) { if let _document = document as? KMMainDocument { _document.km_updateChangeCount(.changeDone) } else { document.updateChangeCount(.changeDone) } } } public func clearDocumentEditedState(window: NSWindow? = nil) { var _win = window if (_win == nil) { _win = self.view.window } guard let _window = _win else { return } guard let _document = NSDocumentController.shared.document(for: _window) else { return } self.clearDocumentEditedState(document: _document) } public func clearDocumentEditedState(url: URL? = nil) { if let _url = url { KMTools.clearDocumentEditedState(url: _url) } else { self.clearDocumentEditedState(window: self.view.window) } } public func clearDocumentEditedState(document: NSDocument) { km_synchronized(document) { if let _document = document as? KMMainDocument { _document.km_updateChangeCount(.changeCleared) } else { document.updateChangeCount(.changeCleared) } } } } extension KMBaseViewController: NSMenuDelegate, NSMenuItemValidation { func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { return true } func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() } }