KMPDFThumbnailView.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. //
  2. // KMPDFThumbnailView.swift
  3. // PDF Master
  4. //
  5. // Created by lxy on 2022/12/15.
  6. //
  7. class KMPDFThumbnailView: KMThumbnailView {
  8. var document: CPDFDocument?
  9. var isShowPageSize = false
  10. var thumbnailSzie = NSZeroSize
  11. //标记线
  12. var isNeedMarkerLine: Bool = false
  13. var markerLineView: NSView = NSView()
  14. var markBeginIndexes: IndexSet = IndexSet()
  15. //注释状态
  16. var annotationShowState: KMAnnotationViewShowType = .none {
  17. didSet {
  18. self.collectionView.reloadData()
  19. }
  20. }
  21. //悬浮
  22. var hoverIndex: Int = -1
  23. var filePromiseQueue: OperationQueue = {
  24. let queue = OperationQueue()
  25. return queue
  26. }()
  27. fileprivate var dragFilePath: String?
  28. fileprivate var dragFlag = false
  29. fileprivate var dragIn = false
  30. var limit = false
  31. override func initDefaultValue() {
  32. super.initDefaultValue()
  33. self.wantsLayer = true
  34. self.layer?.backgroundColor = NSColor.km_init(hex: "#F7F8FA").cgColor
  35. self.thumbnailSzie = CGSize(width: 120, height: 155)
  36. self.minimumLineSpacing = 8
  37. self.register(KMPDFThumbnailItem.self)
  38. self.collectionView.registerForDraggedTypes(NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) })
  39. self.collectionView.setDraggingSourceOperationMask([.copy, .delete], forLocal: true)
  40. }
  41. override func initSubViews() {
  42. super.initSubViews()
  43. self.markerLineView.wantsLayer = true
  44. self.markerLineView.layer?.backgroundColor = NSColor.km_init(hex: "#1770F4").cgColor
  45. self.markerLineView.frame = CGRectMake(0, 0, 100, 2)
  46. self.collectionView.addSubview(markerLineView)
  47. self.hiddenMarkerLineView()
  48. self.markerLineView.isHidden = true
  49. }
  50. }
  51. extension KMPDFThumbnailView {
  52. override func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  53. if let size_ = self.delegate?.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) {
  54. return size_
  55. }
  56. let page = self.document?.page(at: UInt(indexPath.item))
  57. let height = KMPDFThumbnailItem.sizeToFit(size: self.thumbnailSzie, page: page!, isShow: self.isShowPageSize)
  58. return NSMakeSize(collectionView.frame.size.width - 20, CGFloat(height))
  59. }
  60. override func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  61. if let count = self.document?.pageCount {
  62. return Int(count)
  63. }
  64. return 0
  65. }
  66. override func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  67. if let item = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) {
  68. return item
  69. }
  70. let cellView: KMPDFThumbnailItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: NSStringFromClass(KMPDFThumbnailItem.self)), for: indexPath) as! KMPDFThumbnailItem
  71. cellView.thumbnailView = self
  72. if let page_ = self.document?.page(at: UInt(indexPath.item)) {
  73. cellView.page = page_
  74. }
  75. cellView.annotationShowState = self.annotationShowState
  76. if indexPath.item == hoverIndex {
  77. cellView.hover = true
  78. } else {
  79. cellView.hover = false
  80. }
  81. cellView.mouseDownAction = { [unowned self] (view, event) in
  82. self.delegate?.thumbnailView?(thumbanView: self, didSelectItemAt: indexPath, object: event)
  83. }
  84. cellView.rightMouseDownAction = { [unowned self] (view, event) in
  85. self.delegate?.thumbnailView?(thumbanView: self, rightMouseDidClick: indexPath, item: view, object: event)
  86. }
  87. cellView.hoverCallBack = { [unowned self] view, mouseEntered in
  88. if let _ = self.collectionView.item(at: hoverIndex)?.view {
  89. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  90. tempCell!.hover = false
  91. }
  92. if mouseEntered {
  93. hoverIndex = indexPath.item
  94. if let _ = self.collectionView.item(at: hoverIndex)?.view {
  95. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  96. tempCell!.hover = true
  97. }
  98. } else {
  99. hoverIndex = -1
  100. }
  101. }
  102. return cellView
  103. }
  104. override func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) {
  105. if self.isNeedMarkerLine {
  106. self.markBeginIndexes = collectionView.selectionIndexes
  107. }
  108. // 将拖拽的page插入临时路径(文档)
  109. var indexs = IndexSet()
  110. for indexpath in self.dragedIndexPaths {
  111. indexs.insert(indexpath.item)
  112. }
  113. // 清空临时数据
  114. if let _path = self.dragTempFilePath, FileManager.default.fileExists(atPath: _path) {
  115. try?FileManager.default.removeItem(atPath: _path)
  116. }
  117. // 重置拖拽标识
  118. self.dragFlag = false
  119. if (indexs.count > 0) {
  120. let document = CPDFDocument()
  121. document?.importPages(indexs, from: self.document, at: 0)
  122. if let data = self.dragTempFloderPath, !FileManager.default.fileExists(atPath: data) {
  123. try?FileManager.default.createDirectory(atPath: data, withIntermediateDirectories: false)
  124. }
  125. if let data = self.dragTempFilePath, !FileManager.default.fileExists(atPath: data) {
  126. FileManager.default.createFile(atPath: data, contents: nil)
  127. }
  128. if let data = self.dragTempFilePath {
  129. if (self.limit) {
  130. let _ = KMTools.saveWatermarkDocument(document: document!, to: URL(fileURLWithPath: data))
  131. } else {
  132. document?.write(to: URL(fileURLWithPath: data))
  133. }
  134. }
  135. self.dragFilePath = self.dragTempFilePath
  136. }
  137. return super.collectionView(collectionView, draggingSession: session, willBeginAt: screenPoint, forItemsAt: indexes)
  138. }
  139. override func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  140. if self.isNeedMarkerLine {
  141. KMPrint(collectionView)
  142. // if self.markBeginIndexes.count != 0 {
  143. // if !self.markBeginIndexes.contains(proposedDropIndexPath.pointee.item) {
  144. //标记线
  145. // if collectionView == self.collectionView {
  146. if (self.collectionView.item(at: proposedDropIndexPath.pointee.item) != nil) {
  147. var rect = self.collectionView.frameForItem(at: proposedDropIndexPath.pointee.item)
  148. // KMPrint("标记线范围 \(rect)")
  149. rect.size.height = 2
  150. self.markerLineView.frame = rect
  151. self.markerLineView.isHidden = false
  152. } else {
  153. let count = self.collectionView.numberOfItems(inSection: 0)
  154. if proposedDropIndexPath.pointee.item == count {
  155. var rect = self.collectionView.frameForItem(at: count - 1)
  156. // KMPrint("标记线范围 \(rect)")
  157. rect.origin.y += rect.size.height
  158. rect.size.height = 2
  159. self.markerLineView.frame = rect
  160. self.markerLineView.isHidden = false
  161. }
  162. }
  163. //// }
  164. //// }
  165. print(proposedDropIndexPath.pointee.item)
  166. // }
  167. }
  168. // debugPrint("移动中")
  169. if !KMThumbnailManager.manager.dragCollectionViews.contains(collectionView) {
  170. KMThumbnailManager.manager.dragCollectionViews.append(collectionView)
  171. }
  172. return super.collectionView(collectionView, validateDrop: draggingInfo, proposedIndexPath: proposedDropIndexPath, dropOperation: proposedDropOperation)
  173. }
  174. override func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
  175. self.hiddenMarkerLineView()
  176. self.markBeginIndexes = IndexSet()
  177. self.dragIn = true
  178. let pboard = draggingInfo.draggingPasteboard
  179. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  180. let dragIndexPath = self.dragedIndexPaths.first
  181. if (dragIndexPath == nil) {
  182. return false
  183. }
  184. let dragIndex = dragIndexPath!.item
  185. let toIndex = max(0, indexPath.item)
  186. if dragIndex < 0 || dragIndex > self.document!.pageCount {
  187. return false
  188. }
  189. if (toIndex >= self.document!.pageCount) {
  190. return false
  191. }
  192. if dragIndex == toIndex {
  193. return false
  194. }
  195. return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation)
  196. } else if (pboard.availableType(from: [.localDraggedTypes]) != nil) {
  197. if let data = draggingInfo.draggingSource as? NSCollectionView, data.isEqual(to: collectionView) {
  198. // Swift.debugPrint("当前文件拖拽")
  199. return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation)
  200. } else {
  201. // Swift.debugPrint("不同文件拖拽")
  202. if let _urlString = self.dragTempFilePath {
  203. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: [URL(fileURLWithPath: _urlString)], indexpath: indexPath)
  204. }
  205. }
  206. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  207. if let should = self.delegate?.thumbnailView?(thumbanView: self, shouldAcceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation), !should {
  208. return should
  209. }
  210. guard let pbItems = pboard.pasteboardItems else {
  211. return false
  212. }
  213. //获取url
  214. var array: [URL] = []
  215. for item in pbItems {
  216. guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else {
  217. continue
  218. }
  219. let type = _url.pathExtension.lowercased()
  220. if let _allowedFileTypes = self.kmAllowedFileTypes {
  221. if (_allowedFileTypes.contains(type)) {
  222. array.append(_url)
  223. }
  224. } else {
  225. array.append(_url)
  226. }
  227. }
  228. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath)
  229. }
  230. return false
  231. }
  232. func collectionView(_ collectionView: NSCollectionView,
  233. pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
  234. if let can = self.delegate?.thumbnailView?(thumbanView: self, canPasteboardWriterForItemAt: indexPath), !can {
  235. return nil
  236. }
  237. if let provider = self.delegate?.thumbnailView?(thumbanView: self, pasteboardWriterForItemAt: indexPath) {
  238. return provider
  239. }
  240. var provider: NSFilePromiseProvider?
  241. // 创建数据提供者
  242. let fileExtension = "pdf"
  243. if #available(macOS 11.0, *) {
  244. let typeIdentifier = UTType(filenameExtension: fileExtension)
  245. provider = KMFilePromiseProvider(fileType: typeIdentifier!.identifier, delegate: self)
  246. } else {
  247. let typeIdentifier =
  248. UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil)
  249. provider = KMFilePromiseProvider(fileType: typeIdentifier!.takeRetainedValue() as String, delegate: self)
  250. }
  251. // 记录拖拽索引
  252. self.dragedIndexPaths.append(indexPath)
  253. do {
  254. if let _url = self.document?.documentURL {
  255. let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false)
  256. provider!.userInfo = [KMFilePromiseProvider.UserInfoKeys.urlKey: _url,
  257. KMFilePromiseProvider.UserInfoKeys.indexPathKey: data]
  258. } else {
  259. let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false)
  260. provider!.userInfo = [KMFilePromiseProvider.UserInfoKeys.indexPathKey: data]
  261. }
  262. } catch {
  263. fatalError("failed to archive indexPath to pasteboard")
  264. }
  265. return provider
  266. }
  267. override func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
  268. // if let _ = session.draggingPasteboard.availableType(from: [.localDraggedTypes]) {
  269. // Swift.debugPrint("本地拖拽")
  270. // } else {
  271. if (!self.dragIn) {
  272. var indexpaths = Set<IndexPath>()
  273. for indexpath in self.dragedIndexPaths {
  274. indexpaths.insert(indexpath)
  275. }
  276. // 清空数据
  277. self.dragedIndexPaths.removeAll()
  278. // 刷新数据
  279. self.reloadData()
  280. // 重新选中数据
  281. self.selectionIndexPaths = indexpaths
  282. } else {
  283. Swift.debugPrint("拖入文件 或 本地拖拽")
  284. }
  285. self.dragIn = false
  286. self.hiddenMarkerLineView()
  287. self.markBeginIndexes = IndexSet()
  288. // }
  289. super.collectionView(collectionView, draggingSession: session, endedAt: screenPoint, dragOperation: operation)
  290. }
  291. }
  292. extension KMPDFThumbnailView {
  293. func hiddenMarkerLineView() {
  294. for item in KMThumbnailManager.manager.dragCollectionViews {
  295. let view = item.superview?.superview?.superview as? KMPDFThumbnailView
  296. view?.markerLineView.isHidden = true
  297. }
  298. KMThumbnailManager.manager.dragCollectionViews.removeAll()
  299. }
  300. }
  301. // MARK: - KMExtensions
  302. extension KMPDFThumbnailView {
  303. var dragTempFloderPath: String? {
  304. get {
  305. return NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("KMPDFThumbnailView_Drag_Temp")
  306. }
  307. }
  308. var dragTempFilePath: String? {
  309. get {
  310. return self.dragTempFloderPath?.stringByAppendingPathComponent("drag_tmp.pdf")
  311. }
  312. }
  313. }
  314. // MARK: - NSFilePromiseProviderDelegate
  315. extension KMPDFThumbnailView: NSFilePromiseProviderDelegate {
  316. func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
  317. var fileName: String = "Untitle"
  318. if let _string = self.document?.documentURL.deletingPathExtension().lastPathComponent {
  319. fileName = _string
  320. }
  321. fileName.append(" pages")
  322. var indexs = IndexSet()
  323. for indexpath in self.dragedIndexPaths {
  324. indexs.insert(indexpath.item)
  325. }
  326. fileName.append(" ")
  327. fileName.append(KMPageRangeTools.newParseSelectedIndexs(selectedIndex: indexs.sorted()))
  328. fileName.append(".pdf")
  329. return fileName
  330. }
  331. func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider,
  332. writePromiseTo url: URL,
  333. completionHandler: @escaping (Error?) -> Void) {
  334. do {
  335. /** Copy the file to the location provided to you. You always do a copy, not a move.
  336. It's important you call the completion handler.
  337. */
  338. if let _urlString = self.dragFilePath, !self.dragFlag {
  339. self.dragFlag = true
  340. if let should = self.delegate?.thumbnailView?(thumbanView: self, shouldPasteboardWriterForItemAt: IndexPath(item: 0, section: 0)), !should {
  341. completionHandler(nil)
  342. return
  343. }
  344. try FileManager.default.copyItem(at: URL(fileURLWithPath: _urlString), to: url)
  345. }
  346. completionHandler(nil)
  347. } catch let error {
  348. OperationQueue.main.addOperation {
  349. self.presentError(error, modalFor: self.window!,
  350. delegate: nil, didPresent: nil, contextInfo: nil)
  351. }
  352. completionHandler(error)
  353. }
  354. }
  355. /** You should provide a non main operation queue (e.g. one you create) via this function.
  356. This way you don't stall the main thread while writing the promise file.
  357. */
  358. func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
  359. return self.filePromiseQueue
  360. }
  361. // Utility function to return a PhotoItem object from the NSFilePromiseProvider.
  362. // func photoFromFilePromiserProvider(filePromiseProvider: NSFilePromiseProvider) -> CPDFPage? {
  363. // var result: CPDFPage?
  364. // if let userInfo = filePromiseProvider.userInfo as? [String: AnyObject] {
  365. // do {
  366. // if let indexPathData = userInfo[KMFilePromiseProvider.UserInfoKeys.indexPathKey] as? Data {
  367. // if let indexPath =
  368. // try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(indexPathData) as? IndexPath {
  369. // result = self.document?.page(at: UInt(indexPath.item))
  370. // }
  371. // }
  372. // } catch {
  373. // fatalError("failed to unarchive indexPath from promise provider.")
  374. // }
  375. // }
  376. // return result
  377. // }
  378. }