KMBookmarkController.swift 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. //
  2. // KMBookmarkController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lizhe on 2024/2/5.
  6. //
  7. import Cocoa
  8. private let kLabelIdentifier = NSUserInterfaceItemIdentifier("label")
  9. private let kFileIdentifier = NSUserInterfaceItemIdentifier("file")
  10. private let kPageIdentifier = NSUserInterfaceItemIdentifier("page")
  11. let kTextWithIconStringKey = "string";
  12. let kTextWithIconImageKey = "image";
  13. let kBookmarksToolbarIdentifier = "BookmarksToolbarIdentifier"
  14. let kBookmarksNewFolderToolbarItemIdentifier = "BookmarksNewFolderToolbarItemIdentifier"
  15. let kBookmarksNewSeparatorToolbarItemIdentifier = "BookmarksNewSeparatorToolbarItemIdentifier"
  16. let kBookmarksDeleteToolbarItemIdentifier = "BookmarksDeleteToolbarItemIdentifier"
  17. let kPasteboardTypeBookmarkRows = NSPasteboard.PasteboardType(rawValue: "pasteboard.bookmarkrows")
  18. class KMBookmarkController: NSWindowController {
  19. @IBOutlet weak var bookmarkOutlineView: KMBookmarkOutlineView!
  20. @IBOutlet weak var outlineView: KMCustomOutlineView!
  21. var draggedBookmarks: [KMBookmark] = []
  22. var recentDocuments: [[String: Any]] {
  23. get {
  24. return KMBookmarkManager.manager.recentDocuments
  25. }
  26. set {
  27. }
  28. }
  29. var bookmarkRoot: KMRootBookmark {
  30. get {
  31. return KMBookmarkManager.manager.rootBookmark
  32. }
  33. set {
  34. }
  35. }
  36. var toolbarItems: [String: NSToolbarItem] = [:]
  37. override func windowDidLoad() {
  38. super.windowDidLoad()
  39. setupToolbar()
  40. // outlineView.delegate = self
  41. // outlineView.dataSource = self
  42. // outlineView.registerForDraggedTypes([kPasteboardTypeBookmarkRows, .fileURL, .string])
  43. // bookmarkOutlineView.outlineView.doubleAction = #selector(doubleClickBookmark(_:))
  44. bookmarkOutlineView.data = self.bookmarkRoot
  45. bookmarkOutlineView.doubleClickAction = { [unowned self] view in
  46. self.doubleClickBookmark(nil)
  47. }
  48. }
  49. func updateStatus() {
  50. let row = outlineView.selectedRow
  51. var message = ""
  52. if row != -1 {
  53. if let bookmark = outlineView.item(atRow: row) as? KMBookmark {
  54. switch bookmark.bookmarkType {
  55. case .bookmark:
  56. message = bookmark.fileURL?.path ?? ""
  57. case .folder:
  58. let count = bookmark.children.count
  59. message = count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String(format: NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
  60. default:
  61. break
  62. }
  63. }
  64. }
  65. // statusBar.leftStringValue = message
  66. }
  67. //
  68. static func showBookmarkController() -> KMBookmarkController {
  69. let controller = KMBookmarkController.init(windowNibName: "KMBookmarkController")
  70. NSWindow.currentWindow().addChildWindow(controller.window!, ordered: NSWindow.OrderingMode.above)
  71. controller.window?.center()
  72. return controller
  73. }
  74. //
  75. //
  76. // //MARK: Recent Documents
  77. func recentDocumentInfo(at fileURL: URL) -> [String: Any]? {
  78. let path = fileURL.path
  79. for info in recentDocuments {
  80. // if let aliasData = info[ALIASDATA_KEY] as? Data,
  81. // let alias = SKAlias(aliasData),
  82. // alias.fileURLNoUI?.path.caseInsensitiveCompare(path) == .orderedSame {
  83. // return info
  84. // }
  85. }
  86. return nil
  87. }
  88. //
  89. func addRecentDocument(for fileURL: URL, pageIndex: UInt, scaleFactor factor: CGFloat, snapshots setups: [Any]?) {
  90. // if let info = recentDocumentInfo(at: fileURL) {
  91. // recentDocuments.removeObject(info)
  92. // }
  93. //
  94. // if let alias = SKAlias(url: fileURL) {
  95. // var bm: [String: Any] = [
  96. // PAGEINDEX_KEY: pageIndex,
  97. // SCALE_KEY: factor,
  98. // ALIASDATA_KEY: alias.data,
  99. // ALIAS_KEY: alias,
  100. // SNAPSHOTS_KEY: setups ?? []
  101. // ]
  102. // recentDocuments.insert(bm, at: 0)
  103. // if recentDocuments.count > maxRecentDocumentsCount {
  104. // recentDocuments.removeLastObject()
  105. // }
  106. // }
  107. }
  108. //
  109. // func pageIndex(forRecentDocumentAt fileURL: URL) -> UInt {
  110. // guard let fileURL = fileURL else { return UInt.max }
  111. // if let pageIndex = recentDocumentInfo(at: fileURL)?[PAGEINDEX_KEY] as? UInt {
  112. // return pageIndex
  113. // }
  114. // return UInt.max
  115. // }
  116. //
  117. // func scaleFactor(forRecentDocumentAt fileURL: URL) -> CGFloat {
  118. // guard let fileURL = fileURL else { return 0 }
  119. // if let scaleFactor = recentDocumentInfo(at: fileURL)?[SCALE_KEY] as? CGFloat {
  120. // return scaleFactor
  121. // }
  122. // return 0
  123. // }
  124. //
  125. // func snapshots(forRecentDocumentAt fileURL: URL) -> [Any]? {
  126. // guard let fileURL = fileURL else { return nil }
  127. // if let setups = recentDocumentInfo(at: fileURL)?[SNAPSHOTS_KEY] as? [Any], !setups.isEmpty {
  128. // return setups
  129. // }
  130. // return nil
  131. // }
  132. //
  133. // //MARK: Bookmarks support
  134. func getInsertionFolder(_ bookmarkPtr: inout KMBookmark?, childIndex indexPtr: inout Int) {
  135. let rowIndex = outlineView.clickedRow
  136. var indexes = outlineView.selectedRowIndexes
  137. if rowIndex != -1 && !indexes.contains(rowIndex) {
  138. indexes = IndexSet(integer: rowIndex)
  139. }
  140. let rowIdx = indexes.last ?? NSNotFound
  141. var item = KMBookmarkManager.manager.rootBookmark
  142. var idx = item.children.count
  143. if rowIdx != NSNotFound {
  144. if let selectedItem = outlineView.item(atRow: rowIdx) as? KMBookmark {
  145. if outlineView.isItemExpanded(selectedItem) {
  146. item = selectedItem as! KMRootBookmark
  147. idx = item.children.count
  148. } else if let parent = selectedItem.parent, let itemIdx = parent.children.firstIndex(of: selectedItem) {
  149. item = parent as! KMRootBookmark
  150. idx = itemIdx + 1
  151. }
  152. }
  153. }
  154. bookmarkPtr = item
  155. indexPtr = idx
  156. }
  157. @IBAction func openBookmark(_ sender: Any) {
  158. if let bookmark = (sender as AnyObject).representedObject as? KMBookmark {
  159. bookmark.open()
  160. }
  161. }
  162. @IBAction func doubleClickBookmark(_ sender: Any?) {
  163. let row = bookmarkOutlineView.outlineView.clickedRow
  164. if let bm = (row != -1 ? bookmarkOutlineView.outlineView.item(atRow: row) : nil) as? KMBookmark,
  165. [KMBookmarkType.bookmark, .session].contains(bm.bookmarkType) {
  166. bm.open()
  167. }
  168. }
  169. func deleteBookmarks(bookmarks: [KMBookmark]) {
  170. for item in minimumCoverForBookmarks(bookmarks).reversed() {
  171. guard let parent = item.parent, let itemIndex = parent.children.firstIndex(of: item) else { continue }
  172. parent.removeObjectFromChildren(index: itemIndex)
  173. }
  174. bookmarkOutlineView.reloadData()
  175. }
  176. @IBAction func insertBookmarkFolder(_ sender: Any) {
  177. let folder = KMFolderBookmark.folderBookmark(label: NSLocalizedString("Folder", comment: "default folder name"))
  178. var item: KMBookmark?
  179. var idx: Int = 0
  180. getInsertionFolder(&item, childIndex: &idx)
  181. item?.insert(child: folder, atIndex: idx)
  182. bookmarkOutlineView.outlineView.reloadData()
  183. let row = bookmarkOutlineView.outlineView.row(forItem: folder)
  184. if row > 0 {
  185. bookmarkOutlineView.outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
  186. bookmarkOutlineView.outlineView.editColumn(0, row: row, with: nil, select: true)
  187. }
  188. }
  189. @IBAction func insertBookmarkSeparator(_ sender: Any) {
  190. let separator = KMSeparatorBookmark()
  191. var item: KMBookmark?
  192. var idx: Int = 0
  193. getInsertionFolder(&item, childIndex: &idx)
  194. item?.insert(child: separator, atIndex: idx)
  195. bookmarkOutlineView.outlineView.reloadData()
  196. // let row = outlineView.row(forItem: separator)
  197. // outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
  198. }
  199. @IBAction func addBookmark(_ sender: Any) {
  200. let openPanel = NSOpenPanel()
  201. var types = [String]()
  202. for docClass in NSDocumentController.shared.documentClassNames {
  203. if let docClass = NSClassFromString(docClass) as? NSDocument.Type {
  204. types += docClass.readableTypes
  205. }
  206. }
  207. openPanel.allowsMultipleSelection = true
  208. openPanel.canChooseDirectories = true
  209. openPanel.allowedFileTypes = types
  210. openPanel.beginSheetModal(for: self.window!) { (result) in
  211. guard result == .OK else { return }
  212. let newBookmarks = KMBookmark.bookmarks(urls: openPanel.urls)
  213. if newBookmarks != nil {
  214. var item: KMBookmark?
  215. var index: Int = 0
  216. self.getInsertionFolder(&item, childIndex: &index)
  217. var indexes = IndexSet(integersIn: Int(index)..<Int(index + newBookmarks.count))
  218. item?.mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
  219. if item == self.bookmarkRoot || self.outlineView.isItemExpanded(item) {
  220. if item != self.bookmarkRoot {
  221. indexes.shift(startingAt: 0, by: self.outlineView.row(forItem: item) + 1)
  222. }
  223. self.outlineView.selectRowIndexes(indexes, byExtendingSelection: false)
  224. }
  225. }
  226. }
  227. }
  228. @IBAction func deleteBookmark(_ sender: Any) {
  229. print("deleteBookmark")
  230. guard let bookmark = bookmarkOutlineView.outlineView.selectedItem() as? KMBookmark else { return }
  231. self.deleteBookmarks(bookmarks: [bookmark])
  232. }
  233. //
  234. // @IBAction func toggleStatusBar(_ sender: Any) {
  235. // UserDefaults.standard.set(!statusBar.isVisible, forKey: SKShowBookmarkStatusBarKey)
  236. // statusBar.toggle(below: outlineView.enclosingScrollView, animate: sender != nil)
  237. // }
  238. //
  239. // func clickedBookmarks() -> [Any]? {
  240. // let row = outlineView.clickedRow
  241. // guard row != -1 else { return nil }
  242. // var indexes = outlineView.selectedRowIndexes
  243. // if !indexes.contains(row) {
  244. // indexes = IndexSet(integer: row)
  245. // }
  246. // return indexes.compactMap { outlineView.item(atRow: $0) }
  247. // }
  248. //
  249. // @IBAction func deleteBookmarks(_ sender: Any) {
  250. // guard let items = clickedBookmarks() as? [KMBookmark] else { return }
  251. // for item in items.reversed() {
  252. // if let parent = item.parent, let itemIndex = parent.children.index(of: item) {
  253. // parent.removeObject(fromChildrenAtIndex: itemIndex)
  254. // }
  255. // }
  256. // }
  257. //
  258. // @IBAction func openBookmarks(_ sender: Any) {
  259. // guard let items = clickedBookmarks() as? [KMBookmark] else { return }
  260. // for item in items.reversed() {
  261. // item.open()
  262. // }
  263. // }
  264. //
  265. // @IBAction func previewBookmarks(_ sender: Any) {
  266. // if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
  267. // QLPreviewPanel.shared().orderOut(nil)
  268. // } else if let row = outlineView.clickedRow {
  269. // outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
  270. // QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
  271. // }
  272. // }
  273. //
  274. //
  275. // // MARK: - NSMenu delegate methods
  276. // func addItemForBookmark(_ bookmark: KMBookmark, toMenu menu: NSMenu, isFolder: Bool, isAlternate: Bool) {
  277. // var item: NSMenuItem?
  278. // if isFolder {
  279. // item = menu.addItem(withSubmenuAndTitle: bookmark.label)
  280. // item?.submenu?.delegate = self
  281. // } else {
  282. // item = menu.addItem(withTitle: bookmark.label, action: #selector(openBookmark(_:)), target: self)
  283. // }
  284. // item?.representedObject = bookmark
  285. // if isAlternate {
  286. // item?.keyEquivalentModifierMask = .alternate
  287. // item?.isAlternate = true
  288. // item?.setImageAndSize(bookmark.alternateIcon)
  289. // } else {
  290. // item?.setImageAndSize(bookmark.icon)
  291. // }
  292. // }
  293. //
  294. // func menuNeedsUpdate(_ menu: NSMenu) {
  295. // if menu == outlineView.menu {
  296. // let row = outlineView.clickedRow
  297. // menu.removeAllItems()
  298. // if row != -1 {
  299. // menu.addItem(withTitle: NSLocalizedString("Remove", comment: "Menu item title"), action: #selector(deleteBookmarks(_:)), target: self)
  300. // menu.addItem(withTitle: NSLocalizedString("Open", comment: "Menu item title"), action: #selector(openBookmarks(_:)), target: self)
  301. // menu.addItem(withTitle: NSLocalizedString("Quick Look", comment: "Menu item title"), action: #selector(previewBookmarks(_:)), target: self)
  302. // menu.addItem(.separator())
  303. // }
  304. // menu.addItem(withTitle: NSLocalizedString("New Folder", comment: "Menu item title"), action: #selector(insertBookmarkFolder(_:)), target: self)
  305. // menu.addItem(withTitle: NSLocalizedString("New Separator", comment: "Menu item title"), action: #selector(insertBookmarkSeparator(_:)), target: self)
  306. // } else {
  307. // guard let supermenu = menu.supermenu, let idx = supermenu.indexOfItem(withSubmenu: menu), let bm = (supermenu == NSApp.mainMenu) ? bookmarkRoot : supermenu.item(at: idx)?.representedObject as? KMBookmark else { return }
  308. //
  309. // let bookmarks = bm.children
  310. // var i = menu.numberOfItems
  311. //
  312. // while i > 0 {
  313. // if let menuItem = menu.item(at: i - 1), menuItem.isSeparatorItem || menuItem.representedObject != nil {
  314. // menu.removeItem(menuItem)
  315. // }
  316. // i -= 1
  317. // }
  318. //
  319. // if supermenu == NSApp.mainMenu, let previousSession = previousSession {
  320. // menu.addItem(.separator())
  321. // addItemForBookmark(previousSession, toMenu: menu, isFolder: false, isAlternate: false)
  322. // addItemForBookmark(previousSession, toMenu: menu, isFolder: true, isAlternate: true)
  323. // }
  324. //
  325. // if menu.numberOfItems > 0, bookmarks.count > 0 {
  326. // menu.addItem(.separator())
  327. // }
  328. //
  329. // for bm in bookmarks {
  330. // switch bm.bookmarkType {
  331. // case .folder:
  332. // addItemForBookmark(bm, toMenu: menu, isFolder: true, isAlternate: false)
  333. // addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: true)
  334. // case .session:
  335. // addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: false)
  336. // addItemForBookmark(bm, toMenu: menu, isFolder: true, isAlternate: true)
  337. // case .separator:
  338. // menu.addItem(.separator())
  339. // default:
  340. // addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: false)
  341. // }
  342. // }
  343. // }
  344. // }
  345. //
  346. // // avoid rebuilding the bookmarks menu on every key event
  347. // func menuHasKeyEquivalent(_ menu: NSMenu, for event: NSEvent, target: AutoreleasingUnsafeMutablePointer<AnyObject?>?, action: UnsafeMutablePointer<Selector?>?) -> Bool { false }
  348. //
  349. // // MARK: - Toolbar
  350. //
  351. func setupToolbar() {
  352. // Create a new toolbar instance, and attach it to our document window
  353. let toolbar = NSToolbar(identifier: kBookmarksToolbarIdentifier)
  354. var dict = [String: NSToolbarItem]()
  355. // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
  356. toolbar.allowsUserCustomization = true
  357. toolbar.autosavesConfiguration = true
  358. toolbar.displayMode = .default
  359. // We are the delegate
  360. toolbar.delegate = self
  361. // Add template toolbar items
  362. var item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier))
  363. item.label = NSLocalizedString("New Folder", comment: "Toolbar item label")
  364. item.paletteLabel = NSLocalizedString("New Folder", comment: "Toolbar item label")
  365. item.toolTip = NSLocalizedString("Add a New Folder", comment: "Tool tip message")
  366. // item.image = NSImage(named: "NewFolder")
  367. item.image = NSImage(named: NSImage.folderName)!
  368. item.target = self
  369. item.action = #selector(insertBookmarkFolder(_:))
  370. dict[kBookmarksNewFolderToolbarItemIdentifier] = item
  371. item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier))
  372. item.label = NSLocalizedString("New Separator", comment: "Toolbar item label")
  373. item.paletteLabel = NSLocalizedString("New Separator", comment: "Toolbar item label")
  374. item.toolTip = NSLocalizedString("Add a New Separator", comment: "Tool tip message")
  375. // item.image = NSImage(named: "NewSeparator")
  376. item.image = NSImage(named: NSImage.shareTemplateName)!
  377. item.target = self
  378. item.action = #selector(insertBookmarkSeparator(_:))
  379. dict[kBookmarksNewSeparatorToolbarItemIdentifier] = item
  380. item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier))
  381. item.label = NSLocalizedString("Delete", comment: "Toolbar item label")
  382. item.paletteLabel = NSLocalizedString("Delete", comment: "Toolbar item label")
  383. item.toolTip = NSLocalizedString("Delete Selected Items", comment: "Tool tip message")
  384. item.image = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kToolbarDeleteIcon)))
  385. item.target = self
  386. item.action = #selector(deleteBookmark(_:))
  387. dict[kBookmarksDeleteToolbarItemIdentifier] = item
  388. toolbarItems = dict
  389. // Attach the toolbar to the window
  390. self.window?.toolbar = toolbar
  391. }
  392. //
  393. // // MARK: - Quick Look Panel Support
  394. //
  395. // func acceptsPreviewPanelControl(_ panel: QLPreviewPanel) -> Bool {
  396. // return true
  397. // }
  398. //
  399. // func beginPreviewPanelControl(_ panel: QLPreviewPanel) {
  400. // panel.delegate = self
  401. // panel.dataSource = self
  402. // }
  403. //
  404. // func endPreviewPanelControl(_ panel: QLPreviewPanel) {
  405. // }
  406. //
  407. // func previewItems() -> [KMBookmark] {
  408. // var items = [KMBookmark]()
  409. //
  410. // outlineView.selectedRowIndexes.enumerated().forEach { (idx, _) in
  411. // if let item = outlineView.item(atRow: idx) as? KMBookmark {
  412. // if item.bookmarkType == .bookmark {
  413. // items.append(item)
  414. // } else if item.bookmarkType == .session {
  415. // items.append(contentsOf: item.children)
  416. // }
  417. // }
  418. // }
  419. // return items
  420. // }
  421. //
  422. // func numberOfPreviewItems(in panel: QLPreviewPanel) -> Int {
  423. // return previewItems().count
  424. // }
  425. //
  426. // func previewPanel(_ panel: QLPreviewPanel, previewItemAt anIndex: Int) -> QLPreviewItem {
  427. // return previewItems()[anIndex]
  428. // }
  429. //
  430. // func previewPanel(_ panel: QLPreviewPanel, sourceFrameOnScreenForPreviewItem item: QLPreviewItem) -> NSRect {
  431. // var item = item
  432. // if let parent = (item as? KMBookmark)?.parent, parent.bookmarkType == .session {
  433. // item = parent
  434. // }
  435. // let row = outlineView.row(forItem: item)
  436. // var iconRect = NSZeroRect
  437. // if let item = item as? KMBookmark, row != -1 {
  438. // let cell = outlineView.preparedCell(atColumn: 0, row: row) as? SKTextWithIconCell
  439. // iconRect = cell?.iconRect(forBounds: outlineView.frameOfCell(atColumn: 0, row: row)) ?? NSZeroRect
  440. // if outlineView.visibleRect.intersects(iconRect) {
  441. // iconRect = outlineView.convert(iconRect, to: nil)
  442. // } else {
  443. // iconRect = NSZeroRect
  444. // }
  445. // }
  446. // return iconRect
  447. // }
  448. //
  449. // func previewPanel(_ panel: QLPreviewPanel, transitionImageForPreviewItem item: QLPreviewItem, contentRect: UnsafeMutablePointer<NSRect>) -> NSImage? {
  450. // var item = item
  451. // if let parent = (item as? KMBookmark)?.parent, parent.bookmarkType == .session {
  452. // item = parent
  453. // }
  454. // return (item as? KMBookmark)?.icon
  455. // }
  456. //
  457. // func previewPanel(_ panel: QLPreviewPanel, handle event: NSEvent) -> Bool {
  458. // if event.type == .keyDown {
  459. // outlineView.keyDown(with: event)
  460. // return true
  461. // }
  462. // return false
  463. // }
  464. //
  465. }
  466. extension KMBookmarkController: NSOutlineViewDelegate, NSOutlineViewDataSource {
  467. //MARK: NSOutlineViewDataSource
  468. func minimumCoverForBookmarks(_ items: [KMBookmark]) -> [KMBookmark] {
  469. var lastBm: KMBookmark?
  470. var minimalCover = [KMBookmark]()
  471. for bm in items {
  472. if !(bm.isDescendant(of: lastBm)) {
  473. minimalCover.append(bm)
  474. lastBm = bm
  475. }
  476. }
  477. return minimalCover
  478. }
  479. func outlineView(_ ov: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
  480. let bookmark = item as? KMBookmark ?? bookmarkRoot
  481. return bookmark.bookmarkType == .folder ? bookmark.children.count : 0
  482. }
  483. func outlineView(_ ov: NSOutlineView, isItemExpandable item: Any) -> Bool {
  484. let bookmark = item as! KMBookmark
  485. return bookmark.bookmarkType == .folder
  486. }
  487. func outlineView(_ ov: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
  488. let bookmark = (item as? KMBookmark) ?? bookmarkRoot
  489. return bookmark.objectOfChidren(index: index)
  490. }
  491. func outlineView(_ ov: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
  492. guard let column = tableColumn else { return nil }
  493. guard let bm = item as? KMBookmark else { return nil }
  494. let tcID = column.identifier
  495. switch tcID {
  496. case kLabelIdentifier:
  497. return [kTextWithIconStringKey: bm.label, kTextWithIconImageKey: bm.icon]
  498. case kFileIdentifier:
  499. if bm.bookmarkType == .folder || bm.bookmarkType == .session {
  500. let count = bm.children.count
  501. return count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String.localizedStringWithFormat(NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
  502. } else {
  503. return bm.fileURL?.path ?? ""
  504. }
  505. case kPageIdentifier:
  506. return bm.pageNumber
  507. default:
  508. return nil
  509. }
  510. }
  511. func outlineView(_ ov: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) {
  512. guard let column = tableColumn else { return }
  513. guard let bm = item as? KMBookmark else { return }
  514. let tcID = column.identifier
  515. switch tcID {
  516. case kLabelIdentifier:
  517. if let newLabel = (object as? [String: Any])?[kTextWithIconStringKey] as? String, newLabel != bm.label {
  518. bm.label = newLabel
  519. }
  520. case kPageIdentifier:
  521. if let newPageNumber = object as? Int, newPageNumber != bm.pageNumber.intValue {
  522. bm.pageNumber = newPageNumber as NSNumber
  523. }
  524. default:
  525. break
  526. }
  527. }
  528. func outlineView(_ ov: NSOutlineView, writeItems items: [Any], to pboard: NSPasteboard) -> Bool {
  529. draggedBookmarks = minimumCoverForBookmarks(items as! [KMBookmark])
  530. pboard.clearContents()
  531. pboard.setData(Data(), forType: kPasteboardTypeBookmarkRows)
  532. return true
  533. }
  534. func outlineView(_ ov: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
  535. guard index != NSOutlineViewDropOnItemIndex else { return [] }
  536. let pboard = info.draggingPasteboard
  537. if pboard.canReadItem(withDataConformingToTypes: [kPasteboardTypeBookmarkRows.rawValue]) && info.draggingSource as? NSOutlineView == ov {
  538. return .move
  539. } else if NSURL.canReadFileURL(from: pboard) {
  540. return .every
  541. }
  542. return []
  543. }
  544. func outlineView(_ ov: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
  545. let pboard = info.draggingPasteboard
  546. if pboard.canReadItem(withDataConformingToTypes: [kPasteboardTypeBookmarkRows.rawValue]) && info.draggingSource as? NSOutlineView == ov {
  547. var movedBookmarks = [KMBookmark]()
  548. var indexes = IndexSet()
  549. var insertionIndex = index
  550. let targetItem = item as? KMBookmark ?? bookmarkRoot
  551. for bookmark in draggedBookmarks {
  552. guard let parent = bookmark.parent else { continue }
  553. guard let bookmarkIndex = parent.children.firstIndex(of: bookmark) else { continue }
  554. if targetItem == parent {
  555. if insertionIndex > bookmarkIndex {
  556. insertionIndex -= 1
  557. }
  558. if insertionIndex == bookmarkIndex {
  559. continue
  560. }
  561. }
  562. parent.removeObjectFromChildren(index: bookmarkIndex)
  563. targetItem.insert(child: bookmark, atIndex: insertionIndex)
  564. movedBookmarks.append(bookmark)
  565. insertionIndex += 1
  566. }
  567. for bookmark in movedBookmarks {
  568. let row = ov.row(forItem: bookmark)
  569. if row != -1 {
  570. indexes.insert(row)
  571. }
  572. }
  573. if !indexes.isEmpty {
  574. ov.selectRowIndexes(indexes, byExtendingSelection: false)
  575. }
  576. return true
  577. } else {
  578. let urls = NSURL.readFileURLs(from: pboard)
  579. let newBookmarks = KMBookmark.bookmarks(urls: urls)
  580. if !newBookmarks.isEmpty {
  581. var indexes = IndexSet(integersIn: index..<(index + newBookmarks.count))
  582. (item as? KMBookmark ?? bookmarkRoot).mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
  583. if (item as? KMBookmark ?? bookmarkRoot) === bookmarkRoot || ov.isItemExpanded(item) {
  584. if (item as? KMBookmark ?? bookmarkRoot) !== bookmarkRoot {
  585. indexes.shift(startingAt: 0, by: ov.row(forItem: item) + 1)
  586. }
  587. ov.selectRowIndexes(indexes, byExtendingSelection: false)
  588. }
  589. return true
  590. }
  591. return false
  592. }
  593. }
  594. func outlineView(_ ov: NSOutlineView, dragEndedWith operation: NSDragOperation) {
  595. draggedBookmarks.removeAll()
  596. }
  597. // MARK: NSOutlineViewDelegate
  598. func outlineView(_ ov: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> Any? {
  599. if tableColumn == nil {
  600. return (item as? KMBookmark)?.bookmarkType == .separator ? KMSeparatorCell() : nil
  601. }
  602. return tableColumn?.dataCell(forRow: ov.row(forItem: item))
  603. }
  604. func outlineView(_ ov: NSOutlineView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
  605. guard let column = tableColumn else { return }
  606. guard let cell = cell as? NSCell else { return }
  607. if column.identifier == kFileIdentifier {
  608. if let bm = item as? KMBookmark {
  609. // if bm.bookmarkType == .folder || bm.bookmarkType == .session {
  610. // cell.textColor = .disabledControlTextColor
  611. // } else {
  612. // cell.textColor = .controlTextColor
  613. // }
  614. }
  615. }
  616. }
  617. func outlineView(_ ov: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
  618. guard let column = tableColumn else { return false }
  619. guard let bm = item as? KMBookmark else { return false }
  620. let tcID = column.identifier
  621. switch tcID {
  622. case kLabelIdentifier:
  623. return bm.bookmarkType != .separator
  624. case kPageIdentifier:
  625. return bm.pageIndex != NSNotFound
  626. default:
  627. return false
  628. }
  629. }
  630. func outlineView(_ ov: NSOutlineView, toolTipFor cell: NSCell, rect: UnsafeMutablePointer<NSRect>, tableColumn tc: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String {
  631. guard let column = tc else { return "" }
  632. guard let bm = item as? KMBookmark else { return "" }
  633. let tcID = column.identifier
  634. switch tcID {
  635. case kLabelIdentifier:
  636. return bm.label
  637. case kFileIdentifier:
  638. if bm.bookmarkType == .session {
  639. return ""
  640. // return bm.children.map { $0.path ?? "" }.joined(separator: "\n")
  641. } else if bm.bookmarkType == .folder {
  642. let count = bm.children.count
  643. return count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String.localizedStringWithFormat(NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
  644. } else {
  645. return bm.fileURL?.path ?? ""
  646. }
  647. case kPageIdentifier:
  648. return bm.pageNumber.stringValue
  649. default:
  650. return ""
  651. }
  652. }
  653. func outlineViewSelectionDidChange(_ notification: Notification) {
  654. updateStatus()
  655. if QLPreviewPanel.sharedPreviewPanelExists(), let previewPanel = QLPreviewPanel.shared(), previewPanel.isVisible, previewPanel.dataSource === self {
  656. previewPanel.reloadData()
  657. }
  658. }
  659. func outlineView(_ ov: NSOutlineView, deleteItems items: [Any]) {
  660. for item in minimumCoverForBookmarks(items as! [KMBookmark]).reversed() {
  661. guard let parent = item.parent, let itemIndex = parent.children.firstIndex(of: item) else { continue }
  662. parent.removeObjectFromChildren(index: itemIndex)
  663. }
  664. }
  665. func outlineView(_ ov: NSOutlineView, canDeleteItems items: [Any]) -> Bool {
  666. return !items.isEmpty
  667. }
  668. func outlineView(_ ov: NSOutlineView, copyItems items: [Any]) {
  669. var urls = [URL]()
  670. addBookmarkURLsToArray(minimumCoverForBookmarks(items as! [KMBookmark]), &urls)
  671. if !urls.isEmpty {
  672. let pboard = NSPasteboard.general
  673. pboard.clearContents()
  674. pboard.writeObjects(urls as [NSPasteboardWriting])
  675. }
  676. }
  677. func outlineView(_ ov: NSOutlineView, canCopyItems items: [Any]) -> Bool {
  678. return !items.isEmpty
  679. }
  680. func outlineView(_ ov: NSOutlineView, pasteFromPasteboard pboard: NSPasteboard) {
  681. let urls = NSURL.readFileURLs(from: pboard)
  682. let newBookmarks = KMBookmark.bookmarks(urls: urls)
  683. if !newBookmarks.isEmpty {
  684. var item: KMBookmark?
  685. var anIndex = 0
  686. getInsertionFolder(&item, childIndex: &anIndex)
  687. var indexes = IndexSet(integersIn: anIndex..<(anIndex + newBookmarks.count))
  688. (item ?? bookmarkRoot).mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
  689. if item === bookmarkRoot || ov.isItemExpanded(item) {
  690. if item !== bookmarkRoot {
  691. indexes.shift(startingAt: 0, by: ov.row(forItem: item) + 1)
  692. }
  693. ov.selectRowIndexes(indexes, byExtendingSelection: false)
  694. }
  695. }
  696. }
  697. func outlineView(_ ov: NSOutlineView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool {
  698. return NSURL.canReadFileURL(from: pboard)
  699. }
  700. func outlineView(_ ov: NSOutlineView, typeSelectHelperSelectionStrings typeSelectHelper: SKTypeSelectHelper) -> [String] {
  701. let count = ov.numberOfRows
  702. var labels = [String]()
  703. for i in 0..<count {
  704. if let label = ov.item(atRow: i) {
  705. labels.append(label as! String)
  706. }
  707. }
  708. return labels
  709. }
  710. // func outlineView(_ ov: NSOutlineView, typeSelectHelper typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
  711. // statusBar.setLeftStringValue(String.localizedStringWithFormat(NSLocalizedString("No match: \"%@\"", comment: "Status message"), searchString))
  712. // }
  713. //
  714. // func outlineView(_ ov: NSOutlineView, typeSelectHelper typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String?) {
  715. // if let searchString = searchString {
  716. // statusBar.setLeftStringValue(String.localizedStringWithFormat(NSLocalizedString("Finding: \"%@\"", comment: "Status message"), searchString))
  717. // } else {
  718. // updateStatus()
  719. // }
  720. // }
  721. func addBookmarkURLsToArray(_ items: [KMBookmark], _ array: inout [URL]) {
  722. for bm in items {
  723. if bm.bookmarkType == .bookmark {
  724. if let url = bm.fileURL {
  725. array.append(url)
  726. }
  727. } else if bm.bookmarkType != .separator {
  728. addBookmarkURLsToArray(bm.children, &array)
  729. }
  730. }
  731. }
  732. }
  733. extension KMBookmarkController: NSToolbarDelegate, NSToolbarItemValidation {
  734. func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
  735. // return [.flexibleSpace, .yourItem1, .yourItem2, .yourItem3]
  736. return [
  737. NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier),
  738. NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier),
  739. NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier)
  740. ]
  741. }
  742. func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
  743. // return [.yourItem1, .yourItem2, .yourItem3, .flexibleSpace, .space]
  744. return [
  745. NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier),
  746. NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier),
  747. NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier),
  748. .flexibleSpace,
  749. .space
  750. ]
  751. }
  752. func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
  753. return toolbarItems[itemIdentifier.rawValue]
  754. }
  755. func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
  756. // guard let toolbar = self.window?.toolbar else { return false }
  757. //
  758. // if toolbar.customizationPaletteIsRunning {
  759. // return false
  760. // } else if toolbarItem.itemIdentifier == kBookmarksDeleteToolbarItemIdentifier {
  761. // return outlineView.canDelete
  762. // }
  763. return true
  764. }
  765. }
  766. extension KMBookmarkController: NSMenuDelegate, NSMenuItemValidation {
  767. func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
  768. // if menuItem.action == #selector(toggleStatusBar(_:)) {
  769. // if statusBar.isVisible {
  770. // menuItem.title = NSLocalizedString("Hide Status Bar", comment: "Menu item title")
  771. // } else {
  772. // menuItem.title = NSLocalizedString("Show Status Bar", comment: "Menu item title")
  773. // }
  774. // return true
  775. // } else if menuItem.action == #selector(addBookmark(_:)) {
  776. // return menuItem.tag == 0
  777. // }
  778. return true
  779. }
  780. }
  781. extension NSURL {
  782. static func canReadFileURL(from pboard: NSPasteboard) -> Bool {
  783. let canReadFileURLsOnly = [NSPasteboard.ReadingOptionKey.urlReadingFileURLsOnly: true]
  784. let canReadClasses = [NSURL.self]
  785. return pboard.canReadObject(forClasses: canReadClasses, options: canReadFileURLsOnly) ||
  786. pboard.canReadItem(withDataConformingToTypes: [NSPasteboard.PasteboardType.fileURL.rawValue])
  787. }
  788. static func readFileURLs(from pboard: NSPasteboard) -> [URL] {
  789. if let fileURLs = pboard.readObjects(forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true]) as? [URL], !fileURLs.isEmpty {
  790. return fileURLs
  791. } else if ((pboard.types?.contains(.fileURL)) != nil) {
  792. if let filenames = pboard.propertyList(forType: .fileURL) as? [String] {
  793. return filenames.compactMap { URL(fileURLWithPath: $0) }
  794. }
  795. }
  796. return []
  797. }
  798. }