KMPDFThumbnailView.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. //
  2. // KMPDFThumbnailView.swift
  3. // PDF Master
  4. //
  5. // Created by lxy on 2022/12/15.
  6. //
  7. import Cocoa
  8. @objc protocol KMPDFThumbnailViewDelegate: NSObjectProtocol {
  9. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didSelectPageAtIndex index: UInt, event: NSEvent)
  10. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, item: KMPDFThumbnailItem, rightMouseDidSelect index: UInt, event: NSEvent)
  11. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didDragAddFiles files: [URL], indexpath: IndexPath)
  12. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, didDragPages pages: [Int], indexpath: IndexPath)
  13. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, minimumLineSpacingForSectionAt section: Int) -> CGFloat
  14. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
  15. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, insetForSectionAt section: Int) -> NSEdgeInsets
  16. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, sizeForItemAt indexpath: IndexPath) -> NSSize
  17. @objc optional func thumbnailView(thumbanView: KMPDFThumbnailView, itemForRepresentedObjectAt indexpath: IndexPath) -> NSCollectionViewItem
  18. }
  19. class KMPDFThumbnailView: NSView {
  20. open weak var delegate: KMPDFThumbnailViewDelegate?
  21. var document : CPDFDocument = CPDFDocument()
  22. var isShowPageSize : Bool = false
  23. var backgroundColor : NSColor = NSColor.white
  24. var highlightColor : NSColor = NSColor.blue
  25. var pageSelectionColor : NSColor = NSColor()
  26. var textSelectionColor : NSColor = NSColor()
  27. var thumbnailSzie : CGSize = CGSize()
  28. var scrollView : NSScrollView = NSScrollView()
  29. var collectionView : NSCollectionView = NSCollectionView()
  30. var doublePageMode : Bool! = false
  31. private let thumbnailDraggedTypes = NSPasteboard.PasteboardType(rawValue: "localForDraggedTypes")
  32. private var dragedIndexPaths: [IndexPath] = []
  33. //标记线
  34. var isNeedMarkerLine: Bool = false
  35. var markerLineView: NSView = NSView()
  36. var markBeginIndexes: IndexSet = IndexSet()
  37. //注释状态
  38. var annotationShowState: KMAnnotationViewShowType = .none {
  39. didSet {
  40. self.collectionView.reloadData()
  41. }
  42. }
  43. //悬浮
  44. var hoverIndex: Int = -1
  45. override func draw(_ dirtyRect: NSRect) {
  46. super.draw(dirtyRect)
  47. }
  48. required init?(coder: NSCoder) {
  49. super.init(coder: coder)
  50. self.initUI()
  51. }
  52. override init(frame frameRect: NSRect) {
  53. super.init(frame: frameRect)
  54. self.initUI()
  55. }
  56. private func initUI() {
  57. self.wantsLayer = true
  58. self.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor
  59. self.thumbnailSzie = CGSize(width: 120, height: 155)
  60. let layout = NSCollectionViewFlowLayout()
  61. layout.sectionInset = NSEdgeInsetsMake(8, 15, 8, 15)
  62. layout.minimumLineSpacing = 0
  63. layout.minimumInteritemSpacing = 0
  64. self.collectionView.autoresizingMask = NSView.AutoresizingMask(rawValue: 18)
  65. self.collectionView.collectionViewLayout = layout
  66. self.collectionView.dataSource = self
  67. self.collectionView.delegate = self
  68. self.collectionView.register(KMPDFThumbnailItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem"))
  69. self.collectionView.registerForDraggedTypes([thumbnailDraggedTypes])
  70. self.collectionView.isSelectable = true
  71. self.collectionView.wantsLayer = true
  72. self.collectionView.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor
  73. self.scrollView.hasHorizontalScroller = true
  74. self.scrollView.hasVerticalScroller = true
  75. self.scrollView.autohidesScrollers = true
  76. self.scrollView.minMagnification = 1.0
  77. self.scrollView.scrollerStyle = NSScroller.Style.overlay
  78. self.scrollView.documentView = self.collectionView
  79. self.scrollView.wantsLayer = true
  80. self.scrollView.layer?.backgroundColor = NSColor.clear.cgColor
  81. self.scrollView.contentView.layer?.backgroundColor = NSColor(hex: "#FFFFFF").cgColor
  82. self.scrollView.contentView.wantsLayer = true
  83. self.addSubview(self.scrollView)
  84. markerLineView.wantsLayer = true
  85. markerLineView.layer?.backgroundColor = NSColor(hex: "#1770F4").cgColor
  86. markerLineView.frame = CGRectMake(0, 0, 100, 2)
  87. self.collectionView.addSubview(markerLineView)
  88. markerLineView.isHidden = true
  89. }
  90. override func setFrameSize(_ newSize: NSSize) {
  91. super.setFrameSize(newSize)
  92. self.scrollView.frame = self.bounds
  93. }
  94. public func reloadData(indexs: Set<IndexPath> = NSSet() as! Set<IndexPath>) {
  95. if indexs.count == 0 {
  96. self.collectionView.reloadData()
  97. } else {
  98. var indexpaths: Set<IndexPath> = []
  99. for index in indexs {
  100. if (index.section >= self.collectionView.numberOfSections) {
  101. continue
  102. }
  103. if (index.item >= self.collectionView.numberOfItems(inSection: index.section)) {
  104. continue
  105. }
  106. indexpaths.insert(index)
  107. }
  108. if (indexpaths.count == 0) {
  109. return
  110. }
  111. if (Thread.isMainThread) {
  112. self.collectionView.reloadItems(at: indexpaths)
  113. } else {
  114. DispatchQueue.main.async {
  115. self.collectionView.reloadItems(at: indexpaths)
  116. }
  117. }
  118. }
  119. }
  120. }
  121. extension KMPDFThumbnailView : NSCollectionViewDataSource {
  122. func numberOfSections(in collectionView: NSCollectionView) -> Int {
  123. return 1
  124. }
  125. func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  126. if self.document.isLocked {
  127. return 0
  128. }
  129. return Int(self.document.pageCount)
  130. }
  131. func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  132. let view = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath)
  133. if (view != nil) {
  134. return view!
  135. }
  136. let cellView: KMPDFThumbnailItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem"), for: indexPath) as! KMPDFThumbnailItem
  137. cellView.thumbnailView = self
  138. cellView.page = self.document.page(at: UInt(indexPath.item))
  139. cellView.annotationShowState = self.annotationShowState
  140. if indexPath.item == hoverIndex {
  141. cellView.hover = true
  142. } else {
  143. cellView.hover = false
  144. }
  145. cellView.mouseDownAction = { [unowned self] (view, event) in
  146. self.delegate?.thumbnailView?(thumbanView: self, didSelectPageAtIndex: UInt(indexPath.item), event: event)
  147. }
  148. cellView.rightMouseDownAction = { [unowned self] (view, event) in
  149. self.delegate?.thumbnailView?(thumbanView: self, item: view, rightMouseDidSelect: UInt(indexPath.item), event: event)
  150. }
  151. cellView.hoverCallBack = { [unowned self] view, mouseEntered in
  152. if self.collectionView.item(at: hoverIndex)?.view != nil {
  153. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  154. tempCell!.hover = false
  155. }
  156. if mouseEntered {
  157. hoverIndex = indexPath.item
  158. if self.collectionView.item(at: hoverIndex)?.view != nil {
  159. let tempCell = self.collectionView.item(at: hoverIndex) as? KMPDFThumbnailItem
  160. tempCell!.hover = true
  161. }
  162. } else {
  163. hoverIndex = -1
  164. }
  165. }
  166. return cellView
  167. }
  168. }
  169. extension KMPDFThumbnailView: NSCollectionViewDelegateFlowLayout {
  170. /**
  171. * MARK: item大小
  172. */
  173. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  174. if (self.delegate != nil) {
  175. let size = self.delegate!.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath)
  176. if (size != nil) {
  177. // self.thumbnailSzie = size!
  178. return size!
  179. }
  180. }
  181. let page = self.document.page(at: UInt(indexPath.item))
  182. let height = KMPDFThumbnailItem.sizeToFit(size: self.thumbnailSzie, page: page!, isShow: self.isShowPageSize)
  183. return NSMakeSize(collectionView.frame.size.width - 20, CGFloat(height))
  184. }
  185. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  186. if (self.delegate != nil) {
  187. let minimumLineSpacing = self.delegate!.thumbnailView?(thumbanView: self, minimumLineSpacingForSectionAt: section)
  188. if (minimumLineSpacing != nil) {
  189. return minimumLineSpacing!
  190. }
  191. }
  192. return 8
  193. }
  194. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  195. if (self.delegate != nil) {
  196. let minimumInteritemSpacing = self.delegate!.thumbnailView?(thumbanView: self, minimumInteritemSpacingForSectionAt: section)
  197. if (minimumInteritemSpacing != nil) {
  198. return minimumInteritemSpacing!
  199. }
  200. }
  201. return 0.01
  202. }
  203. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets {
  204. let inset = self.delegate?.thumbnailView?(thumbanView: self, insetForSectionAt: section)
  205. if (inset != nil) {
  206. return inset!
  207. }
  208. return NSEdgeInsetsZero
  209. }
  210. }
  211. extension KMPDFThumbnailView: NSCollectionViewDelegate {
  212. func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  213. return indexPaths
  214. }
  215. func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
  216. // let index: IndexPath = indexPaths.first!
  217. // self.delegate?.thumbnailView?(thumbanView: self, didSelectPageAtIndex: UInt(index.item))
  218. }
  219. func collectionView(_ collectionView: NSCollectionView, shouldDeselectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  220. return indexPaths
  221. }
  222. func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set<IndexPath>) {
  223. }
  224. func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
  225. return true
  226. }
  227. func collectionView(_ collectionView: NSCollectionView, writeItemsAt indexPaths: Set<IndexPath>, to pasteboard: NSPasteboard) -> Bool {
  228. let data: Data = try! NSKeyedArchiver.archivedData(withRootObject: indexPaths, requiringSecureCoding: true)
  229. pasteboard.declareTypes([self.thumbnailDraggedTypes], owner: self)
  230. pasteboard.setData(data, forType: self.thumbnailDraggedTypes)
  231. self.dragedIndexPaths.removeAll()
  232. for indexPath in indexPaths {
  233. self.dragedIndexPaths.append(indexPath)
  234. }
  235. return true
  236. }
  237. func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) {
  238. if self.isNeedMarkerLine {
  239. self.markBeginIndexes = self.collectionView.selectionIndexes
  240. }
  241. }
  242. func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  243. if self.isNeedMarkerLine {
  244. if self.markBeginIndexes.count != 0 {
  245. if !self.markBeginIndexes.contains(proposedDropIndexPath.pointee.item) {
  246. //标记线
  247. var rect = self.collectionView.frameForItem(at: proposedDropIndexPath.pointee.item)
  248. rect.size.height = 2
  249. self.markerLineView.frame = rect
  250. self.markerLineView.isHidden = false
  251. }
  252. }
  253. }
  254. let pboard = draggingInfo.draggingPasteboard
  255. if (pboard.availableType(from: [thumbnailDraggedTypes]) != nil) {
  256. return .move
  257. }
  258. return NSDragOperation(rawValue: 0)
  259. }
  260. func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
  261. markerLineView.isHidden = true
  262. self.markBeginIndexes = IndexSet()
  263. let pboard = draggingInfo.draggingPasteboard
  264. if (pboard.availableType(from: [NSPasteboard.PasteboardType(rawValue: "localForDraggedTypes")]) != nil) {
  265. let dragIndexPath = self.dragedIndexPaths.first
  266. if (dragIndexPath == nil) {
  267. return false
  268. }
  269. let dragIndex = dragIndexPath!.item
  270. let toIndex = max(0, indexPath.item)
  271. if dragIndex < 0 || dragIndex > self.document.pageCount {
  272. return false
  273. }
  274. if (toIndex >= self.document.pageCount) {
  275. return false
  276. }
  277. if dragIndex == toIndex {
  278. return false
  279. }
  280. var dragPages: [Int] = []
  281. for indexpath in dragedIndexPaths {
  282. dragPages.append(indexpath.item)
  283. }
  284. self.dragedIndexPaths.removeAll()
  285. self.delegate?.thumbnailView?(thumbanView: self, didDragPages: dragPages, indexpath: indexPath)
  286. return true
  287. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  288. //获取url
  289. var array: [URL] = []
  290. for item: NSPasteboardItem in pboard.pasteboardItems! {
  291. let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)!
  292. let url = NSURL(string: string)
  293. /// MARK: 暂时支持PDF格式
  294. if (url?.pathExtension?.lowercased() == "pdf") {
  295. array.append(url! as URL)
  296. }
  297. }
  298. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath)
  299. }
  300. return false
  301. }
  302. }
  303. class MyLayoutAttributes: NSCollectionViewLayoutAttributes {
  304. var markerLocation = CGPoint.zero
  305. }