// // KMPDFThumbnailView.swift // PDF Master // // Created by lxy on 2022/12/15. // import Cocoa @objc protocol KMPDFThumbnailViewDelegate: NSObjectProtocol { @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didSelectPageAtIndex index: UInt, event: NSEvent) @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, item: KMPDFThumbnailItem, rightMouseDidSelect index: UInt, event: NSEvent) @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didDragAddFiles files: [URL], indexpath: IndexPath) @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didDragPages pages: [Int], indexpath: IndexPath) @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, minimumLineSpacingForSectionAt section: Int) -> CGFloat @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, insetForSectionAt section: Int) -> NSEdgeInsets @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, sizeForItemAt indexpath: IndexPath) -> NSSize @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, itemForRepresentedObjectAt indexpath: IndexPath) -> NSCollectionViewItem } class KMPDFThumbnailView: NSView { open weak var delegate: KMPDFThumbnailViewDelegate? var document : CPDFDocument = CPDFDocument() var isShowPageSize : Bool = false var backgroundColor : NSColor = NSColor.white var highlightColor : NSColor = NSColor.blue var pageSelectionColor : NSColor = NSColor() var textSelectionColor : NSColor = NSColor() var thumbnailSzie : CGSize = CGSize() var scrollView : NSScrollView = NSScrollView() var collectionView : NSCollectionView = NSCollectionView() var doublePageMode : Bool! = false private let thumbnailDraggedTypes = NSPasteboard.PasteboardType(rawValue: "localForDraggedTypes") private var dragedIndexPaths: [IndexPath] = [] //标记线 var isNeedMarkerLine: Bool = false var markerLineView: NSView = NSView() var markBeginIndexes: IndexSet = IndexSet() //注释状态 var annotationShowState: KMAnnotationViewShowType = .none { didSet { self.collectionView.reloadData() } } //悬浮 var hoverIndex: Int = -1 override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) } required init?(coder: NSCoder) { super.init(coder: coder) self.initUI() } override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.initUI() } private func initUI() { self.wantsLayer = true self.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor self.thumbnailSzie = CGSize(width: 120, height: 155) let layout = NSCollectionViewFlowLayout() layout.sectionInset = NSEdgeInsetsMake(8, 15, 8, 15) layout.minimumLineSpacing = 0 layout.minimumInteritemSpacing = 0 self.collectionView.autoresizingMask = NSView.AutoresizingMask(rawValue: 18) self.collectionView.collectionViewLayout = layout self.collectionView.dataSource = self self.collectionView.delegate = self self.collectionView.register(KMPDFThumbnailItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem")) self.collectionView.registerForDraggedTypes([thumbnailDraggedTypes]) self.collectionView.isSelectable = true self.collectionView.wantsLayer = true self.collectionView.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor self.scrollView.hasHorizontalScroller = true self.scrollView.hasVerticalScroller = true self.scrollView.autohidesScrollers = true self.scrollView.minMagnification = 1.0 self.scrollView.scrollerStyle = NSScroller.Style.overlay self.scrollView.documentView = self.collectionView self.scrollView.wantsLayer = true self.scrollView.layer?.backgroundColor = NSColor.clear.cgColor self.scrollView.contentView.layer?.backgroundColor = NSColor(hex: "#FFFFFF").cgColor self.scrollView.contentView.wantsLayer = true self.addSubview(self.scrollView) markerLineView.wantsLayer = true markerLineView.layer?.backgroundColor = NSColor(hex: "#1770F4").cgColor markerLineView.frame = CGRectMake(0, 0, 100, 2) self.collectionView.addSubview(markerLineView) markerLineView.isHidden = true } override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) self.scrollView.frame = self.bounds } public func reloadData(indexs: Set = NSSet() as! Set) { if indexs.count == 0 { self.collectionView.reloadData() } else { var indexpaths: Set = [] for index in indexs { if (index.section >= self.collectionView.numberOfSections) { continue } if (index.item >= self.collectionView.numberOfItems(inSection: index.section)) { continue } indexpaths.insert(index) } if (indexpaths.count == 0) { return } if (Thread.isMainThread) { self.collectionView.reloadItems(at: indexpaths) } else { DispatchQueue.main.async { self.collectionView.reloadItems(at: indexpaths) } } } } } extension KMPDFThumbnailView : NSCollectionViewDataSource { func numberOfSections(in collectionView: NSCollectionView) -> Int { return 1 } func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { if self.document.isLocked { return 0 } return Int(self.document.pageCount) } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let view = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) if (view != nil) { return view! } let cellView: KMPDFThumbnailItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem"), for: indexPath) as! KMPDFThumbnailItem cellView.thumbnailView = self cellView.page = self.document.page(at: UInt(indexPath.item)) 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, didSelectPageAtIndex: UInt(indexPath.item), event: event) } cellView.rightMouseDownAction = { [unowned self] (view, event) in self.delegate?.thumbnailView?(thumbanView: self, item: view, rightMouseDidSelect: UInt(indexPath.item), event: event) } cellView.hoverCallBack = { [unowned self] view, mouseEntered in if self.collectionView.item(at: hoverIndex)?.view != nil { let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem tempCell!.hover = false } if mouseEntered { hoverIndex = indexPath.item if self.collectionView.item(at: hoverIndex)?.view != nil { let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem tempCell!.hover = true } } else { hoverIndex = -1 } } return cellView } } extension KMPDFThumbnailView: NSCollectionViewDelegateFlowLayout { /** * MARK: item大小 */ func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { if (self.delegate != nil) { let size = self.delegate!.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) if (size != nil) { // self.thumbnailSzie = size! 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)) } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { if (self.delegate != nil) { let minimumLineSpacing = self.delegate!.thumbnailView?(thumbanView: self, minimumLineSpacingForSectionAt: section) if (minimumLineSpacing != nil) { return minimumLineSpacing! } } return 8 } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { if (self.delegate != nil) { let minimumInteritemSpacing = self.delegate!.thumbnailView?(thumbanView: self, minimumInteritemSpacingForSectionAt: section) if (minimumInteritemSpacing != nil) { return minimumInteritemSpacing! } } return 0.01 } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets { let inset = self.delegate?.thumbnailView?(thumbanView: self, insetForSectionAt: section) if (inset != nil) { return inset! } return NSEdgeInsetsZero } } extension KMPDFThumbnailView: NSCollectionViewDelegate { func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set) -> Set { return indexPaths } func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { // let index: IndexPath = indexPaths.first! // self.delegate?.thumbnailView?(thumbanView: self, didSelectPageAtIndex: UInt(index.item)) } func collectionView(_ collectionView: NSCollectionView, shouldDeselectItemsAt indexPaths: Set) -> Set { return indexPaths } func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set) { } func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set, with event: NSEvent) -> Bool { return true } func collectionView(_ collectionView: NSCollectionView, writeItemsAt indexPaths: Set, to pasteboard: NSPasteboard) -> Bool { let data: Data = try! NSKeyedArchiver.archivedData(withRootObject: indexPaths, requiringSecureCoding: true) pasteboard.declareTypes([self.thumbnailDraggedTypes], owner: self) pasteboard.setData(data, forType: self.thumbnailDraggedTypes) self.dragedIndexPaths.removeAll() for indexPath in indexPaths { self.dragedIndexPaths.append(indexPath) } return true } func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) { if self.isNeedMarkerLine { self.markBeginIndexes = self.collectionView.selectionIndexes } } 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 } } } let pboard = draggingInfo.draggingPasteboard if (pboard.availableType(from: [thumbnailDraggedTypes]) != nil) { return .move } return NSDragOperation(rawValue: 0) } func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool { markerLineView.isHidden = true self.markBeginIndexes = IndexSet() let pboard = draggingInfo.draggingPasteboard if (pboard.availableType(from: [NSPasteboard.PasteboardType(rawValue: "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 } var dragPages: [Int] = [] for indexpath in dragedIndexPaths { dragPages.append(indexpath.item) } self.dragedIndexPaths.removeAll() self.delegate?.thumbnailView?(thumbanView: self, didDragPages: dragPages, indexpath: indexPath) return true } 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 false } } class MyLayoutAttributes: NSCollectionViewLayoutAttributes { var markerLocation = CGPoint.zero }