KMCustomOutlineView.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. //
  2. // KMCustomOutlineView.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/11/17.
  6. //
  7. import Cocoa
  8. @objc protocol KMCustomOutlineViewDelegate: NSOutlineViewDelegate {
  9. @objc optional func outlineView(_ anOutlineView: NSOutlineView, deleteItems items: [Any])
  10. @objc optional func outlineView(_ anOutlineView: NSOutlineView, canDeleteItems items: [Any]) -> Bool
  11. @objc optional func outlineView(_ anOutlineView: NSOutlineView, copyItems items: [Any])
  12. @objc optional func outlineView(_ anOutlineView: NSOutlineView, canCopyItems items: [Any]) -> Bool
  13. @objc optional func outlineView(_ anOutlineView: NSOutlineView, pasteFromPasteboard pboard: NSPasteboard)
  14. @objc optional func outlineView(_ anOutlineView: NSOutlineView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool
  15. @objc optional func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray
  16. @objc optional func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String)
  17. @objc optional func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String)
  18. }
  19. @objc protocol KMCustomOutlineViewDataSource: NSOutlineViewDataSource {
  20. @objc optional func outlineView(_ anOutlineView: NSOutlineView, dragEndedWithOperation dragEndedO: NSDragOperation)
  21. }
  22. open class KMCustomOutlineView: NSOutlineView {
  23. var supportsQuickLook = false
  24. private var _typeSelectHelper: SKTypeSelectHelper?
  25. var typeSelectHelper: SKTypeSelectHelper? {
  26. get {
  27. return self._typeSelectHelper
  28. }
  29. set {
  30. if self._typeSelectHelper != newValue {
  31. if self.isEqual(to: self._typeSelectHelper?.delegate) {
  32. self._typeSelectHelper?.delegate = nil
  33. }
  34. self._typeSelectHelper = newValue
  35. self._typeSelectHelper?.delegate = self
  36. }
  37. }
  38. }
  39. weak var botaDelegate: KMCustomOutlineViewDelegate?
  40. weak var botaDataSource: KMCustomOutlineViewDataSource?
  41. open override func draw(_ dirtyRect: NSRect) {
  42. super.draw(dirtyRect)
  43. // Drawing code here.
  44. }
  45. open override func expandItem(_ item: Any?, expandChildren: Bool) {
  46. super.expandItem(item, expandChildren: expandChildren)
  47. self.typeSelectHelper?.rebuildTypeSelectSearchCache()
  48. }
  49. open override func collapseItem(_ item: Any?, collapseChildren: Bool) {
  50. super.collapseItem(item, collapseChildren: collapseChildren)
  51. self.typeSelectHelper?.rebuildTypeSelectSearchCache()
  52. }
  53. open override func reloadData() {
  54. super.reloadData()
  55. self.typeSelectHelper?.rebuildTypeSelectSearchCache()
  56. }
  57. open override func keyDown(with event: NSEvent) {
  58. let eventChar = event.firstCharacter()
  59. let modifierFlags = event.deviceIndependentModifierFlags()
  60. if ((eventChar == NSNewlineCharacter || eventChar == NSEnterCharacter || eventChar == NSCarriageReturnCharacter) && modifierFlags == 0) {
  61. if self.doubleValue == nil || self.sendAction(self.doubleAction, to: self.target) == false {
  62. NSSound.beep()
  63. }
  64. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == 0) {
  65. if self.supportsQuickLook == false {
  66. self.enclosingScrollView?.pageDown(nil)
  67. } else if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
  68. QLPreviewPanel.shared().orderOut(nil)
  69. } else {
  70. QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
  71. }
  72. } else if ((eventChar == SKSpaceCharacter) && modifierFlags == NSEvent.ModifierFlags.shift.rawValue) {
  73. if self.supportsQuickLook == false {
  74. self.enclosingScrollView?.pageUp(nil)
  75. }
  76. }
  77. // else if (eventChar == NSHomeFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  78. // [self scrollToBeginningOfDocument:nil];
  79. // } else if (eventChar == NSEndFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
  80. // [self scrollToEndOfDocument:nil];
  81. // }
  82. else if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && modifierFlags == 0 && self.canDelete()) {
  83. self.delete(self)
  84. } else if let data = self.typeSelectHelper?.handleEvent(event), data == false {
  85. super.keyDown(with: event)
  86. }
  87. }
  88. /*
  89. - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation {
  90. if ([[SKOutlineView superclass] instancesRespondToSelector:_cmd])
  91. [super draggedImage:anImage endedAt:aPoint operation:operation];
  92. if ([[self dataSource] respondsToSelector:@selector(outlineView:dragEndedWithOperation:)])
  93. [[self dataSource] outlineView:self dragEndedWithOperation:operation];
  94. }
  95. */
  96. open override var font: NSFont? {
  97. get {
  98. for tc in self.tableColumns {
  99. if let cell = tc.dataCell as? NSCell {
  100. if cell.type == .textCellType {
  101. return cell.font
  102. }
  103. }
  104. }
  105. return nil
  106. }
  107. set {
  108. for tc in self.tableColumns {
  109. if let cell = tc.dataCell as? NSCell {
  110. if cell.type == .textCellType {
  111. cell.font = newValue
  112. }
  113. }
  114. }
  115. var rowHeight = font?.defaultViewLineHeight() ?? 34
  116. if self.selectionHighlightStyle == .sourceList {
  117. rowHeight += 2.0
  118. }
  119. self.rowHeight = rowHeight
  120. self.noteHeightOfRows(withIndexesChanged: IndexSet(0..<self.numberOfRows))
  121. }
  122. }
  123. func scrollToBeginningOfDocument(_ sender: AnyObject?) {
  124. if self.numberOfRows > 0 {
  125. self.scrollRowToVisible(0)
  126. }
  127. }
  128. func scrollToEndOfDocument(_ sender: AnyObject?) {
  129. if self.numberOfRows > 0 {
  130. self.scrollRowToVisible(self.numberOfRows-1)
  131. }
  132. }
  133. func canPaste() -> Bool {
  134. return self.botaDelegate?.outlineView?(self, canPasteFromPasteboard: .general) ?? true
  135. }
  136. @objc func paste(_ sender: AnyObject?) {
  137. if self.canPaste() {
  138. self.botaDelegate?.outlineView?(self, pasteFromPasteboard: .general)
  139. }
  140. }
  141. func canCopy() -> Bool {
  142. let items = self.selectedItems()
  143. if items.count > 0 {
  144. return self.botaDelegate?.outlineView?(self, canCopyItems: items) ?? true
  145. }
  146. return false
  147. }
  148. @objc func copy(_ sender: AnyObject?) {
  149. if self.canCopy() {
  150. self.botaDelegate?.outlineView?(self, copyItems: self.selectedItems())
  151. }
  152. }
  153. open func canDelete() -> Bool {
  154. let items = self.selectedItems()
  155. if items.count > 0 {
  156. return self.botaDelegate?.outlineView?(self, canDeleteItems: items) ?? true
  157. }
  158. return false
  159. }
  160. @objc func delete(_ sender: Any) {
  161. if self.canDelete() {
  162. self.botaDelegate?.outlineView?(self, deleteItems: self.selectedItems())
  163. }
  164. }
  165. func selectedItems() -> [Any] {
  166. return self.itemsAtRowIndexes(self.selectedRowIndexes)
  167. }
  168. func itemsAtRowIndexes(_ indexes: IndexSet) -> [Any] {
  169. var items: [Any] = []
  170. for i in indexes {
  171. if let item = self.item(atRow: i) {
  172. items.append(item)
  173. }
  174. }
  175. return items
  176. }
  177. }
  178. extension KMCustomOutlineView: NSMenuItemValidation {
  179. public func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
  180. if (menuItem.action == #selector(delete)) {
  181. return self.canDelete()
  182. } else if (menuItem.action == KMSelectorCopy) {
  183. return self.canCopy()
  184. } else if (menuItem.action == #selector(paste)) {
  185. return self.canPaste()
  186. } else if (menuItem.action == #selector(selectAll)) {
  187. return self.allowsMultipleSelection
  188. } else if (menuItem.action == #selector(deselectAll)) {
  189. return self.allowsEmptySelection
  190. }
  191. // else if ([[SKOutlineView superclass] instancesRespondToSelector:@selector(validateMenuItem:)]) {
  192. // return [super validateMenuItem:menuItem];
  193. // }
  194. return true
  195. }
  196. }
  197. extension KMCustomOutlineView: SKTypeSelectHelperDelegate {
  198. func typeSelectHelperSelectionStrings(_ typeSelectHelper: SKTypeSelectHelper) -> NSArray {
  199. return self.botaDelegate?.outlineView?(self, typeSelectHelperSelectionStrings: typeSelectHelper) ?? NSArray()
  200. }
  201. func typeSelectHelperCurrentlySelectedIndex(_ typeSelectHelper: SKTypeSelectHelper) -> Int {
  202. return self.selectedRowIndexes.last ?? NSNotFound
  203. }
  204. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, selectItemAtIndex itemIndex: Int) {
  205. self.selectRowIndexes(NSIndexSet(index: itemIndex) as IndexSet, byExtendingSelection: false)
  206. self.scrollRowToVisible(itemIndex)
  207. }
  208. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
  209. self.botaDelegate?.outlineView?(self, typeSelectHelper: typeSelectHelper, didFailToFindMatchForSearchString: searchString)
  210. }
  211. func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) {
  212. self.botaDelegate?.outlineView?(self, typeSelectHelper: typeSelectHelper, updateSearchString: searchString)
  213. }
  214. }