Add new option to freeze columns in the Browse Data tab

This adds a new context menu action called "Freeze Columns" to the
context menu which appears when you right click the column headers in
a Browse Data dock. With this action all columns from the first up to
the clicked column are fixed in place when you scroll horizontally. This
can be used to make for example the id column always visible.

See issue #1888.
This commit is contained in:
Martin Kleusberg
2020-06-21 13:53:25 +02:00
parent 34040f81aa
commit a15f81bc35
8 changed files with 282 additions and 21 deletions

View File

@@ -224,7 +224,9 @@ void ExtendedTableWidgetEditorDelegate::updateEditorGeometry(QWidget* editor, co
ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
QTableView(parent)
QTableView(parent),
m_frozen_table_view(qobject_cast<ExtendedTableWidget*>(parent) ? nullptr : new ExtendedTableWidget(this)),
m_frozen_column_count(0)
{
setHorizontalScrollMode(ExtendedTableWidget::ScrollPerPixel);
// Force ScrollPerItem, so scrolling shows all table rows
@@ -405,21 +407,81 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
selectionModel()->select(QItemSelection(selectionModel()->selectedIndexes().first(), selectionModel()->selectedIndexes().last()), QItemSelectionModel::Select | QItemSelectionModel::Rows);
});
// Set up frozen columns child widget
if(m_frozen_table_view)
{
// Set up widget
m_frozen_table_view->setFocusPolicy(Qt::NoFocus);
m_frozen_table_view->verticalHeader()->hide();
m_frozen_table_view->setStyleSheet("QTableView { border: none; }"
"QTableView::item:selected{ background-color: " + palette().color(QPalette::Active, QPalette::Highlight).name() + "}");
m_frozen_table_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_frozen_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_frozen_table_view->setVerticalScrollMode(verticalScrollMode());
viewport()->stackUnder(m_frozen_table_view);
m_tableHeader->stackUnder(m_frozen_table_view);
// Keep both widgets in sync
connect(horizontalHeader(), &QHeaderView::sectionResized, this, &ExtendedTableWidget::updateSectionWidth);
connect(m_frozen_table_view->horizontalHeader(), &QHeaderView::sectionResized, this, &ExtendedTableWidget::updateSectionWidth);
connect(verticalHeader(), &QHeaderView::sectionResized, this, &ExtendedTableWidget::updateSectionHeight);
connect(m_frozen_table_view->verticalHeader(), &QHeaderView::sectionResized, this, &ExtendedTableWidget::updateSectionHeight);
connect(m_frozen_table_view->verticalScrollBar(), &QAbstractSlider::valueChanged, verticalScrollBar(), &QAbstractSlider::setValue);
connect(verticalScrollBar(), &QAbstractSlider::valueChanged, m_frozen_table_view->verticalScrollBar(), &QAbstractSlider::setValue);
// Forward signals from frozen table view widget to the main table view widget
connect(m_frozen_table_view, &ExtendedTableWidget::doubleClicked, this, &ExtendedTableWidget::doubleClicked);
connect(m_frozen_table_view->filterHeader(), &FilterTableHeader::sectionClicked, filterHeader(), &FilterTableHeader::sectionClicked);
connect(m_frozen_table_view->filterHeader(), &QHeaderView::sectionDoubleClicked, filterHeader(), &QHeaderView::sectionDoubleClicked);
connect(m_frozen_table_view->verticalHeader(), &QHeaderView::sectionResized, verticalHeader(), &QHeaderView::sectionResized);
connect(m_frozen_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, horizontalHeader(), &QHeaderView::customContextMenuRequested);
connect(m_frozen_table_view->verticalHeader(), &QHeaderView::customContextMenuRequested, verticalHeader(), &QHeaderView::customContextMenuRequested);
connect(m_frozen_table_view, &ExtendedTableWidget::openFileFromDropEvent, this, &ExtendedTableWidget::openFileFromDropEvent);
connect(m_frozen_table_view, &ExtendedTableWidget::selectedRowsToBeDeleted, this, &ExtendedTableWidget::selectedRowsToBeDeleted);
connect(m_frozen_table_view->filterHeader(), &FilterTableHeader::filterChanged, filterHeader(), &FilterTableHeader::filterChanged);
connect(m_frozen_table_view->filterHeader(), &FilterTableHeader::addCondFormat, filterHeader(), &FilterTableHeader::addCondFormat);
connect(m_frozen_table_view->filterHeader(), &FilterTableHeader::allCondFormatsCleared, filterHeader(), &FilterTableHeader::allCondFormatsCleared);
connect(m_frozen_table_view->filterHeader(), &FilterTableHeader::condFormatsEdited, filterHeader(), &FilterTableHeader::condFormatsEdited);
connect(m_frozen_table_view, &ExtendedTableWidget::editCondFormats, this, &ExtendedTableWidget::editCondFormats);
connect(m_frozen_table_view, &ExtendedTableWidget::dataAboutToBeEdited, this, &ExtendedTableWidget::dataAboutToBeEdited);
connect(m_frozen_table_view, &ExtendedTableWidget::foreignKeyClicked, this, &ExtendedTableWidget::foreignKeyClicked);
connect(m_frozen_table_view, &ExtendedTableWidget::currentIndexChanged, this, &ExtendedTableWidget::currentIndexChanged);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) && QT_VERSION < QT_VERSION_CHECK(5, 12, 3)
// This work arounds QTBUG-73721 and it is applied only for the affected version range.
setWordWrap(false);
#endif
}
ExtendedTableWidget::~ExtendedTableWidget()
{
delete m_frozen_table_view;
}
void ExtendedTableWidget::setModel(QAbstractItemModel* item_model)
{
// Set model
QTableView::setModel(item_model);
// Set up frozen table view widget
if(item_model)
setFrozenColumns(m_frozen_column_count);
else
m_frozen_table_view->hide();
}
void ExtendedTableWidget::reloadSettings()
{
// Set the new font and font size
// We only get the font here to get its metrics. The actual font for the view is set in the model
QFont dataBrowserFont(Settings::getValue("databrowser", "font").toString());
dataBrowserFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());
setFont(dataBrowserFont);
// Set new default row height depending on the font size
verticalHeader()->setDefaultSectionSize(verticalHeader()->fontMetrics().height()+10);
QFontMetrics fontMetrics(dataBrowserFont);
verticalHeader()->setDefaultSectionSize(fontMetrics.height()+10);
if(m_frozen_table_view)
m_frozen_table_view->reloadSettings();
}
void ExtendedTableWidget::copyMimeData(const QModelIndexList& fromIndices, QMimeData* mimeData, const bool withHeaders, const bool inSQL)
@@ -872,13 +934,21 @@ void ExtendedTableWidget::updateGeometries()
// Call the parent implementation first - it does most of the actual logic
QTableView::updateGeometries();
// Update frozen columns view too
if(m_frozen_table_view)
m_frozen_table_view->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->rowCount() - numVisibleRows() + 1);
if(m_frozen_table_view)
m_frozen_table_view->verticalScrollBar()->setMaximum(verticalScrollBar()->maximum());
}
}
}
@@ -1120,3 +1190,125 @@ void ExtendedTableWidget::setToNull(const QModelIndexList& indices)
return;
}
}
void ExtendedTableWidget::setFrozenColumns(size_t count)
{
if(!m_frozen_table_view)
return;
m_frozen_column_count = count;
// Set up frozen table view widget
m_frozen_table_view->setModel(model());
m_frozen_table_view->setSelectionModel(selectionModel());
// Only show frozen columns in extra table view and copy column widths
m_frozen_table_view->horizontalHeader()->blockSignals(true); // Signals need to be blocked because hiding a column would emit resizedSection
for(size_t col=0;col<static_cast<size_t>(model()->columnCount());++col)
m_frozen_table_view->setColumnHidden(static_cast<int>(col), col >= count);
m_frozen_table_view->horizontalHeader()->blockSignals(false);
for(int col=0;col<static_cast<int>(count);++col)
m_frozen_table_view->setColumnWidth(col, columnWidth(col));
updateFrozenTableGeometry();
// Only show extra table view when there are frozen columns to see
if(count)
m_frozen_table_view->show();
else
m_frozen_table_view->hide();
}
void ExtendedTableWidget::generateFilters(size_t number, bool show_rowid)
{
m_tableHeader->generateFilters(number, m_frozen_column_count);
if(m_frozen_table_view)
{
size_t frozen_columns = std::min(m_frozen_column_count, number);
m_frozen_table_view->m_tableHeader->generateFilters(frozen_columns, show_rowid ? 0 : 1);
}
}
void ExtendedTableWidget::updateSectionWidth(int logicalIndex, int /* oldSize */, int newSize)
{
if(!m_frozen_table_view)
return;
if(logicalIndex < static_cast<int>(m_frozen_column_count))
{
m_frozen_table_view->setColumnWidth(logicalIndex, newSize);
setColumnWidth(logicalIndex, newSize);
updateFrozenTableGeometry();
}
}
void ExtendedTableWidget::updateSectionHeight(int logicalIndex, int /* oldSize */, int newSize)
{
if(!m_frozen_table_view)
return;
m_frozen_table_view->setRowHeight(logicalIndex, newSize);
}
void ExtendedTableWidget::resizeEvent(QResizeEvent* event)
{
QTableView::resizeEvent(event);
updateFrozenTableGeometry();
}
QModelIndex ExtendedTableWidget::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);
if(!m_frozen_table_view)
return current;
int width = 0;
for(int i=0;i<static_cast<int>(m_frozen_column_count);i++)
width += m_frozen_table_view->columnWidth(i);
if(cursorAction == MoveLeft && current.column() > 0 && visualRect(current).topLeft().x() < width)
{
const int newValue = horizontalScrollBar()->value() + visualRect(current).topLeft().x() - width;
horizontalScrollBar()->setValue(newValue);
}
return current;
}
void ExtendedTableWidget::scrollTo(const QModelIndex& index, ScrollHint hint)
{
if(index.column() >= static_cast<int>(m_frozen_column_count))
QTableView::scrollTo(index, hint);
}
void ExtendedTableWidget::updateFrozenTableGeometry()
{
if(!m_frozen_table_view)
return;
int width = 0;
for(int i=0;i<static_cast<int>(m_frozen_column_count);i++)
{
if(!isColumnHidden(i))
width += columnWidth(i);
}
m_frozen_table_view->setGeometry(verticalHeader()->width() + frameWidth(),
frameWidth(),
width,
viewport()->height() + horizontalHeader()->height());
}
void ExtendedTableWidget::setEditTriggers(QAbstractItemView::EditTriggers editTriggers)
{
QTableView::setEditTriggers(editTriggers);
if(m_frozen_table_view)
m_frozen_table_view->setEditTriggers(editTriggers);
}
void ExtendedTableWidget::setFilter(size_t column, const QString& value)
{
filterHeader()->setFilter(column, value);
if(m_frozen_table_view)
m_frozen_table_view->filterHeader()->setFilter(column, value);
}

