KMLeftSideViewController+Outline.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. //
  2. // KMLeftSideViewController+Outline.swift
  3. // PDF Master
  4. //
  5. // Created by tangchao on 2023/12/22.
  6. //
  7. import Foundation
  8. enum KMOutlineViewMenuItemTag: Int {
  9. case addEntry = 0
  10. case addChild
  11. case addAunt
  12. case remove
  13. case edit
  14. case setDestination
  15. case rename
  16. case promote
  17. case demote
  18. }
  19. extension KMLeftSideViewController {
  20. func updateOutlineSelection() {
  21. // if ([[pdfView document] outlineRoot] == nil || mwcFlags.updatingOutlineSelection)
  22. if self.listView.document.outlineRoot() == nil {
  23. return
  24. }
  25. // mwcFlags.updatingOutlineSelection = YES;
  26. let numRows = self.tocOutlineView.numberOfRows
  27. var arr = NSMutableArray()
  28. for i in 0 ..< numRows {
  29. guard let tPDFOutline = self.tocOutlineView.item(atRow: i) as? CPDFOutline else {
  30. continue
  31. }
  32. let tPage = tPDFOutline.destination.page()
  33. if (tPage == nil) {
  34. continue
  35. }
  36. let tDict = NSDictionary(object: tPage as Any, forKey: "\(i)" as NSCopying)
  37. arr.add(tDict)
  38. }
  39. let currentPage = self.listView.currentPage()
  40. var hasExistInOutlineView = false
  41. for dict in arr {
  42. guard let _dict = dict as? NSDictionary else {
  43. continue
  44. }
  45. let page = _dict.allValues.last as? CPDFPage
  46. // NSInteger index = [dict.allKeys.lastObject integerValue];
  47. let index = Int(_dict.allKeys.last as? String ?? "0") ?? 0
  48. if let data = page?.isEqual(to: currentPage), data {
  49. self.tocOutlineView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
  50. self.tocOutlineView.scrollRowToVisible(index)
  51. hasExistInOutlineView = true
  52. break
  53. }
  54. }
  55. if (!hasExistInOutlineView) {
  56. self.tocOutlineView.deselectRow(self.tocOutlineView.selectedRow)
  57. }
  58. // mwcFlags.updatingOutlineSelection = NO;
  59. }
  60. func newAddOutlineEntryEditingMode(_ index: Int) {
  61. self.renamePDFOutline = self.tocOutlineView.item(atRow: self.tocOutlineView.selectedRow) as? CPDFOutline
  62. let row = self.tocOutlineView.selectedRow
  63. let viewS = self.tocOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true)
  64. let targrtTextField = viewS?.subviews.first as? NSTextField
  65. self.renamePDFOutlineTextField = targrtTextField
  66. targrtTextField?.delegate = self
  67. targrtTextField?.isEditable = true
  68. targrtTextField?.becomeFirstResponder()
  69. }
  70. func loadUnfoldDate(_ foldType: KMFoldType) {
  71. self.allFoldNotes.removeAll()
  72. var mutableArray: [CPDFAnnotation] = []
  73. for note in self.notes {
  74. if note is CPDFMarkupAnnotation {
  75. mutableArray.append(note)
  76. }
  77. }
  78. self.canFoldNotes = mutableArray
  79. self.allFoldNotes = []
  80. }
  81. func selectedRowIndexes() -> IndexSet {
  82. var selectedIndexes = self.tocOutlineView.selectedRowIndexes
  83. let clickedRow = self.tocOutlineView.clickedRow
  84. if clickedRow != -1 && selectedIndexes.contains(clickedRow) == false {
  85. var indexes = IndexSet(integer: clickedRow)
  86. selectedIndexes = indexes
  87. }
  88. return selectedIndexes
  89. }
  90. func addoutline(parent parentOutline: CPDFOutline?, addOutline: CPDFOutline, index: Int, needExpand: Bool) {
  91. var tempO = addOutline
  92. if addOutline.label != nil {
  93. parentOutline?.insertChild(addOutline, at: UInt(index))
  94. } else {
  95. let outline = parentOutline?.insertChild(at: UInt(index))
  96. outline?.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), self.listView.currentPageIndex+1)
  97. outline?.destination = self.listView.currentDestination
  98. tempO = outline!
  99. }
  100. // [[[self.document undoManager] prepareWithInvocationTarget:self] removeOutlineWithParent:parentOutline removeOutline:addOutline index:index needExpand:needExpand];
  101. self.view.window?.makeFirstResponder(nil)
  102. Task { @MainActor in
  103. self.tocOutlineView.reloadData()
  104. if (needExpand) {
  105. self.tocOutlineView.expandItem(parentOutline)
  106. }
  107. let idx = self.tocOutlineView.row(forItem: tempO)
  108. self.tocOutlineView.selectRowIndexes(IndexSet(integer: idx), byExtendingSelection: false)
  109. self.newAddOutlineEntryEditingMode(index)
  110. }
  111. // __block PDFOutline *taddOutline = [addOutline retain];
  112. // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  113. // [self.leftSideController.tocOutlineView scrollRowToVisible:[leftSideController.tocOutlineView rowForItem:taddOutline]];
  114. // [taddOutline release];
  115. // });
  116. }
  117. func removeOutline(parent parentOutline: CPDFOutline?, removeOutline: CPDFOutline, index: Int, needExpand: Bool) {
  118. // [[[self.document undoManager] prepareWithInvocationTarget:self] addoutlineWithParent:parentOutline addOutline:removeOutline index:index needExpand:YES];
  119. removeOutline.removeFromParent()
  120. if (needExpand) {
  121. self.tocOutlineView.expandItem(parentOutline)
  122. }
  123. Task { @MainActor in
  124. self.tocOutlineView.reloadData()
  125. }
  126. }
  127. func editOutlineUI(_ editVC: KMOutlineEditViewController) {
  128. if editVC.pageButton.state == .on {
  129. let numberString = editVC.outlineTargetPageIndexTextField.stringValue
  130. let idx = Int(numberString) ?? 1
  131. let newPage = editVC.pdfView.document.page(at: UInt(idx-1))
  132. let originalPage = editVC.originalDestination?.page()
  133. if let data = newPage?.isEqual(to: originalPage), data {
  134. //新page不存在
  135. if (newPage == nil) {
  136. } else {
  137. let pageSize = newPage?.bounds(for: .cropBox).size ?? .zero
  138. if let destination = CPDFDestination(page: newPage, at: CGPointMake(pageSize.width, pageSize.height)) {
  139. self.changePDFOutlineDestination(destination, PDFoutline: editVC.outline)
  140. }
  141. }
  142. }
  143. } else if editVC.urlButton.state == .on {
  144. var urlString = editVC.outlineURLTextField.stringValue
  145. let tLowerUrl = urlString.lowercased()
  146. if tLowerUrl.hasPrefix("https://") == false && tLowerUrl.hasPrefix("ftp://") == false && tLowerUrl.hasPrefix("http://") == false && urlString.isEmpty == false {
  147. urlString = "http://\(urlString)"
  148. }
  149. if let urlAction = CPDFURLAction(url: urlString) {
  150. if editVC.originalURLString != editVC.outlineURLTextField.stringValue {
  151. self.changePDFAction(urlAction, PDFOutline: editVC.outline)
  152. }
  153. }
  154. } else if editVC.mailButton.state == .on {
  155. var mailString = editVC.mailAddressTextField.stringValue
  156. let tLowerStr = mailString.lowercased()
  157. if tLowerStr.hasPrefix("mailto:") == false {
  158. mailString = "mailto:\(mailString)"
  159. }
  160. if var urlAction = CPDFURLAction(url: mailString) {
  161. if urlAction.url() == nil {
  162. urlAction = CPDFURLAction(url: "mailto:")
  163. }
  164. if mailString != editVC.originalURLString {
  165. self.changePDFAction(urlAction, PDFOutline: editVC.outline)
  166. }
  167. }
  168. }
  169. if editVC.outlineNameTextView.string == editVC.originalLabel {
  170. return
  171. }
  172. self.renamePDFOutline(editVC.outline, label: editVC.outlineNameTextView.string)
  173. }
  174. }
  175. // MARK: - Undo & Redo
  176. extension KMLeftSideViewController {
  177. @objc dynamic func changePDFOutlineDestination(_ destination: CPDFDestination, PDFoutline outline: CPDFOutline) {
  178. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).changePDFOutlineDestination(outline.destination, PDFoutline: outline)
  179. outline.destination = destination
  180. }
  181. @objc dynamic func changePDFAction(_ action: CPDFAction, PDFOutline outline: CPDFOutline) {
  182. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).changePDFAction(outline.action, PDFOutline: outline)
  183. outline.action = action
  184. }
  185. @objc dynamic func renamePDFOutline(_ outline: CPDFOutline, label: String) {
  186. if (self.isRenameNoteOutline) {
  187. if outline is CPDFAnnotation {
  188. // if([outline isKindOfClass:[PDFAnnotation class]]) {
  189. // PDFAnnotation *annotation = (PDFAnnotation *)outline;
  190. // annotation.string = label;
  191. // [rightSideController.noteOutlineView reloadData];
  192. // [rightSideController.noteOutlineView selectRowIndexes:[[[NSIndexSet alloc] initWithIndex:[rightSideController.noteOutlineView rowForItem:outline]] autorelease] byExtendingSelection:NO];
  193. }
  194. } else {
  195. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).renamePDFOutline(outline, label: outline.label)
  196. outline.label = label
  197. self.tocOutlineView.reloadData()
  198. self.tocOutlineView.km_selectItem(outline, byExtendingSelection: false)
  199. }
  200. }
  201. }
  202. // MARK: - Menu Actions
  203. extension KMLeftSideViewController {
  204. //添加子节点
  205. @objc func outlineContextMenuItemClicked_AddChildEntry(_ sender: AnyObject?) {
  206. let PDFOutlineArray = NSMutableArray()
  207. let rowSet = self.selectedRowIndexes()
  208. for idx in rowSet {
  209. PDFOutlineArray.add(self.tocOutlineView.item(atRow: idx))
  210. }
  211. let currentPDFline = PDFOutlineArray.lastObject as? CPDFOutline
  212. let addOutLine = CPDFOutline()
  213. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), "\(self.listView.currentPageIndex + 1)")
  214. addOutLine.destination = self.listView.currentDestination
  215. self.addoutline(parent: currentPDFline, addOutline: addOutLine, index: Int(currentPDFline?.numberOfChildren ?? 0), needExpand: true)
  216. }
  217. //添加上一级节点
  218. @objc func outlineContextMenuItemClicked_AddAuntEntry(_ sender: AnyObject?) {
  219. let PDFOutlineArray = NSMutableArray()
  220. let rowSet = self.selectedRowIndexes()
  221. for idx in rowSet {
  222. PDFOutlineArray.add(self.tocOutlineView.item(atRow: idx))
  223. }
  224. let clickedOutline = PDFOutlineArray.lastObject as? CPDFOutline
  225. let fatherOutLine = clickedOutline?.parent
  226. let grandfatherOutLine = fatherOutLine?.parent
  227. if (grandfatherOutLine != nil) {
  228. let addOutLine = CPDFOutline()
  229. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), "\(self.listView.currentPageIndex + 1)")
  230. addOutLine.destination = self.listView.currentDestination
  231. self.addoutline(parent: grandfatherOutLine, addOutline: addOutLine, index: Int(fatherOutLine?.numberOfChildren ?? 0) + 1, needExpand: false)
  232. }
  233. }
  234. //移除节点
  235. @objc func outlineContextMenuItemClicked_RemoveEntry(_ sender: AnyObject?) {
  236. let set = self.selectedRowIndexes()
  237. var selectedPDFOutlineArr = NSMutableArray()
  238. for idx in set {
  239. selectedPDFOutlineArr.add(self.tocOutlineView.item(atRow: idx))
  240. }
  241. //整体移除多选
  242. for tOutline in selectedPDFOutlineArr {
  243. guard let outL = tOutline as? CPDFOutline else {
  244. continue
  245. }
  246. self.removeOutline(parent: outL.parent , removeOutline: outL, index: Int(outL.index), needExpand: false)
  247. }
  248. }
  249. //重置目的
  250. @objc func outlineContextMenuItemClicked_SetDestination(_ sender: AnyObject?) {
  251. guard let setPDFOutline = self.tocOutlineView.item(atRow: self.tocOutlineView.clickedRow) as? CPDFOutline else {
  252. return
  253. }
  254. Task {
  255. let modalRes = await KMAlertTool.runModel(style: .informational, message: KMLocalizedString("Are you sure you want to set the destination as the current location?", nil), buttons: [KMLocalizedString("Yes", nil), KMLocalizedString("No", nil)])
  256. if (modalRes == .alertFirstButtonReturn) {
  257. self.changePDFOutlineDestination(self.listView.currentDestination, PDFoutline: setPDFOutline)
  258. self.tocOutlineView.reloadData()
  259. let idx = self.tocOutlineView.row(forItem: setPDFOutline)
  260. self.tocOutlineView.selectRowIndexes(IndexSet(integer: idx), byExtendingSelection: false)
  261. }
  262. }
  263. }
  264. //弹出菜单
  265. @objc func outlineContextMenuItemClicked_Edit(_ sender: AnyObject?) {
  266. let popover = NSPopover()
  267. popover.delegate = self
  268. let targetOutline: CPDFOutline? = self.tocOutlineView.km.clickedItem()
  269. let outlineEditViewController = KMOutlineEditViewController(outline: targetOutline, document: self.listView)
  270. let cell = self.tocOutlineView.rowView(atRow: self.tocOutlineView.clickedRow, makeIfNecessary: true)
  271. popover.contentViewController = outlineEditViewController
  272. popover.animates = true
  273. popover.behavior = .transient
  274. popover.show(relativeTo: cell?.bounds ?? .zero, of: cell!, preferredEdge: .minX)
  275. }
  276. }