KMBotaTableView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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.trackingArea?.userInfo as? [String : Any]
  80. let row = userInfo?["row"] as? Int
  81. if row != nil {
  82. let context = self.botaDelegate?.tableView?(self, imageContextForRow: row!)
  83. if context is KMImageToolTipContext {
  84. KMImageToolTipWindow.shared.showForImageContext(context as! KMImageToolTipContext, at: .zero)
  85. }
  86. }
  87. }
  88. override func mouseExited(with event: NSEvent) {
  89. if (self.kmTrackingAreas == nil) {
  90. return
  91. }
  92. let userInfo = event.trackingArea?.userInfo as? [String : Any]
  93. let row = userInfo?["row"] as? Int
  94. if row != nil {
  95. KMImageToolTipWindow.shared.fadeOut()
  96. }
  97. }
  98. func scrollToBeginningOfDocument(_ sender: AnyObject?) {
  99. if self.numberOfRows > 0 {
  100. self.scrollRowToVisible(0)
  101. }
  102. }
  103. func scrollToEndOfDocument(_ sender: AnyObject?) {
  104. if self.numberOfRows > 0 {
  105. self.scrollRowToVisible(self.numberOfRows-1)
  106. }
  107. }
  108. func moveLeft(_ sender: AnyObject?) {
  109. self.botaDelegate?.tableViewMoveLeft?(self)
  110. }
  111. func moveRight(_ sender: AnyObject?) {
  112. self.botaDelegate?.tableViewMoveRight?(self)
  113. }
  114. func canDelete() -> Bool {
  115. let indexes = self.selectedRowIndexes
  116. if indexes.isEmpty {
  117. return false
  118. }
  119. return self.botaDelegate?.tableView?(self, canDeleteRowsWithIndexes: indexes) ?? false
  120. }
  121. @objc func delete(_ sender: AnyObject?) {
  122. if self.canDelete() {
  123. self.botaDelegate?.tableView?(self, deleteRowsWithIndexes: self.selectedRowIndexes)
  124. }
  125. }
  126. func canCopy() -> Bool {
  127. let indexes = self.selectedRowIndexes
  128. if indexes.isEmpty {
  129. return false
  130. }
  131. return self.botaDelegate?.tableView?(self, canCopyRowsWithIndexes: indexes) ?? false
  132. }
  133. @objc func copy(_ sender: AnyObject?) {
  134. if self.canCopy() {
  135. self.botaDelegate?.tableView?(self, copyRowsWithIndexes: self.selectedRowIndexes)
  136. }
  137. }
  138. func canPaste() -> Bool {
  139. return self.botaDelegate?.tableView?(self, canPasteFromPasteboard: NSPasteboard.general) ?? false
  140. }
  141. @objc func paste(_ sender: AnyObject?) {
  142. if self.canPaste() {
  143. self.botaDelegate?.tableView?(self, pasteFromPasteboard: NSPasteboard.general)
  144. }
  145. }
  146. override func keyDown(with event: NSEvent) {
  147. let eventChar = event.pdfListViewFirstCharacter()
  148. let modifierFlags = event.deviceIndependentModifierFlags()
  149. if ((eventChar == NSNewlineCharacter || eventChar == NSEnterCharacter || eventChar == NSCarriageReturnCharacter) && modifierFlags == 0) {
  150. if self.doubleAction == nil || self.sendAction(self.doubleAction, to: self.target) == false {
  151. NSSound.beep()
  152. }
  153. } else if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && modifierFlags == 0 && self.canDelete()) {
  154. self.delete(self)
  155. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == 0) {
  156. if (self.supportsQuickLook == false) {
  157. self.enclosingScrollView?.pageDown(nil)
  158. } else if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
  159. QLPreviewPanel.shared().orderOut(nil)
  160. } else {
  161. QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
  162. }
  163. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == NSEvent.ModifierFlags.shift.rawValue) {
  164. if (self.supportsQuickLook == false) {
  165. self.enclosingScrollView?.pageUp(nil)
  166. }
  167. }
  168. // else if (eventChar == NSHomeFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  169. // [self scrollToBeginningOfDocument:nil];
  170. // } else if (eventChar == NSEndFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  171. // [self scrollToEndOfDocument:nil];
  172. // }
  173. else if (eventChar == NSLeftArrowFunctionKey && modifierFlags == 0) {
  174. self.moveLeft(nil)
  175. } else if (eventChar == NSRightArrowFunctionKey && modifierFlags == 0) {
  176. self.moveRight(nil)
  177. }
  178. // else if ([typeSelectHelper handleEvent:theEvent] == NO) {
  179. // [super keyDown:theEvent];
  180. // }
  181. }
  182. override var font: NSFont? {
  183. get {
  184. for tc in self.tableColumns {
  185. if let cell = tc.dataCell as? NSCell {
  186. if cell.type == .textCellType {
  187. return cell.font
  188. }
  189. }
  190. }
  191. return nil
  192. }
  193. set {
  194. for tc in self.tableColumns {
  195. if let cell = tc.dataCell as? NSCell {
  196. if cell.type == .textCellType {
  197. cell.font = newValue
  198. }
  199. }
  200. }
  201. var rowHeight = font?.defaultViewLineHeight() ?? 34
  202. if self.selectionHighlightStyle == .sourceList {
  203. rowHeight += 2.0
  204. }
  205. self.rowHeight = rowHeight
  206. self.noteHeightOfRows(withIndexesChanged: IndexSet(0..<self.numberOfRows))
  207. }
  208. }
  209. override func dragImageForRows(with dragRows: IndexSet, tableColumns: [NSTableColumn], event dragEvent: NSEvent, offset dragImageOffset: NSPointPointer) -> NSImage {
  210. return super.dragImageForRows(with: dragRows, tableColumns: tableColumns[0..<1].shuffled(), event: dragEvent, offset: dragImageOffset)
  211. }
  212. }
  213. // MARK: - Private Methods
  214. extension KMBotaTableView {
  215. private func _rebuildTrackingAreas() {
  216. if self.kmTrackingAreas == nil {
  217. return
  218. }
  219. let context = self.botaDelegate?.tableView?(self, imageContextForRow: 0)
  220. if context == nil {
  221. return
  222. }
  223. self._removeTrackingAreas()
  224. if self.window != nil {
  225. let visibleRect = self.visibleRect
  226. let rowRange = self.rows(in: visibleRect)
  227. for i in rowRange.location ..< NSMaxRange(rowRange) {
  228. self._addTrackingAreaForRow(Int(i))
  229. }
  230. }
  231. }
  232. private func _removeTrackingAreas() {
  233. if (self.kmTrackingAreas == nil) {
  234. return
  235. }
  236. for area in self.kmTrackingAreas! {
  237. self.removeTrackingArea(area as! NSTrackingArea)
  238. }
  239. self.kmTrackingAreas?.removeAllObjects()
  240. }
  241. private func _addTrackingAreaForRow(_ row: Int) {
  242. if (self.kmTrackingAreas == nil) {
  243. return
  244. }
  245. let userInfo = ["row" : row]
  246. var rect = self.rect(ofRow: row)
  247. rect.size.height -= 1
  248. let area = NSTrackingArea(rect: rect, 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. }