KMBookMarkViewController.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. //
  2. // KMBookMarkViewController.swift
  3. // PDF Master
  4. //
  5. // Created by lxy on 2022/10/10.
  6. //
  7. import Cocoa
  8. import PDFKit
  9. typealias KMBookMarkViewControllerBookMarkDidChange = (_ controller: KMBookMarkViewController, _ bookMarks: [KMBookMarkItem]) -> Void
  10. class KMBookMarkViewController: NSViewController, NSTextFieldDelegate {
  11. @IBOutlet weak var addBookButton: NSButton!
  12. @IBOutlet weak var titleTextField: NSTextField!
  13. @IBOutlet var topSeplineView: NSView!
  14. @IBOutlet weak var bookTableView: NSTableView!
  15. @IBOutlet weak var emptyView: NSView!
  16. @IBOutlet weak var bigTipLabel: NSTextField!
  17. @IBOutlet weak var tipLabel: NSTextField!
  18. var dataSource: [KMBookMarkItem]!
  19. var renameTextField: NSTextField!
  20. var renamePDFBook: KMBookMarkItem!
  21. var renameCellView: KMBookCellView!
  22. var listView: CPDFListView!
  23. let pdfView = PDFView.init()
  24. var isLocalEvent: Bool = false //区分外部点击还是内部点击
  25. var selectItems: [KMBookMarkItem] = []
  26. var bookMarkDidChange: KMBookMarkViewControllerBookMarkDidChange?
  27. func dealloc() {
  28. NotificationCenter.default.removeObserver(self)
  29. }
  30. deinit {
  31. NotificationCenter.default.removeObserver(self)
  32. }
  33. override func viewDidLoad() {
  34. super.viewDidLoad()
  35. self.view.wantsLayer = true
  36. self.view.layer?.backgroundColor = NSColor(red: 247.0/255.0, green: 248.0/255.0, blue: 250.0/255.0, alpha: 1).cgColor
  37. self.topSeplineView.wantsLayer = true
  38. self.topSeplineView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.1).cgColor
  39. self.bookTableView.backgroundColor = NSColor(hex: "#F7F8FA")
  40. if #available(macOS 11, *) {
  41. self.bookTableView.style = NSTableView.Style.plain
  42. }
  43. self.bookTableView.allowsMultipleSelection = true
  44. self.bookTableView.doubleAction = #selector(renameBookAction)
  45. // self.bookTableView.selectionHighlightStyle = NSTableView.SelectionHighlightStyle.none;
  46. self.refreshUI()
  47. self.reloadData()
  48. self.initNotification()
  49. }
  50. func refreshUI() {
  51. self.titleTextField.font = NSFont.SFProTextSemibold(14.0)
  52. self.titleTextField.textColor = NSColor(hex: "#252629")
  53. self.titleTextField.stringValue = NSLocalizedString("Bookmarks", comment: "")
  54. self.addBookButton.toolTip = NSLocalizedString("Add Bookmark", comment: "")
  55. self.bigTipLabel.font = NSFont.SFProTextRegular(14.0)
  56. self.bigTipLabel.textColor = NSColor(hex: "#616469")
  57. self.bigTipLabel.stringValue = NSLocalizedString("No Bookmarks", comment: "")
  58. let title = NSLocalizedString("To create a bookmark, please right-click on the selected page and choose \"Add Bookmark\", or click \"Add\" button in the upper right corner.", comment: "")
  59. let paragraphStyle = NSMutableParagraphStyle()
  60. paragraphStyle.lineHeightMultiple = 1.32
  61. paragraphStyle.alignment = .center
  62. self.tipLabel.attributedStringValue = NSMutableAttributedString(string: title, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle, .foregroundColor : NSColor(hex: "#94989C")])
  63. }
  64. func reloadData() {
  65. let array = self.listView.document.bookmarks() ?? [CPDFBookmark]()
  66. var bookMarks: [KMBookMarkItem] = []
  67. for bookMark in array {
  68. let item = KMBookMarkItem()
  69. item.bookMark = bookMark
  70. item.index = UInt(bookMark.pageIndex)
  71. item.label = bookMark.label
  72. bookMarks.append(item)
  73. }
  74. self.dataSource = bookMarks
  75. self.dataSource.sort(){$0.bookMark.pageIndex < $1.bookMark.pageIndex}
  76. self.bookTableView.reloadData()
  77. self.updateAddBookMarkState()
  78. }
  79. func addBookMarkAndEdit(newBookMark: KMBookMarkItem) {
  80. _ = self.dataSource.contains { KMBookMarkItem in
  81. if KMBookMarkItem.bookMark == newBookMark.bookMark {
  82. let index = KMOCToolClass.arrayIndex(of: self.dataSource, annotation: KMBookMarkItem)
  83. self.didSelectItem(row: index, event: NSEvent())
  84. self.renameBookWithRow(row: index)
  85. return true
  86. }
  87. return false
  88. }
  89. }
  90. func initNotification() {
  91. NotificationCenter.default.addObserver(self, selector: #selector(KMPDFViewCurrentPageDidChangedNotification), name: NSNotification.Name.init(rawValue: "KMPDFViewCurrentPageDidChanged"), object: nil)
  92. NotificationCenter.default.addObserver(self, selector: #selector(documentPageCountChangedNotification), name: NSNotification.Name.init(rawValue: "CPDFDocumentPageCountChangedNotification"), object: nil)
  93. }
  94. func removeNotification() {
  95. NotificationCenter.default.removeObserver(self)
  96. }
  97. private func addMenuTitle(view: NSView, event: NSEvent) {
  98. let menus : NSMenu = NSMenu(title: "")
  99. menus.delegate = self
  100. let addItem = self.menuItemWithTitle(title: NSLocalizedString("Rename", comment: ""), action: #selector(renameBookAction))
  101. let addChildItem = self.menuItemWithTitle(title: NSLocalizedString("Change Destination", comment: ""), action: #selector(changeLocationAction))
  102. let addHigherItem = self.menuItemWithTitle(title: NSLocalizedString("Delete", comment: ""), action: #selector(deleteBookAction))
  103. menus.addItem(addItem)
  104. menus.addItem(addChildItem)
  105. menus.addItem(addHigherItem)
  106. let point = view.convert(event.locationInWindow, from: nil)
  107. menus.popUp(positioning: nil, at: point, in: view)
  108. // self.bookTableView.menu = menus
  109. }
  110. func menuItemWithTitle(title:String, action:Selector?) -> NSMenuItem {
  111. let menuItem = NSMenuItem.init(title: title as String, action: action, keyEquivalent: "")
  112. menuItem.target = self
  113. return menuItem
  114. }
  115. //MARK: Menu Action
  116. @objc func renameBookAction() {
  117. if self.bookTableView.selectedRowIndexes.count == 1 {
  118. self.renameBookWithRow(row: self.bookTableView.selectedRowIndexes.first!)
  119. } else {
  120. __NSBeep()
  121. }
  122. }
  123. @IBAction func escButtonAction(_ sender: Any) {
  124. self.bookTableView.deselectAll(nil)
  125. }
  126. @IBAction func addBookmarkAction(_ sender: Any) {
  127. if self.listView.document.bookmark(forPageIndex: UInt(self.listView.currentPageIndex)) == nil {
  128. let label = "\(NSLocalizedString("Page", comment:"")) \(self.listView.currentPageIndex + 1)"
  129. let bookMark = KMBookMarkItem()
  130. bookMark.label = label
  131. bookMark.index = UInt(self.listView.currentPageIndex)
  132. self.addBookMark(bookMarks: [bookMark])
  133. } else {
  134. let bookMark = self.listView.document.bookmark(forPageIndex: UInt(self.listView.currentPageIndex))
  135. self.dataSource.contains { KMBookMarkItem in
  136. if KMBookMarkItem.bookMark == bookMark {
  137. let index = Int(KMBookMarkItem.index)
  138. self.bookTableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
  139. self.didSelectItem(row: index, event: NSEvent())
  140. return true
  141. }
  142. return false
  143. }
  144. }
  145. }
  146. @objc func changeLocationAction() {
  147. if self.bookTableView.selectedRowIndexes.count == 1 {
  148. let item = self.dataSource[self.bookTableView.selectedRowIndexes.first!]
  149. let alter = NSAlert()
  150. alter.alertStyle = NSAlert.Style.informational
  151. alter.messageText = NSLocalizedString("Are you sure you want to set the target location of the selected outline to the current location", comment: "")
  152. alter.addButton(withTitle: NSLocalizedString("YES", comment:""))
  153. alter.addButton(withTitle: NSLocalizedString("NO", comment:""))
  154. let modlres = alter.runModal()
  155. if modlres == NSApplication.ModalResponse.alertFirstButtonReturn {
  156. let bookMark = KMBookMarkItem()
  157. bookMark.bookMark = item.bookMark
  158. bookMark.label = item.label
  159. bookMark.index = UInt(self.listView.currentPageIndex)
  160. self.changeLocation(oldBookMark: item,
  161. newBookMark: bookMark)
  162. }
  163. } else {
  164. __NSBeep()
  165. }
  166. }
  167. @objc func deleteBookAction() {
  168. if self.bookTableView.selectedRowIndexes.count != 0 {
  169. var bookMarks:[KMBookMarkItem] = []
  170. for index in self.bookTableView.selectedRowIndexes {
  171. let item = self.dataSource[index]
  172. bookMarks.append(item)
  173. }
  174. self.deleteBookMark(bookMarks: bookMarks)
  175. } else {
  176. __NSBeep()
  177. }
  178. }
  179. private func renameBookWithRow(row: Int) {
  180. self.renamePDFBook = self.dataSource[row]
  181. self.renameCellView = self.bookTableView.view(atColumn: 0, row: row, makeIfNecessary: true) as! KMBookCellView
  182. self.renameTextField = self.renameCellView.bookTitle
  183. self.renameTextField.delegate = self
  184. self.renameTextField.isEditable = true
  185. self.renameTextField.becomeFirstResponder()
  186. }
  187. //MARK: Noti
  188. @objc func KMPDFViewCurrentPageDidChangedNotification(notification: NSNotification) {
  189. let pdfdocument : CPDFDocument = notification.object as! CPDFDocument
  190. if pdfdocument.isEqual(self.listView.document) {
  191. if !isLocalEvent {
  192. var containSelIndex:Bool = false
  193. for (index, value) in self.dataSource.enumerated() {
  194. if value.bookMark == self.listView.document.bookmark(forPageIndex: UInt(self.listView.currentPageIndex)) {
  195. containSelIndex = true
  196. self.didSelectItem(row: index, event: NSEvent())
  197. break
  198. }
  199. }
  200. if !containSelIndex {
  201. self.cancelSelect()
  202. }
  203. }
  204. isLocalEvent = false
  205. }
  206. self.updateAddBookMarkState()
  207. }
  208. @objc func documentPageCountChangedNotification(notification: NSNotification) {
  209. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { [unowned self] in
  210. let pdfdocument : CPDFDocument = notification.object as! CPDFDocument
  211. if pdfdocument.isEqual(self.listView.document) {
  212. self.reloadData()
  213. }
  214. }
  215. }
  216. //MARK: - NSTextFieldDelegate
  217. func controlTextDidEndEditing(_ obj: Notification) {
  218. if (self.renameTextField.isEqual(obj.object)) {
  219. let textField : NSTextField = obj.object as! NSTextField
  220. self.renamePDFBook(bookmark: self.renamePDFBook, label: textField.stringValue)
  221. self.renameTextField.isEditable = false
  222. }
  223. }
  224. }
  225. extension KMBookMarkViewController : NSTableViewDelegate,NSTableViewDataSource {
  226. func numberOfRows(in tableView: NSTableView) -> Int {
  227. let count = self.dataSource?.count ?? 0
  228. if count == 0 {
  229. self.emptyView.isHidden = false
  230. } else {
  231. self.emptyView.isHidden = true
  232. }
  233. return count
  234. }
  235. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  236. if row < self.dataSource.count {
  237. let item: KMBookMarkItem = self.dataSource[row]
  238. let cell : KMBookCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMBookCellView"), owner: self) as! KMBookCellView
  239. cell.bookTitle.stringValue = item.bookMark.label
  240. return cell
  241. }
  242. return nil
  243. }
  244. func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
  245. let rowView = KMBookMarkTableRowView()
  246. if row < self.dataSource.count {
  247. rowView.model = self.dataSource[row]
  248. rowView.mouseDownAction = { [unowned self] (view, event) in
  249. self.didSelectItem(row: row, event: event)
  250. }
  251. rowView.rightMouseDownAction = { [unowned self] (view, event) in
  252. if !KMOCToolClass.arrayContains(self.selectItems, annotation: rowView.model) ||
  253. self.selectItems.count == 1 {
  254. self.selectIndex(index: row)
  255. }
  256. if self.bookTableView.rowView(atRow: row, makeIfNecessary: false) != nil {
  257. let tempView = self.bookTableView.rowView(atRow: row, makeIfNecessary: false)
  258. self.addMenuTitle(view: tempView!, event: event)
  259. }
  260. }
  261. }
  262. return rowView
  263. }
  264. func tableView(_ tableView: NSTableView, shouldSelect tableColumn: NSTableColumn?) -> Bool {
  265. self.isLocalEvent = true
  266. return true
  267. }
  268. func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
  269. self.isLocalEvent = true
  270. return true
  271. }
  272. func tableViewSelectionDidChange(_ notification: Notification) {
  273. if self.bookTableView.selectedRow == -1 {
  274. self.cancelSelect()
  275. }
  276. }
  277. func selectIndex(index: Int) {
  278. self.bookTableView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index)), byExtendingSelection: false)
  279. self.didSelectItem(row: index, event: NSEvent(), needJump: false)
  280. }
  281. func didSelectItem(row: Int, event: NSEvent?, needJump: Bool = true) {
  282. //当选中一个时
  283. if self.bookTableView.selectedRowIndexes.count == 1 || (event != nil &&
  284. (!event!.modifierFlags.contains(NSEvent.ModifierFlags.command) &&
  285. !event!.modifierFlags.contains(NSEvent.ModifierFlags.shift))) {
  286. self.bookTableView.selectRowIndexes(IndexSet(integer: IndexSet.Element(row)), byExtendingSelection: false)
  287. }
  288. //原始数据置空
  289. for model in self.selectItems {
  290. self.dataSource.contains { KMBookMarkItem in
  291. if KMBookMarkItem.bookMark == model.bookMark {
  292. let index = KMOCToolClass.arrayIndex(of: self.dataSource, annotation: KMBookMarkItem)
  293. if index != nil {
  294. KMBookMarkItem.select = false
  295. if self.bookTableView.rowView(atRow: index, makeIfNecessary: false) != nil {
  296. let rowView: KMBookMarkTableRowView = self.bookTableView.rowView(atRow: index, makeIfNecessary: false) as! KMBookMarkTableRowView
  297. rowView.reloadData()
  298. }
  299. }
  300. return true
  301. }
  302. return false
  303. }
  304. }
  305. //获取最新数据
  306. var items: [KMBookMarkItem] = []
  307. for index in self.bookTableView.selectedRowIndexes {
  308. if index < self.dataSource.count {
  309. let model: KMBookMarkItem = self.dataSource[index]
  310. model.select = true
  311. if self.bookTableView.rowView(atRow: index, makeIfNecessary: false) != nil {
  312. let rowView: KMBookMarkTableRowView = self.bookTableView.rowView(atRow: index, makeIfNecessary: false) as! KMBookMarkTableRowView
  313. rowView.reloadData()
  314. }
  315. items.append(model)
  316. }
  317. }
  318. self.selectItems = items
  319. //刷新数据
  320. if needJump {
  321. self.updateListViewData()
  322. }
  323. }
  324. func updateListViewData() {
  325. if self.bookTableView.selectedRowIndexes.count == 1 &&
  326. self.bookTableView.selectedRowIndexes.first! < self.dataSource.count {
  327. let index = self.bookTableView.selectedRowIndexes.first
  328. let selectBookMark = self.dataSource[index!]
  329. self.listView.go(toPageIndex: selectBookMark.bookMark.pageIndex, animated: true)
  330. }
  331. }
  332. func cancelSelect() {
  333. self.bookTableView.deselectAll(nil)
  334. for model in self.selectItems {
  335. model.select = false
  336. let index = self.dataSource.firstIndex(of: model)
  337. if index != nil {
  338. if (self.bookTableView.rowView(atRow: index!, makeIfNecessary: false) != nil) {
  339. let rowView: KMBookMarkTableRowView = self.bookTableView.rowView(atRow: index!, makeIfNecessary: false) as! KMBookMarkTableRowView
  340. rowView.reloadData()
  341. }
  342. }
  343. }
  344. }
  345. func updateAddBookMarkState() {
  346. self.addBookButton.isEnabled = self.canAddBorkMark()
  347. }
  348. func canAddBorkMark() -> Bool {
  349. if self.listView.document.bookmarks() != nil && self.listView.document.bookmarks()?.count != 0 {
  350. for bookMark in self.listView.document.bookmarks() {
  351. if bookMark.pageIndex == self.listView.currentPageIndex {
  352. return false
  353. }
  354. }
  355. }
  356. return true
  357. }
  358. }
  359. //MARK: undoRedo
  360. extension KMBookMarkViewController {
  361. func changeLocation(oldBookMark: KMBookMarkItem, newBookMark: KMBookMarkItem) {
  362. self.listView.document.removeBookmark(forPageIndex: oldBookMark.index)
  363. self.listView.document.addBookmark(newBookMark.label, forPageIndex: newBookMark.index)
  364. self.reloadData()
  365. self.listView.setNeedsDisplayForVisiblePages()
  366. self.listView.undoManager?.registerUndo(withTarget: self) { [unowned self] targetType in
  367. self.changeLocation(oldBookMark: newBookMark, newBookMark: oldBookMark)
  368. }
  369. }
  370. func renamePDFBook(bookmark : KMBookMarkItem! , label:String) {
  371. if bookmark.bookMark.label == label {
  372. return
  373. }
  374. let temp = bookmark.bookMark.label
  375. bookmark.bookMark.label = label
  376. self.reloadData()
  377. var indexSet = IndexSet()
  378. indexSet.insert(self.bookTableView.row(for: self.renameCellView))
  379. self.bookTableView.selectRowIndexes(indexSet, byExtendingSelection: false)
  380. self.listView.undoManager?.registerUndo(withTarget: self) { [unowned self] targetType in
  381. bookmark.bookMark.label = label
  382. self.renamePDFBook(bookmark: bookmark, label: temp ?? bookmark.label)
  383. }
  384. }
  385. func deleteBookMark(bookMarks: [KMBookMarkItem]) {
  386. for bookMark in bookMarks {
  387. if self.listView.document.removeBookmark(forPageIndex: bookMark.index) {
  388. print("删除标签成功")
  389. }
  390. }
  391. self.listView.setNeedsDisplayForVisiblePages()
  392. self.reloadData()
  393. //undo redo
  394. var saveBooks:[KMBookMarkItem] = bookMarks
  395. self.listView.undoManager?.registerUndo(withTarget: self) { [unowned self] targetType in
  396. saveBooks.sort(){$0.index > $1.index}
  397. self.addBookMark(bookMarks: bookMarks)
  398. }
  399. guard let callBack = bookMarkDidChange else { return }
  400. callBack(self, bookMarks)
  401. }
  402. func addBookMark(bookMarks: [KMBookMarkItem]) {
  403. for bookMark in bookMarks {
  404. self.listView.document.addBookmark(bookMark.label, forPageIndex: UInt(bookMark.index))
  405. }
  406. self.listView.setNeedsDisplayForVisiblePages()
  407. self.reloadData()
  408. if bookMarks.count == 1 {
  409. DispatchQueue.main.async {
  410. if self.listView.document.bookmark(forPageIndex: UInt(bookMarks.first!.index)) != nil {
  411. let item = KMBookMarkItem()
  412. item.bookMark = self.listView.document.bookmark(forPageIndex: UInt(bookMarks.first!.index))
  413. item.label = item.bookMark.label
  414. item.index = UInt(item.bookMark.pageIndex)
  415. self.addBookMarkAndEdit(newBookMark: item)
  416. }
  417. }
  418. }
  419. //undo redo
  420. var saveBooks:[KMBookMarkItem] = bookMarks
  421. self.listView.undoManager?.registerUndo(withTarget: self) { [unowned self] targetType in
  422. saveBooks.sort(){$0.index > $1.index}
  423. self.deleteBookMark(bookMarks: saveBooks)
  424. }
  425. guard let callBack = bookMarkDidChange else { return }
  426. callBack(self, bookMarks)
  427. }
  428. @IBAction func undo(_ sender: Any) {
  429. if (self.listView.undoManager?.canUndo ?? false) {
  430. self.listView.undoManager?.undo()
  431. }
  432. }
  433. @IBAction func redo(_ sender: Any) {
  434. if (self.listView.undoManager?.canRedo ?? false) {
  435. self.listView.undoManager?.redo()
  436. }
  437. }
  438. }
  439. extension KMBookMarkViewController: NSMenuDelegate, NSMenuItemValidation {
  440. func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
  441. let action = menuItem.action
  442. if (action == #selector(undo)) {
  443. return self.listView.undoManager?.canUndo ?? false
  444. }
  445. if (action == #selector(redo)) {
  446. return self.listView.undoManager?.canRedo ?? false
  447. }
  448. if action == #selector(renameBookAction) ||
  449. action == #selector(changeLocationAction) ||
  450. action == #selector(deleteBookAction) {
  451. if self.bookTableView.selectedRowIndexes.count > 1 {
  452. if action == #selector(changeLocationAction) {
  453. return false
  454. } else if action == #selector(renameBookAction) {
  455. return false
  456. }
  457. } else if self.bookTableView.selectedRowIndexes.count == 1 {
  458. return true
  459. } else {
  460. if self.bookTableView.selectedRowIndexes.count == 0 {
  461. if action == #selector(changeLocationAction) {
  462. // if self.listView.document.bookmark(forPageIndex: UInt(self.listView.currentPageIndex)) != nil {
  463. // return false
  464. // }
  465. }
  466. } else {
  467. return false
  468. }
  469. }
  470. }
  471. return true
  472. }
  473. }
  474. class KMBookMarkItem: NSObject {
  475. var label: String = ""
  476. var index: UInt = 0
  477. var bookMark: CPDFBookmark = CPDFBookmark()
  478. var select: Bool = false
  479. }