KMBotaTableView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. //
  2. // KMTableView.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/11/16.
  6. //
  7. import Cocoa
  8. @objc protocol KMBotaTableViewDelegate: NSTableViewDelegate {
  9. @objc optional func tableView(_ aTableView: NSTableView, deleteRowsWithIndexes rowIndexes: IndexSet)
  10. @objc optional func tableView(_ aTableView: NSTableView, canDeleteRowsWithIndexes rowIndexes: IndexSet) -> Bool
  11. @objc optional func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet)
  12. @objc optional func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool
  13. @objc optional func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard)
  14. @objc optional func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool
  15. @objc optional func tableViewMoveLeft(_ aTableView: NSTableView)
  16. @objc optional func tableViewMoveRight(_ aTableView: NSTableView)
  17. @objc optional func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject?
  18. @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray
  19. @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String)
  20. @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String)
  21. }
  22. class KMBotaTableView: NSTableView {
  23. var kmTrackingAreas: NSMutableSet?
  24. var supportsQuickLook = false
  25. var typeSelectHelper: SKTypeSelectHelper?
  26. weak var botaDelegate: KMBotaTableViewDelegate? {
  27. didSet {
  28. self._rebuildTrackingAreas()
  29. }
  30. }
  31. var hasImageToolTips: Bool {
  32. get {
  33. return self.kmTrackingAreas != nil
  34. }
  35. set {
  36. if self.kmTrackingAreas == nil && newValue {
  37. self.kmTrackingAreas = NSMutableSet()
  38. if self.window != nil {
  39. self._rebuildTrackingAreas()
  40. }
  41. } else if self.kmTrackingAreas != nil && newValue == false {
  42. if self.window != nil {
  43. self._removeTrackingAreas()
  44. }
  45. }
  46. }
  47. }
  48. func setTypeSelectHelper(_ newTypeSelectHelper: SKTypeSelectHelper) {
  49. if typeSelectHelper != newTypeSelectHelper {
  50. if self.isEqual(to: self.typeSelectHelper?.delegate) {
  51. typeSelectHelper?.delegate = nil
  52. }
  53. typeSelectHelper = newTypeSelectHelper
  54. typeSelectHelper?.delegate = self
  55. }
  56. }
  57. override func draw(_ dirtyRect: NSRect) {
  58. super.draw(dirtyRect)
  59. // Drawing code here.
  60. }
  61. override func reloadData() {
  62. super.reloadData()
  63. self._rebuildTrackingAreas()
  64. self.typeSelectHelper?.rebuildTypeSelectSearchCache()
  65. }
  66. // MARK: - Tracking
  67. override func updateTrackingAreas() {
  68. super.updateTrackingAreas()
  69. self._rebuildTrackingAreas()
  70. }
  71. override func noteNumberOfRowsChanged() {
  72. super.noteNumberOfRowsChanged()
  73. self._rebuildTrackingAreas()
  74. }
  75. override func mouseEntered(with event: NSEvent) {
  76. if (self.kmTrackingAreas == nil) {
  77. return
  78. }
  79. // let userInfo = event.userData as? [String : Any]
  80. let userInfo = event.trackingArea?.userInfo as? [String : Any]
  81. let row = userInfo?["row"] as? Int
  82. if row != nil {
  83. let context = self.botaDelegate?.tableView?(self, imageContextForRow: row!)
  84. if context is KMImageToolTipContext {
  85. KMImageToolTipWindow.shared.showForImageContext(context as! KMImageToolTipContext, at: .zero)
  86. }
  87. }
  88. }
  89. override func mouseExited(with event: NSEvent) {
  90. if (self.kmTrackingAreas == nil) {
  91. return
  92. }
  93. let userInfo = event.userData as? [String : Any]
  94. let row = userInfo?["row"] as? Int
  95. if row != nil {
  96. KMImageToolTipWindow.shared.fadeOut()
  97. }
  98. }
  99. func scrollToBeginningOfDocument(_ sender: AnyObject?) {
  100. if self.numberOfRows > 0 {
  101. self.scrollRowToVisible(0)
  102. }
  103. }
  104. func scrollToEndOfDocument(_ sender: AnyObject?) {
  105. if self.numberOfRows > 0 {
  106. self.scrollRowToVisible(self.numberOfRows-1)
  107. }
  108. }
  109. func moveLeft(_ sender: AnyObject?) {
  110. self.botaDelegate?.tableViewMoveLeft?(self)
  111. }
  112. func moveRight(_ sender: AnyObject?) {
  113. self.botaDelegate?.tableViewMoveRight?(self)
  114. }
  115. func canDelete() -> Bool {
  116. let indexes = self.selectedRowIndexes
  117. if indexes.isEmpty {
  118. return false
  119. }
  120. return self.botaDelegate?.tableView?(self, canDeleteRowsWithIndexes: indexes) ?? false
  121. }
  122. @objc func delete(_ sender: AnyObject?) {
  123. if self.canDelete() {
  124. self.botaDelegate?.tableView?(self, deleteRowsWithIndexes: self.selectedRowIndexes)
  125. }
  126. }
  127. func canCopy() -> Bool {
  128. let indexes = self.selectedRowIndexes
  129. if indexes.isEmpty {
  130. return false
  131. }
  132. return self.botaDelegate?.tableView?(self, canCopyRowsWithIndexes: indexes) ?? false
  133. }
  134. @objc func copy(_ sender: AnyObject?) {
  135. if self.canCopy() {
  136. self.botaDelegate?.tableView?(self, copyRowsWithIndexes: self.selectedRowIndexes)
  137. }
  138. }
  139. func canPaste() -> Bool {
  140. return self.botaDelegate?.tableView?(self, canPasteFromPasteboard: NSPasteboard.general) ?? false
  141. }
  142. @objc func paste(_ sender: AnyObject?) {
  143. if self.canPaste() {
  144. self.botaDelegate?.tableView?(self, pasteFromPasteboard: NSPasteboard.general)
  145. }
  146. }
  147. override func keyDown(with event: NSEvent) {
  148. let eventChar = event.pdfListViewFirstCharacter()
  149. let modifierFlags = event.deviceIndependentModifierFlags()
  150. if ((eventChar == NSNewlineCharacter || eventChar == NSEnterCharacter || eventChar == NSCarriageReturnCharacter) && modifierFlags == 0) {
  151. if self.doubleAction == nil || self.sendAction(self.doubleAction, to: self.target) == false {
  152. NSSound.beep()
  153. }
  154. } else if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && modifierFlags == 0 && self.canDelete()) {
  155. self.delete(self)
  156. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == 0) {
  157. if (self.supportsQuickLook == false) {
  158. self.enclosingScrollView?.pageDown(nil)
  159. } else if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
  160. QLPreviewPanel.shared().orderOut(nil)
  161. } else {
  162. QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
  163. }
  164. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == NSEvent.ModifierFlags.shift.rawValue) {
  165. if (self.supportsQuickLook == false) {
  166. self.enclosingScrollView?.pageUp(nil)
  167. }
  168. }
  169. // else if (eventChar == NSHomeFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  170. // [self scrollToBeginningOfDocument:nil];
  171. // } else if (eventChar == NSEndFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  172. // [self scrollToEndOfDocument:nil];
  173. // }
  174. else if (eventChar == NSLeftArrowFunctionKey && modifierFlags == 0) {
  175. self.moveLeft(nil)
  176. } else if (eventChar == NSRightArrowFunctionKey && modifierFlags == 0) {
  177. self.moveRight(nil)
  178. }
  179. // else if ([typeSelectHelper handleEvent:theEvent] == NO) {
  180. // [super keyDown:theEvent];
  181. // }
  182. }
  183. override var font: NSFont? {
  184. get {
  185. for tc in self.tableColumns {
  186. if let cell = tc.dataCell as? NSCell {
  187. if cell.type == .textCellType {
  188. return cell.font
  189. }
  190. }
  191. }
  192. return nil
  193. }
  194. set {
  195. for tc in self.tableColumns {
  196. if let cell = tc.dataCell as? NSCell {
  197. if cell.type == .textCellType {
  198. cell.font = newValue
  199. }
  200. }
  201. }
  202. var rowHeight = font?.defaultViewLineHeight() ?? 34
  203. if self.selectionHighlightStyle == .sourceList {
  204. rowHeight += 2.0
  205. }
  206. self.rowHeight = rowHeight
  207. self.noteHeightOfRows(withIndexesChanged: IndexSet(0..<self.numberOfRows))
  208. }
  209. }
  210. override func dragImageForRows(with dragRows: IndexSet, tableColumns: [NSTableColumn], event dragEvent: NSEvent, offset dragImageOffset: NSPointPointer) -> NSImage {
  211. return super.dragImageForRows(with: dragRows, tableColumns: tableColumns[0..<1].shuffled(), event: dragEvent, offset: dragImageOffset)
  212. }
  213. }
  214. // MARK: - Private Methods
  215. extension KMBotaTableView {
  216. private func _rebuildTrackingAreas() {
  217. if self.kmTrackingAreas == nil {
  218. return
  219. }
  220. let context = self.botaDelegate?.tableView?(self, imageContextForRow: 0)
  221. if context == nil {
  222. return
  223. }
  224. self._removeTrackingAreas()
  225. if self.window != nil {
  226. let visibleRect = self.visibleRect
  227. let rowRange = self.rows(in: visibleRect)
  228. var row: UInt = 0
  229. for i in rowRange.location ..< NSMaxRange(rowRange) {
  230. self._addTrackingAreaForRow(Int(i))
  231. }
  232. }
  233. }
  234. private func _removeTrackingAreas() {
  235. if (self.kmTrackingAreas == nil) {
  236. return
  237. }
  238. for area in self.kmTrackingAreas! {
  239. self.removeTrackingArea(area as! NSTrackingArea)
  240. }
  241. self.kmTrackingAreas?.removeAllObjects()
  242. }
  243. private func _addTrackingAreaForRow(_ row: Int) {
  244. if (self.kmTrackingAreas == nil) {
  245. return
  246. }
  247. let userInfo = ["row" : row]
  248. let area = NSTrackingArea(rect: self.rect(ofRow: row), options: [.mouseEnteredAndExited, .activeInActiveApp], owner: self, userInfo: userInfo)
  249. self.addTrackingArea(area)
  250. self.kmTrackingAreas?.add(area)
  251. }
  252. }
  253. // MARK: - NSMenuItemValidation
  254. extension KMBotaTableView: NSMenuItemValidation {
  255. func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
  256. if menuItem.action == #selector(delete) {
  257. return self.canDelete()
  258. } else if menuItem.action == KMSelectorCopy {
  259. return self.canCopy()
  260. } else if menuItem.action == #selector(paste) {
  261. return self.canPaste()
  262. } else if menuItem.action == #selector(selectAll) {
  263. return self.allowsMultipleSelection
  264. } else if menuItem.action == #selector(deselectAll) {
  265. return self.allowsEmptySelection
  266. }
  267. // else if ([[SKTableView superclass] instancesRespondToSelector:@selector(validateMenuItem:)])
  268. // return [super validateMenuItem:menuItem];
  269. return true
  270. }
  271. }
  272. // MARK: - SKTypeSelectHelperDelegate
  273. extension KMBotaTableView: SKTypeSelectHelperDelegate {
  274. func typeSelectHelperSelectionStrings(_ typeSelectHelper: SKTypeSelectHelper) -> NSArray {
  275. return self.botaDelegate?.tableView?(self, typeSelectHelperSelectionStrings: typeSelectHelper) ?? NSArray()
  276. }
  277. func typeSelectHelperCurrentlySelectedIndex(_ typeSelectHelper: SKTypeSelectHelper) -> Int {
  278. return self.selectedRowIndexes.last ?? NSNotFound
  279. }
  280. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, selectItemAtIndex itemIndex: Int) {
  281. self.selectRowIndexes(NSIndexSet(index: itemIndex) as IndexSet, byExtendingSelection: false)
  282. self.scrollRowToVisible(itemIndex)
  283. }
  284. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
  285. self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, didFailToFindMatchForSearchString: searchString)
  286. }
  287. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) {
  288. self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, updateSearchString: searchString)
  289. }
  290. }