2024-06-03 04:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-06-08 01:34:33 +00:00
|
|
|
"os"
|
2024-06-03 04:49:04 +00:00
|
|
|
"strings"
|
|
|
|
"unicode/utf8"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
_ "github.com/ying32/govcl/pkgs/winappres" // Extra _syso files for Windows
|
|
|
|
"github.com/ying32/govcl/vcl"
|
|
|
|
"github.com/ying32/govcl/vcl/types"
|
|
|
|
)
|
|
|
|
|
2024-06-08 02:49:57 +00:00
|
|
|
const (
|
|
|
|
MY_SPACING = 6
|
|
|
|
MY_WIDTH = 180
|
|
|
|
)
|
|
|
|
|
2024-06-03 04:49:04 +00:00
|
|
|
type TMainForm struct {
|
|
|
|
*vcl.TForm
|
2024-06-08 02:24:35 +00:00
|
|
|
|
|
|
|
ImageList *vcl.TImageList
|
2024-06-03 04:49:04 +00:00
|
|
|
Menu *vcl.TMainMenu
|
|
|
|
Buckets *vcl.TTreeView
|
|
|
|
Tabs *vcl.TPageControl
|
|
|
|
propertiesBox *vcl.TMemo
|
|
|
|
contentBox *vcl.TListView
|
|
|
|
|
2024-06-08 01:34:33 +00:00
|
|
|
dbs []loadedDatabase
|
2024-06-03 04:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
mainForm *TMainForm
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
vcl.RunApp(&mainForm)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
2024-06-08 02:24:35 +00:00
|
|
|
f.ImageList = loadImages(f)
|
|
|
|
|
2024-06-03 04:49:04 +00:00
|
|
|
f.SetCaption("yvbolt")
|
|
|
|
|
|
|
|
mnuFile := vcl.NewMenuItem(f)
|
|
|
|
mnuFile.SetCaption("File")
|
|
|
|
|
|
|
|
mnuFileOpen := vcl.NewMenuItem(mnuFile)
|
2024-06-08 02:23:38 +00:00
|
|
|
mnuFileOpen.SetCaption("Open Bolt database...")
|
2024-06-08 02:24:44 +00:00
|
|
|
mnuFileOpen.SetImageIndex(imgDatabaseAdd)
|
2024-06-03 04:49:04 +00:00
|
|
|
mnuFileOpen.SetShortCutFromString("Ctrl+O")
|
|
|
|
mnuFileOpen.SetOnClick(f.OnMnuFileOpenClick)
|
|
|
|
mnuFile.Add(mnuFileOpen)
|
|
|
|
|
2024-06-08 02:23:38 +00:00
|
|
|
mnuFileSqliteOpen := vcl.NewMenuItem(mnuFile)
|
|
|
|
mnuFileSqliteOpen.SetCaption("Open SQLite database...")
|
2024-06-08 02:24:44 +00:00
|
|
|
mnuFileSqliteOpen.SetImageIndex(imgDatabaseAdd)
|
2024-06-08 02:23:38 +00:00
|
|
|
mnuFileSqliteOpen.SetOnClick(f.OnMnuFileSqliteOpenClick)
|
|
|
|
mnuFile.Add(mnuFileSqliteOpen)
|
|
|
|
|
2024-06-08 01:44:18 +00:00
|
|
|
mnuFileSqliteMemory := vcl.NewMenuItem(mnuFile)
|
|
|
|
mnuFileSqliteMemory.SetCaption("New SQLite in-memory database")
|
2024-06-08 02:24:44 +00:00
|
|
|
mnuFileSqliteMemory.SetImageIndex(imgDatabaseAdd)
|
2024-06-08 02:23:38 +00:00
|
|
|
mnuFileSqliteMemory.SetOnClick(f.OnMnuFileSqliteMemoryClick)
|
2024-06-08 01:44:18 +00:00
|
|
|
mnuFile.Add(mnuFileSqliteMemory)
|
|
|
|
|
2024-06-08 01:34:49 +00:00
|
|
|
mnuSep := vcl.NewMenuItem(mnuFile)
|
|
|
|
mnuSep.SetCaption("-") // Creates separator
|
|
|
|
mnuFile.Add(mnuSep)
|
|
|
|
|
|
|
|
mnuFileExit := vcl.NewMenuItem(mnuFile)
|
|
|
|
mnuFileExit.SetCaption("Exit")
|
|
|
|
mnuFileExit.SetOnClick(f.OnMnuFileExitClick)
|
|
|
|
mnuFile.Add(mnuFileExit)
|
|
|
|
|
2024-06-03 04:49:04 +00:00
|
|
|
f.Menu = vcl.NewMainMenu(f)
|
2024-06-08 02:24:44 +00:00
|
|
|
f.Menu.SetImages(f.ImageList)
|
2024-06-03 04:49:04 +00:00
|
|
|
f.Menu.Items().Add(mnuFile)
|
|
|
|
|
|
|
|
f.Buckets = vcl.NewTreeView(f)
|
|
|
|
f.Buckets.SetParent(f)
|
2024-06-08 02:24:44 +00:00
|
|
|
f.Buckets.SetImages(f.ImageList)
|
2024-06-03 04:49:04 +00:00
|
|
|
f.Buckets.SetAlign(types.AlLeft)
|
|
|
|
f.Buckets.SetWidth(MY_WIDTH)
|
|
|
|
f.Buckets.SetReadOnly(true) // prevent click to rename on nodes
|
|
|
|
f.Buckets.SetOnExpanding(f.OnNavExpanding)
|
|
|
|
f.Buckets.SetOnChange(f.OnNavChange)
|
|
|
|
|
|
|
|
hsplit := vcl.NewSplitter(f)
|
|
|
|
hsplit.SetParent(f)
|
|
|
|
hsplit.SetAlign(types.AlLeft)
|
|
|
|
hsplit.SetLeft(1) // Just needs to be further "over" than f.Buckets for auto-alignment
|
|
|
|
|
|
|
|
f.Tabs = vcl.NewPageControl(f)
|
|
|
|
f.Tabs.SetParent(f)
|
|
|
|
f.Tabs.SetAlign(types.AlClient)
|
|
|
|
|
|
|
|
propertiesTab := vcl.NewTabSheet(f.Tabs)
|
|
|
|
propertiesTab.SetParent(f.Tabs)
|
|
|
|
propertiesTab.SetCaption("Properties")
|
|
|
|
|
|
|
|
f.propertiesBox = vcl.NewMemo(propertiesTab)
|
|
|
|
f.propertiesBox.SetParent(propertiesTab)
|
|
|
|
f.propertiesBox.BorderSpacing().SetAround(MY_SPACING)
|
|
|
|
f.propertiesBox.SetAlign(types.AlClient) // fill remaining space
|
|
|
|
f.propertiesBox.SetReadOnly(true)
|
|
|
|
f.propertiesBox.SetEnabled(false)
|
|
|
|
f.propertiesBox.SetBorderStyle(types.BsNone)
|
|
|
|
f.propertiesBox.SetText("Open a database to get started...")
|
|
|
|
|
|
|
|
dataTab := vcl.NewTabSheet(f.Tabs)
|
|
|
|
dataTab.SetParent(f.Tabs)
|
|
|
|
dataTab.SetCaption("Data")
|
|
|
|
|
|
|
|
f.contentBox = vcl.NewListView(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)
|
2024-06-08 02:49:57 +00:00
|
|
|
f.contentBox.Columns().Clear()
|
2024-06-03 04:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *TMainForm) OnMnuFileOpenClick(sender vcl.IObject) {
|
|
|
|
dlg := vcl.NewOpenDialog(f)
|
|
|
|
dlg.SetTitle("Select a database file...")
|
2024-06-08 02:23:38 +00:00
|
|
|
dlg.SetFilter("Bolt database|*.db|All files|*.*")
|
2024-06-03 04:49:04 +00:00
|
|
|
ret := dlg.Execute() // Fake blocking
|
|
|
|
if ret {
|
2024-06-08 02:23:38 +00:00
|
|
|
f.boltAddDatabaseFromFile(dlg.FileName())
|
2024-06-03 04:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-08 02:23:38 +00:00
|
|
|
func (f *TMainForm) OnMnuFileSqliteOpenClick(sender vcl.IObject) {
|
|
|
|
dlg := vcl.NewOpenDialog(f)
|
|
|
|
dlg.SetTitle("Select a database file...")
|
|
|
|
dlg.SetFilter("SQLite database|*.db;*.db3;*.sqlite;*.sqlite3|All files|*.*")
|
|
|
|
ret := dlg.Execute() // Fake blocking
|
|
|
|
if ret {
|
|
|
|
f.sqliteAddDatabaseFromFile(dlg.FileName())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *TMainForm) OnMnuFileSqliteMemoryClick(sender vcl.IObject) {
|
|
|
|
f.sqliteAddDatabaseFromFile(`:memory:`)
|
2024-06-08 01:34:49 +00:00
|
|
|
}
|
|
|
|
|
2024-06-08 02:23:38 +00:00
|
|
|
func (f *TMainForm) OnMnuFileExitClick(sender vcl.IObject) {
|
|
|
|
os.Exit(0)
|
2024-06-08 01:44:18 +00:00
|
|
|
}
|
|
|
|
|
2024-06-03 04:49:04 +00:00
|
|
|
func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
|
|
|
|
|
|
|
if node.Data() == nil {
|
|
|
|
vcl.ShowMessage("unexpected nil data")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ndata := (*navData)(node.Data())
|
2024-06-08 01:34:33 +00:00
|
|
|
ndata.ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
|
2024-06-03 04:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func formatUtf8(in []byte) string {
|
|
|
|
if !utf8.Valid(in) {
|
|
|
|
return fmt.Sprintf("<<Invalid UTF-8 %q>>", in)
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(in)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *TMainForm) OnNavExpanding(sender vcl.IObject, node *vcl.TTreeNode, allowExpansion *bool) {
|
|
|
|
|
|
|
|
if node.Data() == nil {
|
|
|
|
vcl.ShowMessage("unexpected nil data")
|
|
|
|
*allowExpansion = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ndata := (*navData)(node.Data())
|
|
|
|
|
|
|
|
if ndata.childrenLoaded {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the child buckets from this point under the element
|
2024-06-08 01:34:33 +00:00
|
|
|
nextBucketNames, err := ndata.ld.NavChildren(ndata)
|
2024-06-03 04:49:04 +00:00
|
|
|
if err != nil {
|
|
|
|
vcl.ShowMessage(fmt.Sprintf("Failed to find child buckets under %q: %s", strings.Join(ndata.bucketPath, `/`), err.Error()))
|
|
|
|
*allowExpansion = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ndata.childrenLoaded = true // don't repeat this work
|
|
|
|
|
|
|
|
if len(nextBucketNames) == 0 {
|
|
|
|
node.SetHasChildren(false)
|
|
|
|
*allowExpansion = false
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Populate LCL child nodes
|
|
|
|
for _, bucketName := range nextBucketNames {
|
|
|
|
|
|
|
|
node := f.Buckets.Items().AddChild(node, formatUtf8([]byte(bucketName)))
|
|
|
|
node.SetHasChildren(true) // dynamically populate in OnNavExpanding
|
|
|
|
navData := &navData{
|
|
|
|
ld: ndata.ld,
|
|
|
|
childrenLoaded: false, // will be loaded dynamically
|
|
|
|
bucketPath: []string{}, // empty = root
|
|
|
|
}
|
|
|
|
navData.bucketPath = append(navData.bucketPath, ndata.bucketPath...)
|
|
|
|
navData.bucketPath = append(navData.bucketPath, bucketName)
|
|
|
|
node.SetData(unsafe.Pointer(navData))
|
2024-06-08 01:34:33 +00:00
|
|
|
|
|
|
|
ndata.ld.Keepalive(navData)
|
2024-06-03 04:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
*allowExpansion = true
|
|
|
|
}
|
|
|
|
}
|