gui: change tracking for insert, edit, delete actions
This commit is contained in:
parent
0d3b90b879
commit
8af27f8834
121
main.go
121
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)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user