// // KMPasswordInputWindow.swift // PDF Master // // Created by tangchao on 2022/11/29. // import Cocoa import PDFKit @objc enum KMPasswordInputWindowType: Int { case open = 1 case owner = 2 } @objc enum KMPasswordInputWindowResult: Int { case cancel = 1 case success = 2 } typealias KMPasswordInputWindowItemClick = (Int, String) -> () private var passwordInputWindow: KMPasswordInputWindow? private var passwordInputWindow_private: KMPasswordInputWindow? @objcMembers class KMPasswordInputWindow: NSWindow { @IBOutlet weak var titleLabel: NSTextField! @IBOutlet weak var despLabel: NSTextField! @IBOutlet weak var secureTextFiled: KMSecureTextFiled! @IBOutlet weak var iconImageView: NSImageView! @IBOutlet weak var passwordErrorLabel: NSTextField! @IBOutlet weak var cancelButton: NSButton! @IBOutlet weak var confirmButton: NSButton! var confirmButtonVC: KMDesignButton? static var myDocumentURL: URL! var documentURL: URL { get { return KMPasswordInputWindow.myDocumentURL } set { KMPasswordInputWindow.myDocumentURL = newValue } } static var myItemClick: KMPasswordInputWindowItemClick! var itemClick: KMPasswordInputWindowItemClick { get { return KMPasswordInputWindow.myItemClick } set { KMPasswordInputWindow.myItemClick = newValue } } static var myType: KMPasswordInputWindowType = .open var type: KMPasswordInputWindowType { get { return KMPasswordInputWindow.myType } set { KMPasswordInputWindow.myType = newValue let titleLabel = self.myTitleLabel if (titleLabel != nil) { // if (newValue == .open) { // titleLabel!.stringValue = NSLocalizedString("Open Password", comment: "") // } else { titleLabel!.stringValue = NSLocalizedString("Permission Password", comment: "") // } } var despLabel = self.myDespLabel if (despLabel != nil) { var fileName = NSLocalizedString("", comment: "") if (self.documentURL != nil) { fileName.append("\(self.documentURL.lastPathComponent)") } despLabel!.maximumNumberOfLines = 3 despLabel?.lineBreakMode = .byTruncatingTail despLabel?.cell?.truncatesLastVisibleLine = true let ps = NSMutableParagraphStyle() ps.lineSpacing = 5 // ps.lineBreakMode = .byTruncatingTail // if (newValue == .open) { // despLabel!.stringValue = "\"\(fileName)\"\(NSLocalizedString("is protected, please enter a Document Open Password.", comment: ""))" // despLabel!.attributedStringValue = NSAttributedString(string: despLabel!.stringValue, attributes: [.foregroundColor : NSColor.titleColor(), .font : NSFont.SFProTextRegular(14), .paragraphStyle : ps]) // } else { despLabel!.stringValue = "\"\(fileName)\"\(NSLocalizedString("is protected, please enter the password to unlock it.", comment: ""))" despLabel!.attributedStringValue = NSAttributedString(string: despLabel!.stringValue, attributes: [.foregroundColor : NSColor.titleColor(), .font : NSFont.SFProTextRegular(14), .paragraphStyle : ps]) // } } } } static var canEncrpty = false static var permissionsStatus: CPDFDocumentPermissions = .none deinit { KMPrint("KMPasswordInputWindow 已释放了") } override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) } override func awakeFromNib() { super.awakeFromNib() if (titleLabel != nil) { passwordInputWindow_private = self titleLabel.stringValue = NSLocalizedString("Permission Password", comment: "") self.titleLabel.textColor = NSColor.titleColor() titleLabel.font = NSFont.SFProTextRegular(16) self.titleLabel.window?.appearance = NSAppearance(named: .aqua) } if despLabel != nil { despLabel.stringValue = NSLocalizedString("is protected, please enter the password to unlock it.", comment: "") self.despLabel.textColor = NSColor.titleColor() despLabel.font = NSFont.SFProTextRegular(14) despLabel.isSelectable = false let ps = NSMutableParagraphStyle() ps.lineSpacing = 5 despLabel.maximumNumberOfLines = 2 despLabel.lineBreakMode = .byTruncatingTail ps.lineBreakMode = .byTruncatingTail despLabel.attributedStringValue = NSAttributedString(string: despLabel.stringValue, attributes: [.foregroundColor : NSColor.titleColor(), .font : NSFont.SFProTextRegular(14), .paragraphStyle : ps]) } if (iconImageView != nil) { self.iconImageView.image = NSImage(named: "KMImageNameSecureIcon") } if secureTextFiled != nil { secureTextFiled.backgroundView.wantsLayer = true secureTextFiled.backgroundView.layer?.borderWidth = 1 secureTextFiled.backgroundView.layer?.borderColor = NSColor.buttonBorderColor().cgColor secureTextFiled.backgroundView.layer?.cornerRadius = 4 secureTextFiled.placeholderString = NSLocalizedString("Password", comment: "") let rightView = NSView() rightView.frame = NSMakeRect(0, 0, 40, 32); secureTextFiled.rightView = rightView let clearButton = NSButton() rightView.addSubview(clearButton) clearButton.frame = NSMakeRect(10, 6, 20, 20) clearButton.wantsLayer = true clearButton.image = NSImage(named: "KMImageNameSecureClearIcon") clearButton.isBordered = false clearButton.target = self clearButton.action = #selector(clearButtonAction) rightView.isHidden = true secureTextFiled.becomeFirstResponderHandler = { [unowned self] securetextFiled in let mySecureTextField: KMSecureTextFiled = securetextFiled as! KMSecureTextFiled mySecureTextField.backgroundView.wantsLayer = true mySecureTextField.backgroundView.layer?.borderColor = NSColor(hex: "#1770F4").cgColor if mySecureTextField.password().isEmpty { self.secureTextFiled.rightView?.isHidden = true } else { self.secureTextFiled.rightView?.isHidden = false } if self.passwordErrorLabel != nil { self.passwordErrorLabel.isHidden = true } } secureTextFiled.valueDidChange = { [unowned self] string in self.secureTextFiled.backgroundView.layer?.borderColor = NSColor(hex: "#1770F4").cgColor if self.passwordErrorLabel != nil { self.passwordErrorLabel.isHidden = true } if string.isEmpty { self.secureTextFiled.rightView?.isHidden = true self.dealConfirmButtonEnabledState(enabled: false) } else { self.secureTextFiled.rightView?.isHidden = false self.dealConfirmButtonEnabledState(enabled: true) } } secureTextFiled.enterAction = { [unowned self] in self.confirmButtonAction() } } if passwordErrorLabel != nil { passwordErrorLabel.stringValue = NSLocalizedString("Password error", comment: "") passwordErrorLabel.font = NSFont.systemFont(ofSize: 12) passwordErrorLabel.wantsLayer = true passwordErrorLabel.textColor = NSColor(hex: "#F3465B") passwordErrorLabel.isHidden = true } if cancelButton != nil { for button in [cancelButton, confirmButton] { button?.wantsLayer = true button?.layer?.cornerRadius = 4 button!.target = self if ((button?.isEqual(to: cancelButton))!) { button?.layer?.borderWidth = 1 button?.layer?.borderColor = NSColor.buttonBorderColor().cgColor // button?.title = NSLocalizedString("Cancel", comment: "") button?.title = "" // button?.setTitleColor(NSColor.buttonTitleColor()) // button?.font = NSFont.SFProTextRegular(14) // button?.action = #selector(cancelButtonAction) } else { // button?.title = NSLocalizedString("Open", comment: "") // button?.attributedTitle = NSMutableAttributedString(string: button!.title, attributes: [.foregroundColor : NSColor.white]) // button?.font = NSFont.SFProTextRegular(14) // button?.action = #selector(confirmButtonAction) button?.title = "" } } let cancelButtonVC = KMDesignButton(withType: .Text) self.cancelButton.addSubview(cancelButtonVC.view) cancelButtonVC.view.frame = self.cancelButton.bounds cancelButtonVC.view.autoresizingMask = [.width, .height] cancelButtonVC.stringValue = NSLocalizedString("Cancel", comment: "") cancelButtonVC.button(type: .Sec_Icon, size: .m) cancelButtonVC.target = self cancelButtonVC.action = #selector(cancelButtonAction) cancelButtonVC.button.keyEquivalent = KMKeyEquivalent.esc.string() let confirmButtonVC = KMDesignButton(withType: .Text) self.confirmButton.addSubview(confirmButtonVC.view) confirmButtonVC.view.frame = self.confirmButton.bounds confirmButtonVC.view.autoresizingMask = [.width, .height] confirmButtonVC.stringValue = NSLocalizedString("Open", comment: "") confirmButtonVC.button(type: .Cta, size: .m) confirmButtonVC.target = self confirmButtonVC.action = #selector(confirmButtonAction) self.confirmButtonVC = confirmButtonVC self.confirmButtonVC?.button.keyEquivalent = KMKeyEquivalent.enter dealConfirmButtonEnabledState(enabled: false) } } func window() -> Self? { KMPasswordInputWindow.canEncrpty = false KMPasswordInputWindow.permissionsStatus = .none var topLevelArray: NSArray? = nil Bundle.main.loadNibNamed(NSNib.Name("KMPasswordInputWindow"), owner: self, topLevelObjects: &topLevelArray) guard let results = topLevelArray else { return nil } var passwordInputWindow: KMPasswordInputWindow! for object in results { let window: NSObject = object as! NSObject if window.isKind(of: KMPasswordInputWindow.self) { passwordInputWindow = window as! KMPasswordInputWindow? } } guard let myWindow = passwordInputWindow else { return nil } return myWindow as? Self } @objc func cancelButtonAction() { guard let callback = KMPasswordInputWindow.myItemClick else { return } callback(1, "") } @objc func confirmButtonAction() { if (!KMPasswordInputWindow.canEncrpty) { return } if (KMPasswordInputWindow.myDocumentURL == nil) { return } if (KMPasswordInputWindow.myType == .open) { let document: CPDFDocument = CPDFDocument(url: KMPasswordInputWindow.myDocumentURL) if document.permissionsStatus == .none { let reuslt = document.unlock(withPassword: secureTextFiled.password()) /// CPDFDocumentPermissionsNone 解锁失败 /// CPDFDocumentPermissionsUser 输入的开启密码 /// CPDFDocumentPermissionsOwner 输入的权限密码 KMPasswordInputWindow.permissionsStatus = document.permissionsStatus if document.permissionsStatus != CPDFDocumentPermissions.none { /// 密码正确 guard let callback = KMPasswordInputWindow.myItemClick else { return } callback(2, secureTextFiled.password()) } else { /// 密码错误 if passwordErrorLabel != nil { passwordErrorLabel.isHidden = false } if secureTextFiled != nil { secureTextFiled.backgroundView.layer?.borderColor = NSColor(hex: "#F3465B").cgColor } } } return } /// 权限密码类型 let document: CPDFDocument = CPDFDocument(url: KMPasswordInputWindow.myDocumentURL) if (document.isLocked) { if document.permissionsStatus == CPDFDocumentPermissions.none { let reuslt = document.unlock(withPassword: secureTextFiled.password()) KMPasswordInputWindow.permissionsStatus = document.permissionsStatus if document.permissionsStatus == .owner { /// 密码正确 guard let callback = KMPasswordInputWindow.myItemClick else { return } callback(2, secureTextFiled.password()) } else { /// 密码错误 if passwordErrorLabel != nil { passwordErrorLabel.isHidden = false } if secureTextFiled != nil { secureTextFiled.backgroundView.layer?.borderColor = NSColor(hex: "#F3465B").cgColor } } } } else { if document.permissionsStatus == CPDFDocumentPermissions.user { document.unlock(withPassword: secureTextFiled.password()) KMPasswordInputWindow.permissionsStatus = document.permissionsStatus if document.permissionsStatus == .owner { /// 密码正确 guard let callback = KMPasswordInputWindow.myItemClick else { return } callback(2, secureTextFiled.password()) } else { /// 密码错误 if passwordErrorLabel != nil { passwordErrorLabel.isHidden = false } if secureTextFiled != nil { secureTextFiled.backgroundView.layer?.borderColor = NSColor(hex: "#F3465B").cgColor } } } } } @objc func clearButtonAction() { if (self.secureTextFiled != nil) { self.secureTextFiled.clear() } } func dealConfirmButtonEnabledState(enabled: Bool) { if confirmButton != nil { KMPasswordInputWindow.canEncrpty = enabled // confirmButton.wantsLayer = true // confirmButton.layer?.backgroundColor = NSColor.buttonFunctionBackgroundColor(enabled: enabled).cgColor // confirmButton?.title = NSLocalizedString("Open", comment: "") // var color = NSColor.buttonTitleColor(enabled: enabled) // if (enabled) { // color = NSColor(hex: "#FFFFFF") // } // confirmButton?.attributedTitle = NSMutableAttributedString(string: confirmButton!.title, attributes: [.foregroundColor : color]) if (self.confirmButtonVC != nil) { self.confirmButtonVC?.enabled = enabled } } } override func mouseUp(with event: NSEvent) { super.mouseUp(with: event) self.makeFirstResponder(nil) // if self.secureTextFiled != nil { // secureTextFiled.backgroundView.layer?.borderColor = NSColor.black.cgColor // } var contentBox: NSBox = NSBox() for i in 0 ... (contentView?.subviews.count)!-1 { if i == 1 { contentBox = contentView?.subviews[i] as! NSBox } } var secureTextField: KMSecureTextFiled! for i in 0 ... (contentBox.contentView?.subviews.count)!-1 { let view: NSView = (contentBox.contentView?.subviews[i])! if view.isKind(of: KMSecureTextFiled.self) { secureTextField = view as? KMSecureTextFiled } } if secureTextField != nil { secureTextField.backgroundView.layer?.borderColor = NSColor.buttonBorderColor().cgColor } if (passwordErrorLabel != nil) { passwordErrorLabel.isHidden = true } } } extension KMPasswordInputWindow { var myTitleLabel: NSTextField? { get { let topBox = self.contentView?.subviews.first if (topBox == nil || !(topBox is NSBox)) { return nil } var label: NSTextField? for i in 0 ..< (((topBox as! NSBox).contentView?.subviews.count)!) { let view: NSView = ((topBox as! NSBox).contentView?.subviews[i])! if view.isKind(of: NSTextField.self) { label = view as? NSTextField break } } return label } } var myDespLabel: NSTextField? { get { var contentBox: NSBox = NSBox() for i in 0 ..< (contentView?.subviews.count)! { if i == 1 { contentBox = contentView?.subviews[i] as! NSBox break } } var label: NSTextField? for i in 0 ... (contentBox.contentView?.subviews.count)!-1 { let view: NSView = (contentBox.contentView?.subviews[i])! if view.isKind(of: NSTextField.self) { label = view as? NSTextField break } } return label } } @objc class func openWindow(window: NSWindow, type: KMPasswordInputWindowType = .open, url: URL, callback: @escaping (KMPasswordInputWindowResult, String?)->Void) { let passwordWindow = KMPasswordInputWindow().window() passwordWindow?.documentURL = url passwordWindow?.type = type passwordInputWindow = passwordWindow passwordWindow?.itemClick = { index, string in if (passwordInputWindow?.sheetParent != nil) { passwordInputWindow?.sheetParent?.endSheet(passwordInputWindow!) } passwordInputWindow = nil passwordInputWindow_private = nil if index == 1 { /// 关闭 callback(.cancel, "") return } /// 解密成功 callback(.success, string) } window.beginSheet(passwordWindow!) } @objc class func success_openWindow(window: NSWindow, type: KMPasswordInputWindowType = .open, url: URL, callback: @escaping (String)->Void) { let passwordWindow = KMPasswordInputWindow().window() passwordWindow?.documentURL = url passwordWindow?.type = type passwordInputWindow = passwordWindow passwordWindow?.itemClick = { index, string in if (passwordInputWindow?.sheetParent != nil) { passwordInputWindow?.sheetParent?.endSheet(passwordInputWindow!) } passwordInputWindow = nil passwordInputWindow_private = nil if index == 1 { /// 关闭 return } /// 解密成功 callback(string) } window.beginSheet(passwordWindow!) } @objc class func openWindow(window: NSWindow, url: URL, needOwner: Bool, callback: @escaping (KMPasswordInputWindowResult, String?)->Void) { let passwordWindow = KMPasswordInputWindow().window() passwordWindow?.documentURL = url let document = CPDFDocument(url: url) if (document?.isLocked != nil && document!.isLocked) { passwordWindow?.type = .open } else if (document?.isEncrypted != nil && document!.isEncrypted) { passwordWindow?.type = .owner } else { passwordWindow?.type = .open } passwordInputWindow = passwordWindow passwordWindow?.itemClick = { index, string in let type = passwordInputWindow?.type if (passwordInputWindow?.sheetParent != nil) { passwordInputWindow?.sheetParent?.endSheet(passwordInputWindow!) } passwordInputWindow = nil passwordInputWindow_private = nil if index == 1 { /// 关闭 callback(.cancel, "") return } /// 解密成功 if (type == .owner) { // 解除的是权限密码 callback(.success, string) return } // 解除的是开启密码 if (needOwner == false) { // 不需要解除权限密码 callback(.success, string) return } if (document == nil) { callback(.success, string) return } document?.unlock(withPassword: string) if (document?.permissionsStatus == .owner) { // 用户是使用的权限密码解密 callback(.success, string) return } if (document!.allowsCopying == true && document!.allowsPrinting == true) { // 文件没有权限限制 callback(.success, string) return } // 需要解除权限密码 KMPasswordInputWindow.openWindow(window: window, type: .owner, url: url) { result, password in if (result == .cancel) { callback(.cancel, "") return } callback(.success, password) } } window.beginSheet(passwordWindow!) } class func saveDocument(_ document: CPDFDocument) -> Bool { let toPath = document.documentURL.path let tempFilePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("\((document.documentURL.lastPathComponent))") if (FileManager.default.fileExists(atPath: tempFilePath!)) { /// 清空数据 try?FileManager.default.removeItem(atPath: tempFilePath!) } var result: Bool = document.write(to: URL(fileURLWithPath: tempFilePath!)) if (result == false) { return false } try?FileManager.default.removeItem(atPath: toPath) result = ((try?FileManager.default.moveItem(atPath: tempFilePath!, toPath: toPath)) != nil) /// 清空数据 try?FileManager.default.removeItem(atPath: tempFilePath!) return result } class func saveDocumentForRemovePassword(_ document: CPDFDocument) -> Bool { let toPath = document.documentURL.path let tempFilePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("\((document.documentURL.lastPathComponent))") if (FileManager.default.fileExists(atPath: tempFilePath!)) { /// 清空数据 try?FileManager.default.removeItem(atPath: tempFilePath!) } var result: Bool = document.writeDecrypt(to: URL(fileURLWithPath: tempFilePath!)) if (result == false) { return false } try?FileManager.default.removeItem(atPath: toPath) result = ((try?FileManager.default.moveItem(atPath: tempFilePath!, toPath: toPath)) != nil) /// 清空数据 try?FileManager.default.removeItem(atPath: tempFilePath!) return result } } extension NSOpenPanel { /** * 打开 NSOpenPanel 窗口(如果文档存在开启密码或者权限密码,则会弹密码输入框) * @param window 弹出 NSOpenPanel 的窗口 [可选] [默认为 主窗口] * @param needOwner 是否需要限制权限密码(如果存在权限密码,会在解锁后再弹权限密码弹窗(目前未实现)) [可选] [默认为 false] * @param callback 回调 * *  默认弹开启密码输入框,needOwner = true 弹权限密码输入框 */ class func km_secure_openPanel(window: NSWindow = NSApp.mainWindow!, needOwner: Bool = false, callback:@escaping (URL?, KMPasswordInputWindowResult? , String?)->Void) { let panel = NSOpenPanel() panel.allowedFileTypes = ["pdf"] panel.beginSheetModal(for: window) { response in if (response == .cancel) { callback(nil, nil, nil) return } let document = CPDFDocument(url: panel.url) if ((document?.isLocked)! == false) { if (document?.isEncrypted == false) { callback(panel.url, nil, nil) return } if (!needOwner) { callback(panel.url, nil, nil) return } KMPasswordInputWindow.openWindow(window: window, type: .owner, url: panel.url!) { result, password in if (result == .cancel) { callback(panel.url, .cancel , nil) return } callback(panel.url, .success , password) } return } /// 已加锁(开启密码) KMPasswordInputWindow.openWindow(window: window, url: panel.url!) { result, password in if (result == .cancel) { callback(panel.url, .cancel, nil) return } if (!needOwner) { callback(panel.url, .success, password) return } /// 用户输入的是权限密码 if (KMPasswordInputWindow.permissionsStatus == .owner) { callback(panel.url, .success ,password) return } /// 用户输入的是开启密码 (无法判断是否还有权限未解密) /// 还有权限密码未解锁 // KMPasswordInputWindow.openWindow(window: window, type: .owner, url: panel.url!) { result, password in // if (result == .cancel) { // callback(panel.url, .cancel , nil) // return // } // // callback(panel.url, .success , password) // } callback(panel.url, .success ,password) } } } /** * 打开 NSOpenPanel 窗口(如果文档存在开启密码或者权限密码,则会弹密码输入框) * @param window 弹出 NSOpenPanel 的窗口 [可选] [默认为 主窗口] * @param needOwner 是否需要限制权限密码(如果存在权限密码,会在解锁后再弹权限密码弹窗(目前未实现)) [可选] [默认为 false] * @param callback 回调 * *  默认弹开启密码输入框,needOwner = true 弹权限密码输入框 *  只返回成功的结果, 用户关闭的操作都未回调(如果有需要回调的需求可以使用 km_secure_openPanel 方法) */ class func km_secure_openPanel_success(window: NSWindow = NSApp.mainWindow!, needOwner: Bool = false, callback:@escaping (URL, String?)->Void) { let panel = NSOpenPanel() panel.allowedFileTypes = ["pdf"] panel.beginSheetModal(for: window) { response in if (response == .cancel) { return } let document = CPDFDocument(url: panel.url) if ((document?.isLocked)! == false) { if (document?.isEncrypted == false) { callback(panel.url!, nil) return } if (!needOwner) { callback(panel.url!, nil) return } KMPasswordInputWindow.openWindow(window: window, type: .owner, url: panel.url!) { result, password in if (result == .cancel) { return } callback(panel.url!, password) } return } /// 已加锁(开启密码) KMPasswordInputWindow.openWindow(window: window, url: panel.url!) { result, password in if (result == .cancel) { return } if (!needOwner) { callback(panel.url!, password) return } /// 用户输入的是权限密码 if (KMPasswordInputWindow.permissionsStatus == .owner) { callback(panel.url!, password) return } callback(panel.url!, password) } } } }