From a6cbc5a9ede4c24d2746b212ee35a6cedd721285 Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 3 Oct 2024 19:34:28 +1300 Subject: [PATCH] qbolt: initial port to go/miqt --- MemoryStore.go | 56 --- bolt.go | 219 +++++++++ build.sh | 15 + docker/win32-cross-qt-mxe.Dockerfile | 13 - go.mod | 7 +- go.sum | 2 + qbolt/itemwindow.ui => itemwindow.ui | 0 itemwindow_ui.go | 71 +++ main.go | 400 +-------------- mainwindow.go | 509 ++++++++++++++++++++ qbolt/mainwindow.ui => mainwindow.ui | 0 mainwindow_ui.cpp.txt | 336 +++++++++++++ mainwindow_ui.go | 325 +++++++++++++ qbolt/boltdb.cpp | 219 --------- qbolt/boltdb.h | 46 -- qbolt/interop.cpp | 42 -- qbolt/interop.h | 31 -- qbolt/itemwindow.cpp | 18 - qbolt/itemwindow.h | 25 - qbolt/main.cpp | 23 - qbolt/mainwindow.cpp | 478 ------------------ qbolt/mainwindow.h | 70 --- qbolt/qbolt.pro | 48 -- qbolt/qbolt_cgo.h | 10 - resources.go | 17 + qbolt/resources.qrc => resources.qrc | 0 resources.rcc | Bin 0 -> 9498 bytes {qbolt/rsrc => rsrc}/add.png | Bin {qbolt/rsrc => rsrc}/arrow_refresh.png | Bin {qbolt/rsrc => rsrc}/chart_bar.png | Bin {qbolt/rsrc => rsrc}/database.png | Bin {qbolt/rsrc => rsrc}/database_add.png | Bin {qbolt/rsrc => rsrc}/database_connect.png | Bin {qbolt/rsrc => rsrc}/database_lightning.png | Bin {qbolt/rsrc => rsrc}/delete.png | Bin {qbolt/rsrc => rsrc}/disconnect.png | Bin {qbolt/rsrc => rsrc}/door_out.png | Bin {qbolt/rsrc => rsrc}/information.png | Bin {qbolt/rsrc => rsrc}/page.png | Bin {qbolt/rsrc => rsrc}/qbolt.ico | Bin {qbolt/rsrc => rsrc}/table.png | Bin {qbolt/rsrc => rsrc}/table_add.png | Bin {qbolt/rsrc => rsrc}/table_delete.png | Bin util.go | 9 + 44 files changed, 1519 insertions(+), 1470 deletions(-) delete mode 100644 MemoryStore.go create mode 100644 bolt.go create mode 100755 build.sh delete mode 100644 docker/win32-cross-qt-mxe.Dockerfile rename qbolt/itemwindow.ui => itemwindow.ui (100%) create mode 100644 itemwindow_ui.go create mode 100644 mainwindow.go rename qbolt/mainwindow.ui => mainwindow.ui (100%) create mode 100644 mainwindow_ui.cpp.txt create mode 100644 mainwindow_ui.go delete mode 100644 qbolt/boltdb.cpp delete mode 100644 qbolt/boltdb.h delete mode 100644 qbolt/interop.cpp delete mode 100644 qbolt/interop.h delete mode 100644 qbolt/itemwindow.cpp delete mode 100644 qbolt/itemwindow.h delete mode 100644 qbolt/main.cpp delete mode 100644 qbolt/mainwindow.cpp delete mode 100644 qbolt/mainwindow.h delete mode 100644 qbolt/qbolt.pro delete mode 100644 qbolt/qbolt_cgo.h create mode 100644 resources.go rename qbolt/resources.qrc => resources.qrc (100%) create mode 100644 resources.rcc rename {qbolt/rsrc => rsrc}/add.png (100%) rename {qbolt/rsrc => rsrc}/arrow_refresh.png (100%) rename {qbolt/rsrc => rsrc}/chart_bar.png (100%) rename {qbolt/rsrc => rsrc}/database.png (100%) rename {qbolt/rsrc => rsrc}/database_add.png (100%) rename {qbolt/rsrc => rsrc}/database_connect.png (100%) rename {qbolt/rsrc => rsrc}/database_lightning.png (100%) rename {qbolt/rsrc => rsrc}/delete.png (100%) rename {qbolt/rsrc => rsrc}/disconnect.png (100%) rename {qbolt/rsrc => rsrc}/door_out.png (100%) rename {qbolt/rsrc => rsrc}/information.png (100%) rename {qbolt/rsrc => rsrc}/page.png (100%) rename {qbolt/rsrc => rsrc}/qbolt.ico (100%) rename {qbolt/rsrc => rsrc}/table.png (100%) rename {qbolt/rsrc => rsrc}/table_add.png (100%) rename {qbolt/rsrc => rsrc}/table_delete.png (100%) create mode 100644 util.go diff --git a/MemoryStore.go b/MemoryStore.go deleted file mode 100644 index 95ffbf3..0000000 --- a/MemoryStore.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import "C" -import ( - "errors" - "sync" -) - -type ObjectReference int64 - -var NullObjectReference error = errors.New("Null object reference") - -// GoMemoryStore is a int->interface storage structure so that Go pointers are -// never exposed to C code. -type GoMemoryStore struct { - mtx sync.RWMutex - items map[int64]interface{} - next int64 -} - -func NewGoMemoryStore() *GoMemoryStore { - ret := GoMemoryStore{} - ret.items = make(map[int64]interface{}) - return &ret -} - -func (this *GoMemoryStore) Put(itm interface{}) ObjectReference { - this.mtx.Lock() - defer this.mtx.Unlock() - - key := this.next - this.items[key] = itm - this.next++ - return ObjectReference(key) -} - -func (this *GoMemoryStore) Get(i ObjectReference) (interface{}, bool) { - this.mtx.RLock() - defer this.mtx.RUnlock() - - ret, ok := this.items[int64(i)] - return ret, ok -} - -func (this *GoMemoryStore) Delete(i ObjectReference) { - this.mtx.Lock() - defer this.mtx.Unlock() - - delete(this.items, int64(i)) -} - -var gms *GoMemoryStore = nil - -func init() { - gms = NewGoMemoryStore() -} diff --git a/bolt.go b/bolt.go new file mode 100644 index 0000000..44c19df --- /dev/null +++ b/bolt.go @@ -0,0 +1,219 @@ +package main + +import ( + "encoding/json" + "errors" + "os" + "time" + + bolt "github.com/boltdb/bolt" +) + +func Bolt_Open(readOnly bool, path string) (*bolt.DB, error) { + opts := *bolt.DefaultOptions + opts.Timeout = 10 * time.Second + opts.ReadOnly = readOnly + + return bolt.Open(path, os.FileMode(0644), &opts) +} + +func walkBuckets(tx *bolt.Tx, browse []string) (*bolt.Bucket, error) { + bucket := tx.Bucket([]byte(browse[0])) + if bucket == nil { + return nil, errors.New("Unknown bucket") + } + + for i := 1; i < len(browse); i += 1 { + bucket = bucket.Bucket([]byte(browse[i])) + if bucket == nil { + return nil, errors.New("Unknown bucket") + } + } + + return bucket, nil +} + +func withBrowse_ReadOnly(db *bolt.DB, browse []string, fn func(tx *bolt.Tx, bucket *bolt.Bucket) error) error { + if len(browse) == 0 { + // not a bucket + return errors.New("No bucket selected") + } + + return db.View(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now run the user callback + return fn(tx, bucket) + + }) +} + +func Bolt_CreateBucket(db *bolt.DB, browse []string, newBucket string) error { + + return db.Update(func(tx *bolt.Tx) error { + + if len(browse) == 0 { + // Top-level bucket + _, err := tx.CreateBucket([]byte(newBucket)) + return err + + } else { + // Deeper bucket + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now create the new bucket + _, err = bucket.CreateBucket([]byte(newBucket)) + return err + } + }) +} + +func Bolt_DeleteBucket(db *bolt.DB, browse []string, delBucket string) error { + return db.Update(func(tx *bolt.Tx) error { + + if len(browse) == 0 { + // Top-level bucket + return tx.DeleteBucket([]byte(delBucket)) + + } else { + // Deeper bucket + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now delete the selected bucket + return bucket.DeleteBucket([]byte(delBucket)) + } + }) +} + +func Bolt_SetItem(db *bolt.DB, browse []string, key, val string) error { + if len(browse) == 0 { + return errors.New("Can't create top-level items") + } + + return db.Update(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + return bucket.Put([]byte(key), []byte(val)) + }) +} + +func Bolt_DeleteItem(db *bolt.DB, browse []string, key string) error { + if len(browse) == 0 { + return errors.New("Can't create top-level items") + } + + return db.Update(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + return bucket.Delete([]byte(key)) + }) +} + +func Bolt_DBStats(db *bolt.DB) (string, error) { + jBytes, err := json.Marshal(db.Stats()) + if err != nil { + return "", err + } + + return string(jBytes), nil +} + +func Bolt_BucketStats(db *bolt.DB, browse []string) (string, error) { + var stats bolt.BucketStats + + err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + stats = bucket.Stats() + return nil + }) + if err != nil { + return "", err + } + + jBytes, err := json.Marshal(stats) + if err != nil { + return "", err + } + + return string(jBytes), err +} + +func Bolt_ListBuckets(db *bolt.DB, browse []string, cb func(b string)) error { + + if len(browse) == 0 { + // root mode + return db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(k []byte, _ *bolt.Bucket) error { + cb(string(k)) + return nil + }) + }) + + } + + // Nested-mode + return withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + return bucket.ForEach(func(k, v []byte) error { + // non-nil v means it's a data item + if v == nil { + cb(string(k)) + } + return nil + }) + }) +} + +type ListItemInfo struct { + Name string + DataLen int64 +} + +func Bolt_ListItems(db *bolt.DB, browse []string, cb func(ListItemInfo) error) error { + + if len(browse) == 0 { + return errors.New("No bucket specified") + } + + // Nested-mode + return withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + return bucket.ForEach(func(k, v []byte) error { + if v == nil { + return nil // nil v means it's a bucket, skip + } + + return cb(ListItemInfo{string(k), int64(len(v))}) + }) + }) +} + +func Bolt_GetItem(db *bolt.DB, browse []string, key string) (string, error) { + var ret string + + err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + d := bucket.Get([]byte(key)) + ret = string(d) + return nil + }) + return ret, err +} + +func Bolt_Close(db *bolt.DB) error { + return db.Close() +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5d07b70 --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -eu + +MIQT_UIC=${MIQT_UIC:-miqt-uic} + +rm resources.rcc || true +rcc --binary -o resources.rcc resources.qrc + +$MIQT_UIC -InFile mainwindow.ui -OutFile mainwindow_ui.go +$MIQT_UIC -InFile itemwindow.ui -OutFile itemwindow_ui.go + +go build -ldflags "-s -w" + +echo "Build OK" diff --git a/docker/win32-cross-qt-mxe.Dockerfile b/docker/win32-cross-qt-mxe.Dockerfile deleted file mode 100644 index c50e9e4..0000000 --- a/docker/win32-cross-qt-mxe.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM debian:bullseye - -RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ - apt-get install -qyy gnupg2 golang-go ca-certificates - -RUN DEBIAN_FRONTEND=noninteractive \ - echo "deb https://pkg.mxe.cc/repos/apt buster main" >/etc/apt/sources.list.d/mxeapt.list && \ - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 86B72ED9 && \ - apt-get update && \ - apt-get install -qyy mxe-i686-w64-mingw32.static-qt5 && \ - apt-get clean - -ENV PATH=/usr/lib/mxe/usr/bin:$PATH diff --git a/go.mod b/go.mod index b16dcfb..c61c52d 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,10 @@ module code.ivysaur.me/qbolt -go 1.15 +go 1.19 -require github.com/boltdb/bolt v1.3.1 +require ( + github.com/boltdb/bolt v1.3.1 + github.com/mappu/miqt v0.5.0 +) replace github.com/boltdb/bolt => go.etcd.io/bbolt v1.3.5 diff --git a/go.sum b/go.sum index 218565d..bb9a7fb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/mappu/miqt v0.5.0 h1:BWajkNI9PWlWN6ZDgWKwv1gieBGEImRqlWS8ZqDmDfA= +github.com/mappu/miqt v0.5.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/qbolt/itemwindow.ui b/itemwindow.ui similarity index 100% rename from qbolt/itemwindow.ui rename to itemwindow.ui diff --git a/itemwindow_ui.go b/itemwindow_ui.go new file mode 100644 index 0000000..ba2c93e --- /dev/null +++ b/itemwindow_ui.go @@ -0,0 +1,71 @@ +// Generated by miqt-uic. To update this file, edit the .ui file in +// Qt Designer, and then run 'go generate'. +// +//go:generate miqt-uic -InFile itemwindow.ui -OutFile itemwindow_ui.go + +package main + +import ( + "github.com/mappu/miqt/qt" +) + +type ItemWindowUi struct { + ItemWindow *qt.QDialog + gridLayout_2 *qt.QGridLayout + contentArea *qt.QPlainTextEdit + frame *qt.QFrame + gridLayout *qt.QGridLayout + buttonBox *qt.QDialogButtonBox +} + +// NewItemWindowUi creates all Qt widget classes for ItemWindow. +func NewItemWindowUi() *ItemWindowUi { + ui := &ItemWindowUi{} + + ui.ItemWindow = qt.NewQDialog2(nil) + ui.ItemWindow.SetObjectName("ItemWindow") + ui.ItemWindow.Resize(370, 353) + ui.ItemWindow.SetWindowTitle("") + icon0 := qt.NewQIcon() + icon0.AddFile4(":/rsrc/database_lightning.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.ItemWindow.SetWindowIcon(icon0) + + ui.gridLayout_2 = qt.NewQGridLayout(ui.ItemWindow.QWidget) + ui.gridLayout_2.SetObjectName("gridLayout_2") + ui.gridLayout_2.SetVerticalSpacing(0) + ui.gridLayout_2.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_2.SetSpacing(6) + + ui.contentArea = qt.NewQPlainTextEdit3(ui.ItemWindow.QWidget) + ui.contentArea.SetObjectName("contentArea") + ui.contentArea.SetFrameShape(qt.QFrame__NoFrame) + + ui.gridLayout_2.AddWidget2(ui.contentArea.QWidget, 0, 0) + + ui.frame = qt.NewQFrame2(ui.ItemWindow.QWidget) + ui.frame.SetObjectName("frame") + ui.frame.SetFrameShape(qt.QFrame__StyledPanel) + ui.frame.SetFrameShadow(qt.QFrame__Raised) + + ui.gridLayout = qt.NewQGridLayout(ui.frame.QWidget) + ui.gridLayout.SetObjectName("gridLayout") + ui.gridLayout.SetContentsMargins(11, 11, 11, 11) + ui.gridLayout.SetSpacing(6) + + ui.buttonBox = qt.NewQDialogButtonBox5(ui.frame.QWidget) + ui.buttonBox.SetObjectName("buttonBox") + /* miqt-uic: no handler for buttonBox property 'standardButtons' */ + + ui.gridLayout.AddWidget2(ui.buttonBox.QWidget, 0, 0) + + ui.gridLayout_2.AddWidget2(ui.frame.QWidget, 1, 0) + + ui.Retranslate() + + return ui +} + +// Retranslate reapplies all text translations. +func (ui *ItemWindowUi) Retranslate() { + +} diff --git a/main.go b/main.go index 09fdd65..d3e22bf 100644 --- a/main.go +++ b/main.go @@ -1,398 +1,20 @@ package main -import "C" import ( - "encoding/binary" - "encoding/json" - "errors" "os" - "time" - bolt "github.com/boltdb/bolt" + "github.com/mappu/miqt/qt" ) -const ( - ERROR_AND_STOP_CALLING int64 = 100 - ERROR_AND_KEEP_CALLING = 101 - FINISHED_OK = 102 - REAL_MESSAGE = 103 -) - -const Magic int64 = 0x10203040 - -//export GetMagic -func GetMagic() int64 { - return Magic -} - -//export Bolt_Open -func Bolt_Open(readOnly bool, path string) (ObjectReference, *C.char, int) { - opts := *bolt.DefaultOptions - opts.Timeout = 10 * time.Second - opts.ReadOnly = readOnly - - ptrDB, err := bolt.Open(path, os.FileMode(0644), &opts) - if err != nil { - errMsg := err.Error() - return 0, C.CString(errMsg), len(errMsg) - } - - dbRef := gms.Put(ptrDB) - return dbRef, nil, 0 -} - -func withBoltDBReference(b ObjectReference, fn func(db *bolt.DB) error) error { - dbIFC, ok := gms.Get(b) - if !ok { - return NullObjectReference - } - - ptrDB, ok := dbIFC.(*bolt.DB) - if !ok { - return NullObjectReference - } - - return fn(ptrDB) -} - -func walkBuckets(tx *bolt.Tx, browse []string) (*bolt.Bucket, error) { - bucket := tx.Bucket([]byte(browse[0])) - if bucket == nil { - return nil, errors.New("Unknown bucket") - } - - for i := 1; i < len(browse); i += 1 { - bucket = bucket.Bucket([]byte(browse[i])) - if bucket == nil { - return nil, errors.New("Unknown bucket") - } - } - - return bucket, nil -} - -func withBrowse_ReadOnly(b_ref ObjectReference, browse []string, fn func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error) error { - if len(browse) == 0 { - // not a bucket - return errors.New("No bucket selected") - } - - return withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.View(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // Walked the bucket chain, now run the user callback - return fn(db, tx, bucket) - - }) - }) -} - -func err2triple(err error) (int64, *C.char, int) { - if err != nil { - msg := err.Error() - return ERROR_AND_STOP_CALLING, C.CString(msg), len(msg) - } - - return FINISHED_OK, nil, 0 -} - -//export Bolt_CreateBucket -func Bolt_CreateBucket(b_ref ObjectReference, browse []string, newBucket string) (int64, *C.char, int) { - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - if len(browse) == 0 { - // Top-level bucket - _, err := tx.CreateBucket([]byte(newBucket)) - return err - - } else { - // Deeper bucket - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // Walked the bucket chain, now create the new bucket - _, err = bucket.CreateBucket([]byte(newBucket)) - return err - } - }) - }) - - return err2triple(err) -} - -//export Bolt_DeleteBucket -func Bolt_DeleteBucket(b_ref ObjectReference, browse []string, delBucket string) (int64, *C.char, int) { - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - if len(browse) == 0 { - // Top-level bucket - return tx.DeleteBucket([]byte(delBucket)) - - } else { - // Deeper bucket - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // Walked the bucket chain, now delete the selected bucket - return bucket.DeleteBucket([]byte(delBucket)) - } - }) - }) - - return err2triple(err) -} - -//export Bolt_SetItem -func Bolt_SetItem(b_ref ObjectReference, browse []string, key, val string) (int64, *C.char, int) { - if len(browse) == 0 { - return err2triple(errors.New("Can't create top-level items")) - } - - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - return bucket.Put([]byte(key), []byte(val)) - }) - }) - - return err2triple(err) -} - -//export Bolt_DeleteItem -func Bolt_DeleteItem(b_ref ObjectReference, browse []string, key string) (int64, *C.char, int) { - if len(browse) == 0 { - return err2triple(errors.New("Can't create top-level items")) - } - - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - return bucket.Delete([]byte(key)) - }) - }) - - return err2triple(err) -} - -type CallResponse struct { - s string - e error -} - -//export Bolt_DBStats -func Bolt_DBStats(b ObjectReference) (int64, *C.char, int) { - var stats bolt.Stats - - err := withBoltDBReference(b, func(db *bolt.DB) error { - stats = db.Stats() - return nil - }) - - jBytes, err := json.Marshal(stats) - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) -} - -//export Bolt_BucketStats -func Bolt_BucketStats(b ObjectReference, browse []string) (int64, *C.char, int) { - var stats bolt.BucketStats - - err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - stats = bucket.Stats() - return nil - }) - - if err != nil { - return err2triple(err) - } - - jBytes, err := json.Marshal(stats) - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) -} - -type NextCall struct { - content chan CallResponse -} - -//export Bolt_ListBuckets -func Bolt_ListBuckets(b ObjectReference, browse []string) ObjectReference { - pNC := &NextCall{ - content: make(chan CallResponse, 0), - } - - pNC_Ref := gms.Put(pNC) - - go func() { - var err error - - if len(browse) == 0 { - // root mode - err = withBoltDBReference(b, func(db *bolt.DB) error { - return db.View(func(tx *bolt.Tx) error { - return tx.ForEach(func(k []byte, _ *bolt.Bucket) error { - pNC.content <- CallResponse{s: string(k)} - return nil - }) - }) - }) - - } else { - // Nested-mode - err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - return bucket.ForEach(func(k, v []byte) error { - // non-nil v means it's a data item - if v == nil { - pNC.content <- CallResponse{s: string(k)} - } - return nil - }) - }) - - } - - if err != nil { - pNC.content <- CallResponse{e: err} - } - - close(pNC.content) - }() - - return pNC_Ref -} - -//export Bolt_ListItems -func Bolt_ListItems(b ObjectReference, browse []string) ObjectReference { - - pNC := &NextCall{ - content: make(chan CallResponse, 0), - } - - pNC_Ref := gms.Put(pNC) - - go func() { - var err error - - if len(browse) == 0 { - err = errors.New("No bucket specified") - } else { - // Nested-mode - err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - return bucket.ForEach(func(k, v []byte) error { - if v == nil { - return nil // nil v means it's a bucket, skip - } - - itemLength := make([]byte, 8) - binary.LittleEndian.PutUint64(itemLength, uint64(len(v))) - pNC.content <- CallResponse{s: string(itemLength) + string(k)} - return nil - }) - }) - } - - if err != nil { - pNC.content <- CallResponse{e: err} - } - - close(pNC.content) - }() - - return pNC_Ref -} - -//export Bolt_GetItem -func Bolt_GetItem(b ObjectReference, browse []string, key string) (int64, *C.char, int) { - var ret *C.char = nil - var ret_len = 0 - - err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - d := bucket.Get([]byte(key)) - ret = C.CString(string(d)) - ret_len = len(d) - return nil - }) - - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, ret, ret_len -} - -//export GetNext -func GetNext(oRef ObjectReference) (int64, *C.char, int) { - pNC_Iface, ok := gms.Get(oRef) - if !ok { - return err2triple(NullObjectReference) - } - - pNC, ok := pNC_Iface.(*NextCall) - if !ok { - return err2triple(NullObjectReference) - } - - cr, ok := <-pNC.content - if !ok { - gms.Delete(oRef) - return err2triple(nil) - } - - if cr.e != nil { - msg := cr.e.Error() - return ERROR_AND_KEEP_CALLING, C.CString(msg), len(msg) - } - - return REAL_MESSAGE, C.CString(cr.s), len(cr.s) -} - -//export Bolt_ListBucketsAtRoot -func Bolt_ListBucketsAtRoot(b ObjectReference) ObjectReference { - return Bolt_ListBuckets(b, nil) -} - -//export Bolt_Close -func Bolt_Close(b ObjectReference) (*C.char, int) { - err := withBoltDBReference(b, func(db *bolt.DB) error { - return db.Close() - }) - - if err != nil { - msg := err.Error() - return C.CString(msg), len(msg) - } - - gms.Delete(b) - return nil, 0 -} - func main() { - // virtual + + _ = qt.NewQApplication(os.Args) + + qt.QGuiApplication_SetApplicationDisplayName("QBolt") + qt.QGuiApplication_SetWindowIcon(qt.NewQIcon4(":/rsrc/database_lightning.png")) + + w := NewMainWindow() + w.ui.MainWindow.Show() + + qt.QApplication_Exec() } diff --git a/mainwindow.go b/mainwindow.go new file mode 100644 index 0000000..c401ac7 --- /dev/null +++ b/mainwindow.go @@ -0,0 +1,509 @@ +package main + +import ( + "fmt" + "strconv" + + bolt "github.com/boltdb/bolt" + "github.com/mappu/miqt/qt" +) + +type MainWindow struct { + ui *MainWindowUi + + databaseContext *qt.QMenu + bucketContext *qt.QMenu + lastContextSelection *qt.QTreeWidgetItem +} + +func NewMainWindow() *MainWindow { + this := &MainWindow{} + this.ui = NewMainWindowUi() + + this.on_bucketTree_currentItemChanged(nil, nil) + + this.databaseContext = qt.NewQMenu() + this.databaseContext.QWidget.AddAction(this.ui.actionRefresh_buckets) + this.databaseContext.QWidget.AddAction(this.ui.actionAdd_bucket) + this.databaseContext.AddSeparator() + this.databaseContext.QWidget.AddAction(this.ui.actionDisconnect) + + this.bucketContext = qt.NewQMenu() + this.bucketContext.QWidget.AddAction(this.ui.actionRefresh_buckets) + this.bucketContext.QWidget.AddAction(this.ui.actionAdd_bucket) + this.bucketContext.AddSeparator() + this.bucketContext.QWidget.AddAction(this.ui.actionDelete_bucket) + + // Connections + this.ui.actionNew_database.OnTriggered(this.on_actionNew_database_triggered) + this.ui.actionOpen_database.OnTriggered(this.on_actionOpen_database_triggered) + this.ui.actionOpen_database_as_read_only.OnTriggered(this.on_actionOpen_database_as_read_only_triggered) + this.ui.actionExit.OnTriggered(this.on_actionExit_triggered) + this.ui.actionAbout_Qt.OnTriggered(this.on_actionAbout_Qt_triggered) + this.ui.actionAbout_qbolt.OnTriggered(this.on_actionAbout_qbolt_triggered) + this.ui.actionDisconnect.OnTriggered(this.on_actionDisconnect_triggered) + this.ui.bucketTree.OnCustomContextMenuRequested(this.on_bucketTree_customContextMenuRequested) + this.ui.actionRefresh_buckets.OnTriggered(this.on_actionRefresh_buckets_triggered) + this.ui.bucketTree.OnCurrentItemChanged(this.on_bucketTree_currentItemChanged) + this.ui.actionClear_selection.OnTriggered(this.on_actionClear_selection_triggered) + this.ui.bucketData.OnDoubleClicked(this.on_bucketData_doubleClicked) + this.ui.actionAdd_bucket.OnTriggered(this.on_actionAdd_bucket_triggered) + this.ui.actionDelete_bucket.OnTriggered(this.on_actionDelete_bucket_triggered) + this.ui.AddDataButton.OnClicked(this.on_AddDataButton_clicked) + this.ui.DeleteDataButton.OnClicked(this.on_DeleteDataButton_clicked) + this.ui.bucketData.OnItemSelectionChanged(this.on_bucketData_itemSelectionChanged) + + return this +} + +const ( + BdbPointerRole = int(qt.UserRole + 1) + BinaryDataRole = int(qt.UserRole + 2) +) + +var bdbs []*bolt.DB = nil + +func SET_BDB(top *qt.QTreeWidgetItem, bdb *bolt.DB) { + idx := len(bdbs) + bdbs = append(bdbs, bdb) + top.SetData(0, BdbPointerRole, qt.NewQVariant7(idx)) // Don't store a Go pointer in Qt memory +} + +func GET_BDB(top *qt.QTreeWidgetItem) *bolt.DB { + if top == nil { + panic("Passed a nil QTreeWidgetItem") + } + + dataVariant := top.Data(0, BdbPointerRole) + if dataVariant == nil { + panic("Selected item has no bdb") + } + + return bdbs[dataVariant.ToInt()] +} + +func (this *MainWindow) Widget() *qt.QWidget { + return this.ui.centralWidget +} + +func (this *MainWindow) on_actionNew_database_triggered() { + file := qt.QFileDialog_GetSaveFileName2(this.Widget(), "Save new bolt database as...") + if len(file) > 0 { + this.openDatabase(file, false) + } +} + +func (this *MainWindow) on_actionOpen_database_triggered() { + file := qt.QFileDialog_GetOpenFileName2(this.Widget(), "Select bolt database...") + if len(file) > 0 { + this.openDatabase(file, false) + } +} + +func (this *MainWindow) on_actionOpen_database_as_read_only_triggered() { + file := qt.QFileDialog_GetOpenFileName2(this.Widget(), "Select bolt database...") + if len(file) > 0 { + this.openDatabase(file, true) + } +} + +func (this *MainWindow) alert(message string) { + qt.QMessageBox_Critical(this.Widget(), "qbolt", message) +} + +func (this *MainWindow) openDatabase(file string, readOnly bool) { + + // Open + bdb, err := Bolt_Open(readOnly, file) + if err != nil { + this.alert(fmt.Sprintf("Error opening database: %s", err.Error())) + return + } + + top := qt.NewQTreeWidgetItem() + top.SetText(0, file) + top.SetIcon(0, qt.NewQIcon4(":/rsrc/database.png")) + SET_BDB(top, bdb) + this.ui.bucketTree.AddTopLevelItem(top) + + this.refreshBucketTree(top) + this.ui.bucketTree.SetCurrentItem(top) + + this.ui.bucketTree.ExpandItem(top) +} + +func getDisplayName(qba string) string { + ret := strconv.Quote(qba) + return ret[1 : len(ret)-2] +} + +func (this *MainWindow) refreshBucketTree(itm *qt.QTreeWidgetItem) { + var top *qt.QTreeWidgetItem = itm + + var browsePath []string + for { + browsePath = append(browsePath, top.Data(0, BinaryDataRole).ToString()) + if top.Parent() == nil { + break + } else { + top = top.Parent() + } + } + + ReverseSlice(browsePath) + + // Remove existing children + i := itm.ChildCount() + for i > 0 { + itm.TakeChild(i).Delete() + i -= 1 + } + + bdb := GET_BDB(top) + + err := Bolt_ListBuckets(bdb, browsePath, func(qba string) { + child := qt.NewQTreeWidgetItem() + child.SetText(0, getDisplayName(qba)) + child.SetData(0, BinaryDataRole, qt.NewQVariant14(qba)) + child.SetIcon(0, qt.NewQIcon4(":/rsrc/table.png")) + + itm.AddChild(child) + this.refreshBucketTree(child) + }) + if err != nil { + this.alert(fmt.Sprintf("Error listing buckets: %s", err.Error())) + return + } +} + +func (this *MainWindow) on_actionExit_triggered() { + this.ui.MainWindow.Close() +} + +func (this *MainWindow) on_actionAbout_Qt_triggered() { + qt.QApplication_AboutQt() +} + +func (this *MainWindow) on_actionAbout_qbolt_triggered() { + qt.QMessageBox_About( + this.Widget(), + qt.QGuiApplication_ApplicationDisplayName(), + "QBolt
Graphical interface for managing Bolt databases

