// // KMLeftSideViewController.swift // PDF Reader Pro // // Created by lxy on 2022/10/10. // import Cocoa @objc protocol KMLeftSideViewControllerDelegate { @objc optional func controlStateChange(_ obj: KMLeftSideViewController,show:Bool) @objc optional func enterEditMode(_ obj: KMLeftSideViewController, _ pages: [Int]) @objc optional func searchAction(searchString:String, isCase:Bool) @objc optional func controller(_ controller: KMLeftSideViewController, dispayDidChange dispay: KMPDFDisplayType) @objc optional func controller(controller: KMLeftSideViewController, itemClick item: Any?, itemKey: KMItemKey, params: Any?) @objc optional func controller(controller: KMLeftSideViewController, bookMarkDidChange bookMarks: [KMBookMarkItem]) @objc optional func controller(controller: KMLeftSideViewController, rotateType: KMRotateType) @objc optional func controller(controller: KMLeftSideViewController, listViewSelectionDidChange object: Any?, info: [String : Any]?) } extension KMLeftSideViewController.Key { static let disableTableToolTipsKey = "SKDisableTableToolTips" } class KMLeftSideViewController: KMSideViewController { @IBOutlet var segmentedControl: KMSegmentedControl! @IBOutlet var thumbnailTableView: KMThumbnailTableView! @IBOutlet var tocOutlineView: KMTocOutlineView! @IBOutlet var noteOutlineView: KMNoteOutlineView! @IBOutlet var findTableView: KMBotaTableView! @IBOutlet var groupedFindTableView: KMBotaTableView! @IBOutlet var snapshotTableView: KMBotaTableView! @IBOutlet weak var leftListView: NSView! @IBOutlet var searchViewController: KMBotaSearchViewController! @IBOutlet weak var toolButtonBox: NSBox! @IBOutlet weak var toolButtonBoxLayoutConstraint: NSLayoutConstraint! @IBOutlet weak var snapshotNormalView: NSView! @IBOutlet weak var snapshotLabel: NSTextField! @IBOutlet weak var snapshotNormalMoreButton: NSButton! @IBOutlet weak var snapshotNormalSearchButton: NSButton! @IBOutlet weak var snapshotNormalZoomOutButton: NSButton! @IBOutlet weak var snapshotNormalZoomInButton: NSButton! @IBOutlet weak var snapshotSearchZoomOutButton: NSButton! @IBOutlet weak var snapshotSearchZoomInButton: NSButton! @IBOutlet weak var snapshotSearchField: KMLeftSideViewSearchField! @IBOutlet weak var snapshotDoneButton: NSButton! @IBOutlet weak var outlineView: NSView! @IBOutlet weak var outlineMoreButton: NSButton! @IBOutlet weak var outlineAddButton: NSButton! @IBOutlet weak var outlineSearchButton: NSButton! @IBOutlet weak var outlineLabel: NSTextField! @IBOutlet weak var outlineSearchField: KMLeftSideViewSearchField! @IBOutlet weak var outlineDoneButton: NSButton! @IBOutlet weak var noteView: NSView! @IBOutlet weak var noteMoreButton: NSButton! @IBOutlet weak var noteFilterButton: NSButton! @IBOutlet weak var noteSearchButton: NSButton! @IBOutlet weak var noteSearchField: KMLeftSideViewSearchField! @IBOutlet weak var noteTitleLabel: NSTextField! @IBOutlet weak var noteHeaderView: NSView! @IBOutlet weak var sortTypeBox: KMBox! @IBOutlet weak var sortTypeLabel: NSTextField! @IBOutlet weak var noteSortButton: NSButton! @IBOutlet weak var noteDoneButton: NSButton! @IBOutlet weak var thumbnailView: NSView! @IBOutlet weak var thumbnailZoomOutButton: NSButton! @IBOutlet weak var thumbnailZoomInButton: NSButton! @IBOutlet weak var thumbnailTitleLabel: NSTextField! @IBOutlet weak var emptySearchBox: NSBox! @IBOutlet weak var emptySearchLabel: NSTextField! lazy var leftSideEmptyVC: KMLeftSideEmptyFileViewController = { let vc = KMLeftSideEmptyFileViewController(nibName: "KMLeftSideEmptyFileViewController", bundle: nil) vc.view.wantsLayer = true return vc }() lazy var leftView: KMBotaLeftView = { let view = KMBotaLeftView() return view }() static let kMinSidePaneWidth: CGFloat = 270 static let kTinySize: Float = 32.0 static let kSmallSize: Float = 64.0 static let kLargeSize: Float = 128.0 static let kHugeSize: Float = 256.0 static let kFudgeSize: Float = 0.1 static let kOutlineRootBookmarkItem = "Bookmarks" var isFirst = false //打开阅读页初始化 var type : KMLeftMethodMode = KMLeftMethodMode() var isShowPanel : Bool = false weak var delegate: KMLeftSideViewControllerDelegate? let noteColumnId = NSUserInterfaceItemIdentifier(rawValue: "note") let authorColumnId = NSUserInterfaceItemIdentifier(rawValue: "author") struct Key {} var mwcFlags: MwcFlags = MwcFlags() let scalingIncrement: Float = 0.1 var filterButtonLayer: NSView? var moreButtonLayer: KMButtonLayer? // 缩略图 var thumbnails: [KMThumbnail] = [] var isDisplayPageSize = false var thumbnailCacheSize: CGFloat = 32 * 3 var updatingThumbnailSelection = false // 大纲 var isSearchOutlineMode = false var outlineIgnoreCaseFlag = false var tocType: KMFoldType = .none var renamePDFOutline: CPDFOutline? var renamePDFOutlineTextField: NSTextField? private var _dragPDFOutline: CPDFOutline? var updatingOutlineSelection = false // 注释 var noteTypeDict: [String : Any] = [:] private var _noteSortType: KMNoteSortType = .none var noteSortType: KMNoteSortType { get { return self._noteSortType } set { self._noteSortType = newValue NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self) } } var isAscendSort = false { didSet { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self) } } var isRenameNoteOutline = false // 所有注释 var allAnnotations: [CPDFAnnotation] = [] // 注释搜索模式标记 var noteSearchMode = false // 注释搜索数组 var noteSearchArray: [KMBotaAnnotationModel] = [] // 注释搜索 忽略大小写标识 var caseInsensitiveNoteSearch = false // 注释列表数据源 var annoListModel: KMAnnotationListModel? var editNoteTextField: NSTextField? weak var editNote: CPDFAnnotation? // 快照 var snapshotCacheSize: CGFloat = 32 * 3 var isSearchSnapshotMode = false var snapshots: [KMSnapshotModel] = [] var dirtySnapshots: [KMSnapshotWindowController] = [] var searchSnapshots: [KMSnapshotModel] = [] var dataUpdating = false // 搜索 private var _findState: KMFindState = .none var findState: KMFindState { get { return self._findState } set { if self._findState != newValue { self._findState = newValue self.displayFindState() if self._findState == .content { self.search(self.searchField) } else if self.findState == .note { self.searchNotes(nil) } else if self.findState == .snapshot { self.searchNotes(self.searchField) } } } } var searchResults : [KMSearchMode] = [] { didSet { self.updataLeftSideFindView() } } var groupSearchResults: [KMSearchMode] = [] var findPaneState: KMFindPaneState = .singular var searchResultIndex: Int = 0 var leftMargin: CGFloat = 0 private var _copysPages: [CPDFPage] = [] var copyPages: [CPDFPage] { get { return self._copysPages } } var dragIn = false private let KPDFThumbnailDoucumentURLForDraggedTypes = NSPasteboard.PasteboardType("KPDFThumbnailDoucumentURLForDraggedTypes") deinit { KMPrint("KMLeftSideViewController deinit.") NotificationCenter.default.removeObserver(self) DistributedNotificationCenter.default().removeObserver(self) } override var nibName: NSNib.Name? { return "LeftSideView" } convenience init(type : KMLeftMethodMode) { self.init() self.type = type } override func viewDidLoad() { super.viewDidLoad() self.reloadThumbnailSize() self.reloadSnapshotSize() } func showPanelView(show: Bool) { self.isShowPanel = show if show { self.leftView.segmentedControl.selectedSegment = 0 } else { self.leftView.segmentedControl.selectedSegment = UInt8.max self.type.methodType = .None self.delegate?.controlStateChange?(self, show: false) } } func refreshMethodType(methodType: BotaType) { let newType = KMLeftMethodMode() var show = true if self.type.methodType != methodType { newType.methodType = methodType } if self.type.methodType == methodType { show = false } else if methodType == .None { show = false } self.type = newType; self.delegate?.controlStateChange?(self,show:show) } override func loadView() { super.loadView() self.initSubView() self.initDefalutValue() self.searchViewController.loadView() self.searchViewController.contentView = self.findTableView.enclosingScrollView self.searchField = self.searchViewController.searchField self.searchViewController.segmentedControl.setSegmentCount(2, with: 25) self.searchViewController.segmentedControl.setImage(NSImage(named: KMImageNameUXIconBtnSidebarListNor)!, for: 0) self.searchViewController.segmentedControl.setImage(NSImage(named: KMImageNameUXIconBtnSidebarPageNor)!, for: 1) self.searchViewController.segmentedControl.setToolTip(KMLocalizedString("Separate search results", nil), for: 0) self.searchViewController.segmentedControl.setToolTip(KMLocalizedString("Group search results by page", nil), for: 1) self.searchViewController.segmentedControl.isBackgroundHighlighted = true self.searchViewController.segmentedControl.selectedSegment = 0 self.searchViewController.segmentedControl.block = { [unowned self] segIndex in if segIndex == 0 { self.findPaneState = .singular self.displayFindViewAnimating(false) } else { self.findPaneState = .grouped self.displayGroupedFindViewAnimating(false) } } self.leftView.segmentedControl.block = { [unowned self] segIndex in self.toolButtonBox.isHidden = false self.toolButtonBoxLayoutConstraint.constant = 40.0 if (segIndex == 0) { if self.type.methodType == .Thumbnail { self.leftView.segmentedControl.selectedSegment = UInt8.max self.refreshMethodType(methodType: .None) return } FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Thumbnail"]) self.refreshMethodType(methodType: .Thumbnail) DispatchQueue.main.async { self.toolButtonBox.contentView = self.thumbnailView self.displayThumbnailViewAnimating(false) } } else if (segIndex == 1) { if self.type.methodType == .Outline { self.leftView.segmentedControl.selectedSegment = UInt8.max self.refreshMethodType(methodType: .None) return } FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Outline"]) self.refreshMethodType(methodType: .Outline) DispatchQueue.main.async { self.toolButtonBox.contentView = self.outlineView self.displayTocViewAnimating(false) } } else if (segIndex == 2) { if self.type.methodType == .Annotation { self.leftView.segmentedControl.selectedSegment = UInt8.max self.refreshMethodType(methodType: .None) return } FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Annotation"]) self.refreshMethodType(methodType: .Annotation) DispatchQueue.main.async { self.toolButtonBox.contentView = self.noteView self.displayNoteViewAnimating(false) } } else if (segIndex == 3) { if self.type.methodType == .snapshot { self.leftView.segmentedControl.selectedSegment = UInt8.max self.refreshMethodType(methodType: .None) return } FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Snapshot"]) self.refreshMethodType(methodType: .snapshot) self.toolButtonBox.contentView = self.snapshotNormalView self.updateSnapshotFilterPredicate() self.displaySnapshotViewAnimating(false) self.updataLeftSideSnapView() } else if (segIndex == 4) { if self.type.methodType == .Search { self.leftView.segmentedControl.selectedSegment = UInt8.max self.refreshMethodType(methodType: .None) return } FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Search"]) self.refreshMethodType(methodType: .Search) DispatchQueue.main.async { self.toolButtonBox.isHidden = true self.toolButtonBoxLayoutConstraint.constant = 0 self.displayFindViewAnimating(false) } } } let menu = NSMenu() _ = menu.addItem(title: KMLocalizedString("Whole Words Only", "Menu item title"), action: #selector(toggleWholeWordSearch), target: self) _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveSearch), target: self) (self.searchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu (self.searchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search PDF", "placeholder") self.searchField.action = #selector(search) self.searchField.target = self // [thumbnailTableView setTypeSelectHelper:[SKTypeSelectHelper typeSelectHelperWithMatchOption:SKFullStringMatch]]; // //支持拖拽的文字类型 // [tocOutlineView setTypeSelectHelper:[SKTypeSelectHelper typeSelectHelperWithMatchOption:SKSubstringMatch]]; // NSSortDescriptor *countDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKGroupedSearchResultCountKey ascending:NO] autorelease]; // [groupedFindArrayController setSortDescriptors:[NSArray arrayWithObjects:countDescriptor, nil]]; if KMDataManager.ud_bool(forKey: Self.Key.disableTableToolTipsKey) == false { self.tocOutlineView.hasImageToolTips = true self.findTableView.hasImageToolTips = true self.groupedFindTableView.hasImageToolTips = true } self._updateViewColor() } func initSubView() { self.view.addSubview(self.leftView) self.leftView.frame = NSMakeRect(0, 0, 44, NSHeight(self.view.frame)) self.leftView.autoresizingMask = [.height] self.thumb_initSubViews() self.outline_initSubViews() self.note_initSubViews() self.snapshot_initSubViews() self.search_initSubViews() } func initDefalutValue() { self.view.wantsLayer = true self.view.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor self.leftView.wantsLayer = true self.leftView.layer?.backgroundColor = .white self.leftListView.wantsLayer = true self.leftListView.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor self.emptySearchLabel.stringValue = KMLocalizedString("No Results",nil) self.emptySearchLabel.textColor = KMAppearance.Layout.h0Color() self.emptySearchBox.isHidden = true self.thumb_initDefalutValue() self.outline_initDefalutValue() self.note_initDefalutValue() self.snapshot_initDefalutValue() self.search_initDefalutValue() } func displayThumbnailViewAnimating(_ animate: Bool) { if let enclosingScrollView = self.thumbnailTableView.enclosingScrollView { self.replaceSideView(enclosingScrollView, animate: animate) } var frame = self.thumbnailTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.origin.x = self.leftMargin frame.size.height = self.thumbnailTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.thumbnailTableView.enclosingScrollView?.frame = frame self.resetThumbnails() frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.origin.x = self.leftMargin frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.origin.x = self.leftMargin frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame self.updateThumbnailSelection() } func displayFindViewAnimating(_ animate: Bool) { self.replaceSideView(self.searchViewController.view, animate: animate) if (self.findState != .content) { self.findState = .content } else { self.displayFindState() } var frame = self.searchViewController.view.frame frame.origin.y = 0 frame.size.height = self.searchViewController.view.superview?.frame.size.height ?? .zero self.searchViewController.view.frame = frame frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame self.leftSideEmptyVC.emptySnapView.removeFromSuperview() self.updataLeftSideSnapView() } func displayFindState() { if (self.findState == .content) { self.displayFind() } else if (self.findState == .note) { self.displayNoteFind() } else if (self.findState == .snapshot) { self.displaySnapshotFind() } } func updataLeftSideFindView() { if (self.findState != .content) { return } if (self.searchResults.count > 0) { self.searchViewController.emptyBox.isHidden = true self.searchViewController.searchResultsView.isHidden = false self.searchViewController.searchResultsLabel.stringValue = String(format: KMLocalizedString("%ld Results", "Message in search table header"), self.searchResults.count) } else { self.searchViewController.emptyBox.isHidden = false self.searchViewController.searchResultsView.isHidden = true } } func displayGroupedFindViewAnimating(_ animate: Bool) { self.replaceSideView(self.searchViewController.view , animate: animate) if (self.findState != .content) { self.findState = .content } else { self.displayFindState() } var frame = self.searchViewController.view.frame frame.origin.y = 0 frame.size.height = self.searchViewController.view.superview?.frame.size.height ?? 0 self.searchViewController.view.frame = frame frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame self.updataLeftSideSnapView() } func displayNoteViewAnimating(_ animate: Bool) { self.searchViewController.contentView = nil if let data = self.noteOutlineView.enclosingScrollView { self.replaceSideView(data, animate: animate) } if (self.findState != .note) { self.findState = .note } else { self.displayFindState() } var frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame let view = self.noteOutlineView.enclosingScrollView let viewFrame = view?.frame ?? .zero let emptyVcSize = self.leftSideEmptyVC.emptyAnnotationView.frame.size self.leftSideEmptyVC.emptyAnnotationView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height) self.reloadAnnotation() } func displaySnapshotViewAnimating(_ animate: Bool) { self.searchViewController.contentView = nil if let data = self.snapshotTableView.enclosingScrollView { self.replaceSideView(data, animate: animate) } if (self.findState != .snapshot) { self.findState = .snapshot } else { self.displayFindState() } var frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.tocOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.tocOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.tocOutlineView.enclosingScrollView?.frame = frame Task { @MainActor in self.snapshotTableView.reloadData() } } func displayFind() { self.searchField = self.searchViewController.searchField let menu = NSMenu() _ = menu.addItem(title: KMLocalizedString("Whole Words Only", "Menu item title"), action: #selector(toggleWholeWordSearch), target: self) _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveSearch), target: self) (self.searchViewController.searchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu (self.searchViewController.searchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search PDF", "placeholder") self.searchViewController.searchField.action = #selector(search) self.searchViewController.searchField.target = self if (self.findPaneState == .singular) { self.searchViewController.contentView = self.findTableView.enclosingScrollView self.findTableView.wantsLayer = true self.findTableView.layer?.backgroundColor = NSColor.red.cgColor DispatchQueue.main.async { self.findTableView.reloadData() } } else if (self.findPaneState == .grouped) { self.searchViewController.contentView = self.groupedFindTableView.enclosingScrollView var array = KMSearchMode.sortSearchResult(results: self.searchResults) array.sort(){$0.datas.count > $1.datas.count} self.groupSearchResults = array self.groupedFindTableView.reloadData() } } func displayTocViewAnimating(_ animate: Bool) { if Thread.current.isMainThread { self.tocOutlineView.reloadData() } else { Task { @MainActor in self.tocOutlineView.reloadData() } } if let data = self.tocOutlineView.enclosingScrollView { self.replaceSideView(data, animate: animate) } var frame = self.tocOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.tocOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.tocOutlineView.enclosingScrollView?.frame = frame frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.noteOutlineView.enclosingScrollView?.frame = frame frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0 self.snapshotTableView.enclosingScrollView?.frame = frame let view = self.tocOutlineView.enclosingScrollView let viewFrame = view?.frame ?? .zero let emptyVcSize = self.leftSideEmptyVC.emptyOutlineView.frame.size self.leftSideEmptyVC.emptyOutlineView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height); DispatchQueue.main.async { self.tocOutlineView.reloadData() self.updateOutlineSelection() } } func displayNoteFind() { self.searchField = self.noteSearchField let menu = NSMenu() _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveNoteSearch), target: self) (self.noteSearchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu (self.noteSearchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search Notes", "placeholder") self.noteSearchField.action = #selector(searchNotes) self.noteSearchField.target = self } func displaySnapshotFind() { self.searchField = self.snapshotSearchField; let menu = NSMenu() _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveNoteSearch), target: self) (self.snapshotSearchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu (self.snapshotSearchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search Snapshots", "placeholder") self.snapshotSearchField.action = #selector(searchNotes) self.snapshotSearchField.target = self self.snapshotSearchField.delegate = self } func resetThumbnails(ks: Bool = true) { self.thumbnails.removeAll() let pageLabels = self.pdfDocument()?.pageLabels() ?? [] if (pageLabels.isEmpty == false) { for (i, label) in pageLabels.enumerated() { let thumbnail = KMThumbnail(image: nil, label: label, pageIndex: i) thumbnail.dirty = true self.thumbnails.append(thumbnail) } } DispatchQueue.main.async { let ris = self.thumbnailTableView.selectedRowIndexes self.thumbnailTableView.reloadData() if ks { self.thumbnailTableView.km_safe_selectRowIndexes(ris, byExtendingSelection: false) } } } @IBAction func leftSideViewDoneButtonAction(_ sender: AnyObject?) { let button = sender as? NSButton let tag = button?.tag ?? 0 if (tag == 310) { self.outlineSearchField.isHidden = true self.outlineDoneButton.isHidden = true self.outlineLabel.isHidden = false self.outlineSearchButton.isHidden = false self.outlineMoreButton.isHidden = false self.outlineAddButton.isHidden = false } else if (tag == 311) { self.noteSearchField.isHidden = true self.noteTitleLabel.isHidden = false self.noteSearchButton.isHidden = false self.noteDoneButton.isHidden = true self.noteFilterButton.isHidden = false self.noteMoreButton.isHidden = false } else if (tag == 312) { self.snapshotSearchField.isHidden = true // leftSideController.snapshotSearchZoomInButton.hidden = YES; self.snapshotNormalSearchButton.isHidden = false self.snapshotDoneButton.isHidden = true self.snapshotLabel.isHidden = false self.snapshotNormalZoomOutButton.isHidden = false self.snapshotNormalZoomInButton.isHidden = false } } @IBAction func thumbnailSizeScaling(_ sender: NSButton) { let tag = sender.tag if (tag == 0 || tag == 1) { var scaling = KMDataManager.ud_float(forKey: Self.Key.thumbSizeScaling) if (scaling <= 0) { scaling = 1 } if (tag == 0) { // Zoom In scaling += self.scalingIncrement } else if (tag == 1) { // Zoom Out scaling -= self.scalingIncrement if scaling <= 0.4 { return } } KMDataManager.ud_set(scaling, forKey: Self.Key.thumbSizeScaling) let selectRow = self.thumbnailTableView.selectedRow self.thumbnailTableView.reloadData() self.thumbnailTableView.selectRowIndexes(IndexSet(integer: selectRow), byExtendingSelection: false) } else if (tag == 2 || tag == 3) { var scaling = KMDataManager.ud_float(forKey: Self.Key.snapshotSizeScaling) if (scaling <= 0) { scaling = 1 } if (tag == 2) { // Zoom In scaling += self.scalingIncrement } else if (tag == 3) { // Zoom Out scaling -= self.scalingIncrement } KMDataManager.ud_set(scaling, forKey: Self.Key.snapshotSizeScaling) let selectRow = self.snapshotTableView.selectedRow self.snapshotTableView.reloadData() self.snapshotTableView.selectRowIndexes(IndexSet(integer: selectRow), byExtendingSelection: false) } } func tableView(_ tv: NSTableView, cutRowsWithIndexes rowIndexes: IndexSet) { if tv.isEqual(to: self.thumbnailTableView) { self._copysPages.removeAll() for idx in rowIndexes { if (idx != NSNotFound) { if let page = self.pdfDocument()?.page(at: UInt(idx))?.copy() as? CPDFPage { self._copysPages.append(page) } } } self.tableView(tv, deleteRowsWithIndexes: rowIndexes) } } } // MARK: - KMBotaTableViewDelegate extension KMLeftSideViewController: KMBotaTableViewDelegate { func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool { if aTableView.isEqual(to: self.thumbnailTableView) || aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) { return rowIndexes.count > 0 } return false } func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool { if aTableView.isEqual(to: self.thumbnailTableView) { if self._copysPages.count > 0 { return true } } return false } func tableView(_ aTableView: NSTableView, canDeleteRowsWithIndexes rowIndexes: IndexSet) -> Bool { if aTableView.isEqual(to: self.snapshotTableView) { return rowIndexes.count > 0 } else if aTableView.isEqual(to: self.thumbnailTableView) { if self.pageCount() <= 1 { return false } return true } return false } func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard?) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } if aTableView.isEqual(to: self.thumbnailTableView) { if self._copysPages.count > 0 { let index = self.currentPageIndex() if (index == NSNotFound) { return } let idx = index + 1 for page in self._copysPages.reversed() { self.pdfDocument()?.insertPageObject(page, at: UInt(idx)) self.layoutDocumentView() self.resetThumbnails() let pageIndex = min(idx, self.pageCount()-1) self.listView?.go(toPageIndex: Int(pageIndex), animated: true) (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).tableView(self.thumbnailTableView, deleteRowsWithIndexes: IndexSet(integer: IndexSet.Element(index))) } } } } func tableViewMoveLeft(_ aTableView: NSTableView) { if aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) { self.updateFindResultHighlightsForDirection(.selectingPrevious) } } func tableViewMoveRight(_ aTableView: NSTableView) { if aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) { self.updateFindResultHighlightsForDirection(.selectingNext) } } func tableViewMoveUp(_ aTableView: NSTableView) { if aTableView.isEqual(to: self.findTableView) { self.findTableView.km_safe_selectRowIndexes(.init(integer: self.findTableView.selectedRow-1), byExtendingSelection: false) self.findTableView.scrollRowToVisible(self.findTableView.selectedRow) } } func tableViewMoveDown(_ aTableView: NSTableView) { if aTableView.isEqual(to: self.findTableView) { self.findTableView.km_safe_selectRowIndexes(.init(integer: self.findTableView.selectedRow+1), byExtendingSelection: false) self.findTableView.scrollRowToVisible(self.findTableView.selectedRow) } } func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? { if aTableView.isEqual(to: self.findTableView) { if rowIndex >= self.searchResults.count { return nil } let model = self.searchResults[rowIndex] let selection = model.selection let x = selection.bounds.origin.x + NSWidth(selection.bounds) * 0.5 let y = selection.bounds.origin.y + NSHeight(selection.bounds) * 0.5 let point = NSPoint(x: x, y: y) return CPDFDestination(document: self.pdfDocument(), pageIndex: Int(model.selectionPageIndex), at: point, zoom: self.scaleFactor().cgFloat) } else if aTableView.isEqual(to: self.groupedFindTableView) { if rowIndex >= self.groupSearchResults.count { return nil } // let model = self.groupSearchResults[rowIndex] // let selection = model.selection // let point = NSPoint(x: NSWidth(selection.bounds) * 0.5, y: NSHeight(selection.bounds) * 0.5) // return CPDFDestination(document: self.listView.document, pageIndex: Int(selection.page.pageIndex()), at: point, zoom: self.listView.scaleFactor) } return nil } } // MARK: - 扩展 extension KMLeftSideViewController { public func selectType(_ type: BotaType) { // 更新 type var show = true if (self.type.methodType == .None) { show = true } else { if (self.type.methodType == type) { show = false } else { show = true } } self.type = self.getMethodMode(show ? type : .None) // 将事件传递出去 self.delegate?.controlStateChange?(self,show:show) } private func getMethodMode(_ type: BotaType) -> KMLeftMethodMode { let mode = KMLeftMethodMode() mode.methodType = type switch type { case .None: mode.methodName = "" break case .Thumbnail: mode.methodName = thumbnailMethodKey break case .Outline: mode.methodName = outlineMethodKey break case .BookMark: mode.methodName = bookMarkMethodKey break case .Annotation: mode.methodName = anntationMethodKey break case .Search: mode.methodName = searchMethodKey break case .From: mode.methodName = formMethodKey break case .Signature: mode.methodName = signatureMethodKey break case .snapshot: mode.methodName = snapshotMethodKey } return mode } } //MARK: Cache extension KMLeftSideViewController { func clearNotification() {} } // MARK: - Private Methods extension KMLeftSideViewController { func updateViewColor() { self._updateViewColor() } private func _updateViewColor() { if(KMAppearance.isDarkMode()){ self.leftListView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.snapshotNormalView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.thumbnailView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.noteView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.outlineView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.view.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor self.thumbnailTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1) self.groupedFindTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1) self.tocOutlineView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1) self.findTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1) self.snapshotSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.outlineSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.noteSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.segmentedControl.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor self.snapshotSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1) self.outlineSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1) self.noteSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1) } else { self.leftListView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.snapshotNormalView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.thumbnailView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.noteView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.outlineView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.view.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor self.thumbnailTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1) self.groupedFindTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1) self.tocOutlineView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1) self.findTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1) self.snapshotSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor self.outlineSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor self.noteSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor self.segmentedControl.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor self.snapshotSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1) self.outlineSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1) self.noteSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1) } } func hasContainString(_ searchString: String, rootOutline outline: CPDFOutline) -> Bool { var label = outline.label ?? "" var searchLabel = searchString if self.outlineIgnoreCaseFlag { label = label.lowercased() searchLabel = searchLabel.lowercased() } if label.contains(searchLabel) { // if ([outline.label rangeOfString:searchString options:self.outlineIgnoreCaseFlag?NSCaseInsensitiveSearch:0].location != NSNotFound){ return true } else { var subHas = false for i in 0 ..< outline.numberOfChildren { if let subOutline = outline.child(at: i) { subHas = self.hasContainString(searchString, rootOutline: subOutline) } else { continue } if (subHas) { break } } return subHas } } } // MARK: - NSTableViewDelegate, NSTableViewDataSource extension KMLeftSideViewController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { if tableView.isEqual(to: self.thumbnailTableView) { return self.thumbnails.count } else if tableView.isEqual(to: self.findTableView) { return self.searchResults.count } else if tableView.isEqual(to: self.groupedFindTableView) { return self.groupSearchResults.count } else if tableView.isEqual(to: self.snapshotTableView) { if self.isSearchSnapshotMode { return self.searchSnapshots.count } return self.snapshots.count } return 0 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if tableView.isEqual(to: self.thumbnailTableView) { let cell = tableView.makeView(withIdentifier: KMThumbnailTableviewCell.km_identifier, owner: self) as! KMThumbnailTableviewCell let thumbnail = self.thumbnails[row] cell.pageNumLabel.stringValue = thumbnail.label cell.pageView.isThumb = true cell.pageView.page = self.pdfDocument()?.page(at: UInt(row)) if (self.isDisplayPageSize) { cell.sizeLabel.isHidden = false //获取Page的真实尺寸 let page = self.pdfDocument()?.page(at: UInt(row)) let rect = page?.bounds(for: .cropBox) ?? .zero let w = KMPageSizeTool.conversion(withUnit: "mm", value: (CGRectGetWidth(rect)/595 * 210)) let h = KMPageSizeTool.conversion(withUnit: "mm", value: (CGRectGetHeight(rect)/842 * 297)) if let data = page?.rotation, data == 90 || data == 270 { cell.sizeLabel.stringValue = String(format: "%.f × %.f %@", h.stringToCGFloat(), w.stringToCGFloat(), KMLocalizedString("mm", nil)) } else { cell.sizeLabel.stringValue = String(format: "%.f × %.f %@", w.stringToCGFloat(), h.stringToCGFloat(), KMLocalizedString("mm", nil)) } } else { cell.sizeLabel.stringValue = "0 mm" cell.sizeLabel.isHidden = true } cell.sizeTopConstant.constant = cell.sizeLabel.isHidden ? -cell.sizeLabel.frame.size.height : 0 if (self.thumbnailTableView.selectedRowIndexes.contains(row)) { cell.isSelectCell = true } else { cell.isSelectCell = false } return cell } else if (tableView.isEqual(to: self.findTableView)) { let cell = tableView.makeView(withIdentifier: KMFindTableviewCell.km_identifier, owner: self) as! KMFindTableviewCell let selection = searchResults[row] if let data = tableColumn?.identifier.rawValue, data == kResultsColumnId.rawValue { cell.resultLabel.attributedStringValue = selection.attributedString cell.resultLabel.textColor = KMAppearance.Layout.h0Color() } else if let data = tableColumn?.identifier.rawValue, data == kPageColumnId.rawValue { cell.resultLabel.stringValue = "\(Int(selection.selectionPageIndex) + 1)" cell.resultLabel.textColor = KMAppearance.Layout.h2Color() } return cell } else if tableView.isEqual(to: self.snapshotTableView) { let cell = tableView.makeView(withIdentifier: KMSnapshotTableViewCell.km_identifier, owner: self) as! KMSnapshotTableViewCell var snapshot: KMSnapshotModel? if self.isSearchSnapshotMode { snapshot = self.searchSnapshots[row] } else { snapshot = self.snapshots[row] } cell.snapshotImage.image = snapshot?.windowC?.thumbnail cell.snapshotLabel.stringValue = snapshot?.windowC?.pageLabel ?? "" if let data = snapshot?.windowC?.hasWindow, data { cell.snapshotImageView.isHidden = false } else { cell.snapshotImageView.isHidden = true } cell.isSelectCell = snapshot?.isSelected ?? false if (row == tableView.selectedRow) { cell.snapshotImageView.image = NSImage(named: KMImageNameUXIconSidebarSnapshotWindowSel) } else { cell.snapshotImageView.image = NSImage(named: KMImageNameUXIconSidebarSnapshotWindowNor) } return cell } return nil } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { if tableView.isEqual(to: self.thumbnailTableView) { let scaling = KMDataManager.ud_float(forKey: Self.Key.thumbSizeScaling) let thumbnailSize = NSMakeSize(self.thumbnailCacheSize, self.thumbnailCacheSize) let newScaling: CGFloat = scaling.cgFloat + 0.1 let newThumbnailHeight = thumbnailSize.width * newScaling if (newThumbnailHeight > Self.kMinSidePaneWidth) { self.thumbnailZoomOutButton.isEnabled = false } else { self.thumbnailZoomOutButton.isEnabled = true } if ((scaling - 0.1) < 0.4) { self.thumbnailZoomInButton.isEnabled = false } else { self.thumbnailZoomInButton.isEnabled = true } var labelHeight = 0.0 if (self.isDisplayPageSize) { labelHeight = 56.0 } else { labelHeight = 41.5 } let cellHeight = thumbnailSize.height + labelHeight var thumbSize: NSSize = .zero if (scaling > 0) { thumbSize = NSMakeSize(thumbnailSize.width * scaling.cgFloat, cellHeight * scaling.cgFloat) } else { thumbSize = NSMakeSize(thumbnailSize.width, cellHeight) } return thumbSize.height } else if tableView.isEqual(to: self.snapshotTableView) { let scaling = KMDataManager.ud_float(forKey: Self.Key.snapshotSizeScaling) let snapshotSize = (self.snapshots.safe_element(for: row) as? KMSnapshotModel)?.windowC?.thumbnail?.size ?? CGSizeMake(120, 63) var newScaling = scaling + 0.1 let newSnapshotHeight = snapshotSize.width * newScaling.cgFloat if (newSnapshotHeight > Self.kMinSidePaneWidth) { self.snapshotNormalZoomInButton.isEnabled = false } else { self.snapshotNormalZoomInButton.isEnabled = true } if ((scaling - 0.1) < 0.3 || (newSnapshotHeight < 150.0)) { self.snapshotNormalZoomOutButton.isEnabled = false } else { self.snapshotNormalZoomOutButton.isEnabled = true } let cellHeight = snapshotSize.height + 24.0 var thumbSize: NSSize = .zero if (scaling > 0) { thumbSize = NSMakeSize(snapshotSize.width * scaling.cgFloat, cellHeight * scaling.cgFloat) } else { thumbSize = NSMakeSize(snapshotSize.width, cellHeight) } return thumbSize.height } else if (tableView.isEqual(to: self.findTableView)) { return 40.0 } else if tableView.isEqual(to: self.groupedFindTableView) { return 16 } return tableView.rowHeight } func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { if (tableView.isEqual(to: self.findTableView)) { let rowView = KMBotaTableRowView() return rowView } else if tableView.isEqual(to: self.groupedFindTableView) { let rowView = KMGroupFindTableRowView() rowView.totalNumber = self.groupSearchResults.first?.datas.count ?? 0 let model = self.groupSearchResults[row] rowView.number = model.datas.count rowView.pageIndex = Int(model.selectionPageIndex) + 1 return rowView } return nil } func tableViewSelectionIsChanging(_ notification: Notification) { } func tableViewSelectionDidChange(_ notification: Notification) { self.delegate?.controller?(controller: self, listViewSelectionDidChange: notification.object, info: nil) if self.findTableView.isEqual(to: notification.object) { // [self updateFindResultHighlightsForDirection:NSDirectSelection]; let row = self.findTableView.selectedRow if row >= 0 { let model = self.searchResults[row] self.listView?.go(to: model.selection, animated: true) self.listView?.setHighlightedSelection(model.selection, animated: true) self.listView?.setNeedsDisplayAnnotationViewForVisiblePages() } } else if self.groupedFindTableView.isEqual(to: notification.object) { // [self updateFindResultHighlightsForDirection:NSDirectSelection]; } else if self.thumbnailTableView.isEqual(to: notification.object) { self.thumbnailTableView.ks_reloadData() } else if self.snapshotTableView.isEqual(to: notification.object) { let row = self.snapshotTableView.selectedRow // 更新选中数据 for (i, model) in self.snapshots.enumerated() { model.isSelected = i == row } if (row != -1) { let controller = (self.snapshots.safe_element(for: row) as? KMSnapshotModel)?.windowC if let data = controller?.window?.isVisible, data { controller?.window?.orderFront(self) } } self.snapshotTableView.ks_reloadData() } } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { if dropOperation == .on || tableView.isEqual(to: self.thumbnailTableView) == false { return NSDragOperation(rawValue: 0) } let pboard = info.draggingPasteboard if (pboard.availableType(from: [.localDraggedTypes]) != nil) { return .move } else if (pboard.availableType(from: [.fileURL]) != nil) && tableView.isEqual(to: self.thumbnailTableView) { guard let pbItems = pboard.pasteboardItems else { return NSDragOperation(rawValue: 0) } var hasValidFile = false for item in pbItems { guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else { continue } let type = _url.pathExtension.lowercased() if type == "pdf" || KMImageToPDFMethod.supportedImageTypes().contains(type) { hasValidFile = true } } if (!hasValidFile) { return NSDragOperation(rawValue: 0) } else { return .move } } return NSDragOperation(rawValue: 0) } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { var result = false if tableView.isEqual(to: self.thumbnailTableView) == false { return result } let pasteboard = info.draggingPasteboard if (pasteboard.availableType(from: [.localDraggedTypes]) != nil) { result = true guard let rowData = pasteboard.data(forType: .localDraggedTypes) else { return false } let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: rowData) as? IndexSet ?? IndexSet() guard let urlData = pasteboard.data(forType: KPDFThumbnailDoucumentURLForDraggedTypes) else { return false } let url = NSKeyedUnarchiver.unarchiveObject(with: urlData) as? String if url != nil && url != self.pdfDocument()?.documentURL.absoluteString { if (pasteboard.availableType(from: [.fileURL]) != nil) { let fileNames = pasteboard.propertyList(forType: .fileURL) as? [String] ?? [] if (fileNames.count == 1) { let path = fileNames.first ?? "" let pathExtension = path.pathExtension.lowercased() if pathExtension == "pdf" { var index = row var insertIndexSet = IndexSet() let pdf = CPDFDocument(url: URL(fileURLWithPath: path)) if let data = pdf?.isEncrypted, data { KMBaseWindowController.checkPassword(url: URL(fileURLWithPath: path), type: .owner) { success, resultPassword in if success && resultPassword.isEmpty == false { for i in 0 ..< (pdf?.pageCount ?? 0) { if let page = pdf?.page(at: i).copy() as? CPDFPage { self.pdfDocument()?.insertPageObject(page, at: UInt(index)) insertIndexSet.insert(index) index += 1 } } self.insertPages(insertIndexSet, pageAt: index-1) } } } else { for i in 0 ..< (pdf?.pageCount ?? 0) { let page = pdf?.page(at: i) self.pdfDocument()?.insertPageObject(page, at: UInt(index)) insertIndexSet.insert(index) index += 1 } self.insertPages(insertIndexSet, pageAt: index-1) } } return true } } } var pageIndex = 0 var pages: [CPDFPage] = [] for idx in rowIndexes { if let page = self.pdfDocument()?.page(at: UInt(idx))?.copy() as? CPDFPage { pages.append(page) } } var toPage: CPDFPage? if row < self.pageCount() { toPage = self.pdfDocument()?.page(at: UInt(row)) } var toPageIndex = 0 if (toPage != nil) { toPageIndex = Int(self.pdfDocument()?.index(for: toPage) ?? 0) } else { toPageIndex = self.pageCount() } for i in 0 ..< pages.count { let page = pages[i] self.pdfDocument()?.insertPageObject(page, at: UInt(toPageIndex+i)) pageIndex += 1 self.listView?.go(toPageIndex: toPageIndex+i-1, animated: false) } self.layoutDocumentView() var deletepages: [CPDFPage] = [] for idx in rowIndexes { if idx < toPageIndex { if((idx+pageIndex) < self.pageCount()) { if let page = self.pdfDocument()?.page(at: UInt(idx)) { deletepages.append(page) } self.pdfDocument()?.removePage(at: IndexSet(integer: idx)) } } else { if((idx+pageIndex) < self.pageCount()) { if let page = self.pdfDocument()?.page(at: UInt(idx+pageIndex)) { deletepages.append(page) } self.pdfDocument()?.removePage(at: IndexSet(integer: idx+pageIndex)) } } } self.layoutDocumentView() self.resetThumbnails() } else if pasteboard.availableType(from: [.fileURL]) != nil && tableView.isEqual(to: self.thumbnailTableView) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return false } let fileNames = pasteboard.propertyList(forType: .fileURL) var insertIndexSet = IndexSet() var index = row // if (fileNames.count == 1) { if fileNames != nil { // var path = fileNames.first as? String ?? "" let path = fileNames as? String ?? "" let url = URL(string: path) let pathExtension = url?.pathExtension.lowercased() ?? "" if pathExtension == "pdf" { let pdf = CPDFDocument(url: url!) if let data = pdf?.isEncrypted, data { KMBaseWindowController.checkPassword(url: url!, type: .owner) { success, resultPassword in if success && resultPassword.isEmpty == false { pdf?.unlock(withPassword: resultPassword) for i in 0 ..< (pdf?.pageCount ?? 0) { // let page = pdf?.page(at: i).copy() as? CPDFPage if let page = pdf?.page(at: i) { self.pdfDocument()?.insertPageObject(page, at: UInt(index)) insertIndexSet.insert(index) index += 1 } } self.insertPages(insertIndexSet, pageAt: index-1) } } } else { for i in 0 ..< (pdf?.pageCount ?? 0) { // let page = pdf?.page(at: i).copy() as? CPDFPage if let page = pdf?.page(at: i) { self.pdfDocument()?.insertPageObject(page, at: UInt(index)) insertIndexSet.insert(index) index += 1 } } self.insertPages(insertIndexSet, pageAt: index-1) } } else if KMImageToPDFMethod.supportedImageTypes().contains(pathExtension) { if let image = NSImage(contentsOfFile: url?.path ?? "") { // PDFPage * page = [[[PDFPage alloc] initWithImage:image] autorelease]; // [pdfView.document insertPage:page atIndex:index]; _ = self.pdfDocument()?.km_insert(image: image, at: UInt(index)) insertIndexSet.insert(index) self.insertPages(insertIndexSet, pageAt: index-1) } } result = true } else { result = false } } return result == false } func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { if tableView.isEqual(to: self.thumbnailTableView) { let idx = rowIndexes.first ?? NSNotFound if (idx != NSNotFound && self.isLocked() == false) { let page = self.pdfDocument()?.page(at: UInt(idx)) var fileExt: String? let tiffData = page?.PDFListViewTIFFData(for: page?.bounds(for: self.displayBox()) ?? .zero) if self.allowsPrinting() { // NSData *pdfData = [page dataRepresentation]; fileExt = "pdf" // filenames pboard.declareTypes([.pdf, .tiff, .fileURL, .filePromise, .localDraggedTypes, KPDFThumbnailDoucumentURLForDraggedTypes], owner: self) // let newDoc = CPDFDocument() // newDoc?.insertPageObject(page, at: 0) // let data = newDoc?.dataRepresentation() // [pboard setData:pdfData forType:NSPasteboardTypePDF]; pboard.setData(tiffData, forType: .pdf) // NSData *zNSIndexSetData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: rowIndexes) pboard.setData(zNSIndexSetData, forType: .localDraggedTypes) let documentURL = NSKeyedArchiver.archivedData(withRootObject: self.pdfDocument()?.documentURL?.absoluteString) // NSString *docmentName = [[[pdfView.document.documentURL path] lastPathComponent] stringByDeletingPathExtension]; var docmentName = self.pdfDocument()?.documentURL.deletingPathExtension().lastPathComponent ?? "" // __block NSMutableString *pagesName = nil; var pagesName = "" if (rowIndexes.count > 1) { pagesName = " pages" } else { pagesName = " page" } let tFileName = String(format: "%@ %@", pagesName, self.fileNameWithSelectedPages(rowIndexes)) let pdf = CPDFDocument() for idx in rowIndexes { // var copyPage = self.listView.document.page(at: UInt(idx)).copy() as? CPDFPage let copyPage = self.pdfDocument()?.page(at: UInt(idx)) pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0) } let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) var cachesDir = paths.first ?? "" docmentName = String(format: "%@%@", docmentName, tFileName) if (docmentName.count > 50) { docmentName = docmentName.substring(to: 50) } // cachesDir = [[cachesDir stringByAppendingPathComponent:docmentName] stringByAppendingPathExtension:@"pdf"]; cachesDir = "\(cachesDir)/\(docmentName).pdf" let success = pdf?.write(toFile: cachesDir) ?? false if (success) { // [pboard setPropertyList:@[cachesDir] forType:NSFilenamesPboardType]; pboard.setPropertyList([cachesDir], forType: .fileURL) } else{ // [pboard setPropertyList:@[@""] forType:NSFilenamesPboardType]; pboard.setPropertyList([], forType: .fileURL) } pboard.setData(documentURL, forType: KPDFThumbnailDoucumentURLForDraggedTypes) } else { fileExt = "tiff" pboard.declareTypes([.tiff, .filePromise], owner: self) } pboard.setData(tiffData, forType: .tiff) // kPasteboardTypeFileURLPromise pboard.setPropertyList([fileExt], forType: .filePromise) return true } } else if self.snapshotTableView.isEqual(to: tableView) { let idx = rowIndexes.first ?? NSNotFound if (idx != NSNotFound) { let snapshot = (self.snapshots.safe_element(for: idx) as? KMSnapshotModel)?.windowC if let data = snapshot?.thumbnailWithSize(0)?.tiffRepresentation { pboard.declareTypes([.tiff, .filePromise], owner: self) pboard.setData(data, forType: .tiff) pboard.setPropertyList(["tiff"], forType: .filePromise) return true } } } return false } func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) { self.dragIn = true } func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) { self.dragIn = false } @objc dynamic func tableView(_ aTableView: NSTableView, deleteRowsWithIndexes rowIndexes: IndexSet) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } if aTableView.isEqual(to: self.snapshotTableView) { var controllers: [KMSnapshotWindowController] = [] for i in rowIndexes { let model: KMSnapshotModel? = self.snapshots.safe_element(for: i) as? KMSnapshotModel if let winC = model?.windowC { controllers.append(winC) } } for c in controllers { c.close() } } else if aTableView.isEqual(to: self.thumbnailTableView) { guard let last = rowIndexes.last, last < self.pageCount() else { return } //删除page时移除选中状态的annotation self.listView?.activeAnnotations = [] for idx in rowIndexes { if let page = self.pdfDocument()?.page(at: UInt(idx)) { // for anno in page.annotations { // page.removeAnnotation(anno) // } (self.listView?.undoManager?.prepare(withInvocationTarget: self) as AnyObject).insertPage(page, pageAt: idx) // self.pdfDocument()?.removePage(at: IndexSet(integer: idx)) } } self.pdfDocument()?.removePage(at: rowIndexes) self.layoutDocumentView() self.resetThumbnails() let idx = rowIndexes.first ?? -1 let index = min(idx, self.pageCount()-1) self.listView?.go(toPageIndex: index, animated: false) } } func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } if aTableView.isEqual(to: self.thumbnailTableView) { self._copysPages.removeAll() if self.isLocked() == false { for idx in rowIndexes { if let page = self.pdfDocument()?.page(at: UInt(idx))?.copy() as? CPDFPage { self._copysPages.append(page) } } } } else if aTableView.isEqual(to: self.findTableView) { var string = "" for idx in rowIndexes { let match = self.searchResults[idx].selection string.append("* ") // [string appendFormat:NSLocalizedString(@"Page %@", @""), [match firstPageLabel]]; string = string.appendingFormat(KMLocalizedString("Page %@", ""), "\(match.safeFirstPage()?.pageIndex() ?? 0)") // [string appendFormat:@"", [[match contextString] string]]; string = string.appendingFormat(": %@\n", match.string() ?? "") } let pboard = NSPasteboard.general pboard.clearContents() pboard.writeObjects([string as NSPasteboardWriting]) } else if aTableView.isEqual(to: self.groupedFindTableView) { var string = "" for idx in rowIndexes { let result = self.groupSearchResults[idx] let matches = result.datas string.append("* ") string = string.appendingFormat(KMLocalizedString("Page %@", ""), "\(result.selectionPageIndex)") string.append(": ") string = string.appendingFormat(KMLocalizedString("%ld Results", ""), matches.count) // [string appendFormat:@":\n\t%@\n", [[matches valueForKeyPath:@"contextString.string"] componentsJoinedByString:@"\n\t"]]; var tmpString = "" for model in matches { tmpString.append("\(model.selection.string() ?? "")") } string = string.appendingFormat(":\n\t%@\n", tmpString) } let pboard = NSPasteboard.general pboard.clearContents() pboard.writeObjects([string as NSPasteboardWriting]) } } func tableView(_ tableView: NSTableView, namesOfPromisedFilesDroppedAtDestination dropDestination: URL, forDraggedRowsWith indexSet: IndexSet) -> [String] { if self.thumbnailTableView.isEqual(to: tableView) { var fileURLArray: [String] = [] if indexSet.count > 1 { var docmentName = "" let tFileName = String(format: "%@", self.fileNameWithSelectedPages(indexSet)) let pdf = CPDFDocument() for idx in indexSet { if idx != NSNotFound && self.isLocked() == false { // let copyPage = self.listView.document.page(at: UInt(idx)).copy() as? CPDFPage let copyPage = self.pdfDocument()?.page(at: UInt(idx)) pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0) } } let fileURL = dropDestination.appendingPathComponent(tFileName).appendingPathExtension("pdf").uniqueFileURL() docmentName = fileURL.path let success = pdf?.write(toFile: docmentName) ?? false if(success) { fileURLArray.append(fileURL.lastPathComponent) } } else { if let page = self.pdfDocument()?.page(at: UInt(indexSet.first ?? 0)) { var fileURL = dropDestination.appendingPathComponent(self.draggedFileName(for: page)) if self.allowsPrinting() { fileURL = fileURL.appendingPathExtension("pdf").uniqueFileURL() let pdf = CPDFDocument() // let copyPage = page.copy() as? CPDFPage let copyPage = page pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0) let success = pdf?.write(toFile: fileURL.path) ?? false if success { fileURLArray.append(fileURL.lastPathComponent) } } else { fileURL = fileURL.appendingPathExtension("tiff").uniqueFileURL() let fileData = page.PDFListViewTIFFData(for: page.bounds(for: self.displayBox())) let success = (try?fileData?.write(to: fileURL)) != nil if success { fileURLArray.append(fileURL.lastPathComponent) } } } } return fileURLArray } else if self.snapshotTableView.isEqual(to: tableView) { let idx = indexSet.first ?? NSNotFound if (idx != NSNotFound) { if let snapshot = (self.snapshots.safe_element(for: idx) as? KMSnapshotModel)?.windowC { if let page = self.pdfDocument()?.page(at: snapshot.pageIndex()) { var fileURL = dropDestination.appendingPathComponent(self.draggedFileName(for: page)).appendingPathExtension("tiff") fileURL = fileURL.uniqueFileURL() if ((try?snapshot.thumbnailWithSize(0)?.tiffRepresentation?.write(to: fileURL)) != nil) { return [fileURL.lastPathComponent] } } } } } return [] } func tableViewColumnDidResize(_ notification: Notification) { guard let column = notification.userInfo?["NSTableColumn"] as? NSTableColumn else { return } if column.identifier.rawValue == IMAGE_COLUMNID { if self.thumbnailTableView.isEqual(to: notification.object) { self.thumbnailTableView.noteHeightOfRows(withIndexesChanged: NSIndexSet(indexesIn: NSMakeRange(0, self.thumbnailTableView.numberOfRows)) as IndexSet) } else if self.snapshotTableView.isEqual(to: notification.object) { self.snapshotTableView.noteHeightOfRows(withIndexesChanged: NSIndexSet(indexesIn: NSMakeRange(0, self.snapshotTableView.numberOfRows)) as IndexSet) } } } func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) { if tableView.isEqual(to: self.groupedFindTableView) { // [leftSideController.groupedFindArrayController setSortDescriptors:[tv sortDescriptors]]; } } /* #pragma mark NSTableView delegate protocol - (void)tableView:(NSTableView *)tv shareRowsWithIndexes:(NSIndexSet *)rowIndexes { if ([tv isEqual:leftSideController.thumbnailTableView]) { NSUInteger idx = [rowIndexes firstIndex]; if (idx != NSNotFound) { PDFPage *page = [[pdfView document] pageAtIndex:idx]; NSString *fileName = [[[pdfView document] documentURL] lastPathComponent]; NSString *folderPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *filePath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Untitled"]]; filePath = [filePath stringByAppendingPathExtension:[fileName pathExtension]]; [self fileWithPage:page atPath:filePath]; if (rint(NSAppKitVersionNumber) < NSAppKitVersionNumber10_8) { [KMMailHelper sendFileWithPaths:[NSArray arrayWithObject:filePath]]; } else { NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail]; [service performWithItems:[NSArray arrayWithObject:[NSURL fileURLWithPath:filePath]]]; } } } } - (void)fileWithPage:(PDFPage *)page atPath:(NSString *)filePath { NSData *data = [page dataRepresentation]; PDFDocument *document = [[PDFDocument alloc] initWithData:data]; [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; BOOL success = [document writeToURL:[NSURL fileURLWithPath:filePath]]; if (success) { NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; NSURL *url = [NSURL fileURLWithPath:filePath]; [workspace activateFileViewerSelectingURLs:[NSArray arrayWithObject:url]]; } [document release]; */ } // MARK: - NSOutlineViewDelegate, NSOutlineViewDataSource extension KMLeftSideViewController: NSOutlineViewDelegate, NSOutlineViewDataSource { func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if outlineView.isEqual(to: self.tocOutlineView) { if self.isLocked() { return 0 } if item == nil { // 第一层 let bks = self.bookmarks() ?? [] let bkNum = bks.isEmpty ? 0 : 1 guard let rootOL = self.outlineRoot() else { if bkNum > 0 { } else { self.showOutlineEmptyView() } return 0 + bkNum } // 搜索按钮 self.outlineSearchButton.isEnabled = rootOL.numberOfChildren > 0 if self.isSearchOutlineMode { // 是否为搜索模块 if self.hasContainString(self.outlineSearchField.stringValue, rootOutline: rootOL) == false { self.showSearchOutlineBlankState(true) return 0 } self.showSearchOutlineBlankState(false) let ols = self.fetchOutlines(for: rootOL, searchString: self.outlineSearchField.stringValue) return ols.count } if rootOL.numberOfChildren == 0 { // 没有数据 if bkNum > 0 { } else { self.showOutlineEmptyView() } return 0 + bkNum } // 有数据 self.hideOutlineEmptyView() return Int(rootOL.numberOfChildren) + bkNum } else { // 第二层 + if item is CPDFBookmark { // 书签 return 0 } if let data = item as? String, data == Self.kOutlineRootBookmarkItem { // 书签group if self.isSearchOutlineMode { return 0 } return (self.bookmarks()?.count) ?? 0 } guard let ol = item as? CPDFOutline, ol.numberOfChildren > 0 else { return 0 } if self.isSearchOutlineMode == false { return Int(ol.numberOfChildren) } // 搜索模式 let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue) return ols.count } } else if outlineView.isEqual(to: self.noteOutlineView) { if self.noteSearchMode { return self.noteSearchArray.count } let count = self.annoListModel?.datas.count ?? 0 let hasAnno = self.allAnnotations.count >= 1 self.noteSearchButton.isEnabled = hasAnno self.noteFilterButton.isEnabled = hasAnno self.noteOutlineView.usesAlternatingRowBackgroundColors = false if count < 1 { self.showNoteEmptyView() } else { self.hideNoteEmptyView() } return count } return 0 } func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if outlineView.isEqual(to: self.tocOutlineView) { if self.isLocked() { return "" } let bks = self.bookmarks() ?? [] let bkNum = bks.isEmpty ? 0 : 1 if item == nil { if self.isSearchOutlineMode { guard let rootOL = self.outlineRoot(), rootOL.numberOfChildren > 0 else { return "" } let ols = self.fetchOutlines(for: rootOL, searchString: self.outlineSearchField.stringValue) return ols.safe_element(for: index) as Any } if index == 0 && bkNum == 1 { return Self.kOutlineRootBookmarkItem } let idx = bkNum == 1 ? index-1 : index let rootOL = self.outlineRoot() return rootOL?.child(at: UInt(idx)) as Any } else { if let data = item as? String, data == Self.kOutlineRootBookmarkItem { return bks[index] } if item is CPDFBookmark { return "" } if let ol = item as? CPDFOutline { if self.isSearchOutlineMode == false { return ol.child(at: UInt(index)) as Any } let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue) return ols.safe_element(for: index) as Any } } } else if outlineView.isEqual(to: self.noteOutlineView) { if self.noteSearchMode { return self.noteSearchArray[index] } return self.annoListModel?.datas[index] as Any } return item as Any } func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { if outlineView.isEqual(to: self.tocOutlineView) { let cell = outlineView.makeView(withIdentifier: KMTocTableCellView.km_identifier, owner: self) as! KMTocTableCellView let columnId = tableColumn?.identifier.rawValue var title = "" var pageLabel = "" if let data = item as? String, data == Self.kOutlineRootBookmarkItem { title = NSLocalizedString("Bookmarks", comment: "") } else if let ol = item as? CPDFOutline { title = ol.label ?? "" if ol.actionType == .page { pageLabel = "\((ol.destination?.pageIndex ?? 0) + 1)" } } else if let bk = item as? CPDFBookmark { title = bk.label ?? "" pageLabel = "\(bk.pageIndex + 1)" } cell.pageLabel.stringValue = pageLabel if columnId == kLabelColumnId.rawValue { if self.isSearchOutlineMode { let attriString = NSMutableAttributedString(string: title) var searchString = self.outlineSearchField.stringValue var roughString = title if self.outlineIgnoreCaseFlag { roughString = roughString.lowercased() searchString = searchString.lowercased() } let ranges = roughString.ranges(of: searchString) for range in ranges.nsRnage { attriString.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: 13), range: range) } cell.tocLabel.attributedStringValue = attriString } else { cell.tocLabel.stringValue = title } } cell.tocLabel.textColor = KMAppearance.titleColor() return cell } else if outlineView.isEqual(to: self.noteOutlineView) { let model = item as? KMBotaAnnotationModel let note = (item as? KMBotaAnnotationModel)?.anno let cell = outlineView.makeView(withIdentifier: KMNoteTableViewCell.km_identifier, owner: self) as! KMNoteTableViewCell cell.cellNote = note cell.model = model if let data = note { if data.isKind(of: CPDFStampAnnotation.self) && data.isKind(of: KMSelfSignAnnotation.self) == false { } else { if data.isKind(of: CPDFMarkupAnnotation.self) { if (!cell.isFold) { // self.allFoldNotes.append(data) } else { cell.imageViewHeightConstraint.constant = 18.0 + 8 } } else { cell.imageViewHeightConstraint.constant = 18.0 + 8 } } } cell.isUnFoldNote = { cellNote, isUnfold in // 将折叠状态记录到模型 model?.foldType = isUnfold ? .unfold : .fold } return cell } return nil } func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { if outlineView.isEqual(self.noteOutlineView) { if let model = item as? KMBotaAnnotationModel { if model.foldType == .fold { return model.foldH } if let anno = model.anno { return KMBOTAAnnotationTool.fetchCellHeight(annotation: anno, maxSize: CGSize(width: 260+40 - 16, height: 1000)) } } return 30 } else if outlineView.isEqual(self.tocOutlineView) { if let ol = item as? CPDFOutline { let string: NSString = ol.label as NSString let ps = NSMutableParagraphStyle() ps.lineHeightMultiple = 1.32 ps.alignment = .left let attris = [NSAttributedString.Key.paragraphStyle : ps, NSAttributedString.Key.font : NSFont.SFProTextRegularFont(14.0)] let size = string.boundingRect(with: CGSizeMake(outlineView.frame.size.width - 30, 200), options: NSString.DrawingOptions(rawValue: 3), attributes: attris) return max(40, size.height + 16) } return 40 } return outlineView.rowHeight } func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { if outlineView.isEqual(self.tocOutlineView) { if let data = item as? String, data == Self.kOutlineRootBookmarkItem { return true } if item is CPDFBookmark { return false } guard let ol = item as? CPDFOutline else { return false } if self.isSearchOutlineMode == false { return ol.numberOfChildren > 0 } let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue) return ols.count > 0 } else if outlineView.isEqual(to: self.noteOutlineView) { return false } return false } func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? { if outlineView.isEqual(self.tocOutlineView) { let itemView = KMBotaTableRowView() return itemView } else if outlineView.isEqual(self.noteOutlineView) { let itemView = KMBotaTableRowView() return itemView; } return nil } func outlineViewSelectionDidChange(_ notification: Notification) { if self.tocOutlineView.isEqual(to: notification.object) { if self.dragIn { return } if self.updatingOutlineSelection == false { self.updatingOutlineSelection = true self.goToSelectedOutlineItem(nil) self.updatingOutlineSelection = false } } } func outlineViewItemDidExpand(_ notification: Notification) { if self.tocOutlineView.isEqual(to: notification.object) { self.updateOutlineSelection() } } func outlineViewItemDidCollapse(_ notification: Notification) { if self.tocOutlineView.isEqual(to: notification.object) { self.updateOutlineSelection() } } func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation { var dragOp = NSDragOperation(rawValue: 0) if outlineView.isEqual(to: self.noteOutlineView) { let pboard = info.draggingPasteboard if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) && index == NSOutlineViewDropOnItemIndex { if let note = item as? CPDFAnnotation, note.type != nil { dragOp = .every } } } else if outlineView.isEqual(to: self.tocOutlineView) { if (index == -1) { dragOp = NSDragOperation(rawValue: 0) } else { dragOp = .move } } return dragOp } func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool { if outlineView.isEqual(to: self.tocOutlineView) { if (self.tocOutlineView.selectedRowIndexes.count > 1) { return false } let tIndex = IndexSet(integer: self.tocOutlineView.clickedRow) self.tocOutlineView.deselectRow(tIndex.first ?? 0) self._dragPDFOutline = items.first as? CPDFOutline let set = IndexSet(integer: 0) let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: set) // [pasteboard declareTypes:[NSArray arrayWithObject:kKMPDFViewOutlineDragDataType] owner:self]; pasteboard.declareTypes([.localDraggedTypes], owner: self) pasteboard.setData(zNSIndexSetData, forType: .localDraggedTypes) return true } return false } func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { if outlineView.isEqual(to: self.noteOutlineView) { let pboard = info.draggingPasteboard if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) { // [item setColor:[NSColor colorFromPasteboard:pboard] alternate:isAlt updateDefaults:isShift]; let isShift = NSEvent.modifierFlags.contains(.shift) let isAlt = NSEvent.modifierFlags.contains(.option) guard let note = item as? CPDFAnnotation else { NSSound.beep() return false } if let color = NSColor(from: pboard) { note.setColor(color, alternate: isAlt, updateDefaults: isShift) return true } return false } } else if outlineView.isEqual(to: self.tocOutlineView) { if (index < 0) { return false } let outline = item as? CPDFOutline guard let dragPDFOL = self._dragPDFOutline else { return false } //root,drag item to root if ((outline?.parent == nil)) { //fetch root var root = dragPDFOL while (root.parent != nil) { root = root.parent } if dragPDFOL.parent.isEqual(to: root) { if dragPDFOL.index > index { self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: root) } else { if let bks = self.bookmarks(), bks.count > 0 { if let data = item as? String, data == Self.kOutlineRootBookmarkItem { } else { if index > 1 { self.dragPDFOutline(self._dragPDFOutline, toIndex: index-2, newParentOutline: root) } } } else { self.dragPDFOutline(self._dragPDFOutline, toIndex: index-1, newParentOutline: root) } } } else { self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: root) } } else { //在同一个层级内移动 if dragPDFOL.parent.isEqual(to: item) { if (dragPDFOL.index > 0) { if dragPDFOL.index > index { self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: outline) }else{ self.dragPDFOutline(self._dragPDFOutline, toIndex: index-1, newParentOutline: outline) } } else { return false } } else { var tOutlline: CPDFOutline? = outline var isContains = false while (tOutlline != nil) { if tOutlline!.isEqual(to: self._dragPDFOutline) { isContains = true break } tOutlline = tOutlline?.parent } if (!isContains) { self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: outline) } } } return true } return false } func outlineView(_ outlineView: NSOutlineView, willDisplayOutlineCell cell: Any, for tableColumn: NSTableColumn?, item: Any) { if outlineView.isEqual(to: self.tocOutlineView) { if outlineView.selectionHighlightStyle == .regular && outlineView.isRowSelected(outlineView.row(forItem: item)) { (cell as? NSCell)?.backgroundStyle = .lowered } } } func outlineView(_ outlineView: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) { if outlineView.isEqual(to: self.noteOutlineView) { var note: CPDFAnnotation? if let data = item as? CPDFAnnotation { note = data } else if let data = item as? KMBotaAnnotationModel { note = data.anno } if let data = note?.type, data.isEmpty == false { if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue { let string1 = (object as? String) ?? "" let string2 = note?.string() ?? "" if string1 != string2 { note?.setString(string1) } } else if tableColumn?.identifier.rawValue == self.authorColumnId.rawValue { let string1 = (object as? String) ?? "" let string2 = note?.userName() ?? "" if string1 != string2 { note?.setUserName(string1) } } } } } func outlineView(_ outlineView: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> NSCell? { if self.noteOutlineView.isEqual(to: outlineView) { if tableColumn == nil { if let anno = item as? CPDFAnnotation, anno.type == nil { return outlineView.tableColumn(withIdentifier: self.noteColumnId)?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell } } } return tableColumn?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell } func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool { if outlineView.isEqual(to: self.noteOutlineView) { if (tableColumn == nil) { if self.hideNotes() == false { if let anno = item as? CPDFAnnotation, anno.isNote() { // if ([pdfView hideNotes] == NO && [[(SKNoteText *)item note] isNote]) { // PDFAnnotation *annotation = [(SKNoteText *)item note]; self.listView?.scrollAnnotationToVisible(anno) self.listView?.updateActiveAnnotations([anno]) // [self showNote:annotation]; // SKNoteWindowController *noteController = (SKNoteWindowController *)[self windowControllerForNote:annotation]; // [[noteController window] makeFirstResponder:[noteController textView]]; // [[noteController textView] selectAll:nil]; } } return false } else if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue || tableColumn?.identifier.rawValue == self.authorColumnId.rawValue { return true } } return false } func outlineView(_ outlineView: NSOutlineView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String { if outlineView.isEqual(to: self.noteOutlineView) { if tableColumn == nil || tableColumn?.identifier.rawValue == self.noteColumnId.rawValue { return (item as? CPDFAnnotation)?.string() ?? "" } } return "" } func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) { self.dragIn = true } func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) { self.dragIn = false } /* #pragma mark NSOutlineView delegate protocol - (void)outlineView:(NSOutlineView *)ov didClickTableColumn:(NSTableColumn *)tableColumn { if ([ov isEqual:rightSideController.noteOutlineView]) { NSTableColumn *oldTableColumn = [ov highlightedTableColumn]; NSTableColumn *newTableColumn = ([NSEvent modifierFlags] & NSEventModifierFlagCommand) ? nil : tableColumn; NSMutableArray *sortDescriptors = nil; BOOL ascending = YES; if ([oldTableColumn isEqual:newTableColumn]) { sortDescriptors = [[[rightSideController.noteArrayController sortDescriptors] mutableCopy] autorelease]; [sortDescriptors replaceObjectAtIndex:0 withObject:[[sortDescriptors firstObject] reversedSortDescriptor]]; ascending = [[sortDescriptors firstObject] ascending]; } else { NSString *tcID = [newTableColumn identifier]; NSSortDescriptor *pageIndexSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationPageIndexKey ascending:ascending] autorelease]; NSSortDescriptor *boundsSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKPDFAnnotationBoundsOrderKey ascending:ascending selector:@selector(compare:)] autorelease]; sortDescriptors = [NSMutableArray arrayWithObjects:pageIndexSortDescriptor, boundsSortDescriptor, nil]; if ([tcID isEqualToString:TYPE_COLUMNID]) { [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationTypeKey ascending:YES selector:@selector(noteTypeCompare:)] autorelease] atIndex:0]; } else if ([tcID isEqualToString:COLOR_COLUMNID]) { [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationColorKey ascending:YES selector:@selector(colorCompare:)] autorelease] atIndex:0]; } else if ([tcID isEqualToString:NOTE_COLUMNID]) { [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationStringKey ascending:YES selector:@selector(localizedCaseInsensitiveNumericCompare:)] autorelease] atIndex:0]; } else if ([tcID isEqualToString:AUTHOR_COLUMNID]) { [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationUserNameKey ascending:YES selector:@selector(localizedCaseInsensitiveNumericCompare:)] autorelease] atIndex:0]; } else if ([tcID isEqualToString:DATE_COLUMNID]) { [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationModificationDateKey ascending:YES] autorelease] atIndex:0]; } if (oldTableColumn) [ov setIndicatorImage:nil inTableColumn:oldTableColumn]; [ov setHighlightedTableColumn:newTableColumn]; } [rightSideController.noteArrayController setSortDescriptors:sortDescriptors]; if (newTableColumn) [ov setIndicatorImage:[NSImage imageNamed:ascending ? @"NSAscendingSortIndicator" : @"NSDescendingSortIndicator"] inTableColumn:newTableColumn]; [ov reloadData]; } } - (void)outlineViewColumnDidResize:(NSNotification *)notification{ if (mwcFlags.autoResizeNoteRows && [[notification object] isEqual:rightSideController.noteOutlineView] && [[[[notification userInfo] objectForKey:@"NSTableColumn"] identifier] isEqualToString:NOTE_COLUMNID] && [(SKScrollView *)[[notification object] enclosingScrollView] isResizingSubviews] == NO) { [rowHeights removeAllFloats]; [rightSideController.noteOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [rightSideController.noteOutlineView numberOfRows])]]; } } - (void)sizeOutlineViewToContents:(NSOutlineView*) outlineView; { NSInteger rowCount = [outlineView numberOfRows]; for (NSInteger i = 0; i < rowCount; i++){ CGFloat rowHeight = 0; PDFOutline *outline = [leftSideController.tocOutlineView itemAtRow:i]; if (!outline){ continue; } NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc]init] autorelease]; NSDictionary *dictAttr1 = @{NSForegroundColorAttributeName:[KMAppearance KMColor_Layout_H0]}; NSAttributedString *attr1 = [[NSAttributedString alloc]initWithString:outline.label attributes:dictAttr1]; [attributedString appendAttributedString:attr1]; // NSTableCellView *viewS = [leftSideController.tocOutlineView viewAtColumn:0 row:i makeIfNecessary:YES]; NSTableColumn *tableColumn = [leftSideController.tocOutlineView tableColumnWithIdentifier:LABEL_COLUMNID]; // id cell = [tableColumn dataCell]; id cell = [tableColumn dataCellForRow:i]; [cell setObjectValue:attributedString]; CGFloat w = leftSideController.view.frame.size.width - 86;//[tableColumn width] > 260 ? [tableColumn width] : 260; NSInteger num = [self getNum:outline]; CGFloat gap = [leftSideController.tocOutlineView indentationPerLevel]; rowHeight = [cell cellSizeForBounds:NSMakeRect(0.0, 0.0, w - (num > 0?16:0) - gap*num, CGFLOAT_MAX)].height; rowHeight = fmax(rowHeight, [leftSideController.tocOutlineView rowHeight]) + 25; [rowHeights setFloat:rowHeight forKey:outline]; if (@available(macOS 10.13, *)) { } else { rowHeight = 40.0; } // CGRect fram = viewS.frame; // viewS.frame = CGRectMake(fram.origin.x, fram.origin.y, fram.size.width, rowHeight); } [leftSideController.tocOutlineView reloadData]; } */ func noteItems(_ items: NSArray) -> NSArray { let noteItems = NSMutableArray() for item in items { guard let anno = (item as? KMBotaAnnotationModel)?.anno else { continue } if anno.type == nil { // item = [(SKNoteText *)item note]; } if noteItems.contains(anno) == false { noteItems.add(anno) } } return noteItems } } // MARK: - KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource extension KMLeftSideViewController: KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource { func outlineView(_ anOutlineView: NSOutlineView, canDeleteItems items: [Any]) -> Bool { if anOutlineView.isEqual(to: self.noteOutlineView) { return self.hideNotes() == false && items.count > 0 } else if anOutlineView.isEqual(to: self.tocOutlineView) { return items.count > 0 } return false } func outlineView(_ anOutlineView: NSOutlineView, deleteItems items: [Any]) { if anOutlineView.isEqual(to: self.noteOutlineView) { if (items.isEmpty) { return } self.dataUpdating = true for item in self.noteItems(items as NSArray) { guard let anno = item as? CPDFAnnotation else { continue } self.listView?.remove(anno) } self.dataUpdating = false self.listView?.undoManager?.setActionName(KMLocalizedString("Remove Note", "Undo action name")) self.note_refrshUIIfNeed() } else if anOutlineView.isEqual(to: self.tocOutlineView) { self.outlineContextMenuItemClicked_RemoveEntry(nil) } } func outlineView(_ anOutlineView: NSOutlineView, canCopyItems items: [Any]) -> Bool { if anOutlineView.isEqual(to: self.noteOutlineView) { if (items.count == 1) { // PDFAnnotation *annotation = [[self noteItems:items] lastObject]; // if ([annotation isKindOfClass:[PDFAnnotationStamp class]] || // [annotation isKindOfClass:[PDFAnnotationLink class]]) { // return NO; // } } return items.count > 0 } return false } func outlineView(_ anOutlineView: NSOutlineView, copyItems items: [Any]) { if anOutlineView.isEqual(to: self.noteOutlineView) && items.isEmpty == false { // let pboard = NSPasteboard.general // var copiedItems: [Any] = [] // var attrString = NSMutableAttributedString() // var isAttributed = false // var item: AnyObject? // for (item in [self noteItems:items]) { // if ([item isMovable]) // [copiedItems addObject:item]; // } // for (item in items) { // if ([attrString length]) // [attrString replaceCharactersInRange:NSMakeRange([attrString length], 0) withString:@"\n\n"]; // if ([(PDFAnnotation *)item type] == nil && [[(SKNoteText *)item note] isNote]) { // [attrString appendAttributedString:[(SKNoteText *)item text]]; // isAttributed = YES; // } else { // [attrString replaceCharactersInRange:NSMakeRange([attrString length], 0) withString:[item string] ?: @""]; // } // } // // [pboard clearContents]; // if (isAttributed) // [pboard writeObjects:[NSArray arrayWithObjects:attrString, nil]]; // else // [pboard writeObjects:[NSArray arrayWithObjects:[attrString string], nil]]; // if ([copiedItems count] > 0) // [pboard writeObjects:copiedItems]; } } func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray { if self.noteOutlineView.isEqual(to: anOutlineView) { let count = self.noteOutlineView.numberOfRows let texts = NSMutableArray(capacity: count) for i in 0 ..< count { let item = self.noteOutlineView.item(atRow: i) let string = (item as? CPDFAnnotation)?.string() ?? "" texts.add(string) } return texts } else if self.tocOutlineView.isEqual(to: anOutlineView) { let count = self.tocOutlineView.numberOfRows let array = NSMutableArray(capacity: count) for i in 0 ..< count { // [array addObject:[[(PDFOutline *)[leftSideController.tocOutlineView itemAtRow:i] label] lossyStringUsingEncoding:NSASCIIStringEncoding]]; let item = self.tocOutlineView.item(atRow: i) as? CPDFOutline array.add(item?.label ?? "") } return array } return NSArray() } } // MARK: - Other extension KMLeftSideViewController { func tableView(_ tv: NSTableView, extractRowsWithIndexes rowIndexes: IndexSet) { if tv.isEqual(to: self.thumbnailTableView) { guard let document = self.pdfDocument() else { return } let pages = NSMutableArray() for idx in self.thumbnailTableView.selectedRowIndexes { if (idx < document.pageCount) { if let page = self.pdfDocument()?.page(at: UInt(idx)) { pages.add(page) } } } let fileName = document.getFileNameAccordingSelctPages(pages as! [CPDFPage]) let saveAccessCtr = KMSavePanelAccessoryController() let outputSavePanel = NSSavePanel() outputSavePanel.allowedFileTypes = ["pdf"] outputSavePanel.accessoryView = saveAccessCtr.view outputSavePanel.nameFieldStringValue = fileName outputSavePanel.beginSheetModal(for: self.view.window!) { result in if (result == .OK) { DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { let vc = KMProgressWindowController() self.view.window?.beginSheet(vc.window!) let saveFilePath = outputSavePanel.url?.path DispatchQueue.global().async { let pdf = CPDFDocument() let success = pdf?.extractAsOneDocument(withPages: pages as! [CPDFPage], savePath: saveFilePath) ?? false DispatchQueue.main.async { if (success) { if (saveAccessCtr.openAutomaticButton.state == .on) { NSDocumentController.shared.km_safe_openDocument(withContentsOf: outputSavePanel.url!, display: true) { _, _, _ in } } else { KMTools.viewFile(at: saveFilePath!) } } NSApp.endSheet(vc.window!) vc.close() } } } } } } } func fileNameWithSelectedPages(_ itemIndexes: IndexSet) -> String { var pagesName = "" if (itemIndexes.count > 1) { pagesName.append(" pages") } else { pagesName.append(" page") } let docmentName = self.pdfDocument()?.documentURL.deletingPathExtension().lastPathComponent ?? "" let tFileName = String(format: "%@ %@", pagesName,KMTools.parseIndexSet(indexSet: itemIndexes)) return String(format: "%@%@", docmentName,tFileName) } func draggedFileName(for page: CPDFPage) -> String { let pageIndex = "\(page.pageIndex() + 1)" var fileName = "" if let doc = self.view.window?.windowController?.document as? NSDocument { // fileName = doc.displayName.deletingPathExtension fileName = doc.fileURL?.deletingPathExtension().lastPathComponent ?? (MainBundle.nameSpaceName ?? "") } return "\(fileName)-Page \(pageIndex)" } func switchType(_ type: BotaType) { if type == .Thumbnail { self.leftView.segmentedControl.selectedSegment = 0 } else if type == .Outline { self.leftView.segmentedControl.selectedSegment = 1 } else if type == .Annotation { self.leftView.segmentedControl.selectedSegment = 2 } else if type == .snapshot { self.leftView.segmentedControl.selectedSegment = 3 } else if type == .Search { self.leftView.segmentedControl.selectedSegment = 4 } } func updateTableFont() { let font = NSFont.systemFont(ofSize: KMPreference.shared.outlineFontSize.cgFloat) self.tocOutlineView.font = font self.noteOutlineView.font = font self.findTableView.font = font self.groupedFindTableView.font = font } }