Compare commits
88 Commits
archive/co
...
v1.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 18331ae007 | |||
| f08242e93c | |||
| 23965230e2 | |||
| e43b261752 | |||
| f9b4cb71a5 | |||
| 96c05641bd | |||
| 2c5d1946ec | |||
| a6cbc5a9ed | |||
| 6008ae44a2 | |||
| 99096b2360 | |||
| 0e92459779 | |||
| a13eaaf523 | |||
| dca8d277d3 | |||
| 15c739c0b9 | |||
| fc2da972ef | |||
| e45eea7111 | |||
| 1e62d79c07 | |||
| c74c5ae5c0 | |||
| b0092c4a6e | |||
| 5ce6368c4a | |||
| 3e7e54da4b | |||
| 9f80d687b2 | |||
| 96411e877f | |||
| ac7e078c02 | |||
| e13314f5dc | |||
| b50c3e738a | |||
| 54ad6015b7 | |||
| 40e84ac230 | |||
| 767eaa0a47 | |||
| bb594e768c | |||
| 516bd99c4d | |||
| 571bfcf4b6 | |||
| 26f7a11d80 | |||
| 21588021d3 | |||
| 7441e0c15b | |||
| 142f3f6bf4 | |||
| 19ddb1c956 | |||
| 97467eae4d | |||
| 17c37b6568 | |||
| 57237ef2e8 | |||
| 164f5a071d | |||
| ac46b16d0e | |||
| b9d114a2d8 | |||
| b368457247 | |||
| fb940d5f60 | |||
| 6d1e671e44 | |||
| b5149c4efa | |||
| f0f642b6b0 | |||
| 7c62abb352 | |||
| 4f1c48b55d | |||
| 37f9307db1 | |||
| 14e7ebb59c | |||
| 14510545d4 | |||
| 906cff2e57 | |||
| bf28495b1f | |||
| 98b78ab1e6 | |||
| 4394c8a50a | |||
| 9fb5bdad78 | |||
| d2ec12798e | |||
| 5270dd00bc | |||
| 35f28fa5ed | |||
| 5ea969e10c | |||
| d0becd0c3c | |||
| 1c81444645 | |||
| d7c3bfd1f5 | |||
| 0f1cc014d7 | |||
| 6ac8c3e67b | |||
| 546681a0ef | |||
| 679d1140cc | |||
| 60d71104e8 | |||
| 95a1bfeea5 | |||
| 8597f270f6 | |||
| d32ab82d73 | |||
| b6d1cafe54 | |||
| 3911d7d66c | |||
| be594cb1a5 | |||
| 8a93f163c4 | |||
| e82d0a5dd2 | |||
| 4b3fb783c4 | |||
| e8030a9579 | |||
| a94a330345 | |||
| 8ce03cbed6 | |||
| b76fad9512 | |||
| a9158c3a0c | |||
| dc31787526 | |||
| ff7484079e | |||
| 6e1f9ca1e1 | |||
| ac56f5e6c8 |
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# development
|
||||||
|
qbolt
|
||||||
|
dummy-data/dummy-data*
|
||||||
|
|
||||||
|
# temporary build files
|
||||||
|
rsrc_windows_amd64.syso
|
||||||
|
windows-manifest.json
|
||||||
|
|
||||||
|
# release build files
|
||||||
|
build/qbolt
|
||||||
|
build/qbolt.exe
|
||||||
|
build/*.xz
|
||||||
|
build/*.zip
|
||||||
|
|
||||||
|
# local makefile definition scripts
|
||||||
|
make-*
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Converted with codesite2git
|
|
||||||
|
|
||||||
project_name="qbolt"
|
|
||||||
short_description="A graphical database manager for BoltDB."
|
|
||||||
written_in_lang="C++ (Qt), Golang (CGo)"
|
|
||||||
topics=[]
|
|
||||||
ctime=1495324800
|
|
||||||
mtime=1497830400
|
|
||||||
|
|
||||||
60
Makefile
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
VERSION := 1.0.3
|
||||||
|
GOFLAGS_L := -ldflags='-s -w -X main.Version=v$(VERSION)' -buildvcs=false -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)'
|
||||||
|
GOFLAGS_W := -ldflags='-s -w -X main.Version=v$(VERSION) -H windowsgui' -buildvcs=false --tags=windowsqtstatic -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)'
|
||||||
|
SHELL := /bin/bash
|
||||||
|
# Allow overriding DOCKER with e.g. sudo docker
|
||||||
|
DOCKER := docker
|
||||||
|
MIQT_UIC := miqt-uic
|
||||||
|
MIQT_RCC := miqt-rcc
|
||||||
|
GO_WINRES := go-winres
|
||||||
|
SOURCES := $(wildcard *.go *.ui *.qrc) resources.go mainwindow_ui.go itemwindow_ui.go rsrc_windows_amd64.syso
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: build/qbolt build/qbolt.exe
|
||||||
|
|
||||||
|
.PHONY: dist
|
||||||
|
dist: build/qbolt-${VERSION}-windows-x86_64.zip build/qbolt-${VERSION}-debian12-x86_64.tar.xz
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f qbolt || true
|
||||||
|
rm -rf build || true
|
||||||
|
rm -f windows-manifest.json || true
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
|
||||||
|
resources.rcc resources.go: resources.qrc
|
||||||
|
$(MIQT_RCC) resources.qrc
|
||||||
|
|
||||||
|
mainwindow_ui.go: mainwindow.ui
|
||||||
|
$(MIQT_UIC) -InFile mainwindow.ui -OutFile mainwindow_ui.go
|
||||||
|
|
||||||
|
itemwindow_ui.go: itemwindow.ui
|
||||||
|
$(MIQT_UIC) -InFile itemwindow.ui -OutFile itemwindow_ui.go
|
||||||
|
|
||||||
|
windows-manifest.json: windows-manifest.template.json Makefile
|
||||||
|
cat windows-manifest.template.json | sed -re 's_%VERSION%_$(VERSION)_' > windows-manifest.json
|
||||||
|
|
||||||
|
rsrc_windows_amd64.syso: windows-manifest.json
|
||||||
|
$(GO_WINRES) make --in windows-manifest.json
|
||||||
|
rm rsrc_windows_386.syso || true # we do not build x86_32
|
||||||
|
|
||||||
|
# Linux release
|
||||||
|
|
||||||
|
build/qbolt: $(SOURCES)
|
||||||
|
go build $(GOFLAGS_L) -o build/qbolt
|
||||||
|
upx build/qbolt
|
||||||
|
|
||||||
|
build/qbolt-${VERSION}-debian12-x86_64.tar.xz: build/qbolt
|
||||||
|
XZ_OPTS=-9e tar caf build/qbolt-${VERSION}-debian12-x86_64.tar.xz -C build qbolt --owner=0 --group=0
|
||||||
|
|
||||||
|
# Windows release (docker)
|
||||||
|
|
||||||
|
build/qbolt.exe: $(SOURCES)
|
||||||
|
( $(DOCKER) image ls | fgrep qbolt-win64-cross ) || ( cd docker && $(DOCKER) build -t qbolt-win64-cross:latest -f win64-cross.Dockerfile . )
|
||||||
|
$(DOCKER) run --rm -v $(CURDIR):/qbolt -w /qbolt qbolt-win64-cross:latest /bin/sh -c "go build $(GOFLAGS_W) -o build/qbolt.exe"
|
||||||
|
upx --force build/qbolt.exe
|
||||||
|
|
||||||
|
build/qbolt-${VERSION}-windows-x86_64.zip: build/qbolt.exe
|
||||||
|
zip -9 -j build/qbolt-${VERSION}-windows-x86_64.zip build/qbolt.exe
|
||||||
30
README.md
@@ -1,12 +1,10 @@
|
|||||||
# qbolt
|
# qbolt
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
A graphical database manager for BoltDB.
|
A graphical database manager for BoltDB.
|
||||||
|
|
||||||
QBolt allows you to graphically view and edit the content of Bolt databases.
|
QBolt allows you to graphically view and edit the content of Bolt databases.
|
||||||
|
|
||||||
The project consists of two parts; a C binding (CGo) for the embeddable Bolt database engine, and a graphical interface built in C++/Qt that links to it.
|
Written in Golang (Qt)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -24,25 +22,33 @@ Source code content of `qbolt-x.x.x-src.tar.gz` is released under the ISC licens
|
|||||||
BoltDB is released under the MIT license.
|
BoltDB is released under the MIT license.
|
||||||
The Windows binary is released under LGPL-3+ owing to the static copy of Qt.
|
The Windows binary is released under LGPL-3+ owing to the static copy of Qt.
|
||||||
|
|
||||||
## See Also
|
## See also
|
||||||
|
|
||||||
- BoltDB https://github.com/boltdb/bolt
|
- BoltDB https://github.com/boltdb/bolt
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
2024-10-05 1.0.3
|
||||||
|
- Port from hybrid Go/C++ to now using [MIQT](https://github.com/mappu/miqt)
|
||||||
|
- Switch Windows build to win64
|
||||||
|
- Rebuild artefacts with miqt v0.5.0, etcd-io/bbolt v1.3.11, go 1.19 (deb12), go 1.23 (win64)
|
||||||
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.3)
|
||||||
|
|
||||||
|
2020-04-12 1.0.2
|
||||||
|
- Rebuild artefacts with etcd-io/bbolt v1.3.5, go 1.15, Qt 5.15, and new GCC versions
|
||||||
|
- Switch from hg to Git
|
||||||
|
- Use Go modules
|
||||||
|
- Add support for building Windows binary in Docker
|
||||||
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.2)
|
||||||
|
|
||||||
2017-06-19 1.0.1
|
2017-06-19 1.0.1
|
||||||
- Feature: Option to open database as read-only
|
- Feature: Option to open database as read-only
|
||||||
- Fix an issue with support for bucket names and keys not surviving UTF-8 roundtrips (now binary-clean)
|
- Fix an issue with support for bucket names and keys not surviving UTF-8 roundtrips (now binary-clean)
|
||||||
- Fix an issue with crashing when deleting a bucket other than the selected one
|
- Fix an issue with crashing when deleting a bucket other than the selected one
|
||||||
- Fix a cosmetic issue with application icon on Windows
|
- Fix a cosmetic issue with application icon on Windows
|
||||||
- [⬇️ qbolt-1.0.1-win32.zip](dist-archive/qbolt-1.0.1-win32.zip) *(5.07 MiB)*
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.1)
|
||||||
- [⬇️ qbolt-1.0.1-src.tar.gz](dist-archive/qbolt-1.0.1-src.tar.gz) *(265.88 KiB)*
|
|
||||||
- [⬇️ qbolt-1.0.1-linux_amd64.tar.xz](dist-archive/qbolt-1.0.1-linux_amd64.tar.xz) *(584.88 KiB)*
|
|
||||||
|
|
||||||
|
|
||||||
2017-05-21 1.0.0
|
2017-05-21 1.0.0
|
||||||
- Initial public release
|
- Initial public release
|
||||||
- [⬇️ qbolt-1.0.0-win32.zip](dist-archive/qbolt-1.0.0-win32.zip) *(5.07 MiB)*
|
- The project consists of two parts; a C binding (CGo) for the embeddable Bolt database engine, and a graphical interface built in C++/Qt that links to it.
|
||||||
- [⬇️ qbolt-1.0.0-src.tar.gz](dist-archive/qbolt-1.0.0-src.tar.gz) *(221.91 KiB)*
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.0)
|
||||||
- [⬇️ qbolt-1.0.0-linux_amd64.tar.xz](dist-archive/qbolt-1.0.0-linux_amd64.tar.xz) *(584.62 KiB)*
|
|
||||||
|
|
||||||
|
|||||||
219
bolt.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.MarshalIndent(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.MarshalIndent(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()
|
||||||
|
}
|
||||||
0
build/.create_dir
Normal file
20
docker/win64-cross.Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:1.23-bookworm
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
|
apt-get install -qyy gnupg2 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-x86-64-w64-mingw32.static-qt5 && \
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
|
ENV PATH=/usr/lib/mxe/usr/bin:$PATH
|
||||||
|
|
||||||
|
ENV CXX=x86_64-w64-mingw32.static-g++
|
||||||
|
ENV CC=x86_64-w64-mingw32.static-gcc
|
||||||
|
ENV PKG_CONFIG=x86_64-w64-mingw32.static-pkg-config
|
||||||
|
ENV GOOS=windows
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
|
||||||
82
dummy-data/main.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func random_name() string {
|
||||||
|
ret := make([]byte, 12)
|
||||||
|
rand.Read(ret)
|
||||||
|
return string(ret)
|
||||||
|
//return fmt.Sprintf("%08x-%08x-%08x", rand.Int63(), rand.Int63(), rand.Int63())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fill_bucket(tx *bolt.Tx, bucket *bolt.Bucket) error {
|
||||||
|
// fill with some basic items
|
||||||
|
for i := 0; i < 30; i += 1 {
|
||||||
|
err := bucket.Put([]byte(random_name()), []byte("SAMPLE CONTENT "+random_name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1/20 (5%) chance of recursion
|
||||||
|
if rand.Intn(100) >= 95 {
|
||||||
|
|
||||||
|
for i := 0; i < 5; i += 1 {
|
||||||
|
|
||||||
|
child, err := bucket.CreateBucket([]byte(random_name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fill_bucket(tx, child)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := bolt.Open("sample.db", 0644, bolt.DefaultOptions)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Update(func(tx *bolt.Tx) error {
|
||||||
|
// top-level buckets
|
||||||
|
for i := 0; i < 50; i += 1 {
|
||||||
|
bucketName := random_name()
|
||||||
|
bucket, err := tx.CreateBucket([]byte(bucketName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fill_bucket(tx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module code.ivysaur.me/qbolt
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mappu/miqt v0.5.0
|
||||||
|
go.etcd.io/bbolt v1.3.11
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.4.0 // indirect
|
||||||
11
go.sum
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/mappu/miqt v0.5.0 h1:BWajkNI9PWlWN6ZDgWKwv1gieBGEImRqlWS8ZqDmDfA=
|
||||||
|
github.com/mappu/miqt v0.5.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
95
itemwindow.ui
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ItemWindow</class>
|
||||||
|
<widget class="QDialog" name="ItemWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>370</width>
|
||||||
|
<height>353</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/database_lightning.png</normaloff>:/rsrc/database_lightning.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPlainTextEdit" name="contentArea">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QFrame" name="frame">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>ItemWindow</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>184</x>
|
||||||
|
<y>330</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>184</x>
|
||||||
|
<y>176</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>ItemWindow</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>184</x>
|
||||||
|
<y>330</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>184</x>
|
||||||
|
<y>176</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
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.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")
|
||||||
|
ui.buttonBox.SetStandardButtons(qt.QDialogButtonBox__Cancel | qt.QDialogButtonBox__Save)
|
||||||
|
ui.buttonBox.OnAccepted(ui.ItemWindow.Accept)
|
||||||
|
ui.buttonBox.OnRejected(ui.ItemWindow.Reject)
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
|
}
|
||||||
22
main.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mappu/miqt/qt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Version string = "v0.0.0-devel"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
_ = 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()
|
||||||
|
}
|
||||||
496
mainwindow.go
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mappu/miqt/qt"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, filepath.Base(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 {
|
||||||
|
if qba == "" {
|
||||||
|
return "<empty>"
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := strconv.Quote(qba)
|
||||||
|
return ret[1 : len(ret)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MainWindow) refreshBucketTree(itm *qt.QTreeWidgetItem) {
|
||||||
|
ws := this.getSelection(itm)
|
||||||
|
|
||||||
|
// Remove existing children
|
||||||
|
i := itm.ChildCount()
|
||||||
|
for i > 0 {
|
||||||
|
itm.TakeChild(i - 1).Delete()
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Bolt_ListBuckets(ws.bdb, ws.browse, func(qba string) {
|
||||||
|
|
||||||
|
child := qt.NewQTreeWidgetItem6(itm) // NewQTreeWidgetItem()
|
||||||
|
child.SetText(0, getDisplayName(qba))
|
||||||
|
child.SetData(0, BinaryDataRole, qt.NewQVariant15(MakeQByteArray(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 under %s: %s", strings.Join(ws.browse, `/`), 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 "+Version+"</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.Viewport().MapToGlobal(pos))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Top-level item, show the database menu
|
||||||
|
this.databaseContext.Popup(this.ui.bucketTree.Viewport().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()
|
||||||
|
|
||||||
|
ws := this.getSelection(current)
|
||||||
|
|
||||||
|
stats, err := Bolt_BucketStats(ws.bdb, ws.browse)
|
||||||
|
if err != nil {
|
||||||
|
this.ui.bucketPropertiesArea.SetPlainText(fmt.Sprintf("Error retrieving bucket statistics: %s", err.Error()))
|
||||||
|
} else {
|
||||||
|
this.ui.bucketPropertiesArea.SetPlainText(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the data tab
|
||||||
|
this.refreshData(ws.bdb, ws.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.NewQVariant15(MakeQByteArray(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
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getSelection(itm), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MainWindow) getSelection(itm *qt.QTreeWidgetItem) windowSelection {
|
||||||
|
|
||||||
|
top := itm
|
||||||
|
|
||||||
|
var browse []string
|
||||||
|
for {
|
||||||
|
if top.Parent() == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
browse = append(browse, FromQByteArray(top.Data(0, BinaryDataRole).ToByteArray()))
|
||||||
|
top = top.Parent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReverseSlice(browse)
|
||||||
|
|
||||||
|
bdb := GET_BDB(top)
|
||||||
|
|
||||||
|
return windowSelection{itm: itm, top: top, browse: browse, bdb: bdb}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := FromQByteArray(model.Data2(model.Index(index.Row(), 0), BinaryDataRole).ToByteArray())
|
||||||
|
|
||||||
|
// 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 := FromQByteArray(ws.itm.Data(0, BinaryDataRole).ToByteArray())
|
||||||
|
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, FromQByteArray(selection[i-1].Data(0, BinaryDataRole).ToByteArray()))
|
||||||
|
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)
|
||||||
|
}
|
||||||
410
mainwindow.ui
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>668</width>
|
||||||
|
<height>405</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>QBolt</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/database_lightning.png</normaloff>:/rsrc/database_lightning.png</iconset>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralWidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="childrenCollapsible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QTreeWidget" name="bucketTree">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bucket</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="databasePage">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTabWidget" name="databaseTabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="databasePropertiesTab">
|
||||||
|
<attribute name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/chart_bar.png</normaloff>:/rsrc/chart_bar.png</iconset>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Database</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPlainTextEdit" name="databasePropertiesArea">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="plainText">
|
||||||
|
<string>No selection</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="bucketPage">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTabWidget" name="bucketTabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="bucketPropertiesTab">
|
||||||
|
<attribute name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/chart_bar.png</normaloff>:/rsrc/chart_bar.png</iconset>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Bucket</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPlainTextEdit" name="bucketPropertiesArea">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="bucketDataTab">
|
||||||
|
<attribute name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/table.png</normaloff>:/rsrc/table.png</iconset>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Data</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_6">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QTreeWidget" name="bucketData">
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="indentation">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rootIsDecorated">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Key</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Data length</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QPushButton" name="AddDataButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/add.png</normaloff>:/rsrc/add.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QPushButton" name="DeleteDataButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/delete.png</normaloff>:/rsrc/delete.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menuBar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>668</width>
|
||||||
|
<height>29</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuFile">
|
||||||
|
<property name="title">
|
||||||
|
<string>&File</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionNew_database"/>
|
||||||
|
<addaction name="actionOpen_database"/>
|
||||||
|
<addaction name="actionOpen_database_as_read_only"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionExit"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionAbout_qbolt"/>
|
||||||
|
<addaction name="actionAbout_Qt"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuView">
|
||||||
|
<property name="title">
|
||||||
|
<string>&View</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionClear_selection"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QToolBar" name="mainToolBar">
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<addaction name="actionNew_database"/>
|
||||||
|
<addaction name="actionOpen_database"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusBar"/>
|
||||||
|
<action name="actionAbout_qbolt">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/information.png</normaloff>:/rsrc/information.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&About QBolt</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAbout_Qt">
|
||||||
|
<property name="text">
|
||||||
|
<string>About &Qt</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpen_database">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/database.png</normaloff>:/rsrc/database.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Open database...</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+O</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExit">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/door_out.png</normaloff>:/rsrc/door_out.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Exit</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionDisconnect">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/disconnect.png</normaloff>:/rsrc/disconnect.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Disconnect</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionDelete_bucket">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/table_delete.png</normaloff>:/rsrc/table_delete.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete bucket</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionRefresh_buckets">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/arrow_refresh.png</normaloff>:/rsrc/arrow_refresh.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Refresh buckets</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionClear_selection">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Clear selection</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionNew_database">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/database_add.png</normaloff>:/rsrc/database_add.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&New database...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAdd_bucket">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/rsrc/table_add.png</normaloff>:/rsrc/table_add.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Add bucket...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpen_database_as_read_only">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open database as read-only...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
|
<resources>
|
||||||
|
<include location="resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
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.AddWidget3(ui.bucketData.QWidget, 0, 0, 1, 3)
|
||||||
|
|
||||||
|
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, 29)
|
||||||
|
|
||||||
|
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("&File"))
|
||||||
|
ui.menuHelp.SetTitle(qt.QMenuBar_Tr("Help"))
|
||||||
|
ui.menuView.SetTitle(qt.QMenuBar_Tr("&View"))
|
||||||
|
}
|
||||||
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])
|
||||||
|
}
|
||||||
17
resources.qrc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>rsrc/database_add.png</file>
|
||||||
|
<file>rsrc/table.png</file>
|
||||||
|
<file>rsrc/information.png</file>
|
||||||
|
<file>rsrc/database_lightning.png</file>
|
||||||
|
<file>rsrc/database.png</file>
|
||||||
|
<file>rsrc/table_add.png</file>
|
||||||
|
<file>rsrc/table_delete.png</file>
|
||||||
|
<file>rsrc/add.png</file>
|
||||||
|
<file>rsrc/delete.png</file>
|
||||||
|
<file>rsrc/chart_bar.png</file>
|
||||||
|
<file>rsrc/arrow_refresh.png</file>
|
||||||
|
<file>rsrc/disconnect.png</file>
|
||||||
|
<file>rsrc/door_out.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
BIN
resources.rcc
Normal file
BIN
rsrc/add.png
Executable file
|
After Width: | Height: | Size: 733 B |
BIN
rsrc/arrow_refresh.png
Executable file
|
After Width: | Height: | Size: 685 B |
BIN
rsrc/chart_bar.png
Executable file
|
After Width: | Height: | Size: 541 B |
BIN
rsrc/database.png
Executable file
|
After Width: | Height: | Size: 390 B |
BIN
rsrc/database_add.png
Executable file
|
After Width: | Height: | Size: 658 B |
BIN
rsrc/database_connect.png
Executable file
|
After Width: | Height: | Size: 763 B |
BIN
rsrc/database_lightning.png
Executable file
|
After Width: | Height: | Size: 775 B |
BIN
rsrc/delete.png
Executable file
|
After Width: | Height: | Size: 715 B |
BIN
rsrc/disconnect.png
Executable file
|
After Width: | Height: | Size: 796 B |
BIN
rsrc/door_out.png
Executable file
|
After Width: | Height: | Size: 688 B |
BIN
rsrc/information.png
Executable file
|
After Width: | Height: | Size: 778 B |
BIN
rsrc/page.png
Executable file
|
After Width: | Height: | Size: 635 B |
BIN
rsrc/qbolt.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
rsrc/table.png
Executable file
|
After Width: | Height: | Size: 566 B |
BIN
rsrc/table_add.png
Executable file
|
After Width: | Height: | Size: 663 B |
BIN
rsrc/table_delete.png
Executable file
|
After Width: | Height: | Size: 660 B |
24
util.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mappu/miqt/qt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReverseSlice reverses a slice.
|
||||||
|
// @ref https://stackoverflow.com/a/28058324
|
||||||
|
func ReverseSlice[S ~[]E, E any](s S) {
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeQByteArray(s string) *qt.QByteArray {
|
||||||
|
return qt.NewQByteArray7(s, len(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromQByteArray(qba *qt.QByteArray) string {
|
||||||
|
var rawData []byte = unsafe.Slice((*byte)(qba.Data()), qba.Length())
|
||||||
|
return string(rawData) // copy
|
||||||
|
}
|
||||||
17
util_test.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQByteArray(t *testing.T) {
|
||||||
|
foo := "the \x00 quick \x01 brown \x02 fox \x03 jumps \x04 over \x05 the \x06 lazy \x07 dog"
|
||||||
|
|
||||||
|
qb := MakeQByteArray(foo)
|
||||||
|
|
||||||
|
out := FromQByteArray(qb)
|
||||||
|
|
||||||
|
if foo != out {
|
||||||
|
t.Errorf("Expected equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
50
windows-manifest.template.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"RT_GROUP_ICON": {
|
||||||
|
"APP": {
|
||||||
|
"0000": "rsrc/database_lightning.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RT_VERSION": {
|
||||||
|
"#1": {
|
||||||
|
"0000": {
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "%VERSION%.0",
|
||||||
|
"product_version": "%VERSION%.0"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0409": {
|
||||||
|
"FileDescription": "QBolt Database Viewer",
|
||||||
|
"FileVersion": "%VERSION%.0",
|
||||||
|
"ProductName": "QBolt Database Viewer",
|
||||||
|
"ProductVersion": "%VERSION%.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RT_MANIFEST": {
|
||||||
|
"#1": {
|
||||||
|
"0409": {
|
||||||
|
"identity": {
|
||||||
|
"name": "",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"minimum-os": "win7",
|
||||||
|
"execution-level": "as invoker",
|
||||||
|
"ui-access": false,
|
||||||
|
"auto-elevate": false,
|
||||||
|
"dpi-awareness": "per monitor v2",
|
||||||
|
"disable-theming": false,
|
||||||
|
"disable-window-filtering": false,
|
||||||
|
"high-resolution-scrolling-aware": false,
|
||||||
|
"ultra-high-resolution-scrolling-aware": false,
|
||||||
|
"long-path-aware": false,
|
||||||
|
"printer-driver-isolation": false,
|
||||||
|
"gdi-scaling": false,
|
||||||
|
"segment-heap": false,
|
||||||
|
"use-common-controls-v6": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||