package main import ( "fmt" "strings" "unsafe" "github.com/ying32/govcl/vcl" "github.com/ying32/govcl/vcl/types" ) 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.ScreenCenter() 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) mnuFileRedisConnect := vcl.NewMenuItem(mnuFile) mnuFileRedisConnect.SetCaption("Connect to Redis...") mnuFileRedisConnect.SetImageIndex(imgDatabaseAdd) mnuFileRedisConnect.SetOnClick(f.OnMnuFileRedisConnectClick) mnuFile.Add(mnuFileRedisConnect) 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) OnMnuFileRedisConnectClick(sender vcl.IObject) { var child *TRedisConnectionDialog vcl.Application.CreateForm(&child) defer child.Free() var ret TRedisConnectionDialogResult child.CustomReturn = &ret mr := child.ShowModal() // Fake blocking if mr != types.MrOk || ret == nil { return // Cancelled } // Connect f.redisConnect(ret) } 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 } queryString := f.queryInput.Text() if f.queryInput.SelLength() > 0 { queryString = f.queryInput.SelText() // Just the selected text } // Execute node := f.Buckets.Selected() if node == nil { vcl.ShowMessage("No database selected") return } ndata := (*navData)(node.Data()) ndata.ld.ExecQuery(queryString, 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 } }