Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/ExtendedTableWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <QShortcut>
#include <QProgressDialog>

#include <algorithm>
#include <limits>

using BufferRow = std::vector<QByteArray>;
Expand Down Expand Up @@ -433,15 +434,34 @@ void ExtendedTableWidget::reloadSettings()
QFont dataBrowserFont(Settings::getValue("databrowser", "font").toString());
dataBrowserFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());

// Set new default row height depending on the font size
// Set new default row height depending on the font size and the configured padding
QFontMetrics fontMetrics(dataBrowserFont);
verticalHeader()->setDefaultSectionSize(fontMetrics.height()+10);
const int padding = std::max(0, Settings::getValue("databrowser", "row_padding").toInt());
// Qt doesn't allow the default row height to be lower than the minimum row height and, unless told otherwise, the latter depends on
// the font of the row header. So set it to the height of a line of text, otherwise a low padding would not make the rows any smaller.
verticalHeader()->setMinimumSectionSize(fontMetrics.height());
verticalHeader()->setDefaultSectionSize(fontMetrics.height() + padding);
if(m_frozen_table_view)
m_frozen_table_view->reloadSettings();

setWordWrap(Settings::getValue("databrowser", "cell_word_wrap").toBool());
}

void ExtendedTableWidget::fitColumnsToContents(int max_width)
{
if(!model())
return;

resizeColumnsToContents();

// Hidden columns have a width of 0 and thus are left alone here
for(int col = 0; col < model()->columnCount(); ++col)
{
if(columnWidth(col) > max_width)
setColumnWidth(col, max_width);
}
}

