#include "mainwindow.h" #include "ui_mainwindow.h" #include "itemwindow.h" #include "boltdb.h" #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); on_bucketTree_currentItemChanged(nullptr, nullptr); databaseContext = new QMenu(); databaseContext->addAction(ui->actionRefresh_buckets); databaseContext->addAction(ui->actionAdd_bucket); databaseContext->addSeparator(); databaseContext->addAction(ui->actionDisconnect); bucketContext = new QMenu(); bucketContext->addAction(ui->actionRefresh_buckets); bucketContext->addAction(ui->actionAdd_bucket); bucketContext->addSeparator(); bucketContext->addAction(ui->actionDelete_bucket); } MainWindow::~MainWindow() { delete ui; } static const int BdbPointerRole = Qt::UserRole + 1; static const int BinaryDataRole = Qt::UserRole + 2; #define SET_BDB(top, bdb) top->setData(0, BdbPointerRole, QVariant::fromValue(static_cast(bdb))) #define GET_BDB(top) static_cast( top->data(0, BdbPointerRole).value() ) void MainWindow::on_actionNew_database_triggered() { QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as...")); if (file.length()) { openDatabase(file, false); } } void MainWindow::on_actionOpen_database_triggered() { QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database...")); if (file.length()) { openDatabase(file, false); } } void MainWindow::on_actionOpen_database_as_read_only_triggered() { QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database...")); if (file.length()) { openDatabase(file, true); } } void MainWindow::openDatabase(QString file, bool readOnly) { // Open QString error; auto *bdb = BoltDB::createFrom(file, readOnly, error); if (bdb == nullptr) { QMessageBox qmb; qmb.setText(tr("Error opening database: %1").arg(error)); qmb.exec(); return; } QTreeWidgetItem *top = new QTreeWidgetItem(); top->setText(0, QFileInfo(file).fileName()); top->setIcon(0, QIcon(":/rsrc/database.png")); SET_BDB(top, bdb); ui->bucketTree->addTopLevelItem(top); refreshBucketTree(top); ui->bucketTree->setCurrentItem(top); ui->bucketTree->expandItem(top); } static const QString getDisplayName(const QByteArray &qba) { // FIXME the formatting isn't so great when control characters, etc. are used // A C-style escape display, or the unicode-replacement-character would be preferable QString ret(QString::fromUtf8(qba)); bool allPrintable = true; for (auto i = ret.begin(), e = ret.end(); i != e; ++i) { if (! i->isPrint()) { allPrintable = false; break; } } if (allPrintable) { return ret; // fine } // Some of the characters weren't printable. // Build up a replacement string QString replacement; for (auto i = ret.begin(), e = ret.end(); i != e; ++i) { replacement += i->isPrint() ? *i : QStringLiteral("\\u{%1}").arg(i->unicode()); } return replacement; } void MainWindow::refreshBucketTree(QTreeWidgetItem* itm) { QTreeWidgetItem *top = itm; QList browsePath; while(top->parent() != nullptr) { browsePath.push_front(top->data(0, BinaryDataRole).toByteArray()); top = top->parent(); } // Remove existing children for (int i = itm->childCount(); i --> 0;) { delete itm->takeChild(i); } auto *bdb = GET_BDB(top); QString error; bool ok = bdb->listBuckets( browsePath, error, [=](QByteArray qba){ QTreeWidgetItem *child = new QTreeWidgetItem(); child->setText(0, getDisplayName(qba)); child->setData(0, BinaryDataRole, qba); child->setIcon(0, QIcon(":/rsrc/table.png")); itm->addChild(child); refreshBucketTree(child); } ); if (!ok) { QMessageBox qmb; qmb.setText(tr("Error listing buckets: %1").arg(error)); qmb.exec(); // (continue) } } void MainWindow::on_actionExit_triggered() { close(); } void MainWindow::on_actionAbout_Qt_triggered() { QApplication::aboutQt(); } void MainWindow::on_actionAbout_qbolt_triggered() { QMessageBox::about( this, QApplication::applicationDisplayName(), "QBolt
Graphical interface for managing Bolt databases

