KMBotaTableView.swift 13 KB

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