KMBatchProcessingTableView.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. //
  2. // KMBatchProcessingTableView.swift
  3. // PDF Master
  4. //
  5. // Created by lizhe on 2022/11/18.
  6. //
  7. import Cocoa
  8. class KMBatchTableView: NSTableView {
  9. var mouseDownAction: (() -> Void)?
  10. // override func mouseDown(with event: NSEvent) {
  11. // super.mouseDown(with: event)
  12. // mouseDownAction?()
  13. // }
  14. }
  15. class KMBatchProcessingTableView: NSView {
  16. @IBOutlet var contentView: NSView!
  17. @IBOutlet weak var tableView: KMBatchTableView!
  18. var orderClickRow = -1
  19. let tableRowPasteboardType: NSPasteboard.PasteboardType = NSPasteboard.PasteboardType(rawValue: "private.table-row")
  20. lazy var presenter: KMBatchProcessingTableViewPresenter! = KMBatchProcessingTableViewPresenter()
  21. weak var delegate: KMBatchProcessingTableViewDelegate?
  22. var selectModels: [KMBatchProcessingTableViewModel] = []
  23. var inputType: KMBatchCollectionViewType = .convertPDF {
  24. didSet {
  25. self.reloadData()
  26. }
  27. }
  28. var isDrag = false
  29. /**
  30. @abstract 外部传入数据
  31. @param inputData 文件路劲
  32. */
  33. var inputData: [URL]! {
  34. didSet {
  35. self.presenter.addData(data: inputData)
  36. }
  37. }
  38. var isDisable = false
  39. //内部使用数据
  40. var data: [KMBatchProcessingTableViewModel] = []
  41. fileprivate var options: KMBatchProcessingTableViewOptions?
  42. override func draw(_ dirtyRect: NSRect) {
  43. super.draw(dirtyRect)
  44. // Drawing code here.
  45. }
  46. // MARK: 初始化
  47. public required init?(coder decoder: NSCoder) {
  48. super.init(coder: decoder)
  49. initContentView()
  50. setup()
  51. }
  52. override init(frame frameRect: NSRect) {
  53. super.init(frame: frameRect)
  54. initContentView()
  55. setup()
  56. }
  57. private func initContentView() {
  58. //绑定xib
  59. let resource = NSNib(nibNamed: String(describing: self.classForCoder.self),
  60. bundle: Bundle(for: self.classForCoder.self))!
  61. resource.instantiate(withOwner: self, topLevelObjects: nil)
  62. addSubview(contentView)
  63. contentView.translatesAutoresizingMaskIntoConstraints = false
  64. NSLayoutConstraint.activate([
  65. contentView.topAnchor.constraint(equalTo: topAnchor),
  66. contentView.leftAnchor.constraint(equalTo: leftAnchor),
  67. contentView.rightAnchor.constraint(equalTo: rightAnchor),
  68. contentView.bottomAnchor.constraint(equalTo: bottomAnchor)])
  69. contentView.updateConstraintsForSubtreeIfNeeded()
  70. }
  71. func setup() {
  72. self.tableView.dataSource = self
  73. self.tableView.delegate = self
  74. // self.tableView.allowsMultipleSelection = true
  75. self.tableView.registerForDraggedTypes([NSPasteboard.PasteboardType.string, NSPasteboard.PasteboardType.fileURL, self.tableRowPasteboardType]) //支持拖拽
  76. self.tableView.setDraggingSourceOperationMask([.copy, .delete], forLocal: false)
  77. self.tableView.mouseDownAction = { [weak self] in
  78. guard let self = self else { return }
  79. self.cancelAllSelect()
  80. }
  81. self.options = .all
  82. self.presenter.initPresenter(view: self, data: [])
  83. }
  84. func reloadData() {
  85. for _ in self.tableView.tableColumns {
  86. self.tableView.removeTableColumn(self.tableView.tableColumns[0])
  87. }
  88. if inputType == .imageToPDF {
  89. self.options = [.number, .name, .dimension, .size, .state]
  90. } else {
  91. self.options = [.number, .name, .order, .size, .state]
  92. }
  93. if (options!.contains(KMBatchProcessingTableViewOptions.number)) {
  94. let column = NSTableColumn()
  95. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  96. column.title = KMLocalizedString(" ", comment: "")
  97. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.number.rawValue))
  98. // column.resizingMask = .userResizingMask
  99. column.width = 40
  100. self.tableView.addTableColumn(column)
  101. }
  102. if (options!.contains(KMBatchProcessingTableViewOptions.name)) {
  103. let column = NSTableColumn()
  104. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  105. column.title = KMLocalizedString("File Name", comment: "")
  106. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.name.rawValue))
  107. // column.resizingMask = .userResizingMask
  108. if self.inputType == .imageToPDF {
  109. column.width = 220
  110. } else {
  111. column.width = self.canShowOrder() ? 180 : 344
  112. }
  113. self.tableView.addTableColumn(column)
  114. }
  115. if (options!.contains(KMBatchProcessingTableViewOptions.order) && self.canShowOrder()) {
  116. let column = NSTableColumn()
  117. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  118. column.title = KMLocalizedString("Page Range", comment: "")
  119. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.order.rawValue))
  120. column.resizingMask = .userResizingMask
  121. column.width = 164
  122. self.tableView.addTableColumn(column)
  123. }
  124. if (options!.contains(KMBatchProcessingTableViewOptions.dimension)) {
  125. let column = NSTableColumn()
  126. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  127. column.title = KMLocalizedString("Dimension", comment: "")
  128. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.dimension.rawValue))
  129. column.resizingMask = .userResizingMask
  130. column.width = 164
  131. self.tableView.addTableColumn(column)
  132. }
  133. if (options!.contains(KMBatchProcessingTableViewOptions.size)) {
  134. let column = NSTableColumn()
  135. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  136. column.title = KMLocalizedString("Size", comment: "")
  137. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.size.rawValue))
  138. column.resizingMask = .userResizingMask
  139. column.width = 88
  140. self.tableView.addTableColumn(column)
  141. }
  142. if (options!.contains(KMBatchProcessingTableViewOptions.state)) {
  143. let column = NSTableColumn()
  144. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  145. column.title = KMLocalizedString("Status", comment: "")
  146. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.state.rawValue))
  147. column.headerCell.textColor = NSColor.red
  148. column.resizingMask = .userResizingMask
  149. column.width = 56
  150. self.tableView.addTableColumn(column)
  151. }
  152. if (options!.contains(KMBatchProcessingTableViewOptions.delete)) {
  153. let column = NSTableColumn()
  154. column.headerCell = KMBatchProcessingColumnHeaderCell.init()
  155. column.title = KMLocalizedString("", comment: "")
  156. column.identifier = NSUserInterfaceItemIdentifier(String(KMBatchProcessingTableViewOptions.delete.rawValue))
  157. column.resizingMask = .userResizingMask
  158. column.width = 30
  159. self.tableView.addTableColumn(column)
  160. }
  161. self.tableView.reloadData()
  162. }
  163. func canShowOrder() -> Bool {
  164. if (self.inputType == .imageToPDF ||
  165. self.inputType == .security ||
  166. self.inputType == .compress ||
  167. self.inputType == .batchRemove) {
  168. return false
  169. } else {
  170. return true
  171. }
  172. }
  173. }
  174. extension KMBatchProcessingTableView: NSTableViewDelegate {
  175. func numberOfRows(in tableView: NSTableView) -> Int {
  176. return self.data.count
  177. }
  178. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  179. var cell: KMBatchProcessingTableCell?
  180. if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.number.rawValue)) {
  181. cell = KMBatchProcessingNumTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  182. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.name.rawValue)) {
  183. cell = KMBatchProcessingNameTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  184. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.order.rawValue)) {
  185. cell = KMBatchProcessingOrderTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  186. if cell != nil {
  187. (cell as? KMBatchProcessingOrderTableCell)!.cellAction = { [weak self] pageRange in
  188. self?.orderClickRow = row
  189. }
  190. }
  191. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.dimension.rawValue)) {
  192. cell = KMBatchProcessingWidthTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  193. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.size.rawValue)) {
  194. cell = KMBatchProcessingSizeTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  195. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.state.rawValue)) {
  196. cell = KMBatchProcessingStateTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  197. } else if (tableColumn?.identifier.rawValue == String(KMBatchProcessingTableViewOptions.delete.rawValue)) {
  198. cell = KMBatchProcessingDeleteTableCell.init(frame: CGRect(x: 0, y: 0, width: tableColumn!.width, height:tableView.rowHeight))
  199. cell?.action = { [weak self] (view) in
  200. guard let self = self else { return }
  201. self.presenter.deleteData(model: view.model)
  202. }
  203. }
  204. if(cell != nil) {
  205. if (self.data.count > row) {
  206. let model = self.data[row]
  207. model.type = self.inputType
  208. model.row = row + 1
  209. cell!.model = model
  210. }
  211. }
  212. return cell
  213. }
  214. func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
  215. //悬浮删除
  216. let tableRowView = KMBatchProcessingTableRowView()
  217. tableRowView.selectionHighlightStyle = .none
  218. tableRowView.model = self.data[row]
  219. tableRowView.hoverCallback = { [weak self] (mouseEntered, mouseBox) in
  220. guard let self = self else { return }
  221. for i in 0...self.data.count - 1 {
  222. let model = self.data[i]
  223. if model.hover == true {
  224. model.hover = false
  225. let columnIndex = self.tableView.column(withIdentifier: NSUserInterfaceItemIdentifier(KMBatchProcessingTableViewOptions.name.rawValue.description))
  226. self.tableView.reloadData(forRowIndexes: IndexSet(integer: i), columnIndexes: IndexSet(integer: columnIndex))
  227. if self.tableView.rowView(atRow: i, makeIfNecessary: false) is KMBatchProcessingTableRowView {
  228. let rowView: KMBatchProcessingTableRowView = self.tableView.rowView(atRow: i, makeIfNecessary: false) as! KMBatchProcessingTableRowView
  229. rowView.reloadData()
  230. }
  231. }
  232. if i == row {
  233. if mouseEntered {
  234. model.hover = true
  235. } else {
  236. model.hover = false
  237. }
  238. let columnIndex = self.tableView.column(withIdentifier: NSUserInterfaceItemIdentifier(KMBatchProcessingTableViewOptions.name.rawValue.description))
  239. self.tableView.reloadData(forRowIndexes: IndexSet(integer: i), columnIndexes: IndexSet(integer: columnIndex))
  240. if self.tableView.rowView(atRow: i, makeIfNecessary: false) is KMBatchProcessingTableRowView {
  241. let rowView: KMBatchProcessingTableRowView = self.tableView.rowView(atRow: i, makeIfNecessary: false) as! KMBatchProcessingTableRowView
  242. rowView.reloadData()
  243. }
  244. }
  245. }
  246. }
  247. tableRowView.rightMouseCallback = { [weak self] (view, event) in
  248. guard let self = self else { return }
  249. if self.selectModels.count <= 1 {
  250. self.didSelectItem(shouldSelectRow: row)
  251. }
  252. DispatchQueue.main.async {
  253. if self.tableView.rowView(atRow: row, makeIfNecessary: false) != nil {
  254. let rowView = self.tableView.rowView(atRow: row, makeIfNecessary: false)
  255. self.addRightMenu(view: rowView!, event: event)
  256. }
  257. }
  258. }
  259. return tableRowView
  260. }
  261. func tableView(_ tableView: NSTableView, shouldSelect tableColumn: NSTableColumn?) -> Bool {
  262. return true
  263. }
  264. func tableView(_ tableView: NSTableView, didClick tableColumn: NSTableColumn) {
  265. }
  266. func tableViewSelectionDidChange(_ notification: Notification) {
  267. let tableView: NSTableView = notification.object as? NSTableView ?? NSTableView()
  268. if tableView == self.tableView {
  269. // 获取所有选中的行索引
  270. let selectedIndexes = tableView.selectedRowIndexes
  271. if selectedIndexes.isEmpty {
  272. print("没有选中任何行")
  273. self.didSelectItems(indexs: IndexSet())
  274. } else {
  275. // 打印所有选中的行索引
  276. print("选中的行索引: \(selectedIndexes)")
  277. for index in selectedIndexes {
  278. print(index)
  279. }
  280. self.didSelectItems(indexs: selectedIndexes)
  281. }
  282. }
  283. }
  284. func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
  285. return true
  286. }
  287. func didSelectItems(indexs: IndexSet) {
  288. if !self.isDrag {
  289. for i in 0..<(self.data.count) {
  290. let item = self.data[i]
  291. if item.select {
  292. item.select = false
  293. self.tableView.reloadData(forRowIndexes: IndexSet(integer: i), columnIndexes: IndexSet())
  294. self.reloadRowData(index: i)
  295. }
  296. }
  297. self.selectModels.removeAll()
  298. for index in indexs {
  299. let model = self.data[index]
  300. model.select = true
  301. self.selectModels.append(model)
  302. self.tableView.reloadData(forRowIndexes: IndexSet(integer: index), columnIndexes: IndexSet())
  303. self.reloadRowData(index: index)
  304. }
  305. }
  306. }
  307. func reloadRowData(index: Int) {
  308. let columnIndex = self.tableView.column(withIdentifier: NSUserInterfaceItemIdentifier(KMBatchProcessingTableViewOptions.name.rawValue.description))
  309. self.tableView.reloadData(forRowIndexes: IndexSet(integer: index), columnIndexes: IndexSet(integer: columnIndex))
  310. if self.tableView.rowView(atRow: index, makeIfNecessary: false) is KMBatchProcessingTableRowView {
  311. let rowView: KMBatchProcessingTableRowView = self.tableView.rowView(atRow: index, makeIfNecessary: false) as! KMBatchProcessingTableRowView
  312. rowView.reloadData()
  313. }
  314. }
  315. func didSelectItem(shouldSelectRow row: Int) {
  316. self.didSelectItems(indexs: IndexSet(integer: row))
  317. }
  318. func cancelAllSelect() {
  319. for i in 0...self.data.count - 1 {
  320. let model = self.data[i]
  321. model.select = false
  322. model.hover = false
  323. let columnIndex = self.tableView.column(withIdentifier: NSUserInterfaceItemIdentifier(KMBatchProcessingTableViewOptions.name.rawValue.description))
  324. self.tableView.reloadData(forRowIndexes: IndexSet(integer: i), columnIndexes: IndexSet(integer: columnIndex))
  325. if self.tableView.rowView(atRow: i, makeIfNecessary: false) is KMBatchProcessingTableRowView {
  326. let rowView: KMBatchProcessingTableRowView = self.tableView.rowView(atRow: i, makeIfNecessary: false) as! KMBatchProcessingTableRowView
  327. rowView.reloadData()
  328. }
  329. }
  330. }
  331. }
  332. extension KMBatchProcessingTableView: NSTableViewDataSource {
  333. func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
  334. if self.inputType == .imageToPDF {
  335. return 80
  336. } else {
  337. return 40
  338. }
  339. }
  340. func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
  341. //自定义内部拖拽数据
  342. let data: Data = try! NSKeyedArchiver.archivedData(withRootObject: rowIndexes, requiringSecureCoding: true)
  343. pboard.declareTypes([self.tableRowPasteboardType], owner: self)
  344. pboard.setData(data, forType: self.tableRowPasteboardType)
  345. return true
  346. }
  347. func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
  348. guard dropOperation == .above else {
  349. return []
  350. }
  351. self.isDrag = true
  352. self.cancelAllSelect()
  353. if let source = info.draggingSource as? NSTableView, source === tableView {
  354. tableView.draggingDestinationFeedbackStyle = .gap
  355. } else {
  356. tableView.draggingDestinationFeedbackStyle = .regular
  357. }
  358. return .move
  359. }
  360. func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
  361. var result: Bool = false;
  362. let pboard: NSPasteboard = info.draggingPasteboard
  363. if ((pboard.availableType(from: [NSPasteboard.PasteboardType.fileURL])) != nil) {
  364. //获取url
  365. var array: [URL] = []
  366. for item: NSPasteboardItem in pboard.pasteboardItems! {
  367. let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)!
  368. let url = NSURL(string: string)
  369. array.append(url! as URL)
  370. }
  371. //添加url
  372. self.presenter.insetData(data: array, index: row)
  373. result = true
  374. } else if ((pboard.availableType(from: [self.tableRowPasteboardType])) != nil) {
  375. //获取初始数据
  376. let rowData: Data = pboard.data(forType: self.tableRowPasteboardType)!
  377. let rowIndexes: NSIndexSet = try!NSKeyedUnarchiver.unarchivedObject(ofClass: NSIndexSet.self, from: rowData)!
  378. if info.draggingSource as? NSTableView == tableView {
  379. //数据处理
  380. self.data.move(with: IndexSet(rowIndexes), to: row)
  381. /**
  382. 数据刷新
  383. */
  384. tableView.beginUpdates()
  385. tableView.reloadData()
  386. tableView.endUpdates()
  387. }
  388. result = true
  389. }
  390. self.isDrag = false
  391. return result
  392. }
  393. func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
  394. // Handle items dragged to Trash
  395. tableView.draggingDestinationFeedbackStyle = .none
  396. DispatchQueue.main.async {
  397. self.isDrag = false
  398. }
  399. }
  400. }
  401. extension KMBatchProcessingTableView: KMBatchProcessingTableViewPresenterDelegate {
  402. func showData(presenter: KMBatchProcessingTableViewPresenter, data: [KMBatchProcessingTableViewModel]) {
  403. self.data = data
  404. self.tableView.reloadData()
  405. self.delegate?.reloadData(data: data)
  406. }
  407. }
  408. extension Array {
  409. mutating func move(from start: Index, to end: Index) {
  410. guard (0..<count) ~= start, (0...count) ~= end else { return }
  411. if start == end { return }
  412. let targetIndex = start < end ? end - 1 : end
  413. insert(remove(at: start), at: targetIndex)
  414. }
  415. mutating func move(with indexes: IndexSet, to toIndex: Index) {
  416. let movingData = indexes.map{ self[$0] }
  417. let targetIndex = toIndex - indexes.filter{ $0 < toIndex }.count
  418. for (i, e) in indexes.enumerated() {
  419. remove(at: e - i)
  420. }
  421. insert(contentsOf: movingData, at: targetIndex)
  422. }
  423. }
  424. protocol KMBatchProcessingTableViewDelegate: NSObject {
  425. func reloadData(data: [KMBatchProcessingTableViewModel])
  426. }
  427. extension KMBatchProcessingTableView {
  428. func addRightMenu(view: NSView, event: NSEvent) {
  429. if self.isDisable {
  430. return
  431. }
  432. let menus = NSMenu()
  433. menus.addItem(withTitle: KMLocalizedString("Show in Finder", comment: ""), action: #selector(showInFinder), target: self)
  434. menus.addItem(withTitle: KMLocalizedString("Remove", comment: ""), action: #selector(removeItem), target: self)
  435. menus.addItem(withTitle: KMLocalizedString("Select All", comment: ""), action: #selector(selectAllFiles), target: self)
  436. let point = view.convert(event.locationInWindow, from: nil)
  437. menus.popUp(positioning: nil, at: point, in: view)
  438. }
  439. @objc func showInFinder() {
  440. if self.selectModels.count != 0 {
  441. for model in self.selectModels {
  442. if FileManager.default.fileExists(atPath: model.filePath) {
  443. NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: model.filePath)])
  444. }
  445. }
  446. }
  447. }
  448. @objc func removeItem() {
  449. self.presenter.deleteData(models: self.selectModels)
  450. }
  451. @objc func selectAllFiles() {
  452. for model in data {
  453. model.select = true
  454. self.selectModels.append(model)
  455. }
  456. self.tableView.reloadData()
  457. }
  458. }
  459. extension KMBatchProcessingTableView{
  460. func canSelect(row: Int) -> Bool {
  461. if row < data.count {
  462. if !self.isDrag && (self.orderClickRow != row && data[row].pageRange == .custom) {
  463. return true
  464. } else {
  465. return false
  466. }
  467. } else {
  468. return false
  469. }
  470. }
  471. }