// // 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 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]) } private func setUpProperty() { titleLabel.stringValue = KMLocalizedString("Split") titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") titleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/1") methodLabel.stringValue = KMLocalizedString("Split Method") methodLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") methodLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") fileTitleLabel.stringValue = KMLocalizedString("File Name") fileTitleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") fileTitleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") averagePerRadio.properties = ComponentCheckBoxProperty(size: .s, state: .normal, isDisabled: false, showhelp: false, text: KMLocalizedString("Split by every"), checkboxType: .normal) averagePernLabel.stringValue = KMLocalizedString("page(s)") averagePernLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") 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.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") averageSplitLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular") fileNameLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") 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")) splitButton.setTarget(self, action: #selector(splitButtonClicked(_ :))) splitWidthButton.constant = splitButton.properties.propertyInfo.viewWidth cancelButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, state: .normal, buttonText: KMLocalizedString("Cancel")) cancelButton.setTarget(self, action: #selector(cancelButtonClicked(_ :))) cancelWidthButton.constant = cancelButton.properties.propertyInfo.viewWidth fileNameBox.fillColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-dis") 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 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 } 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() setUpProperty() 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 == 2) { fileAttribute.pagesType = .OnlyEven } else { fileAttribute.pagesType = .PagesString fileAttribute.pagesString = splitRangeSelect.properties.text ?? "" } } } if (fileAttribute.fetchSelectPages().isEmpty) { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = String(format: "%@ %@", fileAttribute.filePath.lastPathComponent, KMLocalizedString("Invalid page range or the page number is out of range. Please try again.")) alert.runModal() 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() } } } extension KMNSplitPDFWindowController: ComponentInputDelegate { func componentInputDidEndEditing(inputView: ComponentInput) { } func componentInputDidChanged(inputView: ComponentInput) { updateFileName() } }