// // KMMergeView.swift // PDF Reader Pro // // Created by lizhe on 2023/11/8. // import Cocoa typealias KMMergeViewCancelAction = (_ view: KMMergeView) -> Void typealias KMMergeViewAddFilesAction = (_ view: KMMergeView) -> Void typealias KMMergeViewMergeAction = (_ view: KMMergeView, _ files: [KMFileAttribute], _ size: CGSize) -> Void typealias KMMergeViewClearAction = (_ view: KMMergeView) -> Void enum KMMergeViewType: Int { case add = 0 case merge } class KMMergeView: KMBaseXibView { @IBOutlet weak var tableview: NSTableView! @IBOutlet weak var clearButton: NSButton! @IBOutlet weak var cancelButton: NSButton! @IBOutlet weak var mergeButton: NSButton! @IBOutlet weak var addFilesButton: NSButton! @IBOutlet weak var progress: NSProgressIndicator! @IBOutlet weak var pageSizeWidthTextField: NSTextField! @IBOutlet weak var pageSizeWidthHeightConnectorTextField: NSTextField! @IBOutlet weak var pageSizeHeightTextField: NSTextField! @IBOutlet weak var pageSizeUnitLabel: NSTextField! @IBOutlet weak var originalSizeButton: NSButton! @IBOutlet weak var A4SizeButton: NSButton! @IBOutlet weak var A3SizeButton: NSButton! @IBOutlet weak var USLetterSizeButton: NSButton! @IBOutlet weak var USLegalButton: NSButton! @IBOutlet weak var customSizeButton: NSButton! @IBOutlet weak var box: KMBox! @IBOutlet weak var boxLabel: NSTextField! @IBOutlet weak var blankView: KMMergeBlankView! var cancelAction: KMMergeViewCancelAction? var addFilesAction: KMMergeViewAddFilesAction? var mergeAction: KMMergeViewMergeAction? var clearAction: KMMergeViewClearAction? let MyTableCellViewDataType = NSPasteboard.PasteboardType(rawValue: "KMLocalForDraggedTypes") var type: KMMergeViewType = .add { didSet { self.updateLanguage() } } var files: [KMFileAttribute] = [] { didSet { self.reloadData() } } var lockFiles: [KMFileAttribute] = [] { didSet { self.reloadData() } }//存在密码文件 var lockFilesIndex: Int = 0 var newPageSize: NSSize = .zero var insertRow: Int = 0 override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } override func setup() { self.box.cornerRadius = 4 pageSizeWidthTextField.isEnabled = false pageSizeHeightTextField.isEnabled = false; tableview.delegate = self tableview.dataSource = self tableview.allowsMultipleSelection = true tableview.registerForDraggedTypes([MyTableCellViewDataType, .fileURL, .string, .pdf]) // tableview.register(NSNib.init(nibNamed: "KMMergeTableViewCell", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMMergeTableViewCell")) progress.isHidden = true boxLabel.textColor = KMAppearance.Layout.h0Color() blankView.dragAction = { [weak self] filePaths in var theFileUrls: [URL] = [] for fileUrl in filePaths { if KMTools.isImageType(fileUrl.pathExtension) { self?.addImageFile(fileUrl: fileUrl) } else { theFileUrls.append(fileUrl) } } self?.addFilePaths(urls: theFileUrls) } blankView.mouseDownAction = { [unowned self] view in self.addFilesAction?(self) } } override func updateLanguage() { originalSizeButton.title = NSLocalizedString("Original Size", comment: "") A4SizeButton.title = "A4"; A3SizeButton.title = "A3"; USLetterSizeButton.title = NSLocalizedString("U.S.Letter", comment: ""); USLegalButton.title = NSLocalizedString("U.S.Legal", comment: ""); customSizeButton.title = NSLocalizedString("Custom", comment: ""); pageSizeWidthTextField.stringValue = "595"; pageSizeHeightTextField.stringValue = "841"; cancelButton.title = NSLocalizedString("Cancel", comment: ""); clearButton.title = NSLocalizedString("Clear", comment: ""); addFilesButton.title = NSLocalizedString("Add Files", comment: "") mergeButton.title = self.type == .add ? NSLocalizedString("Append", comment: ""): NSLocalizedString("Merge", comment: "") boxLabel.stringValue = NSLocalizedString("Page size:", comment: "") } override func reloadData() { self.updateButtonState() if files.count != 0 { self.blankView.isHidden = true } else { self.blankView.isHidden = false } self.tableview.reloadData() } func updateButtonState() { if (files.count >= 1) { blankView.isHidden = true clearButton.isEnabled = true mergeButton.isEnabled = true } else { blankView.isHidden = false clearButton.isEnabled = false mergeButton.isEnabled = false } } } extension KMMergeView { func controlTextDidChange(_ obj: Notification) { // NSTextField *textField = (NSTextField*)[obj object]; // NSInteger index = textField.tag; // [[_files objectAtIndex:index] setPagesString:textField.stringValue]; } } extension KMMergeView: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return files.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("KMMergeTableViewCell"), owner: self) if cell == nil { cell = KMMergeTableViewCell.init(frame: .zero) } let myCellView: KMMergeTableViewCell = cell! as! KMMergeTableViewCell if row < files.count { myCellView.fileModel = files[row] } myCellView.index = row + 1 // 配置单元格的显示内容 myCellView.removeAction = { [weak self] view, model in self?.files.removeObject(model) self?.reloadData() } return myCellView } func tableView(_ tableView: NSTableView, shouldSelect tableColumn: NSTableColumn?) -> Bool { return false } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { return 120 } } extension KMMergeView: NSTableViewDelegate { func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { let indexSetData = try? NSKeyedArchiver.archivedData(withRootObject: rowIndexes, requiringSecureCoding: true) pboard.declareTypes([NSPasteboard.PasteboardType.string], owner: self) pboard.setData(indexSetData, forType: MyTableCellViewDataType) return true } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { if dropOperation == .on { return [] } var isCanDrag = false var result = NSDragOperation.copy let pboard = info.draggingPasteboard if pboard.availableType(from: [NSPasteboard.PasteboardType.fileURL]) != nil { let filePath = pboard.propertyList(forType: NSPasteboard.PasteboardType.fileURL) as? String let url = URL(string: filePath!) if url!.path.lowercased().hasSuffix("pdf") { isCanDrag = true } else { if KMTools.isImageType(url?.pathExtension ?? "") { isCanDrag = true } else { isCanDrag = false } } } else if (pboard.availableType(from: [MyTableCellViewDataType]) != nil) { result = .every } if isCanDrag { result = .copy } return result } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { var result = false let pboard = info.draggingPasteboard insertRow = row // NSPasteboard.PasteboardType.fileURL if pboard.availableType(from: [NSPasteboard.PasteboardType.fileURL]) != nil { let filePath = pboard.propertyList(forType: NSPasteboard.PasteboardType.fileURL) as? String let url = URL(string: filePath!) var array = [URL]() array.append(url!) var theFileUrls: [URL] = [] for fileUrl in array { if KMTools.isImageType(fileUrl.pathExtension) { self.addImageFile(fileUrl: fileUrl) } else { theFileUrls.append(fileUrl) } } addFilePaths(urls: theFileUrls) result = true } else if pboard.availableType(from: [MyTableCellViewDataType]) != nil { guard let data = info.draggingPasteboard.data(forType: MyTableCellViewDataType), let rowIndexes = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? IndexSet else { return false } // 移动数据 var draggedItems: [KMFileAttribute] = [] for index in rowIndexes { draggedItems.append(files[index]) } for index in rowIndexes.reversed() { files.remove(at: index) } let insertionIndex = row > rowIndexes.first! ? row - rowIndexes.count : row for (index, item) in draggedItems.enumerated() { files.insert(item, at: insertionIndex + index) } tableView.reloadData() return true } else { result = false } return result } } extension KMMergeView { @IBAction func clearButtonAction(_ sender: Any) { self.files.removeAll() self.reloadData() guard let callBack = clearAction else { return } callBack(self) } @IBAction func cancelButtonAction(_ sender: Any) { guard let callBack = cancelAction else { return } callBack(self) } @IBAction func mergeButtonAction(_ sender: Any) { guard let callBack = mergeAction else { return } self.reloadData() callBack(self, self.files, self.newPageSize) } @IBAction func addFilesButtonAction(_ sender: Any) { guard let callBack = addFilesAction else { return } callBack(self) } @IBAction func sizeButtonAction(_ sender: NSButton) { originalSizeButton.state = .off A3SizeButton.state = .off A4SizeButton.state = .off USLetterSizeButton.state = .off USLegalButton.state = .off customSizeButton.state = .off sender.state = .on pageSizeHeightTextField.isEnabled = sender.isEqual(customSizeButton) pageSizeWidthTextField.isEnabled = sender.isEqual(customSizeButton) var size: NSSize = .zero switch sender.tag { case 0: break case 1: size = NSMakeSize(595, 841); break; case 2: size = NSMakeSize(841, 1190); break; case 3: size = NSMakeSize(612, 792); break; case 4: size = NSMakeSize(612, 1108); break; case 5: size = NSMakeSize(595, 841); pageSizeWidthTextField.stringValue = size.width.description pageSizeHeightTextField.stringValue = size.height.description break; default: break } self.newPageSize = size } } //MARK: public extension KMMergeView { func addFilePaths(urls: [URL]) { lockFiles.removeAll() // files.removeAll() for url in urls { let file = KMFileAttribute() file.filePath = url.path if file.isLocked { lockFiles.append(file) } else { var isExist = false for item in files { if item.filePath == file.filePath { isExist = true break } } if !isExist { files.append(file) } } } lockFilesIndex = 0 self.openPasswordFile { [weak self] success, resultPassword in self?.reloadData() } } func addImageFile(fileUrl: URL) { if let image = NSImage(contentsOf: fileUrl) { if let page = PDFPage(image: image) { let document = PDFDocument() document.insert(page, at: 0) var path = self._saveImagePath() + "/" + fileUrl.deletingPathExtension().lastPathComponent + ".pdf" path = KMTools.getUniqueFilePath(filePath: path) let result = document.write(toFile: path) if result { let file = KMFileAttribute() file.filePath = path file.oriFilePath = fileUrl.path file.myPDFDocument = document self.files.append(file) } } } else { Task { _ = await KMAlertTool.runModel(message: NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "")) } } } func openPasswordFile(completion: @escaping ((_ success: Bool, _ resultPassword: String) -> Void)) { if lockFiles.count != 0 { let file = lockFiles[lockFilesIndex] KMBaseWindowController.checkPassword(url: URL(fileURLWithPath: file.filePath), type: .owner) { [unowned self] success, resultPassword in if success { file.password = resultPassword lockFilesIndex = lockFilesIndex + 1 files.append(file) completion(true, "") if lockFiles.count > lockFilesIndex { self.openPasswordFile(completion: completion) } } else { completion(false, "") } } } else { completion(true, "") } } } //MARK: private extension KMMergeView { private func openFilePath(url: URL) { NSWorkspace.shared.activateFileViewerSelecting([url]) } private func isExistAtFilePath(filePath: String) -> Bool{ for file in self.files { if file.filePath == filePath { return true } } return false } private func _saveImagePath() -> String { let rootPath = KMDataManager.fetchAppSupportOfBundleIdentifierDirectory() let path = rootPath.appendingPathComponent("Merge").path if FileManager.default.fileExists(atPath: path) == false { try?FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false) } return path } }