gui: convert data tables from TListView to TStringGrid

This commit is contained in:
mappu 2024-07-05 19:21:08 +12:00
parent cecfc338d4
commit d7e3363173
10 changed files with 78 additions and 105 deletions

View File

@ -42,25 +42,17 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText(content)
// Load data
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// Badger always uses Key + Value as the columns
f.contentBox.Columns().Clear()
colKey := f.contentBox.Columns().Add()
colKey.SetCaption("Key")
colKey.Title().SetCaption("Key")
colKey.SetWidth(MY_WIDTH)
colKey.SetAlignment(types.TaLeftJustify)
colVal := f.contentBox.Columns().Add()
colVal.SetCaption("Value")
colVal.Title().SetCaption("Value")
err := ld.db.View(func(txn *badger.Txn) error {
// Valid
f.contentBox.Clear()
// Create iterator
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 64
@ -71,9 +63,10 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
item := it.Item()
k := item.Key()
err := item.Value(func(v []byte) error {
dataEntry := f.contentBox.Items().Add()
dataEntry.SetCaption(formatUtf8(k))
dataEntry.SubItems().Add(formatUtf8(v))
rpos := f.contentBox.RowCount()
f.contentBox.SetRowCount(rpos + 1)
f.contentBox.SetCells(0, rpos, formatUtf8(k))
f.contentBox.SetCells(1, rpos, formatUtf8(v))
return nil
})
@ -107,7 +100,7 @@ func (ld *badgerLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
return nil, nil // No special actions are supported
}
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
vcl.ShowMessage("Badger doesn't support querying")
}

View File

@ -48,19 +48,15 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText(content)
// Load data
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// Bolt always uses Key + Value as the columns
f.contentBox.Columns().Clear()
colKey := f.contentBox.Columns().Add()
colKey.SetCaption("Key")
colKey.Title().SetCaption("Key")
colKey.SetWidth(MY_WIDTH)
colKey.SetAlignment(types.TaLeftJustify)
colVal := f.contentBox.Columns().Add()
colVal.SetCaption("Value")
colVal.Title().SetCaption("Value")
err := ld.db.View(func(tx *bbolt.Tx) error {
b := boltTargetBucket(tx, ndata.bucketPath)
@ -70,13 +66,12 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
}
// Valid
f.contentBox.Clear()
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
dataEntry := f.contentBox.Items().Add()
dataEntry.SetCaption(formatUtf8(k))
dataEntry.SubItems().Add(formatUtf8(v))
rpos := f.contentBox.RowCount()
f.contentBox.SetRowCount(rpos + 1)
f.contentBox.SetCells(0, rpos, formatUtf8(k))
f.contentBox.SetCells(1, rpos, formatUtf8(v))
}
return nil
})
@ -142,7 +137,7 @@ func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) {
}
}
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
vcl.ShowMessage("Bolt doesn't support querying")
}

View File

@ -44,32 +44,25 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Load data
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// debconf always uses Key + Value as the columns
indexes := make(map[string]int)
f.contentBox.Columns().Clear()
for i, cname := range ld.db.AllColumnNames {
indexes[cname] = i
col := f.contentBox.Columns().Add()
col.SetCaption(cname)
col.Title().SetCaption(cname)
col.SetWidth(MY_WIDTH)
}
for _, entry := range ld.db.Entries {
cell := f.contentBox.Items().Add()
cell.SetCaption(entry.Name)
rpos := f.contentBox.RowCount()
f.contentBox.SetRowCount(rpos + 1)
f.contentBox.SetCells(0, rpos, entry.Name)
texts := make([]string, len(ld.db.AllColumnNames))
for _, proppair := range entry.Properties {
texts[indexes[proppair[0]]-1 /* compensate for 'Name' always being first */] = proppair[1]
f.contentBox.SetCells(int32(indexes[proppair[0]]), rpos, proppair[1])
}
cell.SubItems().AddStrings2(texts)
}
// Valid
@ -91,7 +84,7 @@ func (ld *debconfLoadedDatabase) NavContext(ndata *navData) ([]contextAction, er
return nil, nil // No special actions are supported
}
func (ld *debconfLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *debconfLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
vcl.ShowMessage("debconf doesn't support querying")
}

View File

