KMLeftSideViewController+Outline.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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 editOutlineUI(_ editVC: KMOutlineEditViewController) {
  91. if editVC.pageButton.state == .on {
  92. let numberString = editVC.outlineTargetPageIndexTextField.stringValue
  93. let idx = Int(numberString) ?? 1
  94. let newPage = editVC.pdfView.document.page(at: UInt(idx-1))
  95. let originalPage = editVC.originalDestination?.page()
  96. if let data = newPage?.isEqual(to: originalPage), data {
  97. //新page不存在
  98. if (newPage == nil) {
  99. } else {
  100. let pageSize = newPage?.bounds(for: .cropBox).size ?? .zero
  101. if let destination = CPDFDestination(page: newPage, at: CGPointMake(pageSize.width, pageSize.height)) {
  102. self.changePDFOutlineDestination(destination, PDFoutline: editVC.outline)
  103. }
  104. }
  105. }
  106. } else if editVC.urlButton.state == .on {
  107. var urlString = editVC.outlineURLTextField.stringValue
  108. let tLowerUrl = urlString.lowercased()
  109. if tLowerUrl.hasPrefix("https://") == false && tLowerUrl.hasPrefix("ftp://") == false && tLowerUrl.hasPrefix("http://") == false && urlString.isEmpty == false {
  110. urlString = "http://\(urlString)"
  111. }
  112. if let urlAction = CPDFURLAction(url: urlString) {
  113. if editVC.originalURLString != editVC.outlineURLTextField.stringValue {
  114. self.changePDFAction(urlAction, PDFOutline: editVC.outline)
  115. }
  116. }
  117. } else if editVC.mailButton.state == .on {
  118. var mailString = editVC.mailAddressTextField.stringValue
  119. let tLowerStr = mailString.lowercased()
  120. if tLowerStr.hasPrefix("mailto:") == false {
  121. mailString = "mailto:\(mailString)"
  122. }
  123. if var urlAction = CPDFURLAction(url: mailString) {
  124. if urlAction.url() == nil {
  125. urlAction = CPDFURLAction(url: "mailto:")
  126. }
  127. if mailString != editVC.originalURLString {
  128. self.changePDFAction(urlAction, PDFOutline: editVC.outline)
  129. }
  130. }
  131. }
  132. if editVC.outlineNameTextView.string == editVC.originalLabel {
  133. return
  134. }
  135. self.renamePDFOutline(editVC.outline, label: editVC.outlineNameTextView.string)
  136. }
  137. @objc func outlineContextMenuItemClicked_AddEntry(_ sender: AnyObject?) {
  138. let PDFOutlineArray = NSMutableArray()
  139. let rowSet = self.selectedRowIndexes()
  140. for idx in rowSet {
  141. PDFOutlineArray.add(self.tocOutlineView.item(atRow: idx))
  142. }
  143. if (PDFOutlineArray.count == 0) {
  144. var lastPDFLine = self.tocOutlineView.item(atRow: self.tocOutlineView.numberOfRows-1) as? CPDFOutline
  145. var rootPDFOutline: CPDFOutline?
  146. if (lastPDFLine != nil) {
  147. while (lastPDFLine!.parent != nil) {
  148. lastPDFLine = lastPDFLine?.parent
  149. }
  150. rootPDFOutline = lastPDFLine
  151. } else {
  152. rootPDFOutline = self.listView.document.outlineRoot()
  153. if ((rootPDFOutline == nil)) {
  154. // rootPDFOutline = CPDFOutline()
  155. // self.listView.document.setOutlineRoot(rootPDFOutline)
  156. rootPDFOutline = self.listView.document.setNewOutlineRoot()
  157. }
  158. }
  159. let addOutLine = CPDFOutline()
  160. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), self.listView.currentPageIndex+1)
  161. addOutLine.destination = self.listView.currentDestination
  162. self.addoutline(parent: rootPDFOutline, addOutline: addOutLine, index: Int(rootPDFOutline?.numberOfChildren ?? 0), needExpand: false)
  163. } else {
  164. let currentPDFline = PDFOutlineArray.lastObject as? CPDFOutline
  165. let currentIndex = currentPDFline?.index ?? 0
  166. var parent: CPDFOutline?
  167. parent = currentPDFline?.parent
  168. let addOutLine = CPDFOutline()
  169. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), self.listView.currentPageIndex+1)
  170. addOutLine.destination = self.listView.currentDestination
  171. self.addoutline(parent: parent, addOutline: addOutLine, index: Int(currentIndex) + 1, needExpand: false)
  172. self.tocOutlineView.scrollRowToVisible(Int(currentIndex) + 1)
  173. self.tocOutlineView.deselectRow(Int(currentIndex)+1)
  174. }
  175. }
  176. }
  177. // MARK: - Undo & Redo
  178. extension KMLeftSideViewController {
  179. @objc dynamic func changePDFOutlineDestination(_ destination: CPDFDestination?, PDFoutline outline: CPDFOutline) {
  180. if let des = destination {
  181. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).changePDFOutlineDestination(outline.destination, PDFoutline: outline)
  182. let newDes = CPDFDestination(document: des.document, pageIndex: des.pageIndex, at: des.point, zoom: des.zoom)
  183. outline.destination = newDes
  184. self.tocOutlineView.reloadItem(outline)
  185. }
  186. }
  187. @objc dynamic func changePDFAction(_ action: CPDFAction, PDFOutline outline: CPDFOutline) {
  188. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).changePDFAction(outline.action, PDFOutline: outline)
  189. outline.action = action
  190. }
  191. @objc dynamic func renamePDFOutline(_ outline: CPDFOutline, label: String) {
  192. if (self.isRenameNoteOutline) {
  193. if outline is CPDFAnnotation {
  194. // if([outline isKindOfClass:[PDFAnnotation class]]) {
  195. // PDFAnnotation *annotation = (PDFAnnotation *)outline;
  196. // annotation.string = label;
  197. // [rightSideController.noteOutlineView reloadData];
  198. // [rightSideController.noteOutlineView selectRowIndexes:[[[NSIndexSet alloc] initWithIndex:[rightSideController.noteOutlineView rowForItem:outline]] autorelease] byExtendingSelection:NO];
  199. }
  200. } else {
  201. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).renamePDFOutline(outline, label: outline.label)
  202. outline.label = label
  203. self.tocOutlineView.reloadData()
  204. self.tocOutlineView.km_selectItem(outline, byExtendingSelection: false)
  205. }
  206. }
  207. @objc dynamic func demoteOutlineWithGrandParent(_ grandParentOutline: CPDFOutline, demoteOutline: CPDFOutline, index: Int) {
  208. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).promoteOutlineWithGrandParent(grandParentOutline, promoteOutline: demoteOutline, rowIndex: index)
  209. if grandParentOutline.isEqual(to: demoteOutline.parent) {
  210. let demoteIndex = demoteOutline.index
  211. let previousOutline = grandParentOutline.child(at: demoteIndex-1)
  212. demoteOutline.removeFromParent()
  213. previousOutline?.insertChild(demoteOutline, at: UInt(index))
  214. self.tocOutlineView.reloadData()
  215. self.tocOutlineView.expandItem(previousOutline)
  216. } else {
  217. demoteOutline.removeFromParent()
  218. grandParentOutline.insertChild(demoteOutline, at: grandParentOutline.numberOfChildren)
  219. self.tocOutlineView.reloadData()
  220. self.tocOutlineView.expandItem(grandParentOutline)
  221. }
  222. self.tocOutlineView.km_selectItem(demoteOutline, byExtendingSelection: false)
  223. }
  224. @objc dynamic func promoteOutlineWithGrandParent(_ grandParentOutline: CPDFOutline, promoteOutline: CPDFOutline, rowIndex: Int) {
  225. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).demoteOutlineWithGrandParent(grandParentOutline, demoteOutline: promoteOutline, index: rowIndex)
  226. let index = promoteOutline.parent?.index ?? 0
  227. if grandParentOutline.isEqual(to: promoteOutline.parent) {
  228. promoteOutline.removeFromParent()
  229. grandParentOutline.parent.insertChild(promoteOutline, at: index+1)
  230. } else {
  231. promoteOutline.removeFromParent()
  232. grandParentOutline.insertChild(promoteOutline, at: index+1)
  233. }
  234. self.tocOutlineView.reloadData()
  235. self.tocOutlineView.km_selectItem(promoteOutline, byExtendingSelection: false)
  236. }
  237. @objc dynamic func addoutline(parent parentOutline: CPDFOutline?, addOutline: CPDFOutline, index: Int, needExpand: Bool) {
  238. var tempO: CPDFOutline? = addOutline
  239. if addOutline.label != nil {
  240. parentOutline?.insertChild(addOutline, at: UInt(index))
  241. } else {
  242. let outline = parentOutline?.insertChild(at: UInt(index))
  243. outline?.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), self.listView.currentPageIndex+1)
  244. outline?.destination = self.listView.currentDestination
  245. tempO = outline
  246. }
  247. guard let outline = tempO else {
  248. return
  249. }
  250. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).removeOutline(parent: parentOutline, removeOutline: outline, index: index, needExpand: needExpand)
  251. self.view.window?.makeFirstResponder(nil)
  252. Task { @MainActor in
  253. self.tocOutlineView.reloadData()
  254. if (needExpand) {
  255. self.tocOutlineView.expandItem(parentOutline)
  256. }
  257. let idx = self.tocOutlineView.row(forItem: outline)
  258. self.tocOutlineView.selectRowIndexes(IndexSet(integer: idx), byExtendingSelection: false)
  259. self.newAddOutlineEntryEditingMode(index)
  260. }
  261. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  262. self.tocOutlineView.scrollRowToVisible(self.tocOutlineView.row(forItem: outline))
  263. }
  264. }
  265. @objc dynamic func removeOutline(parent parentOutline: CPDFOutline?, removeOutline: CPDFOutline, index: Int, needExpand: Bool) {
  266. (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).addoutline(parent: parentOutline, addOutline: removeOutline, index: index, needExpand: true)
  267. removeOutline.removeFromParent()
  268. if (needExpand) {
  269. self.tocOutlineView.expandItem(parentOutline)
  270. }
  271. Task { @MainActor in
  272. self.tocOutlineView.reloadData()
  273. }
  274. }
  275. }
  276. // MARK: - Menu Actions
  277. extension KMLeftSideViewController {
  278. //添加子节点
  279. @objc func outlineContextMenuItemClicked_AddChildEntry(_ sender: AnyObject?) {
  280. let PDFOutlineArray = NSMutableArray()
  281. let rowSet = self.selectedRowIndexes()
  282. for idx in rowSet {
  283. PDFOutlineArray.add(self.tocOutlineView.item(atRow: idx))
  284. }
  285. let currentPDFline = PDFOutlineArray.lastObject as? CPDFOutline
  286. let addOutLine = CPDFOutline()
  287. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), "\(self.listView.currentPageIndex + 1)")
  288. addOutLine.destination = self.listView.currentDestination
  289. self.addoutline(parent: currentPDFline, addOutline: addOutLine, index: Int(currentPDFline?.numberOfChildren ?? 0), needExpand: true)
  290. }
  291. //添加上一级节点
  292. @objc func outlineContextMenuItemClicked_AddAuntEntry(_ sender: AnyObject?) {
  293. let PDFOutlineArray = NSMutableArray()
  294. let rowSet = self.selectedRowIndexes()
  295. for idx in rowSet {
  296. PDFOutlineArray.add(self.tocOutlineView.item(atRow: idx))
  297. }
  298. let clickedOutline = PDFOutlineArray.lastObject as? CPDFOutline
  299. let fatherOutLine = clickedOutline?.parent
  300. let grandfatherOutLine = fatherOutLine?.parent
  301. if (grandfatherOutLine != nil) {
  302. let addOutLine = CPDFOutline()
  303. addOutLine.label = String(format: "%@ %ld", KMLocalizedString("Page", nil), "\(self.listView.currentPageIndex + 1)")
  304. addOutLine.destination = self.listView.currentDestination
  305. self.addoutline(parent: grandfatherOutLine, addOutline: addOutLine, index: Int(fatherOutLine?.index ?? 0) + 1, needExpand: false)
  306. }
  307. }
  308. //移除节点
  309. @objc func outlineContextMenuItemClicked_RemoveEntry(_ sender: AnyObject?) {
  310. let set = self.selectedRowIndexes()
  311. var selectedPDFOutlineArr = NSMutableArray()
  312. for idx in set {
  313. selectedPDFOutlineArr.add(self.tocOutlineView.item(atRow: idx))
  314. }
  315. //整体移除多选
  316. for tOutline in selectedPDFOutlineArr {
  317. guard let outL = tOutline as? CPDFOutline else {
  318. continue
  319. }
  320. self.removeOutline(parent: outL.parent , removeOutline: outL, index: Int(outL.index), needExpand: false)
  321. }
  322. }
  323. //重置目的
  324. @objc func outlineContextMenuItemClicked_SetDestination(_ sender: AnyObject?) {
  325. guard let setPDFOutline = self.tocOutlineView.item(atRow: self.tocOutlineView.clickedRow) as? CPDFOutline else {
  326. return
  327. }
  328. Task {
  329. 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)])
  330. if (modalRes == .alertFirstButtonReturn) {
  331. self.changePDFOutlineDestination(self.listView.currentDestination, PDFoutline: setPDFOutline)
  332. self.tocOutlineView.reloadData()
  333. let idx = self.tocOutlineView.row(forItem: setPDFOutline)
  334. self.tocOutlineView.selectRowIndexes(IndexSet(integer: idx), byExtendingSelection: false)
  335. }
  336. }
  337. }
  338. //弹出菜单
  339. @objc func outlineContextMenuItemClicked_Edit(_ sender: AnyObject?) {
  340. let popover = NSPopover()
  341. popover.delegate = self
  342. let targetOutline: CPDFOutline? = self.tocOutlineView.km.clickedItem()
  343. let outlineEditViewController = KMOutlineEditViewController(outline: targetOutline, document: self.listView)
  344. let cell = self.tocOutlineView.rowView(atRow: self.tocOutlineView.clickedRow, makeIfNecessary: true)
  345. popover.contentViewController = outlineEditViewController
  346. popover.animates = true
  347. popover.behavior = .transient
  348. popover.show(relativeTo: cell?.bounds ?? .zero, of: cell!, preferredEdge: .minX)
  349. }
  350. //重命名
  351. @objc func outlineContextMenuItemClicked_Rename(_ sender: AnyObject?) {
  352. if (self.tocOutlineView.clickedRow < 0) {
  353. return
  354. }
  355. self.renamePDFOutline = self.tocOutlineView.km.clickedItem()
  356. let viewS = self.tocOutlineView.view(atColumn: 0, row: self.tocOutlineView.clickedRow, makeIfNecessary: true)
  357. let targrtTextField = viewS?.subviews.first as? NSTextField
  358. self.renamePDFOutlineTextField = targrtTextField
  359. targrtTextField?.delegate = self
  360. targrtTextField?.isEditable = true
  361. targrtTextField?.becomeFirstResponder()
  362. }
  363. //降级节点
  364. @objc func outlineContextMenuItemClicked_Demote(_ sender: AnyObject?) {
  365. guard let currentOutline: CPDFOutline = self.tocOutlineView.km.clickedItem() else {
  366. return
  367. }
  368. let parentOutLine = currentOutline.parent
  369. let newParentOutLine = parentOutLine?.child(at: currentOutline.index-1)
  370. var newIndex = 0
  371. let newParentOutLineExpandState = self.tocOutlineView.isItemExpanded(newParentOutLine)
  372. if (newParentOutLineExpandState) {
  373. newIndex = self.tocOutlineView.clickedRow
  374. } else {
  375. newIndex = self.tocOutlineView.clickedRow + Int(newParentOutLine?.numberOfChildren ?? 0)
  376. }
  377. let currentIndex = currentOutline.index
  378. currentOutline.removeFromParent()
  379. self.demoteOutlineWithGrandParent(newParentOutLine!, demoteOutline: currentOutline, index: Int(currentIndex))
  380. }
  381. //升级节点
  382. @objc func outlineContextMenuItemClicked_Promote(_ sender: AnyObject?) {
  383. guard let currentOutline: CPDFOutline = self.tocOutlineView.km.clickedItem() else {
  384. return
  385. }
  386. let parentOutLine = currentOutline.parent
  387. if let grandParentOutLine = parentOutLine?.parent {
  388. self.promoteOutlineWithGrandParent(grandParentOutLine, promoteOutline: currentOutline, rowIndex:Int(currentOutline.index))
  389. }
  390. }
  391. }