Browse Source

【2025】【综合】补充基础类

dinglingui 4 months ago
parent
commit
4393811e9f

BIN
PDF Office/PDF Master/KMClass/ComponentLibraryDemo/ComponentGroupItem.nib


+ 166 - 0
PDF Office/PDF Master/KMClass/Tools/Base/KMBaseWindowController.swift

@@ -0,0 +1,166 @@
+//
+//  KMNBaseWindowController.swift
+//  PDF Reader Pro
+//
+//  Created by tangchao on 2023/5/11.
+//
+
+import Cocoa
+
+class KMNBaseWindowController: NSWindowController {
+        
+    public var parentWindow: NSWindow?
+    public var handler: ((String?) -> Void)!
+
+    deinit {
+        KMPrint("self.className" + "deinit.")
+        
+        self.removeNotification()
+    }
+    
+    override func windowDidLoad() {
+        super.windowDidLoad()
+    
+        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
+        self.initSubViews()
+        self.initDefaultValue()
+        self.initNotification()
+    }
+    
+    func initSubViews() {}
+    func initDefaultValue() {
+        self.window?.appearance = NSApp.appearance
+    }
+    
+    func initNotification() {
+        DistributedNotificationCenter.default().addObserver(self, selector: #selector(_themeChanged), name: NSApplication.interfaceThemeChangedNotification, object: nil)
+    }
+    
+    func removeNotification() {
+        DistributedNotificationCenter.default().removeObserver(self)
+    }
+    
+    func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) {
+        
+    }
+    
+    func own_beginSheetModal(for window: NSWindow?, completionHandler handler: ((String?) -> Void)?) {
+        if window != nil {
+            parentWindow = window
+            window!.beginSheet(self.window!) { ModalResponse in
+                self.handler?(nil)
+            }
+        }
+        self.handler = handler
+    }
+    
+    func own_closeEndSheet() {
+        parentWindow?.endSheet(self.window!)
+    }
+
+}
+
+// MARK: - Private Methods
+
+extension KMNBaseWindowController {
+    @objc private func _themeChanged(_ sender: Notification) {
+        let isDarkModel = KMAdvertisementConfig.isDarkModel()
+        if isDarkModel {
+            self.window?.appearance = .init(named: .darkAqua)
+        } else {
+            self.window?.appearance = .init(named: .aqua)
+        }
+        
+        Task { @MainActor in
+            self.interfaceThemeDidChanged(self.window?.appearance?.name ?? .aqua)
+        }
+    }
+}
+
+extension NSWindowController {
+    func knCheckPassword(url: URL, type: KMPasswordInputWindowType, password: String = "", completion: @escaping ((_ success: Bool, _ resultPassword: String) -> Void)) {
+        // 判断路径 + document
+        guard let document = CPDFDocument.init(url: url) else {
+            return completion(false, "")
+        }
+
+        // 判断是否为加密文档
+        if document.isLocked == false {
+            if type == .open {
+                completion(true, "")
+                return
+            }
+        }
+        
+        if document.isLocked == false && (document.allowsCopying && document.allowsPrinting) {
+            completion(true, "")
+            return
+        }
+        
+        // 加密文件,尝试解锁
+        if password.isEmpty == false {
+            let preStatus = document.permissionsStatus
+            document.unlock(withPassword: password)
+            if document.permissionsStatus.rawValue > preStatus.rawValue { // 解密成功
+                completion(true, password)
+                return
+            }
+        }
+        
+        // 弹密码弹窗
+        Task { @MainActor in
+            KMPasswordInputWindow.openWindow(window: self.window!, type: type, url: url) { result , password in
+                if (result == .cancel) {
+                    completion(false, "")
+                    return
+                } else {
+                    completion(true, password ?? "")
+                }
+            }
+        }
+    }
+}
+
+extension KMNBaseWindowController {
+    static func checkPassword(url: URL, type: KMPasswordInputWindowType, password: String = "", completion: @escaping ((_ success: Bool, _ resultPassword: String) -> Void)) {
+        // 判断路径 + document
+        guard let document = CPDFDocument.init(url: url) else {
+            return completion(false, "")
+        }
+
+        // 判断是否为加密文档
+        if document.isLocked == false {
+            if type == .open {
+                completion(true, "")
+                return
+            }
+        }
+        
+        if document.isLocked == false && (document.allowsCopying && document.allowsPrinting) {
+            completion(true, "")
+            return
+        }
+        
+        // 加密文件,尝试解锁
+        if password.isEmpty == false {
+            let preStatus = document.permissionsStatus
+            document.unlock(withPassword: password)
+            if document.permissionsStatus.rawValue > preStatus.rawValue { // 解密成功
+                completion(true, password)
+                return
+            }
+        }
+        
+        // 弹密码弹窗
+        Task { @MainActor in
+            KMPasswordInputWindow.openWindow(window: NSWindow.currentWindow(), type: type, url: url) { result , password in
+                if (result == .cancel) {
+                    completion(false, "")
+                    return
+                } else {
+                    completion(true, password ?? "")
+                }
+            }
+        }
+    }
+}