View File

@@ -51,8 +51,10 @@ class ExtendedTableWidget : public QTableView
public:
explicit ExtendedTableWidget(QWidget* parent = nullptr);
~ExtendedTableWidget() override;
FilterTableHeader* filterHeader() { return m_tableHeader; }
void generateFilters(size_t number, bool show_rowid);
public:
// Get set of selected columns (all cells in column has to be selected)
@@ -64,12 +66,19 @@ public:
void sortByColumns(const std::vector<sqlb::SortedColumn>& columns);
void setFrozenColumns(size_t count);
void setModel(QAbstractItemModel* item_model) override;
void setEditTriggers(QAbstractItemView::EditTriggers editTriggers);
public slots:
void reloadSettings();
void selectTableLine(int lineToSelect);
void selectTableLines(int firstLine, int count);
void selectAll() override;
void openPrintDialog();
void setFilter(size_t column, const QString& value);
signals:
void foreignKeyClicked(const sqlb::ObjectIdentifier& table, const std::string& column, const QByteArray& value);
@@ -95,9 +104,15 @@ private:
static std::vector<std::vector<QByteArray>> m_buffer;
static QString m_generatorStamp;
ExtendedTableWidget* m_frozen_table_view;
size_t m_frozen_column_count;
void updateFrozenTableGeometry();
private slots:
void vscrollbarChanged(int value);
void cellClicked(const QModelIndex& index);
void updateSectionWidth(int logicalIndex, int oldSize, int newSize);
void updateSectionHeight(int logicalIndex, int oldSize, int newSize);
protected:
void keyPressEvent(QKeyEvent* event) override;
@@ -107,6 +122,10 @@ protected:
void dropEvent(QDropEvent* event) override;
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
void resizeEvent(QResizeEvent* event) override;
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
void scrollTo(const QModelIndex& index, ScrollHint hint = EnsureVisible) override;
FilterTableHeader* m_tableHeader;
QMenu* m_contextMenu;
ExtendedTableWidgetEditorDelegate* m_editorDelegate;

