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
2 changes: 1 addition & 1 deletion src/GlobalFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ std::vector<QString> tokenize(const QString& value)

void load(std::vector<QString>& filters, QXmlStreamReader& xml)
{
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("global_filter"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("global_filter"))
{
if(xml.name() == QT_UNICODE_LITERAL("filter"))
{
Expand Down
38 changes: 23 additions & 15 deletions src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2744,10 +2744,10 @@ static void loadCondFormatMap(BrowseDataTableSettings::CondFormatMap& condFormat
// using auto solves Qt 5/6 difference, QStringRef vs QStringView
const auto name = xml.name();

while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != name) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != name) {
if (xml.name() == QT_UNICODE_LITERAL("column")) {
size_t index = xml.attributes().value("index").toUInt();
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("column")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("column")) {
if(xml.name() == QT_UNICODE_LITERAL("format")) {
QFont font;
if (xml.attributes().hasAttribute("font"))
Expand Down Expand Up @@ -2781,10 +2781,10 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb:
if(xml.attributes().hasAttribute("freeze_columns"))
settings.frozenColumns = xml.attributes().value("freeze_columns").toUInt();

while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("table")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("table")) {
if(xml.name() == QT_UNICODE_LITERAL("sort"))
{
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("sort"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("sort"))
{
if(xml.name() == QT_UNICODE_LITERAL("column"))
{
Expand All @@ -2796,15 +2796,15 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb:
}
}
} else if(xml.name() == QT_UNICODE_LITERAL("column_widths")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("column_widths")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("column_widths")) {
if (xml.name() == QT_UNICODE_LITERAL("column")) {
int index = xml.attributes().value("index").toInt();
settings.columnWidths[index] = xml.attributes().value("value").toInt();
xml.skipCurrentElement();
}
}
} else if(xml.name() == QT_UNICODE_LITERAL("filter_values")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("filter_values")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("filter_values")) {
if (xml.name() == QT_UNICODE_LITERAL("column")) {
size_t index = xml.attributes().value("index").toUInt();
QString value = xml.attributes().value("value").toString();
Expand All @@ -2818,23 +2818,23 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb:
} else if(xml.name() == QT_UNICODE_LITERAL("row_id_formats")) {
loadCondFormatMap(settings.rowIdFormats, xml, settings.encoding);
} else if(xml.name() == QT_UNICODE_LITERAL("display_formats")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("display_formats")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("display_formats")) {
if (xml.name() == QT_UNICODE_LITERAL("column")) {
size_t index = xml.attributes().value("index").toUInt();
settings.displayFormats[index] = xml.attributes().value("value").toString();
xml.skipCurrentElement();
}
}
} else if(xml.name() == QT_UNICODE_LITERAL("hidden_columns")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("hidden_columns")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("hidden_columns")) {
if (xml.name() == QT_UNICODE_LITERAL("column")) {
int index = xml.attributes().value("index").toInt();
settings.hiddenColumns[index] = xml.attributes().value("value").toInt();
xml.skipCurrentElement();
}
}
} else if(xml.name() == QT_UNICODE_LITERAL("plot_y_axes")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("plot_y_axes")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("plot_y_axes")) {
QString y1AxisName;
QString y2AxisName;
PlotDock::PlotSettings y1AxisSettings;
Expand Down Expand Up @@ -2935,7 +2935,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
db.setPragma("synchronous", xml.attributes().value("synchronous").toString());
loadPragmas();
} else if(xml.name() == QT_UNICODE_LITERAL("attached")) {
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("attached"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("attached"))
{
if(xml.name() == QT_UNICODE_LITERAL("db"))
{
Expand All @@ -2945,7 +2945,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
}
} else if(xml.name() == QT_UNICODE_LITERAL("window")) {
// Window settings
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("window"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("window"))
{
if(xml.name() == QT_UNICODE_LITERAL("main_tabs")) {
projectRestoreTabs = xml.attributes().value("open").toString();
Expand All @@ -2960,7 +2960,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
}
} else if(xml.name() == QT_UNICODE_LITERAL("tab_structure")) {
// Database Structure tab settings
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("tab_structure"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("tab_structure"))
{
if(xml.name() == QT_UNICODE_LITERAL("column_width"))
{
Expand All @@ -2987,7 +2987,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
delete d;

// Browse Data tab settings
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("tab_browse"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("tab_browse"))
{
if(xml.name() == QT_UNICODE_LITERAL("current_table"))
{
Expand Down Expand Up @@ -3064,7 +3064,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
xml.skipCurrentElement();
} else if(xml.name() == QT_UNICODE_LITERAL("browse_table_settings")) {

while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("browse_table_settings")) {
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("browse_table_settings")) {
if (xml.name() == QT_UNICODE_LITERAL("table")) {

sqlb::ObjectIdentifier tableIdentifier =
Expand All @@ -3091,7 +3091,7 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read
closeSqlTab(i, true);

// Execute SQL tab data
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != QT_UNICODE_LITERAL("tab_sql"))
while(xml.readNext() != QXmlStreamReader::EndElement && !xml.atEnd() && xml.name() != QT_UNICODE_LITERAL("tab_sql"))
{
if(xml.name() == QT_UNICODE_LITERAL("sql"))
{
Expand Down Expand Up @@ -3121,6 +3121,14 @@ MainWindow::LoadAttempResult MainWindow::loadProject(QString filename, bool read

file.close();

// The parts of a damaged project which could be read have been restored, but tell the user that there is more
if(xml.hasError())
{
QMessageBox::warning(this, qApp->applicationName(),
tr("The project file could not be read completely, it is probably damaged. Only part of it has been loaded.\n"
"Reason: %1 (line %2, column %3)").arg(xml.errorString()).arg(xml.lineNumber()).arg(xml.columnNumber()));
}

if (projectRestoreIdx != -1 && !projectRestoreTabs.isEmpty()) {
ui->mainTab->blockSignals(true);
restoreOpenTabs(projectRestoreTabs);
Expand Down
4 changes: 3 additions & 1 deletion src/PlotDock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,9 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett
// Warn user if not all data has been fetched and hint about the button for loading all the data
if (model && (model->rowCountAvailable() != SqliteTableModel::RowCount::Complete || !model->isCacheComplete())) {
ui->buttonLoadAllData->setEnabled(true);
ui->buttonLoadAllData->setStyleSheet("QToolButton {color: white; background-color: rgb(255, 102, 102)}");
// The style sheet needs to set a border, too. Otherwise the button is still drawn by the style of the application, and some
// of them (e.g. the ones from certain platform themes) don't use the background colour of the style sheet in that case.
ui->buttonLoadAllData->setStyleSheet("QToolButton {color: white; background-color: rgb(255, 102, 102); border: 1px solid rgb(204, 51, 51); border-radius: 3px; padding: 2px}");
ui->buttonLoadAllData->setToolTip(tr("Load all data and redraw plot.\n"
"Warning: not all data has been fetched from the table yet due to the partial fetch mechanism."));
} else {
Expand Down
10 changes: 9 additions & 1 deletion src/RowLoader.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include <QDebug>
#include <QRegularExpression>

#include <algorithm>
#include <limits>

#include "RowLoader.h"
#include "sqlite.h"

Expand Down Expand Up @@ -105,7 +108,12 @@ int RowLoader::countRows() const
if(sqlite3_prepare_v2(pDb.get(), utf8Query, utf8Query.size(), &stmt, nullptr) == SQLITE_OK)
{
if(sqlite3_step(stmt) == SQLITE_ROW)
retval = sqlite3_column_int(stmt, 0);
{
// The row count is passed on as an int. Clamp it instead of reading it as an int because that would wrap
// around for tables with more rows than an int can hold, resulting in a negative or just plain wrong count.
const sqlite3_int64 count = sqlite3_column_int64(stmt, 0);
retval = static_cast<int>(std::min<sqlite3_int64>(count, std::numeric_limits<int>::max()));
}
sqlite3_finalize(stmt);
} else {
qWarning() << "Count query failed: " << countQuery;
Expand Down
4 changes: 3 additions & 1 deletion src/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ bool Settings::isVaildSettingsFile(const QString& userSettingsFile)

if(file->open(QIODevice::ReadOnly | QIODevice::Text))
{
if(file->exists() &&
// A file without any content yet is fine, too. It is a settings file which just doesn't contain any settings,
// just as if the file did not exist.
if(file->exists() && file->size() > 0 &&
QString::compare(QString("[%General]\n"), file->readLine(), Qt::CaseInsensitive) != 0)
isNormalUserSettingsFile = false;
}
Expand Down
122 changes: 122 additions & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,125 @@ 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 row loader

set(TESTROWCOUNT_SRC
TestRowCount.cpp
../RowLoader.cpp
)

set(TESTROWCOUNT_HDR
TestRowCount.h
../RowLoader.h
../RowCache.h
)

add_executable(test-row-count ${TESTROWCOUNT_HDR} ${TESTROWCOUNT_SRC})
target_link_libraries(test-row-count ${QT_MAJOR}::Test ${LIBSQLITE_NAME})
add_test(NAME test-row-count COMMAND $<TARGET_FILE:test-row-count>)
if(WIN32)
set(_rl_extra_paths "")
foreach(_p IN LISTS CMAKE_PREFIX_PATH)
list(APPEND _rl_extra_paths "${_p}" "${_p}/bin")
# OpenSSL 3 Universal installer layout: bin/x86, bin/x64
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
list(APPEND _rl_extra_paths "${_p}/bin/x64")
else()
list(APPEND _rl_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 ";" "\;" TESTROWCOUNT_PATH "${_rl_extra_paths};$ENV{PATH}")
set_tests_properties(test-row-count PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;PATH=${TESTROWCOUNT_PATH}")
else()
set_tests_properties(test-row-count PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
endif()

# test settings

set(TESTSETTINGS_SRC
TestSettings.cpp
../Settings.cpp
)

set(TESTSETTINGS_HDR
TestSettings.h
../Settings.h
)

add_executable(test-settings ${TESTSETTINGS_HDR} ${TESTSETTINGS_SRC})
target_link_libraries(test-settings ${QT_MAJOR}::Test ${QT_MAJOR}::Widgets)
add_test(NAME test-settings COMMAND $<TARGET_FILE:test-settings>)
set_tests_properties(test-settings PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")

# test plot dock

set(TESTPLOTDOCK_SRC
TestPlotDock.cpp
../PlotDock.cpp
../Palette.cpp
../FileDialog.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(TESTPLOTDOCK_HDR
TestPlotDock.h
../PlotDock.h
../Palette.h
../FileDialog.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-plot-dock ${TESTPLOTDOCK_HDR} ${TESTPLOTDOCK_SRC} ../CipherDialog.ui ../PlotDock.ui)
target_include_directories(test-plot-dock SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/libs/json)
if(QT_MAJOR STREQUAL "Qt6")
set_target_properties(test-plot-dock PROPERTIES AUTOUIC_OPTIONS "--connections=string")
endif()
target_link_libraries(test-plot-dock ${QT_MAJOR}::Test ${QT_MAJOR}::Widgets ${QT_MAJOR}::Concurrent ${QT_MAJOR}::PrintSupport QCustomPlot::QCustomPlot ${LIBSQLITE_NAME} ${QT5_COMPAT})
add_test(NAME test-plot-dock COMMAND $<TARGET_FILE:test-plot-dock>)
if(WIN32)
set(_pd_extra_paths "")
foreach(_p IN LISTS CMAKE_PREFIX_PATH)
list(APPEND _pd_extra_paths "${_p}" "${_p}/bin")
# OpenSSL 3 Universal installer layout: bin/x86, bin/x64
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
list(APPEND _pd_extra_paths "${_p}/bin/x64")
else()
list(APPEND _pd_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 ";" "\;" TESTPLOTDOCK_PATH "${_pd_extra_paths};$ENV{PATH}")
set_tests_properties(test-plot-dock PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;PATH=${TESTPLOTDOCK_PATH}")
else()
set_tests_properties(test-plot-dock PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
endif()
Loading
Loading