KMBookmarkController.swift 38 KB

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