Files
sqlitebrowser/src/MainWindow.cpp
Martin Kleusberg 6742f5632d Add a filter row to the table view in the browse tab
Add a row of line edits between the table header and the actual content
of the table in the browse tab. Show one input widget per table column
in this row.

Add a live search which hides any table rows which do not fit to the
current filter settings.

Why add this to the parital-data-fetch branch? Because this is an
attempt to get rid of the find dialog which is broken again and not even
worth fixing. Also there is not much to break in this branch at the
moment ;)
2013-04-09 18:52:37 +02:00

1164 lines
42 KiB
C++

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QFileDialog>
#include <QFile>
#include <QApplication>
#include <QTextStream>
#include <QWhatsThis>
#include <QMessageBox>
#include <QUrl>
#include <QStandardItemModel>
#include <QDragEnterEvent>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include "CreateIndexDialog.h"
#include "AboutDialog.h"
#include "EditTableDialog.h"
#include "ImportCsvDialog.h"
#include "ExportCsvDialog.h"
#include "PreferencesDialog.h"
#include "EditDialog.h"
#include "FindDialog.h"
#include "SQLiteSyntaxHighlighter.h"
#include "sqltextedit.h"
#include "sqlitetablemodel.h"
#include "FilterTableHeader.h"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
ui(new Ui::MainWindow),
m_browseTableModel(new SqliteTableModel(this, &db)),
sqliteHighlighterTabSql(0),
sqliteHighlighterLogUser(0),
sqliteHighlighterLogApp(0),
editWin(new EditDialog(this)),
findWin(0),
gotoValidator(new QIntValidator(0, 0, this))
{
ui->setupUi(this);
init();
activateFields(false);
resetBrowser();
updateRecentFileActions();
}
MainWindow::~MainWindow()
{
delete gotoValidator;
delete ui;
}
void MainWindow::init()
{
// Init the SQL log dock
db.mainWindow = this;
// Set up the db tree widget
ui->dbTreeWidget->setColumnHidden(1, true);
ui->dbTreeWidget->setColumnWidth(0, 300);
// Set the validator for the goto line edit
ui->editGoto->setValidator(gotoValidator);
// Create the SQL sytax highlighters
createSyntaxHighlighters();
// Set up DB models
ui->dataTable->setModel(m_browseTableModel);
queryResultListModel = new QStandardItemModel(this);
ui->queryResultTableView->setModel(queryResultListModel);
FilterTableHeader* tableHeader = new FilterTableHeader(ui->dataTable);
connect(tableHeader, SIGNAL(filterChanged(int,QString)), m_browseTableModel, SLOT(updateFilter(int,QString)));
ui->dataTable->setHorizontalHeader(tableHeader);
// Create the actions for the recently opened dbs list
for(int i = 0; i < MaxRecentFiles; ++i) {
recentFileActs[i] = new QAction(this);
recentFileActs[i]->setVisible(false);
connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
}
for(int i = 0; i < MaxRecentFiles; ++i)
ui->fileMenu->insertAction(ui->fileExitAction, recentFileActs[i]);
recentSeparatorAct = ui->fileMenu->insertSeparator(ui->fileExitAction);
// Create popup menus
popupTableMenu = new QMenu(this);
popupTableMenu->addAction(ui->editModifyTableAction);
popupTableMenu->addSeparator();
popupTableMenu->addAction(ui->editDeleteObjectAction);
// Set state of checkable menu actions
ui->sqlLogAction->setChecked(!ui->dockLog->isHidden());
ui->viewDBToolbarAction->setChecked(!ui->toolbarDB->isHidden());
// Set statusbar fields
statusEncodingLabel = new QLabel(ui->statusbar);
statusEncodingLabel->setEnabled(false);
statusEncodingLabel->setText("UTF-8");
statusEncodingLabel->setToolTip(tr("Database encoding"));
ui->statusbar->addPermanentWidget(statusEncodingLabel);
// Connect some more signals and slots
connect(tableHeader, SIGNAL(sectionClicked(int)), this, SLOT(browseTableHeaderClicked(int)));
connect(ui->dataTable->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(setRecordsetLabel()));
connect(editWin, SIGNAL(goingAway()), this, SLOT(editWinAway()));
connect(editWin, SIGNAL(updateRecordText(int, int, QByteArray)), this, SLOT(updateRecordText(int, int, QByteArray)));
// Load window settings
restoreGeometry(PreferencesDialog::getSettingsValue("MainWindow", "geometry").toByteArray());
restoreState(PreferencesDialog::getSettingsValue("MainWindow", "windowState").toByteArray());
ui->comboLogSubmittedBy->setCurrentIndex(ui->comboLogSubmittedBy->findText(PreferencesDialog::getSettingsValue("SQLLogDock", "Log").toString()));
// Set other window settings
setAcceptDrops(true);
setWindowTitle(QApplication::applicationName());
// Fonts for edit fields
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
font.setPointSize(8);
ui->sqlTextEdit->setFont(font);
ui->editLogApplication->setFont(font);
ui->editLogUser->setFont(font);
}
//***********************************************************
//*** Open File
void MainWindow::fileOpen(const QString & fileName)
{
QString wFile = fileName;
if (!QFile::exists(wFile))
{
wFile = QFileDialog::getOpenFileName(
this,
tr("Choose a database file"),
PreferencesDialog::getSettingsValue("db", "defaultlocation").toString());
}
if(QFile::exists(wFile) )
{
fileClose();
if(db.open(wFile))
{
statusEncodingLabel->setText(db.getPragma("encoding"));
setCurrentFile(wFile);
} else {
QString err = tr("An error occurred: %1").arg(db.lastErrorMessage);
QMessageBox::warning(this, QApplication::applicationName(), err);
}
populateStructure();
resetBrowser();
if(ui->mainTab->currentIndex() == 2)
loadPragmas();
}
}
void MainWindow::fileOpen()
{
fileOpen(QString());
}
void MainWindow::fileNew()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Choose a filename to save under"), PreferencesDialog::getSettingsValue("db", "defaultlocation").toString());
if(!fileName.isEmpty())
{
if(QFile::exists(fileName))
QFile::remove(fileName);
db.create(fileName);
setCurrentFile(fileName);
statusEncodingLabel->setText(db.getPragma("encoding"));
populateStructure();
resetBrowser();
createTable();
}
}
//** Populate DbTree Structure
void MainWindow::populateStructure()
{
ui->dbTreeWidget->model()->removeRows(0, ui->dbTreeWidget->model()->rowCount());
ui->sqlTextEdit->clearFieldCompleterModelMap();
ui->sqlTextEdit->setDefaultCompleterModel(new QStandardItemModel());
if (!db.isOpen()){
return;
}
db.updateSchema();
QStringList tblnames = db.getBrowsableObjectNames();
sqliteHighlighterTabSql->setTableNames(tblnames);
sqliteHighlighterLogUser->setTableNames(tblnames);
sqliteHighlighterLogApp->setTableNames(tblnames);
// setup models for sqltextedit autocomplete
QStandardItemModel* completerModel = new QStandardItemModel();
completerModel->setRowCount(tblnames.count());
completerModel->setColumnCount(1);
objectMap tab = db.getBrowsableObjects();
int row = 0;
for(objectMap::ConstIterator it=tab.begin(); it!=tab.end(); ++it, ++row)
{
QString sName = it.value().getname();
QStandardItem* item = new QStandardItem(sName);
item->setIcon(QIcon(QString(":icons/%1").arg(it.value().gettype())));
completerModel->setItem(row, 0, item);
// If it is a table add the field Nodes
if((*it).gettype() == "table" || (*it).gettype() == "view")
{
QStandardItemModel* tablefieldmodel = new QStandardItemModel();
tablefieldmodel->setRowCount((*it).fldmap.count());
tablefieldmodel->setColumnCount(1);
fieldMap::ConstIterator fit;
int fldrow = 0;
for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit, ++fldrow ) {
QString fieldname = fit.value().getname();
QStandardItem* fldItem = new QStandardItem(fieldname);
fldItem->setIcon(QIcon(":/icons/field"));
tablefieldmodel->setItem(fldrow, 0, fldItem);
}
ui->sqlTextEdit->addFieldCompleterModel(sName.toLower(), tablefieldmodel);
}
}
ui->sqlTextEdit->setDefaultCompleterModel(completerModel);
// end setup models for sqltextedit autocomplete
// fill the structure tab
QMap<QString, QTreeWidgetItem*> typeToParentItem;
QTreeWidgetItem* itemTables = new QTreeWidgetItem(ui->dbTreeWidget);
itemTables->setIcon(0, QIcon(QString(":/icons/table")));
itemTables->setText(0, tr("Tables (%1)").arg(db.objMap.values("table").count()));
typeToParentItem.insert("table", itemTables);
QTreeWidgetItem* itemIndices = new QTreeWidgetItem(ui->dbTreeWidget);
itemIndices->setIcon(0, QIcon(QString(":/icons/index")));
itemIndices->setText(0, tr("Indices (%1)").arg(db.objMap.values("index").count()));
typeToParentItem.insert("index", itemIndices);
QTreeWidgetItem* itemViews = new QTreeWidgetItem(ui->dbTreeWidget);
itemViews->setIcon(0, QIcon(QString(":/icons/view")));
itemViews->setText(0, tr("Views (%1)").arg(db.objMap.values("view").count()));
typeToParentItem.insert("view", itemViews);
QTreeWidgetItem* itemTriggers = new QTreeWidgetItem(ui->dbTreeWidget);
itemTriggers->setIcon(0, QIcon(QString(":/icons/trigger")));
itemTriggers->setText(0, tr("Triggers (%1)").arg(db.objMap.values("trigger").count()));
typeToParentItem.insert("trigger", itemTriggers);
ui->dbTreeWidget->setItemExpanded(itemTables, true);
ui->dbTreeWidget->setItemExpanded(itemIndices, true);
ui->dbTreeWidget->setItemExpanded(itemViews, true);
ui->dbTreeWidget->setItemExpanded(itemTriggers, true);
for(objectMap::ConstIterator it=db.objMap.begin();it!=db.objMap.end();++it)
{
// Object node
QTreeWidgetItem *tableItem = new QTreeWidgetItem(typeToParentItem.value((*it).gettype()));
tableItem->setIcon(0, QIcon(QString(":/icons/%1").arg((*it).gettype())));
tableItem->setText(0, (*it).getname());
tableItem->setText(1, (*it).gettype());
tableItem->setText(3, (*it).getsql());
// If it is a table add the field Nodes
if((*it).gettype() == "table" || (*it).gettype() == "view")
{
fieldMap::ConstIterator fit;
for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit ) {
QTreeWidgetItem *fldItem = new QTreeWidgetItem(tableItem);
fldItem->setText(0, fit.value().getname());
fldItem->setText(1, "field");
fldItem->setText(2, fit.value().gettype());
fldItem->setIcon(0, QIcon(":/icons/field"));
}
}
}
}
void MainWindow::populateTable( const QString & tablename)
{
// Remove the model-view link if the table name is empty in order to remove any data from the view
if(tablename.isEmpty())
{
ui->dataTable->setModel(0);
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
// Set model
ui->dataTable->setModel(m_browseTableModel);
// Set new table
m_browseTableModel->setTable(tablename);
ui->dataTable->setColumnHidden(0, true);
// Reset sorting
curBrowseOrderByIndex = 0;
curBrowseOrderByMode = Qt::AscendingOrder;
m_browseTableModel->sort(curBrowseOrderByIndex, curBrowseOrderByMode);
// Get table layout
db.browseTable(tablename);
// Update the filter row
qobject_cast<FilterTableHeader*>(ui->dataTable->horizontalHeader())->generateFilters(m_browseTableModel->columnCount());
// Activate the add and delete record buttons and editing only if a table has been selected
bool is_table = db.getObjectByName(tablename).gettype() == "table";
ui->buttonNewRecord->setEnabled(is_table);
ui->buttonDeleteRecord->setEnabled(is_table);
ui->dataTable->setEditTriggers(is_table ? QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed : QAbstractItemView::NoEditTriggers);
// Set the recordset label
setRecordsetLabel();
//got to keep findWin in synch
if(findWin)
findWin->resetFields();
if(editWin)
editWin->reset();
QApplication::restoreOverrideCursor();
}
void MainWindow::resetBrowser()
{
QString sCurrentTable = ui->comboBrowseTable->currentText();
ui->comboBrowseTable->clear();
objectMap tab = db.getBrowsableObjects();
for(objectMap::ConstIterator i=tab.begin();i!=tab.end();++i)
{
ui->comboBrowseTable->addItem(QIcon(QString(":icons/%1").arg(i.value().gettype())), i.value().getname());
//ui->comboBrowseTable->addItems(tab);
}
setRecordsetLabel();
int pos = ui->comboBrowseTable->findText(sCurrentTable);
pos = pos == -1 ? 0 : pos;
ui->comboBrowseTable->setCurrentIndex(pos);
curBrowseOrderByIndex = 0;
curBrowseOrderByMode = Qt::AscendingOrder;
m_browseTableModel->sort(curBrowseOrderByIndex, curBrowseOrderByMode);
populateTable(ui->comboBrowseTable->currentText());
}
void MainWindow::fileClose()
{
db.close();
setWindowTitle(QApplication::applicationName());
resetBrowser();
populateStructure();
loadPragmas();
activateFields(false);
ui->buttonLogClear->click();
}
void MainWindow::fileExit()
{
if (db.isOpen())
{
if (db.getDirty())
{
QString msg = tr("Do you want to save the changes made to the database file %1?").arg(db.curDBFilename);
if(QMessageBox::question( this, QApplication::applicationName() ,msg, QMessageBox::Yes, QMessageBox::No)==QMessageBox::Yes)
db.save();
else
db.revert(); //not really necessary, I think... but will not hurt.
}
db.close();
}
}
void MainWindow::closeEvent( QCloseEvent* event )
{
PreferencesDialog::setSettingsValue("MainWindow", "geometry", saveGeometry());
PreferencesDialog::setSettingsValue("MainWindow", "windowState", saveState());
PreferencesDialog::setSettingsValue("SQLLogDock", "Log", ui->comboLogSubmittedBy->currentText());
fileExit();
QMainWindow::closeEvent(event);
}
void MainWindow::addRecord()
{
int row = m_browseTableModel->rowCount();
if(m_browseTableModel->insertRow(row))
{
selectTableLine(row);
} else {
QMessageBox::warning( this, QApplication::applicationName(), tr("Error adding record:\n") + db.lastErrorMessage);
}
}
void MainWindow::deleteRecord()
{
if(ui->dataTable->currentIndex().isValid())
{
int row = ui->dataTable->currentIndex().row();
if(m_browseTableModel->removeRow(row))
{
populateTable(db.curBrowseTableName);
if(row > m_browseTableModel->totalRowCount())
row = m_browseTableModel->totalRowCount();
selectTableLine(row);
} else {
QMessageBox::warning( this, QApplication::applicationName(), tr("Error deleting record:\n") + db.lastErrorMessage);
}
} else {
QMessageBox::information( this, QApplication::applicationName(), tr("Please select a record first"));
}
}
void MainWindow::selectTableLine(int lineToSelect)
{
ui->dataTable->clearSelection();
ui->dataTable->selectRow(lineToSelect);
ui->dataTable->scrollTo(ui->dataTable->currentIndex());
}
void MainWindow::navigatePrevious()
{
int curRow = ui->dataTable->currentIndex().row();
curRow -= 100;
if(curRow < 0)
curRow = 0;
selectTableLine(curRow);
}
void MainWindow::navigateNext()
{
// TODO: Fetch more data from DB if necessary
int curRow = ui->dataTable->currentIndex().row();
curRow += 100;
if(curRow >= m_browseTableModel->totalRowCount())
curRow = m_browseTableModel->totalRowCount() - 1;
selectTableLine(curRow);
}
void MainWindow::navigateGoto()
{
// TODO: Fetch more data from DB if necessary
int row = ui->editGoto->text().toInt();
if(row <= 0)
row = 1;
if(row > m_browseTableModel->totalRowCount())
row = m_browseTableModel->totalRowCount();
selectTableLine(row - 1);
ui->editGoto->setText(QString::number(row));
}
void MainWindow::setRecordsetLabel()
{
// Get all the numbers, i.e. the number of the first row and the last row as well as the total number of rows
int from = ui->dataTable->verticalHeader()->visualIndexAt(0) + 1;
int to = ui->dataTable->verticalHeader()->visualIndexAt(ui->dataTable->height()) - 1;
int total = m_browseTableModel->totalRowCount();
if(to == -2)
to = total;
// Update the validator of the goto row field
gotoValidator->setRange(0, total);
// Update the label showing the current position
ui->labelRecordset->setText(tr("%1 - %2 of %3").arg(from).arg(to).arg(total));
}
void MainWindow::browseFind(bool open)
{
if(open)
{
if(!findWin)
{
findWin = new FindDialog(this);
connect(findWin, SIGNAL(lookfor(const QString&, const QString&, const QString&)), this, SLOT(lookfor(const QString&, const QString&, const QString&)));
connect(findWin, SIGNAL(showrecord(int)),this, SLOT(selectTableLine(int)));
connect(findWin, SIGNAL(goingAway()),this, SLOT(browseFindAway()));
}
findWin->resetFields(db.getTableFields(db.curBrowseTableName));
findWin->show();
} else {
if(findWin)
findWin->hide();
}
}
void MainWindow::browseFindAway()
{
ui->buttonFind->toggle();
}
void MainWindow::browseRefresh()
{
populateTable(ui->comboBrowseTable->currentText());
}
void MainWindow::lookfor( const QString & wfield, const QString & woperator, const QString & wsearchterm )
{
if (!db.isOpen()){
QMessageBox::information( this, QApplication::applicationName(), tr("There is no database opened. Please open or create a new database file."));
return;
}
//we may need to modify woperator and wsearchterm, so use copies
QString finaloperator = woperator;
QString finalsearchterm = wsearchterm;
//special case for CONTAINS operator: use LIKE and surround the search word with % characters
if(woperator.compare(tr("contains")) == 0)
{
finaloperator = QString("LIKE");
QString newsearchterm = "%";
newsearchterm.append(wsearchterm);
newsearchterm.append("%");
finalsearchterm = QString(newsearchterm);
}
QApplication::setOverrideCursor( Qt::WaitCursor );
QString statement = QString("SELECT rowid, `%1` FROM `%2` WHERE `%3` %4 ").arg(wfield).arg(db.curBrowseTableName).arg(wfield).arg(finaloperator);
//searchterm needs to be quoted if it is not a number
bool ok = false;
finalsearchterm.toDouble(&ok);
if (!ok) finalsearchterm.toInt(&ok, 10);
if (!ok) {//not a number, quote it
char * formSQL = sqlite3_mprintf("%Q",(const char *) finalsearchterm.toUtf8());
statement.append(formSQL);
if (formSQL) sqlite3_free(formSQL);
} else {//append the number, unquoted
statement.append(finalsearchterm);
}
statement.append(" ORDER BY rowid;");
resultMap res = db.getFindResults(statement);
findWin->showResults(res);
QApplication::restoreOverrideCursor();
}
void MainWindow::createTable()
{
if (!db.isOpen()){
QMessageBox::information( this, QApplication::applicationName(), tr("There is no database opened. Please open or create a new database file."));
return;
}
EditTableDialog dialog(&db, "", this);
if(dialog.exec())
{
populateStructure();
resetBrowser();
}
}
void MainWindow::createIndex()
{
if (!db.isOpen()){
QMessageBox::information( this, QApplication::applicationName(), tr("There is no database opened. Please open or create a new database file."));
return;
}
CreateIndexDialog dialog(&db, this);
if(dialog.exec())
{
populateStructure();
resetBrowser();
}
}
void MainWindow::compact()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
if (!db.compact()){
QString error = tr("Error: could not compact the database file. Message from database engine: %1").arg(db.lastErrorMessage);
QApplication::restoreOverrideCursor( );
QMessageBox::warning( this, QApplication::applicationName(), error );
} else {
QApplication::restoreOverrideCursor( );
QMessageBox::information(this, QApplication::applicationName(), tr("Database successfully compacted."));
}
db.open(db.curDBFilename);
populateStructure();
resetBrowser();
}
void MainWindow::deleteObject()
{
// Get name of table to delete
QString table = ui->dbTreeWidget->currentItem()->text(0);
QString type = ui->dbTreeWidget->currentItem()->text(1);
// Ask user if he really wants to delete that table
if(QMessageBox::warning(this, QApplication::applicationName(), tr("Are you sure you want to delete the %1 '%2'?\nAll data associated with the %1 will be lost.").arg(type).arg(table),
QMessageBox::Yes, QMessageBox::No | QMessageBox::Default | QMessageBox::Escape) == QMessageBox::Yes)
{
// Delete the table
QString statement = QString("DROP %1 `%2`;").arg(type.toUpper()).arg(table);
if(!db.executeSQL( statement))
{
QString error = tr("Error: could not delete the %1. Message from database engine:\n%2").arg(type).arg(db.lastErrorMessage);
QMessageBox::warning(this, QApplication::applicationName(), error);
} else {
populateStructure();
resetBrowser();
}
}
}
void MainWindow::editTable()
{
if (!db.isOpen()){
QMessageBox::information( this, QApplication::applicationName(), tr("There is no database opened."));
return;
}
if(!ui->dbTreeWidget->selectionModel()->hasSelection()){
return;
}
QString tableToEdit = ui->dbTreeWidget->currentItem()->text(0);
EditTableDialog dialog(&db, tableToEdit, this);
if(dialog.exec())
{
populateStructure();
resetBrowser();
}
}
void MainWindow::helpWhatsThis()
{
QWhatsThis::enterWhatsThisMode ();
}
void MainWindow::helpAbout()
{
AboutDialog dialog(this);
dialog.exec();
}
void MainWindow::updateRecordText(int row, int col, const QByteArray& newtext)
{
m_browseTableModel->setData(m_browseTableModel->index(row, col), newtext);
}
void MainWindow::editWinAway()
{
editWin->hide();
activateWindow();
ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(editWin->getCurrentRow(), editWin->getCurrentCol()));
}
void MainWindow::editText(const QModelIndex& index)
{
editWin->loadText(index.data().toByteArray(), index.row(), index.column());
editWin->show();
}
void MainWindow::doubleClickTable(const QModelIndex& index)
{
if(!index.isValid())
{
qDebug("no cell selected");
return;
}
// Don't allow editing of other objects than tables
if(db.getObjectByName(ui->comboBrowseTable->currentText()).gettype() != "table")
return;
editText(index);
}
/*
* I'm still not happy how the results are represented to the user
* right now you only see the result of the last executed statement.
* A better experiance would be tabs on the bottom with query results
* for all the executed statements.
* Or at least a some way the use could see results/status message
* per executed statement.
*/
void MainWindow::executeQuery()
{
// if a part of the query is selected, we will only execute this part
QString query = ui->sqlTextEdit->textCursor().selectedText();
if(query.isEmpty())
query = ui->sqlTextEdit->toPlainText().trimmed();
if (query.isEmpty())
return;
//log the query
db.logSQL(query, kLogMsg_User);
sqlite3_stmt *vm;
QByteArray utf8Query = query.toUtf8();
const char *tail = utf8Query.data();
int sql3status = 0;
QString statusMessage;
bool modified = false;
bool wasdirty = db.getDirty();
// there is no choice, we have to start a transaction before
// we create the prepared statement, otherwise every executed
// statement will get committed after the prepared statement
// gets finalized, see http://www.sqlite.org/lang_transaction.html
db.setRestorePoint();
//Accept multi-line queries, by looping until the tail is empty
do
{
queryResultListModel->removeRows(0, queryResultListModel->rowCount());
queryResultListModel->removeColumns(0, queryResultListModel->columnCount());
queryResultListModel->setHorizontalHeaderLabels(QStringList());
queryResultListModel->setVerticalHeaderLabels(QStringList());
const char* qbegin = tail;
sql3status = sqlite3_prepare_v2(db._db,tail,utf8Query.length(),
&vm, &tail);
QString queryPart = QString::fromUtf8(qbegin, tail - qbegin);
if (sql3status == SQLITE_OK){
int rownum = 0;
bool mustCreateColumns = true;
sql3status = sqlite3_step(vm);
switch(sql3status)
{
case SQLITE_ROW:
{
do
{
int ncol = sqlite3_data_count(vm);
//setup num of cols here for display grid
if (mustCreateColumns)
{
QStringList headerList;
for (int e=0; e<ncol; ++e)
headerList = headerList << QString::fromUtf8((const char *)sqlite3_column_name(vm, e));
queryResultListModel->setHorizontalHeaderLabels(headerList);
mustCreateColumns = false;
}
for (int e=0; e<ncol; ++e){
QString rv = QString::fromUtf8((const char *) sqlite3_column_text(vm, e));
//show it here
QString firstline = rv.section( '\n', 0,0 );
queryResultListModel->setItem(rownum, e, new QStandardItem(QString(firstline)));
}
queryResultListModel->setVerticalHeaderItem(rownum, new QStandardItem(QString::number(rownum + 1)));
rownum++;
} while ( sqlite3_step(vm) == SQLITE_ROW );
sql3status = SQLITE_OK;
}
case SQLITE_DONE:
statusMessage = tr("%1 Rows returned from: %2").arg(rownum).arg(queryPart);
case SQLITE_OK:
{
if( !queryPart.trimmed().startsWith("SELECT", Qt::CaseInsensitive) )
{
modified = true;
statusMessage = tr("Query executed successfully: %1").arg(queryPart);
}
}
break;
default:
statusMessage = QString::fromUtf8((const char*)sqlite3_errmsg(db._db)) +
": " + queryPart;
break;
}
sqlite3_finalize(vm);
}
else
{
statusMessage = QString::fromUtf8((const char*)sqlite3_errmsg(db._db)) +
": " + queryPart;
}
ui->queryErrorLineEdit->setText(statusMessage);
ui->queryResultTableView->resizeColumnsToContents();
} while( tail && *tail != 0 && (sql3status == SQLITE_OK || sql3status == SQLITE_DONE));
if(!modified && !wasdirty)
db.revert(); // better rollback, if the logic is not enough we can tune it.
}
void MainWindow::mainTabSelected(int tabindex)
{
if(tabindex == 0)
{
populateStructure();
} else if(tabindex == 1) {
populateStructure();
resetBrowser();
} else if(tabindex == 2) {
loadPragmas();
}
}
void MainWindow::importTableFromCSV()
{
QString wFile = QFileDialog::getOpenFileName(
this,
tr("Choose a text file"),
PreferencesDialog::getSettingsValue("db", "defaultlocation").toString(),
tr("Text files(*.csv *.txt);;All files(*)"));
if (QFile::exists(wFile) )
{
ImportCsvDialog dialog(wFile, &db, this);
if(dialog.exec())
{
populateStructure();
resetBrowser();
QMessageBox::information(this, QApplication::applicationName(), tr("Import completed"));
}
}
}
void MainWindow::exportTableToCSV()
{
ExportCsvDialog dialog(&db, this);
dialog.exec();
}
void MainWindow::dbState( bool dirty )
{
ui->fileSaveAction->setEnabled(dirty);
ui->fileRevertAction->setEnabled(dirty);
}
void MainWindow::fileSave()
{
if(db.isOpen())
db.save();
}
void MainWindow::fileRevert()
{
if (db.isOpen()){
QString msg = tr("Are you sure you want to undo all changes made to the database file '%1' since the last save?").arg(db.curDBFilename);
if(QMessageBox::question(this, QApplication::applicationName(), msg, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape) == QMessageBox::Yes)
{
db.revert();
populateStructure();
resetBrowser();
}
}
}
void MainWindow::exportDatabaseToSQL()
{
QString fileName = QFileDialog::getSaveFileName(
this,
tr("Choose a filename to export"),
PreferencesDialog::getSettingsValue("db", "defaultlocation").toString(),
tr("Text files(*.sql *.txt)"));
if(fileName.size())
{
if(!db.dump(fileName))
QMessageBox::warning(this, QApplication::applicationName(), tr("Export cancelled or failed."));
else
QMessageBox::information(this, QApplication::applicationName(), tr("Export completed."));
}
}
void MainWindow::importDatabaseFromSQL()
{
QString fileName = QFileDialog::getOpenFileName(
this,
tr("Choose a file to import"),
PreferencesDialog::getSettingsValue("db", "defaultlocation").toString(),
tr("Text files(*.sql *.txt);;All files(*)"));
if (fileName.size() > 0)
{
QString msg = tr("Do you want to create a new database file to hold the imported data?\nIf you answer NO we will attempt to import data in the .sql file to the current database.");
if (QMessageBox::question( this, QApplication::applicationName() ,msg, QMessageBox::Yes, QMessageBox::No)==QMessageBox::Yes)
{
QString newDBfile = QFileDialog::getSaveFileName(
this,
tr("Choose a filename to save under"),
PreferencesDialog::getSettingsValue("db", "defaultlocation").toString());
if (QFile::exists(newDBfile) )
{
QString err = tr("File %1 already exists. Please choose a different name.").arg(newDBfile);
QMessageBox::information( this, QApplication::applicationName() ,err);
return;
}
if(!fileName.isNull())
db.create(newDBfile);
}
// Open, read, execute and close file
QApplication::setOverrideCursor(Qt::WaitCursor);
QFile f(fileName);
f.open(QIODevice::ReadOnly);
if(!db.executeMultiSQL(f.readAll()))
QMessageBox::warning(this, QApplication::applicationName(), tr("Error importing data: %1").arg(db.lastErrorMessage));
else
QMessageBox::information(this, QApplication::applicationName(), tr("Import completed."));
f.close();
QApplication::restoreOverrideCursor();
// Resfresh window
populateStructure();
resetBrowser();
}
}
void MainWindow::openPreferences()
{
PreferencesDialog dialog(this);
if(dialog.exec())
{
createSyntaxHighlighters();
populateStructure();
resetBrowser();
}
}
void MainWindow::createSyntaxHighlighters()
{
delete sqliteHighlighterLogApp;
delete sqliteHighlighterLogUser;
delete sqliteHighlighterTabSql;
sqliteHighlighterLogApp = new SQLiteSyntaxHighlighter(ui->editLogApplication->document());
sqliteHighlighterLogUser = new SQLiteSyntaxHighlighter(ui->editLogUser->document());
sqliteHighlighterTabSql = new SQLiteSyntaxHighlighter(ui->sqlTextEdit->document());
}
//******************************************************************
//** Tree Events
//******************************************************************
//** Db Tree Context Menu
void MainWindow::createTreeContextMenu(const QPoint &qPoint)
{
if(!ui->dbTreeWidget->selectionModel()->hasSelection())
return;
QTreeWidgetItem *cItem = ui->dbTreeWidget->currentItem();
if(cItem->text(1) == "table" || cItem->text(1) == "view" || cItem->text(1) == "trigger" || cItem->text(1) == "index")
popupTableMenu->exec(ui->dbTreeWidget->mapToGlobal(qPoint));
}
//** Tree selection changed
void MainWindow::changeTreeSelection()
{
// Just assume first that something's selected that can not be edited at all
ui->editDeleteObjectAction->setEnabled(false);
ui->editModifyTableAction->setEnabled(false);
if(ui->dbTreeWidget->currentItem() == 0)
return;
// Change the text of the actions
ui->editDeleteObjectAction->setIcon(QIcon(QString(":icons/%1_delete").arg(ui->dbTreeWidget->currentItem()->text(1))));
if(ui->dbTreeWidget->currentItem()->text(1) == "view")
ui->editDeleteObjectAction->setText(tr("Delete View"));
else if(ui->dbTreeWidget->currentItem()->text(1) == "trigger")
ui->editDeleteObjectAction->setText(tr("Delete Trigger"));
else if(ui->dbTreeWidget->currentItem()->text(1) == "index")
ui->editDeleteObjectAction->setText(tr("Delete Index"));
else
ui->editDeleteObjectAction->setText(tr("Delete Table"));
// Activate actions
if(ui->dbTreeWidget->currentItem()->text(1) == "table")
{
ui->editDeleteObjectAction->setEnabled(true);
ui->editModifyTableAction->setEnabled(true);
} else if(ui->dbTreeWidget->currentItem()->text(1) == "view" || ui->dbTreeWidget->currentItem()->text(1) == "trigger" || ui->dbTreeWidget->currentItem()->text(1) == "index") {
ui->editDeleteObjectAction->setEnabled(true);
}
}
void MainWindow::openRecentFile()
{
QAction *action = qobject_cast<QAction *>(sender());
if (action)
fileOpen(action->data().toString());
}
void MainWindow::updateRecentFileActions()
{
QStringList files = PreferencesDialog::getSettingsValue("General", "recentFileList").toStringList();
int numRecentFiles = qMin(files.size(), (int)MaxRecentFiles);
for (int i = 0; i < numRecentFiles; ++i) {
QString text = tr("&%1 %2").arg(i + 1).arg(files[i]);
recentFileActs[i]->setText(text);
recentFileActs[i]->setData(files[i]);
recentFileActs[i]->setVisible(true);
}
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
recentFileActs[j]->setVisible(false);
recentSeparatorAct->setVisible(numRecentFiles > 0);
}
void MainWindow::setCurrentFile(const QString &fileName)
{
setWindowFilePath(fileName);
setWindowTitle( QApplication::applicationName() +" - "+fileName);
activateFields(true);
QStringList files = PreferencesDialog::getSettingsValue("General", "recentFileList").toStringList();
files.removeAll(fileName);
files.prepend(fileName);
while (files.size() > MaxRecentFiles)
files.removeLast();
PreferencesDialog::setSettingsValue("General", "recentFileList", files);
foreach (QWidget *widget, QApplication::topLevelWidgets()) {
MainWindow *mainWin = qobject_cast<MainWindow *>(widget);
if (mainWin)
mainWin->updateRecentFileActions();
}
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if( event->mimeData()->hasFormat("text/uri-list") )
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
if( urls.isEmpty() )
return;
QString fileName = urls.first().toLocalFile();
if(!fileName.isEmpty())
fileOpen(fileName);
}
void MainWindow::activateFields(bool enable)
{
ui->fileCloseAction->setEnabled(enable);
ui->fileCompactAction->setEnabled(enable);
ui->fileExportCSVAction->setEnabled(enable);
ui->fileExportSQLAction->setEnabled(enable);
ui->fileImportCSVAction->setEnabled(enable);
ui->editCreateTableAction->setEnabled(enable);
ui->editCreateIndexAction->setEnabled(enable);
ui->buttonNext->setEnabled(enable);
ui->buttonPrevious->setEnabled(enable);
ui->executeQueryButton->setEnabled(enable);
ui->scrollAreaWidgetContents->setEnabled(enable);
ui->buttonBoxPragmas->setEnabled(enable);
ui->buttonGoto->setEnabled(enable);
ui->editGoto->setEnabled(enable);
ui->buttonRefresh->setEnabled(enable);
ui->buttonDeleteRecord->setEnabled(enable);
ui->buttonNewRecord->setEnabled(enable);
}
void MainWindow::browseTableHeaderClicked(int logicalindex)
{
// Abort if there is more than one column selected because this tells us that the user pretty sure wants to do a range selection instead of sorting data
if(ui->dataTable->selectionModel()->selectedColumns().count() > 1)
return;
// instead of the column name we just use the column index, +2 because 'rowid, *' is the projection
curBrowseOrderByIndex = logicalindex;
curBrowseOrderByMode = curBrowseOrderByMode == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
m_browseTableModel->sort(curBrowseOrderByIndex, curBrowseOrderByMode);
// select the first item in the column so the header is bold
// we might try to select the last selected item
ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(0, logicalindex));
}
void MainWindow::resizeEvent(QResizeEvent*)
{
setRecordsetLabel();
}
void MainWindow::loadPragmas()
{
pragmaValues.autovacuum = db.getPragma("auto_vacuum").toInt();
pragmaValues.automatic_index = db.getPragma("automatic_index").toInt();
pragmaValues.checkpoint_fullsync = db.getPragma("checkpoint_fullfsync").toInt();
pragmaValues.foreign_keys = db.getPragma("foreign_keys").toInt();
pragmaValues.fullfsync = db.getPragma("fullfsync").toInt();
pragmaValues.ignore_check_constraints = db.getPragma("ignore_check_constraints").toInt();
pragmaValues.journal_mode = db.getPragma("journal_mode").toUpper();
pragmaValues.journal_size_limit = db.getPragma("journal_size_limit").toInt();
pragmaValues.locking_mode = db.getPragma("locking_mode").toUpper();
pragmaValues.max_page_count = db.getPragma("max_page_count").toInt();
pragmaValues.page_size = db.getPragma("page_size").toInt();
pragmaValues.recursive_triggers = db.getPragma("recursive_triggers").toInt();
pragmaValues.secure_delete = db.getPragma("secure_delete").toInt();
pragmaValues.synchronous = db.getPragma("synchronous").toInt();
pragmaValues.temp_store = db.getPragma("temp_store").toInt();
pragmaValues.user_version = db.getPragma("user_version").toInt();
pragmaValues.wal_autocheckpoint = db.getPragma("wal_autocheckpoint").toInt();
updatePragmaUi();
}
void MainWindow::updatePragmaUi()
{
ui->comboboxPragmaAutoVacuum->setCurrentIndex(pragmaValues.autovacuum);
ui->checkboxPragmaAutomaticIndex->setChecked(pragmaValues.automatic_index);
ui->checkboxPragmaCheckpointFullFsync->setChecked(pragmaValues.checkpoint_fullsync);
ui->checkboxPragmaForeignKeys->setChecked(pragmaValues.foreign_keys);
ui->checkboxPragmaFullFsync->setChecked(pragmaValues.fullfsync);
ui->checkboxPragmaIgnoreCheckConstraints->setChecked(pragmaValues.ignore_check_constraints);
ui->comboboxPragmaJournalMode->setCurrentIndex(ui->comboboxPragmaJournalMode->findText(pragmaValues.journal_mode, Qt::MatchFixedString));
ui->spinPragmaJournalSizeLimit->setValue(pragmaValues.journal_size_limit);
ui->comboboxPragmaLockingMode->setCurrentIndex(ui->comboboxPragmaLockingMode->findText(pragmaValues.locking_mode, Qt::MatchFixedString));
ui->spinPragmaMaxPageCount->setValue(pragmaValues.max_page_count);
ui->spinPragmaPageSize->setValue(pragmaValues.page_size);
ui->checkboxPragmaRecursiveTriggers->setChecked(pragmaValues.recursive_triggers);
ui->checkboxPragmaSecureDelete->setChecked(pragmaValues.secure_delete);
ui->comboboxPragmaSynchronous->setCurrentIndex(pragmaValues.synchronous);
ui->comboboxPragmaTempStore->setCurrentIndex(pragmaValues.temp_store);
ui->spinPragmaUserVersion->setValue(pragmaValues.user_version);
ui->spinPragmaWalAutoCheckpoint->setValue(pragmaValues.wal_autocheckpoint);
}
void MainWindow::savePragmas()
{
if( db.getDirty() )
{
QString msg = tr("Setting PRAGMA values will commit your current transaction.\nAre you sure?");
if(QMessageBox::question(this, QApplication::applicationName(), msg, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
{
return; // abort
}
}
db.setPragma("auto_vacuum", ui->comboboxPragmaAutoVacuum->currentIndex(), pragmaValues.autovacuum);
db.setPragma("automatic_index", ui->checkboxPragmaAutomaticIndex->isChecked(), pragmaValues.automatic_index);
db.setPragma("checkpoint_fullfsync", ui->checkboxPragmaCheckpointFullFsync->isChecked(), pragmaValues.checkpoint_fullsync);
db.setPragma("foreign_keys", ui->checkboxPragmaForeignKeys->isChecked(), pragmaValues.foreign_keys);
db.setPragma("fullfsync", ui->checkboxPragmaFullFsync->isChecked(), pragmaValues.fullfsync);
db.setPragma("ignore_check_constraints", ui->checkboxPragmaIgnoreCheckConstraints->isChecked(), pragmaValues.ignore_check_constraints);
db.setPragma("journal_mode", ui->comboboxPragmaJournalMode->currentText().toUpper(), pragmaValues.journal_mode);
db.setPragma("journal_size_limit", ui->spinPragmaJournalSizeLimit->value(), pragmaValues.journal_size_limit);
db.setPragma("locking_mode", ui->comboboxPragmaLockingMode->currentText().toUpper(), pragmaValues.locking_mode);
db.setPragma("max_page_count", ui->spinPragmaMaxPageCount->value(), pragmaValues.max_page_count);
db.setPragma("page_size", ui->spinPragmaPageSize->value(), pragmaValues.page_size);
db.setPragma("recursive_triggers", ui->checkboxPragmaRecursiveTriggers->isChecked(), pragmaValues.recursive_triggers);
db.setPragma("secure_delete", ui->checkboxPragmaSecureDelete->isChecked(), pragmaValues.secure_delete);
db.setPragma("synchronous", ui->comboboxPragmaSynchronous->currentIndex(), pragmaValues.synchronous);
db.setPragma("temp_store", ui->comboboxPragmaTempStore->currentIndex(), pragmaValues.temp_store);
db.setPragma("user_version", ui->spinPragmaUserVersion->value(), pragmaValues.user_version);
db.setPragma("wal_autocheckpoint", ui->spinPragmaWalAutoCheckpoint->value(), pragmaValues.wal_autocheckpoint);
updatePragmaUi();
}
void MainWindow::logSql(const QString& sql, int msgtype)
{
if(msgtype == kLogMsg_User)
{
ui->editLogUser->append(sql);
ui->editLogUser->verticalScrollBar()->setValue(ui->editLogUser->verticalScrollBar()->maximum());
} else {
ui->editLogApplication->append(sql);
ui->editLogApplication->verticalScrollBar()->setValue(ui->editLogApplication->verticalScrollBar()->maximum());
}
}