KMThumbnailView.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. //
  2. // KMThumbnailView.swift
  3. // PDF Master
  4. //
  5. // Created by tangchao on 2023/5/4.
  6. //
  7. import Cocoa
  8. @objc enum KMThumbnailViewDragInfoKey: Int {
  9. case dropOperation = 0
  10. case draggingInfo = 1
  11. }
  12. @objc protocol KMThumbnailViewDelegate : NSObjectProtocol {
  13. // layout
  14. @objc optional func thumbnailView(thumbanView: KMThumbnailView, minimumLineSpacingForSectionAt section: Int) -> CGFloat
  15. @objc optional func thumbnailView(thumbanView: KMThumbnailView, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
  16. @objc optional func thumbnailView(thumbanView: KMThumbnailView, insetForSectionAt section: Int) -> NSEdgeInsets
  17. @objc optional func thumbnailView(thumbanView: KMThumbnailView, sizeForItemAt indexpath: IndexPath) -> NSSize
  18. @objc optional func thumbnailView(thumbanView: KMThumbnailView, numberOfItemsInSection section: Int) -> Int
  19. @objc optional func thumbnailView(thumbanView: KMThumbnailView, itemForRepresentedObjectAt indexpath: IndexPath) -> NSCollectionViewItem
  20. // Drag & Drop
  21. @objc optional func thumbnailView(thumbanView: KMThumbnailView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet)
  22. @objc optional func thumbnailView(thumbanView: KMThumbnailView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation)
  23. @objc optional func thumbnailView(thumbanView: KMThumbnailView, canPasteboardWriterForItemAt indexPath: IndexPath) -> Bool
  24. @objc optional func thumbnailView(thumbanView: KMThumbnailView, shouldPasteboardWriterForItemAt indexPath: IndexPath) -> Bool
  25. @objc optional func thumbnailView(thumbanView: KMThumbnailView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting?
  26. @objc optional func thumbnailView(thumbanView: KMThumbnailView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool
  27. @objc optional func thumbnailView(thumbanView: KMThumbnailView, shouldAcceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool
  28. // 本地拖拽
  29. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didDrag dragedIndexPaths: [IndexPath], indexpath: IndexPath, dragInfo:[KMThumbnailViewDragInfoKey.RawValue:Any])
  30. // 外部拖拽
  31. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didDragAddFiles files: [URL], indexpath: IndexPath)
  32. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didSelectItemAt indexpath: IndexPath, object: AnyObject?)
  33. @objc optional func thumbnailView(thumbanView: KMThumbnailView, rightMouseDidClick indexpath: IndexPath, item: NSCollectionViewItem?, object: AnyObject?)
  34. }
  35. private let _KMThumbnailView_collectionViewDrop_lineViewClassName = "NSCollectionViewDropTargetGapIndicator"
  36. @objc class KMThumbnailView_CollectionView: NSCollectionView {
  37. // 是否点击空白取消选中 默认取消选中
  38. var mouseDownCancelSelected: Bool = true
  39. var hasDragLineView: Bool = true {
  40. didSet {
  41. if (!self.hasDragLineView) {
  42. // 隐藏视图
  43. for view in self.subviews {
  44. guard let lineClass = NSClassFromString(_KMThumbnailView_collectionViewDrop_lineViewClassName) else {
  45. continue
  46. }
  47. if (view.isKind(of: lineClass)) {
  48. // view.frame = NSZeroRect
  49. view.isHidden = true
  50. }
  51. }
  52. }
  53. }
  54. }
  55. override func addSubview(_ view: NSView) {
  56. if (self.hasDragLineView) {
  57. return super.addSubview(view)
  58. }
  59. if let lineClass = NSClassFromString(_KMThumbnailView_collectionViewDrop_lineViewClassName) {
  60. if (view.isKind(of: lineClass)) {
  61. Swift.debugPrint("Find Line View......")
  62. return
  63. } else {
  64. super.addSubview(view)
  65. }
  66. }
  67. super.addSubview(view)
  68. }
  69. override func mouseDown(with event: NSEvent) {
  70. let point = convert(event.locationInWindow, from: nil)
  71. if self.mouseDownCancelSelected {
  72. super.mouseDown(with: event)
  73. } else {
  74. if let indexPath = indexPathForItem(at: point) {
  75. // 用户点击了一个项,处理点击事件
  76. super.mouseDown(with: event)
  77. } else {
  78. // 用户点击了空白区域,不取消选中项
  79. }
  80. }
  81. }
  82. }
  83. @objc class KMThumbnailView: NSView {
  84. open weak var delegate: KMThumbnailViewDelegate?
  85. internal let localForDraggedTypes = kKMLocalForDraggedTypes
  86. internal var dragedIndexPaths: [IndexPath] = []
  87. var kmAllowedFileTypes: [String]?
  88. // 记录拖拽移动后的位置
  89. fileprivate var _dragMoveFlagIndexpath: IndexPath?
  90. fileprivate var _dragMoveFlagIndexs: IndexSet?
  91. // 是否隐藏拖放线 dragMoveEffectAnimated 属性为false才有效
  92. var hasDragLineView: Bool = true {
  93. didSet {
  94. if (!self.dragMoveEffectAnimated) {
  95. self.collectionView_.hasDragLineView = self.hasDragLineView
  96. } else {
  97. self.collectionView_.hasDragLineView = false
  98. }
  99. }
  100. }
  101. // 是否显示拖放移动动效 为true时 hasDragLineView属性设置是
  102. var dragMoveEffectAnimated: Bool = false {
  103. didSet {
  104. self.hasDragLineView = !self.dragMoveEffectAnimated
  105. }
  106. }
  107. override init(frame frameRect: NSRect) {
  108. super.init(frame: frameRect)
  109. self.initDefaultValue()
  110. self.initSubViews()
  111. }
  112. required public init?(coder: NSCoder) {
  113. super.init(coder: coder)
  114. self.initDefaultValue()
  115. self.initSubViews()
  116. }
  117. open var minimumLineSpacing: CGFloat = 0.0 {
  118. didSet {
  119. self.reloadData()
  120. }
  121. }
  122. open var minimumInteritemSpacing: CGFloat = 0.0 {
  123. didSet {
  124. self.reloadData()
  125. }
  126. }
  127. open var itemSize: NSSize = NSMakeSize(60, 80) {
  128. didSet {
  129. self.reloadData()
  130. }
  131. }
  132. open var sectionInset: NSEdgeInsets = NSEdgeInsetsZero {
  133. didSet {
  134. self.reloadData()
  135. }
  136. }
  137. open var numberOfSections: Int = 0 {
  138. didSet {
  139. self.reloadData()
  140. }
  141. }
  142. var selectionIndexPaths: Set<IndexPath> {
  143. get {
  144. return self.collectionView.selectionIndexPaths
  145. }
  146. set {
  147. var indexpaths: Set<IndexPath> = []
  148. for indexpath in newValue {
  149. if (indexpath.section >= self.collectionView.numberOfSections) {
  150. continue
  151. }
  152. if (indexpath.item >= self.collectionView.numberOfItems(inSection: indexpath.section)) {
  153. continue
  154. }
  155. indexpaths.insert(indexpath)
  156. }
  157. self.collectionView.selectionIndexPaths = indexpaths
  158. }
  159. }
  160. // MARK: - Publick Methods
  161. public func initDefaultValue() {
  162. self.collectionView.registerForDraggedTypes([self.localForDraggedTypes, .fileURL,.string,.pdf])
  163. }
  164. public func initSubViews() {
  165. self.addSubview(self.scrollView)
  166. self.scrollView.frame = self.bounds
  167. self.scrollView.autoresizingMask = [.width, .height]
  168. self.scrollView.documentView = self.collectionView
  169. }
  170. // MARK: - register ItemClass
  171. open func register(_ itemClass: AnyClass?) {
  172. guard let itemClass_ = itemClass else {
  173. return
  174. }
  175. self.register(itemClass_, forItemWithIdentifier: NSStringFromClass(itemClass_))
  176. }
  177. open func register(_ itemClass: AnyClass?, forItemWithIdentifier identifier: String) {
  178. self.collectionView.register(itemClass, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier))
  179. }
  180. // MARK: - 刷新UI
  181. public func reloadData(indexs: Set<IndexPath> = []) {
  182. if (indexs.count == 0) {
  183. if (Thread.isMainThread) {
  184. self.collectionView.reloadData()
  185. } else {
  186. DispatchQueue.main.async {
  187. self.collectionView.reloadData()
  188. }
  189. }
  190. } else {
  191. var indexpaths: Set<IndexPath> = []
  192. for index in indexs {
  193. if (index.section >= self.collectionView.numberOfSections) {
  194. continue
  195. }
  196. if (index.item >= self.collectionView.numberOfItems(inSection: index.section)) {
  197. continue
  198. }
  199. indexpaths.insert(index)
  200. }
  201. if (indexpaths.count == 0) {
  202. return
  203. }
  204. if (Thread.isMainThread) {
  205. self.collectionView.reloadItems(at: indexpaths)
  206. } else {
  207. DispatchQueue.main.async {
  208. self.collectionView.reloadItems(at: indexpaths)
  209. }
  210. }
  211. }
  212. }
  213. // MARK: - 属性 【懒加载】
  214. internal lazy var scrollView_: NSScrollView = {
  215. let view = NSScrollView()
  216. view.hasHorizontalScroller = true
  217. view.hasVerticalScroller = true
  218. view.autohidesScrollers = true
  219. view.minMagnification = 1.0
  220. view.scrollerStyle = .overlay
  221. view.wantsLayer = true
  222. view.layer?.backgroundColor = NSColor.clear.cgColor
  223. view.wantsLayer = true
  224. view.contentView.layer?.backgroundColor = .white
  225. return view
  226. }()
  227. var scrollView: NSScrollView {
  228. get {
  229. return self.scrollView_
  230. }
  231. }
  232. internal lazy var collectionView_: KMThumbnailView_CollectionView = {
  233. let view = KMThumbnailView_CollectionView()
  234. view.autoresizingMask = [.width, .height]
  235. let layout = NSCollectionViewFlowLayout()
  236. layout.sectionInset = NSEdgeInsetsMake(8, 15, 8, 15)
  237. layout.minimumLineSpacing = 0
  238. layout.minimumInteritemSpacing = 0
  239. view.collectionViewLayout = layout
  240. view.delegate = self
  241. view.dataSource = self
  242. view.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem"))
  243. view.isSelectable = true
  244. view.wantsLayer = true
  245. view.layer?.backgroundColor = NSColor.km_init(hex: "#F7F8FA").cgColor
  246. return view
  247. }()
  248. var collectionView: KMThumbnailView_CollectionView {
  249. get {
  250. return self.collectionView_
  251. }
  252. }
  253. // MARK: - Private Methods
  254. private func _moveItemForDrag(to toIndexPath: IndexPath) -> Bool {
  255. if (self.dragedIndexPaths.isEmpty) {
  256. Swift.debugPrint("not find selected items.\(self.dragedIndexPaths)")
  257. return false
  258. }
  259. if (self.dragedIndexPaths.contains(toIndexPath) == false) { // 目标位置不在拖放数组里
  260. if (self._dragMoveFlagIndexpath == nil) { // 第一次移动
  261. self.collectionView.animator().moveItem(at: self.dragedIndexPaths.first!, to: toIndexPath)
  262. Swift.debugPrint("moveBegin, \(self.dragedIndexPaths.first!.item)->\(toIndexPath.item)")
  263. // 标记位置
  264. self._dragMoveFlagIndexpath = toIndexPath
  265. return true
  266. } else { // 已有移动
  267. if (self._dragMoveFlagIndexpath! != toIndexPath) {
  268. self.collectionView.animator().moveItem(at: self._dragMoveFlagIndexpath!, to: toIndexPath)
  269. Swift.debugPrint("move..., \(self._dragMoveFlagIndexpath!.item)->\(toIndexPath.item)")
  270. // 标记位置
  271. self._dragMoveFlagIndexpath = toIndexPath
  272. return true
  273. } else {
  274. Swift.debugPrint("drag... ")
  275. }
  276. }
  277. } else if (self._dragMoveFlagIndexpath != nil) { // 目标位置在拖放数组里且已有移动
  278. if (self._dragMoveFlagIndexpath! != toIndexPath) {
  279. self.collectionView.animator().moveItem(at: self._dragMoveFlagIndexpath!, to: toIndexPath)
  280. Swift.debugPrint("move..., \(self._dragMoveFlagIndexpath!.item)->\(toIndexPath.item)")
  281. // 标记位置
  282. self._dragMoveFlagIndexpath = toIndexPath
  283. return true
  284. } else {
  285. Swift.debugPrint("drag... ")
  286. }
  287. }
  288. return false
  289. }
  290. private func _moveItemsForDrag(at itemIndexPaths: Set<IndexPath>, to toIndexPath: IndexPath) -> Bool {
  291. if (itemIndexPaths.isEmpty) {
  292. return false
  293. }
  294. var itemIndexs = IndexSet()
  295. for ip in itemIndexPaths {
  296. itemIndexs.insert(ip.item)
  297. }
  298. return self._moveItemsForDrag(at: itemIndexs, to: toIndexPath.item)
  299. }
  300. private func _moveItemsForDrag(at itemIndexs: IndexSet, to toIndex: Int) -> Bool {
  301. if (itemIndexs.isEmpty) {
  302. return false
  303. }
  304. var flagIndexs = IndexSet()
  305. if (itemIndexs.first! > toIndex) { // 往前拖放
  306. // 3,4 -> 2
  307. // 3->2,4->3
  308. var cnt: Int = 0
  309. for item in itemIndexs {
  310. let indexpath = IndexPath(item: item, section: 0)
  311. let _toIndexPath = IndexPath(item: toIndex+cnt, section: 0)
  312. self.collectionView.animator().moveItem(at: indexpath, to: _toIndexPath)
  313. // 标记位置
  314. flagIndexs.insert(_toIndexPath.item)
  315. cnt += 1
  316. }
  317. } else if (itemIndexs.last! < toIndex) { // 往后拖放
  318. // 1,2 -> 3
  319. // 2->3, 1->2
  320. // 1,2 -> 4
  321. // 2->4, 1->3
  322. // 1,3 -> 4
  323. // 3->4, 1->3
  324. var cnt: Int = 0
  325. for item in itemIndexs.reversed() {
  326. let indexpath = IndexPath(item: item, section: 0)
  327. let _toIndexPath = IndexPath(item: toIndex-cnt, section: 0)
  328. self.collectionView.animator().moveItem(at: indexpath, to: _toIndexPath)
  329. // 标记位置
  330. flagIndexs.insert(_toIndexPath.item)
  331. cnt += 1
  332. }
  333. } else { // 往中间拖放(选中不连续)
  334. // 1,3 -> 2
  335. // 1->2, ...
  336. var cnt: Int = 0
  337. for item in itemIndexs {
  338. let indexpath = IndexPath(item: item, section: 0)
  339. if (cnt == 0) { // 第一个
  340. let _toIndexPath = IndexPath(item: toIndex, section: 0)
  341. self.collectionView.animator().moveItem(at: indexpath, to: _toIndexPath)
  342. flagIndexs.insert(toIndex)
  343. } else { // 第二个...
  344. if (indexpath.item == toIndex + cnt) { // 已在对应的位置
  345. // no doings
  346. flagIndexs.insert(toIndex+cnt)
  347. } else { // 需要移动
  348. let _toIndexPath = IndexPath(item: toIndex+cnt, section: 0)
  349. self.collectionView.animator().moveItem(at: indexpath, to: _toIndexPath)
  350. flagIndexs.insert(_toIndexPath.item)
  351. }
  352. }
  353. cnt += 1
  354. }
  355. }
  356. // 标记位置
  357. self._dragMoveFlagIndexs = flagIndexs
  358. return true
  359. }
  360. private func _doDragMoveEffect(to toIndexPath: IndexPath) -> Bool {
  361. if (self.dragedIndexPaths.isEmpty) {
  362. Swift.debugPrint("not find selected items.\(self.dragedIndexPaths)")
  363. return false
  364. }
  365. if (self.dragedIndexPaths.contains(toIndexPath) == false) { // 目标位置不在拖放数组里
  366. if (self._dragMoveFlagIndexs == nil) { // 第一次移动
  367. var itemIndexPaths = Set<IndexPath>()
  368. for indexpath in self.dragedIndexPaths {
  369. itemIndexPaths.insert(indexpath)
  370. }
  371. return self._moveItemsForDrag(at: itemIndexPaths, to: toIndexPath)
  372. } else { // 已有移动
  373. if (self._dragMoveFlagIndexs!.contains(toIndexPath.item) == false) {
  374. return self._moveItemsForDrag(at: self._dragMoveFlagIndexs!, to: toIndexPath.item)
  375. } else {
  376. Swift.debugPrint("drag... ")
  377. }
  378. }
  379. } else if (self._dragMoveFlagIndexs != nil) { // 目标位置在拖放数组里且已有移动
  380. if (self._dragMoveFlagIndexs!.contains(toIndexPath.item) == false) {
  381. return self._moveItemsForDrag(at: self._dragMoveFlagIndexs!, to: toIndexPath.item)
  382. } else {
  383. Swift.debugPrint("drag... ")
  384. }
  385. }
  386. return false
  387. }
  388. }
  389. // MARK: - NSCollectionViewDataSource, NSCollectionViewDelegate
  390. extension KMThumbnailView: NSCollectionViewDataSource {
  391. public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  392. if let items = self.delegate?.thumbnailView?(thumbanView: self, numberOfItemsInSection: section) {
  393. return items
  394. }
  395. return self.numberOfSections
  396. }
  397. func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  398. if let item = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) {
  399. return item
  400. }
  401. return NSCollectionViewItem()
  402. }
  403. }
  404. // MARK: - NSCollectionViewDelegate
  405. extension KMThumbnailView: NSCollectionViewDelegate {
  406. // func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  407. // return indexPaths
  408. // }
  409. func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  410. if let lastSelectedIndexPath = collectionView.selectionIndexPaths.first {
  411. if NSApp.currentEvent?.modifierFlags.contains(.shift) == true {
  412. // Shift 键按住,进行连续多选
  413. let selectedIndexPaths = collectionView.selectionIndexPaths
  414. var allIndexPaths = Set<IndexPath>(selectedIndexPaths)
  415. // 获取两个 IndexPath 之间的所有 IndexPath
  416. let startIndex = lastSelectedIndexPath.item
  417. let endIndex = indexPaths.first?.item ?? startIndex
  418. let range = startIndex < endIndex ? startIndex...endIndex : endIndex...startIndex
  419. for index in range {
  420. let indexPath = IndexPath(item: index, section: lastSelectedIndexPath.section)
  421. allIndexPaths.insert(indexPath)
  422. }
  423. return allIndexPaths
  424. }
  425. }
  426. return indexPaths
  427. }
  428. func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {}
  429. func collectionView(_ collectionView: NSCollectionView, shouldDeselectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  430. return indexPaths
  431. }
  432. func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set<IndexPath>) {}
  433. func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
  434. if let can = self.delegate?.thumbnailView?(thumbanView: self, canDragItemsAt: indexPaths, with: event) {
  435. return can
  436. }
  437. return true
  438. }
  439. func collectionView(_ collectionView: NSCollectionView, writeItemsAt indexPaths: Set<IndexPath>, to pasteboard: NSPasteboard) -> Bool {
  440. let data: Data = try! NSKeyedArchiver.archivedData(withRootObject: indexPaths, requiringSecureCoding: true)
  441. pasteboard.declareTypes([self.localForDraggedTypes], owner: self)
  442. pasteboard.setData(data, forType: self.localForDraggedTypes)
  443. self.dragedIndexPaths.removeAll()
  444. for indexPath in indexPaths {
  445. self.dragedIndexPaths.append(indexPath)
  446. }
  447. return true
  448. }
  449. func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) {
  450. self.delegate?.thumbnailView?(thumbanView: self, draggingSession: session, willBeginAt: screenPoint, forItemsAt: indexes)
  451. }
  452. func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
  453. self._dragMoveFlagIndexpath = nil
  454. self._dragMoveFlagIndexs = nil
  455. self.delegate?.thumbnailView?(thumbanView: self, draggingSession: session, endedAt: screenPoint, dragOperation: operation)
  456. }
  457. func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  458. let pboard = draggingInfo.draggingPasteboard
  459. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  460. return .move
  461. } else if (pboard.availableType(from: [.localDraggedTypes]) != nil) {
  462. if (proposedDropOperation.pointee == .on && self.dragMoveEffectAnimated) {
  463. _ = self._doDragMoveEffect(to: proposedDropIndexPath.pointee as IndexPath)
  464. }
  465. return .move
  466. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  467. guard let pbItems = pboard.pasteboardItems else {
  468. return NSDragOperation(rawValue: 0)
  469. }
  470. guard let _allowedFileTypes = self.kmAllowedFileTypes else {
  471. return .generic
  472. }
  473. var hasValidFile = false
  474. for item in pbItems {
  475. guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else {
  476. continue
  477. }
  478. let type = _url.pathExtension.lowercased()
  479. if (_allowedFileTypes.contains(type)) {
  480. hasValidFile = true
  481. break
  482. }
  483. }
  484. if (!hasValidFile) {
  485. return NSDragOperation(rawValue: 0)
  486. }
  487. }
  488. return .generic
  489. }
  490. func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
  491. if let should = self.delegate?.thumbnailView?(thumbanView: self, shouldAcceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation), !should {
  492. return should
  493. }
  494. let pboard = draggingInfo.draggingPasteboard
  495. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  496. let dragInfo = [
  497. KMThumbnailViewDragInfoKey.draggingInfo.rawValue : draggingInfo,
  498. KMThumbnailViewDragInfoKey.dropOperation.rawValue : dropOperation
  499. ] as [Int : Any]
  500. self.delegate?.thumbnailView?(thumbanView: self, didDrag: self.dragedIndexPaths, indexpath: indexPath, dragInfo: dragInfo)
  501. self.dragedIndexPaths.removeAll()
  502. return true
  503. } else if (pboard.availableType(from: [.localDraggedTypes]) != nil) {
  504. var _dragIndexpaths = Set<IndexPath>()
  505. draggingInfo.enumerateDraggingItems(
  506. options: NSDraggingItemEnumerationOptions.concurrent,
  507. for: collectionView,
  508. classes: [NSPasteboardItem.self],
  509. searchOptions: [:],
  510. using: {(draggingItem, idx, stop) in
  511. if let pasteboardItem = draggingItem.item as? NSPasteboardItem {
  512. do {
  513. if let indexPathData = pasteboardItem.data(forType: .localDraggedTypes), let _indexPath =
  514. try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(indexPathData) as? IndexPath {
  515. _dragIndexpaths.insert(_indexPath)
  516. }
  517. } catch {
  518. Swift.debugPrint("failed to unarchive indexPath for dropped photo item.")
  519. }
  520. }
  521. })
  522. let dragInfo = [
  523. KMThumbnailViewDragInfoKey.draggingInfo.rawValue : draggingInfo,
  524. KMThumbnailViewDragInfoKey.dropOperation.rawValue : dropOperation
  525. ] as [Int : Any]
  526. self.delegate?.thumbnailView?(thumbanView: self, didDrag: _dragIndexpaths.sorted(), indexpath: indexPath, dragInfo: dragInfo)
  527. self.dragedIndexPaths.removeAll()
  528. return true
  529. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  530. var array: [URL] = []
  531. for item: NSPasteboardItem in pboard.pasteboardItems! {
  532. let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)!
  533. let url = NSURL(string: string)
  534. array.append(url! as URL)
  535. }
  536. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath)
  537. return true
  538. }
  539. return false
  540. }
  541. }
  542. // MARK: - NSCollectionViewDelegateFlowLayout
  543. extension KMThumbnailView: NSCollectionViewDelegateFlowLayout {
  544. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  545. if let size_ = self.delegate?.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) {
  546. return size_
  547. }
  548. return self.itemSize
  549. }
  550. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  551. if let minimumLineSpacing_ = self.delegate?.thumbnailView?(thumbanView: self, minimumLineSpacingForSectionAt: section) {
  552. return minimumLineSpacing_
  553. }
  554. return self.minimumLineSpacing
  555. }
  556. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  557. if let minimumInteritemSpacing_ = self.delegate?.thumbnailView?(thumbanView: self, minimumInteritemSpacingForSectionAt: section) {
  558. return minimumInteritemSpacing_
  559. }
  560. return self.minimumInteritemSpacing
  561. }
  562. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets {
  563. if let inset = self.delegate?.thumbnailView?(thumbanView: self, insetForSectionAt: section) {
  564. return inset
  565. }
  566. return self.sectionInset
  567. }
  568. }