KMBOTAOutlineView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //
  2. // KMBOTAOutlineView.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lizhe on 2023/4/2.
  6. //
  7. import Cocoa
  8. protocol KMBOTAOutlineViewDelegate: NSObjectProtocol {
  9. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, didReloadData: KMBOTAOutlineItem)
  10. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, didSelectItem: [KMBOTAOutlineItem])
  11. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, rightDidMoseDown: KMBOTAOutlineItem, event: NSEvent)
  12. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool
  13. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation
  14. func BOTAOutlineView(_ outlineView: KMBOTAOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool
  15. }
  16. class KMBOTAOutlineView: BaseXibView {
  17. @IBOutlet weak var outlineView: KMOutlineView!
  18. @IBOutlet weak var scrollView: NSScrollView!
  19. weak var delegate: KMBOTAOutlineViewDelegate?
  20. var inputData: CPDFOutline? {
  21. didSet {
  22. self.reloadData(expandItemType: .none)
  23. }
  24. }
  25. var data: KMBOTAOutlineItem?
  26. var selectItems: [KMBOTAOutlineItem]?
  27. var dragPDFOutline: KMBOTAOutlineItem!
  28. override func draw(_ dirtyRect: NSRect) {
  29. super.draw(dirtyRect)
  30. // Drawing code here.
  31. }
  32. override func awakeFromNib() {
  33. super.awakeFromNib()
  34. self.setup()
  35. }
  36. func setup() {
  37. self.scrollView.backgroundColor(NSColor.km_init(hex: "#F2F9FF"))
  38. self.outlineView.registerForDraggedTypes([NSPasteboard.PasteboardType(rawValue: "kKMPDFViewOutlineDragDataType")])
  39. self.outlineView.delegate = self
  40. self.outlineView.dataSource = self
  41. self.outlineView.selectionHighlightStyle = NSTableView.SelectionHighlightStyle.none;
  42. self.outlineView.allowsMultipleSelection = true
  43. // self.outlineView.indentationPerLevel = 0
  44. outlineView.tocDelegate = self
  45. outlineView.hasImageToolTips = true
  46. }
  47. func reloadData(expandItemType: KMOutlineViewExpandItemType = .none) {
  48. if self.inputData != nil {
  49. //获取数据
  50. var tempData: KMBOTAOutlineItem = KMBOTAOutlineItem()
  51. if self.inputData!.numberOfChildren > 0 {
  52. let outline: CPDFOutline = self.inputData!
  53. tempData = self.addOutlineItem(outlineItem:tempData, outline: outline, expandItemType: expandItemType)
  54. } else {
  55. tempData.outline = CPDFOutline()
  56. if expandItemType == .collapse {
  57. tempData.isItemExpanded = false
  58. } else if (expandItemType == .expand) {
  59. tempData.isItemExpanded = true
  60. }
  61. }
  62. tempData.parent = nil
  63. self.data = tempData
  64. self.outlineView.reloadData()
  65. }
  66. self.delegate?.BOTAOutlineView(self, didReloadData: self.data ?? KMBOTAOutlineItem())
  67. }
  68. func addOutlineItem(outlineItem: KMBOTAOutlineItem?, outline: CPDFOutline, expandItemType: KMOutlineViewExpandItemType = .none) -> KMBOTAOutlineItem {
  69. //添加根节点
  70. let item: KMBOTAOutlineItem = KMBOTAOutlineItem()
  71. item.outline = outline
  72. item.parent = outlineItem
  73. var items: [KMBOTAOutlineItem] = []
  74. if outline.numberOfChildren > 0 {
  75. for index in 0...outline.numberOfChildren - 1 {
  76. let children: CPDFOutline = outline.child(at: index)
  77. let childrenItem = self.addOutlineItem(outlineItem: item, outline: children, expandItemType: expandItemType)
  78. if expandItemType == .collapse {
  79. childrenItem.isItemExpanded = false
  80. } else if (expandItemType == .expand) {
  81. childrenItem.isItemExpanded = true
  82. }
  83. items.append(childrenItem)
  84. }
  85. }
  86. item.children = items
  87. return item
  88. }
  89. func updateUI() {
  90. }
  91. func updateLanguage() {
  92. }
  93. }
  94. //MARK: NSOutlineViewDataSource,NSOutlineViewDelegate
  95. extension KMBOTAOutlineView : NSOutlineViewDataSource,NSOutlineViewDelegate {
  96. func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
  97. let outline = item as? KMBOTAOutlineItem
  98. if outline != nil {
  99. return outline?.children.count ?? 0
  100. } else {
  101. return Int(self.data?.outline.numberOfChildren ?? 0)
  102. }
  103. }
  104. func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
  105. let outline = item as? KMBOTAOutlineItem
  106. if outline != nil {
  107. return outline?.children[index] as Any
  108. } else {
  109. return self.data?.children[index] as Any
  110. }
  111. }
  112. func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
  113. let outline = item as? KMBOTAOutlineItem
  114. if outline != nil && outline?.children.count != 0 {
  115. return true
  116. }
  117. return false
  118. }
  119. func outlineView(_ outlineView: NSOutlineView, shouldExpandItem item: Any) -> Bool {
  120. if let item = item as? KMBOTAOutlineItem {
  121. if !item.isItemExpanded {
  122. item.isItemExpanded = true
  123. outlineView.animator().expandItem(item, expandChildren: true)
  124. return false
  125. }
  126. }
  127. return true
  128. }
  129. func outlineView(_ outlineView: NSOutlineView, shouldCollapseItem item: Any) -> Bool {
  130. if let item = item as? KMBOTAOutlineItem {
  131. if item.isItemExpanded {
  132. item.isItemExpanded = false
  133. outlineView.animator().collapseItem(item, collapseChildren: true)
  134. return false
  135. }
  136. }
  137. return true
  138. }
  139. func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
  140. let cell : KMBOTAOutlineCellView = KMBOTAOutlineCellView.init()
  141. cell.model = item as? KMBOTAOutlineItem
  142. cell.iconAction = { [unowned self] view in
  143. let rowIndex = outlineView.row(forItem: item)
  144. let rowView = outlineView.rowView(atRow: rowIndex, makeIfNecessary: false)
  145. self.didSelectItem(view: (rowView as? KMBOTAOutlineRowView), event: NSEvent())
  146. if self.selectItems?.count == 1 {
  147. self.needOpenOrCloseItem(oulineItem: (self.selectItems?.first)!)
  148. }
  149. }
  150. return cell
  151. }
  152. func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
  153. let rowView = KMBOTAOutlineRowView()
  154. rowView.model = item as? KMBOTAOutlineItem
  155. rowView.mouseDownCallback = { [unowned self] (view, event) in
  156. self.didSelectItem(view: view, event: event)
  157. }
  158. rowView.rightMouseCallback = { [unowned self] (view, event) in
  159. if !KMOCToolClass.arrayContains(array: self.selectItems, annotation: item) ||
  160. self.selectItems!.count == 1 {
  161. self.selectItem(outlineItem: item as! KMBOTAOutlineItem)
  162. }
  163. self.delegate?.BOTAOutlineView(self, rightDidMoseDown: item as! KMBOTAOutlineItem, event: event)
  164. }
  165. rowView.hoverCallback = { [unowned self] (mouseEntered, mouseBox) in
  166. self.outlineView.enumerateAvailableRowViews { view, row in
  167. if view is KMBOTAOutlineRowView {
  168. (view as? KMBOTAOutlineRowView)?.model.hover = false
  169. (view as? KMBOTAOutlineRowView)?.reloadData()
  170. }
  171. }
  172. if mouseEntered {
  173. rowView.model.hover = true
  174. } else {
  175. rowView.model.hover = false
  176. }
  177. }
  178. return rowView
  179. }
  180. func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
  181. if item is KMBOTAOutlineItem {
  182. let tempItem: KMBOTAOutlineItem = item as! KMBOTAOutlineItem
  183. let string: NSString = tempItem.outline.label as NSString
  184. let paragraphStyle = NSMutableParagraphStyle()
  185. paragraphStyle.lineHeightMultiple = 1.32
  186. paragraphStyle.alignment = .left
  187. let attributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle,
  188. NSAttributedString.Key.font : NSFont.SFProTextRegularFont(14.0)]
  189. let size = string.boundingRect(with: NSMakeSize(outlineView.frame.size.width - 30, 200), options: NSString.DrawingOptions(rawValue: 3), attributes: attributes)
  190. return max(40, size.height + 16)
  191. }
  192. return 40
  193. }
  194. func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool {
  195. guard let callBack = self.delegate else { return false}
  196. return callBack.BOTAOutlineView(self, writeItems: items, to: pasteboard)
  197. }
  198. func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
  199. guard let callBack = self.delegate else { return NSDragOperation.init(rawValue: 0)}
  200. return callBack.BOTAOutlineView(self, validateDrop: info, proposedItem: item, proposedChildIndex: index)
  201. }
  202. func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
  203. guard let callBack = self.delegate else { return false}
  204. return callBack.BOTAOutlineView(self, acceptDrop: info, item: item, childIndex: index)
  205. }
  206. func outlineViewSelectionDidChange(_ notification: Notification) {
  207. // if self.outlineView.selectedRow == -1 {
  208. // self.cancelSelect()
  209. // }
  210. }
  211. }
  212. extension KMBOTAOutlineView: KMTocOutlineViewDelegate {
  213. func outlineView(_ anOutlineView: NSOutlineView, imageContextForItem item: Any?) -> AnyObject? {
  214. if item == nil {
  215. return true as AnyObject
  216. }
  217. if let data = item as? KMBOTAOutlineItem {
  218. return data.outline.destination
  219. }
  220. return nil
  221. }
  222. }
  223. //MARK: Action
  224. extension KMBOTAOutlineView {
  225. @objc func expandAllComments(item: NSMenuItem) {
  226. self.reloadData(expandItemType: .expand)
  227. self.outlineView.reloadData()
  228. self.outlineView.expandItem(nil, expandChildren: true)
  229. }
  230. @objc func collapseAllComments(item: NSMenuItem) {
  231. self.reloadData(expandItemType: .collapse)
  232. self.outlineView.reloadData()
  233. self.outlineView.collapseItem(nil, collapseChildren: true)
  234. }
  235. func selectItem(outlineItem: KMBOTAOutlineItem) {
  236. let index = self.outlineView.row(forItem: outlineItem)
  237. self.outlineView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index)), byExtendingSelection: false)
  238. self.didSelectItem(view: nil, event: NSEvent(), isNeedDelegate: false)
  239. }
  240. func selectIndex(index: Int) {
  241. self.outlineView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index)), byExtendingSelection: false)
  242. self.didSelectItem(view: nil, event: NSEvent(), isNeedDelegate: false)
  243. }
  244. func cancelSelect() {
  245. guard let items = self.selectItems else { return }
  246. self.outlineView.deselectAll(nil)
  247. for model in items {
  248. model.select = false
  249. self.outlineView.reloadItem(model)
  250. }
  251. }
  252. func didSelectItem(view: KMBOTAOutlineRowView?, event: NSEvent, isNeedDelegate: Bool = true) {
  253. //当选中一个时
  254. if view != nil && (self.outlineView.selectedRowIndexes.count == 1 ||
  255. (!event.modifierFlags.contains(NSEvent.ModifierFlags.command) &&
  256. !event.modifierFlags.contains(NSEvent.ModifierFlags.shift))) {
  257. let rowView: KMBOTAOutlineRowView = view!
  258. let index = self.outlineView.row(for: rowView)
  259. self.outlineView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index)), byExtendingSelection: false)
  260. }
  261. //原始数据置空
  262. if self.selectItems != nil {
  263. for item in self.selectItems! {
  264. item.select = false
  265. let index = self.outlineView.row(forItem: item)
  266. if index != -1 {
  267. if self.outlineView.rowView(atRow: index, makeIfNecessary: false) != nil {
  268. let rowView: KMBOTAOutlineRowView = self.outlineView.rowView(atRow: index, makeIfNecessary: false) as! KMBOTAOutlineRowView
  269. rowView.reloadData()
  270. }
  271. }
  272. }
  273. }
  274. //获取最新数据
  275. var items: [KMBOTAOutlineItem] = []
  276. for index in self.outlineView.selectedRowIndexes {
  277. if index != -1 {
  278. let item: KMBOTAOutlineItem = self.outlineView.item(atRow: index) as! KMBOTAOutlineItem
  279. item.select = true
  280. items.append(item)
  281. //刷新数据
  282. if self.outlineView.rowView(atRow: index, makeIfNecessary: false) != nil {
  283. let rowView: KMBOTAOutlineRowView = self.outlineView.rowView(atRow: index, makeIfNecessary: false) as! KMBOTAOutlineRowView
  284. rowView.reloadData()
  285. self.outlineView.reloadItem(item, reloadChildren: true)
  286. }
  287. }
  288. }
  289. self.selectItems = items
  290. // if self.selectItems?.count == 1 {
  291. // self.needOpenOrCloseItem(oulineItem: (self.selectItems?.first)!)
  292. // }
  293. if self.selectItems != nil && isNeedDelegate {
  294. self.delegate?.BOTAOutlineView(self, didSelectItem: self.selectItems!)
  295. }
  296. }
  297. func needOpenOrCloseItem(oulineItem: KMBOTAOutlineItem) {
  298. //只有一个选中项时开启关闭item
  299. if self.selectItems?.count == 1 {
  300. if self.outlineView.isItemExpanded(oulineItem) {
  301. self.outlineView.collapseItem(oulineItem)
  302. oulineItem.isItemExpanded = false
  303. } else {
  304. self.outlineView.expandItem(oulineItem)
  305. oulineItem.isItemExpanded = true
  306. }
  307. let row = self.outlineView.row(forItem: oulineItem)
  308. self.outlineView.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: 0))
  309. }
  310. }
  311. }