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 (
|
const (
|
||||||
APPNAME = "yvbolt"
|
APPNAME = "yvbolt"
|
||||||
HOMEPAGE_URL = "https://code.ivysaur.me/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 {
|
type TMainForm struct {
|
||||||
@ -32,7 +37,10 @@ type TMainForm struct {
|
|||||||
Tabs *vcl.TPageControl
|
Tabs *vcl.TPageControl
|
||||||
propertiesBox *vcl.TMemo
|
propertiesBox *vcl.TMemo
|
||||||
contentBox *vcl.TStringGrid
|
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
|
queryInput *vcl.TMemo
|
||||||
queryResult *vcl.TStringGrid
|
queryResult *vcl.TStringGrid
|
||||||
|
|
||||||
@ -233,14 +241,26 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
|||||||
dataInsertBtn.SetCaption("Insert")
|
dataInsertBtn.SetCaption("Insert")
|
||||||
dataInsertBtn.SetOnClick(f.OnDataInsertClick)
|
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 = vcl.NewStringGrid(dataTab)
|
||||||
f.contentBox.SetParent(dataTab)
|
f.contentBox.SetParent(dataTab)
|
||||||
f.contentBox.BorderSpacing().SetLeft(MY_SPACING)
|
f.contentBox.BorderSpacing().SetLeft(MY_SPACING)
|
||||||
f.contentBox.BorderSpacing().SetRight(MY_SPACING)
|
f.contentBox.BorderSpacing().SetRight(MY_SPACING)
|
||||||
f.contentBox.BorderSpacing().SetBottom(MY_SPACING)
|
f.contentBox.BorderSpacing().SetBottom(MY_SPACING)
|
||||||
f.contentBox.SetAlign(types.AlClient) // fill remaining space
|
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.SetOnPrepareCanvas(f.OnDataPrepareCanvas)
|
||||||
|
f.contentBox.SetOnEditingDone(f.OnDataCellEdited)
|
||||||
|
f.contentBox.SetOnGetEditText(f.OnDataCellEditStarting)
|
||||||
f.contentBox.SetDefaultColWidth(MY_WIDTH)
|
f.contentBox.SetDefaultColWidth(MY_WIDTH)
|
||||||
vcl_stringgrid_clear(f.contentBox)
|
vcl_stringgrid_clear(f.contentBox)
|
||||||
|
|
||||||
@ -510,7 +530,8 @@ func (f *TMainForm) RefreshCurrentItem() {
|
|||||||
|
|
||||||
ndata := (*navData)(item.Data())
|
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) {
|
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) {
|
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
|
if _, ok := f.insertRows[aRow]; ok {
|
||||||
for _, rr := range f.insertRows {
|
f.contentBox.Canvas().Brush().SetColor(CO_INSERT)
|
||||||
if rr == aRow {
|
return
|
||||||
isInsertRow = true
|
}
|
||||||
break
|
|
||||||
|
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 {
|
if chk, ok := f.updateRows[aRow]; ok {
|
||||||
/*sender.(*vcl.TStringGrid)*/
|
if int32slice_contains(chk, aCol) {
|
||||||
f.contentBox.Canvas().Brush().SetColor(colors.ClYellow)
|
// 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) {
|
func (f *TMainForm) OnDataInsertClick(sender vcl.IObject) {
|
||||||
@ -556,12 +621,35 @@ func (f *TMainForm) OnDataInsertClick(sender vcl.IObject) {
|
|||||||
rpos := f.contentBox.RowCount()
|
rpos := f.contentBox.RowCount()
|
||||||
f.contentBox.SetRowCount(rpos + 1)
|
f.contentBox.SetRowCount(rpos + 1)
|
||||||
|
|
||||||
f.insertRows = append(f.insertRows, rpos)
|
f.insertRows[rpos] = struct{}{}
|
||||||
|
|
||||||
// Scroll to bottom
|
// Scroll to bottom
|
||||||
f.contentBox.SetTopRow(rpos)
|
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) {
|
func (f *TMainForm) OnNavContextClose(sender vcl.IObject) {
|
||||||
curItem := f.Buckets.Selected()
|
curItem := f.Buckets.Selected()
|
||||||
if curItem == nil {
|
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
|
// 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()
|
f.propertiesBox.Clear()
|
||||||
vcl_stringgrid_clear(f.contentBox)
|
vcl_stringgrid_clear(f.contentBox)
|
||||||
|
|
||||||
|
@ -8,4 +8,14 @@ func box_interface[T any](input []T) []interface{} {
|
|||||||
ret = append(ret, v)
|
ret = append(ret, v)
|
||||||
}
|
}
|
||||||
return ret
|
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.SetRowCount(1) // The 0th row is the column headers
|
||||||
d.SetEnabled(false)
|
d.SetEnabled(false)
|
||||||
d.Columns().Clear()
|
d.Columns().Clear()
|
||||||
|
d.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func vcl_stringgrid_columnwidths(d *vcl.TStringGrid) {
|
func vcl_stringgrid_columnwidths(d *vcl.TStringGrid) {
|
||||||
|
Loading…
Reference in New Issue
Block a user