// // KMTableView.swift // PDF Reader Pro // // Created by tangchao on 2023/11/16. // import Cocoa @objc protocol KMBotaTableViewDelegate: NSTableViewDelegate { @objc optional func tableView(_ aTableView: NSTableView, deleteRowsWithIndexes rowIndexes: IndexSet) @objc optional func tableView(_ aTableView: NSTableView, canDeleteRowsWithIndexes rowIndexes: IndexSet) -> Bool @objc optional func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) @objc optional func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool @objc optional func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard) @objc optional func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool @objc optional func tableViewMoveLeft(_ aTableView: NSTableView) @objc optional func tableViewMoveRight(_ aTableView: NSTableView) @objc optional func tableViewMoveUp(_ aTableView: NSTableView) @objc optional func tableViewMoveDown(_ aTableView: NSTableView) @objc optional func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) } class KMBotaTableView: NSTableView { var kmTrackingAreas: NSMutableSet? var supportsQuickLook = false var typeSelectHelper: SKTypeSelectHelper? weak var botaDelegate: KMBotaTableViewDelegate? { didSet { self._rebuildTrackingAreas() } } var hasImageToolTips: Bool { get { return self.kmTrackingAreas != nil } set { if self.kmTrackingAreas == nil && newValue { self.kmTrackingAreas = NSMutableSet() if self.window != nil { self._rebuildTrackingAreas() } } else if self.kmTrackingAreas != nil && newValue == false { if self.window != nil { self._removeTrackingAreas() } } } } func setTypeSelectHelper(_ newTypeSelectHelper: SKTypeSelectHelper) { if typeSelectHelper != newTypeSelectHelper { if self.isEqual(to: self.typeSelectHelper?.delegate) { typeSelectHelper?.delegate = nil } typeSelectHelper = newTypeSelectHelper typeSelectHelper?.delegate = self } } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } override func reloadData() { super.reloadData() self._rebuildTrackingAreas() self.typeSelectHelper?.rebuildTypeSelectSearchCache() } // MARK: - Tracking override func updateTrackingAreas() { super.updateTrackingAreas() self._rebuildTrackingAreas() } override func noteNumberOfRowsChanged() { super.noteNumberOfRowsChanged() self._rebuildTrackingAreas() } override func mouseEntered(with event: NSEvent) { if (self.kmTrackingAreas == nil) { return } let userInfo = event.trackingArea?.userInfo as? [String : Any] let row = userInfo?["row"] as? Int if row != nil { let context = self.botaDelegate?.tableView?(self, imageContextForRow: row!) if context is KMImageToolTipContext { KMImageToolTipWindow.shared.showForImageContext(context as! KMImageToolTipContext, at: .zero) } } } override func mouseExited(with event: NSEvent) { if (self.kmTrackingAreas == nil) { return } let userInfo = event.trackingArea?.userInfo as? [String : Any] let row = userInfo?["row"] as? Int if row != nil { KMImageToolTipWindow.shared.fadeOut() } } func scrollToBeginningOfDocument(_ sender: AnyObject?) { if self.numberOfRows > 0 { self.scrollRowToVisible(0) } } func scrollToEndOfDocument(_ sender: AnyObject?) { if self.numberOfRows > 0 { self.scrollRowToVisible(self.numberOfRows-1) } } func moveLeft(_ sender: AnyObject?) { self.botaDelegate?.tableViewMoveLeft?(self) } func moveRight(_ sender: AnyObject?) { self.botaDelegate?.tableViewMoveRight?(self) } func moveUp(_ sender: AnyObject?) { self.botaDelegate?.tableViewMoveUp?(self) } func moveDown(_ sender: AnyObject?) { self.botaDelegate?.tableViewMoveDown?(self) } func canDelete() -> Bool { let indexes = self.selectedRowIndexes if indexes.isEmpty { return false } return self.botaDelegate?.tableView?(self, canDeleteRowsWithIndexes: indexes) ?? false } @objc func delete(_ sender: AnyObject?) { if self.canDelete() { self.botaDelegate?.tableView?(self, deleteRowsWithIndexes: self.selectedRowIndexes) } } func canCopy() -> Bool { let indexes = self.selectedRowIndexes if indexes.isEmpty { return false } return self.botaDelegate?.tableView?(self, canCopyRowsWithIndexes: indexes) ?? false } @objc func copy(_ sender: AnyObject?) { if self.canCopy() { self.botaDelegate?.tableView?(self, copyRowsWithIndexes: self.selectedRowIndexes) } } func canPaste() -> Bool { return self.botaDelegate?.tableView?(self, canPasteFromPasteboard: NSPasteboard.general) ?? false } @objc func paste(_ sender: AnyObject?) { if self.canPaste() { self.botaDelegate?.tableView?(self, pasteFromPasteboard: NSPasteboard.general) } } override func keyDown(with event: NSEvent) { let eventChar = event.firstCharacter() let modifierFlags = event.deviceIndependentModifierFlags() if ((eventChar == NSNewlineCharacter || eventChar == NSEnterCharacter || eventChar == NSCarriageReturnCharacter) && modifierFlags == 0) { if self.doubleAction == nil || self.sendAction(self.doubleAction, to: self.target) == false { NSSound.beep() } } else if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && modifierFlags == 0 && self.canDelete()) { self.delete(self) } else if ((eventChar == SKSpaceCharacter) && modifierFlags == 0) { if (self.supportsQuickLook == false) { self.enclosingScrollView?.pageDown(nil) } else if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible { QLPreviewPanel.shared().orderOut(nil) } else { QLPreviewPanel.shared().makeKeyAndOrderFront(nil) } } else if ((eventChar == SKSpaceCharacter) && modifierFlags == NSEvent.ModifierFlags.shift.rawValue) { if (self.supportsQuickLook == false) { self.enclosingScrollView?.pageUp(nil) } } // else if (eventChar == NSHomeFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) { // [self scrollToBeginningOfDocument:nil]; // } else if (eventChar == NSEndFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) { // [self scrollToEndOfDocument:nil]; // } else if (eventChar == NSLeftArrowFunctionKey && modifierFlags == 0) { self.moveLeft(nil) } else if (eventChar == NSRightArrowFunctionKey && modifierFlags == 0) { self.moveRight(nil) } else if eventChar == NSUpArrowFunctionKey { self.moveUp(nil) } else if eventChar == NSDownArrowFunctionKey { self.moveDown(nil) } // else if ([typeSelectHelper handleEvent:theEvent] == NO) { // [super keyDown:theEvent]; // } } override var font: NSFont? { get { for tc in self.tableColumns { if let cell = tc.dataCell as? NSCell { if cell.type == .textCellType { return cell.font } } } return nil } set { for tc in self.tableColumns { if let cell = tc.dataCell as? NSCell { if cell.type == .textCellType { cell.font = newValue } } } var rowHeight = font?.defaultViewLineHeight() ?? 34 if self.selectionHighlightStyle == .sourceList { rowHeight += 2.0 } self.rowHeight = rowHeight self.noteHeightOfRows(withIndexesChanged: IndexSet(0.. NSImage { return super.dragImageForRows(with: dragRows, tableColumns: tableColumns[0..<1].shuffled(), event: dragEvent, offset: dragImageOffset) } } // MARK: - Private Methods extension KMBotaTableView { private func _rebuildTrackingAreas() { if self.kmTrackingAreas == nil { return } let context = self.botaDelegate?.tableView?(self, imageContextForRow: 0) if context == nil { return } self._removeTrackingAreas() if self.window != nil { let visibleRect = self.visibleRect let rowRange = self.rows(in: visibleRect) for i in rowRange.location ..< NSMaxRange(rowRange) { self._addTrackingAreaForRow(Int(i)) } } } private func _removeTrackingAreas() { if (self.kmTrackingAreas == nil) { return } for area in self.kmTrackingAreas! { self.removeTrackingArea(area as! NSTrackingArea) } self.kmTrackingAreas?.removeAllObjects() } private func _addTrackingAreaForRow(_ row: Int) { if (self.kmTrackingAreas == nil) { return } let userInfo = ["row" : row] var rect = self.rect(ofRow: row) rect.size.height -= 1 let area = NSTrackingArea(rect: rect, options: [.mouseEnteredAndExited, .activeInActiveApp], owner: self, userInfo: userInfo) self.addTrackingArea(area) self.kmTrackingAreas?.add(area) } } // MARK: - NSMenuItemValidation extension KMBotaTableView: NSMenuItemValidation { func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { if menuItem.action == #selector(delete) { return self.canDelete() } else if menuItem.action == KMSelectorCopy { return self.canCopy() } else if menuItem.action == #selector(paste) { return self.canPaste() } else if menuItem.action == #selector(selectAll) { return self.allowsMultipleSelection } else if menuItem.action == #selector(deselectAll) { return self.allowsEmptySelection } // else if ([[SKTableView superclass] instancesRespondToSelector:@selector(validateMenuItem:)]) // return [super validateMenuItem:menuItem]; return true } } // MARK: - SKTypeSelectHelperDelegate extension KMBotaTableView: SKTypeSelectHelperDelegate { func typeSelectHelperSelectionStrings(_ typeSelectHelper: SKTypeSelectHelper) -> NSArray { return self.botaDelegate?.tableView?(self, typeSelectHelperSelectionStrings: typeSelectHelper) ?? NSArray() } func typeSelectHelperCurrentlySelectedIndex(_ typeSelectHelper: SKTypeSelectHelper) -> Int { return self.selectedRowIndexes.last ?? NSNotFound } func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, selectItemAtIndex itemIndex: Int) { self.selectRowIndexes(NSIndexSet(index: itemIndex) as IndexSet, byExtendingSelection: false) self.scrollRowToVisible(itemIndex) } func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) { self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, didFailToFindMatchForSearchString: searchString) } func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) { self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, updateSearchString: searchString) } }