88 Commits

Author SHA1 Message Date
18331ae007 doc/README: updates for v1.0.3 2024-10-05 17:44:27 +13:00
f08242e93c Merge branch 'qbolt-go' 2024-10-05 17:42:14 +13:00
23965230e2 make: add windows resources, improve output compression 2024-10-05 17:41:38 +13:00
e43b261752 build: upgrade makefile/dockerfile for miqt and win64 support 2024-10-05 16:37:23 +13:00
f9b4cb71a5 qbolt: show makefile-based version number in help dialog 2024-10-05 16:37:15 +13:00
96c05641bd mod: upgrade bbolt v1.3.5 -> v1.3.11 2024-10-05 16:36:41 +13:00
2c5d1946ec qbolt: fixes post-port 2024-10-05 15:58:56 +13:00
a6cbc5a9ed qbolt: initial port to go/miqt 2024-10-03 19:34:28 +13:00
6008ae44a2 doc: rename image directory for teafolio 2021-04-12 18:03:05 +12:00
99096b2360 doc/README: add v1.0.2 changelog 2021-04-12 18:01:46 +12:00
0e92459779 no-op merge with legacy-codesite branch 2021-04-12 17:52:00 +12:00
a13eaaf523 make: upx doesn't support current mxe mingw/qt linked binaries 2021-04-12 17:44:38 +12:00
dca8d277d3 make: option to build windows binary in docker 2021-04-12 17:44:17 +12:00
15c739c0b9 squash 2021-04-12 17:43:59 +12:00
fc2da972ef dummy-data: fix build for renamed bbolt package 2021-04-12 17:07:16 +12:00
e45eea7111 makefile: remove 'hg export' function 2021-04-12 17:04:40 +12:00
1e62d79c07 doc: delete TODO.txt (issues now being tracked in Gitea)
The new issue tracker is at https://git.ivysaur.me/code.ivysaur.me/qbolt/issues
2021-04-12 17:04:27 +12:00
c74c5ae5c0 doc: move README/TODO to top-level directory 2021-04-12 17:00:49 +12:00
b0092c4a6e vendor: switch bolt implementation to etcd-io/bbolt v1.3.5 2021-04-12 17:00:40 +12:00
5ce6368c4a qt: fix missing -lpthread on Debian Bullseye 2021-04-12 16:54:06 +12:00
3e7e54da4b go: add go.mod file for bolt dependency 2021-04-12 16:53:57 +12:00
9f80d687b2 hg2git: replace hgignore/hgtags files 2021-04-12 16:53:43 +12:00
96411e877f bump all versions to 1.0.2 2017-06-19 21:04:40 +12:00
ac7e078c02 Added tag release-1.0.1 for changeset 0528a0ab20b6 2017-06-19 21:04:23 +12:00
e13314f5dc 1.0.1 meta 2017-06-19 21:04:17 +12:00
b50c3e738a doc: update features list in readme 2017-06-19 21:02:42 +12:00
54ad6015b7 more binary correctness 2017-06-19 20:53:50 +12:00
40e84ac230 dummy-data: change to generate binary names 2017-06-19 20:45:26 +12:00
767eaa0a47 add fallback display for non-printable characters 2017-06-19 20:42:51 +12:00
bb594e768c doc: update readme 2017-06-19 20:42:42 +12:00
516bd99c4d doc: update TODO 2017-06-19 20:28:33 +12:00
571bfcf4b6 one more preservation for previous 2017-06-19 20:27:52 +12:00
26f7a11d80 preserve the binary content of keys and bucket names during edit operations 2017-06-19 20:27:05 +12:00
21588021d3 doc: update TODO 2017-05-25 20:00:10 +12:00
7441e0c15b option to open database as read-only 2017-05-25 19:59:53 +12:00
142f3f6bf4 track screenshot of qbolt on windows 2017-05-25 19:54:35 +12:00
19ddb1c956 select parent when deleting bucket (maybe fixes a crash?) 2017-05-25 19:53:35 +12:00
97467eae4d add icon for win32 binary 2017-05-25 19:50:11 +12:00
17c37b6568 bump dist version to 1.0.1 2017-05-21 18:23:41 +12:00
57237ef2e8 Added tag release-1.0.0 for changeset 74cacbbe8f6c 2017-05-21 18:23:29 +12:00
164f5a071d doc: update README 2017-05-21 18:22:23 +12:00
ac46b16d0e hgignore: simplify 2017-05-21 18:19:47 +12:00
b9d114a2d8 remove extra .db file from dist archives 2017-05-21 18:18:45 +12:00
b368457247 add some screenshots 2017-05-21 18:18:14 +12:00
fb940d5f60 doc: TODO.txt 2017-05-21 18:18:10 +12:00
6d1e671e44 doc: README 2017-05-21 18:18:04 +12:00
b5149c4efa bolt: strict 10 second timeout for opening databases 2017-05-21 18:05:20 +12:00
f0f642b6b0 add many more icons 2017-05-21 18:05:11 +12:00
7c62abb352 working add items 2017-05-21 17:59:27 +12:00
4f1c48b55d working edit/delete items 2017-05-21 17:53:40 +12:00
37f9307db1 wip set/delete items 2017-05-21 17:44:07 +12:00
14e7ebb59c go: simplify out a wrapper function 2017-05-21 17:26:09 +12:00
14510545d4 fix right-hand default pane when a bucket is selected 2017-05-21 17:14:32 +12:00
906cff2e57 working bucket deletions 2017-05-21 17:14:20 +12:00
bf28495b1f add our application icon to system popups 2017-05-21 17:12:02 +12:00
98b78ab1e6 option to create new buckets / sub-buckets 2017-05-21 17:08:15 +12:00
4394c8a50a option to create new databases 2017-05-21 16:50:17 +12:00
9fb5bdad78 always open databases read/write 2017-05-21 16:47:28 +12:00
d2ec12798e popup editor to view record content 2017-05-21 16:44:44 +12:00
5270dd00bc display data keys, data item length 2017-05-21 16:15:49 +12:00
35f28fa5ed makefile: fix qbolt.a target 2017-05-21 15:59:00 +12:00
5ea969e10c pretty-print json display 2017-05-21 15:49:47 +12:00
d0becd0c3c clean up qt project, always refer to makefile-produced qbolt.a files 2017-05-21 15:00:53 +12:00
1c81444645 makefile: distribution targets 2017-05-21 14:51:22 +12:00
d7c3bfd1f5 unify makefile, make win32 builds 2017-05-21 14:33:46 +12:00
0f1cc014d7 working 'refresh buckets' action 2017-05-21 13:49:41 +12:00
6ac8c3e67b remove dead code 2017-05-21 13:49:36 +12:00
546681a0ef working cgo slices, working nested-bucket operations 2017-05-21 13:47:46 +12:00
679d1140cc recursive bucket scan - we're crashing when passing a slice to go code 2017-05-21 13:31:28 +12:00
60d71104e8 retrieve bucket properties 2017-05-21 12:39:55 +12:00
95a1bfeea5 retrieve database properties 2017-05-21 11:59:38 +12:00
8597f270f6 context menus, disconnection, re-refresh, selection behaviour, ^O shortcut 2017-05-21 11:47:16 +12:00
d32ab82d73 don't pass go strings to c - pass char*+len, callee must call free() 2017-05-20 23:02:58 +12:00
b6d1cafe54 cgo: minor cleanups, remove unused export 2017-05-20 18:15:39 +12:00
3911d7d66c convert cgo function pointer call to repeated iteration 2017-05-20 18:01:55 +12:00
be594cb1a5 call bucket enumeration - function pointers are misbehaving 2017-05-20 17:28:27 +12:00
8a93f163c4 c++ wrappers for bucket enumeration function 2017-05-20 15:47:18 +12:00
e82d0a5dd2 add top-level db items to ui 2017-05-20 15:47:05 +12:00
4b3fb783c4 wire up other menu items, add app logo icon 2017-05-20 15:20:30 +12:00
e8030a9579 cgo: change interface to not return already-collected error interfaces 2017-05-20 15:13:16 +12:00
a94a330345 add GetMagic() check, move c/go interop into separate classes 2017-05-20 14:57:51 +12:00
8ce03cbed6 go: build .a instead of .so 2017-05-20 14:57:19 +12:00
b76fad9512 hgignore: pro.user file 2017-05-18 20:11:19 +12:00
a9158c3a0c gms: fix missing initialisation 2017-05-16 19:47:50 +12:00
dc31787526 track sample.db file 2017-05-16 19:47:41 +12:00
ff7484079e track dummy-data generator 2017-05-16 19:47:26 +12:00
6e1f9ca1e1 hgignore 2017-05-16 19:47:17 +12:00
ac56f5e6c8 initial commit 2017-05-16 19:34:54 +12:00
44 changed files with 1980 additions and 21 deletions

16
.gitignore vendored Normal file
View 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-*

View File

@@ -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
View 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

View File

@@ -1,12 +1,10 @@
# qbolt # qbolt
![](https://img.shields.io/badge/written%20in-C%2B%2B%20%28Qt%29%2C%20Golang%20%28CGo%29-blue)
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
View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 &quot;Silk&quot; 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
View 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>&amp;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>&amp;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>&amp;About QBolt</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About &amp;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>&amp;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>&amp;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>&amp;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>&amp;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
View 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
View 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
View 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

Binary file not shown.

BIN
rsrc/add.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
rsrc/arrow_refresh.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

BIN
rsrc/chart_bar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

BIN
rsrc/database.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
rsrc/database_add.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

BIN
rsrc/database_connect.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

BIN
rsrc/database_lightning.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

BIN
rsrc/delete.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

BIN
rsrc/disconnect.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

BIN
rsrc/door_out.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

BIN
rsrc/information.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

BIN
rsrc/page.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

BIN
rsrc/qbolt.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
rsrc/table.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

BIN
rsrc/table_add.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

BIN
rsrc/table_delete.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

24
util.go Normal file
View 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
View 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")
}
}

View 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
}
}
}
}