yvbolt/main.go

326 lines
9.1 KiB
Go

package main
import (
"fmt"
"strings"
"unsafe"
_ "github.com/ying32/govcl/pkgs/winappres" // Extra _syso files for Windows
"github.com/ying32/govcl/vcl"
"github.com/ying32/govcl/vcl/types"
)
const (
MY_SPACING = 6
MY_HEIGHT = 90
MY_WIDTH = 180
)
type TMainForm struct {
*vcl.TForm
ImageList *vcl.TImageList
Menu *vcl.TMainMenu
StatusBar *vcl.TStatusBar
Buckets *vcl.TTreeView
Tabs *vcl.TPageControl
propertiesBox *vcl.TMemo
contentBox *vcl.TListView
queryInput *vcl.TMemo
queryResult *vcl.TListView
dbs []loadedDatabase
}
var (
mainForm *TMainForm
)
func main() {
vcl.RunApp(&mainForm)
}
func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
f.ImageList = loadImages(f)
f.SetCaption("yvbolt")
f.ImageList.GetIcon(imgDatabaseLightning, f.Icon())
mnuFile := vcl.NewMenuItem(f)
mnuFile.SetCaption("File")
mnuFileOpen := vcl.NewMenuItem(mnuFile)
mnuFileOpen.SetCaption("Open Bolt database...")
mnuFileOpen.SetImageIndex(imgDatabaseAdd)
mnuFileOpen.SetShortCutFromString("Ctrl+O")
mnuFileOpen.SetOnClick(f.OnMnuFileOpenClick)
mnuFile.Add(mnuFileOpen)
mnuFileSqliteOpen := vcl.NewMenuItem(mnuFile)
mnuFileSqliteOpen.SetCaption("Open SQLite database...")
mnuFileSqliteOpen.SetImageIndex(imgDatabaseAdd)
mnuFileSqliteOpen.SetOnClick(f.OnMnuFileSqliteOpenClick)
mnuFile.Add(mnuFileSqliteOpen)
mnuFileBadgerOpen := vcl.NewMenuItem(mnuFile)
mnuFileBadgerOpen.SetCaption("Open Badger v4 database...")
mnuFileBadgerOpen.SetImageIndex(imgDatabaseAdd)
mnuFileBadgerOpen.SetOnClick(f.OnMnuFileBadgerOpenClick)
mnuFile.Add(mnuFileBadgerOpen)
mnuFileSqliteMemory := vcl.NewMenuItem(mnuFile)
mnuFileSqliteMemory.SetCaption("New SQLite in-memory database")
mnuFileSqliteMemory.SetImageIndex(imgDatabaseAdd)
mnuFileSqliteMemory.SetOnClick(f.OnMnuFileSqliteMemoryClick)
mnuFile.Add(mnuFileSqliteMemory)
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)
mnuQuery := vcl.NewMenuItem(f)
mnuQuery.SetCaption("Query")
mnuQueryExecute := vcl.NewMenuItem(mnuQuery)
mnuQueryExecute.SetCaption("Execute")
mnuQueryExecute.SetShortCutFromString("F5")
mnuQueryExecute.SetOnClick(f.OnQueryExecute)
mnuQueryExecute.SetImageIndex(imgLightning)
mnuQuery.Add(mnuQueryExecute)
f.Menu = vcl.NewMainMenu(f)
f.Menu.SetImages(f.ImageList)
f.Menu.Items().Add(mnuFile)
f.Menu.Items().Add(mnuQuery)
//
f.StatusBar = vcl.NewStatusBar(f)
f.StatusBar.SetParent(f)
f.StatusBar.SetSimpleText("")
//
f.Buckets = vcl.NewTreeView(f)
f.Buckets.SetParent(f)
f.Buckets.SetImages(f.ImageList)
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) // fill remaining space
f.Tabs.SetImages(f.ImageList)
propertiesTab := vcl.NewTabSheet(f.Tabs)
propertiesTab.SetParent(f.Tabs)
propertiesTab.SetCaption("Properties")
propertiesTab.SetImageIndex(imgChartBar)
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")
dataTab.SetImageIndex(imgTable)
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)
f.contentBox.Columns().Clear()
queryTab := vcl.NewTabSheet(f.Tabs)
queryTab.SetParent(f.Tabs)
queryTab.SetCaption("Query")
queryTab.SetImageIndex(imgLightning)
queryButtonBar := vcl.NewToolBar(queryTab)
queryButtonBar.SetParent(queryTab)
queryButtonBar.SetAlign(types.AlTop)
queryButtonBar.BorderSpacing().SetLeft(MY_SPACING)
queryButtonBar.BorderSpacing().SetTop(MY_SPACING)
queryButtonBar.BorderSpacing().SetBottom(0)
queryButtonBar.BorderSpacing().SetRight(MY_SPACING)
queryButtonBar.SetImages(f.ImageList)
queryButtonBar.SetShowCaptions(true)
queryExecBtn := vcl.NewToolButton(queryButtonBar)
queryExecBtn.SetParent(queryButtonBar)
queryExecBtn.SetCaption("Execute")
// queryExecBtn.SetImageIndex(imgLightning)
queryExecBtn.SetOnClick(f.OnQueryExecute)
f.queryInput = vcl.NewMemo(queryTab)
f.queryInput.SetParent(queryTab)
f.queryInput.SetHeight(MY_HEIGHT)
f.queryInput.SetAlign(types.AlTop)
f.queryInput.SetTop(1)
f.queryInput.Font().SetName("monospace")
f.queryInput.BorderSpacing().SetLeft(MY_SPACING)
f.queryInput.BorderSpacing().SetTop(0)
f.queryInput.BorderSpacing().SetRight(MY_SPACING)
vsplit := vcl.NewSplitter(queryTab)
vsplit.SetParent(queryTab)
vsplit.SetAlign(types.AlTop)
vsplit.SetTop(2)
f.queryResult = vcl.NewListView(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.BorderSpacing().SetLeft(MY_SPACING)
f.queryResult.BorderSpacing().SetRight(MY_SPACING)
f.queryResult.BorderSpacing().SetBottom(MY_SPACING)
}
func (f *TMainForm) OnMnuFileOpenClick(sender vcl.IObject) {
dlg := vcl.NewOpenDialog(f)
dlg.SetTitle("Select a database file...")
dlg.SetFilter("Bolt database|*.db|All files|*.*")
ret := dlg.Execute() // Fake blocking
if ret {
f.boltAddDatabaseFromFile(dlg.FileName())
}
}
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) OnMnuFileBadgerOpenClick(sender vcl.IObject) {
dlg := vcl.NewSelectDirectoryDialog(f)
dlg.SetTitle("Select a database directory...")
ret := dlg.Execute() // Fake blocking
if ret {
f.badgerAddDatabaseFromDirectory(dlg.FileName())
}
}
func (f *TMainForm) OnMnuFileSqliteMemoryClick(sender vcl.IObject) {
f.sqliteAddDatabaseFromFile(`:memory:`)
}
func (f *TMainForm) OnMnuFileExitClick(sender vcl.IObject) {
f.Close()
}
func (f *TMainForm) OnQueryExecute(sender vcl.IObject) {
// If query tab is not selected, switch to it, but do not exec
if f.Tabs.ActivePageIndex() != 2 {
f.Tabs.SetActivePageIndex(2)
return
}
// Execute
node := f.Buckets.Selected()
if node == nil {
vcl.ShowMessage("No database selected")
return
}
ndata := (*navData)(node.Data())
ndata.ld.ExecQuery(f.queryInput.Text(), f.queryResult)
}
func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
if node.Data() == nil {
vcl.ShowMessage("unexpected nil data")
return
}
ndata := (*navData)(node.Data())
ndata.ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
// We're in charge of common status bar text updates
f.StatusBar.SetSimpleText(ndata.ld.DisplayName() + " | " + ndata.ld.DriverName())
}
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
nextBucketNames, err := ndata.ld.NavChildren(ndata)
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
node.SetImageIndex(imgTable)
node.SetSelectedIndex(imgTable)
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))
ndata.ld.Keepalive(navData)
}
*allowExpansion = true
}
}