// // KMBatchTableCellView.swift // PDF Reader Pro // // Created by kdanmobile on 2023/10/27. // import Cocoa typealias batchTableCellComboboxMouseDownCallback = (_ mouseDown: Bool) -> () typealias batchTableCellRemoveFileCallBack = (_ file: KMBatchOperateFile) -> () @objc enum KMBatchTableCellViewType: Int { case Size = 0 case PageRange case FileName case Status case DPI case Dimensions } class KMBatchTableCellCombobox: KMComboBox{ var mouseDownCallback: batchTableCellComboboxMouseDownCallback? override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) if (self.mouseDownCallback != nil) { self.mouseDownCallback!(true) } } } class KMBatchTableCellView: NSTableCellView,NSComboBoxDelegate{ var removeFileCallBack: batchTableCellRemoveFileCallBack? var pageRangeCombobox: KMBatchTableCellCombobox? var errorTextField: NSTextField? var statusView: NSView? var removeButton: KMButton? var progressIndicator: NSProgressIndicator? var type: KMBatchTableCellViewType? var file: KMBatchOperateFile? var DPIComboBox: KMBatchTableCellCombobox? var indicateImageView: NSImageView? var comboBoxContent: String? deinit { DistributedNotificationCenter.default().removeObserver(self) } convenience init(type: KMBatchTableCellViewType) { self.init() switch type { case .Size: configuUIForSize() case .Dimensions: configuUIForSize() case .PageRange: configuUIForPageRange() case .FileName: configuUIForFileName() case .Status: configuUIForStatus() case .DPI: configuUIForDPI() default: break } DistributedNotificationCenter.default().addObserver(self, selector: #selector(themeChanged(notification:)), name: NSNotification.Name("AppleInterfaceThemeChangedNotification"), object: nil) self.type = type } func configuUIForSize() { let tf = NSTextField(frame: .zero) self.textField = tf self.textField?.isBordered = false self.textField?.drawsBackground = true self.textField?.backgroundColor = NSColor.clear self.textField?.isEditable = false self.addSubview(self.textField!) self.textField?.mas_makeConstraints({ make in make?.top.equalTo()(self)?.offset()(13) make?.left.equalTo()(self) }) self.textField?.textColor = KMAppearance.Layout.h0Color() self.textField?.font = NSFont.systemFont(ofSize: 12) } func configuUIForPageRange() { self.pageRangeCombobox = KMBatchTableCellCombobox(frame: .zero) self.pageRangeCombobox?.focusRingType = .none self.pageRangeCombobox?.type = .none self.pageRangeCombobox?.wantsLayer = true self.pageRangeCombobox?.layer?.backgroundColor = KMAppearance.Layout.l1Color().cgColor self.pageRangeCombobox?.layer?.borderWidth = 1.0 self.pageRangeCombobox?.layer?.borderColor = KMAppearance.Interactive.s0Color().cgColor self.pageRangeCombobox?.layer?.cornerRadius = 1.0 self.pageRangeCombobox?.backgroundColor = KMAppearance.Layout.l1Color() self.addSubview(self.pageRangeCombobox!) self.pageRangeCombobox?.mas_makeConstraints({ make in make?.left.equalTo()(self) make?.right.equalTo()(self)?.offset()(-20) make?.top.equalTo()(self)?.offset()(10) }) let choiceArray = [ " \(NSLocalizedString("All Pages", comment: ""))", " \(NSLocalizedString("Odd Pages Only", comment: ""))", " \(NSLocalizedString("Even Pages Only", comment: ""))", " \(NSLocalizedString("e.g. 1,3-5,10", comment: ""))" ] self.pageRangeCombobox?.addItems(withObjectValues: choiceArray) let placeholderString = " \(NSLocalizedString("e.g. 1,3-5,10", comment: ""))" // self.pageRangeCombobox!.cell.placeholderString = placeholderString self.pageRangeCombobox?.selectItem(at: 0) self.pageRangeCombobox?.isEditable = false self.pageRangeCombobox?.delegate = self self.comboBoxContent = placeholderString self.updateViewColor() self.pageRangeCombobox?.mouseDownCallback = { [weak self] mouseDown in let itemIdex = self?.pageRangeCombobox?.indexOfSelectedItem ?? 0 if itemIdex == 3 {//KMBatchOperatePageChoice.Input if self?.comboBoxContent == placeholderString { self?.pageRangeCombobox?.stringValue = "" } else { self?.pageRangeCombobox?.stringValue = (self?.comboBoxContent) ?? "" } } } } func configuUIForFileName() { var tf = NSTextField(frame: .zero) self.textField = tf self.textField?.isBordered = false self.textField?.drawsBackground = true self.textField?.backgroundColor = NSColor.clear self.textField?.isEditable = false self.textField?.maximumNumberOfLines = 1 self.textField?.lineBreakMode = .byTruncatingMiddle self.addSubview(self.textField!) self.textField?.mas_makeConstraints({ make in make?.top.equalTo()(self)?.offset()(13) make?.left.equalTo()(self) make?.right.equalTo()(self)?.offset()(-10) }) self.textField?.font = NSFont.systemFont(ofSize: 12) self.textField?.textColor = KMAppearance.Layout.h0Color() self.errorTextField = NSTextField(frame: .zero) self.errorTextField?.isBordered = false self.errorTextField?.drawsBackground = true self.errorTextField?.backgroundColor = NSColor.clear self.errorTextField?.isEditable = false self.errorTextField?.font = NSFont.systemFont(ofSize: 12) self.errorTextField?.textColor = KMAppearance.Status.errColor() self.errorTextField?.lineBreakMode = .byTruncatingMiddle self.addSubview(self.errorTextField!) self.errorTextField?.mas_makeConstraints({ make in make?.top.equalTo()(self.textField?.mas_bottom) make?.left.equalTo()(self) make?.bottom.equalTo()(self)?.offset()(-11) make?.right.equalTo()(self.mas_right) }) } func configuUIForStatus() { self.statusView = NSView(frame: .zero) self.statusView?.wantsLayer = true self.addSubview(self.statusView!) self.statusView?.mas_makeConstraints({ make in make?.left.equalTo()(self) make?.top.equalTo()(self)?.offset()(12) make?.width.equalTo()(16) make?.height.equalTo()(16) }) self.indicateImageView = NSImageView(frame: .zero) self.statusView?.addSubview(self.indicateImageView!) self.indicateImageView?.mas_makeConstraints({ make in make?.edges.equalTo()(self.statusView) }) self.progressIndicator = NSProgressIndicator(frame: .zero) self.statusView?.addSubview(self.progressIndicator!) self.progressIndicator?.mas_makeConstraints({ make in make?.edges.equalTo()(self.statusView) }) self.progressIndicator?.maxValue = 1.0 self.progressIndicator?.minValue = 0 self.progressIndicator?.style = .spinning self.progressIndicator?.controlSize = .small self.removeButton = KMButton(image: NSImage(named: KMImageNameUXIconBtnCloseNor)!, target: self, action: #selector(deleteFile(sender:))) self.removeButton?.isBordered = false self.removeButton?.isHidden = true self.addSubview(self.removeButton!) self.removeButton?.mas_makeConstraints({ make in // make?.left.equalTo()(self)?.offset()(10) make?.centerY.equalTo()(self) make?.right.equalTo()(self)?.offset()(0) make?.width.equalTo()(20) make?.height.equalTo()(20) }) self.removeButton!.mouseMoveCallback = { [weak self] mouseEntered in if mouseEntered { self?.removeButton?.image = NSImage(named: KMImageNameUXIconBtnCloseHov) } else { self?.removeButton?.image = NSImage(named: KMImageNameUXIconBtnCloseNor) } } } func configuUIForDPI() { self.DPIComboBox = KMBatchTableCellCombobox(frame: .zero) self.DPIComboBox?.focusRingType = .none self.DPIComboBox?.type = .none self.DPIComboBox?.wantsLayer = true self.DPIComboBox?.layer?.backgroundColor = KMAppearance.Layout.l1Color().cgColor self.DPIComboBox?.layer?.borderWidth = 1.0 self.DPIComboBox?.layer?.borderColor = KMAppearance.Interactive.s0Color().cgColor self.DPIComboBox?.layer?.cornerRadius = 1.0 self.DPIComboBox?.backgroundColor = KMAppearance.Layout.l1Color() self.addSubview(self.DPIComboBox!) let DPIArray = ["50 dpi", "72 dpi", "96 dpi", "150 dpi", "300 dpi", "600 dpi"] self.DPIComboBox?.addItems(withObjectValues: DPIArray) self.DPIComboBox?.selectItem(at: 0) self.DPIComboBox?.isEditable = false self.DPIComboBox?.delegate = self self.DPIComboBox?.mas_makeConstraints({ make in make?.left.equalTo()(self) make?.right.equalTo()(self)?.offset()(-20) make?.top.equalTo()(self)?.offset()(11) make?.bottom.equalTo()(self)?.offset()(-11) }) } @objc func themeChanged(notification: NSNotification) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.updateViewColor() } } @objc func deleteFile(sender: Any) { if (self.removeFileCallBack != nil) { self.removeFileCallBack!(self.file!) } } func updateViewColor() { if KMAppearance.isDarkMode() { self.DPIComboBox?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1) self.DPIComboBox?.layer!.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.DPIComboBox?.layer!.borderColor = NSColor(red: 0.337, green: 0.345, blue: 0.353, alpha: 1).cgColor self.pageRangeCombobox?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1) self.pageRangeCombobox?.layer!.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.pageRangeCombobox?.layer!.borderColor = NSColor(red: 0.337, green: 0.345, blue: 0.353, alpha: 1).cgColor } else { self.DPIComboBox?.backgroundColor = NSColor.white self.DPIComboBox?.layer!.backgroundColor = NSColor.white.cgColor self.DPIComboBox?.layer!.borderColor = NSColor(red: 0.855, green: 0.859, blue: 0.871, alpha: 1).cgColor self.pageRangeCombobox?.backgroundColor = NSColor.white self.pageRangeCombobox?.layer!.backgroundColor = NSColor.white.cgColor self.pageRangeCombobox?.layer!.borderColor = NSColor(red: 0.855, green: 0.859, blue: 0.871, alpha: 1).cgColor } } func updateInterface(_ file: KMBatchOperateFile) { self.file = file if self.type == .Size { self.textField?.stringValue = file.sizeString } else if self.type == .FileName { if (file.error != nil) { self.errorTextField?.stringValue = file.error!.localizedDescription self.errorTextField?.isHidden = false } else { self.errorTextField?.isHidden = true } self.textField?.stringValue = file.filePath.lastPathComponent } else if self.type == .PageRange { self.pageRangeCombobox?.isEnabled = self.file!.status != .processing self.pageRangeCombobox?.delegate = nil self.pageRangeCombobox?.selectItem(at: file.currentOperateInfo?.pageChoice.rawValue ?? 0) self.pageRangeCombobox?.isEditable = false if file.currentOperateInfo?.pageChoice == .Input { self.pageRangeCombobox?.isEditable = true self.pageRangeCombobox?.stringValue = file.currentOperateInfo?.pageRangeString ?? "" } self.pageRangeCombobox?.delegate = self } else if self.type == .Status { if file.status == .Waiting { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: KMImageNameUXIconProgressWaiting) } else if file.status == .Success { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: "KMImageNameUXIconProgressComplete") } else if file.status == .Failed { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: "KMImageNameUXIconProgressFailure") } else if file.status == .processing { self.indicateImageView?.isHidden = true self.progressIndicator?.isHidden = false self.progressIndicator?.startAnimation(nil) } else { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: KMImageNameUXIconProgressWaiting) } } else if self.type == .DPI { switch self.file?.dpi { case 50: self.DPIComboBox?.selectItem(at: 0) case 72: self.DPIComboBox?.selectItem(at: 1) case 96: self.DPIComboBox?.selectItem(at: 2) case 150: self.DPIComboBox?.selectItem(at: 3) case 300: self.DPIComboBox?.selectItem(at: 4) case 600: self.DPIComboBox?.selectItem(at: 5) default: break } self.DPIComboBox?.isEnabled = self.file?.status != .processing } else if self.type == .Dimensions { } } func updateInterface( file: KMBatchOperateFile, progress: Float) { updateInterface(file) if progress > 0 && file.status == .processing { self.progressIndicator?.doubleValue = Double(progress) self.progressIndicator?.startAnimation(nil) self.progressIndicator?.isIndeterminate = false } } func updateInterface(isProgress progress: Int) { if progress == -1 { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: "KMImageNameUXIconProgressFailure") } else if progress == 0 { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: KMImageNameUXIconProgressWaiting) } else { self.indicateImageView?.isHidden = false self.progressIndicator?.isHidden = true self.indicateImageView?.image = NSImage(named: "KMImageNameUXIconProgressComplete") } } func controlTextDidEndEditing(_ obj: Notification) { if let data = self.pageRangeCombobox?.isEqual(obj.object), data { self.file?.currentOperateInfo?.pageRangeString = self.pageRangeCombobox!.stringValue if self.file?.currentOperateInfo?.pagesArray == nil { let alert = NSAlert() alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) alert.alertStyle = .critical alert.messageText = self.file!.filePath.lastPathComponent.lastPathComponent + NSLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: "") alert.beginSheetModal(for: self.window!, completionHandler: nil) self.pageRangeCombobox?.selectItem(at: 0) self.file?.currentOperateInfo?.pageChoice = .All } } } func controlTextDidChange(_ obj: Notification) { let comboBox: NSComboBox = obj.object as! NSComboBox; if comboBox.isEqual(self.pageRangeCombobox) { self.comboBoxContent = comboBox.stringValue; } } func comboBoxSelectionDidChange(_ notification: Notification) { if notification.object as? NSComboBox == self.pageRangeCombobox { self.pageRangeCombobox?.delegate = nil self.file?.currentOperateInfo!.pageChoice = self.tromsformPageChoice(index: self.pageRangeCombobox?.indexOfSelectedItem ?? 0) if self.pageRangeCombobox?.indexOfSelectedItem == KMBatchOperatePageChoice.Input.rawValue { self.pageRangeCombobox?.isEditable = true self.pageRangeCombobox?.isSelectable = true self.window?.makeFirstResponder(self.pageRangeCombobox) } else { self.pageRangeCombobox?.resignFirstResponder() self.pageRangeCombobox?.isEditable = false self.pageRangeCombobox?.isSelectable = false } self.pageRangeCombobox?.delegate = self } else if notification.object as? NSComboBox == self.DPIComboBox { switch self.DPIComboBox?.indexOfSelectedItem { case 0: self.file?.currentConvertParameter?.dpi = 50 case 1: self.file?.currentConvertParameter?.dpi = 72 case 2: self.file?.currentConvertParameter?.dpi = 96 case 3: self.file?.currentConvertParameter?.dpi = 150 case 4: self.file?.currentConvertParameter?.dpi = 300 case 5: self.file?.currentConvertParameter?.dpi = 600 default: break } } } func tromsformPageChoice(index: Int) -> KMBatchOperatePageChoice { var pageChoice: KMBatchOperatePageChoice? switch index { case 0: pageChoice = .All case 1: pageChoice = .Odd case 2: pageChoice = .Even case 3: pageChoice = .Input default: pageChoice = .All } return pageChoice ?? .All } }