// // KMLeftSideViewController+Thumbnail.swift // PDF Master // // Created by tangchao on 2024/1/12. // import Foundation extension KMLeftSideViewController.Key { static let thumbSizeScaling = "KMThumbnailSizeScalingKey" static let thumbDisplayPageSizeKey = "kKMThumbnailDisplayPageSizeKey" } // MARK: - Actions extension KMLeftSideViewController { public func refreshUIOfThumbnailIfNeed(preference: Bool = false) { if self.type.methodType != .Thumbnail { return } if preference { self.reloadThumbnailSize() } Task { @MainActor in self.thumbnailTableView.reloadData() } } public func reloadThumbnailDataIfNeed() { if self.type.methodType != .Thumbnail { return } self.resetThumbnails() } public func reloadThumbnailSize() { let defaultSize = roundf(KMPreference.shared.thumbPageSize) var thumbnailSize = Self.kTinySize if defaultSize < Self.kTinySize + Self.kFudgeSize { } else { if defaultSize < Self.kSmallSize + Self.kFudgeSize { thumbnailSize = Self.kSmallSize } else { if defaultSize < Self.kLargeSize + Self.kFudgeSize { thumbnailSize = Self.kLargeSize } else { thumbnailSize = Self.kHugeSize } } } if (abs(thumbnailSize - Float(self.thumbnailCacheSize)) > Self.kFudgeSize) { self.thumbnailCacheSize = thumbnailSize.cgFloat } Task { @MainActor in self.thumbnailTableView.reloadData() } } // 显示缩略图模块 func showThumbnail() { if self.leftView.segmentedControl.selectedSegment == 1 { } else { self.leftView.segmentedControl.selectedSegment = 1 } } func updateThumbnail(at index: Int) { if index < self.thumbnails.count { self.thumbnails[index].dirty = true self.thumbnailTableView.reloadData(forRowIndexes: IndexSet(integer: index), columnIndexes: IndexSet(integer: 0)) if index < self.thumbnailTableView.numberOfRows { let thumbailTabCell = self.thumbnailTableView.view(atColumn: 0, row: index, makeIfNecessary: true) as? KMThumbnailTableviewCell thumbailTabCell?.pageView.updateThumbnial(needReset: true) } /* 原问题:CrashKit - SKMainWindowController updateThumbnailAtPageIndex:] ([__NSArrayM objectAtIndex:]: index 9223372036854775807 beyond bounds [0 .. 17]) 注释原因:缩略图高亮为自定义后,刷新会将高亮刷新到0行 */ // [leftSideController.thumbnailTableView reloadData]; // BOOL visible = NO; // PDFPage *page = [[pdfView document] pageAtIndex:anIndex]; // if([self.pdfView.visiblePages containsObject:page]) { // visible = YES; // } // NSInteger curIndex = [pdfView.document indexForPage:pdfView.currentPage]; // if (need && curIndex != anIndex && !visible) { // [pdfView goToPage:[[pdfView document] pageAtIndex:anIndex]]; // } } } func allThumbnailsNeedUpdate() { for thumb in self.thumbnails { thumb.dirty = true } self.reloadThumbnailSize() } func thumb_selectRowIndexsIfNeed(_ indexs: IndexSet) { let isReadMode = self.mainViewController?.viewManager.isPDFReadMode ?? false if self.type.methodType != .Thumbnail && isReadMode == false { // return } if indexs.isEmpty { return } Task { @MainActor in self.stopRepeatLoad = true self.thumbnailTableView.selectRowIndexes(indexs, byExtendingSelection: false) self.stopRepeatLoad = false self.thumbnailTableView.scrollRowToVisible(indexs.first ?? 0) } } func thumb_initSubViews() { self.thumbnailZoomInButton.target = self self.thumbnailZoomInButton.action = #selector(thumbnailSizeScaling) self.thumbnailZoomInButton.tag = 1 self.thumbnailZoomOutButton.target = self self.thumbnailZoomOutButton.action = #selector(thumbnailSizeScaling) self.thumbnailZoomOutButton.tag = 0 self.thumbnailTableView.delegate = self self.thumbnailTableView.dataSource = self self.thumbnailTableView.thumbDelegate = self self.thumbnailTableView.botaDelegate = self self.thumbnailTableView.menu?.delegate = self self.thumbnailTableView.registerForDraggedTypes([.localDraggedTypes, .fileURL,.string,.pdf]) self.thumbnailTableView.registerForDraggedTypes(NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) }) self.thumbnailTableView.setDraggingSourceOperationMask(.every, forLocal: false) } func thumb_initDefalutValue() { self.isDisplayPageSize = KMDataManager.ud_bool(forKey: Self.Key.thumbDisplayPageSizeKey) self.thumbnailView.wantsLayer = true self.thumbnailView.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor self.thumbnailTitleLabel.stringValue = KMLocalizedString("Thumbnails") self.thumbnailTitleLabel.textColor = KMAppearance.Layout.h0Color() self.thumbnailZoomInButton.toolTip = KMLocalizedString("Zoom In") self.thumbnailZoomOutButton.toolTip = KMLocalizedString("Zoom Out") self.thumbnailTableView.backgroundColor = KMAppearance.Layout.l0Color() self.thumbnailTableView.allowsMultipleSelection = true self.thumbnailTableView.selectionHighlightStyle = .none } func thumb_fetchSelectedRows() -> [Int]? { return self.thumbnailTableView.selectedRowIndexes.sorted() } func updateThumbnailSelection() { let pageIndex = self.currentPageIndex() self.updatingThumbnailSelection = true self.thumbnailTableView.km_safe_selectRowIndexes(IndexSet(integer: pageIndex), byExtendingSelection: false) self.thumbnailTableView.scrollRowToVisible(pageIndex) self.updatingThumbnailSelection = false } } // MARK: - Others extension KMLeftSideViewController {} // MARK: - KMThumbnailTableViewDelegate extension KMLeftSideViewController: KMThumbnailTableViewDelegate { func tableView(_ tableView: NSTableView, highlightLevelForRow row: Int) -> UInt { if tableView.isEqual(to: self.thumbnailTableView) { // NSUInteger i, iMax = [lastViewedPages count]; // for (i = 0; i < iMax; i++) { // if (row == (NSInteger)[lastViewedPages pointerAtIndex:i]) // return i; // } } return UInt.max } func tableView(_ tableView: NSTableView, commandSelectRow rowIndex: Int) -> Bool { if tableView.isEqual(to: self.thumbnailTableView) { // NSRect rect = [[[pdfView document] pageAtIndex:row] boundsForBox:kPDFDisplayBoxCropBox]; // // rect.origin.y = NSMidY(rect) - 0.5 * SNAPSHOT_HEIGHT; // rect.size.height = SNAPSHOT_HEIGHT; // [self showSnapshotAtPageNumber:row forRect:rect scaleFactor:[pdfView scaleFactor] autoFits:NO]; let thumbailTabCell = tableView.view(atColumn: 0, row: rowIndex, makeIfNecessary: true) as? KMThumbnailTableviewCell thumbailTabCell?.isSelectCell = true var rowIndexSet = IndexSet() for i in self.thumbnailTableView.selectedRowIndexes { rowIndexSet.insert(i) } rowIndexSet.insert(rowIndex) self.thumbnailTableView.selectRowIndexes(rowIndexSet, byExtendingSelection: true) return true } return false } func tableView(_ tableView: NSTableView, shiftSelectRow rowIndex: Int) -> Bool { if tableView.isEqual(to: self.thumbnailTableView) { if (self.thumbnailTableView.selectedRowIndexes.count == 0) { let thumbailTabCell = tableView.view(atColumn: 0, row: rowIndex, makeIfNecessary: true) as? KMThumbnailTableviewCell thumbailTabCell?.isSelectCell = true var rowIndexSet = IndexSet() self.thumbnailTableView.selectRowIndexes(rowIndexSet, byExtendingSelection: true) return true } else if (self.thumbnailTableView.selectedRowIndexes.count == 1 && self.thumbnailTableView.selectedRowIndexes.first == rowIndex) { return false } else { var fristIndex = self.thumbnailTableView.selectedRowIndexes.first ?? Int.min var lastIndex = self.thumbnailTableView.selectedRowIndexes.last ?? Int.max for idx in self.thumbnailTableView.selectedRowIndexes { if(idx < fristIndex) { fristIndex = idx } if(idx > lastIndex) { lastIndex = idx } } if(rowIndex < fristIndex) { fristIndex = rowIndex } else if (rowIndex > lastIndex) { lastIndex = rowIndex } var rowIndexSet = IndexSet() for i in fristIndex ... lastIndex { rowIndexSet.insert(i) } self.thumbnailTableView.selectRowIndexes(rowIndexSet, byExtendingSelection: true) self.thumbnailTableView.ks_reloadData() return true } } return false } } // MARK: - Undo & Redo extension KMLeftSideViewController { @objc dynamic func tableView(_ tv: NSTableView, rotateRowsWithIndexes rowIndexes: NSIndexSet) { if tv.isEqual(to: self.thumbnailTableView) { for idx in rowIndexes { if (idx != NSNotFound) { let page = self.pdfDocument()?.page(at: UInt(idx)) (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).rotatePage(page, pageAt: idx) page?.rightRotate() self.layoutDocumentView() self.resetThumbnails() // NSInteger pageIndex = MIN(idx, [[pdfView document] pageCount]-1); // [pdfView goToPage:[[pdfView document] pageAtIndex:pageIndex]]; } self.thumbnailTableView.selectRowIndexes(rowIndexes as IndexSet, byExtendingSelection: true) } } } @objc dynamic func rotatePage(_ page: CPDFPage?, pageAt index: Int) { (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).tableView(self.thumbnailTableView, rotateRowsWithIndexes: NSIndexSet(index: index)) page?.leftRotate() self.layoutDocumentView() self.resetThumbnails() } @objc dynamic func insertPage(_ page: CPDFPage, pageAt index: Int) { (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject)._undo_removePage(page, at: index) self.pdfDocument()?.insertPageObject(page, at: UInt(index)) self.layoutDocumentView() self.resetThumbnails(ks: false) let pageIndex = min(index, self.pageCount()-1) Task { @MainActor in self.listView?.go(toPageIndex: pageIndex, animated: false) } } @objc private func _undo_removePage(_ page: CPDFPage, at idx: Int) { self.tableView(self.thumbnailTableView, deleteRowsWithIndexes: IndexSet(integer: idx)) } @objc dynamic func insertPages(_ selectedIndexSet: IndexSet, pageAt index: Int) { (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).deletePages(selectedIndexSet, pageAt: index) self.layoutDocumentView() self.resetThumbnails(ks: false) Task { @MainActor in if let pageIndex = selectedIndexSet.first { self.listView?.go(toPageIndex: pageIndex, animated: false) } } } @objc dynamic func deletePages(_ selectedIndexSet: IndexSet, pageAt index: Int) { (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).insertPages(selectedIndexSet, pageAt: index) for idx in selectedIndexSet { if idx < self.pageCount() { self.pdfDocument()?.removePage(at: UInt(idx)) } } self.layoutDocumentView() self.resetThumbnails() let pageIndex = min(index, self.pageCount()-1) self.listView?.go(toPageIndex: pageIndex, animated: false) } } // MARK: - Menu Actions extension KMLeftSideViewController { @objc func cutPage(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.tableView(self.thumbnailTableView, cutRowsWithIndexes: self.thumbnailTableView.selectedRowIndexes) } @objc func copyPage(_ sender: AnyObject?) { self.tableView(self.thumbnailTableView, copyRowsWithIndexes: self.thumbnailTableView.selectedRowIndexes) } @objc func pastePage(_ sender: AnyObject?) { self.tableView(self.thumbnailTableView, pasteFromPasteboard: nil) } @objc func deletePage(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.tableView(self.thumbnailTableView, deleteRowsWithIndexes: self.thumbnailTableView.selectedRowIndexes) } @objc func rotatePageMenuAction(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.tableView(self.thumbnailTableView, rotateRowsWithIndexes: self.thumbnailTableView.selectedRowIndexes as NSIndexSet) } @objc func quickInsert(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } let idx = self.thumbnailTableView.selectedRowIndexes.first ?? NSNotFound if idx == NSNotFound || idx >= self.pageCount() { return } let result = self.listView?.insertPage(KMNormalBlankSize, at: idx+1) ?? false if result == false { return } var selectedIndexSet = IndexSet() selectedIndexSet.insert(idx+1) self.insertPages(selectedIndexSet, pageAt: idx) } @objc func insert(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } guard let document = self.pdfDocument() else { return } let idx = self.thumbnailTableView.selectedRowIndexes.first ?? NSNotFound if idx == NSNotFound || idx >= document.pageCount { return } if document.allowsCopying == false || document.allowsPrinting == false { Task { _ = await KMAlertTool.runModel(message: KMLocalizedString("This is a secured document. Editing is not permitted.")) } return } let winC = KMPDFEditInsertBlankPageWindow(document: document) winC.insertLocation = 3 winC.currentPage = idx + 1 winC.callback = { [weak self] pdfDoc, _, pages, insertI in guard let myWinC = self?.kmCurrentWindowC as? KMPDFEditInsertBlankPageWindow else { self?.km_quick_endSheet() return } if let _ = pages { var pageSize = myWinC.pageSize let direction = myWinC.pageRotation == 0 ? 0 : 1 if (direction == 0) { // 纵向 if (pageSize.width > pageSize.height) { // 需要交换 let tmp = pageSize.width pageSize.width = pageSize.height pageSize.height = tmp } else { // no things. } } else { // 横向 if (pageSize.width > pageSize.height) { // no things. } else { // 需要交换 let tmp = pageSize.width pageSize.width = pageSize.height pageSize.height = tmp } } /// 插入位置 let document = CPDFDocument() document?.insertPage(pageSize, at: 0) if let page = document?.page(at: 0) { self?.insertPage(page, pageAt: insertI) } } self?.km_quick_endSheet() } self.km_beginSheet(windowC: winC) } @objc func insertPDF(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } guard let document = self.pdfDocument() else { return } let idx = self.thumbnailTableView.selectedRowIndexes.first ?? NSNotFound if idx == NSNotFound || idx >= document.pageCount { return } if document.allowsCopying == false || document.allowsPrinting == false { Task { _ = await KMAlertTool.runModel(message: KMLocalizedString("This is a secured document. Editing is not permitted.")) } return } let panel = NSOpenPanel() panel.allowedFileTypes = ["pdf"] panel.beginSheetModal(for: self.view.window!) { response in if response == .cancel { return } for fileURL in panel.urls { let pdfDoc = CPDFDocument(url: fileURL) if let data = pdfDoc?.isLocked, data { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { KMBaseWindowController.checkPassword(url: fileURL, type: .owner) { [unowned self] success, resultPassword in if (resultPassword.isEmpty == false) { let insertVC = KMPDFEditInsertPageWindow(document: document, path: fileURL, password: resultPassword) insertVC.insertLocation = 3 insertVC.currentPage = idx + 1 self.km_beginSheet(windowC: insertVC) insertVC.callback = { [weak self] pdfDoc, pwd, pages, insertIdx in var indexs = IndexSet() guard let _winC = self?.kmCurrentWindowC as? KMPDFEditInsertPageWindow, _winC.insertDocument != nil else { self?.km_quick_endSheet() return } let doc = _winC.insertDocument! // self?.model.insertedDocument = doc self?.model.insertedDocumentSet.insert(doc) let fileAttribute = _winC.fileAttribute var insertIndex = insertIdx var insertPages: [CPDFPage] = [] for number in fileAttribute.fetchSelectPages() { if let page = doc.page(at: UInt(number-1)) { insertPages.append(page) indexs.insert(insertIndex) insertIndex += 1 } } for (i, page) in insertPages.enumerated() { self?.pdfDocument()?.insertPageObject(page, at: UInt(insertIdx + i)) } self?.insertPages(indexs, pageAt: insertIdx) self?.km_quick_endSheet() } } } } } else { let insertVC = KMPDFEditInsertPageWindow(document: document, path: fileURL) insertVC.insertLocation = 3 insertVC.currentPage = idx + 1 self.km_beginSheet(windowC: insertVC) insertVC.callback = { [weak self] pdfDoc, pwd, pages, insertIdx in var indexs = IndexSet() guard let _winC = self?.kmCurrentWindowC as? KMPDFEditInsertPageWindow, _winC.insertDocument != nil else { self?.km_quick_endSheet() return } let doc = _winC.insertDocument! // self?.model.insertedDocument = doc self?.model.insertedDocumentSet.insert(doc) let fileAttribute = _winC.fileAttribute var insertIndex = insertIdx var insertPages: [CPDFPage] = [] for number in fileAttribute.fetchSelectPages() { if let page = doc.page(at: UInt(number-1)) { insertPages.append(page) indexs.insert(insertIndex) insertIndex += 1 } } for (i, page) in insertPages.enumerated() { self?.pdfDocument()?.insertPageObject(page, at: UInt(insertIdx + i)) } self?.insertPages(indexs, pageAt: insertIdx) self?.km_quick_endSheet() } } } } } @objc func extractPage(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.tableView(self.thumbnailTableView, extractRowsWithIndexes: self.thumbnailTableView.selectedRowIndexes) } @objc func pageEdit(_ sender: AnyObject?) { self.delegate?.controller?(controller: self, itemClick: nil, itemKey: .pageEdit, params: nil) } @objc func displayPageSize(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.isDisplayPageSize = !self.isDisplayPageSize KMDataManager.ud_set(self.isDisplayPageSize, forKey: Self.Key.thumbDisplayPageSizeKey) Task { @MainActor in self.thumbnailTableView.ks_reloadData() } } @objc func sharePage(_ sender: AnyObject?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } guard let document = self.pdfDocument() else { KMPrint("document is nil.", beep: true) return } let ris = self.thumbnailTableView.selectedRowIndexes if ris.isEmpty { KMPrint("empty selection.", beep: true) return } let pdf = CPDFDocument() pdf?.importPages(ris, from: document, at: 0) var fileName = self.fileNameWithSelectedPages(ris) if (fileName.count > 50) { fileName = fileName.substring(to: 50) } let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) var cachesDir = paths.first ?? "" cachesDir = "\(cachesDir)/\(fileName).pdf" let url = URL(fileURLWithPath: cachesDir) let winC = KMProgressWindowController() self.km_beginSheet(windowC: winC) { resp, obj in } DispatchQueue.global().async { let sucess = pdf?.write(toFile: cachesDir) ?? false if (sucess) { DispatchQueue.main.async { let represent = (sender as? NSMenuItem)?.representedObject as? NSSharingService represent?.perform(withItems: [url]) self.km_quick_endSheet() winC.close() } } else { self.km_quick_endSheet() winC.close() } } } }