Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions TablePro/Models/Query/TableRows.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct TableRows: Sendable {
var columnNullable: [String: Bool]
var columnComments: [String: String]
var foreignKeysFetched: Bool
var displayMetadataVersion: Int = 0

init(
rows: ContiguousArray<Row> = [],
Expand Down Expand Up @@ -192,6 +193,9 @@ struct TableRows: Sendable {
self.columnComments = columnComments
didChange = true
}
if didChange {
displayMetadataVersion &+= 1
}
return didChange ? .columnsReplaced : .none
}

Expand Down
17 changes: 17 additions & 0 deletions TablePro/Views/Results/DataGridColumnPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ final class DataGridColumnPool {
column.headerCell = cell
}

let resolvedComment = comment?.isEmpty == false ? comment : nil
let typeName = columnType?.displayName ?? columnType?.rawType
if let headerCell = column.headerCell as? SortableHeaderCell {
var changed = false
if headerCell.comment != resolvedComment {
headerCell.comment = resolvedComment
changed = true
}
if headerCell.typeDisplayName != typeName {
headerCell.typeDisplayName = typeName
changed = true
}
if changed {
column.headerCell = headerCell
}
}

var tooltip: String
if let typeName = columnType?.rawType ?? columnType?.displayName {
tooltip = "\(name) (\(typeName))"
Expand Down
1 change: 1 addition & 0 deletions TablePro/Views/Results/DataGridUpdateSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ struct DataGridUpdateSnapshot: Equatable {
let alternatingRows: Bool
let reloadVersion: Int
let showObjectComments: Bool
let displayMetadataVersion: Int
}
28 changes: 25 additions & 3 deletions TablePro/Views/Results/DataGridView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ struct DataGridView: NSViewRepresentable {
rowHeight: rowHeight,
alternatingRows: alternatingRows,
reloadVersion: changeManager.reloadVersion,
showObjectComments: AppSettingsManager.shared.general.showObjectComments
showObjectComments: AppSettingsManager.shared.general.showObjectComments,
displayMetadataVersion: latestRows.displayMetadataVersion
)

if snapshot != coordinator.lastUpdateSnapshot {
Expand Down Expand Up @@ -306,13 +307,15 @@ struct DataGridView: NSViewRepresentable {
tableRows: TableRows,
savedLayout: ColumnLayoutState?
) {
let columnComments = AppSettingsManager.shared.general.showObjectComments
let showComments = AppSettingsManager.shared.general.showObjectComments
let columnComments = showComments
? tableRows.columnComments
: [:]
let columnTypes = showComments ? tableRows.columnTypes : []
coordinator.columnPool.reconcile(
tableView: tableView,
schema: coordinator.identitySchema,
columnTypes: tableRows.columnTypes,
columnTypes: columnTypes,
columnComments: columnComments,
savedLayout: savedLayout,
isEditable: isEditable,
Expand All @@ -325,6 +328,25 @@ struct DataGridView: NSViewRepresentable {
)
}
)

if let headerView = tableView.headerView as? SortableHeaderView {
var maxLineCount = 0
if showComments {
for (index, colName) in tableRows.columns.enumerated() {
let hasType = index < columnTypes.count && columnTypes[index].displayName != nil
let hasComment = !(columnComments[colName]?.isEmpty ?? true)
let lineCount: Int
switch (hasType, hasComment) {
case (true, true): lineCount = 2
case (true, false), (false, true): lineCount = 1
default: lineCount = 0
}
maxLineCount = max(maxLineCount, lineCount)
}
}
headerView.applyHeaderHeight(subtitleLineCount: maxLineCount)
headerView.needsDisplay = true
}
}

private func syncSortDescriptors(tableView: NSTableView, coordinator: TableViewCoordinator, columns: [String]) {
Expand Down
74 changes: 73 additions & 1 deletion TablePro/Views/Results/SortableHeaderCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@ final class SortableHeaderCell: NSTableHeaderCell {
var isValueFiltered: Bool = false
var isFunnelVisible: Bool = false
var supportsValueFilter: Bool = true
var comment: String?
var typeDisplayName: String?

var subtitleLineCount: Int {
let hasType = typeDisplayName != nil
let hasComment = !(comment?.isEmpty ?? true)
switch (hasType, hasComment) {
case (true, true): return 2
case (true, false), (false, true): return 1
case (false, false): return 0
}
}

private static let indicatorPadding: CGFloat = 4
private static let indicatorSpacing: CGFloat = 2
private static let priorityFontSize: CGFloat = 9
private static let defaultIndicatorSize = NSSize(width: 9, height: 6)
private static let funnelSize = NSSize(width: 13, height: 13)
private static let funnelPointSize: CGFloat = 11
private static let fixedTitleHeight: CGFloat = 15

override init(textCell string: String) {
super.init(textCell: string)
Expand All @@ -42,8 +55,20 @@ final class SortableHeaderCell: NSTableHeaderCell {
}

let foreground = foregroundColor(emphasized: isColumnSelected)
let lineCount = subtitleLineCount
let hasType = typeDisplayName != nil
let hasComment = !(comment?.isEmpty ?? true)

let titleFrame: NSRect
if lineCount > 0 {
let titleHeight = min(cellFrame.height * 0.55, Self.fixedTitleHeight)
titleFrame = NSRect(x: cellFrame.minX, y: cellFrame.minY, width: cellFrame.width, height: titleHeight)
} else {
titleFrame = cellFrame
}

drawTitle(
in: titleRect(forBounds: cellFrame),
in: titleRect(forBounds: titleFrame),
font: titleFont(isSorted: sortDirection != nil),
color: foreground
)
Expand All @@ -68,6 +93,22 @@ final class SortableHeaderCell: NSTableHeaderCell {
trailingCursorX -= Self.funnelSize.width + Self.indicatorSpacing
}

if lineCount > 0 {
let subtitleStartY = titleFrame.maxY
let subtitleTotalHeight = cellFrame.maxY - subtitleStartY
let lineHeight = subtitleTotalHeight / CGFloat(lineCount)
var lineY = subtitleStartY
if hasType, let type = typeDisplayName {
let typeFrame = NSRect(x: cellFrame.minX, y: lineY, width: cellFrame.width, height: lineHeight)
drawSubtitle(type, in: typeFrame, color: foreground)
lineY += lineHeight
}
if hasComment, let commentText = comment {
let commentFrame = NSRect(x: cellFrame.minX, y: lineY, width: cellFrame.width, height: lineHeight)
drawSubtitle(commentText, in: commentFrame, color: foreground)
}
}

guard let direction = sortDirection else { return }

let indicatorImage = Self.indicatorImage(for: direction, color: foreground)
Expand Down Expand Up @@ -178,6 +219,37 @@ final class SortableHeaderCell: NSTableHeaderCell {
title.draw(in: drawRect)
}

private func drawSubtitle(_ text: String, in rect: NSRect, color: NSColor) {
let inset = DataGridMetrics.cellHorizontalInset
let drawRect = NSRect(
x: rect.minX + inset,
y: rect.minY + 1,
width: max(0, rect.width - inset * 2),
height: max(0, rect.height - 2)
)
guard drawRect.width > 0, drawRect.height > 0 else { return }

let paragraph = NSMutableParagraphStyle()
paragraph.alignment = alignment
paragraph.lineBreakMode = .byTruncatingTail

let attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: NSFont.labelFontSize),
.foregroundColor: color.withAlphaComponent(0.65),
.paragraphStyle: paragraph
]

let attrString = NSAttributedString(string: text, attributes: attributes)
let textHeight = attrString.size().height
let centeredRect = NSRect(
x: drawRect.minX,
y: drawRect.minY + (drawRect.height - textHeight) / 2,
width: drawRect.width,
height: textHeight
)
attrString.draw(in: centeredRect)
}

override func drawSortIndicator(
withFrame cellFrame: NSRect,
in controlView: NSView,
Expand Down
18 changes: 18 additions & 0 deletions TablePro/Views/Results/SortableHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ final class SortableHeaderView: NSTableHeaderView {

private static let clickDragThreshold: CGFloat = 4
private static let resizeZoneWidth: CGFloat = 4
static let defaultHeaderHeight: CGFloat = 17
static let singleSubtitleHeaderHeight: CGFloat = 28
static let doubleSubtitleHeaderHeight: CGFloat = 38

private var pendingClickStartLocation: NSPoint?
private var dragOccurredDuringClick = false
Expand Down Expand Up @@ -194,6 +197,21 @@ final class SortableHeaderView: NSTableHeaderView {
}
}

func applyHeaderHeight(subtitleLineCount: Int) {
let targetHeight: CGFloat
switch subtitleLineCount {
case 2: targetHeight = Self.doubleSubtitleHeaderHeight
case 1: targetHeight = Self.singleSubtitleHeaderHeight
default: targetHeight = Self.defaultHeaderHeight
}
guard frame.height != targetHeight else { return }
frame.size.height = targetHeight
if let tableView {
tableView.tile()
needsDisplay = true
}
}

override func mouseDragged(with event: NSEvent) {
if let start = pendingClickStartLocation {
let current = convert(event.locationInWindow, from: nil)
Expand Down