// // KMPDFThumbnailView.swift // PDF Reader Pro // // 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 }() fileprivate var dragFilePath: String? fileprivate var dragFlag = false fileprivate var dragIn = false var limit = false override func initDefaultValue() { super.initDefaultValue() self.wantsLayer = true self.layer?.backgroundColor = NSColor.km_init(hex: "#F7F8FA").cgColor self.thumbnailSzie = CGSize(width: 120, height: 155) self.minimumLineSpacing = 8 self.collectionView.km.register(cell: 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.km_init(hex: "#1770F4").cgColor self.markerLineView.frame = NSMakeRect(0, 0, 100, 2) self.collectionView.addSubview(markerLineView) self.hiddenMarkerLineView() 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_ } var height: CGFloat = 0 if let page = self.document?.page(at: UInt(indexPath.item)) { height = KMPDFThumbnailItem.sizeToFit(size: self.thumbnailSzie, page: page, isShow: self.isShowPageSize) } return NSMakeSize(collectionView.frame.size.width - 20, 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.km.dequeueReusableCell(for: indexPath) 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 } // 将拖拽的page插入临时路径(文档) var indexs = IndexSet() for indexpath in self.dragedIndexPaths { indexs.insert(indexpath.item) } // 清空临时数据 if let _path = self.dragTempFilePath, FileManager.default.fileExists(atPath: _path) { try?FileManager.default.removeItem(atPath: _path) } // 重置拖拽标识 self.dragFlag = false if (indexs.count > 0) { let document = CPDFDocument() document?.importPages(indexs, from: self.document, at: 0) if let data = self.dragTempFloderPath, !FileManager.default.fileExists(atPath: data) { try?FileManager.default.createDirectory(atPath: data, withIntermediateDirectories: false) } if let data = self.dragTempFilePath, !FileManager.default.fileExists(atPath: data) { FileManager.default.createFile(atPath: data, contents: nil) } if let data = self.dragTempFilePath { if (self.limit) { // let _ = KMTools.saveWatermarkDocument(document: document!, to: URL(fileURLWithPath: data)) } else { document?.write(to: URL(fileURLWithPath: data)) } } self.dragFilePath = self.dragTempFilePath } 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 { KMPrint(collectionView) // if self.markBeginIndexes.count != 0 { // if !self.markBeginIndexes.contains(proposedDropIndexPath.pointee.item) { //标记线 // if collectionView == self.collectionView { if (self.collectionView.item(at: proposedDropIndexPath.pointee.item) != nil) { var rect = self.collectionView.frameForItem(at: proposedDropIndexPath.pointee.item) // KMPrint("标记线范围 \(rect)") rect.size.height = 2 self.markerLineView.frame = rect self.markerLineView.isHidden = false } else { let count = self.collectionView.numberOfItems(inSection: 0) if proposedDropIndexPath.pointee.item == count { var rect = self.collectionView.frameForItem(at: count - 1) // KMPrint("标记线范围 \(rect)") rect.origin.y += rect.size.height rect.size.height = 2 self.markerLineView.frame = rect self.markerLineView.isHidden = false } } //// } //// } print(proposedDropIndexPath.pointee.item) // } } // debugPrint("移动中") if !KMThumbnailManager.manager.dragCollectionViews.contains(collectionView) { KMThumbnailManager.manager.dragCollectionViews.append(collectionView) } 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.hiddenMarkerLineView() self.markBeginIndexes = IndexSet() self.dragIn = true let pboard = draggingInfo.draggingPasteboard if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) { guard let dragIndexPath = self.dragedIndexPaths.first else { return false } guard let document = self.document else { return false } let dragIndex = dragIndexPath.item let toIndex = max(0, indexPath.item) if dragIndex < 0 || dragIndex > document.pageCount { return false } if (toIndex >= document.pageCount) { return false } if dragIndex == toIndex { return false } return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation) } else if (pboard.availableType(from: [.localDraggedTypes]) != nil) { if let data = draggingInfo.draggingSource as? NSCollectionView, data.isEqual(to: collectionView) { // Swift.debugPrint("当前文件拖拽") return super.collectionView(collectionView, acceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation) } else { // Swift.debugPrint("不同文件拖拽") if let _urlString = self.dragTempFilePath { self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: [URL(fileURLWithPath: _urlString)], indexpath: indexPath) } } } else if ((pboard.availableType(from: [.fileURL])) != nil) { if let should = self.delegate?.thumbnailView?(thumbanView: self, shouldAcceptDrop: draggingInfo, indexPath: indexPath, dropOperation: dropOperation), !should { return should } guard let pbItems = pboard.pasteboardItems else { return false } //获取url var array: [URL] = [] for item in pbItems { guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else { continue } let type = _url.pathExtension.lowercased() if let _allowedFileTypes = self.kmAllowedFileTypes { if (_allowedFileTypes.contains(type)) { array.append(_url) } } else { array.append(_url) } } self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath) } return false } func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? { if let can = self.delegate?.thumbnailView?(thumbanView: self, canPasteboardWriterForItemAt: indexPath), !can { return nil } if let provider = self.delegate?.thumbnailView?(thumbanView: self, pasteboardWriterForItemAt: indexPath) { return provider } var provider: NSFilePromiseProvider? // 创建数据提供者 let fileExtension = "pdf" if #available(macOS 11.0, *) { if let typeIdentifier = UTType(filenameExtension: fileExtension) { provider = KMFilePromiseProvider(fileType: typeIdentifier.identifier, delegate: self) } } else { if let typeIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil) { provider = KMFilePromiseProvider(fileType: typeIdentifier.takeRetainedValue() as String, delegate: self) } } // 记录拖拽索引 self.dragedIndexPaths.append(indexPath) do { if let _url = self.document?.documentURL { let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false) provider?.userInfo = [KMFilePromiseProvider.UserInfoKeys.urlKey: _url, KMFilePromiseProvider.UserInfoKeys.indexPathKey: data] } else { let data = try NSKeyedArchiver.archivedData(withRootObject: indexPath, requiringSecureCoding: false) provider?.userInfo = [KMFilePromiseProvider.UserInfoKeys.indexPathKey: data] } } catch { fatalError("failed to archive indexPath to pasteboard") } return provider } override func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) { // if let _ = session.draggingPasteboard.availableType(from: [.localDraggedTypes]) { // Swift.debugPrint("本地拖拽") // } else { if (!self.dragIn) { var indexpaths = Set() for indexpath in self.dragedIndexPaths { indexpaths.insert(indexpath) } // 清空数据 self.dragedIndexPaths.removeAll() // 刷新数据 self.reloadData() // 重新选中数据 self.selectionIndexPaths = indexpaths } else { Swift.debugPrint("拖入文件 或 本地拖拽") } self.dragIn = false self.hiddenMarkerLineView() self.markBeginIndexes = IndexSet() // } super.collectionView(collectionView, draggingSession: session, endedAt: screenPoint, dragOperation: operation) } } extension KMPDFThumbnailView { func hiddenMarkerLineView() { for item in KMThumbnailManager.manager.dragCollectionViews { let view = item.superview?.superview?.superview as? KMPDFThumbnailView view?.markerLineView.isHidden = true } KMThumbnailManager.manager.dragCollectionViews.removeAll() } } // MARK: - KMExtensions extension KMPDFThumbnailView { var dragTempFloderPath: String? { get { return NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier ?? "").stringByAppendingPathComponent("KMPDFThumbnailView_Drag_Temp") } } var dragTempFilePath: String? { get { return self.dragTempFloderPath?.stringByAppendingPathComponent("drag_tmp.pdf") } } } // MARK: - NSFilePromiseProviderDelegate extension KMPDFThumbnailView: NSFilePromiseProviderDelegate { func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String { var fileName: String = "Untitle" if let _string = self.document?.documentURL.deletingPathExtension().lastPathComponent { fileName = _string } fileName.append(" pages") var indexs = IndexSet() for indexpath in self.dragedIndexPaths { indexs.insert(indexpath.item) } fileName.append(" ") fileName.append(KMPageRangeTools.newParseSelectedIndexs(selectedIndex: indexs.sorted())) fileName.append(".pdf") return fileName } func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) { do { /** 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. */ if let _urlString = self.dragFilePath, !self.dragFlag { self.dragFlag = true if let should = self.delegate?.thumbnailView?(thumbanView: self, shouldPasteboardWriterForItemAt: IndexPath(item: 0, section: 0)), !should { completionHandler(nil) return } try FileManager.default.copyItem(at: URL(fileURLWithPath: _urlString), to: url) } completionHandler(nil) } catch let error { OperationQueue.main.addOperation { if let win = self.window { self.presentError(error, modalFor: win, 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 self.filePromiseQueue } // Utility function to return a PhotoItem object from the NSFilePromiseProvider. // func photoFromFilePromiserProvider(filePromiseProvider: NSFilePromiseProvider) -> CPDFPage? { // var result: CPDFPage? // if let userInfo = filePromiseProvider.userInfo as? [String: AnyObject] { // do { // if let indexPathData = userInfo[KMFilePromiseProvider.UserInfoKeys.indexPathKey] as? Data { // if let indexPath = // try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(indexPathData) as? IndexPath { // result = self.document?.page(at: UInt(indexPath.item)) // } // } // } catch { // fatalError("failed to unarchive indexPath from promise provider.") // } // } // return result // } }