From d50cbcd8866882bfa23590b10fc163e5c4198767 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 13 Mar 2026 09:28:24 +0700 Subject: [PATCH 1/4] feat: add configurable font family and size for data grid --- CHANGELOG.md | 1 + .../Core/Storage/AppSettingsManager.swift | 5 + TablePro/Models/Settings/AppSettings.swift | 13 +++ TablePro/Theme/DataGridFontCache.swift | 35 +++++++ .../Views/Results/CellOverlayEditor.swift | 13 +-- .../Views/Results/DataGridCellFactory.swift | 47 +++------ TablePro/Views/Results/DataGridView.swift | 19 +--- .../Views/Settings/DataGridSettingsView.swift | 21 ++++ .../Models/DataGridSettingsTests.swift | 95 ++++++++++++++++++- docs/customization/settings.mdx | 15 +++ docs/vi/customization/settings.mdx | 15 +++ 11 files changed, 215 insertions(+), 64 deletions(-) create mode 100644 TablePro/Theme/DataGridFontCache.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 2863cef8c..d6270f3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SQL import options (wrap in transaction, disable FK checks) now persist across launches - `needsRestart` banner persists across app quit/relaunch after plugin uninstall - Copy as INSERT/UPDATE SQL statements from data grid context menu +- Configurable font family and size for data grid (Settings > Data Grid > Font) - Plugin download count display in Browse Plugins — fetched from GitHub Releases API and cached for 1 hour - MSSQL query cancellation (`cancelQuery`) and lock timeout (`applyQueryTimeout`) support - `~/.pgpass` file support for PostgreSQL/Redshift connections with live validation in the connection form diff --git a/TablePro/Core/Storage/AppSettingsManager.swift b/TablePro/Core/Storage/AppSettingsManager.swift index 1abd3254a..9966e2c86 100644 --- a/TablePro/Core/Storage/AppSettingsManager.swift +++ b/TablePro/Core/Storage/AppSettingsManager.swift @@ -60,6 +60,7 @@ final class AppSettingsManager { storage.saveDataGrid(validated) // Update date formatting service with new format DateFormattingService.shared.updateFormat(validated.dateFormat) + DataGridFontCache.reloadFromSettings(validated) notifyChange(.dataGridSettingsDidChange) } } @@ -135,6 +136,8 @@ final class AppSettingsManager { // Initialize DateFormattingService with current format DateFormattingService.shared.updateFormat(dataGrid.dateFormat) + DataGridFontCache.reloadFromSettings(dataGrid) + // Observe system accessibility text size changes and re-apply editor fonts observeAccessibilityTextSizeChanges() } @@ -169,6 +172,8 @@ final class AppSettingsManager { Self.logger.debug("Accessibility text size changed, scale: \(newScale, format: .fixed(precision: 2))") // Re-apply editor fonts with the updated accessibility scale factor SQLEditorTheme.reloadFromSettings(editor) + DataGridFontCache.reloadFromSettings(dataGrid) + notifyChange(.dataGridSettingsDidChange) // Notify the editor view to rebuild its configuration NotificationCenter.default.post(name: .accessibilityTextSizeDidChange, object: self) } diff --git a/TablePro/Models/Settings/AppSettings.swift b/TablePro/Models/Settings/AppSettings.swift index 0ec059ad7..e47a85eb1 100644 --- a/TablePro/Models/Settings/AppSettings.swift +++ b/TablePro/Models/Settings/AppSettings.swift @@ -354,6 +354,8 @@ enum DateFormatOption: String, Codable, CaseIterable, Identifiable { /// Data grid settings struct DataGridSettings: Codable, Equatable { + var fontFamily: EditorFont + var fontSize: Int var rowHeight: DataGridRowHeight var dateFormat: DateFormatOption var nullDisplay: String @@ -364,6 +366,8 @@ struct DataGridSettings: Codable, Equatable { static let `default` = DataGridSettings() init( + fontFamily: EditorFont = .systemMono, + fontSize: Int = 13, rowHeight: DataGridRowHeight = .normal, dateFormat: DateFormatOption = .iso8601, nullDisplay: String = "NULL", @@ -371,6 +375,8 @@ struct DataGridSettings: Codable, Equatable { showAlternateRows: Bool = true, autoShowInspector: Bool = false ) { + self.fontFamily = fontFamily + self.fontSize = fontSize self.rowHeight = rowHeight self.dateFormat = dateFormat self.nullDisplay = nullDisplay @@ -381,6 +387,8 @@ struct DataGridSettings: Codable, Equatable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) + fontFamily = try container.decodeIfPresent(EditorFont.self, forKey: .fontFamily) ?? .systemMono + fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 13 rowHeight = try container.decode(DataGridRowHeight.self, forKey: .rowHeight) dateFormat = try container.decode(DateFormatOption.self, forKey: .dateFormat) nullDisplay = try container.decode(String.self, forKey: .nullDisplay) @@ -389,6 +397,11 @@ struct DataGridSettings: Codable, Equatable { autoShowInspector = try container.decodeIfPresent(Bool.self, forKey: .autoShowInspector) ?? false } + /// Clamped font size (10-18) + var clampedFontSize: Int { + min(max(fontSize, 10), 18) + } + // MARK: - Validated Properties /// Validated and sanitized nullDisplay (max 20 chars, no newlines) diff --git a/TablePro/Theme/DataGridFontCache.swift b/TablePro/Theme/DataGridFontCache.swift new file mode 100644 index 000000000..c964db080 --- /dev/null +++ b/TablePro/Theme/DataGridFontCache.swift @@ -0,0 +1,35 @@ +// +// DataGridFontCache.swift +// TablePro +// +// Cached font variants for the data grid. +// Updated via reloadFromSettings() when user changes font preferences. +// + +import AppKit + +struct DataGridFontCache { + private(set) static var regular = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) + private(set) static var italic = regular.withTraits(.italic) + private(set) static var medium = NSFont.monospacedSystemFont(ofSize: 13, weight: .medium) + private(set) static var rowNumber = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .regular) + private(set) static var measureFont = regular + private(set) static var monoCharWidth: CGFloat = { + let attrs: [NSAttributedString.Key: Any] = [.font: regular] + return ("M" as NSString).size(withAttributes: attrs).width + }() + + @MainActor + static func reloadFromSettings(_ settings: DataGridSettings) { + let scale = SQLEditorTheme.accessibilityScaleFactor + let scaledSize = round(CGFloat(settings.clampedFontSize) * scale) + regular = settings.fontFamily.font(size: scaledSize) + italic = regular.withTraits(.italic) + medium = NSFontManager.shared.convert(regular, toHaveTrait: .boldFontMask) + let rowNumSize = max(round(scaledSize - 1), 9) + rowNumber = NSFont.monospacedDigitSystemFont(ofSize: rowNumSize, weight: .regular) + measureFont = regular + let attrs: [NSAttributedString.Key: Any] = [.font: regular] + monoCharWidth = ("M" as NSString).size(withAttributes: attrs).width + } +} diff --git a/TablePro/Views/Results/CellOverlayEditor.swift b/TablePro/Views/Results/CellOverlayEditor.swift index 818f30336..85ba973e4 100644 --- a/TablePro/Views/Results/CellOverlayEditor.swift +++ b/TablePro/Views/Results/CellOverlayEditor.swift @@ -56,7 +56,7 @@ final class CellOverlayEditor: NSObject, NSTextViewDelegate { let cellRect = cellView.convert(cellView.bounds, to: tableView) // Determine overlay height — at least the cell height, up to 120pt - let lineHeight: CGFloat = CellOverlayFonts.regular.boundingRectForFont.height + 4 + let lineHeight: CGFloat = DataGridFontCache.regular.boundingRectForFont.height + 4 let lineCount = CGFloat(value.components(separatedBy: .newlines).count) let contentHeight = max(lineCount * lineHeight + 8, cellRect.height) let overlayHeight = min(contentHeight, 120) @@ -73,7 +73,7 @@ final class CellOverlayEditor: NSObject, NSTextViewDelegate { textView.overlayEditor = self textView.isRichText = false textView.allowsUndo = true - textView.font = CellOverlayFonts.regular + textView.font = DataGridFontCache.regular textView.textColor = .labelColor textView.backgroundColor = .textBackgroundColor textView.isVerticallyResizable = true @@ -216,15 +216,6 @@ final class CellOverlayEditor: NSObject, NSTextViewDelegate { // Up/Down arrows — let NSTextView handle natively for line navigation return false } - - // MARK: - Fonts - - private enum CellOverlayFonts { - static let regular = NSFont.monospacedSystemFont( - ofSize: DesignConstants.FontSize.body, - weight: .regular - ) - } } // MARK: - Overlay Text View diff --git a/TablePro/Views/Results/DataGridCellFactory.swift b/TablePro/Views/Results/DataGridCellFactory.swift index cee0ad0bc..ac9758c8d 100644 --- a/TablePro/Views/Results/DataGridCellFactory.swift +++ b/TablePro/Views/Results/DataGridCellFactory.swift @@ -70,24 +70,6 @@ final class DataGridCellFactory { } } - // MARK: - Cached Fonts (avoid recreation per cell render) - - private enum CellFonts { - static let regular = NSFont.monospacedSystemFont( - ofSize: DesignConstants.FontSize.body, - weight: .regular - ) - static let italic = regular.withTraits(.italic) - static let medium = NSFont.monospacedSystemFont( - ofSize: DesignConstants.FontSize.body, - weight: .medium - ) - static let rowNumber = NSFont.monospacedDigitSystemFont( - ofSize: DesignConstants.FontSize.medium, - weight: .regular - ) - } - // MARK: - Cached Colors (avoid allocation per cell render) private enum CellColors { @@ -120,7 +102,7 @@ final class DataGridCellFactory { cell = NSTextField(labelWithString: "") cell.alignment = .right - cell.font = CellFonts.rowNumber + cell.font = DataGridFontCache.rowNumber cell.textColor = .secondaryLabelColor cell.translatesAutoresizingMaskIntoConstraints = false @@ -194,7 +176,7 @@ final class DataGridCellFactory { cellView.canDrawSubviewsIntoLayer = true cell = CellTextField() - cell.font = CellFonts.regular + cell.font = DataGridFontCache.regular cell.drawsBackground = false cell.isBordered = false cell.focusRingType = .none @@ -333,8 +315,8 @@ final class DataGridCellFactory { if !isLargeDataset { cell.placeholderString = nullDisplayString cell.textColor = .secondaryLabelColor - if cell.font !== CellFonts.italic { - cell.font = CellFonts.italic + if cell.font !== DataGridFontCache.italic { + cell.font = DataGridFontCache.italic } } else { cell.textColor = .secondaryLabelColor @@ -344,7 +326,7 @@ final class DataGridCellFactory { if !isLargeDataset { cell.placeholderString = "DEFAULT" cell.textColor = .systemBlue - cell.font = CellFonts.medium + cell.font = DataGridFontCache.medium } else { cell.textColor = .systemBlue } @@ -353,8 +335,8 @@ final class DataGridCellFactory { if !isLargeDataset { cell.placeholderString = "Empty" cell.textColor = .secondaryLabelColor - if cell.font !== CellFonts.italic { - cell.font = CellFonts.italic + if cell.font !== DataGridFontCache.italic { + cell.font = DataGridFontCache.italic } } else { cell.textColor = .secondaryLabelColor @@ -378,8 +360,8 @@ final class DataGridCellFactory { cell.stringValue = displayValue (cell as? CellTextField)?.originalValue = value cell.textColor = .labelColor - if cell.font !== CellFonts.regular { - cell.font = CellFonts.regular + if cell.font !== DataGridFontCache.regular { + cell.font = DataGridFontCache.regular } } } @@ -394,13 +376,6 @@ final class DataGridCellFactory { private static let sampleRowCount = 30 /// Maximum characters to consider per cell for width estimation private static let maxMeasureChars = 50 - /// Font for measuring cell content (monospaced — all glyphs have equal advance) - private static let measureFont = NSFont.monospacedSystemFont(ofSize: DesignConstants.FontSize.body, weight: .regular) - /// Pre-computed advance width of a single monospaced glyph (avoids per-row CoreText calls) - private static let monoCharWidth: CGFloat = { - let attrs: [NSAttributedString.Key: Any] = [.font: measureFont] - return ("M" as NSString).size(withAttributes: attrs).width - }() /// Font for measuring header private static let headerFont = NSFont.systemFont(ofSize: DesignConstants.FontSize.body, weight: .semibold) @@ -432,14 +407,14 @@ final class DataGridCellFactory { // instead of CoreText measurement. ~0.6 of mono width is a good estimate // for proportional system font. let headerCharCount = (columnName as NSString).length - var maxWidth = CGFloat(headerCharCount) * Self.monoCharWidth * 0.75 + 48 + var maxWidth = CGFloat(headerCharCount) * DataGridFontCache.monoCharWidth * 0.75 + 48 let totalRows = rowProvider.totalRowCount let columnCount = rowProvider.columns.count // Reduce sample count for wide tables to keep total work bounded let effectiveSampleCount = columnCount > 50 ? 10 : Self.sampleRowCount let step = max(1, totalRows / effectiveSampleCount) - let charWidth = Self.monoCharWidth + let charWidth = DataGridFontCache.monoCharWidth for i in stride(from: 0, to: totalRows, by: step) { guard let value = rowProvider.value(atRow: i, column: columnIndex) else { continue } diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index 524939b15..913ee88ec 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -730,22 +730,9 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData DispatchQueue.main.async { [weak self] in guard let self, let tableView = self.tableView else { return } let newRowHeight = CGFloat(AppSettingsManager.shared.dataGrid.rowHeight.rawValue) - - // Only reload if row height changed (requires full reload) - if tableView.rowHeight != newRowHeight { - tableView.rowHeight = newRowHeight - tableView.tile() - } else { - // For other settings (date format, NULL display), just reload visible rows - let visibleRect = tableView.visibleRect - let visibleRange = tableView.rows(in: visibleRect) - if visibleRange.length > 0 { - tableView.reloadData( - forRowIndexes: IndexSet(integersIn: visibleRange.location..<(visibleRange.location + visibleRange.length)), - columnIndexes: IndexSet(integersIn: 0.. 0) + #expect(DataGridFontCache.italic.pointSize > 0) + #expect(DataGridFontCache.medium.pointSize > 0) + #expect(DataGridFontCache.rowNumber.pointSize > 0) + #expect(DataGridFontCache.monoCharWidth > 0) + } + + @MainActor + @Test("fonts update when reloadFromSettings called with different settings") + func fontsUpdateOnReload() { + DataGridFontCache.reloadFromSettings(DataGridSettings(fontFamily: .systemMono, fontSize: 13)) + let initialSize = DataGridFontCache.regular.pointSize + let initialCharWidth = DataGridFontCache.monoCharWidth + + DataGridFontCache.reloadFromSettings(DataGridSettings(fontFamily: .systemMono, fontSize: 18)) + #expect(DataGridFontCache.regular.pointSize > initialSize) + #expect(DataGridFontCache.monoCharWidth >= initialCharWidth) + } + + @MainActor + @Test("different font families produce different fonts") + func differentFamilies() { + DataGridFontCache.reloadFromSettings(DataGridSettings(fontFamily: .systemMono, fontSize: 13)) + let systemMonoName = DataGridFontCache.regular.fontName + + DataGridFontCache.reloadFromSettings(DataGridSettings(fontFamily: .menlo, fontSize: 13)) + let menloName = DataGridFontCache.regular.fontName + + #expect(systemMonoName != menloName) + } } diff --git a/docs/customization/settings.mdx b/docs/customization/settings.mdx index 820709c5e..e8f5fdefe 100644 --- a/docs/customization/settings.mdx +++ b/docs/customization/settings.mdx @@ -147,6 +147,19 @@ Fully anonymous: no personal information, queries, or database content is transm ## Data Grid Settings +### Font + +| Setting | Default | Range | Description | +|---------|---------|-------|-------------| +| **Font family** | System Mono | System Mono, SF Mono, Menlo, Monaco, Courier New | Monospace font for data grid cells | +| **Font size** | 13 pt | 10-18 pt | Text size in data grid cells | + +The font setting affects all data grid cells, including NULL placeholders and cell editing overlays. A live preview is shown in the settings panel. + + +The data grid font is independent of the SQL editor font. You can use different fonts and sizes for each. + + ### Row Height | Option | Height | Best For | @@ -502,6 +515,8 @@ See [Editor Settings](/customization/editor-settings) for details. | Accent color | Appearance | UI accent color | | Row height | Data Grid | Grid row height | | Date format | Data Grid | Date display format | +| Font family | Data Grid | Monospace font for data cells | +| Font size | Data Grid | Data cell text size (10-18 pt) | See [Appearance](/customization/appearance) for details. diff --git a/docs/vi/customization/settings.mdx b/docs/vi/customization/settings.mdx index a141e14ed..a4d589ba2 100644 --- a/docs/vi/customization/settings.mdx +++ b/docs/vi/customization/settings.mdx @@ -145,6 +145,19 @@ Hoàn toàn ẩn danh: không gửi thông tin cá nhân, truy vấn hay nội d ## Cài đặt Bảng Dữ liệu +### Phông chữ + +| Cài đặt | Mặc định | Phạm vi | Mô tả | +|---------|---------|-------|-------------| +| **Phông chữ** | System Mono | System Mono, SF Mono, Menlo, Monaco, Courier New | Phông chữ đơn cách cho ô bảng dữ liệu | +| **Cỡ chữ** | 13 pt | 10-18 pt | Cỡ chữ trong ô bảng dữ liệu | + +Cài đặt phông chữ ảnh hưởng đến tất cả ô bảng dữ liệu, bao gồm placeholder NULL và overlay chỉnh sửa ô. Bản xem trước trực tiếp được hiển thị trong bảng cài đặt. + + +Phông chữ bảng dữ liệu độc lập với phông chữ SQL editor. Bạn có thể dùng phông chữ và cỡ chữ khác nhau cho mỗi loại. + + ### Chiều cao Hàng | Tùy chọn | Chiều cao | Phù hợp | @@ -496,6 +509,8 @@ Xem [Cài đặt Editor](/vi/customization/editor-settings). | Màu nhấn | Appearance | Màu nhấn UI | | Chiều cao hàng | Data Grid | Chiều cao hàng bảng | | Định dạng ngày | Data Grid | Định dạng hiển thị ngày | +| Phông chữ | Data Grid | Phông chữ đơn cách cho ô dữ liệu | +| Cỡ chữ | Data Grid | Cỡ chữ ô dữ liệu (10-18 pt) | Xem [Giao diện](/vi/customization/appearance). From 73b86a01f1178a16fb3be64edbed95dd6a277d4d Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 13 Mar 2026 09:41:38 +0700 Subject: [PATCH 2/4] fix: use partial reload for data grid settings changes to prevent stale data --- TablePro/Views/Results/DataGridView.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index 913ee88ec..ab409818b 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -730,9 +730,22 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData DispatchQueue.main.async { [weak self] in guard let self, let tableView = self.tableView else { return } let newRowHeight = CGFloat(AppSettingsManager.shared.dataGrid.rowHeight.rawValue) - tableView.rowHeight = newRowHeight - tableView.reloadData() - tableView.tile() + + if tableView.rowHeight != newRowHeight { + tableView.rowHeight = newRowHeight + tableView.tile() + } + + // Reload visible rows to apply font/formatting changes. + // Off-screen cells pick up new fonts from DataGridFontCache on scroll. + let visibleRect = tableView.visibleRect + let visibleRange = tableView.rows(in: visibleRect) + if visibleRange.length > 0 { + tableView.reloadData( + forRowIndexes: IndexSet(integersIn: visibleRange.location..<(visibleRange.location + visibleRange.length)), + columnIndexes: IndexSet(integersIn: 0.. Date: Fri, 13 Mar 2026 09:56:10 +0700 Subject: [PATCH 3/4] fix: update data grid fonts in-place to avoid stale cell data on reload --- TablePro/Views/Results/DataGridView.swift | 66 +++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index ab409818b..9bdcaae90 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -666,6 +666,8 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData // Settings observer for real-time updates fileprivate var settingsObserver: NSObjectProtocol? + /// Snapshot of last-seen data grid settings for change detection + private var lastDataGridSettings = AppSettingsManager.shared.dataGrid @Binding var selectedRowIndices: Set @@ -729,22 +731,36 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData DispatchQueue.main.async { [weak self] in guard let self, let tableView = self.tableView else { return } - let newRowHeight = CGFloat(AppSettingsManager.shared.dataGrid.rowHeight.rawValue) + let settings = AppSettingsManager.shared.dataGrid + let prev = self.lastDataGridSettings + self.lastDataGridSettings = settings + let newRowHeight = CGFloat(settings.rowHeight.rawValue) if tableView.rowHeight != newRowHeight { tableView.rowHeight = newRowHeight tableView.tile() } - // Reload visible rows to apply font/formatting changes. - // Off-screen cells pick up new fonts from DataGridFontCache on scroll. - let visibleRect = tableView.visibleRect - let visibleRange = tableView.rows(in: visibleRect) - if visibleRange.length > 0 { - tableView.reloadData( - forRowIndexes: IndexSet(integersIn: visibleRange.location..<(visibleRange.location + visibleRange.length)), - columnIndexes: IndexSet(integersIn: 0.. 0 { + tableView.reloadData( + forRowIndexes: IndexSet(integersIn: visibleRange.location..<(visibleRange.location + visibleRange.length)), + columnIndexes: IndexSet(integersIn: 0.. 0 else { return } + + let columnCount = tableView.numberOfColumns + for row in visibleRange.location..<(visibleRange.location + visibleRange.length) { + for col in 0.. Date: Fri, 13 Mar 2026 10:03:46 +0700 Subject: [PATCH 4/4] fix: address review feedback for data grid font setting --- TablePro/Theme/DataGridFontCache.swift | 10 ++++++ .../Views/Results/DataGridCellFactory.swift | 32 ++++++++----------- TablePro/Views/Results/DataGridView.swift | 18 ++++++----- .../Views/Settings/DataGridSettingsView.swift | 2 +- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/TablePro/Theme/DataGridFontCache.swift b/TablePro/Theme/DataGridFontCache.swift index c964db080..06f2d1fc6 100644 --- a/TablePro/Theme/DataGridFontCache.swift +++ b/TablePro/Theme/DataGridFontCache.swift @@ -8,6 +8,16 @@ import AppKit +/// Tags stored on NSTextField.tag to identify which font variant a cell uses. +/// Used by `updateVisibleCellFonts` to re-apply the correct variant after a font change. +enum DataGridFontVariant { + static let regular = 0 + static let italic = 1 + static let medium = 2 + static let rowNumber = 3 +} + +@MainActor struct DataGridFontCache { private(set) static var regular = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) private(set) static var italic = regular.withTraits(.italic) diff --git a/TablePro/Views/Results/DataGridCellFactory.swift b/TablePro/Views/Results/DataGridCellFactory.swift index ac9758c8d..f54d12ad7 100644 --- a/TablePro/Views/Results/DataGridCellFactory.swift +++ b/TablePro/Views/Results/DataGridCellFactory.swift @@ -96,6 +96,7 @@ final class DataGridCellFactory { let textField = reused.textField { cellView = reused cell = textField + cell.font = DataGridFontCache.rowNumber } else { cellView = NSTableCellView() cellView.identifier = cellViewId @@ -103,6 +104,7 @@ final class DataGridCellFactory { cell = NSTextField(labelWithString: "") cell.alignment = .right cell.font = DataGridFontCache.rowNumber + cell.tag = DataGridFontVariant.rowNumber cell.textColor = .secondaryLabelColor cell.translatesAutoresizingMaskIntoConstraints = false @@ -312,35 +314,28 @@ final class DataGridCellFactory { if value == nil { cell.stringValue = "" + cell.font = DataGridFontCache.italic + cell.tag = DataGridFontVariant.italic if !isLargeDataset { cell.placeholderString = nullDisplayString - cell.textColor = .secondaryLabelColor - if cell.font !== DataGridFontCache.italic { - cell.font = DataGridFontCache.italic - } - } else { - cell.textColor = .secondaryLabelColor } + cell.textColor = .secondaryLabelColor } else if value == "__DEFAULT__" { cell.stringValue = "" + cell.font = DataGridFontCache.medium + cell.tag = DataGridFontVariant.medium if !isLargeDataset { cell.placeholderString = "DEFAULT" - cell.textColor = .systemBlue - cell.font = DataGridFontCache.medium - } else { - cell.textColor = .systemBlue } + cell.textColor = .systemBlue } else if value == "" { cell.stringValue = "" + cell.font = DataGridFontCache.italic + cell.tag = DataGridFontVariant.italic if !isLargeDataset { cell.placeholderString = "Empty" - cell.textColor = .secondaryLabelColor - if cell.font !== DataGridFontCache.italic { - cell.font = DataGridFontCache.italic - } - } else { - cell.textColor = .secondaryLabelColor } + cell.textColor = .secondaryLabelColor } else { var displayValue = value ?? "" @@ -360,9 +355,8 @@ final class DataGridCellFactory { cell.stringValue = displayValue (cell as? CellTextField)?.originalValue = value cell.textColor = .labelColor - if cell.font !== DataGridFontCache.regular { - cell.font = DataGridFontCache.regular - } + cell.font = DataGridFontCache.regular + cell.tag = DataGridFontVariant.regular } } diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index 9bdcaae90..f6bdc4410 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -667,7 +667,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData // Settings observer for real-time updates fileprivate var settingsObserver: NSObjectProtocol? /// Snapshot of last-seen data grid settings for change detection - private var lastDataGridSettings = AppSettingsManager.shared.dataGrid + private var lastDataGridSettings: DataGridSettings @Binding var selectedRowIndices: Set @@ -718,6 +718,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData self.onPasteRows = onPasteRows self.onUndo = onUndo self.onRedo = onRedo + self.lastDataGridSettings = AppSettingsManager.shared.dataGrid super.init() updateCache() @@ -780,7 +781,8 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData // MARK: - Font Updates /// Update fonts on existing visible cell views in-place. - /// Avoids reloadData which recycles cells through the reuse pool. + /// Uses `DataGridFontVariant` tags set during cell configuration + /// to apply the correct font variant without inspecting cell content. @MainActor static func updateVisibleCellFonts(tableView: NSTableView) { let visibleRect = tableView.visibleRect @@ -793,14 +795,14 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData guard let cellView = tableView.view(atColumn: col, row: row, makeIfNecessary: false) as? NSTableCellView, let textField = cellView.textField else { continue } - let columnId = tableView.tableColumns[col].identifier.rawValue - if columnId == "__rowNumber__" { + switch textField.tag { + case DataGridFontVariant.rowNumber: textField.font = DataGridFontCache.rowNumber - } else if textField.stringValue.isEmpty && textField.placeholderString == "DEFAULT" { - textField.font = DataGridFontCache.medium - } else if textField.stringValue.isEmpty && textField.placeholderString != nil { + case DataGridFontVariant.italic: textField.font = DataGridFontCache.italic - } else { + case DataGridFontVariant.medium: + textField.font = DataGridFontCache.medium + default: textField.font = DataGridFontCache.regular } } diff --git a/TablePro/Views/Settings/DataGridSettingsView.swift b/TablePro/Views/Settings/DataGridSettingsView.swift index f1cd5416b..d9caa213b 100644 --- a/TablePro/Views/Settings/DataGridSettingsView.swift +++ b/TablePro/Views/Settings/DataGridSettingsView.swift @@ -27,7 +27,7 @@ struct DataGridSettingsView: View { GroupBox("Preview") { Text("1 John Doe john@example.com NULL") - .font(.custom(settings.fontFamily.displayName, size: CGFloat(settings.clampedFontSize))) + .font(Font(settings.fontFamily.font(size: CGFloat(settings.clampedFontSize)))) .frame(maxWidth: .infinity, alignment: .leading) .padding(8) }