1 Commits

Author SHA1 Message Date
97c58d7fc4 fltk: initial commit 2024-07-18 17:01:02 +12:00
15 changed files with 132 additions and 217 deletions

View File

@@ -1,11 +1,13 @@
# yvbolt # yvbolt
A graphical interface for multiple databases using [GoVCL](https://z-kit.cc/en/). A graphical browser for multiple databases using [GoVCL](https://z-kit.cc/en/).
This is an experimental application and you should generally prefer to use [qbolt](https://code.ivysaur.me/qbolt).
## Features ## Features
- Native desktop application, running on Linux, Windows, and macOS - Native desktop application, running on Linux, Windows, and macOS
- Connect to multiple databases at once - Connect to multiple databases
- Browse table/bucket content - Browse table/bucket content
- Use context menu to perform special table/bucket actions - Use context menu to perform special table/bucket actions
- Run custom SQL queries - Run custom SQL queries
@@ -15,16 +17,12 @@ A graphical interface for multiple databases using [GoVCL](https://z-kit.cc/en/)
- Badger v4 - Badger v4
- Bolt - Bolt
- Recursive bucket support - Recursive bucket support
- Option to open as readonly for shared access - Option to open as readonly
- Supports editing
- See also [qbolt](https://code.ivysaur.me/qbolt) for more/different functionality
- Debconf - Debconf
- Pebble - Pebble
- Redis - Redis
- SQLite - SQLite
- Drivers: mattn (CGo), modernc.org (no cgo), experimental command-line driver - Drivers: mattn (CGo), modernc.org (no cgo), experimental command-line driver
- Supports editing
- Integrated vacuum and export commands
## License ## License
@@ -34,7 +32,7 @@ This project redistributes images from the famfamfam/silk icon set under the [CC
This project includes trademarked logo images for each supported database type. This project includes trademarked logo images for each supported database type.
## Compiling ## Usage
1. `CGO_ENABLED=1 go build` 1. `CGO_ENABLED=1 go build`
2. [Download liblcl](https://github.com/ying32/govcl/releases/download/v2.2.3/liblcl-2.2.3.zip) for your platform, or [compile it yourself](https://github.com/ying32/liblcl) (tested with v2.2.3) 2. [Download liblcl](https://github.com/ying32/govcl/releases/download/v2.2.3/liblcl-2.2.3.zip) for your platform, or [compile it yourself](https://github.com/ying32/liblcl) (tested with v2.2.3)
@@ -43,16 +41,6 @@ This project includes trademarked logo images for each supported database type.
## Changelog ## Changelog
2024-07-18 v0.7.0
- SQLite, Bolt: Initial support for editing data (insert, per-cell update, delete)
- SQLite: Add context menu actions for compact (vacuum), export, and drop table
- App: New grid widget
- App: Add refresh button
- App: Bigger window size, use icons for toolbars, better UI colours for Windows
- App: Prevent submitting blank queries to database
- Refactor database interface and error handling
2024-06-30 v0.6.0 2024-06-30 v0.6.0
- Debconf: Add as supported database - Debconf: Add as supported database

5
TODO
View File

@@ -1,3 +1,4 @@
- Cast DB to wider interface to check feature support
- Syntax highlighting in editor - Syntax highlighting in editor
- Mutation - Mutation
- Get real primary key for mutation instead of string approximation - Get real primary key for mutation instead of string approximation
@@ -5,6 +6,7 @@
- Pebble: Support insert/update/delete - Pebble: Support insert/update/delete
- Debconf: Support insert/update/delete - Debconf: Support insert/update/delete
- Redis: Support insert/update/delete - Redis: Support insert/update/delete
- SQLite: Support insert/update/delete
- Binary data viewer - Binary data viewer
- Detect jpg/png and show as image - Detect jpg/png and show as image
- More DB types - More DB types
@@ -36,6 +38,3 @@
- Context/interrupt slow queries - Context/interrupt slow queries
- Faster virtual rendering - Faster virtual rendering
- https://github.com/ying32/govcl/blob/master/samples/listviewvirtualdata/main.go - https://github.com/ying32/govcl/blob/master/samples/listviewvirtualdata/main.go
- Query history
- Test suite
- `CREATE TABLE foo (id integer primary key, aaa text not null, bbb text not null);`

View File

@@ -83,6 +83,10 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error
return nil return nil
} }
func (n *badgerLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (ld *badgerLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *badgerLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the Badger implementation, there is only one child: "Data" // In the Badger implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {
@@ -98,6 +102,10 @@ func (ld *badgerLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
return nil, nil // No special actions are supported return nil, nil // No special actions are supported
} }
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {
return ErrNotSupported
}
func (ld *badgerLoadedDatabase) Close() { func (ld *badgerLoadedDatabase) Close() {
_ = ld.db.Close() _ = ld.db.Close()
ld.arena = nil ld.arena = nil

View File

@@ -192,6 +192,10 @@ func (ld *boltLoadedDatabase) DeleteBucket(sender vcl.IComponent, ndata *navData
return nil return nil
} }
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {
return ErrNotSupported
}
func (ld *boltLoadedDatabase) Close() { func (ld *boltLoadedDatabase) Close() {
_ = ld.db.Close() _ = ld.db.Close()
ld.arena = nil ld.arena = nil

View File

@@ -70,6 +70,10 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) erro
return nil return nil
} }
func (n *debconfLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (ld *debconfLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *debconfLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the debconf implementation, there is only one child: "Data" // In the debconf implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {
@@ -85,6 +89,10 @@ func (ld *debconfLoadedDatabase) NavContext(ndata *navData) ([]contextAction, er
return nil, nil // No special actions are supported return nil, nil // No special actions are supported
} }
func (ld *debconfLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {
return ErrNotSupported
}
func (ld *debconfLoadedDatabase) Close() { func (ld *debconfLoadedDatabase) Close() {
ld.arena = nil ld.arena = nil
} }

View File

@@ -23,6 +23,14 @@ func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
return nil return nil
} }
func (n *noLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {
return ErrNotSupported
}
func (n *noLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (n *noLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
return []string{}, nil return []string{}, nil
} }

View File

@@ -74,6 +74,10 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error
return nil return nil
} }
func (n *pebbleLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (ld *pebbleLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *pebbleLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the pebble implementation, there is only one child: "Data" // In the pebble implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {
@@ -89,6 +93,10 @@ func (ld *pebbleLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
return nil, nil // No special actions are supported return nil, nil // No special actions are supported
} }
func (ld *pebbleLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {
return ErrNotSupported
}
func (ld *pebbleLoadedDatabase) Close() { func (ld *pebbleLoadedDatabase) Close() {
_ = ld.db.Close() _ = ld.db.Close()
ld.arena = nil ld.arena = nil

View File

@@ -132,6 +132,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error
} }
} }
func (n *redisLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (ld *redisLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *redisLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// ctx := context.Background() // ctx := context.Background()

View File

@@ -1,12 +1,10 @@
package main package main
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"unsafe" "unsafe"
_ "yvbolt/sqliteclidriver" _ "yvbolt/sqliteclidriver"
@@ -102,6 +100,10 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error
} }
func (n *sqliteLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return ErrNotSupported
}
func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) ([]string, error) { func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) ([]string, error) {
colr, err := ld.db.Query(`SELECT name FROM pragma_table_info( ? )`, tableName) colr, err := ld.db.Query(`SELECT name FROM pragma_table_info( ? )`, tableName)
if err != nil { if err != nil {
@@ -189,127 +191,6 @@ func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringG
return nil return nil
} }
func (n *sqliteLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) (retErr error) {
if len(ndata.bucketPath) != 2 {
return errors.New("invalid selection")
}
tableName := ndata.bucketPath[1]
// We have rendered row IDs, need to convert back to an SQLite primary key
// TODO stash the real key inside f.contentBox.Objects()
// FIXME breaks if you try and edit the primary key(!)
ctx := context.Background()
tx, err := n.db.BeginTx(ctx, nil)
if err != nil {
return err
}
var commitOK bool = false
defer func() {
if !commitOK {
err := tx.Rollback()
if err != nil {
retErr = err
}
}
}()
// Data grid properties
var columnNames []string
for i := int32(0); i < f.contentBox.ColCount(); i++ {
columnNames = append(columnNames, f.contentBox.Columns().Items(i).Title().Caption())
}
// Query sqlite table metadata to determine which of these is the PRIMARY KEY
var primaryColumnName string
err = tx.QueryRowContext(ctx, `SELECT l.name FROM pragma_table_info(?) as l WHERE l.pk = 1;`, tableName).Scan(&primaryColumnName)
if err != nil {
return fmt.Errorf("Finding primary key for update: %w", err)
}
// Convert it to an index
var primaryColumnIdx int = -1
for i := 0; i < len(columnNames); i++ {
if columnNames[i] == primaryColumnName {
primaryColumnIdx = i
break
}
}
if primaryColumnIdx == -1 {
return fmt.Errorf("Primary key %q missing from available columns", primaryColumnName)
}
// SQLite can only LIMIT 1 on update/delete if it was compiled with
// SQLITE_ENABLE_UPDATE_DELETE_LIMIT, which isn't the case for the mattn
// cgo library
// Skip that, and just rely on primary key uniqueness
// Edit
for rowid, editcells := range f.updateRows {
stmt := `UPDATE "` + tableName + `" SET `
params := []interface{}{} // FIXME reinstate types for the driver (although SQLite doesn't mind)
for ct, cell := range editcells {
if ct > 0 {
stmt += `, `
}
stmt += `"` + columnNames[cell] + `" = ?`
params = append(params, f.contentBox.Cells(cell, rowid))
}
stmt += ` WHERE "` + primaryColumnName + `" = ?`
pkVal := f.contentBox.Cells(int32(primaryColumnIdx), rowid)
params = append(params, pkVal)
_, err = tx.ExecContext(ctx, stmt, params...)
if err != nil {
return fmt.Errorf("Updating row %q: %w", pkVal, err)
}
}
// Delete by key (affects rowids after re-render)
for rowid, _ := range f.deleteRows {
pkVal := f.contentBox.Cells(int32(primaryColumnIdx), rowid)
stmt := `DELETE FROM "` + tableName + `" WHERE "` + primaryColumnName + `" = ?`
_, err = tx.ExecContext(ctx, stmt, pkVal)
if err != nil {
return fmt.Errorf("Deleting row %q: %w", pkVal, err)
}
}
// Insert all new entries
for rowid, _ := range f.insertRows {
stmt := `INSERT INTO "` + tableName + `" (` + strings.Join(columnNames, `, `) + `) VALUES (`
params := []interface{}{} // FIXME reinstate types for the driver (although SQLite doesn't mind)
for colid := 0; colid < len(columnNames); colid++ {
if colid > 0 {
stmt += `, `
}
stmt += "?"
params = append(params, f.contentBox.Cells(int32(colid), rowid))
}
stmt += `)`
_, err = tx.ExecContext(ctx, stmt, params...)
if err != nil {
return fmt.Errorf("Inserting row: %w", err)
}
}
err = tx.Commit()
if err != nil {
return err
}
commitOK = true // no need for rollback
return nil
}
func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {

47
fltktest/main.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"github.com/pwiecz/go-fltk"
)
const WNDCOLOR = 0xf0f0f000
const FONTSIZE = 12
func main() {
fltk.SetScheme("gtk+")
// ourFont := fltk.FREE_FONT + 1
// fltk.SetFont(ourFont, "Papyrus")
fltk.SetColor(fltk.BACKGROUND_COLOR, 0xf0, 0xf0, 0xf0)
fltk.SetColor(fltk.BACKGROUND2_COLOR, 0xff, 0x00, 0x00)
win := fltk.NewWindow(400, 300)
win.SetLabel("Main Window")
// win.SetLabelSize(FONTSIZE)
// win.SetColor(WNDCOLOR)
mnu := fltk.NewMenuBar(0, 0, win.W(), 25)
mnu.Add("File/Quit", func() {})
mnu.SetLabelSize(FONTSIZE)
mnu.SetBox(fltk.FLAT_BOX)
win.Add(mnu)
btn := fltk.NewButton(160, 200, 80, 30, "Click")
// btn.SetLabelFont(ourFont)
btn.SetLabelSize(FONTSIZE)
btn.SetCallback(func() {
btn.SetLabel("Clicked")
})
// win.End()
// win.Resizable()
win.SetResizeHandler(func() {
mnu.Resize(0, 0, win.W(), 25)
})
win.Show()
fltk.Run()
}

1
go.mod
View File

@@ -45,6 +45,7 @@ require (
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/pwiecz/go-fltk v0.0.0-20240525043121-5313f8a5a643 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
go.opencensus.io v0.22.5 // indirect go.opencensus.io v0.22.5 // indirect

2
go.sum
View File

@@ -239,6 +239,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/pwiecz/go-fltk v0.0.0-20240525043121-5313f8a5a643 h1:t1fpLVVcboeJvXMiwMCpF1MBiQGg7VyTBqjLEEe+qXM=
github.com/pwiecz/go-fltk v0.0.0-20240525043121-5313f8a5a643/go.mod h1:uMK5daOr9p+ba2BPs5QadbfaqqrHR5TGj13yWGsAsmw=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=

View File

@@ -7,6 +7,7 @@ import (
) )
var ErrNavNotExist error = errors.New("The selected item no longer exists") var ErrNavNotExist error = errors.New("The selected item no longer exists")
var ErrNotSupported error = errors.New("Unsupported action for this database type")
type contextAction struct { type contextAction struct {
Name string Name string
@@ -19,20 +20,14 @@ type loadedDatabase interface {
DriverName() string DriverName() string
RootElement() *vcl.TTreeNode RootElement() *vcl.TTreeNode
RenderForNav(f *TMainForm, ndata *navData) error RenderForNav(f *TMainForm, ndata *navData) error
ApplyChanges(f *TMainForm, ndata *navData) error
ExecQuery(query string, resultArea *vcl.TStringGrid) error
NavChildren(ndata *navData) ([]string, error) NavChildren(ndata *navData) ([]string, error)
NavContext(ndata *navData) ([]contextAction, error) NavContext(ndata *navData) ([]contextAction, error)
Keepalive(ndata *navData) Keepalive(ndata *navData)
Close() Close()
} }
type queryableLoadedDatabase interface {
ExecQuery(query string, resultArea *vcl.TStringGrid) error
}
type editableLoadedDatabase interface {
ApplyChanges(f *TMainForm, ndata *navData) error
}
// navData is the .Data() pointer for each TTreeNode in the left-hand tree. // navData is the .Data() pointer for each TTreeNode in the left-hand tree.
type navData struct { type navData struct {
ld loadedDatabase ld loadedDatabase

86
main.go
View File

@@ -37,14 +37,10 @@ type TMainForm struct {
Tabs *vcl.TPageControl Tabs *vcl.TPageControl
propertiesBox *vcl.TMemo propertiesBox *vcl.TMemo
contentBox *vcl.TStringGrid contentBox *vcl.TStringGrid
dataInsertBtn *vcl.TToolButton
dataDelRowBtn *vcl.TToolButton
dataCommitBtn *vcl.TToolButton
isEditing bool isEditing bool
insertRows map[int32]struct{} // Rows in the StringGrid that are to-be-inserted insertRows map[int32]struct{} // Rows in the StringGrid that are to-be-inserted
deleteRows map[int32]struct{} deleteRows map[int32]struct{}
updateRows map[int32][]int32 // Row->cells that are to-be-updated updateRows map[int32][]int32 // Row->cells that are to-be-updated
queryExecBtn *vcl.TToolButton
queryInput *vcl.TRichEdit queryInput *vcl.TRichEdit
queryResult *vcl.TStringGrid queryResult *vcl.TStringGrid
@@ -240,26 +236,26 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
dataRefreshBtn.SetImageIndex(imgArrowRefresh) dataRefreshBtn.SetImageIndex(imgArrowRefresh)
dataRefreshBtn.SetOnClick(func(sender vcl.IObject) { f.RefreshCurrentItem() }) dataRefreshBtn.SetOnClick(func(sender vcl.IObject) { f.RefreshCurrentItem() })
f.dataInsertBtn = vcl.NewToolButton(dataButtonBar) dataInsertBtn := vcl.NewToolButton(dataButtonBar)
f.dataInsertBtn.SetParent(dataButtonBar) dataInsertBtn.SetParent(dataButtonBar)
f.dataInsertBtn.SetImageIndex(imgAdd) dataInsertBtn.SetImageIndex(imgAdd)
f.dataInsertBtn.SetHint("Insert") dataInsertBtn.SetHint("Insert")
f.dataInsertBtn.SetShowHint(true) dataInsertBtn.SetShowHint(true)
f.dataInsertBtn.SetOnClick(f.OnDataInsertClick) dataInsertBtn.SetOnClick(f.OnDataInsertClick)
f.dataDelRowBtn = vcl.NewToolButton(dataButtonBar) dataDelRowBtn := vcl.NewToolButton(dataButtonBar)
f.dataDelRowBtn.SetParent(dataButtonBar) dataDelRowBtn.SetParent(dataButtonBar)
f.dataDelRowBtn.SetImageIndex(imgDelete) dataDelRowBtn.SetImageIndex(imgDelete)
f.dataDelRowBtn.SetHint("Delete Row") dataDelRowBtn.SetHint("Delete Row")
f.dataDelRowBtn.SetShowHint(true) dataDelRowBtn.SetShowHint(true)
f.dataDelRowBtn.SetOnClick(f.OnDataDeleteRowClick) dataDelRowBtn.SetOnClick(f.OnDataDeleteRowClick)
f.dataCommitBtn = vcl.NewToolButton(dataButtonBar) dataCommitBtn := vcl.NewToolButton(dataButtonBar)
f.dataCommitBtn.SetParent(dataButtonBar) dataCommitBtn.SetParent(dataButtonBar)
f.dataCommitBtn.SetImageIndex(imgPencilGo) dataCommitBtn.SetImageIndex(imgPencilGo)
f.dataCommitBtn.SetHint("Commit") dataCommitBtn.SetHint("Commit")
f.dataCommitBtn.SetShowHint(true) dataCommitBtn.SetShowHint(true)
f.dataCommitBtn.SetOnClick(f.OnDataCommitClick) dataCommitBtn.SetOnClick(f.OnDataCommitClick)
f.contentBox = vcl.NewStringGrid(dataTab) f.contentBox = vcl.NewStringGrid(dataTab)
f.contentBox.SetParent(dataTab) f.contentBox.SetParent(dataTab)
@@ -290,12 +286,12 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
queryButtonBar.SetImages(f.ImageList) queryButtonBar.SetImages(f.ImageList)
queryButtonBar.SetShowCaptions(true) queryButtonBar.SetShowCaptions(true)
f.queryExecBtn = vcl.NewToolButton(queryButtonBar) queryExecBtn := vcl.NewToolButton(queryButtonBar)
f.queryExecBtn.SetParent(queryButtonBar) queryExecBtn.SetParent(queryButtonBar)
f.queryExecBtn.SetHint("Execute") queryExecBtn.SetHint("Execute")
f.queryExecBtn.SetShowHint(true) queryExecBtn.SetShowHint(true)
f.queryExecBtn.SetImageIndex(imgResultsetNext) queryExecBtn.SetImageIndex(imgResultsetNext)
f.queryExecBtn.SetOnClick(f.OnQueryExecute) queryExecBtn.SetOnClick(f.OnQueryExecute)
f.queryInput = vcl.NewRichEdit(queryTab) f.queryInput = vcl.NewRichEdit(queryTab)
f.queryInput.SetParent(queryTab) f.queryInput.SetParent(queryTab)
@@ -672,14 +668,7 @@ func (f *TMainForm) OnDataCommitClick(sender vcl.IObject) {
scrollPos := f.contentBox.TopRow() scrollPos := f.contentBox.TopRow()
ndata := (*navData)(node.Data()) ndata := (*navData)(node.Data())
err := ndata.ld.ApplyChanges(f, ndata)
editableLd, ok := ndata.ld.(editableLoadedDatabase)
if !ok {
vcl.ShowMessage("Unsupported action for this database")
return
}
err := editableLd.ApplyChanges(f, ndata)
if err != nil { if err != nil {
vcl.ShowMessage(err.Error()) vcl.ShowMessage(err.Error())
} }
@@ -760,10 +749,6 @@ func (f *TMainForm) OnQueryExecute(sender vcl.IObject) {
queryString = f.queryInput.SelText() // Just the selected text queryString = f.queryInput.SelText() // Just the selected text
} }
if strings.TrimSpace(queryString) == "" {
return // prevent blank query
}
// Execute // Execute
node := f.Buckets.Selected() node := f.Buckets.Selected()
if node == nil { if node == nil {
@@ -772,14 +757,7 @@ func (f *TMainForm) OnQueryExecute(sender vcl.IObject) {
} }
ndata := (*navData)(node.Data()) ndata := (*navData)(node.Data())
err := ndata.ld.ExecQuery(queryString, f.queryResult)
queryableLd, ok := ndata.ld.(queryableLoadedDatabase)
if !ok {
vcl.ShowMessage("Unsupported action for this database")
return
}
err := queryableLd.ExecQuery(queryString, f.queryResult)
if err != nil { if err != nil {
vcl.ShowMessage(err.Error()) vcl.ShowMessage(err.Error())
return return
@@ -812,18 +790,6 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
f.contentBox.SetEnabled(false) f.contentBox.SetEnabled(false)
} }
// Toggle the Edit functionality
_, ok := ld.(editableLoadedDatabase)
f.dataCommitBtn.SetEnabled(ok)
f.dataDelRowBtn.SetEnabled(ok)
f.dataInsertBtn.SetEnabled(ok)
// Toggle the Query functionality
_, ok = ld.(queryableLoadedDatabase)
f.queryInput.SetEnabled(ok)
f.queryResult.SetEnabled(ok)
f.queryExecBtn.SetEnabled(ok)
// We're in charge of common status bar text updates // We're in charge of common status bar text updates
f.StatusBar.SetSimpleText(ld.DisplayName() + " | " + ld.DriverName()) f.StatusBar.SetSimpleText(ld.DisplayName() + " | " + ld.DriverName())
} }

View File

@@ -59,11 +59,7 @@ func vcl_default_tab_background() types.TColor {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Assuming that uxtheme is loaded // Assuming that uxtheme is loaded
// @ref https://stackoverflow.com/a/20332712 // @ref https://stackoverflow.com/a/20332712
// return colors.ClBtnHighlight return colors.ClBtnHighlight
// None of the colors.** constants seem to be quite right on a test
// Windows 11 machine - should be #f9f9f9
return 0x00f9f9f9
} else { } else {
return colors.ClBtnFace // 0x00f0f0f0 return colors.ClBtnFace // 0x00f0f0f0
} }