// // KMPDFThumbnailView.swift // PDF Master // // Created by lxy on 2022/12/15. // class KMPDFThumbnailView: KMThumbnailView { var document: CPDFDocument? var isShowPageSize = false var thumbnailSzie = NSZeroSize //标记线 var isNeedMarkerLine: Bool = false var markerLineView: NSView = NSView() var markBeginIndexes: IndexSet = IndexSet() //注释状态 var annotationShowState: KMAnnotationViewShowType = .none { didSet { self.collectionView.reloadData() } } //悬浮 var hoverIndex: Int = -1 var filePromiseQueue: OperationQueue = { let queue = OperationQueue() return queue }() var provider: NSFilePromiseProvider? override func initDefaultValue() { super.initDefaultValue() self.wantsLayer = true self.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor self.thumbnailSzie = CGSize(width: 120, height: 155) self.minimumLineSpacing = 8 self.register(KMPDFThumbnailItem.self) self.collectionView.registerForDraggedTypes(NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) }) self.collectionView.setDraggingSourceOperationMask([.copy, .delete], forLocal: false) } override func initSubViews() { super.initSubViews() self.markerLineView.wantsLayer = true self.markerLineView.layer?.backgroundColor = NSColor(hex: "#1770F4").cgColor self.markerLineView.frame = CGRectMake(0, 0, 100, 2) self.collectionView.addSubview(markerLineView) self.markerLineView.isHidden = true } } extension KMPDFThumbnailView { override func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { if let size_ = self.delegate?.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) { return size_ } let page = self.document?.page(at: UInt(indexPath.item)) let height = KMPDFThumbnailItem.sizeToFit(size: self.thumbnailSzie, page: page!, isShow: self.isShowPageSize) return NSMakeSize(collectionView.frame.size.width - 20, CGFloat(height)) } override func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { if let count = self.document?.pageCount { return Int(count) } return 0 } override func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { if let item = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) { return item } let cellView: KMPDFThumbnailItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: NSStringFromClass(KMPDFThumbnailItem.self)), for: indexPath) as! KMPDFThumbnailItem cellView.thumbnailView = self if let page_ = self.document?.page(at: UInt(indexPath.item)) { cellView.page = page_ } cellView.annotationShowState = self.annotationShowState if indexPath.item == hoverIndex { cellView.hover = true } else { cellView.hover = false } cellView.mouseDownAction = { [unowned self] (view, event) in self.delegate?.thumbnailView?(thumbanView: self, didSelectItemAt: indexPath, object: event) } cellView.rightMouseDownAction = { [unowned self] (view, event) in self.delegate?.thumbnailView?(thumbanView: self, rightMouseDidClick: indexPath, item: view, object: event) } cellView.hoverCallBack = { [unowned self] view, mouseEntered in if let _ = self.collectionView.item(at: hoverIndex)?.view { let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem tempCell!.hover = false } if mouseEntered { hoverIndex = indexPath.item if let _ = self.collectionView.item(at: hoverIndex)?.view { let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem tempCell!.hover = true } } else { hoverIndex = -1 } } return cellView } override func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) { if self.isNeedMarkerLine { self.markBeginIndexes = collectionView.selectionIndexes } return super.collectionView(collectionView, draggingSession: session, willBeginAt: screenPoint, forItemsAt: indexes) } override func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer, dropOperation proposedDropOperation: UnsafeMutablePointer) -> NSDragOperation { if self.isNeedMarkerLine { if self.markBeginIndexes.count != 0 { if !self.markBeginIndexes.contains(proposedDropIndexPath.pointee.item) { //标记线 var rect = self.collectionView.frameForItem(at: proposedDropIndexPath.pointee.item) rect.size.height = 2 self.markerLineView.frame = rect self.markerLineView.isHidden = false } } } return super.collectionView(collectionView, validateDrop: draggingInfo, proposedIndexPath: proposedDropIndexPath, dropOperation: proposedDropOperation) } override func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool { self.markerLineView.isHidden = true self.markBeginIndexes = IndexSet() let pboard = draggingInfo.draggingPasteboard if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) { let dragIndexPath = self.dragedIndexPaths.first if (dragIndexPath == nil) { return false } let dragIndex = dragIndexPath!.item let toIndex = max(0, indexPath.item) if dragIndex < 0 || dragIndex > self.document!.pageCount { return false } if (toIndex >= self.document!.pageCount) { return false } if dragIndex == toIndex { return false } return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation) } else if ((pboard.availableType(from: [.fileURL])) != nil) { //获取url var array: [URL] = [] for item: NSPasteboardItem in pboard.pasteboardItems! { let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)! let url = NSURL(string: string) /// MARK: 暂时支持PDF格式 if (url?.pathExtension?.lowercased() == "pdf") { array.append(url! as URL) } } self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath) return true } return false } // func collectionView(_ collectionView: NSCollectionView, // pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? { // /** Here the sample provide a custom NSFilePromise#imageLiteral(resourceName: "_DSC9930.jpeg")#imageLiteral(resourceName: "_DSC9930.jpeg")Provider. // Here we provide a custom provider, offering the row to the drag object, and its URL. // */ //// var provider: NSFilePromiseProvider? //// ////// guard let photoItem = ////// dataSource.itemIdentifier(for: IndexPath(item: indexPath.item, section: 0)) else { return provider } ////// let photoFileExtension = photoItem.fileURL.pathExtension //// let photoFileExtension = "pdf" //// //// if #available(macOS 11.0, *) { //// let typeIdentifier = UTType(filenameExtension: photoFileExtension) //// provider = FilePromiseProvider(fileType: typeIdentifier!.identifier, delegate: self) //// } else { //// let typeIdentifier = //// UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, photoFileExtension as CFString, nil) //// provider = FilePromiseProvider(fileType: typeIdentifier!.takeRetainedValue() as String, delegate: self) //// } //// //// // Send out the indexPath and photo's url dictionary. //// do { //// let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false) //// provider!.userInfo = [FilePromiseProvider.UserInfoKeys.urlKey: self.document!.documentURL as Any, //// FilePromiseProvider.UserInfoKeys.indexPathKey: data] //// } catch { //// fatalError("failed to archive indexPath to pasteboard") //// } //// return provider // // guard let model = indexPath as? IndexPath else { // return nil // } // // let pastBoard = NSPasteboardItem.init() // pastBoard.setString(String(format: "%ld", model.item), forType: NSPasteboard.PasteboardType.string) // return pastBoard // } } class FilePromiseProvider: NSFilePromiseProvider { struct UserInfoKeys { static let indexPathKey = "indexPath" static let urlKey = "url" } /** Required: Return an array of UTI strings of data types the receiver can write to the pasteboard. By default, data for the first returned type is put onto the pasteboard immediately, with the remaining types being promised. To change the default behavior, implement -writingOptionsForType:pasteboard: and return NSPasteboardWritingPromised to lazily provided data for types, return no option to provide the data for that type immediately. Use the pasteboard argument to provide different types based on the pasteboard name, if desired. Do not perform other pasteboard operations in the function implementation. */ override func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] { var types = super.writableTypes(for: pasteboard) types.append(kKMLocalForDraggedTypes) // Add our own internal drag type (row drag and drop reordering). types.append(.fileURL) // Add the .fileURL drag type (to promise files to other apps). return types } /** Required: Return the appropriate property list object for the provided type. This will commonly be the NSData for that data type. However, if this function returns either a string, or any other property-list type, the pasteboard will automatically convert these items to the correct NSData format required for the pasteboard. */ override func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? { guard let userInfoDict = userInfo as? [String: Any] else { return nil } switch type { case .fileURL: // Incoming type is "public.file-url", return (from our userInfo) the item's URL. if let url = userInfoDict[FilePromiseProvider.UserInfoKeys.urlKey] as? NSURL { return url.pasteboardPropertyList(forType: type) } case kKMLocalForDraggedTypes: // Incoming type is "com.mycompany.mydragdrop", return (from our userInfo) the item's indexPath. let indexPathData = userInfoDict[FilePromiseProvider.UserInfoKeys.indexPathKey] return indexPathData default: break } return super.pasteboardPropertyList(forType: type) } /** Optional: Returns options for writing data of a type to a pasteboard. Use the pasteboard argument to provide different options based on the pasteboard name, if desired. Do not perform other pasteboard operations in the function implementation. */ public override func writingOptions(forType type: NSPasteboard.PasteboardType, pasteboard: NSPasteboard) -> NSPasteboard.WritingOptions { return super.writingOptions(forType: type, pasteboard: pasteboard) } } // MARK: - NSFilePromiseProviderDelegate extension KMPDFThumbnailView: NSFilePromiseProviderDelegate { /** This function is called at drop time to provide the title of the file being dropped. This sample uses a hard-coded string for simplicity, but depending on your use case, you should take the fileType parameter into account. */ func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String { // Return the photoItem's URL file name. // let photoItem = photoFromFilePromiserProvider(filePromiseProvider: filePromiseProvider) // return (photoItem?.fileURL.lastPathComponent)! // return (self.document?.documentURL.lastPathComponent)! return "Test.pdf" } func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) { do { if let photoItem = photoFromFilePromiserProvider(filePromiseProvider: filePromiseProvider) { /** Copy the file to the location provided to you. You always do a copy, not a move. It's important you call the completion handler. */ // try FileManager.default.copyItem(at: photoItem.fileURL, to: url) try FileManager.default.copyItem(at: (self.document?.documentURL)!, to: url) completionHandler(nil) } } catch let error { OperationQueue.main.addOperation { self.presentError(error, modalFor: self.window!, delegate: nil, didPresent: nil, contextInfo: nil) } completionHandler(error) } } /** You should provide a non main operation queue (e.g. one you create) via this function. This way you don't stall the main thread while writing the promise file. */ func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue { return filePromiseQueue } // Utility function to return a PhotoItem object from the NSFilePromiseProvider. func photoFromFilePromiserProvider(filePromiseProvider: NSFilePromiseProvider) -> CPDFPage? { var returnPhoto: CPDFPage? if let userInfo = filePromiseProvider.userInfo as? [String: AnyObject] { do { if let indexPathData = userInfo[FilePromiseProvider.UserInfoKeys.indexPathKey] as? Data { if let indexPath = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(indexPathData) as? IndexPath { returnPhoto = self.document?.page(at: UInt(indexPath.item)) } } } catch { fatalError("failed to unarchive indexPath from promise provider.") } } return returnPhoto } }