Files
sqlitebrowser/src/MainWindow.cpp
Martin Kleusberg f25892d6d2 SqliteTableModel: Do a few fixes and add sorting again
Fix empty rows being shown in the main window when changing to a table
with less rows than the old one.

Add sorting in the browse tab again.

Do some minor clean ups.
2013-04-06 22:09:36 +02:00

1249 lines
45 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"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
ui(new Ui::MainWindow),
browseTableModel(new QStandardItemModel(this)),
m_browseTableModel(new SqliteTableModel(this, &db)),
m_browseTableSortProxy(new QSortFilterProxyModel(this)),
sqliteHighlighterTabSql(0),
sqliteHighlighterLogUser(0),
sqliteHighlighterLogApp(0),
editWin(new EditDialog(this)),
findWin(0)
{
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);
// Create the validator for the goto line edit
gotoValidator = new QIntValidator(0, 0, this);
ui->editGoto->setValidator(gotoValidator);
// Create the SQL sytax highlighters
createSyntaxHighlighters();
// Set up DB models
m_browseTableSortProxy->setSourceModel(m_browseTableModel);
ui->dataTable->setModel(m_browseTableSortProxy);
queryResultListModel = new QStandardItemModel(this);
ui->queryResultTableView->setModel(queryResultListModel);
// 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(ui->dataTable->horizontalHeader(), 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, bool keepColumnWidths)
{
QApplication::setOverrideCursor( Qt::WaitCursor );
if(!tablename.isEmpty())
m_browseTableModel->setQuery(QString("SELECT * FROM `%1`").arg(tablename));
// bool mustreset = false;
// if (tablename.compare(db.curBrowseTableName)!=0)
// {
// mustreset = true;
// curBrowseOrderByIndex = 1;
// curBrowseOrderByMode = ORDERMODE_ASC;
// }
// QString orderby = QString::number(curBrowseOrderByIndex) + " " + (curBrowseOrderByMode == ORDERMODE_ASC ? "ASC" : "DESC");
// if(!db.browseTable(tablename, orderby))
// {
// browseTableModel->setRowCount(0);
// browseTableModel->setColumnCount(0);
// QApplication::restoreOverrideCursor();
// if(findWin)
// findWin->resetFields(db.getTableFields(""));
// return;
// }
// // 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);
// if (mustreset){
// updateTableView(0, keepColumnWidths);
// if (findWin) findWin->resetFields(db.getTableFields(db.curBrowseTableName));
// } else {
// updateTableView(-1, keepColumnWidths);
// }
// //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 = 1;
curBrowseOrderByMode = Qt::AscendingOrder;
m_browseTableSortProxy->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()
{
if (db.addRecord(db.curBrowseTableName))
{
populateTable(db.curBrowseTableName);
//added record will be the last one in view
updateTableView(db.getRecordCount()-1);
}
else
{
QMessageBox::information( this, QApplication::applicationName(),
tr("Error adding record:\n") + db.lastErrorMessage);
}
}
void MainWindow::deleteRecord()
{
if(ui->dataTable->currentIndex().row() != -1)
{
int lastselected = ui->dataTable->currentIndex().row();
db.deleteRecord(lastselected);
populateTable(db.curBrowseTableName);
int nextselected = lastselected ;
if (nextselected > db.getRecordCount()){
nextselected = db.getRecordCount();
}
if (nextselected>0){
selectTableLine(nextselected);
}
} else {
QMessageBox::information( this, QApplication::applicationName(), tr("Please select a record first"));
}
}
#define WRAP_SIZE 80
QString wrapText(const QString& text)
{
QString wrap;
int textSize = text.size();
int cur = 0;
while( wrap.size() < textSize)
{
wrap += text.mid(cur, WRAP_SIZE);
cur += WRAP_SIZE;
if( textSize - cur > WRAP_SIZE)
wrap += '\n';
}
return wrap;
}
void MainWindow::updateTableView(int lineToSelect, bool keepColumnWidths)
{
QApplication::setOverrideCursor( Qt::WaitCursor );
// browseTableModel->setRowCount(db.getRecordCount());
// browseTableModel->setColumnCount(db.browseFields.count());
// browseTableModel->setHorizontalHeaderLabels(db.browseFields);
// rowList tab = db.browseRecs;
// int maxRecs = db.getRecordCount();
// gotoValidator->setRange(0, maxRecs);
// if ( maxRecs > 0 ) {
// int rowNum = 0;
// int colNum = 0;
// QString rowLabel;
// for (int i = 0; i < tab.size(); ++i)
// {
// rowLabel.setNum(rowNum+1);
// browseTableModel->setVerticalHeaderItem(rowNum, new QStandardItem(rowLabel));
// colNum = 0;
// QList<QByteArray> rt = tab[i];
// for (int e = 1; e < rt.size(); ++e)
// {
// QString content = rt[e];
// QStandardItem* item = new QStandardItem(content);
// item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
// item->setToolTip(wrapText(content));
// browseTableModel->setItem( rowNum, colNum, item);
// colNum++;
// }
// rowNum++;
// if (rowNum==maxRecs) break;
// }
// }
// if(!keepColumnWidths) {
// for(int i=0;i<browseTableModel->columnCount();++i)
// {
// ui->dataTable->resizeColumnToContents(i);
// if( ui->dataTable->columnWidth(i) > 400 )
// ui->dataTable->setColumnWidth(i, 400);
// }
// }
// if (lineToSelect!=-1){
// selectTableLine(lineToSelect);
// }
// setRecordsetLabel();
QApplication::restoreOverrideCursor();
}
void MainWindow::selectTableLine(int lineToSelect)
{
ui->dataTable->clearSelection();
ui->dataTable->selectRow(lineToSelect);
ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(lineToSelect, 0));
ui->dataTable->scrollTo(ui->dataTable->currentIndex().sibling(lineToSelect, 0));
}
void MainWindow::navigatePrevious()
{
int curRow = ui->dataTable->currentIndex().row();
curRow -= 100;
if(curRow < 0) curRow = 0;
updateTableView(curRow);
}
void MainWindow::navigateNext()
{
int curRow = ui->dataTable->currentIndex().row();
curRow += 100;
if(curRow >= browseTableModel->rowCount())
curRow = browseTableModel->rowCount()-1;
updateTableView(curRow);
}
void MainWindow::navigateGoto()
{
QString typed = ui->editGoto->text();
bool ok;
int dec = typed.toInt( &ok);
if (dec==0) dec=1;
if (dec>db.getRecordCount()) dec = db.getRecordCount();
updateTableView(dec-1);
ui->editGoto->setText(QString::number(dec,10));
}
void MainWindow::setRecordsetLabel()
{
int from = ui->dataTable->verticalHeader()->visualIndexAt(0) + 1;
int to = ui->dataTable->verticalHeader()->visualIndexAt(ui->dataTable->height()) - 1;
int total = browseTableModel->rowCount();
if(to == -2)
to = total;
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(updateTableView(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(), true);
}
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 = "SELECT rowid, ";
statement.append(wfield);
statement.append(" FROM ");
statement.append(db.curBrowseTableName);
statement.append(" WHERE ");
statement.append(wfield);
statement.append(" ");
statement.append(finaloperator);
statement.append(" ");
//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)
{
if (!db.updateRecord(row, col, newtext)){
QMessageBox::information( this, QApplication::applicationName(),
tr("Data could not be updated:\n") + db.lastErrorMessage);
}
rowList tab = db.browseRecs;
QList<QByteArray>& rt = tab[row];
QByteArray& cv = rt[col+1];//must account for rowid
QStandardItem* item = new QStandardItem(QString(cv));
item->setToolTip( wrapText(cv) );
browseTableModel->setItem(row, col, item);
}
void MainWindow::editWinAway()
{
editWin->hide();
activateWindow();
ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(editWin->getCurrentRow(), editWin->getCurrentCol()));
}
void MainWindow::editText(int row, int col)
{
rowList tab = db.browseRecs;
QList<QByteArray> rt = tab[row];
QByteArray cv = rt[col+1];//must account for rowid
editWin->loadText(cv , row, col);
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.row(), index.column());
}
/*
* 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_browseTableSortProxy->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());
}
}