Merge branch 'partial-data-fetch'

This commit is contained in:
Peinthor Rene
2013-04-14 21:43:54 +02:00
19 changed files with 674 additions and 720 deletions

View File

@@ -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

View File

@@ -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;i<fields.count();i++)
for(int i=1;i<tableModel.columnCount();i++)
{
stream << quoteChar << fields.at(i) << quoteChar;
if(i < fields.count() - 1)
stream << quoteChar << tableModel.headerData(i, Qt::Horizontal).toString() << quoteChar;
if(i < tableModel.columnCount() - 1)
stream << sepChar;
else
stream << newlineChar;
@@ -70,15 +73,13 @@ void ExportCsvDialog::accept()
}
// Get and write actual data
rowList data = pdb->browseRecs;
for(int i=0;i<data.size();i++)
for(int i=0;i<tableModel.totalRowCount();i++)
{
QList<QByteArray> row = data[i];
for(int j=1;j<row.size();j++)
for(int j=1;j<tableModel.columnCount();j++)
{
QString content = row[j];
QString content = tableModel.data(tableModel.index(i, j)).toString();
stream << quoteChar << content.replace(quoteChar, quotequoteChar) << quoteChar;
if(j < row.count() - 1)
if(j < tableModel.columnCount() - 1)
stream << sepChar;
else
stream << newlineChar;

View File

@@ -3,10 +3,14 @@
#include <QKeySequence>
#include <QKeyEvent>
#include "ExtendedTableWidget.h"
#include "sqlitetablemodel.h"
#include <QScrollBar>
#include <QHeaderView>
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<SqliteTableModel*>(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());
}

View File

@@ -13,8 +13,12 @@ public:
private:
void copy();
private slots:
void vscrollbarChanged(int value);
protected:
virtual void keyPressEvent(QKeyEvent* event);
virtual void updateGeometries();
};
#endif

78
src/FilterTableHeader.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include "FilterTableHeader.h"
#include <QLineEdit>
#include <QTableView>
#include <QScrollBar>
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;i<filterWidgets.size();i++)
delete filterWidgets.at(i);
filterWidgets.clear();
// And generate a bunch of new ones
for(int i=0;i<number;i++)
{
QLineEdit* l = new QLineEdit(this);
l->setPlaceholderText(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;i<filterWidgets.size();++i)
{
// Get the current widget, move it and reisize it
QWidget* w = filterWidgets.at(i);
w->move(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);
}

35
src/FilterTableHeader.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef __FILTERTABLEHEADER_H__
#define __FILTERTABLEHEADER_H__
#include <QHeaderView>
#include <QList>
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<QLineEdit*> filterWidgets;
};
#endif

View File

@@ -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();
}

View File

@@ -1,38 +0,0 @@
#ifndef __FINDDIALOG_H__
#define __FINDDIALOG_H__
#include <QDialog>
#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

View File

