qbolt/qbolt/mainwindow.cpp

404 lines
11 KiB
C++

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "itemwindow.h"
#include "boltdb.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QJsonDocument>
#include <QInputDialog>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
on_bucketTree_currentItemChanged(nullptr, nullptr);
databaseContext = new QMenu();
databaseContext->addAction(ui->actionRefresh_buckets);
databaseContext->addAction(ui->actionAdd_bucket);
databaseContext->addSeparator();
databaseContext->addAction(ui->actionDisconnect);
bucketContext = new QMenu();
bucketContext->addAction(ui->actionRefresh_buckets);
bucketContext->addAction(ui->actionAdd_bucket);
bucketContext->addSeparator();
bucketContext->addAction(ui->actionDelete_bucket);
}
MainWindow::~MainWindow()
{
delete ui;
}
static const int BdbPointerRole = Qt::UserRole + 1;
#define SET_BDB(top, bdb) top->setData(0, BdbPointerRole, QVariant::fromValue<void*>(static_cast<void*>(bdb)))
#define GET_BDB(top) static_cast<BoltDB*>( top->data(0, BdbPointerRole).value<void*>() )
void MainWindow::on_actionNew_database_triggered()
{
QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as..."));
if (file.length()) {
openDatabase(file);
}
}
void MainWindow::on_actionOpen_database_triggered()
{
QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database..."));
if (file.length()) {
openDatabase(file);
}
}
void MainWindow::openDatabase(QString file)
{
// Open
QString error;
auto *bdb = BoltDB::createFrom(file, error);
if (bdb == nullptr) {
QMessageBox qmb;
qmb.setText(tr("Error opening database: %1").arg(error));
qmb.exec();
return;
}
QTreeWidgetItem *top = new QTreeWidgetItem();
top->setText(0, QFileInfo(file).fileName());
top->setIcon(0, QIcon(":/rsrc/database.png"));
SET_BDB(top, bdb);
ui->bucketTree->addTopLevelItem(top);
refreshBucketTree(top);
ui->bucketTree->setCurrentItem(top);
ui->bucketTree->expandItem(top);
}
void MainWindow::refreshBucketTree(QTreeWidgetItem* itm)
{
QTreeWidgetItem *top = itm;
QStringList browsePath;
while(top->parent() != nullptr) {
browsePath.push_front(top->text(0));
top = top->parent();
}
// Remove existing children
for (int i = itm->childCount(); i --> 0;) {
delete itm->takeChild(i);
}
auto *bdb = GET_BDB(top);
QString error;
bool ok = bdb->listBuckets(
browsePath,
error,
[=](QByteArray qba){
QTreeWidgetItem *child = new QTreeWidgetItem();
child->setText(0, QString::fromUtf8(qba));
child->setIcon(0, QIcon(":/rsrc/table.png"));
itm->addChild(child);
refreshBucketTree(child);
}
);
if (!ok) {
QMessageBox qmb;
qmb.setText(tr("Error listing buckets: %1").arg(error));
qmb.exec();
// (continue)
}
}
void MainWindow::on_actionExit_triggered()
{
close();
}
void MainWindow::on_actionAbout_Qt_triggered()
{
QApplication::aboutQt();
}
void MainWindow::on_actionAbout_qbolt_triggered()
{
QMessageBox::about(
this,
QApplication::applicationDisplayName(),
"<b>QBolt</b><br>Graphical interface for managing Bolt databases<br><br>"
"- <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br>"
"- <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam &quot;Silk&quot; icon set</a><br>"
"- <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br>"
);
}
void MainWindow::on_actionDisconnect_triggered()
{
QTreeWidgetItem *top = lastContextSelection;
if (top->parent()) {
return; // somehow we didn't select a top-level item
}
auto *bdb = GET_BDB(top);
// Remove UI
ui->bucketTree->clearSelection();
delete top;
// Disconnect from DB
delete bdb;
}
void MainWindow::on_bucketTree_customContextMenuRequested(const QPoint &pos)
{
auto *itm = ui->bucketTree->itemAt(pos);
if (itm == nullptr) {
return;
}
lastContextSelection = itm;
if (itm->parent() != nullptr) {
// Child item, show the bucket menu
bucketContext->popup(ui->bucketTree->mapToGlobal(pos));
} else {
// Top-level item, show the database menu
databaseContext->popup(ui->bucketTree->mapToGlobal(pos));
}
}
void MainWindow::on_actionRefresh_buckets_triggered()
{
refreshBucketTree(lastContextSelection);
}
void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
Q_UNUSED(previous);
if (current == nullptr) {
ui->stackedWidget->setVisible(false);
return;
}
ui->stackedWidget->setVisible(true);
if (current->parent() == nullptr) {
// Selected a database
ui->stackedWidget->setCurrentWidget(ui->databasePage);
ui->databasePropertiesArea->clear();
auto *bdb = GET_BDB(current);
bdb->getStatsJSON(
[=](QByteArray j) {
auto doc = QJsonDocument::fromJson(j);
ui->databasePropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented)));
},
[=](QString error) {
ui->databasePropertiesArea->setPlainText(tr("Error retrieving database statistics: %1").arg(error));
}
);
// Clean up foreign areas
ui->bucketPropertiesArea->clear();
ui->bucketData->clear();
} else {
// Selected a bucket
ui->stackedWidget->setCurrentWidget(ui->bucketPage);
ui->bucketPropertiesArea->clear();
QStringList browse;
QTreeWidgetItem *top = current;
while (top->parent() != nullptr) {
browse.push_front(top->text(0));
top = top->parent();
}
auto *bdb = GET_BDB(top);
bdb->getBucketStatsJSON(
browse,
[=](QByteArray j) {
auto doc = QJsonDocument::fromJson(j);
ui->bucketPropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented)));
},
[=](QString error) {
ui->bucketPropertiesArea->setPlainText(tr("Error retrieving bucket statistics: %1").arg(error));
}
);
// Load the data tab
refreshData(bdb, browse);
// Clean up foreign areas
ui->databasePropertiesArea->clear();
}
}
void MainWindow::refreshData(BoltDB *bdb, QStringList browse)
{
// Load the data tab
ui->bucketData->clear();
QString err;
bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) {
auto *itm = new QTreeWidgetItem();
itm->setText(0, QString::fromUtf8(name));
itm->setText(1, QString("%1").arg(dataLen));
ui->bucketData->addTopLevelItem(itm);
});
if (! ok) {
QMessageBox qmb;
qmb.setText(tr("Error listing bucket content: %1").arg(err));
qmb.exec();
}
ui->bucketData->resizeColumnToContents(0);
on_bucketData_itemSelectionChanged();
}
void MainWindow::on_actionClear_selection_triggered()
{
ui->bucketTree->setCurrentItem(nullptr);
}
#define GET_ITM_TOP_BROWSE_BDB \
QTreeWidgetItem* itm = lastContextSelection; \
if (itm == nullptr) { \
return; \
} \
QTreeWidgetItem* top = itm; \
QStringList browse; \
while(top->parent() != nullptr) { \
browse.push_front(top->text(0)); \
top = top->parent(); \
} \
auto *bdb = GET_BDB(top);
void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index)
{
GET_ITM_TOP_BROWSE_BDB;
// Get item key
auto model = index.model();
QString key = model->data(model->index(index.row(), 0), 0).toString();
// DB lookup
bdb->getData(
browse,
key.toUtf8(),
[=](QByteArray content) {
auto iw = new ItemWindow();
iw->ContentArea()->setPlainText(QString::fromUtf8(content));
iw->setWindowTitle(key);
connect(iw, &ItemWindow::finished, iw, &ItemWindow::deleteLater);
iw->show();
},
[=](QString error) {
QMessageBox qmb;
qmb.setText(tr("Error loading item content: %1").arg(error));
qmb.exec();
}
);
}
void MainWindow::on_actionAdd_bucket_triggered()
{
GET_ITM_TOP_BROWSE_BDB;
// Prompt for bucket name
QString name = QInputDialog::getText(this, tr("New bucket"), tr("Enter a key for the new bucket:"));
if (name.length() == 0) {
return;
}
// Create
QString err;
if (! bdb->addBucket(browse, name.toUtf8(), err)) {
QMessageBox qmb;
qmb.setText(tr("Error creating bucket: %1").arg(err));
qmb.exec();
return;
}
// Refresh bucket list
refreshBucketTree(itm); // sub-tree only
ui->bucketTree->expandItem(itm);
}
void MainWindow::on_actionDelete_bucket_triggered()
{
GET_ITM_TOP_BROWSE_BDB;
// Prompt for confirmation
QString bucketToDelete = itm->text(0);
if (QMessageBox::question(this, tr("Delete bucket"), tr("Are you sure you want to remove the bucket '%1'?").arg(bucketToDelete), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) {
return;
}
// One level down
browse.pop_back();
QString err;
if (! bdb->deleteBucket(browse, bucketToDelete.toUtf8(), err)) {
QMessageBox qmb;
qmb.setText(tr("Error removing bucket: %1").arg(err));
qmb.exec();
return;
}
// Refresh bucket list
refreshBucketTree(itm->parent()); // sub-tree only
ui->bucketTree->expandItem(itm->parent());
}
void MainWindow::on_AddDataButton_clicked()
{
}
void MainWindow::on_DeleteDataButton_clicked()
{
GET_ITM_TOP_BROWSE_BDB;
auto selection = ui->bucketData->selectedItems();
if (selection.length() == 0) {
return; // nothing to do
}
// Prompt for confirmation
if (QMessageBox::question(this, tr("Delete items"), tr("Are you sure you want to remove %1 item(s)?").arg(selection.length()), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) {
return;
}
QString err;
for (int i = selection.length(); i-->0;) {
if (! bdb->deleteItem(browse, selection[i]->text(0).toUtf8(), err)) {
QMessageBox qmb;
qmb.setText(tr("Error removing item: %1").arg(err));
qmb.exec();
}
}
refreshData(bdb, browse);
}
void MainWindow::on_bucketData_itemSelectionChanged()
{
ui->DeleteDataButton->setEnabled( (ui->bucketData->selectedItems().size() > 0) );
}