mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-28 23:09:32 -06:00
Add support for sorting by multiple columns in the Browse Data tab
This allows sorting by multiple columns (as in ORDER BY Field1, Field2) by holding the Control key while clicking on another column header in the Browse Data tab. See issue #1761.
This commit is contained in:
@@ -978,3 +978,17 @@ void ExtendedTableWidget::openPrintDialog()
|
||||
delete document;
|
||||
delete mimeData;
|
||||
}
|
||||
|
||||
void ExtendedTableWidget::sortByColumns(const std::vector<sqlb::SortedColumn>& columns)
|
||||
{
|
||||
// Are there even any columns to sort by?
|
||||
if(columns.size() == 0)
|
||||
return;
|
||||
|
||||
// Are we using a SqliteTableModel as a model? These support multiple sort columns. Other models might not; for those we just use the first sort column
|
||||
SqliteTableModel* sqlite_model = dynamic_cast<SqliteTableModel*>(model());
|
||||
if(sqlite_model == nullptr)
|
||||
model()->sort(columns.front().column, columns.front().direction == sqlb::Ascending ? Qt::AscendingOrder : Qt::DescendingOrder);
|
||||
else
|
||||
sqlite_model->sort(columns);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "sql/Query.h"
|
||||
|
||||
class QMenu;
|
||||
class QMimeData;
|
||||
class FilterTableHeader;
|
||||
@@ -52,6 +54,8 @@ public:
|
||||
QSet<int> selectedCols();
|
||||
int numVisibleRows();
|
||||
|
||||
void sortByColumns(const std::vector<sqlb::SortedColumn>& columns);
|
||||
|
||||
public slots:
|
||||
void reloadSettings();
|
||||
void selectTableLine(int lineToSelect);
|
||||
|
||||
@@ -80,20 +80,7 @@ QDataStream& operator>>(QDataStream& ds, sqlb::ObjectIdentifier& objid)
|
||||
return ds;
|
||||
}
|
||||
|
||||
// These are temporary helper functions to turn a vector of sorted columns into a single column to sort and vice verse. This is done by just taking the
|
||||
// first sort column there is and ignoring all the others or creating a single item vector respectively. These functions can be removed once all parts
|
||||
// of the application have been converted to deal with vectors of sorted columns.
|
||||
static void fromSortOrderVector(const std::vector<sqlb::SortedColumn>& vector, int& index, Qt::SortOrder& mode)
|
||||
{
|
||||
if(vector.size())
|
||||
{
|
||||
index = vector.at(0).column;
|
||||
mode = vector.at(0).direction == sqlb::Ascending ? Qt::AscendingOrder : Qt::DescendingOrder;
|
||||
} else {
|
||||
index = 0;
|
||||
mode = Qt::AscendingOrder;
|
||||
}
|
||||
}
|
||||
// This is a temporary helper function. Delete it once we clean up the project file loading.
|
||||
static std::vector<sqlb::SortedColumn> toSortOrderVector(int index, Qt::SortOrder mode)
|
||||
{
|
||||
std::vector<sqlb::SortedColumn> vector;
|
||||
@@ -777,10 +764,7 @@ void MainWindow::populateTable()
|
||||
sqlb::Query query(tablename);
|
||||
|
||||
// Sorting
|
||||
int sortOrderIndex;
|
||||
Qt::SortOrder sortOrderMode;
|
||||
fromSortOrderVector(storedData.query.orderBy(), sortOrderIndex, sortOrderMode);
|
||||
query.orderBy().emplace_back(sortOrderIndex, sortOrderMode == Qt::AscendingOrder ? sqlb::Ascending : sqlb::Descending);
|
||||
query.orderBy() = storedData.query.orderBy();
|
||||
|
||||
// Filters
|
||||
for(auto it=storedData.filterValues.constBegin();it!=storedData.filterValues.constEnd();++it)
|
||||
@@ -863,10 +847,10 @@ void MainWindow::applyBrowseTableSettings(BrowseDataTableSettings storedData, bo
|
||||
ui->dataTable->setColumnWidth(widthIt.key(), widthIt.value());
|
||||
|
||||
// Sorting
|
||||
int sortOrderIndex;
|
||||
Qt::SortOrder sortOrderMode;
|
||||
fromSortOrderVector(storedData.query.orderBy(), sortOrderIndex, sortOrderMode);
|
||||
ui->dataTable->filterHeader()->setSortIndicator(sortOrderIndex, sortOrderMode);
|
||||
// For now just use the first sort column for the sort indicator
|
||||
ui->dataTable->filterHeader()->setSortIndicator(
|
||||
storedData.query.orderBy().front().column,
|
||||
storedData.query.orderBy().front().direction == sqlb::Ascending ? Qt::AscendingOrder : Qt::DescendingOrder);
|
||||
|
||||
// Filters
|
||||
if(!skipFilters)
|
||||
@@ -2010,19 +1994,44 @@ void MainWindow::browseTableHeaderClicked(int logicalindex)
|
||||
// Abort if there is more than one column selected because this tells us that the user pretty sure wants to do a range selection
|
||||
// instead of sorting data. But restore before the sort indicator automatically changed by Qt so it still indicates the last
|
||||
// use sort action.
|
||||
if(ui->dataTable->selectionModel()->selectedColumns().count() > 1) {
|
||||
// This check is disabled when the Control key is pressed. This is done because we use the Control key for sorting by multiple columns and
|
||||
// Qt seems to pretty much always select multiple columns when the Control key is pressed.
|
||||
if(!QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && ui->dataTable->selectionModel()->selectedColumns().count() > 1) {
|
||||
applyBrowseTableSettings(settings);
|
||||
return;
|
||||
}
|
||||
int column;
|
||||
Qt::SortOrder order;
|
||||
fromSortOrderVector(settings.query.orderBy(), column, order);
|
||||
if(column == logicalindex)
|
||||
order = order == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||
else
|
||||
order = Qt::AscendingOrder;
|
||||
settings.query.orderBy() = toSortOrderVector(logicalindex, order);
|
||||
ui->dataTable->sortByColumn(logicalindex, order);
|
||||
|
||||
// Get the current list of sort columns
|
||||
auto& columns = settings.query.orderBy();
|
||||
|
||||
// Before sorting, first check if the Control key is pressed. If it is, we want to append this column to the list of sort columns. If it is not,
|
||||
// we want to sort only by the new column.
|
||||
if(QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
{
|
||||
// Multi column sorting
|
||||
|
||||
// If the last sort column was just clicked again, change its sort order.
|
||||
// If not, add the column as a new sort column to the list.
|
||||
if(columns.size() && columns.back().column == logicalindex)
|
||||
columns.back().direction = (columns.back().direction == sqlb::Ascending ? sqlb::Descending : sqlb::Ascending);
|
||||
else
|
||||
columns.emplace_back(logicalindex, sqlb::Ascending);
|
||||
} else {
|
||||
// Single column sorting
|
||||
|
||||
// If we have exactly one sort column and it is the column which was just clicked, change its sort order.
|
||||
// If not, clear the list of sorting columns and replace it by a single new sort column.
|
||||
if(columns.size() == 1 && columns.front().column == logicalindex)
|
||||
{
|
||||
columns.front().direction = (columns.front().direction == sqlb::Ascending ? sqlb::Descending : sqlb::Ascending);
|
||||
} else {
|
||||
columns.clear();
|
||||
columns.emplace_back(logicalindex, sqlb::Ascending);
|
||||
}
|
||||
}
|
||||
|
||||
// Do the actual sorting
|
||||
ui->dataTable->sortByColumns(columns);
|
||||
|
||||
// select the first item in the column so the header is bold
|
||||
// we might try to select the last selected item
|
||||
@@ -2821,10 +2830,7 @@ bool MainWindow::loadProject(QString filename, bool readOnly)
|
||||
populateTable(); // Refresh view
|
||||
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
|
||||
|
||||
int sortIndex;
|
||||
Qt::SortOrder sortMode;
|
||||
fromSortOrderVector(browseTableSettings[current_table].query.orderBy(), sortIndex, sortMode);
|
||||
ui->dataTable->sortByColumn(sortIndex, sortMode);
|
||||
ui->dataTable->sortByColumns(browseTableSettings[current_table].query.orderBy());
|
||||
showRowidColumn(browseTableSettings[current_table].showRowid);
|
||||
unlockViewEditing(!browseTableSettings[current_table].unlockViewPk.isEmpty(), browseTableSettings[current_table].unlockViewPk);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ struct SortedColumn
|
||||
direction(direction_)
|
||||
{}
|
||||
|
||||
bool operator==(const SortedColumn& rhs) const
|
||||
{
|
||||
return column == rhs.column && direction == rhs.direction;
|
||||
}
|
||||
|
||||
int column;
|
||||
SortDirection direction;
|
||||
};
|
||||
|
||||
@@ -453,17 +453,21 @@ Qt::ItemFlags SqliteTableModel::flags(const QModelIndex& index) const
|
||||
}
|
||||
|
||||
void SqliteTableModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
// Construct a sort order list from this item and forward it to the function to sort by lists
|
||||
std::vector<sqlb::SortedColumn> list;
|
||||
list.emplace_back(column, order == Qt::AscendingOrder ? sqlb::Ascending : sqlb::Descending);
|
||||
sort(list);
|
||||
}
|
||||
|
||||
void SqliteTableModel::sort(const std::vector<sqlb::SortedColumn>& columns)
|
||||
{
|
||||
// Don't do anything when the sort order hasn't changed
|
||||
if(m_query.orderBy().size() && m_query.orderBy().at(0).column == column && m_query.orderBy().at(0).direction == (order == Qt::AscendingOrder ? sqlb::Ascending : sqlb::Descending))
|
||||
if(m_query.orderBy() == columns)
|
||||
return;
|
||||
|
||||
// Reset sort order
|
||||
m_query.orderBy().clear();
|
||||
|
||||
// Save sort order
|
||||
if (column >= 0 && column < m_headers.size())
|
||||
m_query.orderBy().emplace_back(column, (order == Qt::AscendingOrder ? sqlb::Ascending : sqlb::Descending));
|
||||
m_query.orderBy() = columns;
|
||||
|
||||
// Set the new query (but only if a table has already been set
|
||||
if(!m_query.table().isEmpty())
|
||||
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
void setChunkSize(size_t chunksize);
|
||||
size_t chunkSize() { return m_chunkSize; };
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
void sort(const std::vector<sqlb::SortedColumn>& columns);
|
||||
sqlb::ObjectIdentifier currentTableName() const { return m_query.table(); }
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
Reference in New Issue
Block a user