@ -20,11 +20,9 @@ func (n *noLoadedDatabase) RootElement() *vcl.TTreeNode {
func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText("Open a database to get started...")
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
}
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
}
func (n *noLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {

View File

@ -46,19 +46,14 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText(content)
// Load data
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// pebble always uses Key + Value as the columns
f.contentBox.Columns().Clear()
colKey := f.contentBox.Columns().Add()
colKey.SetCaption("Key")
colKey.Title().SetCaption("Key")
colKey.SetWidth(MY_WIDTH)
colKey.SetAlignment(types.TaLeftJustify)
colVal := f.contentBox.Columns().Add()
colVal.SetCaption("Value")
colVal.Title().SetCaption("Value")
itr := ld.db.NewIterWithContext(ctx, nil)
defer itr.Close()
@ -71,9 +66,10 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
return
}
dataEntry := f.contentBox.Items().Add()
dataEntry.SetCaption(formatUtf8(k))
dataEntry.SubItems().Add(formatUtf8(v))
rpos := f.contentBox.RowCount()
f.contentBox.SetRowCount(rpos + 1)
f.contentBox.SetCells(0, rpos, formatUtf8(k))
f.contentBox.SetCells(0, rpos, formatUtf8(v))
}
// Valid
@ -95,7 +91,7 @@ func (ld *pebbleLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
return nil, nil // No special actions are supported
}
func (ld *pebbleLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *pebbleLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
vcl.ShowMessage("pebble doesn't support querying")
}

View File

@ -54,9 +54,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText(infostr)
// Disable data tab
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// Leave data tab disabled (default behaviour)
} else if len(ndata.bucketPath) == 1 {
// One selected database
@ -77,18 +75,14 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Redis always uses Key + Value as the columns
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
f.contentBox.Columns().Clear()
colKey := f.contentBox.Columns().Add()
colKey.SetCaption("Key")
colKey.Title().SetCaption("Key")
colKey.SetWidth(MY_WIDTH)
colKey.SetAlignment(types.TaLeftJustify)
colType := f.contentBox.Columns().Add()
colType.SetCaption("Type")
colType.Title().SetCaption("Type")
colVal := f.contentBox.Columns().Add()
colVal.SetCaption("Value")
colVal.Title().SetCaption("Value")
for _, key := range allKeys {
typeName, err := ld.db.Type(ctx, key).Result()
@ -97,9 +91,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
return
}
dataEntry := f.contentBox.Items().Add()
dataEntry.SetCaption(key) // formatUtf8
dataEntry.SubItems().Add(typeName)
rpos := f.contentBox.RowCount()
f.contentBox.SetRowCount(rpos + 1)
f.contentBox.SetCells(0, rpos, formatUtf8([]byte(key)))
f.contentBox.SetCells(1, rpos, typeName)
switch typeName {
case "string":
@ -108,7 +103,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err))
return
}
dataEntry.SubItems().Add(val)
f.contentBox.SetCells(2, rpos, val)
case "hash":
val, err := ld.db.HGetAll(ctx, key).Result()
@ -117,7 +112,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
return
}
// It's a map[string]string
dataEntry.SubItems().Add(formatAny(val))
f.contentBox.SetCells(2, rpos, formatAny(val))
case "lists":
fallthrough
@ -129,7 +124,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
fallthrough
default:
dataEntry.SubItems().Add("<<<other object type>>>")
f.contentBox.SetCells(2, rpos, "<<<other object type>>>")
}
}
@ -170,7 +165,7 @@ func (ld *redisLoadedDatabase) NavContext(ndata *navData) ([]contextAction, erro
return nil, nil // No special actions are supported
}
func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
ctx := context.Background()
// Need to parse the query into separate string+args fields for the protocol
@ -193,21 +188,24 @@ func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView
resultArea.Columns().Clear()
colVal := resultArea.Columns().Add()
colVal.SetCaption("Result")
colVal.Title().SetCaption("Result")
// The result is probably a single value or a string slice
switch ret := ret.(type) {
case []string:
// Multiple values
for _, single := range ret {
cell := resultArea.Items().Add()
cell.SetCaption(single) // formatUtf8
rpos := resultArea.RowCount()
resultArea.SetRowCount(rpos + 1)
resultArea.SetCells(0, rpos, formatUtf8([]byte(single)))
}
default:
// Single value
dataEntry := resultArea.Items().Add()
dataEntry.SetCaption(formatAny(ret)) // formatUtf8
rpos := resultArea.RowCount()
resultArea.SetRowCount(rpos + 1)
resultArea.SetCells(0, rpos, formatAny(ret)) // formatUtf8
}

View File

