// // KMNSplitPDFWindowController.swift // PDF Reader Pro // // Created by 丁林圭 on 2024/10/24. // import Cocoa import KMComponentLibrary class KMNSplitPDFWindowController: KMNBaseWindowController { @IBOutlet var progress: NSProgressIndicator! @IBOutlet var titleLabel: NSTextField! @IBOutlet var methodLabel: NSTextField! @IBOutlet var averagePerRadio: ComponentRadio! @IBOutlet var averagePernInputNumber: ComponentInputNumber! @IBOutlet var averagePernLabel: NSTextField! @IBOutlet var averageSplitRadio: ComponentRadio! @IBOutlet var averageSplitInputNumber: ComponentInputNumber! @IBOutlet var averageSplitLabel: NSTextField! @IBOutlet var splitRangeRadio: ComponentRadio! @IBOutlet var splitRangeSelect: ComponentSelect! @IBOutlet var fileTitleLabel: NSTextField! @IBOutlet var fileNameBox: NSBox! @IBOutlet var fileNameLabel: NSTextField! @IBOutlet var precedeCheckBox: ComponentCheckBox! @IBOutlet var tagCheckBox: ComponentCheckBox! @IBOutlet var tagInput: ComponentInput! @IBOutlet var separatorCheckBox: ComponentCheckBox! @IBOutlet var separatorInput: ComponentInput! @IBOutlet var cancelButton: ComponentButton! @IBOutlet var splitButton: ComponentButton! @IBOutlet var splitRangeHeightButton:NSLayoutConstraint! @IBOutlet var cancelWidthButton:NSLayoutConstraint! @IBOutlet var splitWidthButton:NSLayoutConstraint! @IBOutlet var averagePerRadioWidthButton:NSLayoutConstraint! @IBOutlet var averageSplitRadioWidthButton:NSLayoutConstraint! @IBOutlet var splitRangeRadioWidthButton:NSLayoutConstraint! @IBOutlet var tagCheckWidthButton:NSLayoutConstraint! @IBOutlet var separatorCheckWidthButton:NSLayoutConstraint! private var orgDocument:CPDFDocument? private var selectionIndexPaths: Set = [] private func viewFileAtFinder(_ filePath: String) { let workspace = NSWorkspace.shared let url = URL(fileURLWithPath: filePath) workspace.activateFileViewerSelecting([url]) } override func updateUIThemeColor() { super.updateUIThemeColor() window?.contentView?.wantsLayer = true window?.contentView?.layer?.backgroundColor = ComponentLibrary.shared.getComponentColorFromKey("colorBg/popup").cgColor averagePerRadio.reloadData() averageSplitRadio.reloadData() splitRangeRadio.reloadData() precedeCheckBox.reloadData() tagCheckBox.reloadData() separatorCheckBox.reloadData() cancelButton.reloadData() splitButton.reloadData() titleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") methodLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") fileTitleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") fileNameBox.fillColor = ComponentLibrary.shared.getComponentColorFromKey("colorFill/4") updateButton() } override func updateUILanguage() { super.updateUILanguage() titleLabel.stringValue = KMLocalizedString("Split") methodLabel.stringValue = KMLocalizedString("Split Method") fileTitleLabel.stringValue = KMLocalizedString("File Name") averagePerRadio.properties.text = KMLocalizedString("Split by every") averageSplitRadio.properties.text = KMLocalizedString("Split averagely to") splitRangeRadio.properties.text = KMLocalizedString("Split by page range") averagePerRadio.reloadData() averageSplitRadio.reloadData() splitRangeRadio.reloadData() averagePernLabel.stringValue = KMLocalizedString("page(s)") precedeCheckBox.properties.text = KMLocalizedString("Keep the current file name in front of labels") tagCheckBox.properties.text = KMLocalizedString("Label") separatorCheckBox.properties.text = KMLocalizedString("Separator") precedeCheckBox.reloadData() tagCheckBox.reloadData() separatorCheckBox.reloadData() setUpPageSelctProperty() cancelButton.properties.buttonText = KMLocalizedString("Cancel") splitButton.properties.buttonText = KMLocalizedString("Split") cancelButton.reloadData() splitButton.reloadData() averagePerRadioWidthButton.constant = averagePerRadio.properties.propertyInfo.viewWidth averageSplitRadioWidthButton.constant = averageSplitRadio.properties.propertyInfo.viewWidth splitRangeRadioWidthButton.constant = splitRangeRadio.properties.propertyInfo.viewWidth tagCheckWidthButton.constant = tagCheckBox.properties.propertyInfo.viewWidth separatorCheckWidthButton.constant = separatorCheckBox.properties.propertyInfo.viewWidth cancelWidthButton.constant = cancelButton.properties.propertyInfo.viewWidth splitWidthButton.constant = splitButton.properties.propertyInfo.viewWidth let pageRangeSelectIndex = splitRangeSelect.indexOfSelect() if pageRangeSelectIndex == 0 { splitRangeSelect.properties.text = KMLocalizedString("Odd Pages Only") } else { if(orgDocument?.pageCount ?? 0 > 1 && pageRangeSelectIndex == 1) { splitRangeSelect.properties.text = KMLocalizedString("Even Pages Only") } } } override func initContentView() { super.initContentView() titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") methodLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") fileTitleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") averagePerRadio.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Split by every"), checkboxType: .normal) averagePernLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular") averageSplitRadio.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Split averagely to"), checkboxType: .normal) averageSplitLabel.stringValue = KMLocalizedString("PDF files") averageSplitLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular") fileNameLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular") splitRangeRadio.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Split by page range"), checkboxType: .selected) precedeCheckBox.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Keep the current file name in front of labels"), checkboxType: .selected) tagCheckBox.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Label"), checkboxType: .selected) separatorCheckBox.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Separator"), checkboxType: .selected) averagePernInputNumber.properties = ComponentInputNumberProperty(alignment: .left, size: .s, state: .normal, isError: false, showErrorInfo: false, isDisabled: true, showPrefix: false, showSuffix: false, minSize: 1, maxSize: Int(orgDocument?.pageCount ?? 1), text:"1") averagePernInputNumber.delegate = self averageSplitInputNumber.properties = ComponentInputNumberProperty(alignment: .left, size: .s, state: .normal, isError: false, showErrorInfo: false, isDisabled: true, showPrefix: false, showSuffix: false, minSize: 1, maxSize: Int(orgDocument?.pageCount ?? 1), text: "1") averageSplitInputNumber.delegate = self let inputTagProperty: ComponentInputProperty = ComponentInputProperty(size: .s, state:.pressed , isError: false, showPrefix: false, showSuffix: false, showClear: false, isDisabled: false, placeholder: "", text: "Part") tagInput.properties = inputTagProperty tagInput.delegate = self let inputSeparatorProperty: ComponentInputProperty = ComponentInputProperty(size: .s, state:.pressed , isError: false, showPrefix: false, showSuffix: false, showClear: false, isDisabled: false, placeholder: "", text: "-") separatorInput.properties = inputSeparatorProperty separatorInput.delegate = self splitRangeSelect.properties = ComponentSelectProperties(size: .s, state: .normal, isDisabled: false, isError: false, leftIcon: false, placeholder: nil, errorText: nil, creatable: false, text: KMLocalizedString("Odd Pages Only", comment: "")) splitButton.properties = ComponentButtonProperty(type: .primary, size: .s, state: .normal, isDisable: false, buttonText: KMLocalizedString("Split"),keepPressState: false) splitButton.setTarget(self, action: #selector(splitButtonClicked(_ :))) cancelButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, state: .normal, buttonText: KMLocalizedString("Cancel")) cancelButton.setTarget(self, action: #selector(cancelButtonClicked(_ :))) averagePerRadio.setTarget(self, action: #selector(splitMethodClicked(_:))) averageSplitRadio.setTarget(self, action: #selector(splitMethodClicked(_:))) splitRangeRadio.setTarget(self, action: #selector(splitMethodClicked(_:))) precedeCheckBox.setTarget(self, action: #selector(changeNameClick(_:))) tagCheckBox.setTarget(self, action: #selector(changeNameClick(_:))) separatorCheckBox.setTarget(self, action: #selector(changeNameClick(_:))) setUpPageSelctProperty() splitRangeSelect.delegate = self } func updateButton() { if(averageSplitRadio.properties.checkboxType == .selected) { averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") } else if (splitRangeRadio.properties.checkboxType == .selected) { averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") } else if (averagePerRadio.properties.checkboxType == .selected) { averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") } } private func setUpPageSelctProperty() { var menuItemString:[String] = [KMLocalizedString("Odd Pages Only"), KMLocalizedString("Even Pages Only"), KMLocalizedString("e.g. 1,3-5,10")] if orgDocument?.pageCount ?? 0 <= 1 { menuItemString.removeObject(KMLocalizedString("Even Pages Only")) } var menuItemArr: [ComponentMenuitemProperty] = [] for language in menuItemString { let itemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: language) menuItemArr.append(itemProperty) } splitRangeSelect.updateMenuItemsArr(menuItemArr) } override func windowDidLoad() { super.windowDidLoad() var isEditIndex:Int = 2 if(orgDocument?.pageCount ?? 0 <= 1) { isEditIndex = 1 } splitRangeSelect.selectItemAtIndex(isEditIndex) splitRangeSelect.properties.creatable = true splitRangeSelect.properties.text = KMNTools.parseIndexPathsSet(indexSets: selectionIndexPaths) splitRangeSelect.reloadData() updateFileName() progress.isHidden = true } func updateFileName() { let pdfName = orgDocument?.documentURL.deletingPathExtension().path.lastPathComponent ?? "" var splitPartString = "" var separatorString = "" if (tagCheckBox.properties.checkboxType == .selected) { splitPartString = tagInput.properties.text } if (separatorCheckBox.properties.checkboxType == .selected) { separatorString = separatorInput.properties.text } var partName = "" if (precedeCheckBox.properties.checkboxType == .selected) { if (separatorString.isEmpty == false) { if (partName.isEmpty == false) { partName.append(separatorString) } else { partName = separatorString } } if (splitPartString.isEmpty == false) { if (partName.isEmpty == false) { partName.append(splitPartString) } else { partName = splitPartString } } fileNameLabel.stringValue = String(format: "%@%@.pdf", pdfName, partName) } else { if (splitPartString.isEmpty == false) { if (partName.isEmpty == false) { partName.append(splitPartString) } else { partName = splitPartString } } if (separatorString.isEmpty == false) { if (partName.isEmpty == false) { partName.append(separatorString) } else { partName = separatorString } } fileNameLabel.stringValue = String(format: "%@%@.pdf", partName, pdfName) } } convenience init(_ document: CPDFDocument?, selectionIndexPaths: Set) { self.init(windowNibName: "KMNSplitPDFWindowController") orgDocument = document self.selectionIndexPaths = selectionIndexPaths // 修正此行 } convenience init(_ filePath: String,password:String?) { self.init(windowNibName: "KMNSplitPDFWindowController") let document = CPDFDocument.init(url: URL(fileURLWithPath: filePath)) if password != nil { document?.unlock(withPassword: password as String?) } orgDocument = document selectionIndexPaths = [IndexPath(item: 0, section: 0)] // 修正此行 } //MARK: - Action @objc func cancelButtonClicked(_ sender: NSView) { own_closeEndSheet() } @objc func splitButtonClicked(_ sender: NSView) { let pageRangeSelectIndex = splitRangeSelect.indexOfSelect() let fileAttribute = KMNFileAttribute() if(splitRangeRadio.properties.checkboxType == .selected) { fileAttribute.password = orgDocument?.password ?? "" fileAttribute.pdfDocument = orgDocument fileAttribute.filePath = orgDocument?.documentURL.path ?? "" if(pageRangeSelectIndex == 0) { fileAttribute.bAllPage = false fileAttribute.pagesType = .OnlyOdd } else { fileAttribute.bAllPage = false if(orgDocument?.pageCount ?? 0 <= 1) { fileAttribute.pagesType = .PagesString fileAttribute.pagesString = splitRangeSelect.properties.text ?? "" } else { if(pageRangeSelectIndex == 1) { fileAttribute.pagesType = .OnlyEven } else { fileAttribute.pagesType = .PagesString fileAttribute.pagesString = splitRangeSelect.properties.text ?? "" } } } if (fileAttribute.fetchSelectPages().isEmpty) { splitRangeSelect.properties.isError = true splitRangeSelect.properties.errorText = KMLocalizedString("Invalid page range or the page number is out of range. Please try again.") splitRangeSelect.reloadData() splitRangeHeightButton.constant = 50 return } } let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true panel.canCreateDirectories = true panel.beginSheetModal(for: self.window!) { result in if (result == .OK) { let outputURL = panel.urls.first var tFolderPath = outputURL!.path var index = 0 let folderName = self.fileNameLabel.stringValue.deletingPathExtension.lastPathComponent var folderPath = "\(tFolderPath)" + "/" + "\(folderName)" //创建目录 while FileManager.default.fileExists(atPath: folderPath) { index += 1 folderPath = "\(tFolderPath)" + "/" + "\(folderName)_\(index)" } tFolderPath = folderPath try?FileManager.default.createDirectory(atPath: tFolderPath, withIntermediateDirectories: true) self.progress.isHidden = false self.cancelButton.properties.isDisabled = true self.splitButton.properties.isDisabled = true self.cancelButton.reloadData() self.splitButton.reloadData() self.progress.startAnimation(nil) if(self.averagePerRadio.properties.checkboxType == .selected) { let index = Int(self.averagePernInputNumber.properties.text ?? "1") DispatchQueue.global().async { let successArray = self.orgDocument?.splitByPagesWith(index ?? 1, folerPath: tFolderPath, fileName: folderName) ?? [] DispatchQueue.main.async { self.progress.isHidden = true self.cancelButton.properties.isDisabled = false self.splitButton.properties.isDisabled = false self.cancelButton.reloadData() self.splitButton.reloadData() if (successArray.isEmpty == false) { let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Splitting completed. Tap 'OK' to open the output folder."), buttons: [KMLocalizedString("OK")]) if (response == .alertFirstButtonReturn ) { self.own_closeEndSheet() DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { let filePath = successArray.first ?? "" self.viewFileAtFinder(filePath) } } } else { let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Failed to split!")) if (response == .alertFirstButtonReturn) { self.own_closeEndSheet() } } } } } else if(self.averageSplitRadio.properties.checkboxType == .selected) { let dex = Int(self.averageSplitInputNumber.properties.text ?? "1") DispatchQueue.global().async { let successArray = self.orgDocument?.splitByFileWith(dex ?? 1, folerPath: tFolderPath, fileName: folderName) ?? [] DispatchQueue.main.async { self.progress.isHidden = true self.cancelButton.properties.isDisabled = false self.splitButton.properties.isDisabled = false self.cancelButton.reloadData() self.splitButton.reloadData() if (successArray.isEmpty == false) { let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Splitting completed. Tap 'OK' to open the output folder."), buttons: [KMLocalizedString("OK")]) if (response == .alertFirstButtonReturn) { self.own_closeEndSheet() DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { let filePath = successArray.first ?? "" self.viewFileAtFinder(filePath) } } }else{ let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Failed to split!")) if (response == .alertFirstButtonReturn) { self.own_closeEndSheet() } } } } } else { var isSuccessfully:Bool = false DispatchQueue.global().async { [self] in var pdfDocument1 = CPDFDocument() var pdfDocument2 = CPDFDocument() let pageCnt = self.orgDocument?.pageCount ?? 0 let pages = fileAttribute.fetchSelectPages() for i in 0 ..< pageCnt { var isSelected = false for number in pages { if number == i+1 { isSelected = true if let page = self.orgDocument?.page(at: i) as? CPDFPage { pdfDocument1?.insertPageObject(page, at: pdfDocument1!.pageCount) } } } if (!isSelected) { if let page = self.orgDocument?.page(at: i) as? CPDFPage { pdfDocument2?.insertPageObject(page, at: pdfDocument2!.pageCount) } } } if (pdfDocument1!.pageCount > 0) { let tpath = "\(tFolderPath)/\(folderName) 1.pdf" isSuccessfully = pdfDocument1?.write(toFile:tpath) ?? false } if (pdfDocument2!.pageCount > 0) { let tPath = "\(tFolderPath)/\(folderName) 2.pdf" pdfDocument2?.write(toFile: tPath) } DispatchQueue.main.async { self.progress.isHidden = true self.cancelButton.properties.isDisabled = false self.splitButton.properties.isDisabled = false self.cancelButton.reloadData() self.splitButton.reloadData() if (isSuccessfully) { let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Splitting completed. Tap 'OK' to open the output folder."), buttons: [KMLocalizedString("OK")]) if (response == .alertFirstButtonReturn) { self.own_closeEndSheet() DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { let filePath = "\(tFolderPath)/\(folderName) 1.pdf" self.viewFileAtFinder(filePath) } } } else { let response = KMAlertTool.runModelForMainThread_r(message: KMLocalizedString("Failed to split!")) if (response == .alertFirstButtonReturn) { self.own_closeEndSheet() } } } } } } } } @objc func changeNameClick(_ sender: NSView){ updateFileName() } @objc func splitMethodClicked(_ sender: NSView){ var oldSelectRadioBtn:ComponentRadio? = nil if sender == averagePerRadio { if(averageSplitRadio.properties.checkboxType == .selected) { oldSelectRadioBtn = averageSplitRadio averageSplitInputNumber.properties.isDisabled = true averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averageSplitInputNumber.reloadData() } else if (splitRangeRadio.properties.checkboxType == .selected){ oldSelectRadioBtn = splitRangeRadio splitRangeSelect.properties.isDisabled = true splitRangeSelect.reloadData() } averagePernInputNumber.properties.isDisabled = false averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") averagePernInputNumber.reloadData() } else if sender == averageSplitRadio { if(averagePerRadio.properties.checkboxType == .selected) { oldSelectRadioBtn = averagePerRadio averagePernInputNumber.properties.isDisabled = true averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averagePernInputNumber.reloadData() } else if (splitRangeRadio.properties.checkboxType == .selected){ oldSelectRadioBtn = splitRangeRadio splitRangeSelect.properties.isDisabled = true splitRangeSelect.reloadData() } averageSplitInputNumber.properties.isDisabled = false averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") averageSplitInputNumber.reloadData() } else if sender == splitRangeRadio { if(averagePerRadio.properties.checkboxType == .selected) { oldSelectRadioBtn = averagePerRadio averagePernInputNumber.properties.isDisabled = true averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averagePernInputNumber.reloadData() } else if (averageSplitRadio.properties.checkboxType == .selected){ oldSelectRadioBtn = averageSplitRadio averageSplitInputNumber.properties.isDisabled = true averageSplitLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averageSplitInputNumber.reloadData() } splitRangeSelect.properties.isDisabled = false splitRangeSelect.reloadData() } if(oldSelectRadioBtn != nil) { oldSelectRadioBtn?.properties.checkboxType = .normal oldSelectRadioBtn?.reloadData() } } } //MARK: - ComponentInputNumberDelegate extension KMNSplitPDFWindowController: ComponentInputNumberDelegate { func componentInputNumberDidValueChanged(inputNumber: ComponentInputNumber?) { } } //MARK: - ComponentSelectDelegate extension KMNSplitPDFWindowController: ComponentSelectDelegate { func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) { if(view == splitRangeSelect) { let pageRangeSelectIndex = splitRangeSelect.indexOfSelect() var isEditIndex:Int = 2 if(orgDocument?.pageCount ?? 0 <= 1) { isEditIndex = 1 } if (pageRangeSelectIndex == isEditIndex) { splitRangeSelect.properties.text = "" splitRangeSelect.properties.placeholder = KMLocalizedString("e.g. 1,3-5,10") splitRangeSelect.properties.creatable = true } else { splitRangeSelect.properties.placeholder = "" splitRangeSelect.properties.creatable = false } splitRangeSelect.reloadData() } } func componentSelectTextDidChange(_ view: ComponentSelect) { if splitRangeSelect.properties.isError == true { splitRangeSelect.properties.isError = false splitRangeSelect.reloadData() splitRangeHeightButton.constant = 32 } } } //MARK: - ComponentInputDelegate extension KMNSplitPDFWindowController: ComponentInputDelegate { func componentInputDidEndEditing(inputView: ComponentInput) { } func componentInputDidChanged(inputView: ComponentInput) { updateFileName() } }