bool ExtendedTableWidget::copyMimeData(const QModelIndexList& fromIndices, QMimeData* mimeData, const bool withHeaders, const bool inSQL)
{
QModelIndexList indices = fromIndices;
Expand Down
3 changes: 3 additions & 0 deletions src/ExtendedTableWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class ExtendedTableWidget : public QTableView

void setFrozenColumns(size_t count);

// Resize all visible columns to fit their contents but make sure none of them gets wider than max_width
void fitColumnsToContents(int max_width);

void setModel(QAbstractItemModel* item_model) override;

void setEditTriggers(QAbstractItemView::EditTriggers editTriggers);
Expand Down
2 changes: 2 additions & 0 deletions src/PreferencesDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ void PreferencesDialog::loadSettings()
ui->spinCompleteThreshold->setValue(Settings::getValue("databrowser", "complete_threshold").toInt());
ui->checkShowImagesInline->setChecked(Settings::getValue("databrowser", "image_preview").toBool());
ui->checkCellWordWrap->setChecked(Settings::getValue("databrowser", "cell_word_wrap").toBool());
ui->spinRowPadding->setValue(Settings::getValue("databrowser", "row_padding").toInt());
ui->txtNull->setText(Settings::getValue("databrowser", "null_text").toString());
ui->txtBlob->setText(Settings::getValue("databrowser", "blob_text").toString());
ui->editFilterEscape->setText(Settings::getValue("databrowser", "filter_escape").toString());
Expand Down Expand Up @@ -209,6 +210,7 @@ void PreferencesDialog::saveSettings(bool accept)
Settings::setValue("databrowser", "fontsize", ui->spinDataBrowserFontSize->value());
Settings::setValue("databrowser", "image_preview", ui->checkShowImagesInline->isChecked());
Settings::setValue("databrowser", "cell_word_wrap", ui->checkCellWordWrap->isChecked());
Settings::setValue("databrowser", "row_padding", ui->spinRowPadding->value());
saveColorSetting(ui->fr_null_fg, "null_fg");
saveColorSetting(ui->fr_null_bg, "null_bg");
saveColorSetting(ui->fr_reg_fg, "reg_fg");
Expand Down
21 changes: 21 additions & 0 deletions src/PreferencesDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,26 @@ Can be set to 0 for disabling completion.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelRowPadding">
<property name="text">
<string>Row padding (px)</string>
</property>
<property name="buddy">
<cstring>spinRowPadding</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="spinRowPadding">
<property name="toolTip">
<string>The height of a row is the height of the font plus this many pixels. Lower it to fit more rows on the screen.</string>
</property>
<property name="maximum">
<number>50</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down Expand Up @@ -2071,6 +2091,7 @@ Can be set to 0 for disabling completion.</string>
<tabstop>spinCompleteThreshold</tabstop>
<tabstop>checkShowImagesInline</tabstop>
<tabstop>checkCellWordWrap</tabstop>
<tabstop>spinRowPadding</tabstop>
<tabstop>spinFilterDelay</tabstop>
<tabstop>editFilterEscape</tabstop>
<tabstop>treeSyntaxHighlighting</tabstop>
Expand Down
2 changes: 2 additions & 0 deletions src/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string&
return false;
if(name == "cell_word_wrap")
return true;
if(name == "row_padding")
return 10;
if(name == "indent_compact")
return false;
if (name == "sort_keys")
Expand Down
14 changes: 8 additions & 6 deletions src/TableBrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
std::map<sqlb::ObjectIdentifier, BrowseDataTableSettings> TableBrowser::m_settings;
QString TableBrowser::m_defaultEncoding;

// When columns are automatically resized to fit their contents they don't get wider than this
static const int maxAutoColumnWidth = 300;

TableBrowser::TableBrowser(DBBrowserDB* _db, QWidget* parent) :
QWidget(parent),
ui(new Ui::TableBrowser),
Expand Down Expand Up @@ -64,6 +67,7 @@ TableBrowser::TableBrowser(DBBrowserDB* _db, QWidget* parent) :
popupHeaderMenu->addAction(ui->actionHideColumns);
popupHeaderMenu->addAction(ui->actionShowAllColumns);
popupHeaderMenu->addAction(ui->actionSelectColumn);
popupHeaderMenu->addAction(ui->actionResizeColumnsToContents);
popupHeaderMenu->addSeparator();
popupHeaderMenu->addAction(ui->actionUnlockViewEditing);
popupHeaderMenu->addAction(ui->actionBrowseTableEditDisplayFormat);
Expand All @@ -76,6 +80,9 @@ TableBrowser::TableBrowser(DBBrowserDB* _db, QWidget* parent) :
connect(ui->actionSelectColumn, &QAction::triggered, this, [this]() {
ui->dataTable->selectColumn(ui->actionBrowseTableEditDisplayFormat->property("clicked_column").toInt());
});
connect(ui->actionResizeColumnsToContents, &QAction::triggered, this, [this]() {
ui->dataTable->fitColumnsToContents(maxAutoColumnWidth);
});
connect(ui->actionFreezeColumns, &QAction::triggered, this, [this](bool checked) {
if(checked)
freezeColumns(ui->actionBrowseTableEditDisplayFormat->property("clicked_column").toUInt() + 1);
Expand Down Expand Up @@ -1831,11 +1838,6 @@ void TableBrowser::fetchedData()

if (m_settings[currentlyBrowsedTableName()].columnWidths.empty()) {
// Set column widths according to their contents but make sure they don't exceed a certain size
ui->dataTable->resizeColumnsToContents();
for(int i = 0; i < m_model->columnCount(); i++)
{
if(ui->dataTable->columnWidth(i) > 300)
ui->dataTable->setColumnWidth(i, 300);
}
ui->dataTable->fitColumnsToContents(maxAutoColumnWidth);
}
}
8 changes: 8 additions & 0 deletions src/TableBrowser.ui
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,14 @@
<string>Show all columns that were hidden</string>
</property>
</action>
<action name="actionResizeColumnsToContents">
<property name="text">
<string>Resize columns to contents</string>
</property>
<property name="toolTip">
<string>Resize all columns to fit their contents</string>
</property>
</action>
<action name="actionSetTableEncoding">
<property name="text">
<string>Set encoding</string>
Expand Down
71 changes: 71 additions & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,74 @@ set(TESTCACHE_HDR
add_executable(test-cache ${TESTCACHE_HDR} ${TESTCACHE_SRC})
target_link_libraries(test-cache ${QT_MAJOR}::Test)
add_test(NAME test-cache COMMAND $<TARGET_FILE:test-cache>)

# test extended table widget

set(TESTEXTENDEDTABLEWIDGET_SRC
TestExtendedTableWidget.cpp
../ExtendedTableWidget.cpp
../FilterTableHeader.cpp
../FilterLineEdit.cpp
../sqlitedb.cpp
../sqlitetablemodel.cpp
../RowLoader.cpp
../CipherDialog.cpp
../CipherSettings.cpp
../CondFormat.cpp
../Data.cpp
../Settings.cpp
../sql/sqlitetypes.cpp
../sql/Query.cpp
../sql/ObjectIdentifier.cpp
../sql/parser/ParserDriver.cpp
../sql/parser/sqlite3_lexer.cpp
../sql/parser/sqlite3_parser.cpp
)

set(TESTEXTENDEDTABLEWIDGET_HDR
TestExtendedTableWidget.h
../ExtendedTableWidget.h
../FilterTableHeader.h
../FilterLineEdit.h
../sqlitedb.h
../sqlitetablemodel.h
../RowLoader.h
../RowCache.h
../CipherDialog.h
../CipherSettings.h
../CondFormat.h
../Data.h
../Settings.h
../sql/sqlitetypes.h
../sql/Query.h
../sql/ObjectIdentifier.h
../sql/parser/ParserDriver.h
../sql/parser/sqlite3_lexer.h
../sql/parser/sqlite3_location.h
../sql/parser/sqlite3_parser.hpp
)

add_executable(test-extended-table-widget ${TESTEXTENDEDTABLEWIDGET_HDR} ${TESTEXTENDEDTABLEWIDGET_SRC} ../CipherDialog.ui)
target_include_directories(test-extended-table-widget SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/libs/json)
if(QT_MAJOR STREQUAL "Qt6")
set_target_properties(test-extended-table-widget PROPERTIES AUTOUIC_OPTIONS "--connections=string")
endif()
target_link_libraries(test-extended-table-widget ${QT_MAJOR}::Test ${QT_MAJOR}::Widgets ${QT_MAJOR}::PrintSupport ${QT_MAJOR}::Concurrent ${LIBSQLITE_NAME} ${QT5_COMPAT})
add_test(NAME test-extended-table-widget COMMAND $<TARGET_FILE:test-extended-table-widget>)
if(WIN32)
set(_etw_extra_paths "")
foreach(_p IN LISTS CMAKE_PREFIX_PATH)
list(APPEND _etw_extra_paths "${_p}" "${_p}/bin")
# OpenSSL 3 Universal installer layout: bin/x86, bin/x64
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
list(APPEND _etw_extra_paths "${_p}/bin/x64")
else()
list(APPEND _etw_extra_paths "${_p}/bin/x86")
endif()
endforeach()
# SQLite and SQLCipher are built as DLLs in the CMake prefix by the Windows CI jobs.
string(REPLACE ";" "\;" TESTEXTENDEDTABLEWIDGET_PATH "${_etw_extra_paths};$ENV{PATH}")
set_tests_properties(test-extended-table-widget PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;PATH=${TESTEXTENDEDTABLEWIDGET_PATH}")
else()
set_tests_properties(test-extended-table-widget PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
endif()
147 changes: 147 additions & 0 deletions src/tests/TestExtendedTableWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "TestExtendedTableWidget.h"

#include "../ExtendedTableWidget.h"
#include "../Settings.h"

#include <QHeaderView>
#include <QStandardItemModel>
#include <QTemporaryDir>
#include <QtTest/QtTest>

QTEST_MAIN(TestExtendedTableWidget)

namespace
{

// Three columns of increasing width: the first one is as narrow as a column can get, the last one is far wider than
// any limit used in the tests
void fillModel(QStandardItemModel& model)
{
model.setHorizontalHeaderLabels({"a", "b", "c"});
for(int row = 0; row < model.rowCount(); ++row)
{
model.setItem(row, 0, new QStandardItem("x"));
model.setItem(row, 1, new QStandardItem(QString(15, 'W')));
model.setItem(row, 2, new QStandardItem(QString(400, 'W')));
}
}

// The default row height the data browser font gets when the given padding is added to it
int expectedRowHeight(int padding)
{
QFont font(Settings::getValue("databrowser", "font").toString());
font.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());
return QFontMetrics(font).height() + padding;
}

// Make every column wider than its contents so a test can tell whether the columns have been resized
void widenColumns(ExtendedTableWidget& table, int width)
{
for(int col = 0; col < table.model()->columnCount(); ++col)
table.setColumnWidth(col, width);
}

}

void TestExtendedTableWidget::cleanup()
{
Settings::clearValue("databrowser", "row_padding");
}

void TestExtendedTableWidget::initTestCase()
{
// Never read or write the settings of the user running the tests
QVERIFY(m_settingsDir.isValid());
Settings::setUserSettingsFile(m_settingsDir.filePath("settings.ini"));
}

void TestExtendedTableWidget::fitColumnsToContentsCapsWideColumns()
{
QStandardItemModel model(3, 3);
fillModel(model);
ExtendedTableWidget table;
table.setModel(&model);

// What Qt itself considers the width of each column when fitting it to its contents
table.resizeColumnsToContents();
const int natural0 = table.columnWidth(0);
const int natural1 = table.columnWidth(1);
const int natural2 = table.columnWidth(2);
if(!(natural0 < natural1 && natural1 + 100 < natural2))
QSKIP("The font of this platform does not give the test columns distinguishable widths");

const int limit = natural1 + 40;
widenColumns(table, natural2 + 100);

table.fitColumnsToContents(limit);

QCOMPARE(table.columnWidth(0), natural0);
QCOMPARE(table.columnWidth(1), natural1);
QCOMPARE(table.columnWidth(2), limit);
}

void TestExtendedTableWidget::fitColumnsToContentsKeepsHiddenColumnsHidden()
{
QStandardItemModel model(3, 3);
fillModel(model);
ExtendedTableWidget table;
table.setModel(&model);

table.resizeColumnsToContents();
const int natural0 = table.columnWidth(0);
const int natural1 = table.columnWidth(1);
widenColumns(table, natural1 + 100);
table.setColumnHidden(2, true);

table.fitColumnsToContents(10000);

QVERIFY(table.isColumnHidden(2));
QCOMPARE(table.columnWidth(2), 0);
QVERIFY(!table.isColumnHidden(0));
QVERIFY(!table.isColumnHidden(1));
QCOMPARE(table.columnWidth(0), natural0);
QCOMPARE(table.columnWidth(1), natural1);
}

void TestExtendedTableWidget::fitColumnsToContentsWithoutModel()
{
ExtendedTableWidget table;
table.fitColumnsToContents(300);
QCOMPARE(table.horizontalHeader()->count(), 0);
}

void TestExtendedTableWidget::rowHeightPaddingDefaultsToCurrentBehaviour()
{
// The rows have always been 10 pixels higher than the font
QCOMPARE(Settings::getValue("databrowser", "row_padding").toInt(), 10);

ExtendedTableWidget table;
table.reloadSettings();
QCOMPARE(table.verticalHeader()->defaultSectionSize(), expectedRowHeight(10));
}

void TestExtendedTableWidget::rowHeightFollowsRowHeightPadding()
{
ExtendedTableWidget table;

Settings::setValue("databrowser", "row_padding", 2, false);
table.reloadSettings();
QCOMPARE(table.verticalHeader()->defaultSectionSize(), expectedRowHeight(2));

Settings::setValue("databrowser", "row_padding", 0, false);
table.reloadSettings();
QCOMPARE(table.verticalHeader()->defaultSectionSize(), expectedRowHeight(0));

Settings::setValue("databrowser", "row_padding", 25, false);
table.reloadSettings();
QCOMPARE(table.verticalHeader()->defaultSectionSize(), expectedRowHeight(25));
}

void TestExtendedTableWidget::rowHeightPaddingIsNeverNegative()
{
// A hand-edited settings file must not be able to shrink the rows below the font height
ExtendedTableWidget table;
Settings::setValue("databrowser", "row_padding", -7, false);
table.reloadSettings();
QCOMPARE(table.verticalHeader()->defaultSectionSize(), expectedRowHeight(0));
}
Loading
Loading