diff --git a/TablePro/Core/ChangeTracking/PendingChanges.swift b/TablePro/Core/ChangeTracking/PendingChanges.swift index c1fd6f9b3..e2371622e 100644 --- a/TablePro/Core/ChangeTracking/PendingChanges.swift +++ b/TablePro/Core/ChangeTracking/PendingChanges.swift @@ -364,11 +364,14 @@ struct PendingChanges: Equatable { private mutating func removeChangeAt(_ arrayIndex: Int) { let removed = changes[arrayIndex] changeIndex.removeValue(forKey: RowChangeKey(rowIndex: removed.rowIndex, type: removed.type)) - changes.remove(at: arrayIndex) - for (key, idx) in changeIndex where idx > arrayIndex { - changeIndex[key] = idx - 1 + let lastIndex = changes.count - 1 + if arrayIndex != lastIndex { + let moved = changes[lastIndex] + changes.swapAt(arrayIndex, lastIndex) + changeIndex[RowChangeKey(rowIndex: moved.rowIndex, type: moved.type)] = arrayIndex } + changes.removeLast() } private mutating func rebuildChangeIndex() { diff --git a/TablePro/Views/Results/DataGridCellFactory.swift b/TablePro/Views/Results/DataGridCellFactory.swift index 6c7044c37..ee2d3c6e2 100644 --- a/TablePro/Views/Results/DataGridCellFactory.swift +++ b/TablePro/Views/Results/DataGridCellFactory.swift @@ -13,12 +13,10 @@ final class DataGridCellFactory { private static let sampleRowCount = 30 private static let maxMeasureChars = 50 - private var headerFont: NSFont { - NSFont.systemFont(ofSize: 13, weight: .semibold) - } + private static let headerFont: NSFont = NSFont.systemFont(ofSize: 13, weight: .semibold) func calculateColumnWidth(for columnName: String) -> CGFloat { - let attributes: [NSAttributedString.Key: Any] = [.font: headerFont] + let attributes: [NSAttributedString.Key: Any] = [.font: Self.headerFont] let size = (columnName as NSString).size(withAttributes: attributes) let width = size.width + 48 return min(max(width, Self.minColumnWidth), Self.maxColumnWidth) @@ -105,18 +103,28 @@ internal extension String { let nsString = self as NSString let length = nsString.length guard length > 0 else { return self } - guard containsLineBreak else { return self } - let mutable = NSMutableString(capacity: length) + var mutable: NSMutableString? + var copiedUpTo = 0 for i in 0.. copiedUpTo { + mutable?.append(nsString.substring(with: NSRange(location: copiedUpTo, length: i - copiedUpTo))) + } + mutable?.append(" ") + copiedUpTo = i + 1 + } + + guard let result = mutable else { return self } + if copiedUpTo < length { + result.append(nsString.substring(with: NSRange(location: copiedUpTo, length: length - copiedUpTo))) } - return mutable as String + return result as String } } diff --git a/TablePro/Views/Results/DataGridColumnPool.swift b/TablePro/Views/Results/DataGridColumnPool.swift index a5af5c0cd..20220313c 100644 --- a/TablePro/Views/Results/DataGridColumnPool.swift +++ b/TablePro/Views/Results/DataGridColumnPool.swift @@ -137,15 +137,38 @@ final class DataGridColumnPool { NSAnimationContext.current.allowsImplicitAnimation = false defer { NSAnimationContext.endGrouping() } + var indexByIdentifier: [NSUserInterfaceItemIdentifier: Int] = [:] + indexByIdentifier.reserveCapacity(tableView.tableColumns.count) + for (index, column) in tableView.tableColumns.enumerated() { + indexByIdentifier[column.identifier] = index + } + for (targetPosition, slot) in targetOrder.enumerated() { let identifier = ColumnIdentitySchema.slotIdentifier(slot) - guard let currentIndex = tableView.tableColumns.firstIndex(where: { $0.identifier == identifier }) else { - continue - } + guard let currentIndex = indexByIdentifier[identifier] else { continue } let desiredIndex = baseOffset + targetPosition guard desiredIndex < tableView.tableColumns.count else { continue } - if currentIndex != desiredIndex { - tableView.moveColumn(currentIndex, toColumn: desiredIndex) + if currentIndex == desiredIndex { continue } + + tableView.moveColumn(currentIndex, toColumn: desiredIndex) + updateIndexMap(&indexByIdentifier, movedFrom: currentIndex, to: desiredIndex) + } + } + + private func updateIndexMap( + _ map: inout [NSUserInterfaceItemIdentifier: Int], + movedFrom source: Int, + to destination: Int + ) { + guard source != destination else { return } + let lower = min(source, destination) + let upper = max(source, destination) + let delta = source < destination ? -1 : 1 + for (key, value) in map where value >= lower && value <= upper { + if value == source { + map[key] = destination + } else { + map[key] = value + delta } } } diff --git a/TablePro/Views/Results/DataGridCoordinator.swift b/TablePro/Views/Results/DataGridCoordinator.swift index f4b7d4e00..44bf26860 100644 --- a/TablePro/Views/Results/DataGridCoordinator.swift +++ b/TablePro/Views/Results/DataGridCoordinator.swift @@ -108,9 +108,6 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData var layoutPersistTask: Task? static let rowViewIdentifier = NSUserInterfaceItemIdentifier("TableRowView") - internal var pendingDropdownRow: Int = 0 - internal var pendingDropdownColumn: Int = 0 - internal weak var pendingDropdownTableView: NSTableView? private var rowVisualStateCache: [Int: RowVisualState] = [:] private var lastVisualStateCacheVersion: Int = 0 private let largeDatasetThreshold = 5_000 @@ -187,7 +184,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData object: nil, queue: .main ) { [weak self] _ in - Task { + Task { @MainActor [weak self] in guard let self, let tableView = self.tableView else { return } Self.updateVisibleCellFonts(tableView: tableView) } @@ -263,8 +260,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData func applyFullReplace() { guard let tableView else { return } - displayCache.removeAll() - rebuildVisualStateCache() + invalidateAllDisplayCaches() updateCache() tableView.reloadData() } @@ -312,6 +308,11 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData displayCache.removeAll() } + func invalidateAllDisplayCaches() { + displayCache.removeAll() + rebuildVisualStateCache() + } + func updateDisplayFormats(_ formats: [ValueDisplayFormat?]) { columnDisplayFormats = formats displayCache.removeAll() @@ -368,10 +369,10 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData func applyDelta(_ delta: Delta) { switch delta { case .cellChanged(let row, let column): - guard let tableView else { return } - let tableColumn = DataGridView.tableColumnIndex(for: column) + guard let tableView, + let tableColumn = DataGridView.tableColumnIndex(for: column, in: tableView, schema: identitySchema) + else { return } guard row >= 0, row < tableView.numberOfRows else { return } - guard tableColumn >= 0, tableColumn < tableView.numberOfColumns else { return } invalidateDisplayCache(forDisplayRow: row, column: column) rebuildVisualStateCache() tableView.reloadData( @@ -386,8 +387,11 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData if position.row >= 0, position.row < tableView.numberOfRows { rowSet.insert(position.row) } - let tableColumn = DataGridView.tableColumnIndex(for: position.column) - if tableColumn >= 0, tableColumn < tableView.numberOfColumns { + if let tableColumn = DataGridView.tableColumnIndex( + for: position.column, + in: tableView, + schema: identitySchema + ) { colSet.insert(tableColumn) } invalidateDisplayCache(forDisplayRow: position.row, column: position.column) @@ -406,7 +410,6 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData applyRemovedRows(indices) case .columnsReplaced, .fullReplace: sortedIDs = nil - displayCache.removeAll() applyFullReplace() } } @@ -431,8 +434,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData } func invalidateCachesForUndoRedo() { - displayCache.removeAll() - rebuildVisualStateCache() + invalidateAllDisplayCaches() updateCache() guard let tableView else { return } let visibleRange = tableView.rows(in: tableView.visibleRect) @@ -456,10 +458,10 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData } func beginEditing(displayRow: Int, column: Int) { - guard let tableView else { return } - let displayCol = DataGridView.tableColumnIndex(for: column) - guard displayRow >= 0, displayRow < tableView.numberOfRows, - displayCol >= 0, displayCol < tableView.numberOfColumns else { return } + guard let tableView, + let displayCol = DataGridView.tableColumnIndex(for: column, in: tableView, schema: identitySchema) + else { return } + guard displayRow >= 0, displayRow < tableView.numberOfRows else { return } tableView.scrollRowToVisible(displayRow) tableView.selectRowIndexes(IndexSet(integer: displayRow), byExtendingSelection: false) tableView.editColumn(displayCol, row: displayRow, with: nil, select: true) diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index 661c01be7..59ca5ad57 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -314,12 +314,30 @@ struct DataGridView: NSViewRepresentable { // MARK: - Column Layout Helpers - static func tableColumnIndex(for dataIndex: Int) -> Int { - dataIndex + 1 + static let firstDataTableColumnIndex: Int = 1 + + static func isDataTableColumn(_ tableColumnIndex: Int) -> Bool { + tableColumnIndex >= firstDataTableColumnIndex + } + + static func tableColumnIndex( + for dataIndex: Int, + in tableView: NSTableView, + schema: ColumnIdentitySchema + ) -> Int? { + guard let identifier = schema.identifier(for: dataIndex) else { return nil } + let index = tableView.column(withIdentifier: identifier) + return index >= 0 ? index : nil } - static func dataColumnIndex(for tableColumnIndex: Int) -> Int { - tableColumnIndex - 1 + static func dataColumnIndex( + for tableColumnIndex: Int, + in tableView: NSTableView, + schema: ColumnIdentitySchema + ) -> Int? { + guard tableColumnIndex >= 0, tableColumnIndex < tableView.tableColumns.count else { return nil } + let identifier = tableView.tableColumns[tableColumnIndex].identifier + return schema.dataIndex(from: identifier) } static func dismantleNSView(_ nsView: NSScrollView, coordinator: TableViewCoordinator) { diff --git a/TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift b/TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift index 559eac04d..5972ddcdc 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift @@ -41,7 +41,11 @@ extension TableViewCoordinator { invalidateDisplayCache() rebuildVisualStateCache() - let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex) + guard let tableColumnIndex = DataGridView.tableColumnIndex( + for: columnIndex, + in: tableView, + schema: identitySchema + ) else { return } if storageRow != nil, case .cellChanged = delta { tableRowsController.apply(.cellChanged(row: row, column: tableColumnIndex)) } else { diff --git a/TablePro/Views/Results/Extensions/DataGridView+Click.swift b/TablePro/Views/Results/Extensions/DataGridView+Click.swift index 0828599fc..0231288a1 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+Click.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+Click.swift @@ -15,11 +15,8 @@ extension TableViewCoordinator { let row = sender.clickedRow let column = sender.clickedColumn guard row >= 0, column > 0 else { return } - - let columnIndex = DataGridView.dataColumnIndex(for: column) + guard DataGridView.dataColumnIndex(for: column, in: sender, schema: identitySchema) != nil else { return } guard !changeManager.isRowDeleted(row) else { return } - - // Single click only selects the row. Chevron buttons handle dropdown/picker actions. } @objc func handleDoubleClick(_ sender: NSTableView) { @@ -29,7 +26,7 @@ extension TableViewCoordinator { let column = sender.clickedColumn guard row >= 0, column > 0 else { return } - let columnIndex = DataGridView.dataColumnIndex(for: column) + guard let columnIndex = DataGridView.dataColumnIndex(for: column, in: sender, schema: identitySchema) else { return } guard !changeManager.isRowDeleted(row) else { return } let tableRows = tableRowsProvider() @@ -75,8 +72,11 @@ extension TableViewCoordinator { guard row >= 0, columnIndex >= 0 else { return } guard !changeManager.isRowDeleted(row) else { return } guard let tableView else { return } - - let column = DataGridView.tableColumnIndex(for: columnIndex) + guard let column = DataGridView.tableColumnIndex( + for: columnIndex, + in: tableView, + schema: identitySchema + ) else { return } if let dropdownCols = dropdownColumns, dropdownCols.contains(columnIndex) { showDropdownMenu(tableView: tableView, row: row, column: column, columnIndex: columnIndex) diff --git a/TablePro/Views/Results/Extensions/DataGridView+Columns.swift b/TablePro/Views/Results/Extensions/DataGridView+Columns.swift index 84a8f96af..836710cff 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+Columns.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+Columns.swift @@ -47,10 +47,14 @@ extension TableViewCoordinator { ) let state = visualState(for: row) - let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex) let isFocused: Bool = { guard let keyTableView = tableView as? KeyHandlingTableView, keyTableView.focusedRow == row, + let tableColumnIndex = DataGridView.tableColumnIndex( + for: columnIndex, + in: tableView, + schema: identitySchema + ), keyTableView.focusedColumn == tableColumnIndex else { return false } return true }() diff --git a/TablePro/Views/Results/Extensions/DataGridView+Editing.swift b/TablePro/Views/Results/Extensions/DataGridView+Editing.swift index 00a2af8a4..5c72bd491 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+Editing.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+Editing.swift @@ -102,7 +102,7 @@ extension TableViewCoordinator { if forward { if nextColumn >= tableView.numberOfColumns { - nextColumn = 1 + nextColumn = DataGridView.firstDataTableColumnIndex nextRow += 1 } if nextRow >= tableView.numberOfRows { @@ -110,20 +110,24 @@ extension TableViewCoordinator { nextColumn = tableView.numberOfColumns - 1 } } else { - if nextColumn < 1 { + if !DataGridView.isDataTableColumn(nextColumn) { nextColumn = tableView.numberOfColumns - 1 nextRow -= 1 } if nextRow < 0 { nextRow = 0 - nextColumn = 1 + nextColumn = DataGridView.firstDataTableColumnIndex } } tableView.selectRowIndexes(IndexSet(integer: nextRow), byExtendingSelection: false) - let nextColumnIndex = nextColumn - 1 - if nextColumnIndex >= 0, + if let nextColumnIndex = DataGridView.dataColumnIndex( + for: nextColumn, + in: tableView, + schema: identitySchema + ), + nextColumnIndex >= 0, let nextDisplayRow = displayRow(at: nextRow), nextColumnIndex < nextDisplayRow.values.count, let value = nextDisplayRow.values[nextColumnIndex], @@ -140,9 +144,12 @@ extension TableViewCoordinator { let row = tableView.row(for: textField) let column = tableView.column(for: textField) - guard row >= 0, column > 0 else { return true } - - let columnIndex = DataGridView.dataColumnIndex(for: column) + guard row >= 0, column > 0, + let columnIndex = DataGridView.dataColumnIndex( + for: column, + in: tableView, + schema: identitySchema + ) else { return true } if isEscapeCancelling { isEscapeCancelling = false diff --git a/TablePro/Views/Results/Extensions/DataGridView+Popovers.swift b/TablePro/Views/Results/Extensions/DataGridView+Popovers.swift index 2e390eda0..15fd5b345 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+Popovers.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+Popovers.swift @@ -233,9 +233,7 @@ extension TableViewCoordinator { guard columnIndex >= 0, columnIndex < tableRows.columns.count else { return } let currentValue = cellValue(at: row, column: columnIndex) - pendingDropdownRow = row - pendingDropdownColumn = columnIndex - pendingDropdownTableView = tableView + let context = DropdownMenuContext(row: row, columnIndex: columnIndex) let options: [String] if let custom = customDropdownOptions?[columnIndex] { @@ -250,6 +248,7 @@ extension TableViewCoordinator { for option in options { let item = NSMenuItem(title: option, action: #selector(dropdownMenuItemSelected(_:)), keyEquivalent: "") item.target = self + item.representedObject = context if option == currentValue { item.state = .on } @@ -266,6 +265,7 @@ extension TableViewCoordinator { keyEquivalent: "" ) nullItem.target = self + nullItem.representedObject = context if currentValue == nil { nullItem.state = .on } @@ -277,22 +277,26 @@ extension TableViewCoordinator { } @objc func dropdownMenuItemSelected(_ sender: NSMenuItem) { - commitPopoverEdit( - row: pendingDropdownRow, - columnIndex: pendingDropdownColumn, - newValue: sender.title - ) + guard let context = sender.representedObject as? DropdownMenuContext else { return } + commitPopoverEdit(row: context.row, columnIndex: context.columnIndex, newValue: sender.title) } @objc func dropdownMenuNullSelected(_ sender: NSMenuItem) { - commitPopoverEdit( - row: pendingDropdownRow, - columnIndex: pendingDropdownColumn, - newValue: nil - ) + guard let context = sender.representedObject as? DropdownMenuContext else { return } + commitPopoverEdit(row: context.row, columnIndex: context.columnIndex, newValue: nil) } func commitPopoverEdit(row: Int, columnIndex: Int, newValue: String?) { commitCellEdit(row: row, columnIndex: columnIndex, newValue: newValue) } } + +private final class DropdownMenuContext { + let row: Int + let columnIndex: Int + + init(row: Int, columnIndex: Int) { + self.row = row + self.columnIndex = columnIndex + } +} diff --git a/TablePro/Views/Results/Extensions/DataGridView+Selection.swift b/TablePro/Views/Results/Extensions/DataGridView+Selection.swift index d8537dc49..e7d38f101 100644 --- a/TablePro/Views/Results/Extensions/DataGridView+Selection.swift +++ b/TablePro/Views/Results/Extensions/DataGridView+Selection.swift @@ -14,7 +14,8 @@ extension TableViewCoordinator { func tableViewColumnDidMove(_ notification: Notification) { guard !isRebuildingColumns else { return } - scheduleLayoutPersist() + layoutPersistTask?.cancel() + persistColumnLayoutToStorage() } func scheduleLayoutPersist() { diff --git a/TablePro/Views/Results/KeyHandlingTableView.swift b/TablePro/Views/Results/KeyHandlingTableView.swift index d4bffcb87..87c4c2b38 100644 --- a/TablePro/Views/Results/KeyHandlingTableView.swift +++ b/TablePro/Views/Results/KeyHandlingTableView.swift @@ -87,8 +87,9 @@ final class KeyHandlingTableView: NSTableView { focusedColumn = clickedColumn if alreadyFocusedHere && event.clickCount == 1 && selectedRowIndexes.count == 1 { - let dataColumnIndex = DataGridView.dataColumnIndex(for: clickedColumn) - if coordinator?.canStartInlineEdit(row: clickedRow, columnIndex: dataColumnIndex) == true { + if let schema = coordinator?.identitySchema, + let dataColumnIndex = DataGridView.dataColumnIndex(for: clickedColumn, in: self, schema: schema), + coordinator?.canStartInlineEdit(row: clickedRow, columnIndex: dataColumnIndex) == true { editColumn(clickedColumn, row: clickedRow, with: nil, select: true) } } @@ -106,11 +107,12 @@ final class KeyHandlingTableView: NSTableView { @objc func paste(_ sender: Any?) { guard coordinator?.isEditable == true else { return } - if focusedRow >= 0, focusedColumn >= 1 { - let dataCol = DataGridView.dataColumnIndex(for: focusedColumn) - if coordinator?.pasteCellsFromClipboard(anchorRow: focusedRow, anchorColumn: dataCol) == true { - return - } + if focusedRow >= 0, + DataGridView.isDataTableColumn(focusedColumn), + let schema = coordinator?.identitySchema, + let dataCol = DataGridView.dataColumnIndex(for: focusedColumn, in: self, schema: schema), + coordinator?.pasteCellsFromClipboard(anchorRow: focusedRow, anchorColumn: dataCol) == true { + return } coordinator?.delegate?.dataGridPasteRows() } @@ -124,7 +126,7 @@ final class KeyHandlingTableView: NSTableView { case #selector(paste(_:)): return coordinator?.isEditable == true && coordinator?.delegate != nil case #selector(insertNewline(_:)): - return selectedRow >= 0 && focusedColumn >= 1 && coordinator?.isEditable == true + return selectedRow >= 0 && DataGridView.isDataTableColumn(focusedColumn) && coordinator?.isEditable == true case #selector(cancelOperation(_:)): return false default: @@ -176,9 +178,15 @@ final class KeyHandlingTableView: NSTableView { if let fkCombo = AppSettingsManager.shared.keyboard.shortcut(for: .previewFKReference), !fkCombo.isCleared, fkCombo.matches(event), - selectedRow >= 0, focusedColumn >= 1 { + selectedRow >= 0, + DataGridView.isDataTableColumn(focusedColumn), + let schema = coordinator?.identitySchema, + let columnIndex = DataGridView.dataColumnIndex(for: focusedColumn, in: self, schema: schema) { coordinator?.toggleForeignKeyPreview( - tableView: self, row: selectedRow, column: focusedColumn, columnIndex: focusedColumn - 1 + tableView: self, + row: selectedRow, + column: focusedColumn, + columnIndex: columnIndex ) return } @@ -188,11 +196,14 @@ final class KeyHandlingTableView: NSTableView { @objc override func insertNewline(_ sender: Any?) { let row = selectedRow - guard row >= 0, focusedColumn >= 1, coordinator?.isEditable == true else { + guard row >= 0, + DataGridView.isDataTableColumn(focusedColumn), + coordinator?.isEditable == true, + let schema = coordinator?.identitySchema, + let columnIndex = DataGridView.dataColumnIndex(for: focusedColumn, in: self, schema: schema) else { return } - let columnIndex = DataGridView.dataColumnIndex(for: focusedColumn) if let value = coordinator?.cellValue(at: row, column: columnIndex), value.containsLineBreak { coordinator?.showOverlayEditor(tableView: self, row: row, column: focusedColumn, columnIndex: columnIndex, value: value) @@ -215,29 +226,33 @@ final class KeyHandlingTableView: NSTableView { let target = focusedColumn < 0 ? lastVisibleDataColumn() : previousVisibleDataColumn(before: focusedColumn) - guard target >= 1 else { return } + guard DataGridView.isDataTableColumn(target) else { return } focusedColumn = target if currentRow >= 0 { scrollColumnToVisible(target) } } private func handleRightArrow(currentRow: Int) { - let target = focusedColumn < 1 - ? firstVisibleDataColumn() - : nextVisibleDataColumn(after: focusedColumn) - guard target >= 1 else { return } + let target = DataGridView.isDataTableColumn(focusedColumn) + ? nextVisibleDataColumn(after: focusedColumn) + : firstVisibleDataColumn() + guard DataGridView.isDataTableColumn(target) else { return } focusedColumn = target if currentRow >= 0 { scrollColumnToVisible(target) } } private func firstVisibleDataColumn() -> Int { - for index in 1.. Int { - for index in stride(from: numberOfColumns - 1, through: 1, by: -1) where isVisibleDataColumn(at: index) { + for index in stride( + from: numberOfColumns - 1, + through: DataGridView.firstDataTableColumnIndex, + by: -1 + ) where isVisibleDataColumn(at: index) { return index } return -1 @@ -252,8 +267,12 @@ final class KeyHandlingTableView: NSTableView { } private func previousVisibleDataColumn(before current: Int) -> Int { - guard current > 1 else { return -1 } - for index in stride(from: current - 1, through: 1, by: -1) where isVisibleDataColumn(at: index) { + guard current > DataGridView.firstDataTableColumnIndex else { return -1 } + for index in stride( + from: current - 1, + through: DataGridView.firstDataTableColumnIndex, + by: -1 + ) where isVisibleDataColumn(at: index) { return index } return -1 @@ -267,13 +286,13 @@ final class KeyHandlingTableView: NSTableView { private func handleTabKey() { let row = selectedRow - guard row >= 0, focusedColumn >= 1 else { return } + guard row >= 0, DataGridView.isDataTableColumn(focusedColumn) else { return } var nextColumn = focusedColumn + 1 var nextRow = row if nextColumn >= numberOfColumns { - nextColumn = 1 + nextColumn = DataGridView.firstDataTableColumnIndex nextRow += 1 } if nextRow >= numberOfRows { @@ -290,18 +309,18 @@ final class KeyHandlingTableView: NSTableView { private func handleShiftTabKey() { let row = selectedRow - guard row >= 0, focusedColumn >= 1 else { return } + guard row >= 0, DataGridView.isDataTableColumn(focusedColumn) else { return } var prevColumn = focusedColumn - 1 var prevRow = row - if prevColumn < 1 { + if !DataGridView.isDataTableColumn(prevColumn) { prevColumn = numberOfColumns - 1 prevRow -= 1 } if prevRow < 0 { prevRow = 0 - prevColumn = 1 + prevColumn = DataGridView.firstDataTableColumnIndex } selectRowIndexes(IndexSet(integer: prevRow), byExtendingSelection: false) diff --git a/TablePro/Views/Results/TableRowViewWithMenu.swift b/TablePro/Views/Results/TableRowViewWithMenu.swift index a7dd7b7e2..3ef63dd90 100644 --- a/TablePro/Views/Results/TableRowViewWithMenu.swift +++ b/TablePro/Views/Results/TableRowViewWithMenu.swift @@ -22,8 +22,9 @@ final class TableRowViewWithMenu: NSTableRowView { let locationInTable = tableView.convert(locationInRow, from: self) let clickedColumn = tableView.column(at: locationInTable) - // Adjust for row number column (index 0) - let dataColumnIndex = clickedColumn > 0 ? DataGridView.dataColumnIndex(for: clickedColumn) : -1 + let dataColumnIndex: Int = clickedColumn > 0 + ? DataGridView.dataColumnIndex(for: clickedColumn, in: tableView, schema: coordinator.identitySchema) ?? -1 + : -1 let menu = NSMenu() @@ -288,9 +289,14 @@ final class TableRowViewWithMenu: NSTableRowView { @objc private func previewForeignKey(_ sender: NSMenuItem) { guard let columnIndex = sender.representedObject as? Int, - let coordinator, let tableView = coordinator.tableView else { return } + let coordinator, let tableView = coordinator.tableView, + let column = DataGridView.tableColumnIndex( + for: columnIndex, + in: tableView, + schema: coordinator.identitySchema + ) else { return } coordinator.showForeignKeyPreview( - tableView: tableView, row: rowIndex, column: DataGridView.tableColumnIndex(for: columnIndex), columnIndex: columnIndex + tableView: tableView, row: rowIndex, column: column, columnIndex: columnIndex ) }