From 6742f5632da92604ff0a8d81645f462239d258f6 Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Tue, 9 Apr 2013 18:52:37 +0200 Subject: [PATCH] Add a filter row to the table view in the browse tab Add a row of line edits between the table header and the actual content of the table in the browse tab. Show one input widget per table column in this row. Add a live search which hides any table rows which do not fit to the current filter settings. Why add this to the parital-data-fetch branch? Because this is an attempt to get rid of the find dialog which is broken again and not even worth fixing. Also there is not much to break in this branch at the moment ;) --- src/FilterTableHeader.cpp | 78 +++++++++++++++++++++++++++++++++++++++ src/FilterTableHeader.h | 35 ++++++++++++++++++ src/MainWindow.cpp | 10 ++++- src/sqlitetablemodel.cpp | 38 +++++++++++++++---- src/sqlitetablemodel.h | 6 ++- src/src.pro | 6 ++- 6 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 src/FilterTableHeader.cpp create mode 100644 src/FilterTableHeader.h 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/MainWindow.cpp b/src/MainWindow.cpp index 066ba703..14744e4f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -23,6 +23,7 @@ #include "SQLiteSyntaxHighlighter.h" #include "sqltextedit.h" #include "sqlitetablemodel.h" +#include "FilterTableHeader.h" MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), @@ -70,6 +71,10 @@ void MainWindow::init() 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); @@ -98,7 +103,7 @@ 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))); @@ -296,6 +301,9 @@ void MainWindow::populateTable( const QString & tablename) // 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); diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index e20a8c6a..4689f1c2 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -26,7 +26,9 @@ void SqliteTableModel::setTable(const QString& table) m_headers.push_back("rowid"); m_headers.append(m_db->getTableFields(table)); - setQuery(QString("SELECT rowid,* FROM `%1`").arg(table)); + m_mWhere.clear(); + + buildQuery(); } void SqliteTableModel::setQuery(const QString& sQuery) @@ -149,13 +151,7 @@ void SqliteTableModel::sort(int column, Qt::SortOrder order) // Set the new query (but only if a table has already been set if(m_sTable != "") - { - setQuery(QString("SELECT rowid,* FROM `%1` ORDER BY `%2` %3") - .arg(m_sTable) - .arg(m_headers.at(m_iSortColumn)) - .arg(m_sSortOrder) - ); - } + buildQuery(); } bool SqliteTableModel::insertRows(int row, int count, const QModelIndex& parent) @@ -214,3 +210,29 @@ void SqliteTableModel::fetchData(unsigned int from, unsigned to) beginInsertRows(QModelIndex(), currentsize + 1, m_data.size()); 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, QString value) +{ + // 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("LIKE '%1'").arg(value.replace('\'', ""))); // TODO: Add some code here to detect fancy filter values like '>5' and change the operator in these cases + + // Build the new query + buildQuery(); +} diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index 8cbcab2d..c9d86c36 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -14,7 +14,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const; int totalRowCount() const; - int columnCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); @@ -35,10 +35,13 @@ public: signals: public slots: + void updateFilter(int column, QString value); private: void fetchData(unsigned int from, unsigned to); + void buildQuery(); + DBBrowserDB* m_db; int m_rowCount; QStringList m_headers; @@ -48,6 +51,7 @@ private: QString m_sTable; int m_iSortColumn; QString m_sSortOrder; + QMap m_mWhere; size_t m_chunkSize; }; diff --git a/src/src.pro b/src/src.pro index 2787f12e..47cbc3e7 100644 --- a/src/src.pro +++ b/src/src.pro @@ -26,7 +26,8 @@ HEADERS += \ grammar/Sqlite3Lexer.hpp \ grammar/Sqlite3Parser.hpp \ grammar/sqlite3TokenTypes.hpp \ - sqlitetablemodel.h + sqlitetablemodel.h \ + FilterTableHeader.h SOURCES += \ sqlitedb.cpp \ @@ -45,7 +46,8 @@ SOURCES += \ ExtendedTableWidget.cpp \ grammar/Sqlite3Lexer.cpp \ grammar/Sqlite3Parser.cpp \ - sqlitetablemodel.cpp + sqlitetablemodel.cpp \ + FilterTableHeader.cpp # create a unittest option CONFIG(unittest) {