qbolt: initial port to go/miqt
@ -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()
|
|
||||||
}
|
|
219
bolt.go
Normal file
@ -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()
|
||||||
|
}
|
15
build.sh
Executable file
@ -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"
|
@ -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
|
|
7
go.mod
@ -1,7 +1,10 @@
|
|||||||
module code.ivysaur.me/qbolt
|
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
|
replace github.com/boltdb/bolt => go.etcd.io/bbolt v1.3.5
|
||||||
|
2
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
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 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
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=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
71
itemwindow_ui.go
Normal file
@ -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() {
|
||||||
|
|
||||||
|
}
|
400
main.go
@ -1,398 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"os"
|
"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() {
|
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()
|
||||||
}
|
}
|
||||||
|
509
mainwindow.go
Normal file
@ -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(),
|
||||||
|
"<b>QBolt</b><br>Graphical interface for managing Bolt databases<br><br>"+
|
||||||
|
"- <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br>"+
|
||||||
|
"- <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam "Silk" icon set</a><br>"+
|
||||||
|
"- <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
336
mainwindow_ui.cpp.txt
Normal file
@ -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 <QtCore/QVariant>
|
||||||
|
#include <QtGui/QIcon>
|
||||||
|
#include <QtWidgets/QAction>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtWidgets/QGridLayout>
|
||||||
|
#include <QtWidgets/QHeaderView>
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
#include <QtWidgets/QMenu>
|
||||||
|
#include <QtWidgets/QMenuBar>
|
||||||
|
#include <QtWidgets/QPlainTextEdit>
|
||||||
|
#include <QtWidgets/QPushButton>
|
||||||
|
#include <QtWidgets/QSpacerItem>
|
||||||
|
#include <QtWidgets/QSplitter>
|
||||||
|
#include <QtWidgets/QStackedWidget>
|
||||||
|
#include <QtWidgets/QStatusBar>
|
||||||
|
#include <QtWidgets/QTabWidget>
|
||||||
|
#include <QtWidgets/QToolBar>
|
||||||
|
#include <QtWidgets/QTreeWidget>
|
||||||
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
|
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
|
325
mainwindow_ui.go
Normal file
@ -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"))
|
||||||
|
}
|
219
qbolt/boltdb.cpp
@ -1,219 +0,0 @@
|
|||||||
#include "boltdb.h"
|
|
||||||
|
|
||||||
#include <QtEndian>
|
|
||||||
|
|
||||||
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<QByteArray>& 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<QByteArray>& 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<QByteArray>& 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<QByteArray>& 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<QByteArray>& 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<QByteArray>& bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> 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<qint64>(b.mid(0, 8));
|
|
||||||
cb(b.mid(8), dataLen);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BoltDB::getData(const QList<QByteArray>& bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> 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<void(QByteArray)> onSuccess, std::function<void(QString)> 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<QByteArray>& bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
#ifndef BOLTDB_H
|
|
||||||
#define BOLTDB_H
|
|
||||||
|
|
||||||
#include "interop.h"
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
typedef std::function<void(QByteArray)> 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<QByteArray>& bucketPath, QString& errorOut, NameReciever cb);
|
|
||||||
|
|
||||||
bool addBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut);
|
|
||||||
|
|
||||||
bool deleteBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut);
|
|
||||||
|
|
||||||
bool setItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut);
|
|
||||||
|
|
||||||
bool deleteItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QString& errorOut);
|
|
||||||
|
|
||||||
bool listKeys(const QList<QByteArray>& bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> cb);
|
|
||||||
|
|
||||||
bool getData(const QList<QByteArray>& bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
|
||||||
|
|
||||||
bool getStatsJSON(std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
|
||||||
|
|
||||||
bool getBucketStatsJSON(const QList<QByteArray>& bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
|
||||||
|
|
||||||
~BoltDB();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
bool pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // BOLTDB_H
|
|
@ -1,42 +0,0 @@
|
|||||||
#include "interop.h"
|
|
||||||
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
Interop::Interop()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GoString Interop::toGoString_WeakRef(QByteArray *qba) {
|
|
||||||
return GoString{qba->data(), qba->length()};
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t Interop::GetMagic() {
|
|
||||||
return ::GetMagic();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
GoSliceManagedWrapper::GoSliceManagedWrapper(const QList<QByteArray>& 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<void*>(strings);
|
|
||||||
slice.len = qsl.size(); // * sizeof(GoString);
|
|
||||||
slice.cap = slice.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
GoSliceManagedWrapper::~GoSliceManagedWrapper()
|
|
||||||
{
|
|
||||||
delete[] strings;
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
#ifndef INTEROP_H
|
|
||||||
#define INTEROP_H
|
|
||||||
|
|
||||||
#include "qbolt_cgo.h"
|
|
||||||
#include <QString>
|
|
||||||
#include <QList>
|
|
||||||
|
|
||||||
class GoSliceManagedWrapper {
|
|
||||||
Q_DISABLE_COPY(GoSliceManagedWrapper)
|
|
||||||
|
|
||||||
public:
|
|
||||||
GoSliceManagedWrapper(const QList<QByteArray>& qsl);
|
|
||||||
~GoSliceManagedWrapper();
|
|
||||||
protected:
|
|
||||||
QList<QByteArray> rawStrings;
|
|
||||||
public:
|
|
||||||
GoSlice slice;
|
|
||||||
GoString *strings;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Interop
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Interop();
|
|
||||||
|
|
||||||
static GoString toGoString_WeakRef(QByteArray *qba);
|
|
||||||
|
|
||||||
static int64_t GetMagic();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // INTEROP_H
|
|
@ -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;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
#ifndef ITEMWINDOW_H
|
|
||||||
#define ITEMWINDOW_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QPlainTextEdit>
|
|
||||||
|
|
||||||
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
|
|
@ -1,23 +0,0 @@
|
|||||||
#include "mainwindow.h"
|
|
||||||
#include <QApplication>
|
|
||||||
#include "interop.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
@ -1,478 +0,0 @@
|
|||||||
#include "mainwindow.h"
|
|
||||||
#include "ui_mainwindow.h"
|
|
||||||
#include "itemwindow.h"
|
|
||||||
#include "boltdb.h"
|
|
||||||
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QInputDialog>
|
|
||||||
|
|
||||||
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<void*>(static_cast<void*>(bdb)))
|
|
||||||
#define GET_BDB(top) static_cast<BoltDB*>( top->data(0, BdbPointerRole).value<void*>() )
|
|
||||||
|
|
||||||
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<QByteArray> 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(),
|
|
||||||
"<b>QBolt</b><br>Graphical interface for managing Bolt databases<br><br>"
|
|
||||||
"- <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br>"
|
|
||||||
"- <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam "Silk" icon set</a><br>"
|
|
||||||
"- <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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<QByteArray> 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<QByteArray>& 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<QByteArray> 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<QByteArray>& 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) );
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
#ifndef MAINWINDOW_H
|
|
||||||
#define MAINWINDOW_H
|
|
||||||
|
|
||||||
#include "boltdb.h"
|
|
||||||
#include <QMainWindow>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QTreeWidgetItem>
|
|
||||||
|
|
||||||
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<QByteArray>& browse);
|
|
||||||
void openEditor(BoltDB *bdb, const QList<QByteArray>& saveAs, QByteArray saveAsKey, QByteArray currentContent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::MainWindow *ui;
|
|
||||||
|
|
||||||
QMenu *databaseContext;
|
|
||||||
QMenu *bucketContext;
|
|
||||||
QTreeWidgetItem* lastContextSelection;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
|
@ -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
|
|
@ -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
|
|
17
resources.go
Normal file
@ -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])
|
||||||
|
}
|
BIN
resources.rcc
Normal file
Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 733 B |
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 541 B |
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 390 B |
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 658 B |
Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 763 B |
Before Width: | Height: | Size: 775 B After Width: | Height: | Size: 775 B |
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 796 B After Width: | Height: | Size: 796 B |
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B |
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 778 B |
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B |