@ -42,14 +42,10 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
if len(ndata.bucketPath) == 0 {
// Top-level
f.propertiesBox.SetText("Please select...")
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
} else if len(ndata.bucketPath) == 1 {
// Category (tables, ...)
f.propertiesBox.SetText("Please select...")
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
} else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption {
// Render for specific table
@ -66,9 +62,6 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Display table properties
f.propertiesBox.SetText(fmt.Sprintf("Selected table %q\n\nSchema:\n\n%s", tableName, schemaStmt))
f.contentBox.SetEnabled(false)
f.contentBox.Clear()
// Load column details
// Use SELECT form instead of common PRAGMA table_info so we can just get names
// We could possibly get this from the main data select, but this will
@ -129,17 +122,17 @@ func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) (
return ret, nil
}
func populateColumns(names []string, dest *vcl.TListView) {
func populateColumns(names []string, dest *vcl.TStringGrid) {
dest.Columns().Clear()
for _, columnName := range names {
col := dest.Columns().Add()
col.SetCaption(columnName)
col.Title().SetCaption(columnName)
col.SetWidth(MY_WIDTH)
col.SetAlignment(types.TaLeftJustify)
}
}
func populateRows(rr *sql.Rows, dest *vcl.TListView) {
func populateRows(rr *sql.Rows, dest *vcl.TStringGrid) {
numColumns := int(dest.Columns().Count())
@ -156,10 +149,10 @@ func populateRows(rr *sql.Rows, dest *vcl.TListView) {
return
}
dataEntry := dest.Items().Add()
dataEntry.SetCaption(formatAny(fields[0]))
for i := 1; i < len(fields); i += 1 {
dataEntry.SubItems().Add(formatAny(fields[i]))
rpos := dest.RowCount()
dest.SetRowCount(rpos + 1)
for i := 0; i < len(fields); i += 1 {
dest.SetCells(int32(i), rpos, formatAny(fields[i]))
}
}
@ -169,7 +162,7 @@ func populateRows(rr *sql.Rows, dest *vcl.TListView) {
}
}
func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
rr, err := ld.db.Query(query)
if err != nil {
vcl.ShowMessage(err.Error())

View File

@ -19,7 +19,7 @@ type loadedDatabase interface {
DriverName() string
RootElement() *vcl.TTreeNode
RenderForNav(f *TMainForm, ndata *navData)
ExecQuery(query string, resultArea *vcl.TListView)
ExecQuery(query string, resultArea *vcl.TStringGrid)
NavChildren(ndata *navData) ([]string, error)
NavContext(ndata *navData) ([]contextAction, error)
Keepalive(ndata *navData)

28
main.go
View File

@ -30,9 +30,9 @@ type TMainForm struct {
Buckets *vcl.TTreeView
Tabs *vcl.TPageControl
propertiesBox *vcl.TMemo
contentBox *vcl.TListView
contentBox *vcl.TStringGrid
queryInput *vcl.TMemo
queryResult *vcl.TListView
queryResult *vcl.TStringGrid
none *noLoadedDatabase
dbs []loadedDatabase
@ -209,14 +209,12 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
dataTab.SetCaption("Data")
dataTab.SetImageIndex(imgTable)
f.contentBox = vcl.NewListView(dataTab)
f.contentBox = vcl.NewStringGrid(dataTab)
f.contentBox.SetParent(dataTab)
f.contentBox.BorderSpacing().SetAround(MY_SPACING)
f.contentBox.SetAlign(types.AlClient) // fill remaining space
f.contentBox.SetViewStyle(types.VsReport) // "Report style" i.e. has columns
f.contentBox.SetAutoWidthLastColumn(true)
f.contentBox.SetReadOnly(true)
f.contentBox.Columns().Clear()
f.contentBox.SetAlign(types.AlClient) // fill remaining space
f.contentBox.SetOptions(f.contentBox.Options().Include(types.GoThumbTracking))
vcl_stringgrid_clear(f.contentBox)
queryTab := vcl.NewTabSheet(f.Tabs)
queryTab.SetParent(f.Tabs)
@ -260,16 +258,14 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
vsplit.SetAlign(types.AlTop)
vsplit.SetTop(2)
f.queryResult = vcl.NewListView(queryTab)
f.queryResult = vcl.NewStringGrid(queryTab)
f.queryResult.SetParent(queryTab)
f.queryResult.SetAlign(types.AlClient) // fill remaining space
f.queryResult.SetViewStyle(types.VsReport) // "Report style" i.e. has columns
f.queryResult.SetAutoWidthLastColumn(true)
f.queryResult.SetReadOnly(true)
f.queryResult.Columns().Clear()
f.queryResult.SetAlign(types.AlClient) // fill remaining space
f.queryResult.BorderSpacing().SetLeft(MY_SPACING)
f.queryResult.BorderSpacing().SetRight(MY_SPACING)
f.queryResult.BorderSpacing().SetBottom(MY_SPACING)
f.queryResult.SetOptions(f.queryResult.Options().Include(types.GoThumbTracking))
vcl_stringgrid_clear(f.queryResult)
f.none = &noLoadedDatabase{}
f.OnNavChange(f, nil) // calls f.none.RenderForNav and sets up status bar content
@ -543,6 +539,10 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
ld = ndata.ld
}
// Reset some controls that the render function is expected to populate
f.propertiesBox.Clear()
vcl_stringgrid_clear(f.contentBox)
ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
// We're in charge of common status bar text updates

View File

@ -62,3 +62,10 @@ func vcl_default_tab_background() types.TColor {
return colors.ClBtnFace // 0x00f0f0f0
}
}
func vcl_stringgrid_clear(d *vcl.TStringGrid) {
d.SetFixedCols(0) // No left-hand cols
d.SetRowCount(1) // The 0th row is the column headers
d.SetEnabled(false)
d.Columns().Clear()
}