Move workout filter to the toolbar (#4596)

Now the search/filter box in the toolbar depends on the active view:
- No search/filter box for Athletes view
- Activities search/filter box for Activities/Trends/Diary views
- Workout filter box for Train, view styled like SearchBox
   and applied when editing finished
Fixes #4591
This commit is contained in:
Alejandro Martinez
2025-01-24 15:48:26 -03:00
committed by GitHub
parent ff240a0dd8
commit 9cf362559e
10 changed files with 212 additions and 46 deletions

View File

@@ -72,6 +72,7 @@ class AthleteTab;
class NavigationModel;
class RideMetadata;
class ColorEngine;
class ModelFilter;
class QWebEngineProfile;
@@ -120,6 +121,7 @@ class Context : public QObject
bool showSidebar, showLowbar, showToolbar, showTabbar;
int style;
QString searchText;
QString workoutFilterText;
bool scopehighlighted;
// ride item
@@ -189,6 +191,9 @@ class Context : public QObject
void setFilter(QStringList&f) { filters=f; isfiltered=true; emit filterChanged(); }
void clearFilter() { filters.clear(); isfiltered=false; emit filterChanged(); }
void setWorkoutFilters(QList<ModelFilter*> &f) { emit workoutFiltersChanged(f); }
void clearWorkoutFilters() { emit workoutFiltersRemoved(); }
// user metrics - cascade
void notifyUserMetricsChanged() { emit userMetricsChanged(); }
@@ -273,6 +278,10 @@ class Context : public QObject
void filterChanged();
void homeFilterChanged();
// workout filters
void workoutFiltersChanged(QList<ModelFilter*>&);
void workoutFiltersRemoved();
void workoutsChanged(); // added or deleted a workout in train view
void VideoSyncChanged(); // added or deleted a workout in train view
void presetsChanged();

View File

@@ -89,6 +89,8 @@ HelpWhatsThis::getText(GCHelp chapter) {
return text.arg("Tool%20Bar_Functions#forward").arg(tr("Navigate forward"));
case ToolBar_PerspectiveSelector:
return text.arg("Tool%20Bar_Functions#perspective-selector").arg(tr("Select active perspective for the current view, create new perspectives and manage existing ones"));
case ToolBar_WorkoutFilterBox:
return text.arg("Tool%20Bar-WorkoutFilterBox").arg(tr("Entry field for Searching and Filtering of workouts"));
case ToolBar_ToggleSidebar:
return text.arg("Tool%20Bar_Functions#side-bar").arg(tr("Activate / De-activate the Sidebar - which provides different sub-sections to select data shown in the main view"));
case ToolBar_ToggleComparePane:

View File

@@ -44,6 +44,7 @@ Q_OBJECT
ToolBar_Back,
ToolBar_Forward,
ToolBar_PerspectiveSelector,
ToolBar_WorkoutFilterBox,
ToolBar_ToggleSidebar,
ToolBar_ToggleComparePane,
ToolBar_TabTile,

View File

@@ -93,6 +93,7 @@
// SEARCH / FILTER
#include "NamedSearch.h"
#include "SearchFilterBox.h"
#include "WorkoutFilterBox.h"
// LTM CHART DRAG/DROP PARSE
#include "LTMChartParser.h"
@@ -328,7 +329,7 @@ MainWindow::MainWindow(const QDir &home)
whatsthis->setToolTip(tr("What's This?"));
connect(whatsthis, SIGNAL(clicked(bool)), this, SLOT(enterWhatsThisMode()));
// add a search box on far right, but with a little space too
// Perspective selector
perspectiveSelector = new QComboBox(this);
perspectiveSelector->setStyle(toolStyle);
perspectiveSelector->setFixedWidth(200 * dpiXFactor);
@@ -337,12 +338,22 @@ MainWindow::MainWindow(const QDir &home)
HelpWhatsThis *helpPerspectiveSelector = new HelpWhatsThis(perspectiveSelector);
perspectiveSelector->setWhatsThis(helpPerspectiveSelector->getWhatsThisText(HelpWhatsThis::ToolBar_PerspectiveSelector));
// Search/Filter box
searchBox = new SearchFilterBox(this,context,false);
searchBox->setStyle(toolStyle);
searchBox->setFixedWidth(400 * dpiXFactor);
searchBox->setFixedHeight(gl_toolheight * dpiYFactor);
// Workout Filter Box
workoutFilterBox = new WorkoutFilterBox(this, context);
workoutFilterBox->setStyle(toolStyle);
workoutFilterBox->setFixedWidth(400 * dpiXFactor);
workoutFilterBox->setFixedHeight(gl_toolheight * dpiYFactor);
HelpWhatsThis *helpWorkoutFilterBox = new HelpWhatsThis(workoutFilterBox);
workoutFilterBox->setWhatsThis(helpWorkoutFilterBox->getWhatsThisText(HelpWhatsThis::ToolBar_WorkoutFilterBox));
QWidget *space = new QWidget(this);
space->setAutoFillBackground(false);
space->setFixedWidth(5 * dpiYFactor);
@@ -352,9 +363,6 @@ MainWindow::MainWindow(const QDir &home)
head->addWidget(forward);
head->addWidget(perspectiveSelector);
head->addStretch();
head->addWidget(sidelist);
head->addWidget(lowbar);
head->addWidget(tabtile);
#ifdef Q_OS_MAC // no menu on mac, so lets have some breathing space
head->setFixedHeight(searchBox->height() + (20 *dpiXFactor * 2));
#else
@@ -366,10 +374,14 @@ MainWindow::MainWindow(const QDir &home)
HelpWhatsThis *helpSearchBox = new HelpWhatsThis(searchBox);
searchBox->setWhatsThis(helpSearchBox->getWhatsThisText(HelpWhatsThis::SearchFilterBox));
head->addWidget(searchBox);
head->addWidget(workoutFilterBox);
space = new Spacer(this);
space->setFixedWidth(5 *dpiYFactor);
head->addWidget(space);
head->addWidget(searchBox);
head->addWidget(sidelist);
head->addWidget(lowbar);
head->addWidget(tabtile);
space = new Spacer(this);
space->setFixedWidth(5 *dpiYFactor);
head->addWidget(space);
@@ -1279,6 +1291,8 @@ MainWindow::selectAthlete()
{
viewStack->setCurrentIndex(0);
perspectiveSelector->hide();
searchBox->hide();
workoutFilterBox->hide();
}
void
@@ -1290,6 +1304,8 @@ MainWindow::selectAnalysis()
sidebar->setItemSelected(3, true);
currentAthleteTab->selectView(1);
perspectiveSelector->show();
searchBox->show();
workoutFilterBox->hide();
setToolButtons();
}
@@ -1302,6 +1318,8 @@ MainWindow::selectTrain()
sidebar->setItemSelected(5, true);
currentAthleteTab->selectView(3);
perspectiveSelector->show();
searchBox->hide();
workoutFilterBox->show();
setToolButtons();
}
@@ -1313,6 +1331,8 @@ MainWindow::selectDiary()
viewStack->setCurrentIndex(1);
currentAthleteTab->selectView(2);
perspectiveSelector->show();
searchBox->show();
workoutFilterBox->hide();
setToolButtons();
}
@@ -1325,6 +1345,8 @@ MainWindow::selectTrends()
sidebar->setItemSelected(2, true);
currentAthleteTab->selectView(0);
perspectiveSelector->show();
searchBox->show();
workoutFilterBox->hide();
setToolButtons();
}
@@ -1964,6 +1986,9 @@ MainWindow::openAthleteTab(QString name)
GcUpgrade v3;
if (!v3.upgradeConfirmedByUser(home)) return;
// save how we are
saveGCState(currentAthleteTab->context);
Context *con= new Context(this);
con->athlete = NULL;
emit openingAthlete(name, con);
@@ -1998,6 +2023,9 @@ MainWindow::loadCompleted(QString name, Context *context)
// to show it to avoid crappy paint artefacts
showTabbar(true);
// clear the workout filter box text
workoutFilterBox->clear();
// tell everyone
currentAthleteTab->context->notifyLoadDone(name, context);
@@ -2257,6 +2285,7 @@ MainWindow::saveGCState(Context *context)
context->showLowbar = showhideLowbar->isChecked();
context->showToolbar = showhideToolbar->isChecked();
context->searchText = searchBox->text();
context->workoutFilterText = workoutFilterBox->text();
context->style = styleAction->isChecked();
}
@@ -2284,6 +2313,8 @@ MainWindow::restoreGCState(Context *context)
showLowbar(context->showLowbar);
searchBox->setContext(context);
searchBox->setText(context->searchText);
workoutFilterBox->setContext(context);
workoutFilterBox->setText(context->workoutFilterText);
}
void

