subsurface/desktop-widgets/filterconstraintwidget.cpp
Berthold Stoeger 2d7be7a0e3 preferences: create global settingsChanged signal
So far, the PreferencesDialog emitted a settingsChanged signal.
This meant that models that listened to that signal had to
conditionally compile out the code for mobile or the connection
had to be made in MainWindow.

Instead, introduce a global signal that does this and move
the connects to the listeners to remove inter-dependencies.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-12-12 15:52:40 -08:00

476 lines
16 KiB
C++

// SPDX-License-Identifier: GPL-2.0
#include "filterconstraintwidget.h"
#include "starwidget.h"
#include "core/pref.h"
#include "core/subsurface-qt/divelistnotifier.h"
#include "qt-models/cleanertablemodel.h" // for trashIcon()
#include "qt-models/filterconstraintmodel.h"
#include <QComboBox>
#include <QDateEdit>
#include <QDoubleSpinBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTimeEdit>
// Helper function to get enums through Qt's variants
template<typename T>
static T getEnum(const QModelIndex &idx, int role)
{
return static_cast<T>(idx.data(role).value<int>());
}
// Helper function, which creates a combo box for a given model role
// or returns null if the model doesn't return a proper list.
static QComboBox *makeCombo(const QModelIndex &index, int role)
{
QStringList list = index.data(role).value<QStringList>();
if (list.isEmpty())
return nullptr;
QComboBox *res = new QComboBox;
res->addItems(list);
res->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
return res;
}
// Helper function, which creates a multiple choice list for a given model role
// or returns null if the model doesn't return a proper list.
static QListWidget *makeMultipleChoice(const QModelIndex &index, int role)
{
QStringList list = index.data(role).value<QStringList>();
if (list.isEmpty())
return nullptr;
QListWidget *res = new QListWidget;
res->addItems(list);
res->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum);
res->setSelectionMode(QAbstractItemView::ExtendedSelection);
res->setFixedSize(res->sizeHintForColumn(0) + 2 * res->frameWidth(), res->sizeHintForRow(0) * res->count() + 2 * res->frameWidth());
return res;
}
// Helper function to create a floating point spin box.
// Currently, this allows a large range of values.
// The limits should be adapted to the constraint in question.
static QDoubleSpinBox *makeSpinBox(int numDecimals)
{
QDoubleSpinBox *res = new QDoubleSpinBox;
res->setRange(-10000.0, 10000.0);
res->setDecimals(numDecimals);
res->setSingleStep(pow(10.0, -numDecimals));
return res;
}
// Helper function to create a date edit widget
static QDateEdit *makeDateEdit()
{
QDateEdit *res = new QDateEdit;
res->setCalendarPopup(true);
res->setTimeSpec(Qt::UTC);
return res;
}
// Helper function to create a date edit widget
static QTimeEdit *makeTimeEdit()
{
QTimeEdit *res = new QTimeEdit;
res->setTimeSpec(Qt::UTC);
return res;
}
// Helper function, which creates a label with a given string
// or returns null if the string is empty.
static QLabel *makeLabel(const QString &s)
{
if (s.isEmpty())
return nullptr;
return new QLabel(s);
}
// Helper function, which sets the index of a combo box for a given
// model role, if the combo box is not null.
static void setIndex(QComboBox *w, const QModelIndex &index, int role)
{
if (!w)
return;
w->setCurrentIndex(index.data(role).value<int>());
}
// Helper function to add a widget to a layout if it is non-null
static void addWidgetToLayout(QWidget *w, QHBoxLayout *l)
{
if (w) {
l->addWidget(w);
l->setAlignment(w, Qt::AlignLeft);
}
}
// Helper functions that show / hide non-null widgets
static void showWidget(QWidget *w)
{
if (w)
w->show();
}
// Helper functions that show / hide non-null widgets
static void hideWidget(QWidget *w)
{
if (w)
w->hide();
}
// Helper function to create datatimes from either a single date widget,
// or a combination of a date and a time widget.
static QDateTime makeDateTime(const QDateEdit *d, const QTimeEdit *t)
{
if (!d)
return QDateTime();
if (!t)
return d->dateTime();
QDateTime res = d->dateTime();
res.setTime(t->time());
return res;
}
FilterConstraintWidget::FilterConstraintWidget(FilterConstraintModel *modelIn, const QModelIndex &index, QGridLayout *layoutIn) : QObject(layoutIn),
layout(layoutIn),
model(modelIn),
row(index.row()),
type(getEnum<filter_constraint_type>(index, FilterConstraintModel::TYPE_ROLE))
{
rangeLayout.reset(new QHBoxLayout);
rangeLayout->setAlignment(Qt::AlignLeft);
trashButton.reset(new QPushButton(QIcon(trashIcon()), QString()));
trashButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
trashButton->setToolTip(tr("Click to remove this constraint"));
connect(trashButton.get(), &QPushButton::clicked, this, &FilterConstraintWidget::trash);
typeLabel.reset(new QLabel(index.data(FilterConstraintModel::TYPE_DISPLAY_ROLE).value<QString>()));
typeLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QString unitString = index.data(FilterConstraintModel::UNIT_ROLE).value<QString>();
negate.reset(makeCombo(index, FilterConstraintModel::NEGATE_COMBO_ROLE));
connect(negate.get(), QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FilterConstraintWidget::negateEdited);
stringMode.reset(makeCombo(index, FilterConstraintModel::STRING_MODE_COMBO_ROLE));
rangeMode.reset(makeCombo(index, FilterConstraintModel::RANGE_MODE_COMBO_ROLE));
multipleChoice.reset(makeMultipleChoice(index, FilterConstraintModel::MULTIPLE_CHOICE_LIST_ROLE));
bool isStarWidget = index.data(FilterConstraintModel::IS_STAR_WIDGET_ROLE).value<bool>();
bool hasDateWidget = index.data(FilterConstraintModel::HAS_DATE_WIDGET_ROLE).value<bool>();
bool hasTimeWidget = index.data(FilterConstraintModel::HAS_TIME_WIDGET_ROLE).value<bool>();
bool hasSpinBox = rangeMode && !stringMode && !hasDateWidget && !hasTimeWidget && !isStarWidget && !multipleChoice;
unitFrom.reset(makeLabel(unitString));
unitTo.reset(makeLabel(unitString));
if (stringMode) {
string.reset(new QLineEdit);
string->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
}
int numDecimals = hasSpinBox ? index.data(FilterConstraintModel::NUM_DECIMALS_ROLE).value<int>() : 0;
if (string)
connect(string.get(), &QLineEdit::textEdited, this, &FilterConstraintWidget::stringEdited);
if (multipleChoice)
connect(multipleChoice.get(), &QListWidget::itemSelectionChanged, this, &FilterConstraintWidget::multipleChoiceEdited);
if (stringMode)
connect(stringMode.get(), QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FilterConstraintWidget::stringModeEdited);
if (rangeMode) {
toLabel.reset(new QLabel(tr("and")));
connect(rangeMode.get(), QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FilterConstraintWidget::rangeModeEdited);
}
if (hasSpinBox) {
spinBoxFrom.reset(makeSpinBox(numDecimals));
spinBoxTo.reset(makeSpinBox(numDecimals));
connect(spinBoxFrom.get(), QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &FilterConstraintWidget::fromEditedFloat);
connect(spinBoxTo.get(), QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &FilterConstraintWidget::toEditedFloat);
}
if (isStarWidget) {
starFrom.reset(new StarWidget);
starTo.reset(new StarWidget);
connect(starFrom.get(), &StarWidget::valueChanged, this, &FilterConstraintWidget::fromEditedInt);
connect(starTo.get(), &StarWidget::valueChanged, this, &FilterConstraintWidget::toEditedInt);
}
if (hasDateWidget) {
dateFrom.reset(makeDateEdit());
dateTo.reset(makeDateEdit());
connect(dateFrom.get(), &QDateEdit::dateTimeChanged, this, &FilterConstraintWidget::fromEditedTimestamp);
connect(dateTo.get(), &QDateEdit::dateTimeChanged, this, &FilterConstraintWidget::toEditedTimestamp);
}
if (hasTimeWidget) {
timeFrom.reset(makeTimeEdit());
timeTo.reset(makeTimeEdit());
connect(timeFrom.get(), &QTimeEdit::dateTimeChanged, this, &FilterConstraintWidget::fromEditedTimestamp);
connect(timeTo.get(), &QTimeEdit::dateTimeChanged, this, &FilterConstraintWidget::toEditedTimestamp);
}
addWidgetToLayout(string.get(), rangeLayout.get());
addWidgetToLayout(starFrom.get(), rangeLayout.get());
addWidgetToLayout(spinBoxFrom.get(), rangeLayout.get());
addWidgetToLayout(dateFrom.get(), rangeLayout.get());
addWidgetToLayout(timeFrom.get(), rangeLayout.get());
addWidgetToLayout(unitFrom.get(), rangeLayout.get());
addWidgetToLayout(toLabel.get(), rangeLayout.get());
addWidgetToLayout(starTo.get(), rangeLayout.get());
addWidgetToLayout(spinBoxTo.get(), rangeLayout.get());
addWidgetToLayout(dateTo.get(), rangeLayout.get());
addWidgetToLayout(timeTo.get(), rangeLayout.get());
addWidgetToLayout(unitTo.get(), rangeLayout.get());
rangeLayout->addStretch();
// Update the widget if the settings changed to reflect new units.
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, this, &FilterConstraintWidget::update);
addToLayout();
update();
}
FilterConstraintWidget::~FilterConstraintWidget()
{
}
void FilterConstraintWidget::addToLayout()
{
// Careful: this must mirror the code in removeFromLayout() or weird things will happen.
// Note: we add 2 to row, because the first and second row of the filter layout
// are reserved for the title line and the fulltext widget!
int layoutRow = row + 2;
layout->addWidget(trashButton.get(), layoutRow, 0);
if (!stringMode && !rangeMode && !multipleChoice) {
// If there are no string or range modes, we rearrange to "negate / type" to get
// a layout of the type "is not planned" or "is not logged", where the subject is
// implicitly "dive". This presumes SVO grammar, but so does the rest of the layout.
layout->addWidget(negate.get(), layoutRow, 1, Qt::AlignLeft);
layout->addWidget(typeLabel.get(), layoutRow, 2, Qt::AlignLeft);
} else {
layout->addWidget(negate.get(), layoutRow, 2, Qt::AlignLeft);
layout->addWidget(typeLabel.get(), layoutRow, 1, Qt::AlignLeft);
}
if (stringMode)
layout->addWidget(stringMode.get(), layoutRow, 3, Qt::AlignLeft);
else if (rangeMode)
layout->addWidget(rangeMode.get(), layoutRow, 3, Qt::AlignLeft);
else if (multipleChoice)
layout->addWidget(multipleChoice.get(), layoutRow, 3, 1, 2, Qt::AlignLeft); // column span 2
if (!multipleChoice)
layout->addLayout(rangeLayout.get(), layoutRow, 4, Qt::AlignLeft);
}
void FilterConstraintWidget::removeFromLayout()
{
// Careful: this must mirror the code in addToLayout() or weird things will happen.
layout->removeWidget(trashButton.get());
layout->removeWidget(negate.get());
layout->removeWidget(typeLabel.get());
if (stringMode)
layout->removeWidget(stringMode.get());
else if (rangeMode)
layout->removeWidget(rangeMode.get());
else if (multipleChoice)
layout->removeWidget(multipleChoice.get());
if (!multipleChoice)
layout->removeItem(rangeLayout.get());
}
void FilterConstraintWidget::update()
{
// The user might have changed the date and/or time format. Let's update the widgets.
if (dateFrom)
dateFrom->setDisplayFormat(prefs.date_format);
if (dateTo)
dateTo->setDisplayFormat(prefs.date_format);
if (timeFrom)
timeFrom->setDisplayFormat(prefs.time_format);
if (timeTo)
timeTo->setDisplayFormat(prefs.time_format);
QModelIndex idx = model->index(row, 0);
setIndex(negate.get(), idx, FilterConstraintModel::NEGATE_INDEX_ROLE);
setIndex(stringMode.get(), idx, FilterConstraintModel::STRING_MODE_INDEX_ROLE);
setIndex(rangeMode.get(), idx, FilterConstraintModel::RANGE_MODE_INDEX_ROLE);
if (string)
string->setText(idx.data(FilterConstraintModel::STRING_ROLE).value<QString>());
if (starFrom)
starFrom->setCurrentStars(idx.data(FilterConstraintModel::INTEGER_FROM_ROLE).value<int>());
if (starTo)
starTo->setCurrentStars(idx.data(FilterConstraintModel::INTEGER_TO_ROLE).value<int>());
if (spinBoxFrom)
spinBoxFrom->setValue(idx.data(FilterConstraintModel::FLOAT_FROM_ROLE).value<double>());
if (spinBoxTo)
spinBoxTo->setValue(idx.data(FilterConstraintModel::FLOAT_TO_ROLE).value<double>());
if (dateFrom) {
QDateTime dateTime = idx.data(FilterConstraintModel::TIMESTAMP_FROM_ROLE).value<QDateTime>();
dateFrom->setDateTime(dateTime);
if (timeFrom)
timeFrom->setDateTime(dateTime);
} else if (timeFrom) {
timeFrom->setTime(idx.data(FilterConstraintModel::TIME_FROM_ROLE).value<QTime>());
}
if (dateTo) {
QDateTime dateTime = idx.data(FilterConstraintModel::TIMESTAMP_TO_ROLE).value<QDateTime>();
dateTo->setDateTime(dateTime);
if (timeTo)
timeTo->setDateTime(dateTime);
} else if (timeTo) {
timeTo->setTime(idx.data(FilterConstraintModel::INTEGER_TO_ROLE).value<QTime>());
}
if (multipleChoice) {
uint64_t bits = idx.data(FilterConstraintModel::MULTIPLE_CHOICE_ROLE).value<uint64_t>();
for (int i = 0; i < multipleChoice->count(); ++i)
multipleChoice->item(i)->setSelected(bits & (1ULL << i));
}
// Update the unit strings in case the locale was changed
if (unitFrom || unitTo) {
QString unitString = idx.data(FilterConstraintModel::UNIT_ROLE).value<QString>();
unitFrom->setText(unitString);
unitTo->setText(unitString);
}
if (rangeMode) {
switch(getEnum<filter_constraint_range_mode>(idx, FilterConstraintModel::RANGE_MODE_ROLE)) {
case FILTER_CONSTRAINT_LESS:
hideWidget(toLabel.get());
hideWidget(starFrom.get());
hideWidget(spinBoxFrom.get());
hideWidget(dateFrom.get());
hideWidget(timeFrom.get());
hideWidget(unitFrom.get());
showWidget(starTo.get());
showWidget(spinBoxTo.get());
showWidget(dateTo.get());
showWidget(timeTo.get());
showWidget(unitTo.get());
break;
case FILTER_CONSTRAINT_EQUAL:
case FILTER_CONSTRAINT_GREATER:
hideWidget(toLabel.get());
showWidget(starFrom.get());
showWidget(spinBoxFrom.get());
showWidget(dateFrom.get());
showWidget(timeFrom.get());
showWidget(unitFrom.get());
hideWidget(starTo.get());
hideWidget(spinBoxTo.get());
hideWidget(dateTo.get());
hideWidget(timeTo.get());
hideWidget(unitTo.get());
break;
case FILTER_CONSTRAINT_RANGE:
showWidget(toLabel.get());
showWidget(starFrom.get());
showWidget(spinBoxFrom.get());
showWidget(dateFrom.get());
showWidget(timeFrom.get());
showWidget(unitFrom.get());
showWidget(starTo.get());
showWidget(spinBoxTo.get());
showWidget(dateTo.get());
showWidget(timeTo.get());
showWidget(unitTo.get());
break;
}
}
}
void FilterConstraintWidget::stringEdited(const QString &s)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, s, FilterConstraintModel::STRING_ROLE);
}
void FilterConstraintWidget::multipleChoiceEdited()
{
// Turn selected items into bit-field
uint64_t bits = 0;
for (const QModelIndex &idx: multipleChoice->selectionModel()->selectedIndexes()) {
int row = idx.row();
if (row >= 64) {
qWarning("FilterConstraint: multiple-choice with more than 64 entries not supported");
continue;
}
bits |= 1ULL << row;
}
QModelIndex idx = model->index(row, 0);
model->setData(idx, qulonglong(bits), FilterConstraintModel::MULTIPLE_CHOICE_ROLE);
}
void FilterConstraintWidget::fromEditedInt(int i)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, i, FilterConstraintModel::INTEGER_FROM_ROLE);
}
void FilterConstraintWidget::toEditedInt(int i)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, i, FilterConstraintModel::INTEGER_TO_ROLE);
}
void FilterConstraintWidget::fromEditedFloat(double f)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, f, FilterConstraintModel::FLOAT_FROM_ROLE);
}
void FilterConstraintWidget::toEditedFloat(double f)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, f, FilterConstraintModel::FLOAT_TO_ROLE);
}
void FilterConstraintWidget::fromEditedTimestamp(const QDateTime &datetime)
{
QModelIndex idx = model->index(row, 0);
if (!dateFrom && timeFrom)
model->setData(idx, timeFrom->time(), FilterConstraintModel::TIME_FROM_ROLE);
else
model->setData(idx, makeDateTime(dateFrom.get(), timeFrom.get()), FilterConstraintModel::TIMESTAMP_FROM_ROLE);
}
void FilterConstraintWidget::toEditedTimestamp(const QDateTime &datetime)
{
QModelIndex idx = model->index(row, 0);
if (!dateTo && timeTo)
model->setData(idx, timeTo->time(), FilterConstraintModel::TIME_TO_ROLE);
else
model->setData(idx, makeDateTime(dateTo.get(), timeTo.get()), FilterConstraintModel::TIMESTAMP_TO_ROLE);
}
void FilterConstraintWidget::negateEdited(int i)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, i, FilterConstraintModel::NEGATE_INDEX_ROLE);
}
void FilterConstraintWidget::rangeModeEdited(int i)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, i, FilterConstraintModel::RANGE_MODE_INDEX_ROLE);
update(); // Range mode may change the shown widgets
}
void FilterConstraintWidget::stringModeEdited(int i)
{
QModelIndex idx = model->index(row, 0);
model->setData(idx, i, FilterConstraintModel::STRING_MODE_INDEX_ROLE);
}
void FilterConstraintWidget::moveToRow(int rowIn)
{
removeFromLayout();
row = rowIn;
addToLayout();
}
void FilterConstraintWidget::trash()
{
model->deleteConstraint(row);
}