2017-05-16 07:34:54 +00:00
# include "mainwindow.h"
# include "ui_mainwindow.h"
2017-05-21 04:44:44 +00:00
# include "itemwindow.h"
2017-05-20 02:57:51 +00:00
# include "boltdb.h"
2017-05-16 07:34:54 +00:00
# include <QFileDialog>
# include <QMessageBox>
2017-05-21 03:49:47 +00:00
# include <QJsonDocument>
2017-05-21 05:08:15 +00:00
# include <QInputDialog>
2017-05-16 07:34:54 +00:00
MainWindow : : MainWindow ( QWidget * parent ) :
QMainWindow ( parent ) ,
ui ( new Ui : : MainWindow )
{
ui - > setupUi ( this ) ;
2017-05-20 23:47:16 +00:00
on_bucketTree_currentItemChanged ( nullptr , nullptr ) ;
databaseContext = new QMenu ( ) ;
databaseContext - > addAction ( ui - > actionRefresh_buckets ) ;
2017-05-21 05:08:15 +00:00
databaseContext - > addAction ( ui - > actionAdd_bucket ) ;
databaseContext - > addSeparator ( ) ;
2017-05-20 23:47:16 +00:00
databaseContext - > addAction ( ui - > actionDisconnect ) ;
bucketContext = new QMenu ( ) ;
2017-05-21 01:49:41 +00:00
bucketContext - > addAction ( ui - > actionRefresh_buckets ) ;
2017-05-21 05:08:15 +00:00
bucketContext - > addAction ( ui - > actionAdd_bucket ) ;
bucketContext - > addSeparator ( ) ;
2017-05-20 23:47:16 +00:00
bucketContext - > addAction ( ui - > actionDelete_bucket ) ;
2017-05-16 07:34:54 +00:00
}
MainWindow : : ~ MainWindow ( )
{
delete ui ;
}
2017-05-20 23:47:16 +00:00
static const int BdbPointerRole = Qt : : UserRole + 1 ;
2017-06-19 08:27:05 +00:00
static const int BinaryDataRole = Qt : : UserRole + 2 ;
2017-05-20 23:47:16 +00:00
# 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*>() )
2017-05-21 04:50:17 +00:00
void MainWindow : : on_actionNew_database_triggered ( )
{
QString file = QFileDialog : : getSaveFileName ( this , tr ( " Save new bolt database as... " ) ) ;
if ( file . length ( ) ) {
2017-05-25 07:59:53 +00:00
openDatabase ( file , false ) ;
2017-05-21 04:50:17 +00:00
}
}
2017-05-16 07:34:54 +00:00
void MainWindow : : on_actionOpen_database_triggered ( )
{
QString file = QFileDialog : : getOpenFileName ( this , tr ( " Select bolt database... " ) ) ;
2017-05-21 04:50:17 +00:00
if ( file . length ( ) ) {
2017-05-25 07:59:53 +00:00
openDatabase ( file , false ) ;
2017-05-16 07:34:54 +00:00
}
2017-05-21 04:50:17 +00:00
}
2017-05-16 07:34:54 +00:00
2017-05-25 07:59:53 +00:00
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 )
2017-05-21 04:50:17 +00:00
{
2017-05-20 02:57:51 +00:00
// Open
QString error ;
2017-05-25 07:59:53 +00:00
auto * bdb = BoltDB : : createFrom ( file , readOnly , error ) ;
2017-05-20 02:57:51 +00:00
if ( bdb = = nullptr ) {
2017-05-16 07:34:54 +00:00
QMessageBox qmb ;
2017-05-20 05:28:27 +00:00
qmb . setText ( tr ( " Error opening database: %1 " ) . arg ( error ) ) ;
2017-05-16 07:34:54 +00:00
qmb . exec ( ) ;
return ;
}
2017-05-20 03:47:05 +00:00
QTreeWidgetItem * top = new QTreeWidgetItem ( ) ;
2017-05-20 23:47:16 +00:00
top - > setText ( 0 , QFileInfo ( file ) . fileName ( ) ) ;
2017-05-20 03:47:05 +00:00
top - > setIcon ( 0 , QIcon ( " :/rsrc/database.png " ) ) ;
2017-05-20 23:47:16 +00:00
SET_BDB ( top , bdb ) ;
2017-05-20 03:47:05 +00:00
ui - > bucketTree - > addTopLevelItem ( top ) ;
2017-05-20 23:47:16 +00:00
refreshBucketTree ( top ) ;
2017-05-21 01:31:28 +00:00
ui - > bucketTree - > setCurrentItem ( top ) ;
2017-05-21 04:44:44 +00:00
ui - > bucketTree - > expandItem ( top ) ;
2017-05-20 23:47:16 +00:00
}
2017-06-19 08:27:05 +00:00
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
2017-06-19 08:42:51 +00:00
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 ;
2017-06-19 08:27:05 +00:00
}
2017-05-21 01:31:28 +00:00
void MainWindow : : refreshBucketTree ( QTreeWidgetItem * itm )
2017-05-20 23:47:16 +00:00
{
2017-05-21 01:31:28 +00:00
QTreeWidgetItem * top = itm ;
2017-06-19 08:53:50 +00:00
QList < QByteArray > browsePath ;
2017-05-21 01:31:28 +00:00
while ( top - > parent ( ) ! = nullptr ) {
2017-06-19 08:53:50 +00:00
browsePath . push_front ( top - > data ( 0 , BinaryDataRole ) . toByteArray ( ) ) ;
2017-05-21 01:31:28 +00:00
top = top - > parent ( ) ;
}
2017-05-20 23:47:16 +00:00
2017-05-21 01:31:28 +00:00
// Remove existing children
for ( int i = itm - > childCount ( ) ; i - - > 0 ; ) {
delete itm - > takeChild ( i ) ;
}
2017-05-21 01:49:36 +00:00
2017-05-21 01:31:28 +00:00
auto * bdb = GET_BDB ( top ) ;
2017-05-20 23:47:16 +00:00
QString error ;
2017-05-21 01:31:28 +00:00
bool ok = bdb - > listBuckets (
browsePath ,
error ,
[ = ] ( QByteArray qba ) {
QTreeWidgetItem * child = new QTreeWidgetItem ( ) ;
2017-06-19 08:27:05 +00:00
child - > setText ( 0 , getDisplayName ( qba ) ) ;
child - > setData ( 0 , BinaryDataRole , qba ) ;
2017-05-21 01:31:28 +00:00
child - > setIcon ( 0 , QIcon ( " :/rsrc/table.png " ) ) ;
itm - > addChild ( child ) ;
refreshBucketTree ( child ) ;
}
) ;
2017-05-20 05:28:27 +00:00
if ( ! ok ) {
QMessageBox qmb ;
qmb . setText ( tr ( " Error listing buckets: %1 " ) . arg ( error ) ) ;
qmb . exec ( ) ;
// (continue)
}
2017-05-16 07:34:54 +00:00
}
2017-05-20 03:20:30 +00:00
void MainWindow : : on_actionExit_triggered ( )
{
close ( ) ;
}
void MainWindow : : on_actionAbout_Qt_triggered ( )
{
QApplication : : aboutQt ( ) ;
}
void MainWindow : : on_actionAbout_qbolt_triggered ( )
{
QMessageBox : : about (
this ,
QApplication : : applicationDisplayName ( ) ,
" <b>QBolt</b><br>Graphical interface for managing Bolt databases<br><br> "
" - <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br> "
" - <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam "Silk" icon set</a><br> "
" - <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br> "
) ;
}
2017-05-20 23:47:16 +00:00
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 ( )
{
2017-05-21 01:49:41 +00:00
refreshBucketTree ( lastContextSelection ) ;
2017-05-20 23:47:16 +00:00
}
void MainWindow : : on_bucketTree_currentItemChanged ( QTreeWidgetItem * current , QTreeWidgetItem * previous )
{
Q_UNUSED ( previous ) ;
if ( current = = nullptr ) {
2017-05-21 00:39:55 +00:00
ui - > stackedWidget - > setVisible ( false ) ;
2017-05-20 23:47:16 +00:00
return ;
}
2017-05-21 00:39:55 +00:00
ui - > stackedWidget - > setVisible ( true ) ;
2017-05-20 23:59:38 +00:00
if ( current - > parent ( ) = = nullptr ) {
// Selected a database
2017-05-21 00:39:55 +00:00
ui - > stackedWidget - > setCurrentWidget ( ui - > databasePage ) ;
2017-05-21 01:47:46 +00:00
ui - > databasePropertiesArea - > clear ( ) ;
2017-05-21 00:39:55 +00:00
2017-05-20 23:59:38 +00:00
auto * bdb = GET_BDB ( current ) ;
2017-05-21 00:39:55 +00:00
bdb - > getStatsJSON (
2017-05-21 03:49:47 +00:00
[ = ] ( QByteArray j ) {
auto doc = QJsonDocument : : fromJson ( j ) ;
ui - > databasePropertiesArea - > setPlainText ( QString : : fromUtf8 ( doc . toJson ( QJsonDocument : : Indented ) ) ) ;
2017-05-21 00:39:55 +00:00
} ,
[ = ] ( QString error ) {
2017-05-21 01:47:46 +00:00
ui - > databasePropertiesArea - > setPlainText ( tr ( " Error retrieving database statistics: %1 " ) . arg ( error ) ) ;
2017-05-21 00:39:55 +00:00
}
) ;
2017-05-20 23:59:38 +00:00
2017-05-21 04:15:49 +00:00
// Clean up foreign areas
ui - > bucketPropertiesArea - > clear ( ) ;
ui - > bucketData - > clear ( ) ;
2017-05-20 23:59:38 +00:00
} else {
// Selected a bucket
2017-05-21 00:39:55 +00:00
ui - > stackedWidget - > setCurrentWidget ( ui - > bucketPage ) ;
2017-05-21 01:47:46 +00:00
ui - > bucketPropertiesArea - > clear ( ) ;
2017-05-21 00:39:55 +00:00
2017-06-19 08:53:50 +00:00
QList < QByteArray > browse ;
2017-05-21 00:39:55 +00:00
QTreeWidgetItem * top = current ;
while ( top - > parent ( ) ! = nullptr ) {
2017-06-19 08:53:50 +00:00
browse . push_front ( top - > data ( 0 , BinaryDataRole ) . toByteArray ( ) ) ;
2017-05-21 00:39:55 +00:00
top = top - > parent ( ) ;
}
auto * bdb = GET_BDB ( top ) ;
bdb - > getBucketStatsJSON (
browse ,
2017-05-21 03:49:47 +00:00
[ = ] ( QByteArray j ) {
auto doc = QJsonDocument : : fromJson ( j ) ;
ui - > bucketPropertiesArea - > setPlainText ( QString : : fromUtf8 ( doc . toJson ( QJsonDocument : : Indented ) ) ) ;
2017-05-21 00:39:55 +00:00
} ,
[ = ] ( QString error ) {
2017-05-21 01:47:46 +00:00
ui - > bucketPropertiesArea - > setPlainText ( tr ( " Error retrieving bucket statistics: %1 " ) . arg ( error ) ) ;
2017-05-21 00:39:55 +00:00
}
) ;
2017-05-20 23:59:38 +00:00
// Load the data tab
2017-05-21 05:44:07 +00:00
refreshData ( bdb , browse ) ;
2017-05-21 04:44:44 +00:00
2017-05-21 04:15:49 +00:00
// Clean up foreign areas
ui - > databasePropertiesArea - > clear ( ) ;
2017-05-20 23:59:38 +00:00
}
2017-05-20 23:47:16 +00:00
}
2017-06-19 08:53:50 +00:00
void MainWindow : : refreshData ( BoltDB * bdb , const QList < QByteArray > & browse )
2017-05-21 05:44:07 +00:00
{
// Load the data tab
ui - > bucketData - > clear ( ) ;
QString err ;
bool ok = bdb - > listKeys ( browse , err , [ = ] ( QByteArray name , int64_t dataLen ) {
auto * itm = new QTreeWidgetItem ( ) ;
2017-06-19 08:27:05 +00:00
itm - > setText ( 0 , getDisplayName ( name ) ) ;
itm - > setData ( 0 , BinaryDataRole , name ) ;
2017-05-21 05:44:07 +00:00
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 ( ) ;
}
2017-05-20 23:47:16 +00:00
void MainWindow : : on_actionClear_selection_triggered ( )
{
ui - > bucketTree - > setCurrentItem ( nullptr ) ;
}
2017-05-21 04:44:44 +00:00
2017-05-21 05:44:07 +00:00
# define GET_ITM_TOP_BROWSE_BDB \
2017-05-21 05:53:40 +00:00
QTreeWidgetItem * itm = ui - > bucketTree - > currentItem ( ) ; \
2017-05-21 05:44:07 +00:00
if ( itm = = nullptr ) { \
return ; \
} \
QTreeWidgetItem * top = itm ; \
2017-06-19 08:53:50 +00:00
QList < QByteArray > browse ; \
2017-05-21 05:44:07 +00:00
while ( top - > parent ( ) ! = nullptr ) { \
2017-06-19 08:53:50 +00:00
browse . push_front ( top - > data ( 0 , BinaryDataRole ) . toByteArray ( ) ) ; \
2017-05-21 05:44:07 +00:00
top = top - > parent ( ) ; \
} \
auto * bdb = GET_BDB ( top ) ;
2017-06-19 08:53:50 +00:00
void MainWindow : : openEditor ( BoltDB * bdb , const QList < QByteArray > & saveAs , QByteArray saveAsKey , QByteArray currentContent )
2017-05-21 05:59:27 +00:00
{
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 ( ) ;
}
2017-05-21 04:44:44 +00:00
void MainWindow : : on_bucketData_doubleClicked ( const QModelIndex & index )
{
2017-05-21 05:44:07 +00:00
GET_ITM_TOP_BROWSE_BDB ;
2017-05-21 04:44:44 +00:00
// Get item key
auto model = index . model ( ) ;
2017-06-19 08:27:05 +00:00
const QByteArray & key = model - > data ( model - > index ( index . row ( ) , 0 ) , BinaryDataRole ) . toByteArray ( ) ;
2017-05-21 04:44:44 +00:00
// DB lookup
bdb - > getData (
browse ,
2017-06-19 08:27:05 +00:00
key ,
2017-05-21 04:44:44 +00:00
[ = ] ( QByteArray content ) {
2017-06-19 08:27:05 +00:00
openEditor ( bdb , browse , key , content ) ;
2017-05-21 04:44:44 +00:00
} ,
[ = ] ( QString error ) {
QMessageBox qmb ;
qmb . setText ( tr ( " Error loading item content: %1 " ) . arg ( error ) ) ;
qmb . exec ( ) ;
}
) ;
}
2017-05-21 05:08:15 +00:00
void MainWindow : : on_actionAdd_bucket_triggered ( )
{
2017-05-21 05:44:07 +00:00
GET_ITM_TOP_BROWSE_BDB ;
2017-05-21 05:08:15 +00:00
// 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 ( )
{
2017-05-21 05:44:07 +00:00
GET_ITM_TOP_BROWSE_BDB ;
2017-05-21 05:08:15 +00:00
// Prompt for confirmation
2017-06-19 08:27:05 +00:00
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
) {
2017-05-21 05:14:20 +00:00
return ;
}
2017-05-25 07:53:35 +00:00
QTreeWidgetItem * parent = itm - > parent ( ) ;
2017-05-21 05:14:20 +00:00
// One level down
browse . pop_back ( ) ;
2017-05-21 05:08:15 +00:00
2017-05-21 05:14:20 +00:00
QString err ;
2017-06-19 08:27:05 +00:00
if ( ! bdb - > deleteBucket ( browse , bucketToDelete , err ) ) {
2017-05-21 05:14:20 +00:00
QMessageBox qmb ;
qmb . setText ( tr ( " Error removing bucket: %1 " ) . arg ( err ) ) ;
qmb . exec ( ) ;
2017-05-21 05:08:15 +00:00
return ;
}
2017-05-21 05:14:20 +00:00
// Refresh bucket list
2017-05-25 07:53:35 +00:00
refreshBucketTree ( parent ) ; // sub-tree only
ui - > bucketTree - > expandItem ( parent ) ;
ui - > bucketTree - > setCurrentItem ( parent ) ;
2017-05-21 05:08:15 +00:00
}
2017-05-21 05:44:07 +00:00
void MainWindow : : on_AddDataButton_clicked ( )
{
2017-05-21 05:59:27 +00:00
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 ;
}
2017-05-21 05:44:07 +00:00
2017-05-21 05:59:27 +00:00
openEditor ( bdb , browse , name . toUtf8 ( ) , QByteArray ( ) ) ;
2017-05-21 05:44:07 +00:00
}
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 ; ) {
2017-06-19 08:27:52 +00:00
if ( ! bdb - > deleteItem ( browse , selection [ i ] - > data ( 0 , BinaryDataRole ) . toByteArray ( ) , err ) ) {
2017-05-21 05:44:07 +00:00
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 ) ) ;
}