// // KMBatchOperateLeftViewController.swift // PDF Reader Pro // // Created by kdanmobile on 2023/10/26. // import Cocoa let KMBatchDragType = "KMBatchDragType" class KMBatchOperateLeftViewController: NSViewController,NSTableViewDelegate,NSTableViewDataSource{ @IBOutlet var selectFileButton: KMLongerButton! @IBOutlet var deleteFileButton: NSButton! @IBOutlet var tableView: NSTableView! @IBOutlet var indexTableColumn: NSTableColumn! @IBOutlet var fileNameTableColumn: NSTableColumn! @IBOutlet var pageRangeTableColumn: NSTableColumn! @IBOutlet var sizeTableColumn: NSTableColumn! @IBOutlet var stateTableColumn: NSTableColumn! @IBOutlet var DPIColumn: NSTableColumn! @IBOutlet var dimensionsTableColumn: NSTableColumn! @IBOutlet var headerOperateView: NSView! @IBOutlet var showInFinderMenuItem: NSMenuItem! @IBOutlet var deleteMenuItem: NSMenuItem! @IBOutlet var blankView: KMBlankView! var files: [KMBatchOperateFile] = [] var type: KMBatchOperationType? var lockFilePathArr: NSMutableArray? var lockFileIndex: Int = 0 var popOver: NSPopover? var progressInt: Int = 0 deinit { NotificationCenter.default.removeObserver(self) DistributedNotificationCenter.default().removeObserver(self) } override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self.tableView!, selector: #selector(self.reloadData), name: NSNotification.Name(kNeedChangePageRangeNotification), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(imageToPDFSuccessNotification(aNotification:)), name: NSNotification.Name("KMBatchOperateImageToPDFSuccessNotification"), object: nil) DistributedNotificationCenter.default.addObserver(self, selector: #selector(themeChanged(notification:)), name: NSNotification.Name("AppleInterfaceThemeChangedNotification"), object: nil) for i in 0..= 1 { let winC = KMProductCompareWC.shared if type == .CreatePDF { winC.kEventName = "Onbrd_ImagetoPDF_BuyNow" } KMMemberInfo.shared.advancedFunctionUsage() return } } #endif self.progressInt = 0 let panel = NSOpenPanel() if type == .CreatePDF { panel.allowedFileTypes = KMImageToPDFMethod.supportedImageTypes() } else { panel.allowedFileTypes = ["pdf", "PDF"] } if KMMemberInfo.shared.isMemberAllFunction { panel.allowsMultipleSelection = true panel.canChooseDirectories = true } panel.canChooseFiles = true #if VERSION_FREE #else panel.message = NSLocalizedString("To select multiple files press cmd ⌘ button on keyboard and click on the target files one by one.", comment: "") #endif panel.beginSheetModal(for: self.view.window!) { (result) in if result.rawValue == NSApplication.ModalResponse.OK.rawValue { let addArray = NSMutableArray() for fileURL in panel.urls { let filePath = fileURL.path var isDirectory = ObjCBool(false) FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) if isDirectory.boolValue { let containFiles = self.fetchAvailableFiles(in: filePath) addArray.addObjects(from: containFiles) } else { let typeIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, filePath.customPathExtension as CFString, nil) let TypeCFString = typeIdentifier!.takeRetainedValue() as String if self.type == .CreatePDF { if UTTypeConformsTo(TypeCFString as CFString, kUTTypeImage) { addArray.add(filePath) } } else { if UTTypeConformsTo(TypeCFString as CFString, kUTTypePDF) { addArray.add(filePath) } } } } self.addFilesToList(addArray: addArray) } } } func fetchAvailableFiles(in folderPath: String) -> [String] { let array = NSMutableArray() let subFileArray = (FileManager.default.subpaths(atPath: folderPath)! as NSArray).mutableCopy() as? NSMutableArray for i in 0.. 0 { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.openPasswordWindow() } } } func openPasswordWindow() { if lockFilePathArr!.count > lockFileIndex { let filePath = lockFilePathArr![lockFileIndex] as! String KMBaseWindowController.checkPassword(url: URL(fileURLWithPath: filePath), type: .owner) { [unowned self] success, resultPassword in self.closePwd() if !resultPassword.isEmpty { let file = KMBatchOperateFile(filePath: filePath, type: self.type!) file.password = resultPassword self.files.append(file) self.tableView.reloadData() } self.lockFileIndex += 1 if self.lockFilePathArr?.count ?? 0 > self.lockFileIndex { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.openPasswordWindow() } } else { self.tableView.reloadData() } } } } func closePwd() { self.km_endSheet() } func chooseFileFromCamera() { self.progressInt = 0 let vc = KMDeviceBrowserWindowController.shared vc.type = .camera vc.importCameraFileCallback = { [weak self](url: NSURL) -> Void in self?.addFilesToList(addArray: [url.path!]) } vc.showWindow(nil) } func chooseFileFromScanner() { self.progressInt = 0 let vc = KMDeviceBrowserWindowController.shared vc.type = .scanner vc.importScannerFileCallback = { [weak self](url: NSURL) -> Void in self?.addFilesToList(addArray: [url.path!]) } vc.showWindow(nil) } @IBAction func buttonClicked_SelectFile(_ sender: Any) { } @IBAction func blankViewAddFileAction(_ sender: Any) { self.chooseFile() } @IBAction func buttonClicked_DeleteFile(_ sender: Any) { let set = self.tableView.selectedRowIndexes let newArr: NSMutableArray = NSMutableArray.init(array: self.files) let arr = newArr.objects(at: set) if arr.count > 0 { newArr.removeObjects(in: arr) self.files = newArr as! [KMBatchOperateFile] self.tableView.reloadData() } else { // if KMAlertWindowController.needShowRemoveAllFilesHint() { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Clear All Recents", comment: "")+"?" alert.addButton(withTitle: NSLocalizedString("Delete", comment: "")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) alert.beginSheetModal(for: self.view.window!) {[weak self] response in if response == NSApplication.ModalResponse.alertFirstButtonReturn { self?.files.removeAll() self?.tableView.reloadData() } } // } else { // self.files.removeAll() // self.tableView.reloadData() // } } } func fetchAvailableFilesInFolder(_ folderPath: String) -> [String] { var array: [String] = [] let subFileArray = FileManager().subpaths(atPath: folderPath) for i in 0.. Bool { var containFile = false guard let subFileArray = FileManager.default.subpaths(atPath: folderPath) else { return containFile } for i in 0.. Int { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMBatchFilesCountNotification"), object: self.files, userInfo: [kObjectKey : self]) self.deleteFileButton.isEnabled = self.files.count > 0 self.blankView.isHidden = self.files.count > 0 return self.files.count; } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let identifier = tableColumn?.identifier let file = self.files[row] if identifier?.rawValue == "index" { let cellView = KMBatchTableCellView(type: .Size) _ = row + 1 cellView.textField?.stringValue = String(format: "%d", row + 1); return cellView; } else if identifier?.rawValue == "pageRange"{ if (file.currentOperateType == .CreatePDF) { let cellView = KMBatchTableCellView(type: .Size) if (file.fileType == .Image) { let image = NSImage(contentsOfFile: file.filePath) cellView.textField?.stringValue = String(Int(image?.size.width ?? 0)) + "x" + String(Int(image?.size.height ?? 0)) } return cellView; } else { let cellView = KMBatchTableCellView(type: .PageRange) cellView.updateInterface(file) return cellView; } } else if identifier?.rawValue == "status"{ let cellView = KMBatchTableCellView(type: .Status) if (file.currentOperateType == .CreatePDF) { cellView.updateInterface(file: file, progress: file.progress ?? 0) } else { cellView.updateInterface(file: file, progress: file.progress ?? 0) } cellView.removeFileCallBack = { [weak self] file in self?.files.removeObject(file) self?.tableView.reloadData() } return cellView; } else if identifier?.rawValue == "size"{ let cellView = KMBatchTableCellView(type: .Size) cellView.updateInterface(file) return cellView; } else if identifier?.rawValue == "fileName"{ let cellView = KMBatchTableCellView(type: .FileName) cellView.updateInterface(file) let rowView: KMBatchTableRowView = tableView.rowView(atRow: row, makeIfNecessary: true) as! KMBatchTableRowView if ((file.error) != nil) { rowView.backgroundColor = KMAppearance.Status.errBGColor(); } return cellView; } else if identifier?.rawValue == "dpi"{ let cellView = KMBatchTableCellView(type: .DPI) cellView.updateInterface(file) return cellView; } else if identifier?.rawValue == "dimensions"{ let cellView = KMBatchTableCellView(type: .Size) if (file.fileType == .Image) { let image = NSImage(contentsOfFile: file.filePath) cellView.textField?.stringValue = String(Int(image?.size.width ?? 0)) + "x" + String(Int(image?.size.height ?? 0)) } return cellView; } return nil; } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { let file = self.files[row] if self.type == .CreatePDF { if (file.error != nil) { return 64 } else { return 40 } } else { if file.fileType == .PDF { if (file.error != nil) { return 64 } else { return 40 } } else { return 0.0000001 } } } func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { let rowView = KMBatchTableRowView() return rowView } 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: NSPasteboard.PasteboardType(rawValue: KMBatchDragType) ) return true } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { var isCanDrag = false var result: NSDragOperation = [] let pboard = info.draggingPasteboard if pboard.availableType(from: [.fileURL]) != nil { guard let pbItems = pboard.pasteboardItems else { return NSDragOperation(rawValue: 0) } var hasValidFile = false for item in pbItems { guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else { continue } var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: _url.path, isDirectory: &isDirectory) if fileExists && isDirectory.boolValue { let containFile = folderContainAvailableFile(_url.path) if containFile { isCanDrag = true break } } else { if let UTType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, _url.pathExtension as CFString, nil)?.takeRetainedValue() as String? { if self.type == .CreatePDF { if UTTypeConformsTo(UTType as CFString, kUTTypeImage) { isCanDrag = true } } else { if UTTypeConformsTo(UTType as CFString, kUTTypePDF) { isCanDrag = true } } } if isCanDrag { break } } } if isCanDrag { result = .copy } return result } else { return .move } } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { let pboard = info.draggingPasteboard if pboard.availableType(from: [.init(KMBatchDragType)]) != nil { guard let rowData = pboard.data(forType: .init(KMBatchDragType)), let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: rowData) as? NSIndexSet else { return false } let backUpArray = self.files var referenceIndex = -1 rowIndexes.enumerate(options: .reverse, using: { (idx, stop) in let sortFile = backUpArray[idx] if referenceIndex < 0 { if idx > row { self.files.remove(at: idx) self.files.insert(sortFile, at: row) } else if idx < row { if row > self.files.count { self.files.remove(at: idx) self.files.append(sortFile) } else { self.files.remove(at: idx) self.files.insert(sortFile, at: row - 1) } } referenceIndex = self.files.firstIndex(of: sortFile) ?? -1 } else { let currentIndex = self.files.firstIndex(of: sortFile) ?? -1 if currentIndex > referenceIndex { self.files.remove(at: currentIndex) self.files.insert(sortFile, at: referenceIndex) } else { referenceIndex -= 1 self.files.remove(at: currentIndex) self.files.insert(sortFile, at: referenceIndex) } } }) self.tableView.reloadData() return true } else if pboard.availableType(from: [.fileURL]) != nil { if (pboard.pasteboardItems == nil) { return true } let allFilesArray = NSMutableArray() for item in pboard.pasteboardItems! { let fileURL = item.string(forType: .fileURL) if (fileURL == nil) { continue } let path = URL.init(string: fileURL!) if (path == nil) { continue } var isFolder = ObjCBool(false) FileManager.default.fileExists(atPath: path!.path, isDirectory: &isFolder) if isFolder.boolValue { let containFiles = fetchAvailableFilesInFolder(path!.path) allFilesArray.addObjects(from: containFiles) } else { allFilesArray.add(path!.path) } } var insertArray = NSMutableArray() lockFilePathArr?.removeAllObjects() lockFileIndex = 0 for i in 0.. 0 { let firstObject = insertArray.firstObject insertArray.removeAllObjects() insertArray.add(firstObject as Any) } else { KMMemberInfo.shared.advancedFunctionUsage() return false } } for i in 0.. 0 { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.openPasswordWindow() } } return true } return false } func switchToOperateType(_ operateType: KMBatchOperationType, files: [KMBatchOperateFile]) { switchToOperateType(operateType) self.files = files self.tableView.reloadData() } func checkNeedPasswordSwitchToOperateType(_ operateType: KMBatchOperationType, files: [KMBatchOperateFile]) { switchToOperateType(operateType) var arr: [String] = [] for i in 0.. Bool { let action = menuItem.action if action == #selector(menuItem_ShowInFinder(_:)) || action == #selector(menuItem_Delete(_:)) { if tableView.clickedRow == -1 { return false } return true } return true } }