+ 477 - 0
PDF Office/PDF Master/KMClass/Tools/Base/KMNBaseViewController.swift

@@ -0,0 +1,477 @@
+//
+//  KMNBaseViewController.swift
+//  PDF Reader Pro
+//
+//  Created by tangchao on 2023/5/5.
+//
+
+import Cocoa
+
+// 基类 [抽象类]
+class KMNBaseViewController: NSViewController {
+    // 是否需要菜单
+    var needMenu = false {
+        didSet {
+            if (self.needMenu) {
+                self.addMenu(to: self.view)
+            } else {
+                self.removeMenu(to: self.view)
+            }
+        }
+    }
+    
+    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()
+    }
+    
+    // Noti
+    
+    func addNotifations() { }
+    
+    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: String? = folderPath.kUrlToPDFFolderPath() as String
+            if (savePath == nil) {
+                continue
+            }
+            
+            dispatchGroup.enter()
+            KMConvertPDFManager.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 KMConvertPDFManager.supportFileType() {
+                array.append(fileType)
+            }
+            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 ?? .zero, 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.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
+        progressC.showClose = false
+        progressC.message = message
+
+        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 KMNBaseViewController: NSMenuDelegate, NSMenuItemValidation {
+    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
+        return true
+    }
+    
+    func menuNeedsUpdate(_ menu: NSMenu) {
+        menu.removeAllItems()
+    }
+}

+ 61 - 0
PDF Office/PDF Master/KMClass/Tools/Tool/KMNTools.swift

@@ -0,0 +1,61 @@
+//
+//  KMNTools.swift
+//  PDF Reader Pro
+//
+//  Created by 丁林圭 on 2024/10/24.
+//
+
+import Cocoa
+
+class KMNTools: NSObject {
+
+    @objc class func parseIndexSet(indexSet: IndexSet) -> String {
+        return self.parseIndexs(indexs: indexSet.sorted())
+    }
+    
+    @objc class func parseIndexs(indexs: [Int]) -> String {
+        if (indexs.isEmpty) {
+            return ""
+        }
+        if (indexs.count == 1) {
+            return "\(indexs.first!+1)"
+        }
+        
+        var sortArray: [Int] = []
+        for i in indexs {
+            sortArray.append(i)
+        }
+        /// 排序 (升序)
+        sortArray.sort(){$0 < $1}
+        
+        var a: Int = 0
+        var b: Int = 0
+        var result: String?
+        for i in sortArray {
+            if (result == nil) {
+                a = i
+                b = i
+                result = ""
+                continue
+            }
+            if (i == b+1) {
+                b = i
+                if (i == sortArray.last) {
+                    result?.append(String(format: "%d-%d", a+1,b+1))
+                }
+            } else {
+                if (a == b) {
+                    result?.append(String(format: "%d,", a+1))
+                } else {
+                    result?.append(String(format: "%d-%d,", a+1,b+1))
+                }
+                a = i
+                b = i
+                if (i == sortArray.last) {
+                    result?.append(String(format: "%d", a+1))
+                }
+            }
+        }
+        return result ?? ""
+    }
+}

