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:
@@ -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, *) {
|
||||||
|
|||||||
Reference in New Issue
Block a user