View File

@@ -63,6 +63,7 @@ class QtSegmentControl;
class SaveSingleDialogWidget;
class ChooseCyclistDialog;
class SearchFilterBox;
class WorkoutFilterBox;
class NewSideBar;
class AthleteView;
@@ -307,6 +308,7 @@ class MainWindow : public QMainWindow
QComboBox *perspectiveSelector;
bool pactive; // when programmatically manipulating selector
SearchFilterBox *searchBox;
WorkoutFilterBox *workoutFilterBox;
// Not on Mac so use other types
QPushButton *sidelist, *lowbar, *tabtile, *whatsthis;

View File

@@ -77,8 +77,6 @@
#include "windows.h"
#endif
#include "WorkoutFilter.h"
#include "FilterEditor.h"
#include "TrainDB.h"
#include "Library.h"
@@ -246,6 +244,10 @@ TrainSidebar::TrainSidebar(Context *context) : GcWindow(context), context(contex
connect(context, SIGNAL(newLap()), this, SLOT(resetLapTimer()));
connect(context, SIGNAL(viewChanged(int)), this, SLOT(viewChanged(int)));
// workout filters
connect(context, SIGNAL(workoutFiltersChanged(QList<ModelFilter*>&)), this, SLOT(workoutFiltersChanged(QList<ModelFilter*>&)));
connect(context, SIGNAL(workoutFiltersRemoved()), this, SLOT(workoutFiltersRemoved()));
// not used but kept in case re-instated in the future
recordSelector = new QCheckBox(this);
recordSelector->setText(tr("Save workout data"));
@@ -272,15 +274,6 @@ TrainSidebar::TrainSidebar(Context *context) : GcWindow(context), context(contex
trainSplitter->addWidget(deviceItem);
FilterEditor *workoutFilter = new FilterEditor();
QIcon workoutFilterErrorIcon = workoutFilter->style()->standardIcon(QStyle::SP_MessageBoxCritical);
workoutFilterErrorAction = workoutFilter->addAction(workoutFilterErrorIcon, FilterEditor::LeadingPosition);
workoutFilterErrorAction->setVisible(false);
workoutFilter->setClearButtonEnabled(true);
workoutFilter->setPlaceholderText(tr("Filter..."));
workoutFilter->setFilterCommands(workoutFilterCommands());
connect(workoutFilter, SIGNAL(textChanged(const QString&)), this, SLOT(workoutFilterChanged(const QString&)));
workoutItem->addWidget(workoutFilter);
workoutItem->addWidget(workoutTree);
HelpWhatsThis *helpWorkoutTree = new HelpWhatsThis(workoutTree);
workoutTree->setWhatsThis(helpWorkoutTree->getWhatsThisText(HelpWhatsThis::SideBarTrainView_Workouts));
@@ -1239,30 +1232,6 @@ TrainSidebar::mediaTreeWidgetSelectionChanged()
}
}
void
TrainSidebar::workoutFilterChanged
(const QString &text)
{
workoutFilterErrorAction->setVisible(false);
bool ok = true;
QString msg;
QString input(text.trimmed());
while (input.length() > 0 && (input.back().isSpace() || input.back() == ',')) {
input.chop(1);
}
if (input.length() > 0) {
sortModel->setFilters(parseWorkoutFilter(input, ok, msg));
if (! ok) {
workoutFilterErrorAction->setVisible(true);
workoutFilterErrorAction->setToolTip(tr("ERROR: %1").arg(msg));
}
} else {
sortModel->removeFilters();
}
}
void
TrainSidebar::videosyncTreeWidgetSelectionChanged()
{

View File

@@ -147,8 +147,6 @@ class TrainSidebar : public GcWindow
void videosyncTreeWidgetSelectionChanged();
void mediaTreeWidgetSelectionChanged();
void workoutFilterChanged(const QString &text);
void deviceTreeMenuPopup(const QPoint &);
void deleteDevice();
void moveDevices(int, int);
@@ -171,6 +169,10 @@ class TrainSidebar : public GcWindow
int getCalibrationIndex(void);
// workout filters
void workoutFiltersChanged(QList<ModelFilter*>& f) { sortModel->setFilters(f); }
void workoutFiltersRemoved() { sortModel->removeFilters(); }
public slots:
void configChanged(qint32);
void deleteWorkouts(); // deletes selected workouts
@@ -249,8 +251,6 @@ class TrainSidebar : public GcWindow
QSortFilterProxyModel *vsortModel; // sorting video list
QSortFilterProxyModel *vssortModel; // sorting videosync list
QAction *workoutFilterErrorAction;
int lastAppliedIntensity;// remember how we scaled last time
int FTP; // current FTP / CP

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2023 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WorkoutFilterBox.h"
#include "WorkoutFilter.h"
#include <QString>
#include <QDebug>
#include <QStyle>
#include "Colors.h"
WorkoutFilterBox::WorkoutFilterBox(QWidget *parent, Context *context) : FilterEditor(parent), context(context)
{
QIcon workoutFilterErrorIcon = QPixmap::fromImage(QImage(":images/toolbar/popbutton.png"));
workoutFilterErrorAction = this->addAction(workoutFilterErrorIcon, FilterEditor::LeadingPosition);
workoutFilterErrorAction->setVisible(false);
this->setClearButtonEnabled(true);
this->setPlaceholderText(tr("Filter..."));
this->setFilterCommands(workoutFilterCommands());
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(textChanged(const QString&)));
connect(this, SIGNAL(editingFinished()), this, SLOT(editingFinished()));
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
// set appearance
configChanged(CONFIG_APPEARANCE);
}
WorkoutFilterBox::~WorkoutFilterBox()
{
}
void
WorkoutFilterBox::textChanged(const QString &text)
{
workoutFilterErrorAction->setVisible(false);
bool ok = true;
QString msg;
QString input(text.trimmed());
while (input.length() > 0 && (input.back().isSpace() || input.back() == ',')) {
input.chop(1);
}
if (context && input.length() > 0) {
filters = parseWorkoutFilter(input, ok, msg);
if (! ok) {
workoutFilterErrorAction->setVisible(true);
workoutFilterErrorAction->setToolTip(tr("ERROR: %1").arg(msg));
}
}
}
void
WorkoutFilterBox::editingFinished()
{
if (context && text().trimmed().length() > 0) {
context->setWorkoutFilters(filters);
} else {
context->clearWorkoutFilters();
}
}
void
WorkoutFilterBox::configChanged(qint32 topic)
{
if (topic & CONFIG_APPEARANCE) {
QColor color = QPalette().color(QPalette::Highlight);
setStyleSheet(QString(
"QLineEdit {"
" background-color: %1;"
" color: %2;"
" border-radius: 3px;"
" border: 1px solid rgba(127,127,127,127);"
"}"
"QLineEdit:focus {"
#ifdef WIN32
" border: 1px solid rgba(%3,%4,%5,255);"
#else
" border: 2px solid rgba(%3,%4,%5,255);"
#endif
"}"
).arg(GColor(CTOOLBAR).name())
.arg(GCColor::invertColor(GColor(CTOOLBAR)).name())
.arg(color.red())
.arg(color.green())
.arg(color.blue()));
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GC_WorkoutFilterBox_h
#define _GC_WorkoutFilterBox_h
#include <QString>
#include <QStringList>
#include <QList>
#include "FilterEditor.h"
#include "Context.h"
class WorkoutFilterBox : public FilterEditor
{
Q_OBJECT
public:
WorkoutFilterBox(QWidget *parent=nullptr, Context *context=nullptr);
virtual ~WorkoutFilterBox();
void setContext(Context *ctx) { context = ctx; }
private slots:
void textChanged(const QString &text);
void editingFinished();
void configChanged(qint32 topic);
private:
Context *context;
QAction *workoutFilterErrorAction;
QList<ModelFilter *> filters;
};
#endif

View File

@@ -703,7 +703,7 @@ HEADERS += Train/AddDeviceWizard.h Train/CalibrationData.h Train/ComputrainerCon
Train/PolynomialRegression.h Train/MultiRegressionizer.h Train/StravaRoutesDownload.h \
Train/VideoSyncFileBase.h Train/ErgFileBase.h \
Train/ModelFilter.h Train/MultiFilterProxyModel.h Train/WorkoutFilter.h Train/FilterEditor.h \
Train/TagBar.h Train/Taggable.h Train/TagStore.h Train/TagWidget.h \
Train/WorkoutFilterBox.h Train/TagBar.h Train/Taggable.h Train/TagStore.h Train/TagWidget.h \
Train/TrainerDayAPIQuery.h Train/TrainerDayAPIDialog.h
HEADERS += Train/TrainBottom.h Train/TrainDB.h Train/TrainSidebar.h \
@@ -815,7 +815,7 @@ SOURCES += Train/AddDeviceWizard.cpp Train/CalibrationData.cpp Train/Computraine
Train/PolynomialRegression.cpp Train/StravaRoutesDownload.cpp \
Train/VideoSyncFileBase.cpp Train/ErgFileBase.cpp \
Train/ModelFilter.cpp Train/MultiFilterProxyModel.cpp Train/WorkoutFilter.cpp Train/FilterEditor.cpp \
Train/TagBar.cpp Train/TagWidget.cpp \
Train/WorkoutFilterBox.cpp Train/TagBar.cpp Train/TagWidget.cpp \
Train/TrainerDayAPIQuery.cpp Train/TrainerDayAPIDialog.cpp
SOURCES += Train/TrainBottom.cpp Train/TrainDB.cpp Train/TrainSidebar.cpp \