+ 44 - 2
PDF Office/PDF Reader Pro.xcodeproj/project.pbxproj

@@ -3769,7 +3769,6 @@
 		BB5726F02B20707D0089D283 /* CPDFMarkupAnnotation+PDFListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5726EF2B20707C0089D283 /* CPDFMarkupAnnotation+PDFListView.swift */; };
 		BB5726F12B20707D0089D283 /* CPDFMarkupAnnotation+PDFListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5726EF2B20707C0089D283 /* CPDFMarkupAnnotation+PDFListView.swift */; };
 		BB5726F22B20707D0089D283 /* CPDFMarkupAnnotation+PDFListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5726EF2B20707C0089D283 /* CPDFMarkupAnnotation+PDFListView.swift */; };
-		BB5A9D242CB64F4000F64C1F /* KMComponentLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB5A9D232CB64F1D00F64C1F /* KMComponentLibrary.framework */; };
 		BB5A9D302CB6520100F64C1F /* KMNHomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB5A9D282CB6520100F64C1F /* KMNHomeViewController.xib */; };
 		BB5A9D312CB6520100F64C1F /* KMNHomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB5A9D282CB6520100F64C1F /* KMNHomeViewController.xib */; };
 		BB5A9D322CB6520100F64C1F /* KMNHomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB5A9D282CB6520100F64C1F /* KMNHomeViewController.xib */; };
@@ -5716,6 +5715,11 @@
 		F3A0F8642CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A0F8602CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib */; };
 		F3A0F8652CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A0F8602CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib */; };
 		F3A0F8662CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A0F8602CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib */; };
+		F3A0F86D2CCA118D00E7373F /* KMNTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A0F86C2CCA118D00E7373F /* KMNTools.swift */; };
+		F3A0F86E2CCA118D00E7373F /* KMNTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A0F86C2CCA118D00E7373F /* KMNTools.swift */; };
+		F3A0F86F2CCA118D00E7373F /* KMNTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A0F86C2CCA118D00E7373F /* KMNTools.swift */; };
+		F3A0F8712CCA16A300E7373F /* KMComponentLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB5A9D232CB64F1D00F64C1F /* KMComponentLibrary.framework */; };
+		F3A0F8722CCA16A300E7373F /* KMComponentLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BB5A9D232CB64F1D00F64C1F /* KMComponentLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		F3A9DC81294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A9DC7D294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib */; };
 		F3A9DC82294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A9DC7D294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib */; };
 		F3A9DC83294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A9DC7D294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib */; };
@@ -5728,6 +5732,15 @@
 		F3D395672CBD2C6D000C6729 /* KMNCustomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D395662CBD2C6D000C6729 /* KMNCustomAlertView.swift */; };
 		F3D395682CBD2C6D000C6729 /* KMNCustomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D395662CBD2C6D000C6729 /* KMNCustomAlertView.swift */; };
 		F3D395692CBD2C6D000C6729 /* KMNCustomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D395662CBD2C6D000C6729 /* KMNCustomAlertView.swift */; };