View File

@@ -29,7 +29,7 @@ FilterTableHeader::FilterTableHeader(QTableView* parent) :
setContextMenuPolicy(Qt::CustomContextMenu);
}
void FilterTableHeader::generateFilters(size_t number, bool showFirst)
void FilterTableHeader::generateFilters(size_t number, size_t number_of_hidden_filters)
{
// Delete all the current filter widgets
qDeleteAll(filterWidgets);
@@ -39,10 +39,7 @@ void FilterTableHeader::generateFilters(size_t number, bool showFirst)
for(size_t i=0;i < number; ++i)
{
FilterLineEdit* l = new FilterLineEdit(this, &filterWidgets, i);
if(!showFirst && i == 0) // This hides the first input widget which belongs to the hidden rowid column
l->setVisible(false);
else
l->setVisible(true);
l->setVisible(i >= number_of_hidden_filters);
connect(l, &FilterLineEdit::delayedTextChanged, this, &FilterTableHeader::inputChanged);
connect(l, &FilterLineEdit::addFilterAsCondFormat, this, &FilterTableHeader::addFilterAsCondFormat);
connect(l, &FilterLineEdit::clearAllCondFormats, this, &FilterTableHeader::clearAllCondFormats);
@@ -51,7 +48,7 @@ void FilterTableHeader::generateFilters(size_t number, bool showFirst)
}
// Position them correctly
adjustPositions();
updateGeometries();
}
QSize FilterTableHeader::sizeHint() const
@@ -128,3 +125,8 @@ void FilterTableHeader::setFilter(size_t column, const QString& value)
if(column < filterWidgets.size())
filterWidgets.at(column)->setText(value);
}
QString FilterTableHeader::filterValue(size_t column) const
{
return filterWidgets[column]->text();
}

