// // KMHomeRightView.swift // PDF Reader Pro // // Created by Niehaoyu on 2024/10/10. // import Cocoa import KMComponentLibrary @objc public protocol KMHomeRightViewDelegate: AnyObject { //点击管理快捷工具按钮 @objc optional func homeRightViewDidManageQuickTools(_ view: KMHomeRightView) //点击快捷工具列表中的某一项 @objc optional func homeRightViewDidQuickToolsItemClicked(_ view: KMHomeRightView, _ toolType: HomeQuickToolType) //最近文件列表删除更新结束后回调 @objc optional func homeRightViewDidRecentFilesUpdated(_ view: KMHomeRightView) //选择打开文件 @objc optional func homeRightViewDidChooseFileToOpen(_ view: KMHomeRightView, _ fileURL: URL) } public class KMHomeRightView: BaseXibView { @IBOutlet var scrollView: NSScrollView! @IBOutlet var collectionView: NSCollectionView! var filesHeaderView: KMHomeFilesHeaderView = KMHomeFilesHeaderView() var quickToolsView: KMHomeQuickToolsView = KMHomeQuickToolsView() var filesEmptyView: KMHistoryEmptyView = KMHistoryEmptyView() var groupView: ComponentGroup! var menuActionIndexPaths: Set = [] var groupActionView: NSView? var groupViewPoint: CGPoint = CGPointZero var refreshItemData: Bool = true weak open var delegate: KMHomeRightViewDelegate? //MARK: - func public override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } public required init?(coder decoder: NSCoder) { super.init(coder: decoder) } override init(frame frameRect: NSRect) { super.init(frame: frameRect) } public override func awakeFromNib() { super.awakeFromNib() configUI() } public func resetScrollerStyle() { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { self.scrollView.scrollerStyle = .overlay } } func configUI() { collectionView.backgroundColors = [NSColor.clear] collectionView.wantsLayer = true collectionView.layer?.backgroundColor = NSColor.clear.cgColor collectionView.delegate = self collectionView.dataSource = self collectionView.allowsEmptySelection = true collectionView.register(KMHistoryFileThumbItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHistoryFileThumbItem")) collectionView.register(KMHistoryFileListItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHistoryFileListItem")) collectionView.register(KMHomeQuickToolsView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "kmHomeQuickToolsView")) collectionView.register(KMHomeFilesHeaderView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "kmHomeFilesHeaderView")) collectionView.register(KMHomeFilesEmptyHeaderView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHomeFilesEmptyHeaderView")) collectionView.register(KMHomeFilesEmptyHeaderView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionFooter, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHomeFilesEmptyHeaderView")) } //刷新所有数据 public func reloadData() { HistoryFilesManager.manager.refreshHistoryFile() collectionView.reloadData() } //刷新列表文件UI public func updateHistoryFileListItemUI() { if HistoryFilesManager.manager.showMode == .List { refreshItemData = false collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems()) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { self.refreshItemData = true } } } //刷新快捷工具列表 public func reloadQuickTools() { quickToolsView.reloadData() } public func updateQuickToolsUI() { quickToolsView.refreshUI() } //窗口大小变化时调用此方法 public func windowFrameUpdated() { updateHistoryFileListItemUI() updateQuickToolsUI() } //MARK: - Private private func collectionViewSelectedChanged() { let indexs = collectionView.selectionIndexPaths HistoryFilesManager.manager.selectFiles.removeAll() if indexs.count >= 0 { for index in indexs { let url = HistoryFilesManager.manager.files[index.item] HistoryFilesManager.manager.selectFiles.append(url) } } filesHeaderView.updateDeleteButtonState() } //显示右键菜单 func showFileMoreActionMenu(point: CGPoint) { var viewHeight: CGFloat = 8 var menuItemArr: [ComponentMenuitemProperty] = [] var items: [(String, String)] = [("Show in Finder", "ShowInFinderKey"), ("File Information...", "FileInformationKey"), ("Remove from Recents", "RemovefromRecentKey"), (" ", " "), ("Share...", "ShareKey"), ("Print...", "PrintKey")] if collectionView.selectionIndexPaths.count > 1 { items = [("Show in Finder", "ShowInFinderKey"), ("Remove from Recents", "RemovefromRecentKey"), (" ", " "), ("Share...", "ShareKey")] } for (i, value) in items { if i == " " { let property: ComponentMenuitemProperty = ComponentMenuitemProperty.divider() menuItemArr.append(property) viewHeight += 8 } else { let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString(i), identifier: value) if value == "FileInformationKey" { properties_Menuitem.keyEquivalent = "⌘ D" } else if value == "PrintKey" { properties_Menuitem.keyEquivalent = "⌘ P" } menuItemArr.append(properties_Menuitem) viewHeight += 36 } } if groupView == nil { groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) } groupView.groupDelegate = self groupView?.frame = CGRectMake(0, 0, 180, viewHeight) groupView.updateGroupInfo(menuItemArr) if HistoryFilesManager.manager.showMode == .Thumbnail { groupView.showWithPoint(CGPoint(x: point.x + 60 + CGRectGetWidth(groupView.frame), y: point.y - viewHeight), relativeTo: self) } else { groupView.showWithPoint(CGPoint(x: point.x + 40 + CGRectGetWidth(groupView.frame), y: point.y - viewHeight), relativeTo: self) } menuActionIndexPaths = collectionView.selectionIndexPaths } //MARK: -删除文件列表 func historyFileDeleteAction(_ indexPaths: [URL]) -> Void { let urls: Array = NSDocumentController.shared.recentDocumentURLs NSDocumentController.shared.clearRecentDocuments(nil) DispatchQueue.main.asyncAfter(deadline: .now()) { [weak self] in guard let weakSelf = self else { return } for (_, url) in urls.enumerated() { if !indexPaths.contains(url) { NSDocumentController.shared.noteNewRecentDocumentURL(url) } } weakSelf.delegate?.homeRightViewDidRecentFilesUpdated?(weakSelf) weakSelf.reloadData() } } @objc func emptyViewAddFileClicked(_ sender: NSView) { let openPanel = NSOpenPanel() openPanel.allowedFileTypes = ["pdf", "PDF"] openPanel.allowsMultipleSelection = false openPanel.beginSheetModal(for: self.window!) { [weak self] result in if result == NSApplication.ModalResponse.OK { if let url = openPanel.url { guard let weakSelf = self else { return } weakSelf.delegate?.homeRightViewDidChooseFileToOpen?(weakSelf, url) } } } } //MARK: - MouseEvent public override func rightMouseDown(with event: NSEvent) { super.rightMouseDown(with: event) var point = convert(event.locationInWindow, from: nil) if HistoryFilesManager.manager.showMode == .List { point.x += 10 } let collectionPoint = convert(point, to: collectionView) if let indexPath = collectionView.indexPathForItem(at: collectionPoint) { if collectionView.selectionIndexPaths.contains(indexPath) { } else { collectionView.deselectAll(nil) collectionView.selectItems(at: [indexPath], scrollPosition: .nearestHorizontalEdge) } groupActionView = collectionView.item(at: indexPath)?.view groupViewPoint = point showFileMoreActionMenu(point: point) } collectionViewSelectedChanged() } } //MARK: - NSCollectionViewDelegate, NSCollectionViewDataSource extension KMHomeRightView: NSCollectionViewDelegate, NSCollectionViewDataSource, NSCollectionViewDelegateFlowLayout { public func numberOfSections(in collectionView: NSCollectionView) -> Int { return 2 } public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { if section == 0 { return 0 } return HistoryFilesManager.manager.files.count } public func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { if indexPath.item >= HistoryFilesManager.manager.files.count { return NSCollectionViewItem() } if HistoryFilesManager.manager.showMode == .Thumbnail { let item: KMHistoryFileThumbItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHistoryFileThumbItem"), for: indexPath) as! KMHistoryFileThumbItem item.fileURL = HistoryFilesManager.manager.files[indexPath.item] item.reloadData() item.delegate = self return item } else if HistoryFilesManager.manager.showMode == .List { let item: KMHistoryFileListItem = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMHistoryFileListItem"), for: indexPath) as! KMHistoryFileListItem item.fileURL = HistoryFilesManager.manager.files[indexPath.item] item.reloadData() item.delegate = self return item } return NSCollectionViewItem() } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { if HistoryFilesManager.manager.showMode == .Thumbnail { return CGSize(width: 172, height: 248) } else if HistoryFilesManager.manager.showMode == .List { print(collectionView.frame.size.width, self.frame.size.width) return CGSize(width: collectionView.frame.size.width-80, height: 80) } return CGSize(width: 0.01, height: 0.01) } public func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView { if kind == NSCollectionView.elementKindSectionHeader { if indexPath.section == 0 { //quick tools quickToolsView = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: NSUserInterfaceItemIdentifier("kmHomeQuickToolsView"), for: indexPath) as! KMHomeQuickToolsView quickToolsView.delegate = self quickToolsView.reloadData() return quickToolsView } else if indexPath.section == 1 { //Recently filesHeaderView = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: NSUserInterfaceItemIdentifier("kmHomeFilesHeaderView"), for: indexPath) as! KMHomeFilesHeaderView filesHeaderView.delegate = self return filesHeaderView } } else if kind == NSCollectionView.elementKindSectionFooter { let view = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: NSUserInterfaceItemIdentifier("KMHomeFilesEmptyHeaderView"), for: indexPath) as! KMHomeFilesEmptyHeaderView if self.filesEmptyView.superview != nil { self.filesEmptyView.removeFromSuperview() } if indexPath.section == 1 && HistoryFilesManager.manager.files.count == 0 { filesEmptyView.frame = CGRectMake((CGRectGetWidth(collectionView.frame)-520)/2, (344-184)/2, 520, 184) filesEmptyView.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin] filesEmptyView.emptyView.setTarget(self, action: #selector(emptyViewAddFileClicked(_:))) view.addSubview(filesEmptyView) } return view } return NSView() } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 8 } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 0.01 } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize { if section == 0 { if KMNHomeQuickToolManager.defaultManager.collapseTools { if KMNHomeQuickToolManager.defaultManager.quickToolsItemMutableArray.count <= 4 { return NSSize(width: collectionView.frame.size.width, height: 172+32+2-60-14) //折叠 } return NSSize(width: collectionView.frame.size.width, height: 172+32+2) //折叠 } else { if KMNHomeQuickToolManager.defaultManager.quickToolsItemMutableArray.count <= 4 { return NSSize(width: collectionView.frame.size.width, height: 228+32-88-14) } return NSSize(width: collectionView.frame.size.width, height: 228+32) } } else if section == 1 { return NSSize(width: collectionView.frame.size.width, height: 28) } return NSSize(width: 0, height: 0.01) } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForFooterInSection section: Int) -> NSSize { if section == 0 { return NSSize(width: 0, height: 40-16) } else if section == 1 { if HistoryFilesManager.manager.files.count == 0 { return NSSize(width: collectionView.frame.size.width, height: 344) } } return NSSize(width: 0, height: 0.01) } public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets { return NSEdgeInsetsMake(12, 40, 0, 40) } public func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { self.collectionViewSelectedChanged() } public func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set) { self.collectionViewSelectedChanged() } } //MARK: - KMHomeFilesHeaderViewDelegate extension KMHomeRightView: KMHomeFilesHeaderViewDelegate { public func homeFilesShowModeDidUpdate(_ view: KMHomeFilesHeaderView) { self.reloadData() } public func homeFilesHeaderViewDeleteButtonClicked(_ view: KMHomeFilesHeaderView) { if UserDefaults.standard.object(forKey: "HomeFilesDeleteConfirmKey") != nil { var selects: [URL] = [] if HistoryFilesManager.manager.selectFiles.count > 0 { for selecturl in HistoryFilesManager.manager.selectFiles { selects.append(selecturl) } } HistoryFilesManager.manager.historyFileDeleteAction(selects) self.reloadData() return } let alert = NSAlert() alert.alertStyle = .informational if HistoryFilesManager.manager.selectFiles.count > 0 { alert.informativeText = KMLocalizedString("Remove Selected Files from your Recent Files?") } else { alert.informativeText = KMLocalizedString("Remove Files from your Recent Files?") } alert.messageText = KMLocalizedString("The file will disappear from the recent list.") alert.addButton(withTitle: KMLocalizedString("Delete")) alert.addButton(withTitle: KMLocalizedString("Cancel")) alert.showsSuppressionButton = true let response = alert.runModal() if response.rawValue == 1000 { var selects: [URL] = [] if HistoryFilesManager.manager.selectFiles.count > 0 { for selecturl in HistoryFilesManager.manager.selectFiles { selects.append(selecturl) } } else { for selecturl in HistoryFilesManager.manager.files { selects.append(selecturl) } } HistoryFilesManager.manager.historyFileDeleteAction(selects) self.reloadData() if alert.suppressionButton?.state == .on { UserDefaults.standard.set("YES", forKey: "HomeFilesDeleteConfirmKey") UserDefaults.standard.synchronize() } } } } //MARK: - KMHomeQuickToolsViewDelegate extension KMHomeRightView: KMHomeQuickToolsViewDelegate { public func homeQuickToolsViewDidCollapseStateChanged(_ view: KMHomeQuickToolsView) { DispatchQueue.main.async { self.reloadData() } } public func homeQuickToolsViewDidManageTools(_ view: KMHomeQuickToolsView) { DispatchQueue.main.async { self.delegate?.homeRightViewDidManageQuickTools?(self) } } public func homeQuickToolsViewDidItemClicked(_ view: KMHomeQuickToolsView, _ toolType: HomeQuickToolType) { DispatchQueue.main.async { self.delegate?.homeRightViewDidQuickToolsItemClicked?(self, toolType) } } } //MARK: - ComponentGroupDelegate extension KMHomeRightView: ComponentGroupDelegate { public func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) { var selects: [URL] = [] let indexs = self.menuActionIndexPaths if indexs.count >= 0 { for index in indexs { let url = HistoryFilesManager.manager.files[index.item] selects.append(url) } } if menuItemProperty?.identifier == "ShowInFinderKey" { NSWorkspace.shared.activateFileViewerSelecting(selects) } else if menuItemProperty?.identifier == "FileInformationKey" { } else if menuItemProperty?.identifier == "RemovefromRecentKey" { self.historyFileDeleteAction(selects) } else if menuItemProperty?.identifier == "ShareKey" { let picker = NSSharingServicePicker.init(items: selects) if let actionView = self.groupActionView { picker.show(relativeTo: actionView.bounds, of: actionView, preferredEdge: NSRectEdge.minY) } else { picker.show(relativeTo: NSRect(x: self.groupViewPoint.x, y: self.groupViewPoint.y, width: 0, height: 0), of: self.collectionView, preferredEdge: NSRectEdge.minY) } } else if menuItemProperty?.identifier == "PrintKey" { } } } //MARK: - KMHistoryFileListItemDelegate extension KMHomeRightView: KMHistoryFileListItemDelegate { public func historyFileListItemDidClickedMore(_ view: KMHistoryFileListItem) { if let indexPath = collectionView.indexPath(for: view) { if collectionView.selectionIndexPaths.contains(indexPath) { } else { collectionView.deselectAll(nil) collectionView.selectItems(at: [indexPath], scrollPosition: .nearestHorizontalEdge) } } groupActionView = view.moreButton var point = view.moreButton.superview?.convert(view.moreButton.frame.origin, to: self) ?? CGPointZero showFileMoreActionMenu(point: point) } public func historyFileListItemDidDoubleClicked(_ view: KMHistoryFileListItem) { if let url = view.fileURL { delegate?.homeRightViewDidChooseFileToOpen?(self, url) } } } //MARK: - KMHistoryFileThumbItemDelegate extension KMHomeRightView: KMHistoryFileThumbItemDelegate { func historyFileThumbItemDidDoubleClicked(_ view: KMHistoryFileThumbItem) { if let url = view.fileURL { delegate?.homeRightViewDidChooseFileToOpen?(self, url) } } }