+		F3DB85E52CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */ = {isa = PBXBuildFile; fileRef = F3DB85E32CCA1C4100D0AFDE /* ComponentGroupItem.nib */; };
+		F3DB85E62CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */ = {isa = PBXBuildFile; fileRef = F3DB85E32CCA1C4100D0AFDE /* ComponentGroupItem.nib */; };
+		F3DB85E72CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */ = {isa = PBXBuildFile; fileRef = F3DB85E32CCA1C4100D0AFDE /* ComponentGroupItem.nib */; };
+		F3DB85EC2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85E92CCA209600D0AFDE /* KMNBaseViewController.swift */; };
+		F3DB85ED2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85E92CCA209600D0AFDE /* KMNBaseViewController.swift */; };
+		F3DB85EE2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85E92CCA209600D0AFDE /* KMNBaseViewController.swift */; };
+		F3DB85F22CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85EB2CCA209600D0AFDE /* KMBaseWindowController.swift */; };
+		F3DB85F32CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85EB2CCA209600D0AFDE /* KMBaseWindowController.swift */; };
+		F3DB85F42CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DB85EB2CCA209600D0AFDE /* KMBaseWindowController.swift */; };
 		F3F0B27329B8ACD000722957 /* CPDFListViewDragObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F3F0B27229B8ACD000722957 /* CPDFListViewDragObject.m */; };
 		F3F0B27429B8ACD000722957 /* CPDFListViewDragObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F3F0B27229B8ACD000722957 /* CPDFListViewDragObject.m */; };
 		F3F0B27529B8ACD000722957 /* CPDFListViewDragObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F3F0B27229B8ACD000722957 /* CPDFListViewDragObject.m */; };
@@ -5878,6 +5891,7 @@
 				89752DA82936ECE4003FF08E /* Masonry.framework in Embed Frameworks */,
 				BBD7FDFC2A13210700F96075 /* AFNetworking.framework in Embed Frameworks */,
 				BB88401F2A132C6B0062446B /* ObjectiveDropboxOfficial.framework in Embed Frameworks */,
+				F3A0F8722CCA16A300E7373F /* KMComponentLibrary.framework in Embed Frameworks */,
 				F328C0AF2CA16C6C00BFDD23 /* libpaddle2onnx.1.0.7.dylib in Embed Frameworks */,
 				BBD54ECA2A1C53A80012A230 /* libopencv_world.4.2.dylib in Embed Frameworks */,
 				BBD7FE002A13210C00F96075 /* ComPDFKit.framework in Embed Frameworks */,
@@ -8160,11 +8174,15 @@
 		F3A0F8582CC940DD00E7373F /* KMNPDFInsertBlankWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KMNPDFInsertBlankWindowController.xib; sourceTree = "<group>"; };
 		F3A0F85F2CC9459400E7373F /* KMNPDFInsertClipboardWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMNPDFInsertClipboardWindowController.swift; sourceTree = "<group>"; };
 		F3A0F8602CC9459400E7373F /* KMNPDFInsertClipboardWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KMNPDFInsertClipboardWindowController.xib; sourceTree = "<group>"; };
+		F3A0F86C2CCA118D00E7373F /* KMNTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMNTools.swift; sourceTree = "<group>"; };
 		F3A9DC7D294309D80074E5D2 /* CPDFListEditAnnotationViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CPDFListEditAnnotationViewController.xib; sourceTree = "<group>"; };
 		F3B7DF992948565000333201 /* CPDFListHoverAnnotationViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CPDFListHoverAnnotationViewController.xib; sourceTree = "<group>"; };
 		F3B7DFA32949C8E300333201 /* CPDFListView.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = CPDFListView.xcassets; sourceTree = "<group>"; };
 		F3D395662CBD2C6D000C6729 /* KMNCustomAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMNCustomAlertView.swift; sourceTree = "<group>"; };
 		F3D547B129559E1D00AA3953 /* ComPDFUIKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComPDFUIKit.h; sourceTree = "<group>"; };
+		F3DB85E32CCA1C4100D0AFDE /* ComponentGroupItem.nib */ = {isa = PBXFileReference; lastKnownFileType = file; path = ComponentGroupItem.nib; sourceTree = "<group>"; };
+		F3DB85E92CCA209600D0AFDE /* KMNBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMNBaseViewController.swift; sourceTree = "<group>"; };
+		F3DB85EB2CCA209600D0AFDE /* KMBaseWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMBaseWindowController.swift; sourceTree = "<group>"; };
 		F3F0B27129B8ACD000722957 /* CPDFListViewDragObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CPDFListViewDragObject.h; sourceTree = "<group>"; };
 		F3F0B27229B8ACD000722957 /* CPDFListViewDragObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CPDFListViewDragObject.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -8274,7 +8292,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				BB5A9D242CB64F4000F64C1F /* KMComponentLibrary.framework in Frameworks */,
 				9F00CF7F2A386DC500AC462E /* GoogleAppMeasurementIdentitySupport.framework in Frameworks */,
 				9F00CF7C2A386DC500AC462E /* FirebaseCoreDiagnostics.framework in Frameworks */,
 				9F00CCC52A2F2E2400AC462E /* AppCenter.framework in Frameworks */,