"+ + "- About BoltDB
"+ + "- FamFamFam "Silk" icon set
"+ + "- QBolt homepage
", + ) +} + +func (this *MainWindow) on_actionDisconnect_triggered() { + top := this.lastContextSelection + if top.Parent() != nil { + return // somehow we didn't select a top-level item + } + + bdb := GET_BDB(top) + + // Remove UI + this.ui.bucketTree.ClearSelection() + top.Delete() + + // Disconnect from DB + bdb.Close() +} + +func (this *MainWindow) on_bucketTree_customContextMenuRequested(pos *qt.QPoint) { + + itm := this.ui.bucketTree.ItemAt(pos) + if itm == nil { + return + } + + this.lastContextSelection = itm + + if itm.Parent() != nil { + // Child item, show the bucket menu + this.bucketContext.Popup(this.ui.bucketTree.MapToGlobal(pos)) + + } else { + // Top-level item, show the database menu + this.databaseContext.Popup(this.ui.bucketTree.MapToGlobal(pos)) + } +} + +func (this *MainWindow) on_actionRefresh_buckets_triggered() { + this.refreshBucketTree(this.lastContextSelection) +} + +func (this *MainWindow) on_bucketTree_currentItemChanged(current, previous *qt.QTreeWidgetItem) { + _ = previous // Q_UNUSED + if current == nil { + this.ui.stackedWidget.SetVisible(false) + return + } + + this.ui.stackedWidget.SetVisible(true) + + if current.Parent() == nil { + // Selected a database + this.ui.stackedWidget.SetCurrentWidget(this.ui.databasePage) + this.ui.databasePropertiesArea.Clear() + + bdb := GET_BDB(current) + stats, err := Bolt_DBStats(bdb) + if err != nil { + this.ui.databasePropertiesArea.SetPlainText(fmt.Sprintf("Error retrieving database statistics: %s", err.Error())) + } else { + this.ui.databasePropertiesArea.SetPlainText(stats) + } + + // Clean up foreign areas + this.ui.bucketPropertiesArea.Clear() + this.ui.bucketData.Clear() + + } else { + // Selected a bucket + + this.ui.stackedWidget.SetCurrentWidget(this.ui.bucketPage) + this.ui.bucketPropertiesArea.Clear() + + top := current + var browse []string + for { + browse = append(browse, top.Data(0, BinaryDataRole).ToString()) + if top.Parent() == nil { + break + } else { + top = top.Parent() + } + } + ReverseSlice(browse) + bdb := GET_BDB(top) + + stats, err := Bolt_BucketStats(bdb, browse) + if err != nil { + this.ui.databasePropertiesArea.SetPlainText(fmt.Sprintf("Error retrieving bucket statistics: %s", err.Error())) + } else { + this.ui.databasePropertiesArea.SetPlainText(stats) + } + + // Load the data tab + this.refreshData(bdb, browse) + + // Clean up foreign areas + this.ui.databasePropertiesArea.Clear() + } +} + +func (this *MainWindow) refreshData(bdb *bolt.DB, browse []string) { + // Load the data tab + this.ui.bucketData.Clear() + + err := Bolt_ListItems(bdb, browse, func(lii ListItemInfo) error { + itm := qt.NewQTreeWidgetItem() + itm.SetText(0, getDisplayName(lii.Name)) + itm.SetData(0, BinaryDataRole, qt.NewQVariant14(lii.Name)) + itm.SetText(1, fmt.Sprintf("%d", lii.DataLen)) + this.ui.bucketData.AddTopLevelItem(itm) + return nil + }) + + if err != nil { + this.alert(fmt.Sprintf("Error listing bucket content: %s", err.Error())) + return + } + + this.ui.bucketData.ResizeColumnToContents(0) + this.on_bucketData_itemSelectionChanged() +} + +func (this *MainWindow) on_actionClear_selection_triggered() { + this.ui.bucketTree.SetCurrentItem(nil) +} + +type windowSelection struct { + itm *qt.QTreeWidgetItem + top *qt.QTreeWidgetItem + browse []string + bdb *bolt.DB +} + +func (this *MainWindow) getWindowSelection() (windowSelection, bool) { + + itm := this.ui.bucketTree.CurrentItem() + if itm == nil { + return windowSelection{}, false // No selection + } + + top := itm + + var browse []string + for { + browse = append(browse, top.Data(0, BinaryDataRole).ToString()) + if top.Parent() == nil { + break + } else { + top = top.Parent() + } + } + + ReverseSlice(browse) + + bdb := GET_BDB(top) + + return windowSelection{itm: itm, top: top, browse: browse, bdb: bdb}, true +} + +func (this *MainWindow) openEditor(bdb *bolt.DB, saveAs []string, saveAsKey string, currentContent []byte) { + iw := NewItemWindowUi() + + iw.contentArea.SetPlainText(string(currentContent)) + iw.ItemWindow.SetWindowTitle(getDisplayName(saveAsKey)) + iw.ItemWindow.SetWindowModality(qt.ApplicationModal) // we need this - otherwise we'll refresh a possibly-changed area after saving + iw.ItemWindow.OnFinished(func(exitCode int) { + if exitCode == int(qt.QDialog__Accepted) { + + err := Bolt_SetItem(bdb, saveAs, saveAsKey, iw.contentArea.ToPlainText()) + if err != nil { + this.alert(fmt.Sprintf("Error saving item content: %s", err.Error())) + } + + this.refreshData(bdb, saveAs) + } + iw.ItemWindow.DeleteLater() + }) + + iw.ItemWindow.Show() +} + +func (this *MainWindow) on_bucketData_doubleClicked(index *qt.QModelIndex) { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Get item key + + model := index.Model() + key := model.Data2(model.Index(index.Row(), 0), BinaryDataRole).ToString() + + // DB lookup + content, err := Bolt_GetItem(ws.bdb, ws.browse, key) + if err != nil { + this.alert(fmt.Sprintf("Error loading item content: %s", err.Error())) + return + } + + this.openEditor(ws.bdb, ws.browse, key, []byte(content)) +} + +func (this *MainWindow) on_actionAdd_bucket_triggered() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for bucket name + + name := qt.QInputDialog_GetText(this.Widget(), "New bucket", "Enter a key for the new bucket:") + if len(name) == 0 { + return + } + + // Create + err := Bolt_CreateBucket(ws.bdb, ws.browse, name) + if err != nil { + this.alert(fmt.Sprintf("Error creating bucket: %s", err.Error())) + return + } + + // Refresh bucket list + this.refreshBucketTree(ws.itm) // sub-tree only + this.ui.bucketTree.ExpandItem(ws.itm) +} + +func (this *MainWindow) on_actionDelete_bucket_triggered() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for confirmation + bucketToDelete := ws.itm.Data(0, BinaryDataRole).ToString() + if qt.QMessageBox_Question4( + this.Widget(), + "Delete bucket", + fmt.Sprintf("Are you sure you want to remove the bucket '%s'?", getDisplayName(bucketToDelete)), + qt.QMessageBox__Yes, + qt.QMessageBox__Cancel, + ) != int(qt.QMessageBox__Yes) { + return + } + + parent := ws.itm.Parent() + + // One level down + + if len(ws.browse) > 0 { + ws.browse = ws.browse[0 : len(ws.browse)-1] + } + + err := Bolt_DeleteBucket(ws.bdb, ws.browse, bucketToDelete) + if err != nil { + this.alert(fmt.Sprintf("Error removing bucket: %s", err.Error())) + return + } + + // Refresh bucket list + this.refreshBucketTree(parent) // sub-tree only + this.ui.bucketTree.ExpandItem(parent) + this.ui.bucketTree.SetCurrentItem(parent) +} + +func (this *MainWindow) on_AddDataButton_clicked() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for bucket name + + name := qt.QInputDialog_GetText(this.Widget(), "New item", "Enter a key for the new item:") + if len(name) == 0 { + return + } + + this.openEditor(ws.bdb, ws.browse, name, []byte("")) +} + +func (this *MainWindow) on_DeleteDataButton_clicked() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + selection := this.ui.bucketData.SelectedItems() + if len(selection) == 0 { + return // nothing to do + } + + // Prompt for confirmation + if qt.QMessageBox_Question4(this.Widget(), "Delete items", fmt.Sprintf("Are you sure you want to remove %d item(s)?", len(selection)), qt.QMessageBox__Yes, qt.QMessageBox__Cancel) != int(qt.QMessageBox__Yes) { + return + } + + var i int = len(selection) + for i > 0 { + err := Bolt_DeleteItem(ws.bdb, ws.browse, selection[i].Data(0, BinaryDataRole).ToString()) + if err != nil { + this.alert(fmt.Sprintf("Error removing item: %s", err.Error())) + return + } + i -= 1 + } + + this.refreshData(ws.bdb, ws.browse) +} + +func (this *MainWindow) on_bucketData_itemSelectionChanged() { + this.ui.DeleteDataButton.SetEnabled(len(this.ui.bucketData.SelectedItems()) > 0) +} diff --git a/qbolt/mainwindow.ui b/mainwindow.ui similarity index 100% rename from qbolt/mainwindow.ui rename to mainwindow.ui diff --git a/mainwindow_ui.cpp.txt b/mainwindow_ui.cpp.txt new file mode 100644 index 0000000..9a177c0 --- /dev/null +++ b/mainwindow_ui.cpp.txt @@ -0,0 +1,336 @@ +/******************************************************************************** +** Form generated from reading UI file 'mainwindow.ui' +** +** Created by: Qt User Interface Compiler version 5.15.8 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef MAINWINDOW_UI_H +#define MAINWINDOW_UI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QAction *actionAbout_qbolt; + QAction *actionAbout_Qt; + QAction *actionOpen_database; + QAction *actionExit; + QAction *actionDisconnect; + QAction *actionDelete_bucket; + QAction *actionRefresh_buckets; + QAction *actionClear_selection; + QAction *actionNew_database; + QAction *actionAdd_bucket; + QAction *actionOpen_database_as_read_only; + QWidget *centralWidget; + QGridLayout *gridLayout; + QSplitter *splitter; + QTreeWidget *bucketTree; + QStackedWidget *stackedWidget; + QWidget *databasePage; + QGridLayout *gridLayout_4; + QTabWidget *databaseTabWidget; + QWidget *databasePropertiesTab; + QGridLayout *gridLayout_2; + QPlainTextEdit *databasePropertiesArea; + QWidget *bucketPage; + QGridLayout *gridLayout_3; + QTabWidget *bucketTabWidget; + QWidget *bucketPropertiesTab; + QGridLayout *gridLayout_5; + QPlainTextEdit *bucketPropertiesArea; + QWidget *bucketDataTab; + QGridLayout *gridLayout_6; + QTreeWidget *bucketData; + QPushButton *AddDataButton; + QSpacerItem *horizontalSpacer; + QPushButton *DeleteDataButton; + QMenuBar *menuBar; + QMenu *menuFile; + QMenu *menuHelp; + QMenu *menuView; + QToolBar *mainToolBar; + QStatusBar *statusBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(668, 405); + QIcon icon; + icon.addFile(QString::fromUtf8(":/rsrc/database_lightning.png"), QSize(), QIcon::Normal, QIcon::Off); + MainWindow->setWindowIcon(icon); + actionAbout_qbolt = new QAction(MainWindow); + actionAbout_qbolt->setObjectName(QString::fromUtf8("actionAbout_qbolt")); + QIcon icon1; + icon1.addFile(QString::fromUtf8(":/rsrc/information.png"), QSize(), QIcon::Normal, QIcon::Off); + actionAbout_qbolt->setIcon(icon1); + actionAbout_Qt = new QAction(MainWindow); + actionAbout_Qt->setObjectName(QString::fromUtf8("actionAbout_Qt")); + actionOpen_database = new QAction(MainWindow); + actionOpen_database->setObjectName(QString::fromUtf8("actionOpen_database")); + QIcon icon2; + icon2.addFile(QString::fromUtf8(":/rsrc/database.png"), QSize(), QIcon::Normal, QIcon::Off); + actionOpen_database->setIcon(icon2); + actionExit = new QAction(MainWindow); + actionExit->setObjectName(QString::fromUtf8("actionExit")); + QIcon icon3; + icon3.addFile(QString::fromUtf8(":/rsrc/door_out.png"), QSize(), QIcon::Normal, QIcon::Off); + actionExit->setIcon(icon3); + actionDisconnect = new QAction(MainWindow); + actionDisconnect->setObjectName(QString::fromUtf8("actionDisconnect")); + QIcon icon4; + icon4.addFile(QString::fromUtf8(":/rsrc/disconnect.png"), QSize(), QIcon::Normal, QIcon::Off); + actionDisconnect->setIcon(icon4); + actionDelete_bucket = new QAction(MainWindow); + actionDelete_bucket->setObjectName(QString::fromUtf8("actionDelete_bucket")); + QIcon icon5; + icon5.addFile(QString::fromUtf8(":/rsrc/table_delete.png"), QSize(), QIcon::Normal, QIcon::Off); + actionDelete_bucket->setIcon(icon5); + actionRefresh_buckets = new QAction(MainWindow); + actionRefresh_buckets->setObjectName(QString::fromUtf8("actionRefresh_buckets")); + QIcon icon6; + icon6.addFile(QString::fromUtf8(":/rsrc/arrow_refresh.png"), QSize(), QIcon::Normal, QIcon::Off); + actionRefresh_buckets->setIcon(icon6); + actionClear_selection = new QAction(MainWindow); + actionClear_selection->setObjectName(QString::fromUtf8("actionClear_selection")); + actionNew_database = new QAction(MainWindow); + actionNew_database->setObjectName(QString::fromUtf8("actionNew_database")); + QIcon icon7; + icon7.addFile(QString::fromUtf8(":/rsrc/database_add.png"), QSize(), QIcon::Normal, QIcon::Off); + actionNew_database->setIcon(icon7); + actionAdd_bucket = new QAction(MainWindow); + actionAdd_bucket->setObjectName(QString::fromUtf8("actionAdd_bucket")); + QIcon icon8; + icon8.addFile(QString::fromUtf8(":/rsrc/table_add.png"), QSize(), QIcon::Normal, QIcon::Off); + actionAdd_bucket->setIcon(icon8); + actionOpen_database_as_read_only = new QAction(MainWindow); + actionOpen_database_as_read_only->setObjectName(QString::fromUtf8("actionOpen_database_as_read_only")); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + gridLayout = new QGridLayout(centralWidget); + gridLayout->setSpacing(6); + gridLayout->setContentsMargins(11, 11, 11, 11); + gridLayout->setObjectName(QString::fromUtf8("gridLayout")); + gridLayout->setContentsMargins(0, 0, 0, 0); + splitter = new QSplitter(centralWidget); + splitter->setObjectName(QString::fromUtf8("splitter")); + splitter->setOrientation(Qt::Horizontal); + splitter->setChildrenCollapsible(false); + bucketTree = new QTreeWidget(splitter); + bucketTree->setObjectName(QString::fromUtf8("bucketTree")); + bucketTree->setContextMenuPolicy(Qt::CustomContextMenu); + bucketTree->setUniformRowHeights(true); + splitter->addWidget(bucketTree); + stackedWidget = new QStackedWidget(splitter); + stackedWidget->setObjectName(QString::fromUtf8("stackedWidget")); + databasePage = new QWidget(); + databasePage->setObjectName(QString::fromUtf8("databasePage")); + gridLayout_4 = new QGridLayout(databasePage); + gridLayout_4->setSpacing(6); + gridLayout_4->setContentsMargins(11, 11, 11, 11); + gridLayout_4->setObjectName(QString::fromUtf8("gridLayout_4")); + gridLayout_4->setContentsMargins(0, 0, 0, 0); + databaseTabWidget = new QTabWidget(databasePage); + databaseTabWidget->setObjectName(QString::fromUtf8("databaseTabWidget")); + databasePropertiesTab = new QWidget(); + databasePropertiesTab->setObjectName(QString::fromUtf8("databasePropertiesTab")); + gridLayout_2 = new QGridLayout(databasePropertiesTab); + gridLayout_2->setSpacing(6); + gridLayout_2->setContentsMargins(11, 11, 11, 11); + gridLayout_2->setObjectName(QString::fromUtf8("gridLayout_2")); + gridLayout_2->setContentsMargins(3, 3, 3, 3); + databasePropertiesArea = new QPlainTextEdit(databasePropertiesTab); + databasePropertiesArea->setObjectName(QString::fromUtf8("databasePropertiesArea")); + databasePropertiesArea->setFrameShape(QFrame::NoFrame); + databasePropertiesArea->setReadOnly(true); + + gridLayout_2->addWidget(databasePropertiesArea, 0, 0, 1, 1); + + QIcon icon9; + icon9.addFile(QString::fromUtf8(":/rsrc/chart_bar.png"), QSize(), QIcon::Normal, QIcon::Off); + databaseTabWidget->addTab(databasePropertiesTab, icon9, QString()); + + gridLayout_4->addWidget(databaseTabWidget, 0, 0, 1, 1); + + stackedWidget->addWidget(databasePage); + bucketPage = new QWidget(); + bucketPage->setObjectName(QString::fromUtf8("bucketPage")); + gridLayout_3 = new QGridLayout(bucketPage); + gridLayout_3->setSpacing(6); + gridLayout_3->setContentsMargins(11, 11, 11, 11); + gridLayout_3->setObjectName(QString::fromUtf8("gridLayout_3")); + gridLayout_3->setContentsMargins(0, 0, 0, 0); + bucketTabWidget = new QTabWidget(bucketPage); + bucketTabWidget->setObjectName(QString::fromUtf8("bucketTabWidget")); + bucketPropertiesTab = new QWidget(); + bucketPropertiesTab->setObjectName(QString::fromUtf8("bucketPropertiesTab")); + gridLayout_5 = new QGridLayout(bucketPropertiesTab); + gridLayout_5->setSpacing(6); + gridLayout_5->setContentsMargins(11, 11, 11, 11); + gridLayout_5->setObjectName(QString::fromUtf8("gridLayout_5")); + gridLayout_5->setContentsMargins(3, 3, 3, 3); + bucketPropertiesArea = new QPlainTextEdit(bucketPropertiesTab); + bucketPropertiesArea->setObjectName(QString::fromUtf8("bucketPropertiesArea")); + bucketPropertiesArea->setFrameShape(QFrame::NoFrame); + bucketPropertiesArea->setReadOnly(true); + + gridLayout_5->addWidget(bucketPropertiesArea, 0, 0, 1, 1); + + bucketTabWidget->addTab(bucketPropertiesTab, icon9, QString()); + bucketDataTab = new QWidget(); + bucketDataTab->setObjectName(QString::fromUtf8("bucketDataTab")); + gridLayout_6 = new QGridLayout(bucketDataTab); + gridLayout_6->setSpacing(6); + gridLayout_6->setContentsMargins(11, 11, 11, 11); + gridLayout_6->setObjectName(QString::fromUtf8("gridLayout_6")); + gridLayout_6->setContentsMargins(3, 3, 3, 3); + bucketData = new QTreeWidget(bucketDataTab); + bucketData->setObjectName(QString::fromUtf8("bucketData")); + bucketData->setSelectionMode(QAbstractItemView::ExtendedSelection); + bucketData->setIndentation(0); + bucketData->setRootIsDecorated(false); + bucketData->setUniformRowHeights(true); + bucketData->setItemsExpandable(false); + + gridLayout_6->addWidget(bucketData, 0, 0, 1, 3); + + AddDataButton = new QPushButton(bucketDataTab); + AddDataButton->setObjectName(QString::fromUtf8("AddDataButton")); + QIcon icon10; + icon10.addFile(QString::fromUtf8(":/rsrc/add.png"), QSize(), QIcon::Normal, QIcon::Off); + AddDataButton->setIcon(icon10); + + gridLayout_6->addWidget(AddDataButton, 1, 0, 1, 1); + + horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + gridLayout_6->addItem(horizontalSpacer, 1, 2, 1, 1); + + DeleteDataButton = new QPushButton(bucketDataTab); + DeleteDataButton->setObjectName(QString::fromUtf8("DeleteDataButton")); + QIcon icon11; + icon11.addFile(QString::fromUtf8(":/rsrc/delete.png"), QSize(), QIcon::Normal, QIcon::Off); + DeleteDataButton->setIcon(icon11); + + gridLayout_6->addWidget(DeleteDataButton, 1, 1, 1, 1); + + QIcon icon12; + icon12.addFile(QString::fromUtf8(":/rsrc/table.png"), QSize(), QIcon::Normal, QIcon::Off); + bucketTabWidget->addTab(bucketDataTab, icon12, QString()); + + gridLayout_3->addWidget(bucketTabWidget, 0, 0, 1, 1); + + stackedWidget->addWidget(bucketPage); + splitter->addWidget(stackedWidget); + + gridLayout->addWidget(splitter, 0, 0, 1, 1); + + MainWindow->setCentralWidget(centralWidget); + menuBar = new QMenuBar(MainWindow); + menuBar->setObjectName(QString::fromUtf8("menuBar")); + menuBar->setGeometry(QRect(0, 0, 668, 21)); + menuFile = new QMenu(menuBar); + menuFile->setObjectName(QString::fromUtf8("menuFile")); + menuHelp = new QMenu(menuBar); + menuHelp->setObjectName(QString::fromUtf8("menuHelp")); + menuView = new QMenu(menuBar); + menuView->setObjectName(QString::fromUtf8("menuView")); + MainWindow->setMenuBar(menuBar); + mainToolBar = new QToolBar(MainWindow); + mainToolBar->setObjectName(QString::fromUtf8("mainToolBar")); + MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QString::fromUtf8("statusBar")); + MainWindow->setStatusBar(statusBar); + + menuBar->addAction(menuFile->menuAction()); + menuBar->addAction(menuView->menuAction()); + menuBar->addAction(menuHelp->menuAction()); + menuFile->addAction(actionNew_database); + menuFile->addAction(actionOpen_database); + menuFile->addAction(actionOpen_database_as_read_only); + menuFile->addSeparator(); + menuFile->addAction(actionExit); + menuHelp->addAction(actionAbout_qbolt); + menuHelp->addAction(actionAbout_Qt); + menuView->addAction(actionClear_selection); + mainToolBar->addAction(actionNew_database); + mainToolBar->addAction(actionOpen_database); + mainToolBar->addSeparator(); + + retranslateUi(MainWindow); + + stackedWidget->setCurrentIndex(0); + databaseTabWidget->setCurrentIndex(0); + bucketTabWidget->setCurrentIndex(0); + + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "QBolt", nullptr)); + actionAbout_qbolt->setText(QCoreApplication::translate("MainWindow", "&About QBolt", nullptr)); + actionAbout_Qt->setText(QCoreApplication::translate("MainWindow", "About &Qt", nullptr)); + actionOpen_database->setText(QCoreApplication::translate("MainWindow", "&Open database...", nullptr)); +#if QT_CONFIG(shortcut) + actionOpen_database->setShortcut(QCoreApplication::translate("MainWindow", "Ctrl+O", nullptr)); +#endif // QT_CONFIG(shortcut) + actionExit->setText(QCoreApplication::translate("MainWindow", "&Exit", nullptr)); + actionDisconnect->setText(QCoreApplication::translate("MainWindow", "Disconnect", nullptr)); + actionDelete_bucket->setText(QCoreApplication::translate("MainWindow", "Delete bucket", nullptr)); + actionRefresh_buckets->setText(QCoreApplication::translate("MainWindow", "Refresh buckets", nullptr)); + actionClear_selection->setText(QCoreApplication::translate("MainWindow", "&Clear selection", nullptr)); + actionNew_database->setText(QCoreApplication::translate("MainWindow", "&New database...", nullptr)); + actionAdd_bucket->setText(QCoreApplication::translate("MainWindow", "Add bucket...", nullptr)); + actionOpen_database_as_read_only->setText(QCoreApplication::translate("MainWindow", "Open database as read-only...", nullptr)); + QTreeWidgetItem *___qtreewidgetitem = bucketTree->headerItem(); + ___qtreewidgetitem->setText(0, QCoreApplication::translate("MainWindow", "Bucket", nullptr)); + databasePropertiesArea->setPlainText(QCoreApplication::translate("MainWindow", "No selection", nullptr)); + databaseTabWidget->setTabText(databaseTabWidget->indexOf(databasePropertiesTab), QCoreApplication::translate("MainWindow", "Database", nullptr)); + bucketTabWidget->setTabText(bucketTabWidget->indexOf(bucketPropertiesTab), QCoreApplication::translate("MainWindow", "Bucket", nullptr)); + QTreeWidgetItem *___qtreewidgetitem1 = bucketData->headerItem(); + ___qtreewidgetitem1->setText(1, QCoreApplication::translate("MainWindow", "Data length", nullptr)); + ___qtreewidgetitem1->setText(0, QCoreApplication::translate("MainWindow", "Key", nullptr)); + AddDataButton->setText(QCoreApplication::translate("MainWindow", "Add...", nullptr)); + DeleteDataButton->setText(QCoreApplication::translate("MainWindow", "Delete...", nullptr)); + bucketTabWidget->setTabText(bucketTabWidget->indexOf(bucketDataTab), QCoreApplication::translate("MainWindow", "Data", nullptr)); + menuFile->setTitle(QCoreApplication::translate("MainWindow", "Fi&le", nullptr)); + menuHelp->setTitle(QCoreApplication::translate("MainWindow", "Help", nullptr)); + menuView->setTitle(QCoreApplication::translate("MainWindow", "&View", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // MAINWINDOW_UI_H diff --git a/mainwindow_ui.go b/mainwindow_ui.go new file mode 100644 index 0000000..f387c4f --- /dev/null +++ b/mainwindow_ui.go @@ -0,0 +1,325 @@ +// Generated by miqt-uic. To update this file, edit the .ui file in +// Qt Designer, and then run 'go generate'. +// +//go:generate miqt-uic -InFile mainwindow.ui -OutFile mainwindow_ui.go + +package main + +import ( + "github.com/mappu/miqt/qt" +) + +type MainWindowUi struct { + MainWindow *qt.QMainWindow + centralWidget *qt.QWidget + gridLayout *qt.QGridLayout + splitter *qt.QSplitter + bucketTree *qt.QTreeWidget + stackedWidget *qt.QStackedWidget + databasePage *qt.QWidget + gridLayout_4 *qt.QGridLayout + databaseTabWidget *qt.QTabWidget + databasePropertiesTab *qt.QWidget + gridLayout_2 *qt.QGridLayout + databasePropertiesArea *qt.QPlainTextEdit + bucketPage *qt.QWidget + gridLayout_3 *qt.QGridLayout + bucketTabWidget *qt.QTabWidget + bucketPropertiesTab *qt.QWidget + gridLayout_5 *qt.QGridLayout + bucketPropertiesArea *qt.QPlainTextEdit + bucketDataTab *qt.QWidget + gridLayout_6 *qt.QGridLayout + bucketData *qt.QTreeWidget + AddDataButton *qt.QPushButton + horizontalSpacer *qt.QSpacerItem + DeleteDataButton *qt.QPushButton + menuBar *qt.QMenuBar + menuFile *qt.QMenu + menuHelp *qt.QMenu + menuView *qt.QMenu + mainToolBar *qt.QToolBar + statusBar *qt.QStatusBar + actionAbout_qbolt *qt.QAction + actionAbout_Qt *qt.QAction + actionOpen_database *qt.QAction + actionExit *qt.QAction + actionDisconnect *qt.QAction + actionDelete_bucket *qt.QAction + actionRefresh_buckets *qt.QAction + actionClear_selection *qt.QAction + actionNew_database *qt.QAction + actionAdd_bucket *qt.QAction + actionOpen_database_as_read_only *qt.QAction +} + +// NewMainWindowUi creates all Qt widget classes for MainWindow. +func NewMainWindowUi() *MainWindowUi { + ui := &MainWindowUi{} + + ui.MainWindow = qt.NewQMainWindow2(nil) + ui.MainWindow.SetObjectName("MainWindow") + ui.MainWindow.Resize(668, 405) + ui.MainWindow.SetWindowTitle("QBolt") + icon0 := qt.NewQIcon() + icon0.AddFile4(":/rsrc/database_lightning.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.MainWindow.SetWindowIcon(icon0) + + ui.actionAbout_qbolt = qt.NewQAction() + ui.actionAbout_qbolt.SetObjectName("actionAbout_qbolt") + icon1 := qt.NewQIcon() + icon1.AddFile4(":/rsrc/information.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionAbout_qbolt.SetIcon(icon1) + + ui.actionAbout_Qt = qt.NewQAction() + ui.actionAbout_Qt.SetObjectName("actionAbout_Qt") + + ui.actionOpen_database = qt.NewQAction() + ui.actionOpen_database.SetObjectName("actionOpen_database") + icon2 := qt.NewQIcon() + icon2.AddFile4(":/rsrc/database.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionOpen_database.SetIcon(icon2) + + ui.actionExit = qt.NewQAction() + ui.actionExit.SetObjectName("actionExit") + icon3 := qt.NewQIcon() + icon3.AddFile4(":/rsrc/door_out.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionExit.SetIcon(icon3) + + ui.actionDisconnect = qt.NewQAction() + ui.actionDisconnect.SetObjectName("actionDisconnect") + icon4 := qt.NewQIcon() + icon4.AddFile4(":/rsrc/disconnect.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionDisconnect.SetIcon(icon4) + + ui.actionDelete_bucket = qt.NewQAction() + ui.actionDelete_bucket.SetObjectName("actionDelete_bucket") + icon5 := qt.NewQIcon() + icon5.AddFile4(":/rsrc/table_delete.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionDelete_bucket.SetIcon(icon5) + + ui.actionRefresh_buckets = qt.NewQAction() + ui.actionRefresh_buckets.SetObjectName("actionRefresh_buckets") + icon6 := qt.NewQIcon() + icon6.AddFile4(":/rsrc/arrow_refresh.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionRefresh_buckets.SetIcon(icon6) + + ui.actionClear_selection = qt.NewQAction() + ui.actionClear_selection.SetObjectName("actionClear_selection") + + ui.actionNew_database = qt.NewQAction() + ui.actionNew_database.SetObjectName("actionNew_database") + icon7 := qt.NewQIcon() + icon7.AddFile4(":/rsrc/database_add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionNew_database.SetIcon(icon7) + + ui.actionAdd_bucket = qt.NewQAction() + ui.actionAdd_bucket.SetObjectName("actionAdd_bucket") + icon8 := qt.NewQIcon() + icon8.AddFile4(":/rsrc/table_add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionAdd_bucket.SetIcon(icon8) + + ui.actionOpen_database_as_read_only = qt.NewQAction() + ui.actionOpen_database_as_read_only.SetObjectName("actionOpen_database_as_read_only") + + ui.centralWidget = qt.NewQWidget2(ui.MainWindow.QWidget) + ui.centralWidget.SetObjectName("centralWidget") + + ui.gridLayout = qt.NewQGridLayout(ui.centralWidget) + ui.gridLayout.SetObjectName("gridLayout") + ui.gridLayout.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout.SetSpacing(6) + + ui.splitter = qt.NewQSplitter3(ui.centralWidget) + ui.splitter.SetObjectName("splitter") + ui.splitter.SetOrientation(qt.Horizontal) + ui.splitter.SetChildrenCollapsible(false) + + ui.bucketTree = qt.NewQTreeWidget2(ui.splitter.QWidget) + ui.bucketTree.SetObjectName("bucketTree") + ui.bucketTree.SetContextMenuPolicy(qt.CustomContextMenu) + ui.bucketTree.SetUniformRowHeights(true) + ui.splitter.AddWidget(ui.bucketTree.QWidget) + + ui.stackedWidget = qt.NewQStackedWidget2(ui.splitter.QWidget) + ui.stackedWidget.SetObjectName("stackedWidget") + + ui.databasePage = qt.NewQWidget2(ui.stackedWidget.QWidget) + ui.databasePage.SetObjectName("databasePage") + + ui.gridLayout_4 = qt.NewQGridLayout(ui.databasePage) + ui.gridLayout_4.SetObjectName("gridLayout_4") + ui.gridLayout_4.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_4.SetSpacing(6) + + ui.databaseTabWidget = qt.NewQTabWidget2(ui.databasePage) + ui.databaseTabWidget.SetObjectName("databaseTabWidget") + + ui.databasePropertiesTab = qt.NewQWidget2(ui.databaseTabWidget.QWidget) + ui.databasePropertiesTab.SetObjectName("databasePropertiesTab") + + ui.gridLayout_2 = qt.NewQGridLayout(ui.databasePropertiesTab) + ui.gridLayout_2.SetObjectName("gridLayout_2") + ui.gridLayout_2.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_2.SetSpacing(6) + + ui.databasePropertiesArea = qt.NewQPlainTextEdit3(ui.databasePropertiesTab) + ui.databasePropertiesArea.SetObjectName("databasePropertiesArea") + ui.databasePropertiesArea.SetFrameShape(qt.QFrame__NoFrame) + ui.databasePropertiesArea.SetReadOnly(true) + + ui.gridLayout_2.AddWidget2(ui.databasePropertiesArea.QWidget, 0, 0) + icon9 := qt.NewQIcon() + icon9.AddFile4(":/rsrc/chart_bar.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.databaseTabWidget.AddTab2(ui.databasePropertiesTab, icon9, "") + + ui.gridLayout_4.AddWidget2(ui.databaseTabWidget.QWidget, 0, 0) + ui.stackedWidget.AddWidget(ui.databasePage) + + ui.bucketPage = qt.NewQWidget2(ui.stackedWidget.QWidget) + ui.bucketPage.SetObjectName("bucketPage") + + ui.gridLayout_3 = qt.NewQGridLayout(ui.bucketPage) + ui.gridLayout_3.SetObjectName("gridLayout_3") + ui.gridLayout_3.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_3.SetSpacing(6) + + ui.bucketTabWidget = qt.NewQTabWidget2(ui.bucketPage) + ui.bucketTabWidget.SetObjectName("bucketTabWidget") + + ui.bucketPropertiesTab = qt.NewQWidget2(ui.bucketTabWidget.QWidget) + ui.bucketPropertiesTab.SetObjectName("bucketPropertiesTab") + + ui.gridLayout_5 = qt.NewQGridLayout(ui.bucketPropertiesTab) + ui.gridLayout_5.SetObjectName("gridLayout_5") + ui.gridLayout_5.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_5.SetSpacing(6) + + ui.bucketPropertiesArea = qt.NewQPlainTextEdit3(ui.bucketPropertiesTab) + ui.bucketPropertiesArea.SetObjectName("bucketPropertiesArea") + ui.bucketPropertiesArea.SetFrameShape(qt.QFrame__NoFrame) + ui.bucketPropertiesArea.SetReadOnly(true) + + ui.gridLayout_5.AddWidget2(ui.bucketPropertiesArea.QWidget, 0, 0) + icon10 := qt.NewQIcon() + icon10.AddFile4(":/rsrc/chart_bar.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.bucketTabWidget.AddTab2(ui.bucketPropertiesTab, icon10, "") + + ui.bucketDataTab = qt.NewQWidget2(ui.bucketTabWidget.QWidget) + ui.bucketDataTab.SetObjectName("bucketDataTab") + + ui.gridLayout_6 = qt.NewQGridLayout(ui.bucketDataTab) + ui.gridLayout_6.SetObjectName("gridLayout_6") + ui.gridLayout_6.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_6.SetSpacing(6) + + ui.bucketData = qt.NewQTreeWidget2(ui.bucketDataTab) + ui.bucketData.SetObjectName("bucketData") + ui.bucketData.SetSelectionMode(qt.QAbstractItemView__ExtendedSelection) + ui.bucketData.SetIndentation(0) + ui.bucketData.SetRootIsDecorated(false) + ui.bucketData.SetUniformRowHeights(true) + ui.bucketData.SetItemsExpandable(false) + + ui.gridLayout_6.AddWidget2(ui.bucketData.QWidget, 0, 0) + + ui.AddDataButton = qt.NewQPushButton4(ui.bucketDataTab) + ui.AddDataButton.SetObjectName("AddDataButton") + icon11 := qt.NewQIcon() + icon11.AddFile4(":/rsrc/add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.AddDataButton.SetIcon(icon11) + + ui.gridLayout_6.AddWidget2(ui.AddDataButton.QWidget, 1, 0) + /* miqt-uic: no handler for spacer */ + + ui.DeleteDataButton = qt.NewQPushButton4(ui.bucketDataTab) + ui.DeleteDataButton.SetObjectName("DeleteDataButton") + icon12 := qt.NewQIcon() + icon12.AddFile4(":/rsrc/delete.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.DeleteDataButton.SetIcon(icon12) + + ui.gridLayout_6.AddWidget2(ui.DeleteDataButton.QWidget, 1, 1) + icon13 := qt.NewQIcon() + icon13.AddFile4(":/rsrc/table.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.bucketTabWidget.AddTab2(ui.bucketDataTab, icon13, "") + + ui.gridLayout_3.AddWidget2(ui.bucketTabWidget.QWidget, 0, 0) + ui.stackedWidget.AddWidget(ui.bucketPage) + ui.splitter.AddWidget(ui.stackedWidget.QWidget) + + ui.gridLayout.AddWidget2(ui.splitter.QWidget, 0, 0) + ui.MainWindow.SetCentralWidget(ui.centralWidget) // Set central widget + + ui.menuBar = qt.NewQMenuBar2(ui.MainWindow.QWidget) + ui.menuBar.SetObjectName("menuBar") + ui.menuBar.Resize(668, 21) + + ui.menuFile = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuFile.SetObjectName("menuFile") + ui.menuFile.QWidget.AddAction(ui.actionNew_database) + ui.menuFile.QWidget.AddAction(ui.actionOpen_database) + ui.menuFile.QWidget.AddAction(ui.actionOpen_database_as_read_only) + ui.menuFile.AddSeparator() + ui.menuFile.QWidget.AddAction(ui.actionExit) + + ui.menuHelp = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuHelp.SetObjectName("menuHelp") + ui.menuHelp.QWidget.AddAction(ui.actionAbout_qbolt) + ui.menuHelp.QWidget.AddAction(ui.actionAbout_Qt) + + ui.menuView = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuView.SetObjectName("menuView") + ui.menuView.QWidget.AddAction(ui.actionClear_selection) + ui.menuBar.AddMenu(ui.menuFile) + ui.menuBar.AddMenu(ui.menuView) + ui.menuBar.AddMenu(ui.menuHelp) + ui.MainWindow.SetMenuBar(ui.menuBar) + + ui.mainToolBar = qt.NewQToolBar4(ui.MainWindow.QWidget) + ui.mainToolBar.SetObjectName("mainToolBar") + ui.MainWindow.AddToolBar(qt.TopToolBarArea, ui.mainToolBar) + /* miqt-uic: no handler for mainToolBar attribute 'toolBarBreak' */ + ui.mainToolBar.QWidget.AddAction(ui.actionNew_database) + ui.mainToolBar.QWidget.AddAction(ui.actionOpen_database) + ui.mainToolBar.AddSeparator() + + ui.statusBar = qt.NewQStatusBar2(ui.MainWindow.QWidget) + ui.statusBar.SetObjectName("statusBar") + ui.MainWindow.SetStatusBar(ui.statusBar) + + ui.Retranslate() + + ui.stackedWidget.SetCurrentIndex(0) + ui.databaseTabWidget.SetCurrentIndex(0) + ui.bucketTabWidget.SetCurrentIndex(0) + + return ui +} + +// Retranslate reapplies all text translations. +func (ui *MainWindowUi) Retranslate() { + ui.actionAbout_qbolt.SetText(qt.QMainWindow_Tr("&About QBolt")) + ui.actionAbout_Qt.SetText(qt.QMainWindow_Tr("About &Qt")) + ui.actionOpen_database.SetText(qt.QMainWindow_Tr("&Open database...")) + ui.actionOpen_database.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("Ctrl+O"))) + ui.actionExit.SetText(qt.QMainWindow_Tr("&Exit")) + ui.actionDisconnect.SetText(qt.QMainWindow_Tr("Disconnect")) + ui.actionDelete_bucket.SetText(qt.QMainWindow_Tr("Delete bucket")) + ui.actionRefresh_buckets.SetText(qt.QMainWindow_Tr("Refresh buckets")) + ui.actionClear_selection.SetText(qt.QMainWindow_Tr("&Clear selection")) + ui.actionNew_database.SetText(qt.QMainWindow_Tr("&New database...")) + ui.actionAdd_bucket.SetText(qt.QMainWindow_Tr("Add bucket...")) + ui.actionOpen_database_as_read_only.SetText(qt.QMainWindow_Tr("Open database as read-only...")) + ui.bucketTree.HeaderItem().SetText(0, qt.QTreeWidget_Tr("Bucket")) + ui.databaseTabWidget.SetTabText(ui.databaseTabWidget.IndexOf(ui.databasePropertiesTab), qt.QTabWidget_Tr("Database")) + ui.databasePropertiesArea.SetPlainText(qt.QWidget_Tr("No selection")) + ui.bucketTabWidget.SetTabText(ui.bucketTabWidget.IndexOf(ui.bucketPropertiesTab), qt.QTabWidget_Tr("Bucket")) + ui.bucketTabWidget.SetTabText(ui.bucketTabWidget.IndexOf(ui.bucketDataTab), qt.QTabWidget_Tr("Data")) + ui.bucketData.HeaderItem().SetText(0, qt.QTreeWidget_Tr("Key")) + ui.bucketData.HeaderItem().SetText(1, qt.QTreeWidget_Tr("Data length")) + ui.AddDataButton.SetText(qt.QWidget_Tr("Add...")) + ui.DeleteDataButton.SetText(qt.QWidget_Tr("Delete...")) + ui.menuFile.SetTitle(qt.QMenuBar_Tr("Fi&le")) + ui.menuHelp.SetTitle(qt.QMenuBar_Tr("Help")) + ui.menuView.SetTitle(qt.QMenuBar_Tr("&View")) +} diff --git a/qbolt/boltdb.cpp b/qbolt/boltdb.cpp deleted file mode 100644 index 2aa8db3..0000000 --- a/qbolt/boltdb.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "boltdb.h" - -#include - -BoltDB::BoltDB() -{ - -} - -BoltDB* BoltDB::createFrom(QString filePath, bool readOnly, QString &errorOut) -{ - QByteArray filePathBytes(filePath.toUtf8()); - GoString filePathGS = Interop::toGoString_WeakRef(&filePathBytes); - - auto open_ret = ::Bolt_Open(readOnly, filePathGS); - if (open_ret.r2 != 0) { - errorOut = QString::fromUtf8(open_ret.r1, open_ret.r2); - free(open_ret.r1); - return nullptr; - } - - BoltDB *ret = new BoltDB(); - ret->gmsDbRef = open_ret.r0; - return ret; -} - -static const int ERROR_AND_STOP_CALLING = 100; -static const int ERROR_AND_KEEP_CALLING = 101; -static const int FINISHED_OK = 102; -static const int REAL_MESSAGE = 103; - -static bool handleTriple(int r0, char* r1, int64_t r2, QString& errorOut) { - if (r0 == ERROR_AND_STOP_CALLING) { - errorOut = QString::fromUtf8(r1, r2); - free(r1); - return false; - - } else if (r0 == FINISHED_OK) { - return true; - - } else { - // ?? unreachable - return false; - - } -} - -bool BoltDB::addBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName); - auto resp = ::Bolt_CreateBucket(this->gmsDbRef, browse.slice, bucketNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::deleteBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName); - auto resp = ::Bolt_DeleteBucket(this->gmsDbRef, browse.slice, bucketNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::setItem(const QList& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyNameGS = Interop::toGoString_WeakRef(&keyName); - GoString valueGS = Interop::toGoString_WeakRef(&value); - auto resp = ::Bolt_SetItem(this->gmsDbRef, browse.slice, keyNameGS, valueGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::deleteItem(const QList& bucketPath, QByteArray keyName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyNameGS = Interop::toGoString_WeakRef(&keyName); - auto resp = ::Bolt_DeleteItem(this->gmsDbRef, browse.slice, keyNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - - -bool BoltDB::listBucketsAtRoot(QString& errorOut, NameReciever cb) -{ - auto listJob = ::Bolt_ListBucketsAtRoot(this->gmsDbRef); - return pumpNext(listJob, errorOut, cb); -} - -bool BoltDB::listBuckets(const QList& bucketPath, QString& errorOut, NameReciever cb) -{ - if (bucketPath.size() == 0) { - return listBucketsAtRoot(errorOut, cb); - } - - GoSliceManagedWrapper browse(bucketPath); - auto listJob = ::Bolt_ListBuckets(this->gmsDbRef, browse.slice); - return pumpNext(listJob, errorOut, cb); -} - -bool BoltDB::listKeys(const QList& bucketPath, QString& errorOut, std::function cb) -{ - GoSliceManagedWrapper browse(bucketPath); - auto listJob = ::Bolt_ListItems(this->gmsDbRef, browse.slice); - return pumpNext(listJob, errorOut, [=](QByteArray b) { - // First 8 bytes are little-endian uint64 len - int64_t dataLen = qFromLittleEndian(b.mid(0, 8)); - cb(b.mid(8), dataLen); - }); -} - -bool BoltDB::getData(const QList& bucketPath, QByteArray key, std::function onSuccess, std::function onError) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyGS = Interop::toGoString_WeakRef(&key); - auto resp = ::Bolt_GetItem(this->gmsDbRef, browse.slice, keyGS); - - if (resp.r0 == ERROR_AND_STOP_CALLING) { - onError(QString::fromUtf8(resp.r1, resp.r2)); - free(resp.r1); - return false; - - } else if (resp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(resp.r1, resp.r2)); - free(resp.r1); - return true; - - } else { - // ?? unreachable - return false; - - } -} - -bool BoltDB::pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb) -{ - errorOut.clear(); - - for(;;) { - auto gnr = ::GetNext(jobRef); - - if (gnr.r0 == ERROR_AND_STOP_CALLING) { - errorOut.append(QString::fromUtf8(gnr.r1, gnr.r2)); // log error - free(gnr.r1); - break; // done - - } else if (gnr.r0 == ERROR_AND_KEEP_CALLING) { - errorOut.append(QString::fromUtf8(gnr.r1, gnr.r2)); // log error - free(gnr.r1); - continue; - - } else if (gnr.r0 == FINISHED_OK) { - // Once we hit this, the go-side will clean up the channel / associated goroutines - break; - - } else if (gnr.r0 == REAL_MESSAGE) { - cb(QByteArray(gnr.r1, gnr.r2)); - free(gnr.r1); - continue; - } - } - - return (errorOut.length() == 0); -} - -bool BoltDB::getStatsJSON(std::function onSuccess, std::function onError) -{ - auto statresp = Bolt_DBStats(this->gmsDbRef); - - if (statresp.r0 == ERROR_AND_STOP_CALLING) { - onError(QString::fromUtf8(statresp.r1, statresp.r2)); - free(statresp.r1); - return false; - - } else if (statresp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(statresp.r1, statresp.r2)); - free(statresp.r1); - return true; - - } else { - // ?? shouldn't be reachable - return false; - } -} - -bool BoltDB::getBucketStatsJSON(const QList& bucketPath, std::function onSuccess, std::function onError) -{ - GoSliceManagedWrapper sliceWrapper(bucketPath); - auto statresp = Bolt_BucketStats(this->gmsDbRef, sliceWrapper.slice); - - if (statresp.r0 == ERROR_AND_STOP_CALLING) { - QString err = QString::fromUtf8(statresp.r1, statresp.r2); - free(statresp.r1); - onError(err); - return false; - - } else if (statresp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(statresp.r1, statresp.r2)); - free(statresp.r1); - return true; - - } else { - // ?? shouldn't be reachable - return false; - } -} - -BoltDB::~BoltDB() -{ - auto err = ::Bolt_Close(this->gmsDbRef); - if (err.r1 != 0) { - // Error closing database! - // Need to display an alert... somewhere - - free(err.r0); - } -} diff --git a/qbolt/boltdb.h b/qbolt/boltdb.h deleted file mode 100644 index 65fe09e..0000000 --- a/qbolt/boltdb.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef BOLTDB_H -#define BOLTDB_H - -#include "interop.h" -#include - -typedef std::function NameReciever; - -class BoltDB -{ -protected: - BoltDB(); - - GoInt64 gmsDbRef; - -public: - static BoltDB* createFrom(QString filePath, bool readOnly, QString &errorOut); - - bool listBucketsAtRoot(QString& errorOut, NameReciever cb); - - bool listBuckets(const QList& bucketPath, QString& errorOut, NameReciever cb); - - bool addBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut); - - bool deleteBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut); - - bool setItem(const QList& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut); - - bool deleteItem(const QList& bucketPath, QByteArray keyName, QString& errorOut); - - bool listKeys(const QList& bucketPath, QString& errorOut, std::function cb); - - bool getData(const QList& bucketPath, QByteArray key, std::function onSuccess, std::function onError); - - bool getStatsJSON(std::function onSuccess, std::function onError); - - bool getBucketStatsJSON(const QList& bucketPath, std::function onSuccess, std::function onError); - - ~BoltDB(); - -protected: - - bool pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb); -}; - -#endif // BOLTDB_H diff --git a/qbolt/interop.cpp b/qbolt/interop.cpp deleted file mode 100644 index a4e173f..0000000 --- a/qbolt/interop.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "interop.h" - -#include - -Interop::Interop() -{ - -} - -GoString Interop::toGoString_WeakRef(QByteArray *qba) { - return GoString{qba->data(), qba->length()}; -} - -int64_t Interop::GetMagic() { - return ::GetMagic(); -} - -// - -GoSliceManagedWrapper::GoSliceManagedWrapper(const QList& qsl) : - rawStrings(), - slice(), - strings(nullptr) -{ - rawStrings.reserve(qsl.size()); - strings = new GoString[qsl.size()]; - - for (int i = 0; i < qsl.size(); ++i) { - rawStrings.push_back( qsl.at(i) ); - strings[i].p = rawStrings[i].data(); - strings[i].n = rawStrings[i].size(); - } - - slice.data = static_cast(strings); - slice.len = qsl.size(); // * sizeof(GoString); - slice.cap = slice.len; -} - -GoSliceManagedWrapper::~GoSliceManagedWrapper() -{ - delete[] strings; -} diff --git a/qbolt/interop.h b/qbolt/interop.h deleted file mode 100644 index fabb163..0000000 --- a/qbolt/interop.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef INTEROP_H -#define INTEROP_H - -#include "qbolt_cgo.h" -#include -#include - -class GoSliceManagedWrapper { - Q_DISABLE_COPY(GoSliceManagedWrapper) - -public: - GoSliceManagedWrapper(const QList& qsl); - ~GoSliceManagedWrapper(); -protected: - QList rawStrings; -public: - GoSlice slice; - GoString *strings; -}; - -class Interop -{ -public: - Interop(); - - static GoString toGoString_WeakRef(QByteArray *qba); - - static int64_t GetMagic(); -}; - -#endif // INTEROP_H diff --git a/qbolt/itemwindow.cpp b/qbolt/itemwindow.cpp deleted file mode 100644 index 6ba97bf..0000000 --- a/qbolt/itemwindow.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "itemwindow.h" -#include "ui_itemwindow.h" - -ItemWindow::ItemWindow(QWidget *parent) : - QDialog(parent), - ui(new Ui::ItemWindow) -{ - ui->setupUi(this); -} - -ItemWindow::~ItemWindow() -{ - delete ui; -} - -QPlainTextEdit* ItemWindow::ContentArea() const { - return ui->contentArea; -} diff --git a/qbolt/itemwindow.h b/qbolt/itemwindow.h deleted file mode 100644 index 326ae52..0000000 --- a/qbolt/itemwindow.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ITEMWINDOW_H -#define ITEMWINDOW_H - -#include -#include - -namespace Ui { -class ItemWindow; -} - -class ItemWindow : public QDialog -{ - Q_OBJECT - -public: - explicit ItemWindow(QWidget *parent = 0); - ~ItemWindow(); - - QPlainTextEdit* ContentArea() const; - -private: - Ui::ItemWindow *ui; -}; - -#endif // ITEMWINDOW_H diff --git a/qbolt/main.cpp b/qbolt/main.cpp deleted file mode 100644 index 7ee0f34..0000000 --- a/qbolt/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "mainwindow.h" -#include -#include "interop.h" - -#include - -int main(int argc, char *argv[]) -{ - int64_t magic = Interop::GetMagic(); - if (magic != 0x10203040) { - qDebug() << "bad magic " << magic; - return 1; - } - - QApplication a(argc, argv); - QApplication::setApplicationDisplayName("QBolt"); - QApplication::setWindowIcon(QIcon(":/rsrc/database_lightning.png")); - - MainWindow w; - w.show(); - - return a.exec(); -} diff --git a/qbolt/mainwindow.cpp b/qbolt/mainwindow.cpp deleted file mode 100644 index d149055..0000000 --- a/qbolt/mainwindow.cpp +++ /dev/null @@ -1,478 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "itemwindow.h" -#include "boltdb.h" - -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - on_bucketTree_currentItemChanged(nullptr, nullptr); - - databaseContext = new QMenu(); - databaseContext->addAction(ui->actionRefresh_buckets); - databaseContext->addAction(ui->actionAdd_bucket); - databaseContext->addSeparator(); - databaseContext->addAction(ui->actionDisconnect); - - bucketContext = new QMenu(); - bucketContext->addAction(ui->actionRefresh_buckets); - bucketContext->addAction(ui->actionAdd_bucket); - bucketContext->addSeparator(); - bucketContext->addAction(ui->actionDelete_bucket); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -static const int BdbPointerRole = Qt::UserRole + 1; -static const int BinaryDataRole = Qt::UserRole + 2; - -#define SET_BDB(top, bdb) top->setData(0, BdbPointerRole, QVariant::fromValue(static_cast(bdb))) -#define GET_BDB(top) static_cast( top->data(0, BdbPointerRole).value() ) - -void MainWindow::on_actionNew_database_triggered() -{ - QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as...")); - if (file.length()) { - openDatabase(file, false); - } -} - -void MainWindow::on_actionOpen_database_triggered() -{ - QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database...")); - if (file.length()) { - openDatabase(file, false); - } -} - -void MainWindow::on_actionOpen_database_as_read_only_triggered() -{ - QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database...")); - if (file.length()) { - openDatabase(file, true); - } -} - -void MainWindow::openDatabase(QString file, bool readOnly) -{ - // Open - QString error; - auto *bdb = BoltDB::createFrom(file, readOnly, error); - if (bdb == nullptr) { - QMessageBox qmb; - qmb.setText(tr("Error opening database: %1").arg(error)); - qmb.exec(); - return; - } - - QTreeWidgetItem *top = new QTreeWidgetItem(); - top->setText(0, QFileInfo(file).fileName()); - top->setIcon(0, QIcon(":/rsrc/database.png")); - SET_BDB(top, bdb); - ui->bucketTree->addTopLevelItem(top); - - refreshBucketTree(top); - ui->bucketTree->setCurrentItem(top); - - ui->bucketTree->expandItem(top); -} - -static const QString getDisplayName(const QByteArray &qba) { - // FIXME the formatting isn't so great when control characters, etc. are used - // A C-style escape display, or the unicode-replacement-character would be preferable - QString ret(QString::fromUtf8(qba)); - - bool allPrintable = true; - for (auto i = ret.begin(), e = ret.end(); i != e; ++i) { - if (! i->isPrint()) { - allPrintable = false; - break; - } - } - - if (allPrintable) { - return ret; // fine - } - - // Some of the characters weren't printable. - // Build up a replacement string - QString replacement; - for (auto i = ret.begin(), e = ret.end(); i != e; ++i) { - replacement += i->isPrint() ? *i : QStringLiteral("\\u{%1}").arg(i->unicode()); - } - return replacement; -} - -void MainWindow::refreshBucketTree(QTreeWidgetItem* itm) -{ - QTreeWidgetItem *top = itm; - QList browsePath; - while(top->parent() != nullptr) { - browsePath.push_front(top->data(0, BinaryDataRole).toByteArray()); - top = top->parent(); - } - - // Remove existing children - for (int i = itm->childCount(); i --> 0;) { - delete itm->takeChild(i); - } - - auto *bdb = GET_BDB(top); - - QString error; - bool ok = bdb->listBuckets( - browsePath, - error, - [=](QByteArray qba){ - QTreeWidgetItem *child = new QTreeWidgetItem(); - child->setText(0, getDisplayName(qba)); - child->setData(0, BinaryDataRole, qba); - child->setIcon(0, QIcon(":/rsrc/table.png")); - itm->addChild(child); - - refreshBucketTree(child); - } - ); - - if (!ok) { - QMessageBox qmb; - qmb.setText(tr("Error listing buckets: %1").arg(error)); - qmb.exec(); - // (continue) - } -} - -void MainWindow::on_actionExit_triggered() -{ - close(); -} - -void MainWindow::on_actionAbout_Qt_triggered() -{ - QApplication::aboutQt(); -} - -void MainWindow::on_actionAbout_qbolt_triggered() -{ - QMessageBox::about( - this, - QApplication::applicationDisplayName(), - "QBolt
Graphical interface for managing Bolt databases

" - "- About BoltDB
" - "- FamFamFam "Silk" icon set
" - "- QBolt homepage
" - ); -} - -void MainWindow::on_actionDisconnect_triggered() -{ - QTreeWidgetItem *top = lastContextSelection; - if (top->parent()) { - return; // somehow we didn't select a top-level item - } - - auto *bdb = GET_BDB(top); - - // Remove UI - ui->bucketTree->clearSelection(); - delete top; - - // Disconnect from DB - delete bdb; -} - -void MainWindow::on_bucketTree_customContextMenuRequested(const QPoint &pos) -{ - auto *itm = ui->bucketTree->itemAt(pos); - if (itm == nullptr) { - return; - } - - lastContextSelection = itm; - - if (itm->parent() != nullptr) { - // Child item, show the bucket menu - bucketContext->popup(ui->bucketTree->mapToGlobal(pos)); - - } else { - // Top-level item, show the database menu - databaseContext->popup(ui->bucketTree->mapToGlobal(pos)); - } -} - -void MainWindow::on_actionRefresh_buckets_triggered() -{ - refreshBucketTree(lastContextSelection); -} - -void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) -{ - Q_UNUSED(previous); - if (current == nullptr) { - ui->stackedWidget->setVisible(false); - return; - } - - ui->stackedWidget->setVisible(true); - - if (current->parent() == nullptr) { - // Selected a database - ui->stackedWidget->setCurrentWidget(ui->databasePage); - ui->databasePropertiesArea->clear(); - - auto *bdb = GET_BDB(current); - bdb->getStatsJSON( - [=](QByteArray j) { - auto doc = QJsonDocument::fromJson(j); - ui->databasePropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); - }, - [=](QString error) { - ui->databasePropertiesArea->setPlainText(tr("Error retrieving database statistics: %1").arg(error)); - } - ); - - // Clean up foreign areas - ui->bucketPropertiesArea->clear(); - ui->bucketData->clear(); - - } else { - // Selected a bucket - - ui->stackedWidget->setCurrentWidget(ui->bucketPage); - ui->bucketPropertiesArea->clear(); - - QList browse; - QTreeWidgetItem *top = current; - while (top->parent() != nullptr) { - browse.push_front(top->data(0, BinaryDataRole).toByteArray()); - top = top->parent(); - } - auto *bdb = GET_BDB(top); - - bdb->getBucketStatsJSON( - browse, - [=](QByteArray j) { - auto doc = QJsonDocument::fromJson(j); - ui->bucketPropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); - }, - [=](QString error) { - ui->bucketPropertiesArea->setPlainText(tr("Error retrieving bucket statistics: %1").arg(error)); - } - ); - - // Load the data tab - refreshData(bdb, browse); - - // Clean up foreign areas - ui->databasePropertiesArea->clear(); - } -} - -void MainWindow::refreshData(BoltDB *bdb, const QList& browse) -{ - // Load the data tab - ui->bucketData->clear(); - QString err; - bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) { - auto *itm = new QTreeWidgetItem(); - itm->setText(0, getDisplayName(name)); - itm->setData(0, BinaryDataRole, name); - itm->setText(1, QString("%1").arg(dataLen)); - ui->bucketData->addTopLevelItem(itm); - }); - - if (! ok) { - QMessageBox qmb; - qmb.setText(tr("Error listing bucket content: %1").arg(err)); - qmb.exec(); - } - - ui->bucketData->resizeColumnToContents(0); - on_bucketData_itemSelectionChanged(); -} - -void MainWindow::on_actionClear_selection_triggered() -{ - ui->bucketTree->setCurrentItem(nullptr); -} - -#define GET_ITM_TOP_BROWSE_BDB \ - QTreeWidgetItem* itm = ui->bucketTree->currentItem(); \ - if (itm == nullptr) { \ - return; \ - } \ - QTreeWidgetItem* top = itm; \ - QList browse; \ - while(top->parent() != nullptr) { \ - browse.push_front(top->data(0, BinaryDataRole).toByteArray()); \ - top = top->parent(); \ - } \ - auto *bdb = GET_BDB(top); - -void MainWindow::openEditor(BoltDB *bdb, const QList& saveAs, QByteArray saveAsKey, QByteArray currentContent) -{ - auto iw = new ItemWindow(); - iw->ContentArea()->setPlainText(QString::fromUtf8(currentContent)); - iw->setWindowTitle(QString::fromUtf8(saveAsKey)); - iw->setWindowModality(Qt::ApplicationModal); // we need this - otherwise we'll refresh a possibly-changed area after saving - connect(iw, &ItemWindow::finished, iw, [=](int exitCode){ - if (exitCode == ItemWindow::Accepted) { - QString err; - if (! bdb->setItem(saveAs, saveAsKey, iw->ContentArea()->toPlainText().toUtf8(), err)) { - QMessageBox qmb; - qmb.setText(tr("Error saving item content: %1").arg(err)); - qmb.exec(); - } - - refreshData(bdb, saveAs); - } - iw->deleteLater(); - }); - - iw->show(); -} - -void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index) -{ - GET_ITM_TOP_BROWSE_BDB; - - // Get item key - - auto model = index.model(); - const QByteArray& key = model->data(model->index(index.row(), 0), BinaryDataRole).toByteArray(); - - // DB lookup - - bdb->getData( - browse, - key, - [=](QByteArray content) { - openEditor(bdb, browse, key, content); - }, - [=](QString error) { - QMessageBox qmb; - qmb.setText(tr("Error loading item content: %1").arg(error)); - qmb.exec(); - } - ); - -} - -void MainWindow::on_actionAdd_bucket_triggered() -{ - GET_ITM_TOP_BROWSE_BDB; - - // Prompt for bucket name - - QString name = QInputDialog::getText(this, tr("New bucket"), tr("Enter a key for the new bucket:")); - if (name.length() == 0) { - return; - } - - // Create - QString err; - if (! bdb->addBucket(browse, name.toUtf8(), err)) { - QMessageBox qmb; - qmb.setText(tr("Error creating bucket: %1").arg(err)); - qmb.exec(); - return; - } - - // Refresh bucket list - refreshBucketTree(itm); // sub-tree only - ui->bucketTree->expandItem(itm); -} - -void MainWindow::on_actionDelete_bucket_triggered() -{ - GET_ITM_TOP_BROWSE_BDB; - - // Prompt for confirmation - const QByteArray& bucketToDelete = itm->data(0, BinaryDataRole).toByteArray(); - if ( - QMessageBox::question( - this, - tr("Delete bucket"), - tr("Are you sure you want to remove the bucket '%1'?").arg(getDisplayName(bucketToDelete)), - QMessageBox::Yes, - QMessageBox::Cancel - ) != QMessageBox::Yes - ) { - return; - } - - QTreeWidgetItem* parent = itm->parent(); - - // One level down - - browse.pop_back(); - - QString err; - if (! bdb->deleteBucket(browse, bucketToDelete, err)) { - QMessageBox qmb; - qmb.setText(tr("Error removing bucket: %1").arg(err)); - qmb.exec(); - return; - } - - // Refresh bucket list - refreshBucketTree(parent); // sub-tree only - ui->bucketTree->expandItem(parent); - ui->bucketTree->setCurrentItem(parent); -} - -void MainWindow::on_AddDataButton_clicked() -{ - GET_ITM_TOP_BROWSE_BDB; - - // Prompt for bucket name - - QString name = QInputDialog::getText(this, tr("New item"), tr("Enter a key for the new item:")); - if (name.length() == 0) { - return; - } - - openEditor(bdb, browse, name.toUtf8(), QByteArray()); -} - -void MainWindow::on_DeleteDataButton_clicked() -{ - GET_ITM_TOP_BROWSE_BDB; - - auto selection = ui->bucketData->selectedItems(); - if (selection.length() == 0) { - return; // nothing to do - } - - // Prompt for confirmation - if (QMessageBox::question(this, tr("Delete items"), tr("Are you sure you want to remove %1 item(s)?").arg(selection.length()), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) { - return; - } - - QString err; - for (int i = selection.length(); i-->0;) { - if (! bdb->deleteItem(browse, selection[i]->data(0, BinaryDataRole).toByteArray(), err)) { - QMessageBox qmb; - qmb.setText(tr("Error removing item: %1").arg(err)); - qmb.exec(); - } - } - - refreshData(bdb, browse); -} - -void MainWindow::on_bucketData_itemSelectionChanged() -{ - ui->DeleteDataButton->setEnabled( (ui->bucketData->selectedItems().size() > 0) ); -} diff --git a/qbolt/mainwindow.h b/qbolt/mainwindow.h deleted file mode 100644 index ae541c8..0000000 --- a/qbolt/mainwindow.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "boltdb.h" -#include -#include -#include - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: - void on_actionOpen_database_triggered(); - - void on_actionExit_triggered(); - - void on_actionAbout_Qt_triggered(); - - void on_actionAbout_qbolt_triggered(); - - void on_actionDisconnect_triggered(); - - void on_bucketTree_customContextMenuRequested(const QPoint &pos); - - void on_actionRefresh_buckets_triggered(); - - void on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); - - void on_actionClear_selection_triggered(); - - void on_bucketData_doubleClicked(const QModelIndex &index); - - void on_actionNew_database_triggered(); - - void on_actionAdd_bucket_triggered(); - - void on_actionDelete_bucket_triggered(); - - void on_AddDataButton_clicked(); - - void on_DeleteDataButton_clicked(); - - void on_bucketData_itemSelectionChanged(); - - void on_actionOpen_database_as_read_only_triggered(); - -protected: - void openDatabase(QString file, bool readOnly); - void refreshBucketTree(QTreeWidgetItem* top); - void refreshData(BoltDB *bdb, const QList& browse); - void openEditor(BoltDB *bdb, const QList& saveAs, QByteArray saveAsKey, QByteArray currentContent); - -private: - Ui::MainWindow *ui; - - QMenu *databaseContext; - QMenu *bucketContext; - QTreeWidgetItem* lastContextSelection; -}; - -#endif // MAINWINDOW_H diff --git a/qbolt/qbolt.pro b/qbolt/qbolt.pro deleted file mode 100644 index 402b58d..0000000 --- a/qbolt/qbolt.pro +++ /dev/null @@ -1,48 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2017-05-15T19:38:33 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = qbolt -TEMPLATE = app - -# Enforce Qt deprecations -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 - -win32: { - # for some reason, qbolt_cgo.h never realises that Q_OS_WIN is defined for win32 builds... weird - DEFINES += CGO_WINDOWS - QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/win32/qbolt.a - QMAKE_LIBS += -lntdll - - RC_ICONS = rsrc/qbolt.ico -} - -linux: { - QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/linux/qbolt.a - QMAKE_LIBS += -lpthread -} - -SOURCES += main.cpp\ - mainwindow.cpp \ - interop.cpp \ - boltdb.cpp \ - itemwindow.cpp - -HEADERS += mainwindow.h \ - interop.h \ - boltdb.h \ - qbolt_cgo.h \ - itemwindow.h - -FORMS += mainwindow.ui \ - itemwindow.ui - -RESOURCES += \ - resources.qrc diff --git a/qbolt/qbolt_cgo.h b/qbolt/qbolt_cgo.h deleted file mode 100644 index 1c000d1..0000000 --- a/qbolt/qbolt_cgo.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef QBOLT_CGO_H -#define QBOLT_CGO_H - -#if defined(Q_OS_WIN) || defined(CGO_WINDOWS) -#include "../build/win32/qbolt.h" -#else -#include "../build/linux/qbolt.h" -#endif - -#endif // QBOLT_CGO_H diff --git a/resources.go b/resources.go new file mode 100644 index 0000000..20abcf2 --- /dev/null +++ b/resources.go @@ -0,0 +1,17 @@ +package main + +//go:generate miqt-rcc.sh resources.qrc + +import ( + "embed" + + "github.com/mappu/miqt/qt" +) + +//go:embed resources.rcc +var _resourceRcc []byte + +func init() { + _ = embed.FS{} + qt.QResource_RegisterResourceWithRccData(&_resourceRcc[0]) +} diff --git a/qbolt/resources.qrc b/resources.qrc similarity index 100% rename from qbolt/resources.qrc rename to resources.qrc diff --git a/resources.rcc b/resources.rcc new file mode 100644 index 0000000000000000000000000000000000000000..0f693c56c1de4e5a25c74ffb8c5b73440ad8fe6f GIT binary patch literal 9498 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