@@ -1,193 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FindDialog</class>
<widget class="QDialog" name="FindDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>288</width>
<height>351</height>
</rect>
</property>
<property name="windowTitle">
<string>Find</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboColumn">
<property name="toolTip">
<string>Field to be searched</string>
</property>
<property name="whatsThis">
<string>Use this control to select the field to be searched in the current table</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboOperator">
<property name="toolTip">
<string>Search criteria: use 'contains' for partial matches</string>
</property>
<property name="whatsThis">
<string>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.</string>
</property>
<item>
<property name="text">
<string>=</string>
</property>
</item>
<item>
<property name="text">
<string>contains</string>
</property>
</item>
<item>
<property name="text">
<string>&gt;</string>
</property>
</item>
<item>
<property name="text">
<string>&gt;=</string>
</property>
</item>
<item>
<property name="text">
<string>&lt;=</string>
</property>
</item>
<item>
<property name="text">
<string>&lt;</string>
</property>
</item>
<item>
<property name="text">
<string>&lt;&gt;</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="editSearchString">
<property name="toolTip">
<string>Enter values or words to search</string>
</property>
<property name="whatsThis">
<string>This is a place to enter the word or number to be searched in the database</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonSearch">
<property name="toolTip">
<string>Perform the search</string>
</property>
<property name="whatsThis">
<string>This button starts the search process</string>
</property>
<property name="text">
<string>&amp;Search</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="tableResults">
<property name="whatsThis">
<string>Results of the search will appear in this area. Click on a result to select the corresponding record in the database</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<column>
<property name="text">
<string>Records</string>
</property>
</column>
<column>
<property name="text">
<string>Data</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="labelNumberResults">
<property name="text">
<string>Found: 0</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>comboColumn</tabstop>
<tabstop>comboOperator</tabstop>
<tabstop>editSearchString</tabstop>
<tabstop>buttonSearch</tabstop>
<tabstop>tableResults</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonSearch</sender>
<signal>clicked()</signal>
<receiver>FindDialog</receiver>
<slot>find()</slot>
<hints>
<hint type="sourcelabel">
<x>233</x>
<y>45</y>
</hint>
<hint type="destinationlabel">
<x>267</x>
<y>30</y>
</hint>
</hints>
</connection>
<connection>
<sender>tableResults</sender>
<signal>itemClicked(QTableWidgetItem*)</signal>
<receiver>FindDialog</receiver>
<slot>recordSelected(QTableWidgetItem*)</slot>
<hints>
<hint type="sourcelabel">
<x>100</x>
<y>176</y>
</hint>
<hint type="destinationlabel">
<x>86</x>
<y>58</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>find()</slot>
<slot>recordSelected(QTableWidgetItem*)</slot>
</slots>
</ui>

View File

