diff --git a/db_badger.go b/db_badger.go index b5af2e7..488a0f6 100644 --- a/db_badger.go +++ b/db_badger.go @@ -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") } diff --git a/db_bolt.go b/db_bolt.go index dac984a..12a9364 100644 --- a/db_bolt.go +++ b/db_bolt.go @@ -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") } diff --git a/db_debconf.go b/db_debconf.go index 50a8d77..22813bb 100644 --- a/db_debconf.go +++ b/db_debconf.go @@ -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") } diff --git a/db_none.go b/db_none.go index 50f6426..bf6e8a2 100644 --- a/db_none.go +++ b/db_none.go @@ -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) { diff --git a/db_pebble.go b/db_pebble.go index 10ab2a6..601d93c 100644 --- a/db_pebble.go +++ b/db_pebble.go @@ -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") } diff --git a/db_redis.go b/db_redis.go index 6af61ea..c8c5ca3 100644 --- a/db_redis.go +++ b/db_redis.go @@ -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("<<>>") + f.contentBox.SetCells(2, rpos, "<<>>") } } @@ -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 } diff --git a/db_sqlite.go b/db_sqlite.go index da2fb48..8e77e4a 100644 --- a/db_sqlite.go +++ b/db_sqlite.go @@ -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()) diff --git a/loadedDatabase.go b/loadedDatabase.go index 6828309..c94e3cc 100644 --- a/loadedDatabase.go +++ b/loadedDatabase.go @@ -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) diff --git a/main.go b/main.go index bc95f96..5bd19af 100644 --- a/main.go +++ b/main.go @@ -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 diff --git a/util_vcl.go b/util_vcl.go index 4b94005..c4d40a7 100644 --- a/util_vcl.go +++ b/util_vcl.go @@ -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() +}