Fix dictionary table: view-based editable cells, prune empty rows

The cell-based table couldn't be edited reliably (no click-to-edit, no
Tab between columns). Rewritten as a view-based NSTableView where each
cell is an editable NSTextField committed via controlTextDidEndEditing
(Enter/Tab/focus-loss). Tab now moves from "Felismert" to "Helyette".
Rows where both columns are blank are pruned on add and on window close,
so empty rows are never persisted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-06-27 09:12:33 +02:00
szülő 636a4acf97
commit 913ce5ca1a

Fájl megtekintése

@@ -386,7 +386,7 @@ final class RecordingHUD {
} }
// MARK: - App Delegate // MARK: - App Delegate
class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate, NSTableViewDataSource, NSTableViewDelegate { class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate, NSWindowDelegate {
var statusItem: NSStatusItem! var statusItem: NSStatusItem!
var audioRecorder: AVAudioRecorder? var audioRecorder: AVAudioRecorder?
var isRecording = false var isRecording = false
@@ -631,6 +631,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
) )
window.title = "WhisperDictate Settings" window.title = "WhisperDictate Settings"
window.center() window.center()
window.delegate = self
let contentView = NSView(frame: window.contentView!.bounds) let contentView = NSView(frame: window.contentView!.bounds)
@@ -741,15 +742,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
let colFrom = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("from")) let colFrom = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("from"))
colFrom.title = "Felismert"; colFrom.width = 195; colFrom.isEditable = true colFrom.title = "Felismert"; colFrom.width = 195; colFrom.isEditable = true
let colTo = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("to")) let colTo = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("to"))
colTo.title = "Helyette"; colTo.width = 195; colTo.isEditable = true colTo.title = "Helyette"; colTo.width = 195
for col in [colFrom, colTo] {
let cell = NSTextFieldCell()
cell.isEditable = true
cell.isSelectable = true
cell.font = NSFont.systemFont(ofSize: 12)
cell.lineBreakMode = .byTruncatingTail
col.dataCell = cell
}
table.addTableColumn(colFrom) table.addTableColumn(colFrom)
table.addTableColumn(colTo) table.addTableColumn(colTo)
table.dataSource = self table.dataSource = self
@@ -870,22 +863,44 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
setLaunchAtLogin(sender.state == .on) setLaunchAtLogin(sender.state == .on)
} }
// MARK: - Custom dictionary table (cell-based, editable) // MARK: - Custom dictionary table (view-based, editable text fields)
func numberOfRows(in tableView: NSTableView) -> Int { replacements.count } func numberOfRows(in tableView: NSTableView) -> Int { replacements.count }
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard row < replacements.count, let id = tableColumn?.identifier.rawValue else { return nil } guard let column = tableColumn, row < replacements.count else { return nil }
return replacements[row][id] ?? "" let id = column.identifier
let field: NSTextField
if let reused = tableView.makeView(withIdentifier: id, owner: self) as? NSTextField {
field = reused
} else {
field = NSTextField()
field.identifier = id
field.isBordered = false
field.drawsBackground = false
field.isEditable = true
field.isSelectable = true
field.font = NSFont.systemFont(ofSize: 12)
field.lineBreakMode = .byTruncatingTail
field.delegate = self
field.placeholderString = id.rawValue == "from" ? "felismert szó" : "csere (\\n = új sor)"
}
field.stringValue = replacements[row][id.rawValue] ?? ""
return field
} }
func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { // Commit an edited cell back into the model (fires on Enter, Tab, focus loss).
guard row < replacements.count, let id = tableColumn?.identifier.rawValue else { return } func controlTextDidEndEditing(_ obj: Notification) {
guard let field = obj.object as? NSTextField, let table = dictTable else { return }
let row = table.row(for: field), col = table.column(for: field)
guard row >= 0, row < replacements.count, col >= 0, col < table.tableColumns.count else { return }
let id = table.tableColumns[col].identifier.rawValue
var rows = replacements var rows = replacements
rows[row][id] = (object as? String) ?? "" rows[row][id] = field.stringValue
replacements = rows replacements = rows
} }
@objc func addReplacement() { @objc func addReplacement() {
pruneEmptyReplacements()
var rows = replacements var rows = replacements
rows.append(["from": "", "to": ""]) rows.append(["from": "", "to": ""])
replacements = rows replacements = rows
@@ -901,6 +916,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
table.reloadData() table.reloadData()
} }
// Drop rows where both columns are blank so empty rows aren't persisted.
func pruneEmptyReplacements() {
let cleaned = replacements.filter {
!(($0["from"] ?? "").trimmingCharacters(in: .whitespaces).isEmpty
&& ($0["to"] ?? "").trimmingCharacters(in: .whitespaces).isEmpty)
}
if cleaned.count != replacements.count { replacements = cleaned }
}
func windowWillClose(_ notification: Notification) {
pruneEmptyReplacements()
}
// MARK: - Launch at Login // MARK: - Launch at Login
func isLaunchAtLoginEnabled() -> Bool { func isLaunchAtLoginEnabled() -> Bool {
if #available(macOS 13.0, *) { if #available(macOS 13.0, *) {