View File

@@ -15,9 +15,10 @@ public:
explicit FilterTableHeader(QTableView* parent = nullptr);
QSize sizeHint() const override;
bool hasFilters() const {return (filterWidgets.size() > 0);}
QString filterValue(size_t column) const;
public slots:
void generateFilters(size_t number, bool showFirst = false);
void generateFilters(size_t number, size_t number_of_hidden_filters = 1);
void adjustPositions();
void clearFilters();
void setFilter(size_t column, const QString& value);

View File

@@ -2432,6 +2432,8 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlS
settings.encoding = xml.attributes().value("encoding").toString();
settings.plotXAxis = xml.attributes().value("plot_x_axis").toString();
settings.unlockViewPk = xml.attributes().value("unlock_view_pk").toString();
if(xml.attributes().hasAttribute("freeze_columns"))
settings.frozenColumns = xml.attributes().value("freeze_columns").toUInt();
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "table") {
if(xml.name() == "sort")
@@ -2820,6 +2822,7 @@ static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, Q
xml.writeAttribute("encoding", object.encoding);
xml.writeAttribute("plot_x_axis", object.plotXAxis);
xml.writeAttribute("unlock_view_pk", object.unlockViewPk);
xml.writeAttribute("freeze_columns", QString::number(object.frozenColumns));
xml.writeStartElement("sort");
for(const auto& column : object.sortColumns)

View File

@@ -56,6 +56,7 @@ TableBrowser::TableBrowser(DBBrowserDB* _db, QWidget* parent) :
popupHeaderMenu = new QMenu(this);
popupHeaderMenu->addAction(ui->actionShowRowidColumn);
popupHeaderMenu->addAction(ui->actionFreezeColumns);
popupHeaderMenu->addAction(ui->actionHideColumns);
popupHeaderMenu->addAction(ui->actionShowAllColumns);
popupHeaderMenu->addAction(ui->actionSelectColumn);
@@ -68,7 +69,13 @@ TableBrowser::TableBrowser(DBBrowserDB* _db, QWidget* parent) :
connect(ui->actionSelectColumn, &QAction::triggered, [this]() {
ui->dataTable->selectColumn(ui->actionBrowseTableEditDisplayFormat->property("clicked_column").toInt());
});
});
connect(ui->actionFreezeColumns, &QAction::triggered, [this](bool checked) {
if(checked)
freezeColumns(ui->actionBrowseTableEditDisplayFormat->property("clicked_column").toUInt() + 1);
else
freezeColumns(0);
});
// Set up shortcuts
QShortcut* dittoRecordShortcut = new QShortcut(QKeySequence("Ctrl+\""), this);
@@ -728,7 +735,7 @@ void TableBrowser::updateRecordsetLabel()
generateFilters();
ui->dataTable->adjustSize();
} else if(!needs_filters && header->hasFilters()) {
header->generateFilters(0);
ui->dataTable->generateFilters(0, false);
}
}
}
@@ -836,6 +843,9 @@ void TableBrowser::applyViewportSettings(const BrowseDataTableSettings& storedDa
ui->actionUnlockViewEditing->setVisible(true);
ui->actionShowRowidColumn->setVisible(false);
}
// Frozen columns
freezeColumns(storedData.frozenColumns);
}
void TableBrowser::enableEditing(bool enable_edit)
@@ -850,8 +860,8 @@ void TableBrowser::enableEditing(bool enable_edit)
void TableBrowser::showRowidColumn(bool show)
{
// Block all signals from the horizontal header. Otherwise the QHeaderView::sectionResized signal causes us trouble
ui->dataTable->horizontalHeader()->blockSignals(true);
// Disconnect the resized signal from the horizontal header. Otherwise it's resetting the automatic column widths
disconnect(ui->dataTable->horizontalHeader(), &QHeaderView::sectionResized, this, &TableBrowser::updateColumnWidth);
// WORKAROUND
// Set the opposite hidden/visible status of what we actually want for the rowid column. This is to work around a Qt bug which
@@ -864,6 +874,8 @@ void TableBrowser::showRowidColumn(bool show)
// Show/hide rowid column
ui->dataTable->setColumnHidden(0, !show);
if(show)
ui->dataTable->setColumnWidth(0, ui->dataTable->horizontalHeader()->defaultSectionSize());
// Update checked status of the popup menu action
ui->actionShowRowidColumn->setChecked(show);
@@ -878,25 +890,43 @@ void TableBrowser::showRowidColumn(bool show)
// Update the filter row
generateFilters();
// Re-enable signals
ui->dataTable->horizontalHeader()->blockSignals(false);
// Re-enable signal
connect(ui->dataTable->horizontalHeader(), &QHeaderView::sectionResized, this, &TableBrowser::updateColumnWidth);
ui->dataTable->update();
}
void TableBrowser::freezeColumns(size_t columns)
{
// Update checked status of the popup menu action
ui->actionFreezeColumns->setChecked(columns != 0);
// Save settings for this table
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
if (m_settings[current_table].frozenColumns != columns) {
emit projectModified();
m_settings[current_table].frozenColumns = columns;
}
// Apply settings
ui->dataTable->horizontalHeader()->blockSignals(true);
ui->dataTable->setFrozenColumns(columns);
generateFilters();
ui->dataTable->horizontalHeader()->blockSignals(false);
}
void TableBrowser::generateFilters()
{
// Generate a new row of filter line edits
const auto& settings = m_settings[currentlyBrowsedTableName()];
qobject_cast<FilterTableHeader*>(ui->dataTable->horizontalHeader())->generateFilters(static_cast<size_t>(m_model->columnCount()),
settings.showRowid);
ui->dataTable->generateFilters(static_cast<size_t>(m_model->columnCount()), settings.showRowid);
// Apply the stored filter strings to the new row of line edits
// Set filters blocking signals for this since the filter is already applied to the browse table model
FilterTableHeader* filterHeader = qobject_cast<FilterTableHeader*>(ui->dataTable->horizontalHeader());
bool oldState = filterHeader->blockSignals(true);
for(auto filterIt=settings.filterValues.cbegin();filterIt!=settings.filterValues.cend();++filterIt)
filterHeader->setFilter(static_cast<size_t>(filterIt->first), filterIt->second);
ui->dataTable->setFilter(static_cast<size_t>(filterIt->first), filterIt->second);
filterHeader->blockSignals(oldState);
}

View File

@@ -40,11 +40,13 @@ struct BrowseDataTableSettings
QString unlockViewPk;
std::map<int, bool> hiddenColumns;
std::vector<QString> globalFilters;
size_t frozenColumns;
BrowseDataTableSettings() :
showRowid(false),
plotYAxes({std::map<QString, PlotDock::PlotSettings>(), std::map<QString, PlotDock::PlotSettings>()}),
unlockViewPk("_rowid_")
unlockViewPk("_rowid_"),
frozenColumns(0)
{
}
};
@@ -105,6 +107,7 @@ private slots:
void editCondFormats(size_t column);
void enableEditing(bool enable_edit);
void showRowidColumn(bool show);
void freezeColumns(size_t columns);
void unlockViewEditing(bool unlock, QString pk = QString());
void hideColumns(int column = -1, bool hide = true);
void on_actionShowAllColumns_triggered();

View File

@@ -1042,6 +1042,17 @@
<string>Replace text in cells</string>
</property>
</action>
<action name="actionFreezeColumns">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Freeze columns</string>
</property>
<property name="toolTip">
<string>Make all columns from the first column up to this column not move when scrolling horizontally</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>