From 8af27f883455f34f80db30cf90ac4caf69ff65d2 Mon Sep 17 00:00:00 2001 From: mappu Date: Sat, 6 Jul 2024 11:04:19 +1200 Subject: [PATCH] gui: change tracking for insert, edit, delete actions --- main.go | 121 +++++++++++++++++++++++++++++++++++++++++++------- util_types.go | 10 +++++ util_vcl.go | 1 + 3 files changed, 117 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index c459f3a..e18b504 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,11 @@ import ( const ( APPNAME = "yvbolt" HOMEPAGE_URL = "https://code.ivysaur.me/yvbolt" + + CO_INSERT = colors.ClYellow + CO_EDIT_IMPLICIT = colors.ClLightgreen + CO_EDIT_EXPLICIT = colors.ClGreen + CO_DELETE = colors.ClRed ) type TMainForm struct { @@ -32,7 +37,10 @@ type TMainForm struct { Tabs *vcl.TPageControl propertiesBox *vcl.TMemo contentBox *vcl.TStringGrid - insertRows []int32 // Rows in the StringGrid that are to-be-inserted + isEditing bool + insertRows map[int32]struct{} // Rows in the StringGrid that are to-be-inserted + deleteRows map[int32]struct{} + updateRows map[int32][]int32 // Row->cells that are to-be-updated queryInput *vcl.TMemo queryResult *vcl.TStringGrid @@ -233,14 +241,26 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) { dataInsertBtn.SetCaption("Insert") dataInsertBtn.SetOnClick(f.OnDataInsertClick) + dataDelRowBtn := vcl.NewToolButton(dataButtonBar) + dataDelRowBtn.SetParent(dataButtonBar) + dataDelRowBtn.SetCaption("Delete Row") + dataDelRowBtn.SetOnClick(f.OnDataDeleteRowClick) + + dataCommitBtn := vcl.NewToolButton(dataButtonBar) + dataCommitBtn.SetParent(dataButtonBar) + dataCommitBtn.SetCaption("Commit") + dataCommitBtn.SetOnClick(f.OnDataCommitClick) + f.contentBox = vcl.NewStringGrid(dataTab) f.contentBox.SetParent(dataTab) f.contentBox.BorderSpacing().SetLeft(MY_SPACING) f.contentBox.BorderSpacing().SetRight(MY_SPACING) f.contentBox.BorderSpacing().SetBottom(MY_SPACING) f.contentBox.SetAlign(types.AlClient) // fill remaining space - f.contentBox.SetOptions(f.contentBox.Options().Include(types.GoThumbTracking, types.GoColSizing, types.GoDblClickAutoSize)) + f.contentBox.SetOptions(f.contentBox.Options().Include(types.GoThumbTracking, types.GoColSizing, types.GoDblClickAutoSize, types.GoEditing)) f.contentBox.SetOnPrepareCanvas(f.OnDataPrepareCanvas) + f.contentBox.SetOnEditingDone(f.OnDataCellEdited) + f.contentBox.SetOnGetEditText(f.OnDataCellEditStarting) f.contentBox.SetDefaultColWidth(MY_WIDTH) vcl_stringgrid_clear(f.contentBox) @@ -510,7 +530,8 @@ func (f *TMainForm) RefreshCurrentItem() { ndata := (*navData)(item.Data()) - f.OnNavContextRefresh(item, ndata) + f.OnNavContextRefresh(item, ndata) // Refresh LHS pane/children + f.OnNavChange(f.Buckets, item) // Refresh RHS pane/data content } func (f *TMainForm) OnNavContextRefresh(item *vcl.TTreeNode, ndata *navData) { @@ -530,22 +551,66 @@ func (f *TMainForm) OnNavContextRefresh(item *vcl.TTreeNode, ndata *navData) { } func (f *TMainForm) OnDataPrepareCanvas(sender vcl.IObject, aCol, aRow int32, aState types.TGridDrawState) { - if len(f.insertRows) == 0 { - return // fast-path + + if _, ok := f.deleteRows[aRow]; ok { + f.contentBox.Canvas().Brush().SetColor(CO_DELETE) + return } - isInsertRow := false - for _, rr := range f.insertRows { - if rr == aRow { - isInsertRow = true - break + if _, ok := f.insertRows[aRow]; ok { + f.contentBox.Canvas().Brush().SetColor(CO_INSERT) + return + } + + if er, ok := f.updateRows[aRow]; ok { + // This row is being edited + if int32slice_contains(er, aCol) { + f.contentBox.Canvas().Brush().SetColor(CO_EDIT_EXPLICIT) + } else { + f.contentBox.Canvas().Brush().SetColor(CO_EDIT_IMPLICIT) } + return + } +} + +// func (f *TMainForm) OnDataCellEditStarting(sender vcl.IObject, aCol, aRow int32, editor **vcl.TWinControl) { +func (f *TMainForm) OnDataCellEditStarting(sender vcl.IObject, aCol, aRow int32, value *string) { + f.isEditing = true +} + +func (f *TMainForm) OnDataCellEdited(sender vcl.IObject) { + // The OnEditingDone event fires whenever the TStringGrid loses focus, even + // if editing was not currently taking place + // To detect real edits, set a flag in the OnSelectEditor event + if !f.isEditing { + return + } + f.isEditing = false + + aRow := f.contentBox.Row() + aCol := f.contentBox.Col() + + // If this is an insert row, no need to patch updateRows + if _, ok := f.insertRows[aRow]; ok { + return // nothing to do } - if isInsertRow { - /*sender.(*vcl.TStringGrid)*/ - f.contentBox.Canvas().Brush().SetColor(colors.ClYellow) + if chk, ok := f.updateRows[aRow]; ok { + if int32slice_contains(chk, aCol) { + // nothing to do + } else { + chk = append(chk, aCol) + f.updateRows[aRow] = chk + } + } else { + f.updateRows[aRow] = []int32{aCol} } + + // If this row was marked for deletion, this new event takes priority + delete(f.deleteRows, aRow) + + // Signal repaint + f.contentBox.InvalidateRow(aRow) } func (f *TMainForm) OnDataInsertClick(sender vcl.IObject) { @@ -556,12 +621,35 @@ func (f *TMainForm) OnDataInsertClick(sender vcl.IObject) { rpos := f.contentBox.RowCount() f.contentBox.SetRowCount(rpos + 1) - f.insertRows = append(f.insertRows, rpos) + f.insertRows[rpos] = struct{}{} // Scroll to bottom f.contentBox.SetTopRow(rpos) } +func (f *TMainForm) OnDataDeleteRowClick(sender vcl.IObject) { + if !f.contentBox.Enabled() { + return // Not an active data view + } + + rpos := f.contentBox.Row() + f.deleteRows[rpos] = struct{}{} + + // If this row was marked for edit, this takes priority + delete(f.updateRows, rpos) + + // Repaint + f.contentBox.InvalidateRow(rpos) +} + +func (f *TMainForm) OnDataCommitClick(sender vcl.IObject) { + if !f.contentBox.Enabled() { + return // Not an active data view + } + + // TODO +} + func (f *TMainForm) OnNavContextClose(sender vcl.IObject) { curItem := f.Buckets.Selected() if curItem == nil { @@ -613,7 +701,10 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) { } // Reset some controls that the render function is expected to populate - f.insertRows = nil + f.insertRows = make(map[int32]struct{}) + f.updateRows = make(map[int32][]int32) + f.deleteRows = make(map[int32]struct{}) + f.isEditing = false f.propertiesBox.Clear() vcl_stringgrid_clear(f.contentBox) diff --git a/util_types.go b/util_types.go index 19379c2..3d5e2f8 100644 --- a/util_types.go +++ b/util_types.go @@ -8,4 +8,14 @@ func box_interface[T any](input []T) []interface{} { ret = append(ret, v) } return ret +} + +func int32slice_contains(haystack []int32, needle int32) bool { + for _, v := range haystack { + if v == needle { + return true + } + } + + return false } \ No newline at end of file diff --git a/util_vcl.go b/util_vcl.go index a556c1a..d650f90 100644 --- a/util_vcl.go +++ b/util_vcl.go @@ -70,6 +70,7 @@ func vcl_stringgrid_clear(d *vcl.TStringGrid) { d.SetRowCount(1) // The 0th row is the column headers d.SetEnabled(false) d.Columns().Clear() + d.Invalidate() } func vcl_stringgrid_columnwidths(d *vcl.TStringGrid) {