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 0000000..0f693c5 Binary files /dev/null and b/resources.rcc differ diff --git a/qbolt/rsrc/add.png b/rsrc/add.png similarity index 100% rename from qbolt/rsrc/add.png rename to rsrc/add.png diff --git a/qbolt/rsrc/arrow_refresh.png b/rsrc/arrow_refresh.png similarity index 100% rename from qbolt/rsrc/arrow_refresh.png rename to rsrc/arrow_refresh.png diff --git a/qbolt/rsrc/chart_bar.png b/rsrc/chart_bar.png similarity index 100% rename from qbolt/rsrc/chart_bar.png rename to rsrc/chart_bar.png diff --git a/qbolt/rsrc/database.png b/rsrc/database.png similarity index 100% rename from qbolt/rsrc/database.png rename to rsrc/database.png diff --git a/qbolt/rsrc/database_add.png b/rsrc/database_add.png similarity index 100% rename from qbolt/rsrc/database_add.png rename to rsrc/database_add.png diff --git a/qbolt/rsrc/database_connect.png b/rsrc/database_connect.png similarity index 100% rename from qbolt/rsrc/database_connect.png rename to rsrc/database_connect.png diff --git a/qbolt/rsrc/database_lightning.png b/rsrc/database_lightning.png similarity index 100% rename from qbolt/rsrc/database_lightning.png rename to rsrc/database_lightning.png diff --git a/qbolt/rsrc/delete.png b/rsrc/delete.png similarity index 100% rename from qbolt/rsrc/delete.png rename to rsrc/delete.png diff --git a/qbolt/rsrc/disconnect.png b/rsrc/disconnect.png similarity index 100% rename from qbolt/rsrc/disconnect.png rename to rsrc/disconnect.png diff --git a/qbolt/rsrc/door_out.png b/rsrc/door_out.png similarity index 100% rename from qbolt/rsrc/door_out.png rename to rsrc/door_out.png diff --git a/qbolt/rsrc/information.png b/rsrc/information.png similarity index 100% rename from qbolt/rsrc/information.png rename to rsrc/information.png diff --git a/qbolt/rsrc/page.png b/rsrc/page.png similarity index 100% rename from qbolt/rsrc/page.png rename to rsrc/page.png diff --git a/qbolt/rsrc/qbolt.ico b/rsrc/qbolt.ico similarity index 100% rename from qbolt/rsrc/qbolt.ico rename to rsrc/qbolt.ico diff --git a/qbolt/rsrc/table.png b/rsrc/table.png similarity index 100% rename from qbolt/rsrc/table.png rename to rsrc/table.png diff --git a/qbolt/rsrc/table_add.png b/rsrc/table_add.png similarity index 100% rename from qbolt/rsrc/table_add.png rename to rsrc/table_add.png diff --git a/qbolt/rsrc/table_delete.png b/rsrc/table_delete.png similarity index 100% rename from qbolt/rsrc/table_delete.png rename to rsrc/table_delete.png diff --git a/util.go b/util.go new file mode 100644 index 0000000..5545995 --- /dev/null +++ b/util.go @@ -0,0 +1,9 @@ +package main + +// ReverseSlice reverses a slice. +// @ref https://stackoverflow.com/a/28058324 +func ReverseSlice[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +}