mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-01 06:30:26 +00:00
2d7be7a0e3
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>
476 lines
16 KiB
C++
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);
|
|
}
|