KMBookmarkController.swift 38 KB

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