KMPDFThumbnailView.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. var provider: NSFilePromiseProvider?
  28. override func initDefaultValue() {
  29. super.initDefaultValue()
  30. self.wantsLayer = true
  31. self.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor
  32. self.thumbnailSzie = CGSize(width: 120, height: 155)
  33. self.minimumLineSpacing = 8
  34. self.register(KMPDFThumbnailItem.self)
  35. self.collectionView.registerForDraggedTypes(NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) })
  36. self.collectionView.setDraggingSourceOperationMask([.copy, .delete], forLocal: false)
  37. }
  38. override func initSubViews() {
  39. super.initSubViews()
  40. self.markerLineView.wantsLayer = true
  41. self.markerLineView.layer?.backgroundColor = NSColor(hex: "#1770F4").cgColor
  42. self.markerLineView.frame = CGRectMake(0, 0, 100, 2)
  43. self.collectionView.addSubview(markerLineView)
  44. self.markerLineView.isHidden = true
  45. }
  46. }
  47. extension KMPDFThumbnailView {
  48. override func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  49. if let size_ = self.delegate?.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) {
  50. return size_
  51. }
  52. let page = self.document?.page(at: UInt(indexPath.item))
  53. let height = KMPDFThumbnailItem.sizeToFit(size: self.thumbnailSzie, page: page!, isShow: self.isShowPageSize)
  54. return NSMakeSize(collectionView.frame.size.width - 20, CGFloat(height))
  55. }
  56. override func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  57. if let count = self.document?.pageCount {
  58. return Int(count)
  59. }
  60. return 0
  61. }
  62. override func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  63. if let item = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) {
  64. return item
  65. }
  66. let cellView: KMPDFThumbnailItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: NSStringFromClass(KMPDFThumbnailItem.self)), for: indexPath) as! KMPDFThumbnailItem
  67. cellView.thumbnailView = self
  68. if let page_ = self.document?.page(at: UInt(indexPath.item)) {
  69. cellView.page = page_
  70. }
  71. cellView.annotationShowState = self.annotationShowState
  72. if indexPath.item == hoverIndex {
  73. cellView.hover = true
  74. } else {
  75. cellView.hover = false
  76. }
  77. cellView.mouseDownAction = { [unowned self] (view, event) in
  78. self.delegate?.thumbnailView?(thumbanView: self, didSelectItemAt: indexPath, object: event)
  79. }
  80. cellView.rightMouseDownAction = { [unowned self] (view, event) in
  81. self.delegate?.thumbnailView?(thumbanView: self, rightMouseDidClick: indexPath, item: view, object: event)
  82. }
  83. cellView.hoverCallBack = { [unowned self] view, mouseEntered in
  84. if let _ = self.collectionView.item(at: hoverIndex)?.view {
  85. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  86. tempCell!.hover = false
  87. }
  88. if mouseEntered {
  89. hoverIndex = indexPath.item
  90. if let _ = self.collectionView.item(at: hoverIndex)?.view {
  91. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  92. tempCell!.hover = true
  93. }
  94. } else {
  95. hoverIndex = -1
  96. }
  97. }
  98. return cellView
  99. }
  100. override func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) {
  101. if self.isNeedMarkerLine {
  102. self.markBeginIndexes = collectionView.selectionIndexes
  103. }
  104. return super.collectionView(collectionView, draggingSession: session, willBeginAt: screenPoint, forItemsAt: indexes)
  105. }
  106. override func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  107. if self.isNeedMarkerLine {
  108. if self.markBeginIndexes.count != 0 {
  109. if !self.markBeginIndexes.contains(proposedDropIndexPath.pointee.item) {
  110. //标记线
  111. var rect = self.collectionView.frameForItem(at: proposedDropIndexPath.pointee.item)
  112. rect.size.height = 2
  113. self.markerLineView.frame = rect
  114. self.markerLineView.isHidden = false
  115. }
  116. }
  117. }
  118. return super.collectionView(collectionView, validateDrop: draggingInfo, proposedIndexPath: proposedDropIndexPath, dropOperation: proposedDropOperation)
  119. }
  120. override func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
  121. self.markerLineView.isHidden = true
  122. self.markBeginIndexes = IndexSet()
  123. let pboard = draggingInfo.draggingPasteboard
  124. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  125. let dragIndexPath = self.dragedIndexPaths.first
  126. if (dragIndexPath == nil) {
  127. return false
  128. }
  129. let dragIndex = dragIndexPath!.item
  130. let toIndex = max(0, indexPath.item)
  131. if dragIndex < 0 || dragIndex > self.document!.pageCount {
  132. return false
  133. }
  134. if (toIndex >= self.document!.pageCount) {
  135. return false
  136. }
  137. if dragIndex == toIndex {
  138. return false
  139. }
  140. return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation)
  141. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  142. //获取url
  143. var array: [URL] = []
  144. for item: NSPasteboardItem in pboard.pasteboardItems! {
  145. let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)!
  146. let url = NSURL(string: string)
  147. /// MARK: 暂时支持PDF格式
  148. if (url?.pathExtension?.lowercased() == "pdf") {
  149. array.append(url! as URL)
  150. }
  151. }
  152. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath)
  153. return true
  154. }
  155. return false
  156. }
  157. // func collectionView(_ collectionView: NSCollectionView,
  158. // pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
  159. // /** Here the sample provide a custom NSFilePromise#imageLiteral(resourceName: "_DSC9930.jpeg")#imageLiteral(resourceName: "_DSC9930.jpeg")Provider.
  160. // Here we provide a custom provider, offering the row to the drag object, and its URL.
  161. // */
  162. //// var provider: NSFilePromiseProvider?
  163. ////
  164. ////// guard let photoItem =
  165. ////// dataSource.itemIdentifier(for: IndexPath(item: indexPath.item, section: 0)) else { return provider }
  166. ////// let photoFileExtension = photoItem.fileURL.pathExtension
  167. //// let photoFileExtension = "pdf"
  168. ////
  169. //// if #available(macOS 11.0, *) {
  170. //// let typeIdentifier = UTType(filenameExtension: photoFileExtension)
  171. //// provider = FilePromiseProvider(fileType: typeIdentifier!.identifier, delegate: self)
  172. //// } else {
  173. //// let typeIdentifier =
  174. //// UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, photoFileExtension as CFString, nil)
  175. //// provider = FilePromiseProvider(fileType: typeIdentifier!.takeRetainedValue() as String, delegate: self)
  176. //// }
  177. ////
  178. //// // Send out the indexPath and photo's url dictionary.
  179. //// do {
  180. //// let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false)
  181. //// provider!.userInfo = [FilePromiseProvider.UserInfoKeys.urlKey: self.document!.documentURL as Any,
  182. //// FilePromiseProvider.UserInfoKeys.indexPathKey: data]
  183. //// } catch {
  184. //// fatalError("failed to archive indexPath to pasteboard")
  185. //// }
  186. //// return provider
  187. //
  188. // guard let model = indexPath as? IndexPath else {
  189. // return nil
  190. // }
  191. //
  192. // let pastBoard = NSPasteboardItem.init()
  193. // pastBoard.setString(String(format: "%ld", model.item), forType: NSPasteboard.PasteboardType.string)
  194. // return pastBoard
  195. // }
  196. }
  197. class FilePromiseProvider: NSFilePromiseProvider {
  198. struct UserInfoKeys {
  199. static let indexPathKey = "indexPath"
  200. static let urlKey = "url"
  201. }
  202. /** Required:
  203. Return an array of UTI strings of data types the receiver can write to the pasteboard.
  204. By default, data for the first returned type is put onto the pasteboard immediately, with the remaining types being promised.
  205. To change the default behavior, implement -writingOptionsForType:pasteboard: and return
  206. NSPasteboardWritingPromised to lazily provided data for types, return no option to provide the data for that type immediately.
  207. Use the pasteboard argument to provide different types based on the pasteboard name, if desired.
  208. Do not perform other pasteboard operations in the function implementation.
  209. */
  210. override func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
  211. var types = super.writableTypes(for: pasteboard)
  212. types.append(kKMLocalForDraggedTypes) // Add our own internal drag type (row drag and drop reordering).
  213. types.append(.fileURL) // Add the .fileURL drag type (to promise files to other apps).
  214. return types
  215. }
  216. /** Required:
  217. Return the appropriate property list object for the provided type.
  218. This will commonly be the NSData for that data type. However, if this function returns either a string, or any other property-list type,
  219. the pasteboard will automatically convert these items to the correct NSData format required for the pasteboard.
  220. */
  221. override func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
  222. guard let userInfoDict = userInfo as? [String: Any] else { return nil }
  223. switch type {
  224. case .fileURL:
  225. // Incoming type is "public.file-url", return (from our userInfo) the item's URL.
  226. if let url = userInfoDict[FilePromiseProvider.UserInfoKeys.urlKey] as? NSURL {
  227. return url.pasteboardPropertyList(forType: type)
  228. }
  229. case kKMLocalForDraggedTypes:
  230. // Incoming type is "com.mycompany.mydragdrop", return (from our userInfo) the item's indexPath.
  231. let indexPathData = userInfoDict[FilePromiseProvider.UserInfoKeys.indexPathKey]
  232. return indexPathData
  233. default:
  234. break
  235. }
  236. return super.pasteboardPropertyList(forType: type)
  237. }
  238. /** Optional:
  239. Returns options for writing data of a type to a pasteboard.
  240. Use the pasteboard argument to provide different options based on the pasteboard name, if desired.
  241. Do not perform other pasteboard operations in the function implementation.
  242. */
  243. public override func writingOptions(forType type: NSPasteboard.PasteboardType, pasteboard: NSPasteboard)
  244. -> NSPasteboard.WritingOptions {
  245. return super.writingOptions(forType: type, pasteboard: pasteboard)
  246. }
  247. }
  248. // MARK: - NSFilePromiseProviderDelegate
  249. extension KMPDFThumbnailView: NSFilePromiseProviderDelegate {
  250. /** This function is called at drop time to provide the title of the file being dropped.
  251. This sample uses a hard-coded string for simplicity, but depending on your use case, you should take the fileType parameter into account.
  252. */
  253. func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
  254. // Return the photoItem's URL file name.
  255. // let photoItem = photoFromFilePromiserProvider(filePromiseProvider: filePromiseProvider)
  256. // return (photoItem?.fileURL.lastPathComponent)!
  257. // return (self.document?.documentURL.lastPathComponent)!
  258. return "Test.pdf"
  259. }
  260. func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider,
  261. writePromiseTo url: URL,
  262. completionHandler: @escaping (Error?) -> Void) {
  263. do {
  264. if let photoItem = photoFromFilePromiserProvider(filePromiseProvider: filePromiseProvider) {
  265. /** Copy the file to the location provided to you. You always do a copy, not a move.
  266. It's important you call the completion handler.
  267. */
  268. // try FileManager.default.copyItem(at: photoItem.fileURL, to: url)
  269. try FileManager.default.copyItem(at: (self.document?.documentURL)!, to: url)
  270. completionHandler(nil)
  271. }
  272. } catch let error {
  273. OperationQueue.main.addOperation {
  274. self.presentError(error, modalFor: self.window!,
  275. delegate: nil, didPresent: nil, contextInfo: nil)
  276. }
  277. completionHandler(error)
  278. }
  279. }
  280. /** You should provide a non main operation queue (e.g. one you create) via this function.
  281. This way you don't stall the main thread while writing the promise file.
  282. */
  283. func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
  284. return filePromiseQueue
  285. }
  286. // Utility function to return a PhotoItem object from the NSFilePromiseProvider.
  287. func photoFromFilePromiserProvider(filePromiseProvider: NSFilePromiseProvider) -> CPDFPage? {
  288. var returnPhoto: CPDFPage?
  289. if let userInfo = filePromiseProvider.userInfo as? [String: AnyObject] {
  290. do {
  291. if let indexPathData = userInfo[FilePromiseProvider.UserInfoKeys.indexPathKey] as? Data {
  292. if let indexPath =
  293. try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(indexPathData) as? IndexPath {
  294. returnPhoto = self.document?.page(at: UInt(indexPath.item))
  295. }
  296. }
  297. } catch {
  298. fatalError("failed to unarchive indexPath from promise provider.")
  299. }
  300. }
  301. return returnPhoto
  302. }
  303. }