" "- About BoltDB
" "- FamFamFam "Silk" icon set
" "- QBolt homepage
" ); } void MainWindow::on_actionDisconnect_triggered() { QTreeWidgetItem *top = lastContextSelection; if (top->parent()) { return; // somehow we didn't select a top-level item } auto *bdb = GET_BDB(top); // Remove UI ui->bucketTree->clearSelection(); delete top; // Disconnect from DB delete bdb; } void MainWindow::on_bucketTree_customContextMenuRequested(const QPoint &pos) { auto *itm = ui->bucketTree->itemAt(pos); if (itm == nullptr) { return; } lastContextSelection = itm; if (itm->parent() != nullptr) { // Child item, show the bucket menu bucketContext->popup(ui->bucketTree->mapToGlobal(pos)); } else { // Top-level item, show the database menu databaseContext->popup(ui->bucketTree->mapToGlobal(pos)); } } void MainWindow::on_actionRefresh_buckets_triggered() { refreshBucketTree(lastContextSelection); } void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) { Q_UNUSED(previous); if (current == nullptr) { ui->stackedWidget->setVisible(false); return; } ui->stackedWidget->setVisible(true); if (current->parent() == nullptr) { // Selected a database ui->stackedWidget->setCurrentWidget(ui->databasePage); ui->databasePropertiesArea->clear(); auto *bdb = GET_BDB(current); bdb->getStatsJSON( [=](QByteArray j) { auto doc = QJsonDocument::fromJson(j); ui->databasePropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); }, [=](QString error) { ui->databasePropertiesArea->setPlainText(tr("Error retrieving database statistics: %1").arg(error)); } ); // Clean up foreign areas ui->bucketPropertiesArea->clear(); ui->bucketData->clear(); } else { // Selected a bucket ui->stackedWidget->setCurrentWidget(ui->bucketPage); ui->bucketPropertiesArea->clear(); QList browse; QTreeWidgetItem *top = current; while (top->parent() != nullptr) { browse.push_front(top->data(0, BinaryDataRole).toByteArray()); top = top->parent(); } auto *bdb = GET_BDB(top); bdb->getBucketStatsJSON( browse, [=](QByteArray j) { auto doc = QJsonDocument::fromJson(j); ui->bucketPropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); }, [=](QString error) { ui->bucketPropertiesArea->setPlainText(tr("Error retrieving bucket statistics: %1").arg(error)); } ); // Load the data tab refreshData(bdb, browse); // Clean up foreign areas ui->databasePropertiesArea->clear(); } } void MainWindow::refreshData(BoltDB *bdb, const QList& browse) { // Load the data tab ui->bucketData->clear(); QString err; bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) { auto *itm = new QTreeWidgetItem(); itm->setText(0, getDisplayName(name)); itm->setData(0, BinaryDataRole, name); itm->setText(1, QString("%1").arg(dataLen)); ui->bucketData->addTopLevelItem(itm); }); if (! ok) { QMessageBox qmb; qmb.setText(tr("Error listing bucket content: %1").arg(err)); qmb.exec(); } ui->bucketData->resizeColumnToContents(0); on_bucketData_itemSelectionChanged(); } void MainWindow::on_actionClear_selection_triggered() { ui->bucketTree->setCurrentItem(nullptr); } #define GET_ITM_TOP_BROWSE_BDB \ QTreeWidgetItem* itm = ui->bucketTree->currentItem(); \ if (itm == nullptr) { \ return; \ } \ QTreeWidgetItem* top = itm; \ QList browse; \ while(top->parent() != nullptr) { \ browse.push_front(top->data(0, BinaryDataRole).toByteArray()); \ top = top->parent(); \ } \ auto *bdb = GET_BDB(top); void MainWindow::openEditor(BoltDB *bdb, const QList& saveAs, QByteArray saveAsKey, QByteArray currentContent) { auto iw = new ItemWindow(); iw->ContentArea()->setPlainText(QString::fromUtf8(currentContent)); iw->setWindowTitle(QString::fromUtf8(saveAsKey)); iw->setWindowModality(Qt::ApplicationModal); // we need this - otherwise we'll refresh a possibly-changed area after saving connect(iw, &ItemWindow::finished, iw, [=](int exitCode){ if (exitCode == ItemWindow::Accepted) { QString err; if (! bdb->setItem(saveAs, saveAsKey, iw->ContentArea()->toPlainText().toUtf8(), err)) { QMessageBox qmb; qmb.setText(tr("Error saving item content: %1").arg(err)); qmb.exec(); } refreshData(bdb, saveAs); } iw->deleteLater(); }); iw->show(); } void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index) { GET_ITM_TOP_BROWSE_BDB; // Get item key auto model = index.model(); const QByteArray& key = model->data(model->index(index.row(), 0), BinaryDataRole).toByteArray(); // DB lookup bdb->getData( browse, key, [=](QByteArray content) { openEditor(bdb, browse, key, content); }, [=](QString error) { QMessageBox qmb; qmb.setText(tr("Error loading item content: %1").arg(error)); qmb.exec(); } ); } void MainWindow::on_actionAdd_bucket_triggered() { GET_ITM_TOP_BROWSE_BDB; // Prompt for bucket name QString name = QInputDialog::getText(this, tr("New bucket"), tr("Enter a key for the new bucket:")); if (name.length() == 0) { return; } // Create QString err; if (! bdb->addBucket(browse, name.toUtf8(), err)) { QMessageBox qmb; qmb.setText(tr("Error creating bucket: %1").arg(err)); qmb.exec(); return; } // Refresh bucket list refreshBucketTree(itm); // sub-tree only ui->bucketTree->expandItem(itm); } void MainWindow::on_actionDelete_bucket_triggered() { GET_ITM_TOP_BROWSE_BDB; // Prompt for confirmation const QByteArray& bucketToDelete = itm->data(0, BinaryDataRole).toByteArray(); if ( QMessageBox::question( this, tr("Delete bucket"), tr("Are you sure you want to remove the bucket '%1'?").arg(getDisplayName(bucketToDelete)), QMessageBox::Yes, QMessageBox::Cancel ) != QMessageBox::Yes ) { return; } QTreeWidgetItem* parent = itm->parent(); // One level down browse.pop_back(); QString err; if (! bdb->deleteBucket(browse, bucketToDelete, err)) { QMessageBox qmb; qmb.setText(tr("Error removing bucket: %1").arg(err)); qmb.exec(); return; } // Refresh bucket list refreshBucketTree(parent); // sub-tree only ui->bucketTree->expandItem(parent); ui->bucketTree->setCurrentItem(parent); } void MainWindow::on_AddDataButton_clicked() { GET_ITM_TOP_BROWSE_BDB; // Prompt for bucket name QString name = QInputDialog::getText(this, tr("New item"), tr("Enter a key for the new item:")); if (name.length() == 0) { return; } openEditor(bdb, browse, name.toUtf8(), QByteArray()); } void MainWindow::on_DeleteDataButton_clicked() { GET_ITM_TOP_BROWSE_BDB; auto selection = ui->bucketData->selectedItems(); if (selection.length() == 0) { return; // nothing to do } // Prompt for confirmation if (QMessageBox::question(this, tr("Delete items"), tr("Are you sure you want to remove %1 item(s)?").arg(selection.length()), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) { return; } QString err; for (int i = selection.length(); i-->0;) { if (! bdb->deleteItem(browse, selection[i]->data(0, BinaryDataRole).toByteArray(), err)) { QMessageBox qmb; qmb.setText(tr("Error removing item: %1").arg(err)); qmb.exec(); } } refreshData(bdb, browse); } void MainWindow::on_bucketData_itemSelectionChanged() { ui->DeleteDataButton->setEnabled( (ui->bucketData->selectedItems().size() > 0) ); }