diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 541b9842d..5a23cd7b4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -289,10 +289,6 @@ jobs: # (which is not yet generated) uses a real certificate that will be supplied by Signpath and will be suitable # for signing released versions of the application. # - # For the moment, everything uses "test-signing". Once that's working, we'll use the logic below. - # - # -=-=-=-=-=-=-=-=-=-=- - # # Select "release-signing" policy for things we're going to release and "test-signing" otherwise. # # Currently our main branch for releasing is called "develop", but we'll probably change it to "main" in the @@ -301,11 +297,10 @@ jobs: # We don't do release branches per se, but, before we do a lot of commits for a major release, we'll usually # cut a "stable/" branch for the prior one. # - SIGNPATH_SIGNING_POLICY_SLUG: 'test-signing' -# SIGNPATH_SIGNING_POLICY_SLUG: | -# ${{ (github.ref == 'refs/heads/develop' || -# github.ref == 'refs/heads/main' || -# startsWith(github.ref, 'refs/heads/stable/')) && 'release-signing' || 'test-signing' }} + SIGNPATH_SIGNING_POLICY_SLUG: | + ${{ (github.ref == 'refs/heads/develop' || + github.ref == 'refs/heads/main' || + startsWith(github.ref, 'refs/heads/stable/')) && 'release-signing' || 'test-signing' }} with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '${{ vars.SIGNPATH_ORGANIZATION_ID }}' diff --git a/CHANGES.markdown b/CHANGES.markdown index b04a516fd..10355af83 100644 --- a/CHANGES.markdown +++ b/CHANGES.markdown @@ -23,9 +23,10 @@ Bug fixes for the 4.0.5 release (ie bugs in 4.0.5 are fixed in this 4.0.6 releas * Ingredient inventory edits not saved [832](https://github.com/Brewtarget/brewtarget/issues/832) * Unsatisfied dependency for Brewtarget update in ubuntu 24.01 [840](https://github.com/Brewtarget/brewtarget/issues/840) * Cmake error on Linux Mint 22 Wilma [843](https://github.com/Brewtarget/brewtarget/issues/843) +* Binaries are not signed on Windows [827](https://github.com/Brewtarget/brewtarget/issues/827) ### Release Timestamp -Tue, 1 Oct 2024 04:00:06 +0100 +Wed, 2 Oct 2024 04:00:06 +0100 ## v4.0.5 Bug fixes for the 4.0.4 release (ie bugs in 4.0.4 are fixed in this 4.0.5 release). diff --git a/scripts/buildTool.py b/scripts/buildTool.py index 8d7d2792b..e4c9dcdd4 100755 --- a/scripts/buildTool.py +++ b/scripts/buildTool.py @@ -660,35 +660,37 @@ def installDependencies(): ) ) - # - # Ubuntu 20.04 packages only have Meson 0.53.2, and we need 0.60.0 or later. In this case it means we have to - # install Meson via pip, which is not ideal on Linux. - # - # Specifically, as explained at https://mesonbuild.com/Getting-meson.html#installing-meson-with-pip, although - # using the pip3 install gets a newer version, we have to do the pip install as root (which is normally not - # recommended). If we don't do this, then running `meson install` (or even `sudo meson install`) will barf on - # Linux (because we need to be able to install files into system directories). - # - # So, where a sufficiently recent version of Meson is available in the distro packages (eg - # `sudo apt install meson` on Ubuntu etc) it is much better to install this. Installing via pip is a last - # resort. - # - # The distro ID we get from 'lsb_release -is' will be 'Ubuntu' for all the variants of Ubuntu (eg including - # Kubuntu). Not sure what happens on derivatives such as Linux Mint though. - # - distroName = str( - btUtils.abortOnRunFail(subprocess.run(['lsb_release', '-is'], encoding = "utf-8", capture_output = True)).stdout - ).rstrip() - log.debug('Linux distro: ' + distroName) - if ('Ubuntu' == distroName): - ubuntuRelease = str( - btUtils.abortOnRunFail(subprocess.run(['lsb_release', '-rs'], encoding = "utf-8", capture_output = True)).stdout - ).rstrip() - log.debug('Ubuntu release: ' + ubuntuRelease) - if (Decimal(ubuntuRelease) < Decimal('22.04')): - log.info('Installing newer version of Meson the hard way') - btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'remove', '-y', 'meson'])) - btUtils.abortOnRunFail(subprocess.run(['sudo', 'pip3', 'install', 'meson'])) +### # +### # Commented this out as, as of 2024, we don't support Ubuntu 20.04 any more. +### # +### # Ubuntu 20.04 packages only have Meson 0.53.2, and we need 0.60.0 or later. In this case it means we have to +### # install Meson via pip, which is not ideal on Linux. +### # +### # Specifically, as explained at https://mesonbuild.com/Getting-meson.html#installing-meson-with-pip, although +### # using the pip3 install gets a newer version, we have to do the pip install as root (which is normally not +### # recommended). If we don't do this, then running `meson install` (or even `sudo meson install`) will barf on +### # Linux (because we need to be able to install files into system directories). +### # +### # So, where a sufficiently recent version of Meson is available in the distro packages (eg +### # `sudo apt install meson` on Ubuntu etc) it is much better to install this. Installing via pip is a last +### # resort. +### # +### # The distro ID we get from 'lsb_release -is' will be 'Ubuntu' for all the variants of Ubuntu (eg including +### # Kubuntu). Not sure what happens on derivatives such as Linux Mint though. +### # +### distroName = str( +### btUtils.abortOnRunFail(subprocess.run(['lsb_release', '-is'], encoding = "utf-8", capture_output = True)).stdout +### ).rstrip() +### log.debug('Linux distro: ' + distroName) +### if ('Ubuntu' == distroName): +### ubuntuRelease = str( +### btUtils.abortOnRunFail(subprocess.run(['lsb_release', '-rs'], encoding = "utf-8", capture_output = True)).stdout +### ).rstrip() +### log.debug('Ubuntu release: ' + ubuntuRelease) +### if (Decimal(ubuntuRelease) < Decimal('22.04')): +### log.info('Installing newer version of Meson the hard way') +### btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'remove', '-y', 'meson'])) +### btUtils.abortOnRunFail(subprocess.run(['sudo', 'pip3', 'install', 'meson'])) #----------------------------------------------------------------------------------------------------------------- #--------------------------------------------- Windows Dependencies ---------------------------------------------- diff --git a/src/AncestorDialog.cpp b/src/AncestorDialog.cpp index 2d741cb61..2b17302bb 100644 --- a/src/AncestorDialog.cpp +++ b/src/AncestorDialog.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * AncestorDialog.cpp is part of Brewtarget, and is copyright the following authors 2021-2023: + * AncestorDialog.cpp is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * • Mik Firestone * @@ -18,9 +18,9 @@ #include +#include #include #include -#include #include #include #include diff --git a/src/AncestorDialog.h b/src/AncestorDialog.h index 1f81802cc..e862b8627 100644 --- a/src/AncestorDialog.h +++ b/src/AncestorDialog.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * AncestorDialog.h is part of Brewtarget, and is copyright the following authors 2021-2023: + * AncestorDialog.h is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * • Mik Firestone * @@ -19,7 +19,6 @@ #pragma once #include -#include #include #include #include diff --git a/src/BtTabWidget.cpp b/src/BtTabWidget.cpp index 84c99681e..4eaf9cbc7 100644 --- a/src/BtTabWidget.cpp +++ b/src/BtTabWidget.cpp @@ -23,12 +23,8 @@ #include "trees/TreeNode.h" #include "database/ObjectStoreWrapper.h" #include "model/Equipment.h" -#include "model/Fermentable.h" -#include "model/Hop.h" -#include "model/Misc.h" #include "model/Recipe.h" #include "model/Style.h" -#include "model/Yeast.h" //! \brief set up the popup window. diff --git a/src/BtTabWidget.h b/src/BtTabWidget.h index 1c075789e..f776c0d4e 100644 --- a/src/BtTabWidget.h +++ b/src/BtTabWidget.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * BtTabWidget.h is part of Brewtarget, and is copyright the following authors 2009-2022: + * BtTabWidget.h is part of Brewtarget, and is copyright the following authors 2009-2024: * • Matt Young * • Mik Firestone * • Philip Greggory Lee @@ -21,13 +21,14 @@ #include +#include "model/Fermentable.h" +#include "model/Hop.h" +#include "model/Misc.h" +#include "model/Yeast.h" + class Equipment; -class Fermentable; -class Hop; -class Misc; class Recipe; class Style; -class Yeast; /*! * \class BtTabWdiget diff --git a/src/Localization.cpp b/src/Localization.cpp index 5b90e2fa2..806819a78 100644 --- a/src/Localization.cpp +++ b/src/Localization.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * Localization.cpp is part of Brewtarget, and is copyright the following authors 2011-2023: + * Localization.cpp is part of Brewtarget, and is copyright the following authors 2011-2024: * • Greg Meess * • Matt Young * • Mik Firestone @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "Application.h" @@ -150,15 +151,16 @@ QString const & Localization::getSystemLanguage() { } bool Localization::hasUnits(QString qstr) { - QString decimal = QRegExp::escape(Localization::getLocale().decimalPoint()); - QString grouping = QRegExp::escape(Localization::getLocale().groupSeparator()); + QString decimal = QRegularExpression::escape(Localization::getLocale().decimalPoint()); + QString grouping = QRegularExpression::escape(Localization::getLocale().groupSeparator()); - QRegExp amtUnit("((?:\\d+" + grouping + ")?\\d+(?:" + decimal + "\\d+)?|" + decimal + "\\d+)\\s*(\\w+)?"); - amtUnit.indexIn(qstr); + QRegularExpression amtUnit("((?:\\d+" + grouping + ")?\\d+(?:" + decimal + "\\d+)?|" + decimal + "\\d+)\\s*(\\w+)?"); + QRegularExpressionMatch match = amtUnit.match(qstr); - bool result = amtUnit.cap(2).size() > 0; - - qDebug() << Q_FUNC_INFO << qstr << (result ? "has" : "does not have") << "units"; + // We could use hasCaptured here, but for debugging it's helpful to be able to show what we matched. + QString const units = match.captured(2); + bool const result = units.size() > 0; + qDebug() << Q_FUNC_INFO << qstr << (result ? "has" : "does not have") << "units:" << units; return result; } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7d87376b4..1c4c551d8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -44,7 +44,6 @@ #include #include -#include #include #include #include @@ -57,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -1276,13 +1276,14 @@ void MainWindow::restoreSavedState() { restoreState(PersistentSettings::value(PersistentSettings::Names::windowState).toByteArray()); } else { // otherwise, guess a reasonable size at 1/4 of the screen. - QDesktopWidget *desktop = QApplication::desktop(); - int width = desktop->width(); - int height = desktop->height(); + QScreen * screen = this->screen(); + QRect const desktop = screen->availableGeometry(); + int const width = desktop.width(); + int const height = desktop.height(); this->resize(width/2,height/2); // Or we could do the same in one line: - // this->resize(QDesktopWidget().availableGeometry(this).size() * 0.5); + // this->resize(this->screen().availableGeometry().size() * 0.5); } // If we saved the selected recipe name the last time we ran, select it and show it. diff --git a/src/RadarChart.cpp b/src/RadarChart.cpp index d2b4a16d3..cc5591b38 100644 --- a/src/RadarChart.cpp +++ b/src/RadarChart.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * RadarChart.cpp is part of Brewtarget, and is copyright the following authors 2021-2023: + * RadarChart.cpp is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -46,7 +46,7 @@ namespace std { namespace { struct ColorAndObject{ QColor color; - QObject const * object; + NamedEntity const * object; }; // Qt measures angles either in degrees or sixteenths of a degree, measured clockwise starting from 12 o'clock as 0° @@ -98,6 +98,9 @@ class RadarChart::impl { void updateMaxAxisValue() { double maxInAllSeries = 0.0; for (auto currSeries : qAsConst(this->allSeries)) { + // It's a coding error if we have a series with a null pointer. (If the object has gone away, we should remove + // the series.) + Q_ASSERT(currSeries.object); auto maxVariableInThisSeries = std::max_element( this->variableNames.begin(), this->variableNames.end(), @@ -183,15 +186,28 @@ void RadarChart::init(QString const unitsName, return; } -void RadarChart::addSeries(QString name, QColor color, QObject const & object) { +void RadarChart::addSeries(QString name, QColor color, NamedEntity const & object) { // Can't store an object or a reference in a hash, but we can store a pointer // (Still good to have the object passed in by reference as it saves having to check/assert for null pointers.) this->pimpl->allSeries.insert(name, {color, &object}); - this->replot(); + qDebug() << + Q_FUNC_INFO << "Added" << name << object.metaObject()->className() << "#" << object.key() << ":" << object.name(); + // We deliberately don't replot here because the caller might need to make further calls before it is OK to redraw + // the graph. return; } +void RadarChart::removeSeries(QString name) { + // It doesn't matter if the series is not present - QHash remove will just return false, but nothing bad happens + this->pimpl->allSeries.remove(name); + qDebug() << Q_FUNC_INFO << "Removed" << name; + // We deliberately don't replot here because the caller might need to make further calls before it is OK to redraw + // the graph. + return; +} + + void RadarChart::replot() { this->pimpl->updateMaxAxisValue(); diff --git a/src/RadarChart.h b/src/RadarChart.h index 30839fb13..0c6848ac9 100644 --- a/src/RadarChart.h +++ b/src/RadarChart.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * RadarChart.h is part of Brewtarget, and is copyright the following authors 2021-2023: + * RadarChart.h is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -23,6 +23,7 @@ #include #include +#include "model/NamedEntity.h" #include "utils/BtStringConst.h" /** @@ -67,7 +68,10 @@ class RadarChart: public QWidget { * @param color Color in which to plot the series * @param values The object whose properties are to be plotted for this series */ - void addSeries(QString name, QColor color, QObject const & object); + void addSeries(QString name, QColor color, NamedEntity const & object); + + //! \brief Remove the named series if present + void removeSeries(QString name); /** * @brief (Re)plot the graph. Call this when there's a change to a property on an object being plotted, so that the diff --git a/src/RangedSlider.cpp b/src/RangedSlider.cpp index f6d37387a..df220a3d5 100644 --- a/src/RangedSlider.cpp +++ b/src/RangedSlider.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * RangedSlider.cpp is part of Brewtarget, and is copyright the following authors 2009-2022: + * RangedSlider.cpp is part of Brewtarget, and is copyright the following authors 2009-2024: * • Matt Young * • Mik Firestone * • Philip Greggory Lee @@ -17,6 +17,8 @@ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌*/ #include "RangedSlider.h" +#include + #include #include #include @@ -34,35 +36,35 @@ RangedSlider::RangedSlider(QWidget* parent) : QWidget(parent), - _min(0.0), - _max(1.0), - _prefMin(0.25), - _prefMax(0.75), - _val(0.5), - _valText("0.500"), - _prec(3), - _tickInterval(0), - _secondaryTicks(1), - _tooltipText(""), - _bgBrush(QColor(255,255,255)), - _prefRangeBrush(QColor(0,0,0)), - _prefRangePen(Qt::NoPen), - _markerBrush(QColor(255,255,255)), - _markerTextIsValue(false), - valueTextFont("Arial", - 14, // QFonts are specified in point size, so the hard-coded number is fine here. - QFont::Black), // Note that QFont::Black is a weight (more bold than ExtraBold), not a colour. - indicatorTextFont("Arial", - 10, - QFont::Normal) // Previously we just did the indicator text in 'default' font + m_min(0.0), + m_max(1.0), + m_prefMin(0.25), + m_prefMax(0.75), + m_val(0.5), + m_valText("0.500"), + m_prec(3), + m_tickInterval(0), + m_secondaryTicks(1), + m_tooltipText(""), + m_bgBrush(QColor(255,255,255)), + m_prefRangeBrush(QColor(0,0,0)), + m_prefRangePen(Qt::NoPen), + m_markerBrush(QColor(255,255,255)), + m_markerTextIsValue(false), + m_valueTextFont("Arial", + 14, // QFonts are specified in point size, so the hard-coded number is fine here. + QFont::Black), // Note that QFont::Black is a weight (more bold than ExtraBold), not a colour. + m_indicatorTextFont("Arial", + 10, + QFont::Normal) // Previously we just did the indicator text in 'default' font { - // Ensure this->heightInPixels is properly initialised + // Ensure this->m_heightInPixels is properly initialised this->recalculateHeightInPixels(); // In principle we want to set our min/max sizes etc here. However, if, say, a maximumSize property has been set // for this object in a Designer UI File (eg ui/mainWindow.ui) then that setting will override this one, because it // will be applied later (in fact pretty much straight after this constructor returns). So we also make this call - // inside setValue(), which will be invoked _after_ the setter calls that were auto-generated from the Designer UI + // inside setValue(), which will be invoked m_afterm_ the setter calls that were auto-generated from the Designer UI // File. this->setSizes(); @@ -70,42 +72,44 @@ RangedSlider::RangedSlider(QWidget* parent) this->setMouseTracking(true); this->repaint(); + return; } -void RangedSlider::setPreferredRange( double min, double max ) -{ - _prefMin = min; - _prefMax = max; +double RangedSlider::value() const { return m_val; } + +void RangedSlider::setPreferredRange(double min, double max) { + m_prefMin = min; + m_prefMax = max; // Only show tooltips if the range has nonzero size. setMouseTracking(min < max); - _tooltipText = QString("%1 - %2").arg(min, 0, 'f', _prec).arg(max, 0, 'f', _prec); + m_tooltipText = QString("%1 - %2").arg(min, 0, 'f', m_prec).arg(max, 0, 'f', m_prec); update(); + return; } -void RangedSlider::setPreferredRange(QPair minmax) -{ +void RangedSlider::setPreferredRange(QPair minmax) { setPreferredRange( minmax.first, minmax.second ); + return; } -void RangedSlider::setRange( double min, double max ) -{ - _min = min; - _max = max; +void RangedSlider::setRange(double min, double max) { + m_min = min; + m_max = max; update(); + return; } -void RangedSlider::setRange(QPair minmax) -{ +void RangedSlider::setRange(QPair minmax) { setRange( minmax.first, minmax.second ); + return; } -void RangedSlider::setValue(double value) -{ - _val = value; - _valText = QString("%1").arg(_val, 0, 'f', _prec); +void RangedSlider::setValue(double value) { + m_val = value; + m_valText = QString("%1").arg(m_val, 0, 'f', m_prec); update(); // See comment in constructor for why we call this here @@ -113,54 +117,54 @@ void RangedSlider::setValue(double value) return; } -void RangedSlider::setPrecision(int precision) -{ - _prec = precision; +void RangedSlider::setPrecision(int precision) { + m_prec = precision; update(); + return; } -void RangedSlider::setBackgroundBrush( QBrush const& brush ) -{ - _bgBrush = brush; +void RangedSlider::setBackgroundBrush(QBrush const & brush) { + m_bgBrush = brush; update(); + return; } -void RangedSlider::setPreferredRangeBrush( QBrush const& brush ) -{ - _prefRangeBrush = brush; +void RangedSlider::setPreferredRangeBrush(QBrush const & brush) { + m_prefRangeBrush = brush; update(); + return; } -void RangedSlider::setPreferredRangePen( QPen const& pen ) -{ - _prefRangePen = pen; +void RangedSlider::setPreferredRangePen(QPen const & pen) { + m_prefRangePen = pen; update(); + return; } -void RangedSlider::setMarkerBrush( QBrush const& brush ) -{ - _markerBrush = brush; +void RangedSlider::setMarkerBrush(QBrush const & brush) { + m_markerBrush = brush; update(); + return; } -void RangedSlider::setMarkerText( QString const& text ) -{ - _markerText = text; +void RangedSlider::setMarkerText(QString const & text) { + m_markerText = text; update(); + return; } -void RangedSlider::setMarkerTextIsValue(bool val) -{ - _markerTextIsValue = val; +void RangedSlider::setMarkerTextIsValue(bool val) { + m_markerTextIsValue = val; update(); + return; } -void RangedSlider::setTickMarks( double primaryInterval, int secondaryTicks ) -{ - _secondaryTicks = (secondaryTicks<1)? 1 : secondaryTicks; - _tickInterval = primaryInterval/_secondaryTicks; +void RangedSlider::setTickMarks(double primaryInterval, int secondaryTicks) { + m_secondaryTicks = (secondaryTicks<1)? 1 : secondaryTicks; + m_tickInterval = primaryInterval/m_secondaryTicks; update(); + return; } void RangedSlider::recalculateHeightInPixels() const { @@ -207,16 +211,16 @@ void RangedSlider::recalculateHeightInPixels() const { // assumptions about space below the baseline are locale-specific, so, say, using ascent() instead of lineSpacing() // could end up painting us into a corner. // - QFontMetrics indicatorTextFontMetrics(this->indicatorTextFont); - QFontMetrics valueTextFontMetrics(this->valueTextFont); - this->heightInPixels = indicatorTextFontMetrics.lineSpacing() + valueTextFontMetrics.lineSpacing(); + QFontMetrics indicatorTextFontMetrics(this->m_indicatorTextFont); + QFontMetrics valueTextFontMetrics(this->m_valueTextFont); + this->m_heightInPixels = indicatorTextFontMetrics.lineSpacing() + valueTextFontMetrics.lineSpacing(); return; } void RangedSlider::setSizes() { // Caller's responsibility to have recently called this->recalculateHeightInPixels(). (See comment in that function // for how we choose minimum width.) - this->setMinimumSize(2 * this->heightInPixels, this->heightInPixels); + this->setMinimumSize(2 * this->m_heightInPixels, this->m_heightInPixels); this->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); @@ -227,58 +231,68 @@ void RangedSlider::setSizes() { return; } -QSize RangedSlider::sizeHint() const -{ +QSize RangedSlider::sizeHint() const { this->recalculateHeightInPixels(); - return QSize(4 * this->heightInPixels, this->heightInPixels); + return QSize(4 * this->m_heightInPixels, this->m_heightInPixels); } -void RangedSlider::mouseMoveEvent(QMouseEvent* event) -{ +void RangedSlider::mouseMoveEvent(QMouseEvent* event) { event->accept(); QPoint tipPoint( mapToGlobal(QPoint(0,0)) ); - QToolTip::showText( tipPoint, _tooltipText, this ); + QToolTip::showText( tipPoint, m_tooltipText, this ); + return; } void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { // // Simplistically, the high-level layout of the slider is: // - // |-------------------------------------------------------------| - // | Indicator text | B L A N K | - // |------------------------------------------------+------------| - // | <--------------- Graphical Area -------------> | Value text | - // |-------------------------------------------------------------| + // ╔════════════════════════════════════════════════════════════════╗ + // ║ Indicator text ┊ B L A N K ║ + // ║┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈║ + // ║ ╭────────────────┲━━━━━━━━━━━━━━━━━━━━┱─────────╮ ┊ ║ + // ║ │ ┃ ┋ ┃ │ ┊ Value text ║ + // ║ ╰────────────────┺━━━━━━━━━━━━━━━━━━━━┹─────────╯ ┊ ║ + // ║ <---------------- Graphical Area ---------------> ┊ ║ + // ╚════════════════════════════════════════════════════════════════╝ + // ↑ ↑ ↑ ↑ ↑ + // m_min m_prefMin m_val m_prefMax m_max + // │ │ │ │ │ + // 0.0 fgRectLeft │ │ graphicalAreaWidth + // │ │ + // │ fgRectLeft + fgRectWidth + // │ + // indicatorLineMiddle // // The graphical area has: - // - a background rectangle of the full width of the area, representing the range from this->_min to this->_max - // - a foreground rectangle showing the sub-range of this background from this->_prefMin to this->_prefMax - // - a line ("the indicator") showing where this->_val lies in the (this->_min to this->_max) range + // - a background rectangle of the full width of the area, representing the range from this->m_min to this->m_max + // - a foreground rectangle showing the sub-range of this background from this->m_prefMin to this->m_prefMax + // - a line ("the indicator") showing where this->m_val lies in the (this->m_min to this->m_max) range // - // The indicator text sits above the indicator line and shows either its value (this->_valText) or some textual - // description (eg "Slightly Malty" on the IBU/GU scale) which comes from this->_markerText. + // The indicator text sits above the indicator line and shows either its value (this->m_valText) or some textual + // description (eg "Slightly Malty" on the IBU/GU scale) which comes from this->m_markerText. // // In principle, we could have the value text a slightly different height than the graphical area - eg to help // squeeze into smaller available vertical space on small screens (as we know there is blank space of // indicatorTextHeight pixels above the space for the value text). // - // The value text also shows this->_valText. + // The value text also shows this->m_valText. // - QFontMetrics indicatorTextFontMetrics(this->indicatorTextFont); + QFontMetrics indicatorTextFontMetrics(this->m_indicatorTextFont); int indicatorTextHeight = indicatorTextFontMetrics.lineSpacing(); // The heights of the slider graphic and the value text are usually the same, but we calculate them differently in // case, in future, we want to squeeze things up a bit. - QFontMetrics valueTextFontMetrics(this->valueTextFont); - int valueTextHeight = valueTextFontMetrics.lineSpacing(); + QFontMetrics valueTextFontMetrics(this->m_valueTextFont); + int const valueTextHeight = valueTextFontMetrics.lineSpacing(); - int graphicalAreaHeight = this->height() - indicatorTextHeight; + int const graphicalAreaHeight = this->height() - indicatorTextHeight; // Although the Qt calls take an x- and a y- radius, we want the radius on the rectangle corners to be the same // vertically and horizontally, so only define one measure here. - int rectangleCornerRadius = graphicalAreaHeight / 4; + int const rectangleCornerRadius = graphicalAreaHeight / 4; static const QPalette palette(QApplication::palette()); static const int indicatorLineWidth = 4; @@ -287,23 +301,17 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { static const QColor valueTextColor(0,127,0); // We need to allow for the width of the text that displays to the right of the slider showing the current value. - // If there were just one slider, we might ask Qt for the width of this text with one of the following calls: - // const int valueTextWidth = valueTextFontMetrics.width(_valText); // Pre Qt 5.13 - // const int valueTextWidth = valueTextFontMetrics.horizontalAdvance(_valText); // Since Qt 5.13 + // If there were just one slider, we might ask Qt for the width of this text with the following call: + // const int valueTextWidth = valueTextFontMetrics.horizontalAdvance(m_valText); // Since Qt 5.13 // However, we want all the sliders to have exact same width, so we choose some representative text to measure the // width of. We assume that all sliders show no more than 4 digits and a decimal point, and then add a space to // ensure a gap between the value text and the graphical area. (Note that digits are all the same width in the font // we are using. - int valueTextWidth = -#if QT_VERSION < QT_VERSION_CHECK(5,13,0) - valueTextFontMetrics.width(" 1.000"); -#else - valueTextFontMetrics.horizontalAdvance(" 1.000"); -#endif - - QLinearGradient glassGrad( QPointF(0,0), QPointF(0,graphicalAreaHeight) ); - glassGrad.setColorAt( 0, QColor(255,255,255,127) ); - glassGrad.setColorAt( 1, QColor(255,255,255,0) ); + int valueTextWidth = valueTextFontMetrics.horizontalAdvance(" 1.000"); + + QLinearGradient glassGrad( QPointF(0, 0), QPointF(0, graphicalAreaHeight) ); + glassGrad.setColorAt(0, QColor(255, 255, 255, 127)); + glassGrad.setColorAt(1, QColor(255, 255, 255, 0)); QBrush glassBrush(glassGrad); // Per https://doc.qt.io/qt-5/highdpi.html, for best High DPI display support, we need to: @@ -312,51 +320,78 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { // • Replace hard-coded sizes in layouts and drawing code with values calculated from font metrics or screen size QPainter painter(this); - // Work out the left-to-right (ie x-coordinate) positions of things in the graphical area - double graphicalAreaWidth = this->width() - valueTextWidth; - double range = this->_max - this->_min; - double fgRectLeft = graphicalAreaWidth * ((this->_prefMin - this->_min )/range); - double fgRectWidth = graphicalAreaWidth * ((this->_prefMax - this->_prefMin)/range); - double indicatorLineMiddle = graphicalAreaWidth * ((this->_val - this->_min )/range); - double indicatorLineLeft = indicatorLineMiddle - (indicatorLineWidth / 2); + // Usually leave this debug log commented out unless trouble-shooting as it generates a lot of logging +// qDebug() << +// Q_FUNC_INFO << "Width:" << this->width() << "; valueTextWidth:" << valueTextWidth << "; m_min:" << this->m_min << +// "; m_max:" << this->m_max << "; m_prefMin:" << this->m_prefMin << "; m_prefMax:" << this->m_prefMax << +// "; m_val" << this->m_val; + // + // Work out the left-to-right (ie x-coordinate) positions of things in the graphical area. Note that we have to + // gracefully handle extreme cases -- eg where we do not have enough space to paint ourselves. In particular, we + // have to be careful about using calculated values as the min and/or max parameters to qBound, because that function + // (quite reasonably) asserts at runtime that min < max, and it's easy to break that if you're doing simple + // calculations for how to draw the graphical area when there isn't really enough space to draw it. + // + double const graphicalAreaWidth = static_cast(std::max(0, this->width() - valueTextWidth)); + double const range = this->m_max - this->m_min; + double fgRectLeft = std::max(0.0, graphicalAreaWidth * ((this->m_prefMin - this->m_min )/range)); + double fgRectWidth = std::max(0.0, graphicalAreaWidth * ((this->m_prefMax - this->m_prefMin)/range)); + double indicatorLineMiddle = std::max(0.0, graphicalAreaWidth * ((this->m_val - this->m_min )/range)); + double indicatorLineLeft = std::max(0.0, indicatorLineMiddle - (static_cast(indicatorLineWidth) / 2.0)); + // Usually leave this debug log commented out unless trouble-shooting as it generates a lot of logging +// qDebug() << +// Q_FUNC_INFO << "graphicalAreaWidth:" << graphicalAreaWidth << "; range:" << range << "; fgRectLeft:" << +// fgRectLeft << "; fgRectWidth:" << fgRectWidth << "; indicatorLineMiddle:" << indicatorLineMiddle << +// "; indicatorLineLeft:" << indicatorLineLeft; + + // // Make sure all coordinates are valid. - fgRectLeft = qBound(0.0, fgRectLeft, graphicalAreaWidth); - fgRectWidth = qBound(0.0, fgRectWidth, graphicalAreaWidth - fgRectLeft); - indicatorLineMiddle = qBound(0.0, indicatorLineMiddle, graphicalAreaWidth - (indicatorLineWidth / 2)); - indicatorLineLeft = qBound(0.0, indicatorLineLeft, graphicalAreaWidth - indicatorLineWidth); + // + fgRectLeft = std::min(fgRectLeft, graphicalAreaWidth); + fgRectWidth = std::max(0.0, std::min(fgRectWidth, graphicalAreaWidth - fgRectLeft)); + indicatorLineMiddle = std::max(0.0, std::min(indicatorLineMiddle, graphicalAreaWidth - (indicatorLineWidth / 2))); + indicatorLineLeft = std::max(0.0, std::min(indicatorLineLeft, graphicalAreaWidth - indicatorLineWidth)); // The left-to-right position of the indicator text (also known as marker text) depends on where the slider is. // First we ask the painter what size rectangle it will need to display this text painter.setPen(indicatorTextColor); - painter.setFont(this->indicatorTextFont); - QRectF indicatorTextRect = painter.boundingRect(QRectF(), - Qt::AlignCenter | Qt::AlignBottom, - this->_markerTextIsValue ? this->_valText : this->_markerText); + painter.setFont(this->m_indicatorTextFont); + QRectF const indicatorTextRect = + painter.boundingRect(QRectF(), + Qt::AlignCenter | Qt::AlignBottom, + this->m_markerTextIsValue ? this->m_valText : this->m_markerText); + // // Then we use the size of this rectangle to try to make the middle of the text sit over the indicator marker on // the slider - but bounding things so that the text doesn't go off the edge of the slider. - double indicatorTextLeft = qBound(0.0, - indicatorLineMiddle - (indicatorTextRect.width() / 2), - graphicalAreaWidth - indicatorTextRect.width()); + // + // This is a more robust way of writing + // + // double indicatorTextLeft = qBound(0.0, + // indicatorLineMiddle - (indicatorTextRect.width() / 2), + // graphicalAreaWidth - indicatorTextRect.width()); + // + double indicatorTextLeft = std::max(0.0, indicatorLineMiddle - (indicatorTextRect.width() / 2)); + indicatorTextLeft = std::max(0.0, std::min(indicatorTextLeft, graphicalAreaWidth - indicatorTextRect.width())); // Now we can draw the indicator text painter.drawText( indicatorTextLeft, 0, indicatorTextRect.width(), indicatorTextRect.height(), Qt::AlignCenter | Qt::AlignBottom, - this->_markerTextIsValue ? this->_valText : this->_markerText + this->m_markerTextIsValue ? this->m_valText : this->m_markerText ); // Next draw the value text // We work out its vertical position relative to the bottom of the graphical area in case (in future) we want to be // able to use some of the blank space above it (to the right of the indicator text). painter.setPen(valueTextColor); - painter.setFont(this->valueTextFont); + painter.setFont(this->m_valueTextFont); painter.drawText(graphicalAreaWidth, this->height() - valueTextHeight, valueTextWidth, valueTextHeight, Qt::AlignRight | Qt::AlignVCenter, - this->_valText ); + this->m_valText ); // All the rest of what we need to do is inside the graphical area, so move the origin to the top-left corner of it painter.translate(0, indicatorTextRect.height()); @@ -370,7 +405,7 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { painter.setClipPath(clipRect); // Draw the background rectangle. - painter.setBrush(_bgBrush); + painter.setBrush(m_bgBrush); painter.setRenderHint(QPainter::Antialiasing); painter.drawRoundedRect( QRectF(0, 0, graphicalAreaWidth, graphicalAreaHeight), rectangleCornerRadius, @@ -379,8 +414,8 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { // Draw the style "foreground" rectangle. painter.save(); - painter.setBrush(_prefRangeBrush); - painter.setPen(_prefRangePen); + painter.setBrush(m_prefRangeBrush); + painter.setPen(m_prefRangePen); painter.setRenderHint(QPainter::Antialiasing); painter.drawRoundedRect( QRectF(fgRectLeft, 0, fgRectWidth, graphicalAreaHeight), rectangleCornerRadius, @@ -388,7 +423,7 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { painter.restore(); // Draw the indicator. - painter.setBrush(_markerBrush); + painter.setBrush(m_markerBrush); painter.drawRect( QRectF(indicatorLineLeft, 0, indicatorLineWidth, graphicalAreaHeight) ); // Draw a white-to-clear gradient to suggest "glassy." @@ -401,13 +436,13 @@ void RangedSlider::paintEvent([[maybe_unused]] QPaintEvent * event) { // Draw the ticks. painter.setPen(Qt::black); - if( _tickInterval > 0.0 ) + if( m_tickInterval > 0.0 ) { int secTick = 1; - for( double currentTick = _min+_tickInterval; _max - currentTick > _tickInterval-1e-6; currentTick += _tickInterval ) + for( double currentTick = m_min+m_tickInterval; m_max - currentTick > m_tickInterval-1e-6; currentTick += m_tickInterval ) { - painter.translate( graphicalAreaWidth/(_max-_min) * _tickInterval, 0); - if( secTick == _secondaryTicks ) + painter.translate( graphicalAreaWidth/(m_max-m_min) * m_tickInterval, 0); + if( secTick == m_secondaryTicks ) { painter.drawLine( QPointF(0,0.25*graphicalAreaHeight), QPointF(0,0.75*graphicalAreaHeight) ); secTick = 1; diff --git a/src/RangedSlider.h b/src/RangedSlider.h index 8e1985a40..56b7bd18b 100644 --- a/src/RangedSlider.h +++ b/src/RangedSlider.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * RangedSlider.h is part of Brewtarget, and is copyright the following authors 2009-2020: + * RangedSlider.h is part of Brewtarget, and is copyright the following authors 2009-2024: * • Matt Young * • Mik Firestone * • Philip Greggory Lee @@ -24,23 +24,22 @@ #include #include #include + class QPaintEvent; class QMouseEvent; /*! * \brief Widget to display a number with an optional range on a type of read-only slider. - * */ -class RangedSlider : public QWidget -{ +class RangedSlider : public QWidget { Q_OBJECT public: - RangedSlider(QWidget* parent=0); + RangedSlider(QWidget * parent = nullptr); - Q_PROPERTY( double value READ value WRITE setValue ) + Q_PROPERTY(double value READ value WRITE setValue ) - double value() const { return _val; } + double value() const; //! \brief Set the background brush for the widget. void setBackgroundBrush( QBrush const& brush ); @@ -82,7 +81,7 @@ public slots: * \param range \c range.first and \c range.second are the min and max * values for the preferred range resp. */ - void setPreferredRange(QPair range); + void setPreferredRange(QPair range); /*! * \brief Set the range of values that the widget displays @@ -90,14 +89,14 @@ public slots: * \param range \c range.first and \c range.second are the min and max * values for the preferred range resp. */ - void setRange(QPair range); + void setRange(QPair range); //! \brief Convenience method for setting the widget range - void setRange( double min, double max ); + void setRange(double min, double max); //! \brief Convenience method for setting the preferred range // Note that this is completely unrelated to "preferred size". - void setPreferredRange( double min, double max ); + void setPreferredRange(double min, double max); protected: //! \brief Reimplemented from QWidget. @@ -114,47 +113,39 @@ public slots: void setSizes(); void recalculateHeightInPixels() const; - /** - * Minimum value the widget displays - */ - double _min; - /** - * Maximum value the widget displays - */ - double _max; - - /** - * Minimum value for the "best" sub-range - */ - double _prefMin; - /** - * Maximum value for the "best" sub-range - */ - double _prefMax; - double _val; - QString _valText; - QString _markerText; - int _prec; - double _tickInterval; - int _secondaryTicks; - QString _tooltipText; - QBrush _bgBrush; - QBrush _prefRangeBrush; - QPen _prefRangePen; - QBrush _markerBrush; - bool _markerTextIsValue; + //! Minimum value the widget displays + double m_min; + //! Maximum value the widget displays + double m_max; + + //! Minimum value for the "best" sub-range + double m_prefMin; + //! Maximum value for the "best" sub-range + double m_prefMax; + double m_val; + QString m_valText; + QString m_markerText; + int m_prec; + double m_tickInterval; + int m_secondaryTicks; + QString m_tooltipText; + QBrush m_bgBrush; + QBrush m_prefRangeBrush; + QPen m_prefRangePen; + QBrush m_markerBrush; + bool m_markerTextIsValue; /** * The font used for showing the value at the right-hand side of the slider */ - QFont const valueTextFont; + QFont const m_valueTextFont; /** * The font used for showing the indicator above the "needle" on the slider. Often this is just showing the same as * the value - eg OG, FG, ABV - but sometimes it's something else - eg descriptive text such as "slightly hoppy" for * the IBU/GU reading. */ - QFont const indicatorTextFont; + QFont const m_indicatorTextFont; /** * Since preferred and minimum dimensions are all based off a height we need to calculate based on the resolution of @@ -162,7 +153,7 @@ public slots: * that height here. However, its value is not really part of the current value/state of the object, hence mutable * (ie OK to change in a const function). */ - mutable int heightInPixels; + mutable int m_heightInPixels; }; #endif diff --git a/src/RecipeFormatter.cpp b/src/RecipeFormatter.cpp index 11e0e0101..99e410def 100644 --- a/src/RecipeFormatter.cpp +++ b/src/RecipeFormatter.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1155,7 +1156,7 @@ QString RecipeFormatter::getBBCodeFormat() { } QString tmp = ""; - QRegExp regexp("(^[^\n]*\n)(.*$)"); //Regexp to match the first line of tables + QRegularExpression const regexp("(^[^\n]*\n)(.*$)"); // Regexp to match the first line of tables auto style = this->pimpl->rec->style(); diff --git a/src/database/BtSqlQuery.h b/src/database/BtSqlQuery.h index d2c518373..4154faa2a 100644 --- a/src/database/BtSqlQuery.h +++ b/src/database/BtSqlQuery.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * database/BtSqlQuery.h is part of Brewtarget, and is copyright the following authors 2021: + * database/BtSqlQuery.h is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -50,8 +50,12 @@ */ class BtSqlQuery : public QSqlQuery { public: - // Use the same constructors as QSqlQuery + // Use the same constructors as QSqlQuery... using QSqlQuery::QSqlQuery; + // ...except that QSqlQuery copy constructor is deprecated (and, if you use it, you get a warning: "QSqlQuery is not + // meant to be copied. Use move construction instead."). So, let's not do copy construction! + BtSqlQuery(BtSqlQuery const & other) = delete; + BtSqlQuery(QSqlQuery const & other) = delete; /** * \brief As \c QSqlQuery::prepare() except we don't actually call QSqlQuery::prepare() unless and until a value is @@ -79,7 +83,8 @@ class BtSqlQuery : public QSqlQuery { void reallyPrepare(); - + // This is deprecated in the base class + BtSqlQuery & operator=(BtSqlQuery const & other) = delete; }; #endif diff --git a/src/database/DatabaseSchemaHelper.cpp b/src/database/DatabaseSchemaHelper.cpp index 318e2ab31..a039b1488 100644 --- a/src/database/DatabaseSchemaHelper.cpp +++ b/src/database/DatabaseSchemaHelper.cpp @@ -102,7 +102,7 @@ namespace { // This is when we first defined the settings table, and defined the version as a string. // In the new world, this will create the settings table and define the version as an int. // Since we don't set the version until the very last step of the update, I think this will be fine. - bool migrate_to_202(Database & db, BtSqlQuery q) { + bool migrate_to_202(Database & db, BtSqlQuery & q) { bool ret = true; // Add "projected_ferm_points" to brewnote table @@ -204,7 +204,7 @@ namespace { return executeSqlQueries(q, migrationQueries); } - bool migrate_to_5([[maybe_unused]] Database & db, BtSqlQuery q) { + bool migrate_to_5([[maybe_unused]] Database & db, BtSqlQuery & q) { QVector const migrationQueries{ // Drop the previous bugged TRIGGER {QString("DROP TRIGGER dec_ins_num")}, @@ -221,12 +221,12 @@ namespace { } // - bool migrate_to_6([[maybe_unused]] Database & db, [[maybe_unused]] BtSqlQuery q) { + bool migrate_to_6([[maybe_unused]] Database & db, [[maybe_unused]] BtSqlQuery & q) { // I drop this table in version 8. There is no sense doing anything here, and it breaks other things. return true; } - bool migrate_to_7([[maybe_unused]] Database & db, BtSqlQuery q) { + bool migrate_to_7([[maybe_unused]] Database & db, BtSqlQuery & q) { QVector const migrationQueries{ // Add "attenuation" to brewnote table {QString("ALTER TABLE brewnote ADD COLUMN attenuation %1").arg(db.getDbNativeTypeName())} // Previously DEFAULT 0.0 @@ -234,7 +234,7 @@ namespace { return executeSqlQueries(q, migrationQueries); } - bool migrate_to_8(Database & db, BtSqlQuery q) { + bool migrate_to_8(Database & db, BtSqlQuery & q) { QString createTmpBrewnoteSql; QTextStream createTmpBrewnoteSqlStream(&createTmpBrewnoteSql); createTmpBrewnoteSqlStream << @@ -462,7 +462,7 @@ namespace { // To support the water chemistry, I need to add two columns to water and to // create the salt and salt_in_recipe tables - bool migrate_to_9(Database & db, BtSqlQuery q) { + bool migrate_to_9(Database & db, BtSqlQuery & q) { QString createSaltSql; QTextStream createSaltSqlStream(&createSaltSql); createSaltSqlStream << @@ -509,7 +509,7 @@ namespace { return executeSqlQueries(q, migrationQueries); } - bool migrate_to_10(Database & db, BtSqlQuery q) { + bool migrate_to_10(Database & db, BtSqlQuery & q) { QVector const migrationQueries{ // DB-specific version of ALTER TABLE recipe ADD COLUMN ancestor_id INTEGER REFERENCES recipe(id) {QString(db.getSqlToAddColumnAsForeignKey()).arg("recipe", @@ -534,7 +534,7 @@ namespace { * names ending in "_pct" (for percent), "_l" (for liters), etc. One day perhaps we'll rename all the * relevant existing columns, but I think we've got enough other change in this update! */ - bool migrate_to_11(Database & db, BtSqlQuery q) { + bool migrate_to_11(Database & db, BtSqlQuery & q) { // // Some of the bits of SQL would be too cumbersome to build up in-place inside the migrationQueries vector, so // we use string streams to do the string construction here. @@ -2077,7 +2077,7 @@ namespace { * \brief This is not actually a schema change, but rather data fixes that were missed from migrate_to_11 - mostly * things where we should have been less case-sensitive. */ - bool migrate_to_12([[maybe_unused]] Database & db, BtSqlQuery q) { + bool migrate_to_12([[maybe_unused]] Database & db, BtSqlQuery & q) { QVector const migrationQueries{ {QString( "UPDATE hop SET htype = 'aroma/bittering' WHERE lower(htype) = 'both'" )}, {QString(" UPDATE fermentable SET ftype = 'dry extract' WHERE lower(ftype) = 'dry extract'")}, @@ -2108,7 +2108,7 @@ namespace { * \brief Small schema change to support measuring diameter of boil kettle for new IBU calculations. Plus, some * tidy-ups to Salt and Boil / BoilStep which we didn't get to before. */ - bool migrate_to_13([[maybe_unused]] Database & db, BtSqlQuery q) { + bool migrate_to_13([[maybe_unused]] Database & db, BtSqlQuery & q) { QVector const migrationQueries{ {QString("ALTER TABLE equipment ADD COLUMN kettleInternalDiameter_cm %1").arg(db.getDbNativeTypeName())}, {QString("ALTER TABLE equipment ADD COLUMN kettleOpeningDiameter_cm %1").arg(db.getDbNativeTypeName())}, diff --git a/src/database/ObjectStore.cpp b/src/database/ObjectStore.cpp index dc8308448..d4ad84b54 100644 --- a/src/database/ObjectStore.cpp +++ b/src/database/ObjectStore.cpp @@ -214,9 +214,12 @@ namespace { QString result; QTextStream resultAsStream{&result}; - QMap boundValueMap = sqlQuery.boundValues(); - for (auto bv = boundValueMap.begin(); bv != boundValueMap.end(); ++bv) { - resultAsStream << bv.key() << ": " << bv.value().toString() << "\n"; + // From Qt 6.6 we'll be able to write: + // for (auto const & bvn : sqlQuery.boundValueNames()) { + // resultAsStream << bvn << ": " << sqlQuery.boundValue(bvn).toString() << "\n"; + // } + for (auto const & bv : sqlQuery.boundValues()) { + resultAsStream << bv.toString() << "\n"; } return result; @@ -319,7 +322,7 @@ namespace { // Here and elsewhere, although we could just do a Q_ASSERT, we prefer (a) some extra diagnostics on debug builds // and (b) to bail out immediately of the DB transaction on non-debug builds. // - if (QVariant::Type::Int != primaryKey.type()) { + if (QMetaType::Type::Int != primaryKey.userType()) { qCritical() << Q_FUNC_INFO << "Unexpected contents of primaryKey QVariant: " << primaryKey.typeName(); Q_ASSERT(false); // Stop here on debug builds return false; // Continue but bail out of the current DB transaction on other builds @@ -1561,7 +1564,7 @@ void ObjectStore::loadAll(Database * database) { } queryStringAsStream << ";"; - sqlQuery = BtSqlQuery{connection}; +/// sqlQuery = BtSqlQuery{connection}; sqlQuery.prepare(queryString); if (!sqlQuery.exec()) { qCritical() << diff --git a/src/editors/EditorBase.h b/src/editors/EditorBase.h index 2a5d45f5f..7b216d002 100755 --- a/src/editors/EditorBase.h +++ b/src/editors/EditorBase.h @@ -246,12 +246,16 @@ class EditorBase : public CuriouslyRecurringTemplateBase } //! \brief No-op version - void makeLiveEditItem() requires (!HasLiveEditItem) { + void setLiveEditItem() requires (!HasLiveEditItem) { return; } - void makeLiveEditItem() requires HasLiveEditItem { - this->m_liveEditItem = std::make_unique(*this->m_editItem); + void setLiveEditItem() requires HasLiveEditItem { + if (this->m_editItem) { + this->m_liveEditItem = std::make_unique(*this->m_editItem); + } else { + this->m_liveEditItem.reset(); + } return; } @@ -270,7 +274,7 @@ class EditorBase : public CuriouslyRecurringTemplateBase this->readFieldsFromEditItem(std::nullopt); } - this->makeLiveEditItem(); + this->setLiveEditItem(); // Comment below about calling this->derived().validateBeforeSave() also applies here this->derived().postSetEditItem(); diff --git a/src/editors/WaterEditor.cpp b/src/editors/WaterEditor.cpp index 43723540c..86c0713e2 100755 --- a/src/editors/WaterEditor.cpp +++ b/src/editors/WaterEditor.cpp @@ -25,6 +25,11 @@ #include "database/ObjectStoreWrapper.h" #include "model/Water.h" +namespace { + auto const seriesNameCurrent {WaterEditor::tr("Current" )}; + auto const seriesNameModified{WaterEditor::tr("Modified")}; +} + WaterEditor::WaterEditor(QWidget *parent, QString const editorName) : QDialog(parent), EditorBase(editorName) { @@ -88,9 +93,13 @@ void WaterEditor::postSetEditItem() { if (this->m_editItem) { // Note that we don't need to remove the old series from any previous Water objects as the call to addSeries will // replace them. - this->waterEditRadarChart->addSeries(tr("Current"), Qt::darkGreen, *this->m_editItem); + this->waterEditRadarChart->addSeries(seriesNameCurrent, Qt::darkGreen, *this->m_editItem); - this->waterEditRadarChart->addSeries(tr("Modified"), Qt::green, *this->m_liveEditItem); + this->waterEditRadarChart->addSeries(seriesNameModified, Qt::green, *this->m_liveEditItem); + this->waterEditRadarChart->replot(); + } else { + this->waterEditRadarChart->removeSeries(seriesNameCurrent ); + this->waterEditRadarChart->removeSeries(seriesNameModified); } return; } diff --git a/src/measurement/PhysicalQuantity.cpp b/src/measurement/PhysicalQuantity.cpp index 86ae6443c..f50a1c808 100644 --- a/src/measurement/PhysicalQuantity.cpp +++ b/src/measurement/PhysicalQuantity.cpp @@ -225,7 +225,6 @@ std::vector const & Measurement::allPossibilitiesAsInt( return allOfAsInt_Mass_Volume; } - template S & operator<<(S & stream, Measurement::PhysicalQuantity const val) { stream << diff --git a/src/measurement/PhysicalQuantity.h b/src/measurement/PhysicalQuantity.h index ea3f1eec9..878dd268b 100644 --- a/src/measurement/PhysicalQuantity.h +++ b/src/measurement/PhysicalQuantity.h @@ -297,7 +297,6 @@ namespace Measurement { } - /** * \brief Convenience functions for logging */ diff --git a/src/measurement/Unit.cpp b/src/measurement/Unit.cpp index ae96bcbb6..fd2f7c3ea 100644 --- a/src/measurement/Unit.cpp +++ b/src/measurement/Unit.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * measurement/Unit.cpp is part of Brewtarget, and is copyright the following authors 2009-2023: + * measurement/Unit.cpp is part of Brewtarget, and is copyright the following authors 2009-2024: * • Mark de Wever * • Matt Young * • Mik Firestone @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include "Algorithms.h" @@ -59,22 +59,26 @@ namespace { Measurement::PhysicalQuantity physicalQuantity; QString lowerCaseUnitName; // - // We need an ordering on the struct to use it as a key of QMap / QMultiMap. In theory, in C++20, we should be - // able to ask the compiler to do all the work to give a suitable default ordering, by writing: + // We need an ordering on the struct to use it as a key of QMap / QMultiMap. AIUI, these two operators suffice + // for the compiler to deduce all the others. // - // friend auto operator<=>(NameLookupKey const & lhs, NameLookupKey const & rhs) = default; - // - // However, in practice, this requires the spaceship operator to be defined for QString, which it isn't in Qt5. - // So, for now, define our own operator<, which will suffice for QMultiMap. - // - bool operator<(NameLookupKey const & rhs) const { - NameLookupKey const & lhs = *this; - if (lhs.physicalQuantity != rhs.physicalQuantity) { - return (lhs.physicalQuantity < rhs.physicalQuantity); - } - return (lhs.lowerCaseUnitName < rhs.lowerCaseUnitName); - } + friend auto operator==(NameLookupKey const & lhs, NameLookupKey const & rhs); + friend auto operator<=>(NameLookupKey const & lhs, NameLookupKey const & rhs); + }; + + auto operator==(NameLookupKey const & lhs, NameLookupKey const & rhs) { + return lhs.physicalQuantity == rhs.physicalQuantity && lhs.lowerCaseUnitName == rhs.lowerCaseUnitName; + } + + auto operator<=>(NameLookupKey const & lhs, NameLookupKey const & rhs) { + if (lhs.physicalQuantity < rhs.physicalQuantity) { return std::strong_ordering::less; } + if (lhs.physicalQuantity > rhs.physicalQuantity) { return std::strong_ordering::greater; } + if (lhs.lowerCaseUnitName < rhs.lowerCaseUnitName) { return std::strong_ordering::less; } + if (lhs.lowerCaseUnitName > rhs.lowerCaseUnitName) { return std::strong_ordering::greater; } + return std::strong_ordering::equal; + } + QMultiMap unitNameLookup; QMap physicalQuantityToCanonicalUnit; @@ -129,7 +133,8 @@ namespace { std::call_once(initFlag_Lookups, &Measurement::Unit::initialiseLookups); QList allMatches; - for (auto key : unitNameLookup.uniqueKeys()) { + auto const & keys {unitNameLookup.uniqueKeys()}; + for (auto const & key : keys) { // // Although it's slightly clunky to call getUnitsByNameAndPhysicalQuantity() here, it saves us re-implementing // all the case-insensitive logic. @@ -275,8 +280,9 @@ std::pair Measurement::Unit::splitAmountString(QString const & *ok = false; } - // All functions in QRegExp are reentrant, so it should be safe to use as a shared const in multi-threaded code. - static QRegExp const amtUnit { + // All functions in QRegularExpression are reentrant, so it should be safe to use as a shared const in multi-threaded + // code. + static QRegularExpression const amtUnit { // // For the numeric part (the quantity) we need to make sure we get the right decimal point (. or ,) and the right // grouping separator (, or .). Some locales write 1.000,10 and others write 1,000.10. We need to catch both. @@ -285,22 +291,23 @@ std::pair Measurement::Unit::splitAmountString(QString const & // This was fine when we had "simple" unit names such as "kg" and "floz", but it breaks for names containing // symbols, such as "L/kg" or "c/g·C". Instead, we have to match non-space characters // - "((?:\\d+" + QRegExp::escape(Localization::getLocale().groupSeparator()) + ")?\\d+(?:" + - QRegExp::escape(Localization::getLocale().decimalPoint()) + "\\d+)?|" + - QRegExp::escape(Localization::getLocale().decimalPoint()) + "\\d+)\\s*([^\\s]+)?", - Qt::CaseInsensitive + "((?:\\d+" + QRegularExpression::escape(Localization::getLocale().groupSeparator()) + ")?\\d+(?:" + + QRegularExpression::escape(Localization::getLocale().decimalPoint()) + "\\d+)?|" + + QRegularExpression::escape(Localization::getLocale().decimalPoint()) + "\\d+)\\s*([^\\s]+)?", + QRegularExpression::CaseInsensitiveOption }; // Make sure we can parse the string - if (amtUnit.indexIn(inputString) == -1) { + QRegularExpressionMatch match = amtUnit.match(inputString); + if (!match.hasMatch()) { qDebug() << Q_FUNC_INFO << "Unable to parse" << inputString << "so treating as 0.0"; return std::pair{0.0, ""}; } - QString const unitName = amtUnit.cap(2); + QString const unitName = match.captured(2); double quantity = 0.0; - QString numericPartOfInput{amtUnit.cap(1)}; + QString numericPartOfInput{match.captured(1)}; try { quantity = Localization::toDouble(numericPartOfInput, Q_FUNC_INFO); // If we didn't throw an exception then all must finally be OK! diff --git a/src/measurement/UnitSystem.cpp b/src/measurement/UnitSystem.cpp index 6a516034f..c0314a521 100644 --- a/src/measurement/UnitSystem.cpp +++ b/src/measurement/UnitSystem.cpp @@ -21,7 +21,6 @@ #include #include -#include #include "Localization.h" #include "measurement/Unit.h" diff --git a/src/model/NamedEntity.cpp b/src/model/NamedEntity.cpp index ec31a1947..228aa22d2 100644 --- a/src/model/NamedEntity.cpp +++ b/src/model/NamedEntity.cpp @@ -186,14 +186,14 @@ void NamedEntity::makeChild(NamedEntity const & copiedFrom) { return; } -QRegExp const & NamedEntity::getDuplicateNameNumberMatcher() { +QRegularExpression const & NamedEntity::getDuplicateNameNumberMatcher() { // // Note that, in the regexp, to match a bracket, we need to escape it, thus "\(" instead of "(". However, we // must also escape the backslash so that the C++ compiler doesn't think we want a special character (such as // '\n') and barf a "unknown escape sequence" warning at us. So "\\(" is needed in the string literal here to // pass "\(" to the regexp to match literal "(" (and similarly for close bracket). // - static QRegExp const duplicateNameNumberMatcher{" *\\(([0-9]+)\\)$"}; + static QRegularExpression const duplicateNameNumberMatcher{" *\\(([0-9]+)\\)$"}; return duplicateNameNumberMatcher; } @@ -226,13 +226,13 @@ bool NamedEntity::operator==(NamedEntity const & other) const { // "Tettnang" and another called "Tettnang (1)" we wouldn't say they are different just because of the names. // So we want to strip off any number in brackets at the ends of the names and then compare again. // - QRegExp const & duplicateNameNumberMatcher = NamedEntity::getDuplicateNameNumberMatcher(); + QRegularExpression const & duplicateNameNumberMatcher = NamedEntity::getDuplicateNameNumberMatcher(); QString names[2] {this->m_name, other.m_name}; for (auto ii = 0; ii < 2; ++ii) { - int positionOfMatch = duplicateNameNumberMatcher.indexIn(names[ii]); - if (positionOfMatch > -1) { + QRegularExpressionMatch match = duplicateNameNumberMatcher.match(names[ii]); + if (match.hasMatch()) { // There's some integer in brackets at the end of the name. Chop it off. - names[ii].truncate(positionOfMatch); + names[ii].truncate(match.capturedStart(1)); } } // qDebug() << Q_FUNC_INFO << "Adjusted names to " << names[0] << " & " << names[1]; diff --git a/src/model/NamedEntity.h b/src/model/NamedEntity.h index 3e2066572..90f90f79f 100644 --- a/src/model/NamedEntity.h +++ b/src/model/NamedEntity.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "model/FolderBase.h" @@ -255,7 +256,7 @@ class NamedEntity : public QObject { * \brief Returns a regexp that will match the " (n)" (for n some positive integer) added on the end of a name to * prevent name clashes. It will also "capture" n to allow you to extract it. */ - static QRegExp const & getDuplicateNameNumberMatcher(); + static QRegularExpression const & getDuplicateNameNumberMatcher(); void setName(QString const & var); void setDeleted(bool const var); diff --git a/src/model/Recipe.h b/src/model/Recipe.h index 5f65ece07..2d2cea0ff 100644 --- a/src/model/Recipe.h +++ b/src/model/Recipe.h @@ -39,7 +39,9 @@ #include "database/ObjectStoreWrapper.h" #include "model/BrewNote.h" #include "model/FolderBase.h" +#include "model/Instruction.h" #include "model/NamedEntity.h" +#include "model/Salt.h" //====================================================================================================================== //========================================== Start of property name constants ========================================== @@ -115,7 +117,6 @@ AddPropertyName(yeastAdditions ) //=========================================== End of property name constants =========================================== //====================================================================================================================== - // Forward declarations class Boil; class BoilStep; @@ -132,7 +133,6 @@ class RecipeAdditionMisc; class RecipeAdjustmentSalt; class RecipeAdditionYeast; class RecipeUseOfWater; -class Salt; class Style; class Water; class Yeast; diff --git a/src/serialization/SerializationRecord.cpp b/src/serialization/SerializationRecord.cpp index 6f7b9e5a2..a31f4b1dd 100755 --- a/src/serialization/SerializationRecord.cpp +++ b/src/serialization/SerializationRecord.cpp @@ -15,6 +15,7 @@ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌*/ #include "serialization/SerializationRecord.h" +#include SerializationRecord::SerializationRecord() : m_namedParameterBundle{NamedParameterBundle::OperationMode::NotStrict}, @@ -85,13 +86,13 @@ void SerializationRecord::modifyClashingName(QString & candidateName) { // space(s) preceding the left bracket. If so, we want to replace this with " (n+1)". If not, we try " (1)". // int duplicateNumber = 1; - QRegExp const & nameNumberMatcher = NamedEntity::getDuplicateNameNumberMatcher(); - int positionOfMatch = nameNumberMatcher.indexIn(candidateName); - if (positionOfMatch > -1) { - // There's already some integer in brackets at the end of the name, extract it, add one, and truncate the - // name. - duplicateNumber = nameNumberMatcher.cap(1).toInt() + 1; - candidateName.truncate(positionOfMatch); + QRegularExpression const & nameNumberMatcher = NamedEntity::getDuplicateNameNumberMatcher(); + QRegularExpressionMatch match = nameNumberMatcher.match(candidateName); + QString matchedValue = match.captured(1); + if (matchedValue.size() > 0) { + // There's already some integer in brackets at the end of the name, extract it, add one, and truncate the name + duplicateNumber = matchedValue.toInt() + 1; + candidateName.truncate(match.capturedStart(1)); } candidateName += QString(" (%1)").arg(duplicateNumber); return; diff --git a/src/serialization/json/JsonSchema.cpp b/src/serialization/json/JsonSchema.cpp index b51cacc1f..1d3a2bc62 100755 --- a/src/serialization/json/JsonSchema.cpp +++ b/src/serialization/json/JsonSchema.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * serialization/json/JsonSchema.cpp is part of Brewtarget, and is copyright the following authors 2021-2022: + * serialization/json/JsonSchema.cpp is part of Brewtarget, and is copyright the following authors 2021-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -20,6 +20,7 @@ #include #include +#include #include #include diff --git a/src/serialization/json/JsonUtils.cpp b/src/serialization/json/JsonUtils.cpp index bf1426748..9dec076f1 100755 --- a/src/serialization/json/JsonUtils.cpp +++ b/src/serialization/json/JsonUtils.cpp @@ -226,7 +226,7 @@ void JsonUtils::serialize(std::ostream & stream, // if (ii->value().kind() == boost::json::kind::object && ii->value().get_object().size() == 0) { - qDebug() << Q_FUNC_INFO << "Skipping output of empty object for" << ii->key(); + qDebug() << Q_FUNC_INFO << "Skipping output of empty object for" << QString::fromStdString(ii->key()); continue; } diff --git a/src/serialization/json/JsonXPath.cpp b/src/serialization/json/JsonXPath.cpp index 234fe84da..03583241f 100755 --- a/src/serialization/json/JsonXPath.cpp +++ b/src/serialization/json/JsonXPath.cpp @@ -35,9 +35,14 @@ JsonXPath::JsonXPath(char const * const xPath) : m_pathParts{}, m_pathNodes{} { + // // Because we're using std::string (because it's easier to pass in and out of Boost.JSON), we use the C++ standard - // regular expressions library rather than QRegExp here. (The latter is obviously a better choice when we're - // manipulating QString objects.) + // regular expressions library rather than QRegularExpression here. (The latter is obviously a better choice when + // we're manipulating QString objects.) + // + // TBD: If we wanted to improve performance we should look at + // https://github.com/hanickadot/compile-time-regular-expressions + // // // We could, in principle, use std::cregex_iterator on the raw char * string, but it's a bit clunky because you need diff --git a/src/serialization/xml/BtDomErrorHandler.cpp b/src/serialization/xml/BtDomErrorHandler.cpp index f63348f10..a44725712 100755 --- a/src/serialization/xml/BtDomErrorHandler.cpp +++ b/src/serialization/xml/BtDomErrorHandler.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * serialization/xml/BtDomErrorHandler.cpp is part of Brewtarget, and is copyright the following authors 2020-2022: + * serialization/xml/BtDomErrorHandler.cpp is part of Brewtarget, and is copyright the following authors 2020-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -16,6 +16,7 @@ #include "serialization/xml/BtDomErrorHandler.h" #include +#include #include #include @@ -135,8 +136,9 @@ bool BtDomErrorHandler::handleError(xercesc::DOMError const & domError) { // if (nullptr != this->pimpl->errorPatternsToIgnore) { for (auto ii = this->pimpl->errorPatternsToIgnore->cbegin(); ii != this->pimpl->errorPatternsToIgnore->cend(); ++ii) { - QRegExp pattern(ii->regExMatchingErrorMessage); - if (pattern.indexIn(message) != -1) { + QRegularExpression pattern(ii->regExMatchingErrorMessage); + QRegularExpressionMatch match = pattern.match(message); + if (match.hasMatch()) { // We want to force the parse error onto a separate line, as it will be quite long, hence // ".noquote()" here. qWarning().noquote() << diff --git a/src/sortFilterProxyModels/SortFilterProxyModelBase.h b/src/sortFilterProxyModels/SortFilterProxyModelBase.h index d1023b3d6..1e25af3d6 100755 --- a/src/sortFilterProxyModels/SortFilterProxyModelBase.h +++ b/src/sortFilterProxyModels/SortFilterProxyModelBase.h @@ -1,6 +1,6 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ * sortFilterProxyModels/SortFilterProxyModelBase.h is part of Brewtarget, and is copyright the following authors - * 2023: + * 2023-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -77,8 +77,20 @@ class SortFilterProxyModelBase : public CuriouslyRecurringTemplateBaseindex(source_row, 0, source_parent); - return !m_filter || (tableModel->data(index).toString().contains(this->derived().filterRegExp()) && - tableModel->getRow(source_row)->display()); + if (!this->m_filter) { + // No filter, so we accept + return true; + } + if (!tableModel->getRow(source_row)->display()) { + // Row not displayed, so reject + return false; + } + + // The filterRegularExpression() member function we call here is inherited from QSortFilterProxyModel + QRegularExpression const filterRegExp {this->derived().filterRegularExpression()}; + QString const dataAsString {tableModel->data(index).toString()}; + QRegularExpressionMatch const match {filterRegExp.match(dataAsString)}; + return match.hasMatch(); } NeListModel* listModel = qobject_cast(this->derived().sourceModel()); diff --git a/src/tableModels/StepTableModelBase.h b/src/tableModels/StepTableModelBase.h index d098eff93..0bf6f5ed6 100755 --- a/src/tableModels/StepTableModelBase.h +++ b/src/tableModels/StepTableModelBase.h @@ -101,7 +101,7 @@ class StepTableModelBase : public CuriouslyRecurringTemplateBase step) { - int ii {this->derived().rows.indexOf(step)}; + int ii {static_cast(this->derived().rows.indexOf(step))}; if (ii >= 0) { qDebug() << Q_FUNC_INFO << "Removing" << StepClass::staticMetaObject.className() << step->name() << "(#" << diff --git a/src/tableModels/TableModelBase.h b/src/tableModels/TableModelBase.h index 8df51b123..605b07e93 100755 --- a/src/tableModels/TableModelBase.h +++ b/src/tableModels/TableModelBase.h @@ -737,7 +737,7 @@ class TableModelBase : public CuriouslyRecurringTemplateBase()); // // For cases where we have an Amount and a drop-down chooser to select PhysicalQuantity (eg between Mass and diff --git a/src/trees/TreeModel.cpp b/src/trees/TreeModel.cpp index 5a57cb620..842fe68d4 100644 --- a/src/trees/TreeModel.cpp +++ b/src/trees/TreeModel.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -989,7 +990,7 @@ QModelIndex TreeModel::createFolderTree(QStringList dirs, TreeNode * parent, QSt fPath = pPath % "/" % cur; // If it isn't we need the parent path } - fPath.replace(QRegExp("//"), "/"); + fPath.replace(QRegularExpression("//"), "/"); // Set the full path, which will set the name and the path temp->setfullPath(fPath); diff --git a/src/unitTests/Testing.cpp b/src/unitTests/Testing.cpp index 8c58c898f..1ba434a74 100644 --- a/src/unitTests/Testing.cpp +++ b/src/unitTests/Testing.cpp @@ -885,7 +885,7 @@ void Testing::testLogRotation() { qDebug() << Q_FUNC_INFO << "Logging::logFileSize =" << Logging::logFileSize; for (int i = 0; i < fileList.size(); i++) { QFile f(QString(fileList.at(i).canonicalFilePath())); - qDebug() << Q_FUNC_INFO << "File" << f << "has size" << f.size(); + qDebug() << Q_FUNC_INFO << "File" << f.fileName() << "has size" << f.size(); //Here we test if the file is more than 10% bigger than the specified logFileSize", if so, fail. QVERIFY2(f.size() <= (Logging::logFileSize * 1.1), "Wrong Sized file"); } diff --git a/src/widgets/SelectionControl.h b/src/widgets/SelectionControl.h index 55e397894..092a100cf 100644 --- a/src/widgets/SelectionControl.h +++ b/src/widgets/SelectionControl.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * widgets/SelectionControl.h is is part of Brewtarget, and is copyright the following authors 2018-2021: + * widgets/SelectionControl.h is is part of Brewtarget, and is copyright the following authors 2018-2024: * • Iman Ahmadvand * • Matt Young * @@ -39,7 +39,7 @@ class SelectionControl : public QAbstractButton { void stateChanged(int); protected: - void enterEvent(QEvent *) override; + virtual void enterEvent(QEvent *) override; void checkStateSet() override; void nextCheckState() override; virtual void toggle(Qt::CheckState state) = 0; diff --git a/src/widgets/SmartLabel.h b/src/widgets/SmartLabel.h index dc897dabf..a3412eb12 100644 --- a/src/widgets/SmartLabel.h +++ b/src/widgets/SmartLabel.h @@ -183,14 +183,14 @@ class SmartLabel : public QLabel, public SmartBase { * \brief We override the \c QWidget event handlers \c enterEvent and \c leaveEvent to implement mouse-over effects * on the label text - specifically to give the user a visual clue that the label text is (right)-clickable */ - virtual void enterEvent(QEvent* event); - virtual void leaveEvent(QEvent* event); + virtual void enterEvent(QEvent* event) override; + virtual void leaveEvent(QEvent* event) override; /** * \brief We override the \c QWidget event handler \c mouseReleaseEvent to capture left mouse clicks on us. (Right * clicks get notified to us via the \c QWidget::customContextMenuRequested signal.) */ - virtual void mouseReleaseEvent(QMouseEvent * event); + virtual void mouseReleaseEvent(QMouseEvent * event) override; private: diff --git a/src/widgets/UnitAndScalePopUpMenu.cpp b/src/widgets/UnitAndScalePopUpMenu.cpp index cdaf9d434..e8fe9d06b 100644 --- a/src/widgets/UnitAndScalePopUpMenu.cpp +++ b/src/widgets/UnitAndScalePopUpMenu.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * widgets/UnitAndScalePopUpMenu.cpp is part of Brewtarget, and is copyright the following authors 2012-2023: + * widgets/UnitAndScalePopUpMenu.cpp is part of Brewtarget, and is copyright the following authors 2012-2024: * • Mark de Wever * • Matt Young * • Mik Firestone @@ -17,6 +17,7 @@ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌*/ #include "widgets/UnitAndScalePopUpMenu.h" +#include #include #include diff --git a/ui/waterEditor.ui b/ui/waterEditor.ui index b43ea65bb..d8cc9e810 100644 --- a/ui/waterEditor.ui +++ b/ui/waterEditor.ui @@ -532,42 +532,7 @@ lineEdit_so4 lineEdit_alk lineEdit_ph - plainTextEdit_notes - buttonBox + textEdit_notes - - - buttonBox - accepted() - waterEditor - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - waterEditor - reject() - - - 316 - 260 - - - 286 - 274 - - - -