@@ -10,6 +10,7 @@
#include <QStandardItemModel>
#include <QDragEnterEvent>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#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<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);
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<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)
{
// 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<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);
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<QByteArray> 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

View File

@@ -4,17 +4,14 @@
#include <QMainWindow>
#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();

View File

@@ -116,29 +116,6 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonFind">
<property name="toolTip">
<string>Open or close the floating find window</string>
</property>
<property name="whatsThis">
<string>This button toggles the appearance of the Find window, used to search records in the database view</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/searchfind</normaloff>:/icons/searchfind</iconset>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonRefresh">
<property name="toolTip">
@@ -205,6 +182,9 @@
<property name="whatsThis">
<string>This is the database view. You can double-click any record to edit its contents in the cell editor window.</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
@@ -1364,7 +1344,6 @@
<tabstops>
<tabstop>dbTreeWidget</tabstop>
<tabstop>comboBrowseTable</tabstop>
<tabstop>buttonFind</tabstop>
<tabstop>buttonRefresh</tabstop>
<tabstop>buttonNewRecord</tabstop>
<tabstop>buttonDeleteRecord</tabstop>
@@ -1578,22 +1557,6 @@
</hint>
</hints>
</connection>
<connection>
<sender>buttonFind</sender>
<signal>toggled(bool)</signal>
<receiver>MainWindow</receiver>
<slot>browseFind(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>141</x>
<y>81</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>299</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonRefresh</sender>
<signal>clicked()</signal>

View File

@@ -22,7 +22,6 @@
<file alias="trigger">script.png</file>
<file alias="trigger_create">script_add.png</file>
<file alias="trigger_delete">script_delete.png</file>
<file alias="searchfind">magnifier.png</file>
<file alias="settings">wrench.png</file>
<file alias="whatis">help.png</file>
</qresource>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 B

View File

@@ -1,5 +1,5 @@
#include "sqlitedb.h"
#include "sqlitetablemodel.h"
#include "MainWindow.h"
#include <QFile>
@@ -234,8 +234,11 @@ bool DBBrowserDB::dump(const QString& filename)
unsigned int numRecordsTotal = 0, numRecordsCurrent = 0;
QList<DBBrowserObject> tables = objMap.values("table");
for(QList<DBBrowserObject>::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<data.size();row++)
for(int row=0;row<tableModel.totalRowCount();row++)
{
stream << "INSERT INTO `" << (*it).getname() << "` VALUES(";
for(int col=1;col<data[row].size();col++)
for(int col=1;col<tableModel.columnCount();col++)
{
QString content = data[row][col];
QString content = tableModel.data(tableModel.index(row, col)).toString();
content.replace("'", "''");
if(content.isNull())
content = "NULL";
@@ -275,7 +280,7 @@ bool DBBrowserDB::dump(const QString& filename)
content = "''";
stream << content;
if(col < data[row].count() - 1)
if(col < tableModel.columnCount() - 1)
stream << ",";
else
stream << ");\n";
@@ -385,7 +390,7 @@ bool DBBrowserDB::executeMultiSQL(const QString& statement, bool dirty, bool log
return true;
}
bool DBBrowserDB::addRecord(const QString& sTableName)
int DBBrowserDB::addRecord(const QString& sTableName)
{
char *errmsg;
if (!isOpen()) return false;
@@ -404,23 +409,21 @@ bool DBBrowserDB::addRecord(const QString& sTableName)
{
lastErrorMessage = QString::fromUtf8(errmsg);
qCritical() << "addRecord: " << lastErrorMessage;
return false;
return -1;
} else {
return sqlite3_last_insert_rowid(_db);
}
return true;
}
bool DBBrowserDB::deleteRecord( int wrow)
bool DBBrowserDB::deleteRecord(const QString& table, int rowid)
{
char * errmsg;
if (!hasValidBrowseSet) return false;
if (!isOpen()) return false;
bool ok = false;
rowList tab = browseRecs;
QList<QByteArray> 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<QByteArray>& 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<QByteArray> 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<ncol;++e)
{
QByteArray rv = QByteArray(static_cast<const char*>(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<ncol; ++e){
r = QString::fromUtf8((const char *) sqlite3_column_text(vm, e));
if (e==0){
rownum = r.toInt();
rowIdMap::iterator mit = idmap.find(rownum);
recnum = *mit;
}
}
res.insert(recnum, r);
}
sqlite3_finalize(vm);
}else{
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
}
return res;
}
QStringList DBBrowserDB::getBrowsableObjectNames() const
{
objectMap::ConstIterator it;

View File

@@ -99,9 +99,9 @@ public:
*/
QString getTableSQL(const QString& sTable);
void updateSchema() ;
bool addRecord(const QString& sTableName);
bool deleteRecord(int wrow);
bool updateRecord(int wrow, int wcol, const QByteArray& wtext);
int addRecord(const QString& sTableName);
bool deleteRecord(const QString& table, int rowid);
bool updateRecord(const QString& table, const QString& column, int row, const QByteArray& value);
bool browseTable( const QString & tablename, const QString& orderby = "rowid" );
bool createTable(const QString& name, const QList<DBBrowserField>& 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

291
src/sqlitetablemodel.cpp Normal file
View File

@@ -0,0 +1,291 @@
#include "sqlitetablemodel.h"
#include "sqlitedb.h"
#include <QDebug>
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<SqliteTableModel*>(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;i<m_headers.size();i++)
blank_data.push_back("");
for(int i=0;i<count;i++)
{
m_data.insert(row, blank_data);
m_data[row].replace(0, QByteArray::number(m_db->addRecord(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<const char*>(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<int, QString>::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();
}

70
src/sqlitetablemodel.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef SQLITETABLEMODEL_H
#define SQLITETABLEMODEL_H
#include <QAbstractTableModel>
#include <QStringList>
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<QByteArray> 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<QByteArrayList> m_data;
QString m_sQuery;
QString m_sTable;
int m_iSortColumn;
QString m_sSortOrder;
QMap<int, QString> 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

View File

@@ -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