@@ -8303,6 +8320,7 @@
 				BB88401E2A132C6B0062446B /* ObjectiveDropboxOfficial.framework in Frameworks */,
 				BBD7FDFD2A13210A00F96075 /* ComPDFKit_Conversion.framework in Frameworks */,
 				ADFCEB3E2B4FB8C90001EBAF /* FirebaseRemoteConfig.framework in Frameworks */,
+				F3A0F8712CCA16A300E7373F /* KMComponentLibrary.framework in Frameworks */,
 				9F00CF4F2A38655500AC462E /* FirebaseCore.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -14515,6 +14533,7 @@
 		BBE7884A2CBD2450008086E2 /* ComponentLibraryDemo */ = {
 			isa = PBXGroup;
 			children = (
+				F3DB85E32CCA1C4100D0AFDE /* ComponentGroupItem.nib */,
 				BBE7884C2CBD2463008086E2 /* WCCompWindowController.swift */,
 				BBE7884B2CBD2462008086E2 /* WCCompWindowController.xib */,
 				BBE7884D2CBD2463008086E2 /* Demo */,
@@ -15180,6 +15199,7 @@
 			isa = PBXGroup;
 			children = (
 				F3A0F8532CC8F59400E7373F /* KMNConvertTool.swift */,
+				F3A0F86C2CCA118D00E7373F /* KMNTools.swift */,
 			);
 			path = Tool;
 			sourceTree = "<group>";
@@ -15195,6 +15215,7 @@
 		F3D395632CBD2BE7000C6729 /* Tools */ = {
 			isa = PBXGroup;
 			children = (
+				F3DB85E82CCA209600D0AFDE /* Base */,
 				BB4583C42CC91531005737F3 /* NSImage+Extension */,
 				F3A0F8522CC8F56400E7373F /* Tool */,
 				BBA633352CC09DA30040B7F6 /* NSWindowController+Extension */,
@@ -15211,6 +15232,15 @@
 			path = CustomAlertView;
 			sourceTree = "<group>";
 		};
+		F3DB85E82CCA209600D0AFDE /* Base */ = {
+			isa = PBXGroup;
+			children = (
+				F3DB85E92CCA209600D0AFDE /* KMNBaseViewController.swift */,
+				F3DB85EB2CCA209600D0AFDE /* KMBaseWindowController.swift */,
+			);
+			path = Base;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -15645,6 +15675,7 @@
 				BB8810AC2B4F7D7500AFA63E /* KMVerificationViewController.xib in Resources */,
 				BB52F5702CC236A2007418DB /* KMLinkEmailView.xib in Resources */,
 				BBB376A42B10A7FD009539CC /* a_4b.png in Resources */,
+				F3DB85E52CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */,
 				BB853C952AF8DCC7009C20C1 /* KMBatchOperateRemovePasswordViewController.xib in Resources */,
 				BB52372A29C313CA00663BD7 /* KMAnnotationPropertyBaseController.xib in Resources */,
 				ADE86A962B0226BB00414DFA /* KMRemovePasswordView.xib in Resources */,
@@ -16252,6 +16283,7 @@
 				BB031B7F2C47BB090099F7AD /* KMUserListItemCellView.xib in Resources */,
 				F3B7DF9E2948565000333201 /* CPDFListHoverAnnotationViewController.xib in Resources */,
 				BBADCF592AF3C7B3004ECE0C /* KMBatchOperateAddWatermarkViewController.xib in Resources */,
+				F3DB85E62CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */,
 				BB1B0AF02B4FC6E900889528 /* KMFunctionGuideNameItemView.xib in Resources */,
 				9FF371D22C69B8BC005F9CC5 /* CDistanceMeasureInfoWindowController.xib in Resources */,
 				BBD8EE9A2B8EC86A00EB05FE /* AutoSaveFileItem.xib in Resources */,
@@ -17000,6 +17032,7 @@
 				BBEDC2242B980A8400970C54 /* KMOutlineEditViewController.xib in Resources */,
 				BBD8EE9B2B8EC86A00EB05FE /* AutoSaveFileItem.xib in Resources */,
 				BBD1F781296F9BE000343885 /* KMPageEditSettingBaseWindowController.xib in Resources */,
+				F3DB85E72CCA1C4100D0AFDE /* ComponentGroupItem.nib in Resources */,
 				BB5DF1EE2959C5CB0025CDA1 /* KMHeaderFooterPreviewController.xib in Resources */,
 				ADBC2D17299CCD10006280C8 /* KMTextfieldButton.xib in Resources */,
 				9F512CD42B469A7700EC0BC3 /* KMPageDisplayThemeCollectionViewItem.xib in Resources */,
@@ -17604,6 +17637,7 @@
 				BBBE20932B21B18900509C4E /* KMPDFInsertWindowController.swift in Sources */,
 				BBC28F4A2B0F509B00D73206 /* KMAnimatedBorderlessWindow.swift in Sources */,
 				BB74DA7B2AC41DE9006EDFE7 /* NSString+KMExtension.swift in Sources */,
+				F3DB85EC2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */,
 				ADE86ADD2B0AF4B600414DFA /* KMCompareContentSettingWindowController.swift in Sources */,
 				AD1CA4112A061CCD0070541F /* KMAnnotationScreenColorViewItem.swift in Sources */,
 				ADDDCE242B43A32A005B4AB5 /* AppSandboxFileAccess.m in Sources */,
@@ -18034,6 +18068,7 @@
 				BB8115FB29924A5F0008F536 /* KMSecureEncryptCheckCellView.swift in Sources */,
 				AD867FAF29DFBB1200F00440 /* KMAnnotationOutlineCellView.swift in Sources */,
 				AD867F9429D955BF00F00440 /* KMBOTAOutlineCellView.swift in Sources */,
+				F3DB85F22CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */,
 				9F1FE4A529406E4700E952CA /* CTToolbarView.m in Sources */,
 				BBB7899C2BE8BF2400F7E09C /* CustomCornerView.swift in Sources */,
 				BB162E92294FFE020088E9D1 /* KMWatermarkModel.swift in Sources */,
@@ -18363,6 +18398,7 @@
 				BB8F4557295AA1270037EA22 /* KMHeaderFooterPropertyInfoController.swift in Sources */,
 				BB6347C42AF24F6300F5438E /* KMBatchoperateConvertCollectionViewItem.swift in Sources */,
 				AD88108D29A760D100178CA1 /* KMRegisterPresenter.swift in Sources */,
+				F3A0F86D2CCA118D00E7373F /* KMNTools.swift in Sources */,
 				BB146FB4299DC0D100784A6A /* GTLRUtilities.m in Sources */,
 				BBC745F7296178BD0072C2ED /* KMCropTools.swift in Sources */,
 				AD9527DB2952EE700039D2BC /* KMPrintPage_C.swift in Sources */,
@@ -18724,6 +18760,7 @@
 				BB89723B294B3C840045787C /* KMWatermarkPropertyController.swift in Sources */,
 				9FA607DF28FD4C9F00B46586 /* KMHomePopViewController.swift in Sources */,
 				BBC3484F29582920008D2CD1 /* KMBackgroundColorView.swift in Sources */,
+				F3A0F86E2CCA118D00E7373F /* KMNTools.swift in Sources */,
 				ADF1569429A62D1D001D1018 /* KMLoginLeftImageView.swift in Sources */,
 				AD3AAD352B0B7AF500DE5FE7 /* KMCompareThumbView.swift in Sources */,
 				BBE78F1C2B36F69F0071AC1A /* KMLeftSideViewController+Note.swift in Sources */,
@@ -18987,6 +19024,7 @@
 				9F8539D72943180000DF644E /* KMTabAppearance.swift in Sources */,
 				9FDD0F6C294AD12C000C4DAD /* KMMainViewController+UI.swift in Sources */,
 				BB3AD6FA29935483004FC1AE /* Reachability.m in Sources */,
+				F3DB85F32CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */,
 				BBEC00C0295C306400A26C98 /* KMBatesPropertyController.swift in Sources */,
 				BB0A55222A30968900B6E84B /* KMDesignBaseView.swift in Sources */,
 				BB61F2132B59120F00777E27 /* KMScroller.swift in Sources */,
@@ -19842,6 +19880,7 @@
 				9F0CB49029683DEE00007028 /* KMPropertiesPanelLineSubVC.swift in Sources */,
 				AD53B6FF29AC5FCD00D61E81 /* KMLightMemberToken.swift in Sources */,
 				9FB220F22B1863C800A5B208 /* KMAnnotationFromSignature.swift in Sources */,
+				F3DB85ED2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */,
 				ADFCEB372B4F78220001EBAF /* KMFile.swift in Sources */,
 				BB0542162965705D00F2665C /* KMCropTipView.swift in Sources */,
 				AD02573B2A8601AA00EAD5D5 /* KMLoginManager.swift in Sources */,
@@ -20518,6 +20557,7 @@
 				9F53D5552AD683A700CCF9D8 /* KMAnnotationPropertyBaseController.swift in Sources */,
 				BB3198182AC55E6D00107371 /* CPDFDocument+KMExtension.swift in Sources */,
 				BBBE20952B21B18900509C4E /* KMPDFInsertWindowController.swift in Sources */,
+				F3DB85EE2CCA209600D0AFDE /* KMNBaseViewController.swift in Sources */,
 				BB147037299DC0D200784A6A /* OIDRegistrationResponse.m in Sources */,
 				BB8D52A72BA29A5C00D5CB31 /* SKVersionNumber.m in Sources */,
 				9F221ED929A9EC0900978A59 /* KMFillSignTextPanel.swift in Sources */,
@@ -20597,6 +20637,7 @@
 				F38FB9412CBA535C00F0DBA5 /* KMNHomeQuickToolManager.swift in Sources */,
 				BB90E4F42AF37F9F00B04B9F /* KMCustomViewButton.swift in Sources */,
 				BB4A948F2B04726A00940F8B /* KMOCTool.m in Sources */,
+				F3A0F86F2CCA118D00E7373F /* KMNTools.swift in Sources */,
 				F35B484D29A4903300756255 /* NSPointerArray+PDFListView.m in Sources */,
 				BBC70EB62AEA847500AC1585 /* KMToolbarCustomViewController.swift in Sources */,
 				89752E062939DB42003FF08E /* KMToolbarViewController.swift in Sources */,
@@ -20642,6 +20683,7 @@
 				ADCFFC0429C004AD007D3657 /* KMBookMarkTableRowView.swift in Sources */,
 				BBEC00CE295C31F900A26C98 /* KMBatesModel.swift in Sources */,
 				BBF729B92B19632C00576AC5 /* KMRemoveBatesOperationQueue.swift in Sources */,
+				F3DB85F42CCA209600D0AFDE /* KMBaseWindowController.swift in Sources */,
 				BB5A9D3E2CB6520100F64C1F /* KMNHomeViewController.swift in Sources */,
 				BBE788D12CBD2463008086E2 /* InputDemoVC.swift in Sources */,
 				ADBC373429CA95AA00D93208 /* KMComparativeModel.swift in Sources */,