diff --git a/README.rst b/README.rst index 13f1211f..8accffd4 100644 --- a/README.rst +++ b/README.rst @@ -29,6 +29,9 @@ What's been done since then - Cleaned up the code, reducing the SLOC quite a bit - Added basic support for triggers and views - Added pragma editing +- Added BLOB support +- Added a new filter row for searching +- Improved performance when opening large tables - Fixed a ton of bugs - Probably more @@ -43,8 +46,6 @@ What's still to do - Further improvement of the UI, adding more features and making it easier to use - Inline editing of records instead of having to use a special dialog for it -- Avoid loading all records of a table to avoid problems when opening a very - big table - Feel free to add more issues at https://github.com/rp-/sqlitebrowser/issues diff --git a/src/ExportCsvDialog.cpp b/src/ExportCsvDialog.cpp index adeb053a..c97beadf 100644 --- a/src/ExportCsvDialog.cpp +++ b/src/ExportCsvDialog.cpp @@ -6,6 +6,7 @@ #include "ui_ExportCsvDialog.h" #include "sqlitedb.h" #include "PreferencesDialog.h" +#include "sqlitetablemodel.h" ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, QWidget* parent) : QDialog(parent), @@ -39,7 +40,10 @@ void ExportCsvDialog::accept() if(fileName.size() > 0) { // Get data from selected table - pdb->browseTable(ui->comboTable->currentText()); + SqliteTableModel tableModel(this, pdb); + tableModel.setTable(ui->comboTable->currentText()); + while(tableModel.canFetchMore()) + tableModel.fetchMore(); // Prepare the quote and separating characters QString quoteChar = ui->comboQuoteCharacter->currentText(); @@ -58,11 +62,10 @@ void ExportCsvDialog::accept() // Put field names in first row if user wants to have them if(ui->checkHeader->isChecked()) { - QStringList fields = pdb->browseFields; - for(int i=0;ibrowseRecs; - for(int i=0;i row = data[i]; - for(int j=1;j #include #include "ExtendedTableWidget.h" +#include "sqlitetablemodel.h" +#include +#include ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : QTableView(parent) { + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(vscrollbarChanged(int))); } void ExtendedTableWidget::copy() @@ -53,3 +57,34 @@ void ExtendedTableWidget::keyPressEvent(QKeyEvent* event) else QTableView::keyPressEvent(event); } + +void ExtendedTableWidget::updateGeometries() +{ + // Call the parent implementation first - it does most of the actual logic + QTableView::updateGeometries(); + + // Check if a model has already been set yet + if(model()) + { + // If so and if it is a SqliteTableModel and if the parent implementation of this method decided that a scrollbar is needed, update its maximum value + SqliteTableModel* m = qobject_cast(model()); + if(m && verticalScrollBar()->maximum()) + verticalScrollBar()->setMaximum(m->totalRowCount()); + } +} + +void ExtendedTableWidget::vscrollbarChanged(int value) +{ + // Cancel if there is no model set yet - this shouldn't happen (because without a model there should be no scrollbar) but just to be sure... + if(!model()) + return; + + // How many rows are visible right now? + int row_top = rowAt(0) == -1 ? 0 : rowAt(0); + int row_bottom = rowAt(height()) == -1 ? model()->rowCount() : rowAt(height()); + int num_visible_rows = row_bottom - row_top; + + // Fetch more data from the DB if necessary + if((value + num_visible_rows) >= model()->rowCount() && model()->canFetchMore(QModelIndex())) + model()->fetchMore(QModelIndex()); +} diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index 7e8adbbf..8cec83d8 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -13,8 +13,12 @@ public: private: void copy(); +private slots: + void vscrollbarChanged(int value); + protected: virtual void keyPressEvent(QKeyEvent* event); + virtual void updateGeometries(); }; #endif diff --git a/src/FilterTableHeader.cpp b/src/FilterTableHeader.cpp new file mode 100644 index 00000000..926396dc --- /dev/null +++ b/src/FilterTableHeader.cpp @@ -0,0 +1,78 @@ +#include "FilterTableHeader.h" +#include +#include +#include + +FilterTableHeader::FilterTableHeader(QTableView* parent) : + QHeaderView(Qt::Horizontal, parent) +{ + // Activate the click signals to allow sorting + setClickable(true); + + // Do some connects: Basically just resize and reposition the input widgets whenever anything changes + connect(this, SIGNAL(sectionResized(int,int,int)), this, SLOT(adjustPositions())); + connect(parent->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjustPositions())); + connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjustPositions())); +} + +void FilterTableHeader::generateFilters(int number) +{ + // Delete all the current filter widgets + for(int i=0;isetPlaceholderText(tr("Filter")); + l->setProperty("column", i); // Store the column number for later use + l->setVisible(i>0); // This hides the first input widget which belongs to the hidden rowid column + connect(l, SIGNAL(textChanged(QString)), this, SLOT(inputChanged(QString))); + filterWidgets.push_back(l); + } + + // Position them correctly + adjustPositions(); +} + +QSize FilterTableHeader::sizeHint() const +{ + // For the size hint just take the value of the standard implementation and add the height of a input widget to it if necessary + QSize s = QHeaderView::sizeHint(); + if(filterWidgets.size()) + s.setHeight(s.height() + filterWidgets.at(0)->sizeHint().height() + 5); // The 5 adds just adds some extra space + return s; +} + +void FilterTableHeader::updateGeometries() +{ + // If there are any input widgets add a viewport margin to the header to generate some empty space for them which is not affected by scrolling + if(filterWidgets.size()) + setViewportMargins(0, 0, 0, filterWidgets.at(0)->sizeHint().height()); + else + setViewportMargins(0, 0, 0, 0); + + // Now just call the parent implementation and reposition the input widgets + QHeaderView::updateGeometries(); + adjustPositions(); +} + +void FilterTableHeader::adjustPositions() +{ + // Loop through all widgets + for(int i=0;imove(sectionPosition(i) - offset(), filterWidgets.at(i)->sizeHint().height() + 2); // The two adds some extra space between the header label and the input widget + w->resize(sectionSize(i), filterWidgets.at(i)->sizeHint().height()); + } +} + +void FilterTableHeader::inputChanged(const QString& new_value) +{ + // Just get the column number and the new value and send them to anybody interested in filter changes + emit filterChanged(sender()->property("column").toInt(), new_value); +} diff --git a/src/FilterTableHeader.h b/src/FilterTableHeader.h new file mode 100644 index 00000000..1a7aa050 --- /dev/null +++ b/src/FilterTableHeader.h @@ -0,0 +1,35 @@ +#ifndef __FILTERTABLEHEADER_H__ +#define __FILTERTABLEHEADER_H__ + +#include +#include + +class QLineEdit; +class QTableView; + +class FilterTableHeader : public QHeaderView +{ + Q_OBJECT + +public: + explicit FilterTableHeader(QTableView* parent = 0); + virtual QSize sizeHint() const; + +public slots: + void generateFilters(int number); + void adjustPositions(); + +signals: + void filterChanged(int column, QString value); + +protected: + virtual void updateGeometries(); + +private slots: + void inputChanged(const QString& new_value); + +private: + QList filterWidgets; +}; + +#endif diff --git a/src/FindDialog.cpp b/src/FindDialog.cpp deleted file mode 100644 index ade4c713..00000000 --- a/src/FindDialog.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "FindDialog.h" -#include "ui_FindDialog.h" - -FindDialog::FindDialog(QWidget* parent) - : QDialog(parent), - ui(new Ui::FindDialog) -{ - ui->setupUi(this); -} - -FindDialog::~FindDialog() -{ - delete ui; -} - -void FindDialog::showResults(const resultMap& rmap) -{ - ui->tableResults->setSortingEnabled(false); - ui->tableResults->clearContents(); - resultMap::const_iterator it; - int rowNum; - ui->tableResults->setRowCount(rmap.size()); - for(it=rmap.begin(),rowNum=0;it!=rmap.end();++it,rowNum++) - { - QString firstline = it.value().section('\n', 0, 0); - ui->tableResults->setItem(rowNum, 0, new QTableWidgetItem(QString::number(it.key() + 1))); - ui->tableResults->setItem(rowNum, 1, new QTableWidgetItem(firstline)); - } - QString results = tr("Found: %1").arg(ui->tableResults->rowCount()); - ui->labelNumberResults->setText(results); - ui->tableResults->setSortingEnabled(true); -} - -void FindDialog::find() -{ - emit lookfor(ui->comboColumn->currentText(), ui->comboOperator->currentText(), ui->editSearchString->text()); -} - -void FindDialog::resetFields(const QStringList& fieldlist) -{ - ui->comboColumn->clear(); - ui->comboColumn->addItems(fieldlist); - ui->editSearchString->setText(""); - ui->comboOperator->setCurrentIndex(0); - ui->tableResults->clearContents(); - ui->labelNumberResults->setText(tr("Found: 0")); -} - -void FindDialog::recordSelected(QTableWidgetItem* witem) -{ - if(witem) - { - int recNum = ui->tableResults->item(witem->row(), 0)->text().toInt(); - emit showrecord(recNum - 1); - } -} - -void FindDialog::closeEvent(QCloseEvent*) -{ - emit goingAway(); -} diff --git a/src/FindDialog.h b/src/FindDialog.h deleted file mode 100644 index 46b0eef1..00000000 --- a/src/FindDialog.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef __FINDDIALOG_H__ -#define __FINDDIALOG_H__ - -#include -#include "sqlitedb.h" -class QTableWidgetItem; - -namespace Ui { -class FindDialog; -} - -class FindDialog : public QDialog -{ - Q_OBJECT - -public: - explicit FindDialog(QWidget* parent = 0); - ~FindDialog(); - -public slots: - virtual void showResults(const resultMap& rmap); - virtual void resetFields(const QStringList& fieldlist = QStringList()); - -private slots: - virtual void find(); - virtual void recordSelected(QTableWidgetItem* witem); - virtual void closeEvent(QCloseEvent*); - -signals: - void lookfor(const QString&, const QString&, const QString&); - void showrecord(int); - void goingAway(); - -private: - Ui::FindDialog* ui; -}; - -#endif diff --git a/src/FindDialog.ui b/src/FindDialog.ui deleted file mode 100644 index aa86b6be..00000000 --- a/src/FindDialog.ui +++ /dev/null @@ -1,193 +0,0 @@ - - - FindDialog - - - - 0 - 0 - 288 - 351 - - - - Find - - - true - - - - - - - - Field to be searched - - - Use this control to select the field to be searched in the current table - - - - - - - Search criteria: use 'contains' for partial matches - - - This control is used to select the search criteria used to look for the search term in the database. Use '=' or 'contains' to find words, and the comparison symbols to filter numeric data. - - - - = - - - - - contains - - - - - > - - - - - >= - - - - - <= - - - - - < - - - - - <> - - - - - - - - - - - - Enter values or words to search - - - This is a place to enter the word or number to be searched in the database - - - - - - - Perform the search - - - This button starts the search process - - - &Search - - - true - - - - - - - - - Results of the search will appear in this area. Click on a result to select the corresponding record in the database - - - QAbstractItemView::NoEditTriggers - - - false - - - true - - - QAbstractItemView::NoSelection - - - - Records - - - - - Data - - - - - - - - Found: 0 - - - - - - - comboColumn - comboOperator - editSearchString - buttonSearch - tableResults - - - - - buttonSearch - clicked() - FindDialog - find() - - - 233 - 45 - - - 267 - 30 - - - - - tableResults - itemClicked(QTableWidgetItem*) - FindDialog - recordSelected(QTableWidgetItem*) - - - 100 - 176 - - - 86 - 58 - - - - - - find() - recordSelected(QTableWidgetItem*) - - diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 94575d82..e0046e20 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "CreateIndexDialog.h" #include "AboutDialog.h" @@ -18,19 +19,20 @@ #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), - browseTableModel(new QStandardItemModel(this)), + 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(); @@ -55,19 +57,22 @@ void MainWindow::init() ui->dbTreeWidget->setColumnHidden(1, true); ui->dbTreeWidget->setColumnWidth(0, 300); - // Create the validator for the goto line edit - gotoValidator = new QIntValidator(0, 0, this); + // 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(browseTableModel); + 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); @@ -96,10 +101,10 @@ void MainWindow::init() ui->statusbar->addPermanentWidget(statusEncodingLabel); // Connect some more signals and slots - connect(ui->dataTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(browseTableHeaderClicked(int))); + 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))); + connect(editWin, SIGNAL(updateRecordText(int, int, QByteArray)), this, SLOT(updateRecordText(int, int, QByteArray))); // Load window settings restoreGeometry(PreferencesDialog::getSettingsValue("MainWindow", "geometry").toByteArray()); @@ -268,45 +273,48 @@ void MainWindow::populateStructure() } } -void MainWindow::populateTable( const QString & tablename, bool keepColumnWidths) +void MainWindow::populateTable( const QString & tablename) { - bool mustreset = false; - QApplication::setOverrideCursor( Qt::WaitCursor ); - if (tablename.compare(db.curBrowseTableName)!=0) + // Remove the model-view link if the table name is empty in order to remove any data from the view + if(tablename.isEmpty()) { - 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("")); + 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(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); - 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(); + // Set the recordset label + setRecordsetLabel(); + + // Reset the edit dialog if(editWin) editWin->reset(); + QApplication::restoreOverrideCursor(); } @@ -325,8 +333,9 @@ void MainWindow::resetBrowser() int pos = ui->comboBrowseTable->findText(sCurrentTable); pos = pos == -1 ? 0 : pos; ui->comboBrowseTable->setCurrentIndex(pos); - curBrowseOrderByIndex = 1; - curBrowseOrderByMode = ORDERMODE_ASC; + curBrowseOrderByIndex = 0; + curBrowseOrderByMode = Qt::AscendingOrder; + m_browseTableModel->sort(curBrowseOrderByIndex, curBrowseOrderByMode); populateTable(ui->comboBrowseTable->currentText()); } @@ -368,234 +377,107 @@ void MainWindow::closeEvent( QCloseEvent* event ) void MainWindow::addRecord() { - if (db.addRecord(db.curBrowseTableName)) + int row = m_browseTableModel->rowCount(); + if(m_browseTableModel->insertRow(row)) { - 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); + selectTableLine(row); + } else { + QMessageBox::warning( this, QApplication::applicationName(), tr("Error adding record:\n") + db.lastErrorMessage); } } - void MainWindow::deleteRecord() { - if(ui->dataTable->currentIndex().row() != -1) + if(ui->dataTable->currentIndex().isValid()) { - 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); + 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")); } } -#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 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;icolumnCount();++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) { + // Are there even that many lines? + if(lineToSelect >= m_browseTableModel->totalRowCount()) + return; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + // Make sure this line has already been fetched + while(lineToSelect >= m_browseTableModel->rowCount() && m_browseTableModel->canFetchMore()) + m_browseTableModel->fetchMore(); + + // Select it 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)); + ui->dataTable->scrollTo(ui->dataTable->currentIndex()); + QApplication::restoreOverrideCursor(); } void MainWindow::navigatePrevious() { int curRow = ui->dataTable->currentIndex().row(); curRow -= 100; - if(curRow < 0) curRow = 0; - updateTableView(curRow); + 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 >= browseTableModel->rowCount()) - curRow = browseTableModel->rowCount()-1; - updateTableView(curRow); + if(curRow >= m_browseTableModel->totalRowCount()) + curRow = m_browseTableModel->totalRowCount() - 1; + selectTableLine(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(); + // TODO: Fetch more data from DB if necessary - updateTableView(dec-1); - ui->editGoto->setText(QString::number(dec,10)); + 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 = browseTableModel->rowCount(); + 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(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(); + populateTable(ui->comboBrowseTable->currentText()); } void MainWindow::createTable() @@ -698,18 +580,7 @@ void MainWindow::helpAbout() 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& 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); + m_browseTableModel->setData(m_browseTableModel->index(row, col), newtext); } void MainWindow::editWinAway() @@ -719,13 +590,9 @@ void MainWindow::editWinAway() ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(editWin->getCurrentRow(), editWin->getCurrentCol())); } -void MainWindow::editText(int row, int col) +void MainWindow::editText(const QModelIndex& index) { - rowList tab = db.browseRecs; - QList rt = tab[row]; - QByteArray cv = rt[col+1];//must account for rowid - - editWin->loadText(cv , row, col); + editWin->loadText(index.data().toByteArray(), index.row(), index.column()); editWin->show(); } @@ -741,7 +608,7 @@ void MainWindow::doubleClickTable(const QModelIndex& index) if(db.getObjectByName(ui->comboBrowseTable->currentText()).gettype() != "table") return; - editText(index.row(), index.column()); + editText(index); } /* @@ -1138,9 +1005,9 @@ void MainWindow::browseTableHeaderClicked(int logicalindex) return; // instead of the column name we just use the column index, +2 because 'rowid, *' is the projection - curBrowseOrderByIndex = logicalindex + 2; - curBrowseOrderByMode = curBrowseOrderByMode == ORDERMODE_ASC ? ORDERMODE_DESC : ORDERMODE_ASC; - populateTable(ui->comboBrowseTable->currentText(), true); + 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 diff --git a/src/MainWindow.h b/src/MainWindow.h index 2a9c38f1..97925056 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -4,17 +4,14 @@ #include #include "sqlitedb.h" -#define ORDERMODE_ASC 0 -#define ORDERMODE_DESC 1 - class QDragEnterEvent; class EditDialog; -class FindDialog; class SQLiteSyntaxHighlighter; class QStandardItemModel; class QIntValidator; class QLabel; class QModelIndex; +class SqliteTableModel; namespace Ui { class MainWindow; @@ -52,7 +49,7 @@ private: Ui::MainWindow* ui; - QStandardItemModel *browseTableModel; + SqliteTableModel* m_browseTableModel; QStandardItemModel *queryResultListModel; QMenu *popupTableMenu; QMenu *recentFilesMenu; @@ -68,10 +65,9 @@ private: QAction *recentSeparatorAct; int curBrowseOrderByIndex; - int curBrowseOrderByMode; + Qt::SortOrder curBrowseOrderByMode; EditDialog* editWin; - FindDialog* findWin; QIntValidator* gotoValidator; DBBrowserDB db; @@ -99,22 +95,18 @@ private slots: virtual void fileOpen(); virtual void fileNew(); virtual void populateStructure(); - virtual void populateTable(const QString & tablename , bool keepColumnWidths = false); + virtual void populateTable(const QString& tablename); virtual void resetBrowser(); virtual void fileClose(); virtual void fileExit(); virtual void addRecord(); virtual void deleteRecord(); - virtual void updateTableView(int lineToSelect , bool keepColumnWidths = false); virtual void selectTableLine( int lineToSelect ); virtual void navigatePrevious(); virtual void navigateNext(); virtual void navigateGoto(); virtual void setRecordsetLabel(); - virtual void browseFind( bool open ); - virtual void browseFindAway(); virtual void browseRefresh(); - virtual void lookfor( const QString & wfield, const QString & woperator, const QString & wsearchterm ); virtual void createTable(); virtual void createIndex(); virtual void compact(); @@ -124,7 +116,7 @@ private slots: virtual void helpAbout(); virtual void updateRecordText(int row, int col, const QByteArray& newtext); virtual void editWinAway(); - virtual void editText( int row, int col ); + virtual void editText(const QModelIndex& index); virtual void doubleClickTable(const QModelIndex& index); virtual void executeQuery(); virtual void importTableFromCSV(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index dbe2da9c..d21187b6 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -116,29 +116,6 @@ - - - - Open or close the floating find window - - - This button toggles the appearance of the Find window, used to search records in the database view - - - - - - - :/icons/searchfind:/icons/searchfind - - - Ctrl+F - - - true - - - @@ -205,6 +182,9 @@ This is the database view. You can double-click any record to edit its contents in the cell editor window. + + QAbstractItemView::NoEditTriggers + QAbstractItemView::ExtendedSelection @@ -1364,7 +1344,6 @@ dbTreeWidget comboBrowseTable - buttonFind buttonRefresh buttonNewRecord buttonDeleteRecord @@ -1578,22 +1557,6 @@ - - buttonFind - toggled(bool) - MainWindow - browseFind(bool) - - - 141 - 81 - - - 399 - 299 - - - buttonRefresh clicked() diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index b515de78..ed3d0978 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -22,7 +22,6 @@ script.png script_add.png script_delete.png - magnifier.png wrench.png help.png diff --git a/src/icons/magnifier.png b/src/icons/magnifier.png deleted file mode 100644 index cf3d97f7..00000000 Binary files a/src/icons/magnifier.png and /dev/null differ diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index 195d93c7..2abfca85 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -1,5 +1,5 @@ #include "sqlitedb.h" - +#include "sqlitetablemodel.h" #include "MainWindow.h" #include @@ -234,8 +234,11 @@ bool DBBrowserDB::dump(const QString& filename) unsigned int numRecordsTotal = 0, numRecordsCurrent = 0; QList tables = objMap.values("table"); for(QList::ConstIterator it=tables.begin();it!=tables.end();++it) - numRecordsTotal += getFindResults( - QString("SELECT COUNT(*) FROM `%1`;").arg((*it).getname())).value(0).toInt(); + { + SqliteTableModel tableModel(0, this); + tableModel.setTable((*it).getname()); + numRecordsTotal += tableModel.totalRowCount(); + } QProgressDialog progress(QObject::tr("Exporting database to SQL file..."), QObject::tr("Cancel"), 0, numRecordsTotal); progress.setWindowModality(Qt::ApplicationModal); @@ -256,16 +259,18 @@ bool DBBrowserDB::dump(const QString& filename) stream << (*it).getsql() << ";\n"; // Get data of this table - browseTable((*it).getname()); + SqliteTableModel tableModel(0, this); + tableModel.setTable((*it).getname()); + while(tableModel.canFetchMore()) + tableModel.fetchMore(); // Dump all the content of the table - rowList data = browseRecs; - for(int row=0;row rt = tab[wrow]; - QString rowid = rt[0]; lastErrorMessage = QString("no error"); - QString statement = QString("DELETE FROM `%1` WHERE rowid=%2;").arg(curBrowseTableName).arg(rowid); + QString statement = QString("DELETE FROM `%1` WHERE rowid=%2;").arg(table).arg(rowid); if (_db){ logSQL(statement, kLogMsg_App); @@ -436,19 +439,13 @@ bool DBBrowserDB::deleteRecord( int wrow) return ok; } -bool DBBrowserDB::updateRecord(int wrow, int wcol, const QByteArray& wtext) +bool DBBrowserDB::updateRecord(const QString& table, const QString& column, int row, const QByteArray& value) { - if (!hasValidBrowseSet) return false; if (!isOpen()) return false; - + lastErrorMessage = QString("no error"); - - QList& rt = browseRecs[wrow]; - QString rowid = rt[0]; - QByteArray& cv = rt[wcol+1];//must account for rowid - QString ct = browseFields.at(wcol); - - QString sql = QString("UPDATE `%1` SET `%2`=? WHERE rowid=%4;").arg(curBrowseTableName).arg(ct).arg(rowid); + + QString sql = QString("UPDATE `%1` SET `%2`=? WHERE rowid=%3;").arg(table).arg(column).arg(row); logSQL(sql, kLogMsg_App); setRestorePoint(); @@ -457,7 +454,7 @@ bool DBBrowserDB::updateRecord(int wrow, int wcol, const QByteArray& wtext) int success = 1; if(sqlite3_prepare_v2(_db, sql.toUtf8(), -1, &stmt, 0) != SQLITE_OK) success = 0; - if(success == 1 && sqlite3_bind_text(stmt, 1, wtext.constData(), wtext.length(), SQLITE_STATIC) != SQLITE_OK) + if(success == 1 && sqlite3_bind_text(stmt, 1, value.constData(), value.length(), SQLITE_STATIC) != SQLITE_OK) success = -1; if(success == 1 && sqlite3_step(stmt) != SQLITE_DONE) success = -1; @@ -466,7 +463,6 @@ bool DBBrowserDB::updateRecord(int wrow, int wcol, const QByteArray& wtext) if(success == 1) { - cv = wtext; return true; } else { lastErrorMessage = sqlite3_errmsg(_db); @@ -475,12 +471,11 @@ bool DBBrowserDB::updateRecord(int wrow, int wcol, const QByteArray& wtext) } } -bool DBBrowserDB::browseTable( const QString & tablename, const QString& orderby ) +bool DBBrowserDB::browseTable( const QString & tablename, const QString& /*orderby*/ ) { QStringList testFields = getTableFields( tablename ); if (testFields.count()>0) {//table exists - getTableRecords( tablename, orderby ); browseFields = testFields; hasValidBrowseSet = true; curBrowseTableName = tablename; @@ -711,87 +706,6 @@ bool DBBrowserDB::renameTable(const QString& from_table, const QString& to_table } } -void DBBrowserDB::getTableRecords( const QString & tablename, const QString& orderby ) -{ - sqlite3_stmt* stmt; - - int ncol; - QList r; - browseRecs.clear(); - idmap.clear(); - lastErrorMessage = QObject::tr("no error"); - - QString sql = QString("SELECT rowid, * FROM `%1` ORDER BY %2;").arg(tablename).arg(orderby); - logSQL(sql, kLogMsg_App); - if(sqlite3_prepare_v2(_db, sql.toUtf8(), -1, &stmt, 0) != SQLITE_OK) - { - lastErrorMessage = QObject::tr("could not get fields"); - return; - } - - int rownum = 0; - - while(sqlite3_step(stmt) == SQLITE_ROW) - { - r.clear(); - ncol = sqlite3_data_count(stmt); - for(int e=0;e(sqlite3_column_blob(stmt, e)), sqlite3_column_bytes(stmt, e)); - r.append(rv); - - if(e == 0) - { - idmap.insert(rv.toInt(), rownum); - rownum++; - } - } - browseRecs.append(r); - } - - sqlite3_finalize(stmt); -} - -resultMap DBBrowserDB::getFindResults( const QString & wstatement) -{ - sqlite3_stmt *vm; - const char *tail; - - int ncol; - - // char *errmsg; - int err=0; - resultMap res; - lastErrorMessage = QObject::tr("no error"); - logSQL(wstatement, kLogMsg_App); - QByteArray statementutf8 = wstatement.toUtf8(); - err=sqlite3_prepare_v2(_db, statementutf8, statementutf8.length(), - &vm, &tail); - if (err == SQLITE_OK){ - int rownum = 0; - int recnum = 0; - QString r; - while ( sqlite3_step(vm) == SQLITE_ROW ){ - ncol = sqlite3_data_count(vm); - for (int e=0; e& structure); @@ -116,7 +116,6 @@ public: objectMap getBrowsableObjects() const; DBBrowserObject getObjectByName(const QString& name) const; QStringList getIndexNames() const; - resultMap getFindResults( const QString & wstatement); int getRecordCount() const; bool isOpen() const; void setDirty(bool dirtyval); @@ -144,12 +143,8 @@ public: MainWindow* mainWindow; - private: bool dirty; - void getTableRecords( const QString & tablename, const QString& orderby = "rowid" ); - - }; #endif diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp new file mode 100644 index 00000000..aca5c64b --- /dev/null +++ b/src/sqlitetablemodel.cpp @@ -0,0 +1,291 @@ +#include "sqlitetablemodel.h" +#include "sqlitedb.h" + +#include + +SqliteTableModel::SqliteTableModel(QObject* parent, DBBrowserDB* db) + : QAbstractTableModel(parent) + , m_db(db) + , m_rowCount(0) + , m_iSortColumn(0) + , m_sSortOrder("ASC") + , m_chunkSize(50000) +{ +} + +void SqliteTableModel::setChunkSize(size_t chunksize) +{ + m_chunkSize = chunksize; +} + +void SqliteTableModel::setTable(const QString& table) +{ + m_sTable = table; + + m_headers.clear(); + m_headers.push_back("rowid"); + m_headers.append(m_db->getTableFields(table)); + + m_mWhere.clear(); + + buildQuery(); +} + +void SqliteTableModel::setQuery(const QString& sQuery) +{ + if(!m_db->isOpen()) + return; + + sqlite3_stmt *stmt; + m_rowCount = 0; + m_sQuery = sQuery; + + // do a count query to get the full row count in a fast manner + QString sCountQuery = QString("SELECT COUNT(*) FROM (%1);").arg(sQuery); + m_db->logSQL(sCountQuery, kLogMsg_App); + QByteArray utf8Query = sCountQuery.toUtf8(); + int status = sqlite3_prepare_v2(m_db->_db, utf8Query, utf8Query.size(), &stmt, NULL); + + if(SQLITE_OK == status) + { + status = sqlite3_step(stmt); + if(SQLITE_ROW == status) + { + QString sCount = QString::fromUtf8((const char *) sqlite3_column_text(stmt, 0)); + m_rowCount = sCount.toInt(); + } + } + sqlite3_finalize(stmt); + + // now fetch the first entries + clearCache(); + fetchData(0, m_chunkSize); + + emit layoutChanged(); +} + +int SqliteTableModel::rowCount(const QModelIndex&) const +{ + return m_data.size(); // current fetched row count +} + +int SqliteTableModel::totalRowCount() const +{ + return m_rowCount; +} + +int SqliteTableModel::columnCount(const QModelIndex&) const +{ + return m_headers.size(); +} + +QVariant SqliteTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return m_headers.at(section); + else + return QString("%1").arg(section + 1); +} + +QVariant SqliteTableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= m_rowCount) + return QVariant(); + + if (role == Qt::DisplayRole) + { + // If this row is not in the cache yet get it first + while(index.row() >= m_data.size() && canFetchMore()) + const_cast(this)->fetchMore(); // Nothing evil to see here, move along + + return m_data.at(index.row()).at(index.column()); + } else { + return QVariant(); + } +} + +bool SqliteTableModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if(index.isValid() && role == Qt::EditRole) + { + if(m_db->updateRecord(m_sTable, m_headers.at(index.column()), m_data[index.row()].at(0).toInt(), value.toByteArray())) + { + // Only update the cache if this row has already been read, if not there's no need to do any changes to the cache + if(index.row() < m_data.size()) + m_data[index.row()].replace(index.column(), value.toByteArray()); + + emit(dataChanged(index, index)); + return true; + } else { + return false; + } + } + + return false; +} + +bool SqliteTableModel::canFetchMore(const QModelIndex&) const +{ + return m_data.size() < m_rowCount; +} + +void SqliteTableModel::fetchMore(const QModelIndex&) +{ + int row = m_data.size(); + fetchData(row, row + m_chunkSize); +} + +Qt::ItemFlags SqliteTableModel::flags(const QModelIndex& index) const +{ + if(!index.isValid()) + return Qt::ItemIsEnabled; + + return QAbstractTableModel::flags(index)/* | Qt::ItemIsEditable*/; +} + +void SqliteTableModel::sort(int column, Qt::SortOrder order) +{ + // Don't do anything when the sort order hasn't changed + if(m_iSortColumn == column && m_sSortOrder == (order == Qt::AscendingOrder ? "ASC" : "DESC")) + return; + + // Save sort order + m_iSortColumn = column; + m_sSortOrder = (order == Qt::AscendingOrder ? "ASC" : "DESC"); + + // Set the new query (but only if a table has already been set + if(m_sTable != "") + buildQuery(); +} + +bool SqliteTableModel::insertRows(int row, int count, const QModelIndex& parent) +{ + beginInsertRows(parent, row, row + count - 1); + + QByteArrayList blank_data; + for(int i=0;iaddRecord(m_sTable))); + } + + m_rowCount += count; + + endInsertRows(); + return true; +} + +bool SqliteTableModel::removeRows(int row, int count, const QModelIndex& parent) +{ + beginRemoveRows(parent, row, row + count - 1); + + for(int i=count-1;i>=0;i--) + { + m_db->deleteRecord(m_sTable, m_data.at(row + i).at(0).toInt()); + m_data.removeAt(row + i); + } + + m_rowCount -= count; + + endRemoveRows(); + return true; +} + +void SqliteTableModel::fetchData(unsigned int from, unsigned to) +{ + int currentsize = m_data.size(); + + QString sLimitQuery = QString("%1 LIMIT %2, %3;").arg(m_sQuery).arg(from).arg(to-from); + m_db->logSQL(sLimitQuery, kLogMsg_App); + QByteArray utf8Query = sLimitQuery.toUtf8(); + sqlite3_stmt *stmt; + int status = sqlite3_prepare_v2(m_db->_db, utf8Query, utf8Query.size(), &stmt, NULL); + + if(SQLITE_OK == status) + { + while(sqlite3_step(stmt) == SQLITE_ROW) + { + QByteArrayList rowdata; + for (int i = 0; i < m_headers.size(); ++i) + rowdata.append(QByteArray(static_cast(sqlite3_column_blob(stmt, i)), sqlite3_column_bytes(stmt, i))); + m_data.push_back(rowdata); + } + } + sqlite3_finalize(stmt); + + beginInsertRows(QModelIndex(), currentsize, m_data.size()-1); + endInsertRows(); +} + +void SqliteTableModel::buildQuery() +{ + QString where; + if(m_mWhere.size()) + { + where = "WHERE 1=1"; + for(QMap::const_iterator i=m_mWhere.constBegin();i!=m_mWhere.constEnd();++i) + where.append(QString(" AND `%1` %2").arg(m_headers.at(i.key())).arg(i.value())); + } + + QString sql = QString("SELECT rowid,* FROM `%1` %2 ORDER BY `%3` %4").arg(m_sTable).arg(where).arg(m_headers.at(m_iSortColumn)).arg(m_sSortOrder); + setQuery(sql); +} + +void SqliteTableModel::updateFilter(int column, const QString& value) +{ + // Check for any special comparison operators at the beginning of the value string. If there are none default to LIKE. + QString op = "LIKE"; + QString val; + if(value.left(2) == ">=" || value.left(2) == "<=" || value.left(2) == "<>") + { + bool ok; + value.mid(2).toFloat(&ok); + if(ok) + { + op = value.left(2); + val = value.mid(2); + } + } else if(value.left(1) == ">" || value.left(1) == "<") { + bool ok; + value.mid(1).toFloat(&ok); + if(ok) + { + op = value.left(1); + val = value.mid(1); + } + } else { + if(value.left(1) == "=") + { + op = "="; + val = value.mid(1); + } else { + val = value; + } + val = QString("'%1'").arg(val.replace("'", "")); + } + + // If the value was set to an empty string remove any filter for this column. Otherwise insert a new filter rule or replace the old one if there is already one + if(value.isEmpty()) + m_mWhere.remove(column); + else + m_mWhere.insert(column, QString("%1 %2").arg(op).arg(val)); + + // Build the new query + buildQuery(); +} + +void SqliteTableModel::clearCache() +{ + beginRemoveRows(QModelIndex(), 0, m_data.size()-1); + m_data.clear(); + endRemoveRows(); +} diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h new file mode 100644 index 00000000..3ccefc99 --- /dev/null +++ b/src/sqlitetablemodel.h @@ -0,0 +1,70 @@ +#ifndef SQLITETABLEMODEL_H +#define SQLITETABLEMODEL_H + +#include +#include + +class DBBrowserDB; + +class SqliteTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + explicit SqliteTableModel(QObject *parent = 0, DBBrowserDB* db = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int totalRowCount() const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; + void fetchMore(const QModelIndex &parent = QModelIndex()); + size_t queryMore(size_t offset); + + bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); + + void setQuery(const QString& sQuery); + void setTable(const QString& table); + void setChunkSize(size_t chunksize); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + Qt::ItemFlags flags(const QModelIndex& index) const; + + typedef QList QByteArrayList; + +signals: + +public slots: + void updateFilter(int column, const QString& value); + +private: + void fetchData(unsigned int from, unsigned to); + void clearCache(); + + void buildQuery(); + + DBBrowserDB* m_db; + int m_rowCount; + QStringList m_headers; + QList m_data; + + QString m_sQuery; + QString m_sTable; + int m_iSortColumn; + QString m_sSortOrder; + QMap m_mWhere; + + /** + * @brief m_chunkSize Size of the next chunk fetch more will try to fetch. + * This value should be rather high, because our query + * uses LIMIT and sqlite3 will still execute the whole query and + * just skip the not wanted rows, but the execution will + * still take nearly the same time as doing the query at all up + * to that row count. + */ + size_t m_chunkSize; +}; + +#endif // SQLITETABLEMODEL_H diff --git a/src/src.pro b/src/src.pro index 9433049d..b9ba834b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -16,7 +16,6 @@ HEADERS += \ AboutDialog.h \ EditTableDialog.h \ PreferencesDialog.h \ - FindDialog.h \ EditDialog.h \ ExportCsvDialog.h \ ImportCsvDialog.h \ @@ -25,7 +24,9 @@ HEADERS += \ ExtendedTableWidget.h \ grammar/Sqlite3Lexer.hpp \ grammar/Sqlite3Parser.hpp \ - grammar/sqlite3TokenTypes.hpp + grammar/sqlite3TokenTypes.hpp \ + sqlitetablemodel.h \ + FilterTableHeader.h SOURCES += \ sqlitedb.cpp \ @@ -35,7 +36,6 @@ SOURCES += \ EditTableDialog.cpp \ PreferencesDialog.cpp \ AboutDialog.cpp \ - FindDialog.cpp \ EditDialog.cpp \ ExportCsvDialog.cpp \ ImportCsvDialog.cpp \ @@ -43,7 +43,9 @@ SOURCES += \ sqlitetypes.cpp \ ExtendedTableWidget.cpp \ grammar/Sqlite3Lexer.cpp \ - grammar/Sqlite3Parser.cpp + grammar/Sqlite3Parser.cpp \ + sqlitetablemodel.cpp \ + FilterTableHeader.cpp # create a unittest option CONFIG(unittest) { @@ -85,7 +87,6 @@ FORMS += \ AboutDialog.ui \ EditTableDialog.ui \ PreferencesDialog.ui \ - FindDialog.ui \ EditDialog.ui \ ExportCsvDialog.ui \ ImportCsvDialog.ui