KMNThumbnailBaseViewController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. //
  2. // KMNThumbnailBaseViewController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by 丁林圭 on 2024/10/21.
  6. //
  7. import Cocoa
  8. @objc protocol KMNThumbnailBaseViewDelegate: AnyObject {
  9. @objc optional func clickThumbnailViewControlle(pageEditVC:KMNThumbnailBaseViewController?,currentIndex:Int)
  10. @objc optional func insertPDFThumbnailViewControlle(pageEditVC:KMNThumbnailBaseViewController?,pdfDocment:CPDFDocument?)
  11. @objc optional func changeIndexPathsThumbnailViewControlle(pageEditVC:KMNThumbnailBaseViewController?,selectionIndexPaths: Set<IndexPath>,selectionStrings:String )
  12. }
  13. enum KMNThumbnailChoosePageStyle: Int {
  14. case odd
  15. case even
  16. case horizontal
  17. case vertical
  18. case allPage
  19. case custom
  20. }
  21. internal let kmnThumLocalForDraggedTypes = NSPasteboard.PasteboardType(rawValue: "kmnThumLocalForDraggedTypes")
  22. class KMNThumbnailBaseViewController: KMNBaseViewController,NSCollectionViewDelegate, NSCollectionViewDataSource,NSCollectionViewDelegateFlowLayout {
  23. let subTitleHeight: CGFloat = 20.0
  24. let maxCellHeight: CGFloat = 280.0
  25. weak open var thumbnailBaseViewDelegate: KMNThumbnailBaseViewDelegate?
  26. @IBOutlet var backViewBox: NSBox!
  27. @IBOutlet var collectionView: KMNThumbnailCollectionView!
  28. private var currentDocument:CPDFDocument?
  29. private var thumbnails:[KMNThumbnail] = []
  30. private var isChangeIndexPaths = false
  31. public var dragLocalityPages: [CPDFPage] = []
  32. public var currentUndoManager:UndoManager?
  33. public var isAdjustWidth:Bool = false
  34. public var showDocument: CPDFDocument? {
  35. return currentDocument
  36. }
  37. public var thumbnailChoosePageStyle:KMNThumbnailChoosePageStyle = .custom {
  38. didSet {
  39. var tSelectionIndexPaths: Set<IndexPath> = []
  40. let pageCount = currentDocument?.pageCount ?? 0
  41. switch thumbnailChoosePageStyle {
  42. case .even:
  43. for i in 0 ..< pageCount {
  44. if(i % 2 == 0) {
  45. tSelectionIndexPaths.insert(IndexPath(item: Int(i), section: 0))
  46. }
  47. }
  48. case .odd:
  49. for i in 0 ..< pageCount {
  50. if(i % 2 != 0) {
  51. tSelectionIndexPaths.insert(IndexPath(item: Int(i), section: 0))
  52. }
  53. }
  54. case .allPage:
  55. for i in 0 ..< pageCount {
  56. tSelectionIndexPaths.insert(IndexPath(item: Int(i), section: 0))
  57. }
  58. case .vertical:
  59. for i in 0 ..< pageCount {
  60. let page = showDocument?.page(at: i)
  61. if(page != nil) {
  62. if(page!.rotation % 180 != 0) {
  63. tSelectionIndexPaths.insert(IndexPath(item: Int(i), section: 0))
  64. }
  65. }
  66. }
  67. case .horizontal:
  68. for i in 0 ..< pageCount {
  69. let page = showDocument?.page(at: i)
  70. if(page != nil) {
  71. if(page!.rotation % 180 == 0) {
  72. tSelectionIndexPaths.insert(IndexPath(item: Int(i), section: 0))
  73. }
  74. }
  75. }
  76. default: break
  77. }
  78. isChangeIndexPaths = true
  79. collectionView.selectionIndexPaths = tSelectionIndexPaths
  80. isChangeIndexPaths = false
  81. }
  82. }
  83. var selectionIndexPaths: Set<IndexPath> = [] {
  84. didSet {
  85. var indexpaths: Set<IndexPath> = []
  86. for indexpath in selectionIndexPaths {
  87. if (indexpath.section >= collectionView.numberOfSections) {
  88. continue
  89. }
  90. if indexpath.section < 0 {
  91. continue
  92. }
  93. if (indexpath.item >= collectionView.numberOfItems(inSection: indexpath.section)) {
  94. continue
  95. }
  96. if indexpath.item < 0 {
  97. continue
  98. }
  99. indexpaths.insert(indexpath)
  100. }
  101. collectionView.selectionIndexPaths = indexpaths
  102. collectionView.scrollToItems(at: indexpaths, scrollPosition: .top)
  103. }
  104. }
  105. public var isShowPageSize:Bool = false {
  106. didSet {
  107. if oldValue != isShowPageSize {
  108. var pageSize = pageThumbnailSize
  109. if(isShowPageSize) {
  110. pageSize.height += subTitleHeight
  111. } else {
  112. pageSize.height -= subTitleHeight
  113. }
  114. pageThumbnailSize = pageSize
  115. collectionView.reloadData()
  116. }
  117. }
  118. }
  119. public var pageThumbnailSize:CGSize = CGSizeMake(185.0, 260) {
  120. didSet {
  121. collectionView.reloadData()
  122. }
  123. }
  124. public let defaultItemSize = NSMakeSize(185.0, 260)
  125. deinit {
  126. thumbnailBaseViewDelegate = nil
  127. KMPrint("KMNThumbnailBaseViewController deinit.")
  128. }
  129. init(_ document: CPDFDocument?) {
  130. super.init(nibName: "KMNThumbnailBaseViewController", bundle: nil)
  131. currentDocument = document
  132. }
  133. init(_ filePath: String,password:String?) {
  134. super.init(nibName: "KMNThumbnailBaseViewController", bundle: nil)
  135. let document = CPDFDocument.init(url: URL(fileURLWithPath: filePath))
  136. if password != nil {
  137. document?.unlock(withPassword: password as String?)
  138. }
  139. if document?.allowsCopying == false || document?.allowsPrinting == false {
  140. self.exitCurrentView()
  141. } else {
  142. currentDocument = document
  143. }
  144. }
  145. required init?(coder: NSCoder) {
  146. fatalError("init(coder:) has not been implemented")
  147. }
  148. override func viewDidLoad() {
  149. super.viewDidLoad()
  150. collectionView.delegate = self
  151. collectionView.dataSource = self
  152. collectionView.isSelectable = true //支持拖拽需设置未True
  153. collectionView.allowsMultipleSelection = true
  154. collectionView.register(KMNThumbnailCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "thumbnailCollectionViewItem"))
  155. collectionView.registerForDraggedTypes([.fileURL])
  156. collectionView.setDraggingSourceOperationMask(.every, forLocal: false)
  157. collectionView.setDraggingSourceOperationMask(.every, forLocal: true)
  158. refreshDatas()
  159. }
  160. public func exitCurrentView() {
  161. let minIndexPath = selectionIndexPaths.max(by: { $0.item < $1.item })
  162. thumbnailBaseViewDelegate?.clickThumbnailViewControlle?(pageEditVC: self, currentIndex: minIndexPath?.item ?? 0)
  163. }
  164. public func supportDragFileTypes()->[String] {
  165. let supportFiles = KMNConvertTool.pdfExtensions + KMConvertPDFManager.supportFileType()
  166. return supportFiles
  167. }
  168. public func refreshDatas() {
  169. thumbnails = []
  170. if currentDocument != nil {
  171. for i in 0 ... currentDocument!.pageCount - 1 {
  172. let thumbnail = KMNThumbnail.init(document: currentDocument!, currentPageIndex: Int(i))
  173. thumbnails.append(thumbnail)
  174. }
  175. }
  176. }
  177. // MARK: - NSCollectionViewDataSource
  178. func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  179. return thumbnails.count
  180. }
  181. func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  182. let item: KMNThumbnailCollectionViewItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "thumbnailCollectionViewItem"), for: indexPath) as! KMNThumbnailCollectionViewItem
  183. item.isShowFileSize = isShowPageSize
  184. item.doubleClickBack = { [weak self] in
  185. self?.thumbnailBaseViewDelegate?.clickThumbnailViewControlle?(pageEditVC: self, currentIndex: indexPath.item)
  186. }
  187. item.thumbnailMode = thumbnails[indexPath.item]
  188. return item
  189. }
  190. func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
  191. if isChangeIndexPaths == false {
  192. let indexpathsz = self.collectionView.selectionIndexPaths
  193. let dex:IndexSet = KMNTools.indexpathsToIndexs(indexpaths: indexpathsz)
  194. let selectedIndexPathsString = KMNTools.parseIndexSet(indexSet: dex)
  195. thumbnailBaseViewDelegate?.changeIndexPathsThumbnailViewControlle?(pageEditVC: self, selectionIndexPaths: indexpathsz, selectionStrings: selectedIndexPathsString)
  196. }
  197. }
  198. func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set<IndexPath>) {
  199. if isChangeIndexPaths == false {
  200. let indexpathsz = self.collectionView.selectionIndexPaths
  201. let dex:IndexSet = KMNTools.indexpathsToIndexs(indexpaths: indexpathsz)
  202. let selectedIndexPathsString = KMNTools.parseIndexSet(indexSet: dex)
  203. thumbnailBaseViewDelegate?.changeIndexPathsThumbnailViewControlle?(pageEditVC: self, selectionIndexPaths: indexpathsz, selectionStrings: selectedIndexPathsString)
  204. }
  205. }
  206. // MARK: - NSCollectionViewDelegateFlowLayout
  207. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  208. if isAdjustWidth == false {
  209. return pageThumbnailSize
  210. } else {
  211. let thumbnailMode: KMNThumbnail = thumbnails[indexPath.item]
  212. let pageSize = thumbnailMode.pageSize
  213. var cellHeight = pageSize.width / ((pageThumbnailSize.width - 32) * pageThumbnailSize.height)
  214. var cellWidth = pageThumbnailSize.width
  215. if cellHeight > maxCellHeight {
  216. cellHeight = 280.0
  217. cellWidth = cellHeight/pageSize.height * pageSize.width
  218. }
  219. return CGSize(width: cellWidth, height: cellHeight)
  220. }
  221. }
  222. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  223. return 16.0
  224. }
  225. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  226. return 24.0
  227. }
  228. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets {
  229. return NSEdgeInsetsMake(24.0, 24.0, 24.0, 24.0)
  230. }
  231. //MARK: - NSCollectionViewDelegate
  232. func collectionView(_ collectionView: NSCollectionView,
  233. writeItemsAt indexPaths: Set<IndexPath>,
  234. to pasteboard: NSPasteboard) -> Bool {
  235. if IAPProductsManager.default().isAvailableAllFunction() == false {
  236. return false
  237. }
  238. var docmentName = currentDocument?.documentURL.lastPathComponent.deletingPathExtension ?? ""
  239. let pagesName = indexPaths.count > 1 ? " pages" : " page"
  240. var tFileName = pagesName + KMNTools.parseIndexPathsSet(indexSets: collectionView.selectionIndexPaths)
  241. if tFileName.count > 50 {
  242. tFileName = String(tFileName.prefix(50))
  243. }
  244. pasteboard.declareTypes([.fileURL], owner: self)
  245. let writePDFDocument = CPDFDocument()
  246. for indexPath in indexPaths {
  247. let row = indexPath.item
  248. if let copyPage = currentDocument?.page(at: UInt(row)) as? CPDFPage {
  249. writePDFDocument?.insertPageObject(copyPage, at: writePDFDocument?.pageCount ?? 0)
  250. }
  251. }
  252. var cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  253. cachesDir = cachesDir.appendingPathComponent("PageEdit_Pasteboard")
  254. let fileManager = FileManager.default
  255. if !fileManager.fileExists(atPath: cachesDir.path) {
  256. try? FileManager.default.createDirectory(atPath: cachesDir.path, withIntermediateDirectories: true, attributes: nil)
  257. }
  258. docmentName = "\(docmentName)\(tFileName)"
  259. if docmentName.count > 50 {
  260. docmentName = String(docmentName.prefix(50))
  261. }
  262. let filePathURL = cachesDir.appendingPathComponent(docmentName).appendingPathExtension("pdf")
  263. let success = writePDFDocument?.write(to: filePathURL, isSaveFontSubset:false)
  264. if success == true {
  265. pasteboard.setPropertyList([filePathURL.path], forType: .fileURL)
  266. return true
  267. } else {
  268. return false
  269. }
  270. }
  271. func collectionView(_ collectionView: NSCollectionView,
  272. draggingSession session: NSDraggingSession,
  273. willBeginAt screenPoint: NSPoint,
  274. forItemsAt indexPaths: Set<IndexPath>) {
  275. let sortedIndexPaths = indexPaths.sorted { (ip1, ip2) -> Bool in
  276. if ip1.section == ip2.section {
  277. return ip1.item < ip2.item
  278. }
  279. return ip1.section < ip2.section
  280. }
  281. dragLocalityPages = []
  282. for fromIndexPath in sortedIndexPaths {
  283. let page = thumbnails[fromIndexPath.item].thumbnaiPage
  284. if(page != nil) {
  285. dragLocalityPages.append(page!)
  286. }
  287. }
  288. }
  289. func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  290. let pboard = draggingInfo.draggingPasteboard
  291. if dragLocalityPages.count != 0 {
  292. if proposedDropOperation.pointee == .on {
  293. proposedDropOperation.pointee = .before
  294. }
  295. return .move
  296. } else if let availableType = pboard.availableType(from: [.fileURL]), availableType == .fileURL {
  297. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String], fileNames.count >= 1 {
  298. let path = fileNames.first!
  299. let pathExtension = URL(fileURLWithPath: path).pathExtension.lowercased()
  300. if pathExtension == "pdf" || supportDragFileTypes().contains(pathExtension) {
  301. return .copy
  302. }
  303. }
  304. }
  305. return []
  306. }
  307. func collectionView(_ collectionView: NSCollectionView,
  308. acceptDrop draggingInfo: NSDraggingInfo,
  309. indexPath: IndexPath,
  310. dropOperation: NSCollectionView.DropOperation) -> Bool {
  311. let pboard = draggingInfo.draggingPasteboard
  312. if dragLocalityPages.count != 0 {
  313. movePages(pages: dragLocalityPages, destinationDex: indexPath.item)
  314. return true
  315. } else if let availableType = pboard.availableType(from: [.fileURL]), availableType == .fileURL {
  316. let index = indexPath.item
  317. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String] {
  318. insertFromFilePath(fileNames: fileNames, formDex: 0, indexDex: UInt(index), selectIndexs: []) { newSelectIndexs in
  319. self.refreshDatas()
  320. self.collectionView.reloadData()
  321. self.collectionView.selectionIndexPaths = newSelectIndexs
  322. if newSelectIndexs.isEmpty { return }
  323. let firstIndexPath = newSelectIndexs.first
  324. self.collectionView.scrollToItems(at: [firstIndexPath ?? IndexPath(item: 0, section: 0)], scrollPosition: .top)
  325. }
  326. return true
  327. }
  328. }
  329. return false
  330. }
  331. func collectionView(_ collectionView: NSCollectionView,
  332. draggingSession session: NSDraggingSession,
  333. endedAt screenPoint: NSPoint,
  334. dragOperation operation: NSDragOperation) {
  335. dragLocalityPages = []
  336. }
  337. }