mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 17:09:56 +00:00
.. move use sidebar settings to basic page and disable the other pages if its using sidebar settings .. when working in the sidebar, hide the basic page since it is not shared .. make the add chart dialog bigger if the screen is large enough, since it needs to show a chart and the config pane which has grown a little over time.
1576 lines
52 KiB
C++
1576 lines
52 KiB
C++
/*
|
|
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* 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 "LTMSidebar.h"
|
|
#include "MainWindow.h"
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
#include "RideCache.h"
|
|
#include "Settings.h"
|
|
#include "GcUpgrade.h" // for URL to config at www.goldencheetah.org/
|
|
#include "Colors.h"
|
|
#include "Units.h"
|
|
#include "HelpWhatsThis.h"
|
|
|
|
#include "GcWindowRegistry.h" // for GcWinID types
|
|
#include "HomeWindow.h" // for GcWindowDialog
|
|
#include "LTMWindow.h" // for LTMWindow::settings()
|
|
#include "LTMChartParser.h" // for LTMChartParser::serialize && ChartTreeView
|
|
|
|
#include <QApplication>
|
|
#include <QWebView>
|
|
#include <QWebFrame>
|
|
#include <QScrollBar>
|
|
#include <QtGui>
|
|
#include <QStyle>
|
|
#include <QStyleFactory>
|
|
#include <QScrollBar>
|
|
|
|
// seasons support
|
|
#include "Season.h"
|
|
#include "SeasonParser.h"
|
|
#include <QXmlInputSource>
|
|
#include <QXmlSimpleReader>
|
|
|
|
// named searchs
|
|
#include "FreeSearch.h"
|
|
#include "NamedSearch.h"
|
|
#include "DataFilter.h"
|
|
|
|
// ride cache
|
|
#include "RideCache.h"
|
|
#include "RideItem.h"
|
|
#include "Specification.h"
|
|
|
|
// metadata support
|
|
#include "RideMetadata.h"
|
|
#include "SpecialFields.h"
|
|
|
|
|
|
LTMSidebar::LTMSidebar(Context *context) : QWidget(context->mainWindow), context(context), active(false),
|
|
isqueryfilter(false), isautofilter(false)
|
|
{
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(0,0,0,0);
|
|
mainLayout->setSpacing(0);
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
seasonsWidget = new GcSplitterItem(tr("Date Ranges"), iconFromPNG(":images/sidebar/calendar.png"), this);
|
|
QAction *moreSeasonAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
|
|
seasonsWidget->addAction(moreSeasonAct);
|
|
connect(moreSeasonAct, SIGNAL(triggered(void)), this, SLOT(dateRangePopup(void)));
|
|
|
|
dateRangeTree = new SeasonTreeView(context); // context needed for drag/drop across contexts
|
|
allDateRanges=dateRangeTree->invisibleRootItem();
|
|
// Drop for Seasons
|
|
allDateRanges->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
|
|
allDateRanges->setText(0, tr("Date Ranges"));
|
|
dateRangeTree->setFrameStyle(QFrame::NoFrame);
|
|
dateRangeTree->setColumnCount(1);
|
|
dateRangeTree->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
dateRangeTree->header()->hide();
|
|
dateRangeTree->setIndentation(5);
|
|
dateRangeTree->expandItem(allDateRanges);
|
|
dateRangeTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
#ifdef Q_OS_MAC
|
|
dateRangeTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
QStyle *cde = QStyleFactory::create(OS_STYLE);
|
|
dateRangeTree->verticalScrollBar()->setStyle(cde);
|
|
#endif
|
|
seasonsWidget->addWidget(dateRangeTree);
|
|
|
|
HelpWhatsThis *helpDateRange = new HelpWhatsThis(dateRangeTree);
|
|
dateRangeTree->setWhatsThis(helpDateRange->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_DateRanges));
|
|
|
|
// events
|
|
eventsWidget = new GcSplitterItem(tr("Events"), iconFromPNG(":images/sidebar/bookmark.png"), this);
|
|
QAction *moreEventAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
|
|
eventsWidget->addAction(moreEventAct);
|
|
connect(moreEventAct, SIGNAL(triggered(void)), this, SLOT(eventPopup(void)));
|
|
|
|
eventTree = new QTreeWidget;
|
|
eventTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
allEvents = eventTree->invisibleRootItem();
|
|
allEvents->setText(0, tr("Events"));
|
|
eventTree->setFrameStyle(QFrame::NoFrame);
|
|
eventTree->setColumnCount(2);
|
|
eventTree->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
eventTree->header()->hide();
|
|
eventTree->setIndentation(5);
|
|
eventTree->expandItem(allEvents);
|
|
eventTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
eventTree->horizontalScrollBar()->setDisabled(true);
|
|
eventTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
#ifdef Q_OS_MAC
|
|
eventTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
cde = QStyleFactory::create(OS_STYLE);
|
|
eventTree->verticalScrollBar()->setStyle(cde);
|
|
#endif
|
|
|
|
eventsWidget->addWidget(eventTree);
|
|
|
|
HelpWhatsThis *helpEventsTree = new HelpWhatsThis(eventTree);
|
|
eventTree->setWhatsThis(helpEventsTree->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_Events));
|
|
|
|
// charts
|
|
chartsWidget = new GcSplitterItem(tr("Charts"), iconFromPNG(":images/sidebar/charts.png"), this);
|
|
|
|
// Chart Widget Actions
|
|
QAction *moreChartAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
|
|
chartsWidget->addAction(moreChartAct);
|
|
connect(moreChartAct, SIGNAL(triggered(void)), this, SLOT(presetPopup(void)));
|
|
|
|
chartTree = new ChartTreeView(context);
|
|
chartTree->setFrameStyle(QFrame::NoFrame);
|
|
allCharts = chartTree->invisibleRootItem();
|
|
allCharts->setText(0, tr("Events"));
|
|
allCharts->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
|
|
chartTree->setColumnCount(1);
|
|
chartTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
chartTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
chartTree->header()->hide();
|
|
chartTree->setIndentation(5);
|
|
chartTree->expandItem(allCharts);
|
|
chartTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
chartTree->horizontalScrollBar()->setDisabled(true);
|
|
chartTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
#ifdef Q_OS_MAC
|
|
chartTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
cde = QStyleFactory::create(OS_STYLE);
|
|
chartTree->verticalScrollBar()->setStyle(cde);
|
|
#endif
|
|
chartsWidget->addWidget(chartTree);
|
|
|
|
HelpWhatsThis *helpChartsTree = new HelpWhatsThis(chartTree);
|
|
chartTree->setWhatsThis(helpChartsTree->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_Charts));
|
|
|
|
// setup for first time
|
|
presetsChanged();
|
|
|
|
// filters
|
|
filtersWidget = new GcSplitterItem(tr("Filters"), iconFromPNG(":images/toolbar/filter3.png"), this);
|
|
QAction *moreFilterAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
|
|
filtersWidget->addAction(moreFilterAct);
|
|
connect(moreFilterAct, SIGNAL(triggered(void)), this, SLOT(filterPopup(void)));
|
|
|
|
filterTree = new QTreeWidget;
|
|
filterTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
allFilters = filterTree->invisibleRootItem();
|
|
allFilters->setText(0, tr("Filters"));
|
|
allFilters->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
|
filterTree->setFrameStyle(QFrame::NoFrame);
|
|
filterTree->setColumnCount(1);
|
|
filterTree->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
filterTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
filterTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
filterTree->header()->hide();
|
|
filterTree->setIndentation(5);
|
|
filterTree->expandItem(allFilters);
|
|
filterTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
filterTree->horizontalScrollBar()->setDisabled(true);
|
|
filterTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
#ifdef Q_OS_MAC
|
|
filterTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
cde = QStyleFactory::create(OS_STYLE);
|
|
filterTree->verticalScrollBar()->setStyle(cde);
|
|
#endif
|
|
// we cast the filter tree and this because we use the same constructor XXX fix this!!!
|
|
filterSplitter = new GcSubSplitter(Qt::Vertical, (GcSplitterControl*)filterTree, (GcSplitter*)this, true);
|
|
filtersWidget->addWidget(filterSplitter);
|
|
|
|
HelpWhatsThis *helpFilterTree = new HelpWhatsThis(filterTree);
|
|
filterTree->setWhatsThis(helpFilterTree->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_Filter));
|
|
|
|
seasons = context->athlete->seasons;
|
|
resetSeasons(); // reset the season list
|
|
resetFilters(); // reset the filters list
|
|
|
|
autoFilterMenu = new QMenu(tr("Autofilter"),this);
|
|
configChanged(CONFIG_APPEARANCE); // will reset the metric tree and the autofilters
|
|
|
|
splitter = new GcSplitter(Qt::Vertical);
|
|
splitter->addWidget(seasonsWidget); // goes alongside events
|
|
splitter->addWidget(eventsWidget); // goes alongside date ranges
|
|
splitter->addWidget(filtersWidget);
|
|
splitter->addWidget(chartsWidget); // for charts that 'use sidebar chart' charts ! (confusing or what?!)
|
|
|
|
GcSplitterItem *summaryWidget = new GcSplitterItem(tr("Summary"), iconFromPNG(":images/sidebar/dashboard.png"), this);
|
|
|
|
summary = new QWebView(this);
|
|
summary->setContentsMargins(0,0,0,0);
|
|
summary->page()->view()->setContentsMargins(0,0,0,0);
|
|
summary->page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
|
|
summary->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
summary->setAcceptDrops(false);
|
|
|
|
summaryWidget->addWidget(summary);
|
|
|
|
HelpWhatsThis *helpSummary = new HelpWhatsThis(summary);
|
|
summary->setWhatsThis(helpSummary->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_Summary));
|
|
|
|
QFont defaultFont; // mainwindow sets up the defaults.. we need to apply
|
|
summary->settings()->setFontSize(QWebSettings::DefaultFontSize, defaultFont.pointSize());
|
|
summary->settings()->setFontFamily(QWebSettings::StandardFont, defaultFont.family());
|
|
splitter->addWidget(summaryWidget);
|
|
|
|
mainLayout->addWidget(splitter);
|
|
|
|
splitter->prepare(context->athlete->cyclist, "LTM");
|
|
|
|
// our date ranges
|
|
connect(dateRangeTree,SIGNAL(itemSelectionChanged()), this, SLOT(dateRangeTreeWidgetSelectionChanged()));
|
|
connect(dateRangeTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(dateRangePopup(const QPoint &)));
|
|
connect(dateRangeTree,SIGNAL(itemChanged(QTreeWidgetItem *,int)), this, SLOT(dateRangeChanged(QTreeWidgetItem*, int)));
|
|
connect(dateRangeTree,SIGNAL(itemMoved(QTreeWidgetItem *,int, int)), this, SLOT(dateRangeMoved(QTreeWidgetItem*, int, int)));
|
|
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(setSummary(DateRange)));
|
|
|
|
// filters
|
|
connect(filterTree,SIGNAL(itemSelectionChanged()), this, SLOT(filterTreeWidgetSelectionChanged()));
|
|
|
|
// events
|
|
connect(eventTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(eventPopup(const QPoint &)));
|
|
|
|
// presets
|
|
connect(chartTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(presetPopup(const QPoint &)));
|
|
connect(chartTree,SIGNAL(itemSelectionChanged()), this, SLOT(presetSelectionChanged()));
|
|
|
|
// GC signal
|
|
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
|
|
connect(seasons, SIGNAL(seasonsChanged()), this, SLOT(resetSeasons()));
|
|
connect(context->athlete, SIGNAL(namedSearchesChanged()), this, SLOT(resetFilters()));
|
|
connect(context, SIGNAL(presetsChanged()), this, SLOT(presetsChanged()));
|
|
|
|
// setup colors
|
|
configChanged(CONFIG_APPEARANCE);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::presetsChanged()
|
|
{
|
|
// rebuild the preset chart list as the presets have changed
|
|
chartTree->clear();
|
|
foreach(LTMSettings chart, context->athlete->presets) {
|
|
QTreeWidgetItem *add;
|
|
add = new QTreeWidgetItem(chartTree->invisibleRootItem());
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
|
add->setText(0, chart.name);
|
|
}
|
|
chartTree->setCurrentItem(chartTree->invisibleRootItem()->child(0));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::presetSelectionChanged()
|
|
{
|
|
if (!chartTree->selectedItems().isEmpty()) {
|
|
QTreeWidgetItem *which = chartTree->selectedItems().first();
|
|
if (which != allCharts) {
|
|
int index = allCharts->indexOfChild(which);
|
|
if (index >=0 && index < context->athlete->presets.count())
|
|
context->notifyPresetSelected(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::configChanged(qint32)
|
|
{
|
|
seasonsWidget->setStyleSheet(GCColor::stylesheet());
|
|
eventsWidget->setStyleSheet(GCColor::stylesheet());
|
|
chartsWidget->setStyleSheet(GCColor::stylesheet());
|
|
filtersWidget->setStyleSheet(GCColor::stylesheet());
|
|
setAutoFilterMenu();
|
|
|
|
// set or reset the autofilter widgets
|
|
autoFilterChanged();
|
|
|
|
// forget what we just used...
|
|
from = to = QDate();
|
|
|
|
// let everyone know what date range we are starting with
|
|
dateRangeTreeWidgetSelectionChanged();
|
|
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Selections Made
|
|
*----------------------------------------------------------------------*/
|
|
|
|
void
|
|
LTMSidebar::dateRangeTreeWidgetSelectionChanged()
|
|
{
|
|
if (active == true) return;
|
|
|
|
const Season *dateRange = NULL;
|
|
|
|
if (dateRangeTree->selectedItems().isEmpty()) dateRange = NULL;
|
|
else {
|
|
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
|
|
if (which != allDateRanges) {
|
|
dateRange = &seasons->seasons.at(allDateRanges->indexOfChild(which));
|
|
} else {
|
|
dateRange = NULL;
|
|
}
|
|
}
|
|
|
|
if (dateRange) {
|
|
int i;
|
|
// clear events - we need to add for currently selected season
|
|
for (i=allEvents->childCount(); i > 0; i--) {
|
|
delete allEvents->takeChild(0);
|
|
}
|
|
|
|
// add this seasons events
|
|
for (i=0; i <dateRange->events.count(); i++) {
|
|
SeasonEvent event = dateRange->events.at(i);
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(allEvents);
|
|
add->setText(0, event.name);
|
|
add->setText(1, event.date.toString("MMM d, yyyy"));
|
|
}
|
|
|
|
// make sure they fit
|
|
eventTree->header()->resizeSections(QHeaderView::ResizeToContents);
|
|
appsettings->setCValue(context->athlete->cyclist, GC_LTM_LAST_DATE_RANGE, dateRange->id().toString());
|
|
|
|
}
|
|
|
|
// Let the view know its changed....
|
|
if (dateRange) emit dateRangeChanged(DateRange(dateRange->start, dateRange->end, dateRange->name));
|
|
else emit dateRangeChanged(DateRange());
|
|
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Seasons stuff
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
LTMSidebar::resetSeasons()
|
|
{
|
|
if (active == true) return;
|
|
|
|
active = true;
|
|
int i;
|
|
for (i=allDateRanges->childCount(); i > 0; i--) {
|
|
delete allDateRanges->takeChild(0);
|
|
}
|
|
QString id = appsettings->cvalue(context->athlete->cyclist, GC_LTM_LAST_DATE_RANGE, seasons->seasons.at(0).id().toString()).toString();
|
|
for (i=0; i <seasons->seasons.count(); i++) {
|
|
Season season = seasons->seasons.at(i);
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(allDateRanges, season.getType());
|
|
if (season.id().toString()==id)
|
|
add->setSelected(true);
|
|
|
|
// Drag and Drop is FINE for temporary seasons -- IT IS JUST A DATE RANGE!
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
|
add->setText(0, season.getName());
|
|
}
|
|
|
|
active = false;
|
|
}
|
|
|
|
int
|
|
LTMSidebar::newSeason(QString name, QDate start, QDate end, int type)
|
|
{
|
|
seasons->newSeason(name, start, end, type);
|
|
|
|
QTreeWidgetItem *item = new QTreeWidgetItem(Season::season);
|
|
item->setText(0, name);
|
|
allDateRanges->insertChild(0, item);
|
|
return 0; // always add at the top
|
|
}
|
|
|
|
void
|
|
LTMSidebar::updateSeason(int index, QString name, QDate start, QDate end, int type)
|
|
{
|
|
seasons->updateSeason(index, name, start, end, type);
|
|
allDateRanges->child(index)->setText(0, name);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::dateRangePopup(QPoint pos)
|
|
{
|
|
QTreeWidgetItem *item = dateRangeTree->itemAt(pos);
|
|
if (item != NULL) {
|
|
|
|
// out of bounds or not user defined
|
|
int index = allDateRanges->indexOfChild(item);
|
|
if (index == -1 || index >= seasons->seasons.count()
|
|
|| seasons->seasons[index].getType() == Season::temporary) {
|
|
// on system date we just offer to add a Season, since its
|
|
// the only way of doing it when no seasons are defined!!
|
|
|
|
// create context menu
|
|
QMenu menu(dateRangeTree);
|
|
QAction *add = new QAction(tr("Add season"), dateRangeTree);
|
|
menu.addAction(add);
|
|
|
|
// connect menu to functions
|
|
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(dateRangeTree->mapToGlobal(pos));
|
|
|
|
} else {
|
|
|
|
// create context menu
|
|
QMenu menu(dateRangeTree);
|
|
QAction *add = new QAction(tr("Add season"), dateRangeTree);
|
|
QAction *edit = new QAction(tr("Edit season"), dateRangeTree);
|
|
QAction *del = new QAction(tr("Delete season"), dateRangeTree);
|
|
QAction *event = new QAction(tr("Add Event"), dateRangeTree);
|
|
menu.addAction(add);
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
menu.addAction(event);
|
|
|
|
// connect menu to functions
|
|
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editRange(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteRange(void)));
|
|
connect(event, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(dateRangeTree->mapToGlobal(pos));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::dateRangePopup()
|
|
{
|
|
// no current season selected
|
|
if (dateRangeTree->selectedItems().isEmpty()) return;
|
|
|
|
QTreeWidgetItem *item = dateRangeTree->selectedItems().at(0);
|
|
|
|
// OK - we are working with a specific event..
|
|
QMenu menu(dateRangeTree);
|
|
QAction *add = new QAction(tr("Add season"), dateRangeTree);
|
|
menu.addAction(add);
|
|
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
|
|
|
|
if (item != NULL && allDateRanges->indexOfChild(item) != -1) {
|
|
QAction *edit = new QAction(tr("Edit season"), dateRangeTree);
|
|
QAction *del = new QAction(tr("Delete season"), dateRangeTree);
|
|
QAction *event = new QAction(tr("Add Event"), dateRangeTree);
|
|
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
menu.addAction(event);
|
|
|
|
// connect menu to functions
|
|
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editRange(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteRange(void)));
|
|
connect(event, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
|
|
}
|
|
// execute the menu
|
|
menu.exec(splitter->mapToGlobal(QPoint(seasonsWidget->pos().x()+seasonsWidget->width()-20,
|
|
seasonsWidget->pos().y())));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::eventPopup(QPoint pos)
|
|
{
|
|
// no current season selected
|
|
if (dateRangeTree->selectedItems().isEmpty()) return;
|
|
|
|
QTreeWidgetItem *item = eventTree->itemAt(pos);
|
|
|
|
// save context - which season and event are we working with?
|
|
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
|
|
if (!which || which == allDateRanges) return;
|
|
|
|
// OK - we are working with a specific event..
|
|
QMenu menu(eventTree);
|
|
if (item != NULL && allEvents->indexOfChild(item) != -1) {
|
|
|
|
QAction *edit = new QAction(tr("Edit details"), eventTree);
|
|
QAction *del = new QAction(tr("Delete event"), eventTree);
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
|
|
// connect menu to functions
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editEvent(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteEvent(void)));
|
|
}
|
|
|
|
// we can always add, regardless of any event being selected...
|
|
QAction *addEvent = new QAction(tr("Add event"), eventTree);
|
|
menu.addAction(addEvent);
|
|
connect(addEvent, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(eventTree->mapToGlobal(pos));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::eventPopup()
|
|
{
|
|
// events are against a selected season
|
|
if (dateRangeTree->selectedItems().count() == 0) return; // need a season selected!
|
|
|
|
// and the season must be user defined not temporary
|
|
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
if (seasons->seasons[seasonindex].getType() == Season::temporary) return;
|
|
|
|
// have we selected an event?
|
|
QTreeWidgetItem *item = NULL;
|
|
if (!eventTree->selectedItems().isEmpty()) item = eventTree->selectedItems().at(0);
|
|
|
|
QMenu menu(eventTree);
|
|
|
|
// we can always add, regardless of any event being selected...
|
|
QAction *addEvent = new QAction(tr("Add event"), eventTree);
|
|
menu.addAction(addEvent);
|
|
connect(addEvent, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
|
|
|
|
if (item != NULL && allEvents->indexOfChild(item) != -1) {
|
|
|
|
QAction *edit = new QAction(tr("Edit details"), eventTree);
|
|
QAction *del = new QAction(tr("Delete event"), eventTree);
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
|
|
// connect menu to functions
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editEvent(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteEvent(void)));
|
|
}
|
|
|
|
// execute the menu
|
|
menu.exec(splitter->mapToGlobal(QPoint(eventsWidget->pos().x()+eventsWidget->width()-20, eventsWidget->pos().y())));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::manageFilters()
|
|
{
|
|
EditNamedSearches *editor = new EditNamedSearches(this, context);
|
|
editor->move(QCursor::pos()+QPoint(10,-200));
|
|
editor->show();
|
|
}
|
|
|
|
void
|
|
LTMSidebar::setAutoFilterMenu()
|
|
{
|
|
active = true;
|
|
|
|
QStringList on = appsettings->cvalue(context->athlete->cyclist, GC_LTM_AUTOFILTERS, tr("Workout Code|Sport")).toString().split("|");
|
|
autoFilterMenu->clear();
|
|
autoFilterState.clear();
|
|
|
|
// Convert field names for Internal to Display (to work with the translated values)
|
|
SpecialFields sp;
|
|
foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
|
|
|
|
if (field.tab != "" && (field.type == 0 || field.type == 2)) { // we only do text or shorttext fields
|
|
|
|
QAction *action = new QAction(sp.displayName(field.name), this);
|
|
action->setCheckable(true);
|
|
|
|
if (on.contains(sp.displayName(field.name))) action->setChecked(true);
|
|
else action->setChecked(false);
|
|
|
|
connect(action, SIGNAL(triggered()), this, SLOT(autoFilterChanged()));
|
|
|
|
// remove from tree if its already there
|
|
GcSplitterItem *item = filterSplitter->removeItem(action->text());
|
|
if (item) delete item; // will be removed from splitter too
|
|
|
|
// if you crash on this line compile with QT5.3 or higher
|
|
// or at least avoid the 5.3 RC1 release (see QTBUG-38685)
|
|
autoFilterMenu->addAction(action);
|
|
autoFilterState << false;
|
|
}
|
|
}
|
|
active = false;
|
|
}
|
|
|
|
void
|
|
LTMSidebar::autoFilterChanged()
|
|
{
|
|
if (active) return;
|
|
|
|
QString on;
|
|
|
|
// boop
|
|
int i=0;
|
|
foreach (QAction *action, autoFilterMenu->actions()) {
|
|
|
|
// activate
|
|
if (action->isChecked() && autoFilterState.at(i) == false) {
|
|
autoFilterState[i] = true;
|
|
|
|
GcSplitterItem *item = new GcSplitterItem(action->text(), QIcon(), this);
|
|
|
|
// get a tree of values
|
|
QTreeWidget *tree = new QTreeWidget(item);
|
|
tree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
tree->setFrameStyle(QFrame::NoFrame);
|
|
tree->setColumnCount(1);
|
|
tree->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
tree->setSelectionMode(QAbstractItemView::MultiSelection);
|
|
tree->header()->hide();
|
|
tree->setIndentation(5);
|
|
tree->expandItem(tree->invisibleRootItem());
|
|
tree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
tree->horizontalScrollBar()->setDisabled(true);
|
|
tree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
#ifdef Q_OS_MAC
|
|
tree->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
QStyle *cde = QStyleFactory::create(OS_STYLE);
|
|
tree->verticalScrollBar()->setStyle(cde);
|
|
#endif
|
|
item->addWidget(tree);
|
|
filterSplitter->addWidget(item);
|
|
|
|
HelpWhatsThis *helpFilterTree = new HelpWhatsThis(tree);
|
|
tree->setWhatsThis(helpFilterTree->getWhatsThisText(HelpWhatsThis::SideBarTrendsView_Filter));
|
|
|
|
// Convert field names for Internal to Display (to work with the translated values)
|
|
SpecialFields sp;
|
|
// update the values available in the tree
|
|
foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
|
|
if (sp.displayName(field.name) == action->text()) {
|
|
foreach (QString value, context->athlete->rideCache->getDistinctValues(field.name)) {
|
|
if (value == "") value = tr("(blank)");
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(tree->invisibleRootItem(), 0);
|
|
|
|
// No Drag/Drop for autofilters
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
|
add->setText(0, value);
|
|
}
|
|
}
|
|
}
|
|
connect(tree,SIGNAL(itemSelectionChanged()), this, SLOT(autoFilterSelectionChanged()));
|
|
|
|
}
|
|
|
|
// deactivate
|
|
if (!action->isChecked() && autoFilterState.at(i) == true) {
|
|
autoFilterState[i] = false;
|
|
|
|
// remove from tree
|
|
GcSplitterItem *item = filterSplitter->removeItem(action->text());
|
|
if (item) {
|
|
// if there were items selected in the tree and we
|
|
// remove it we need to clear the filter
|
|
bool selected = static_cast<QTreeWidget*>(item->content)->selectedItems().count() > 0;
|
|
|
|
// will remove from the splitter (is only way to do this!)
|
|
delete item; // splitter deletes handle too !
|
|
|
|
// clear any results of the selection
|
|
if (selected) autoFilterSelectionChanged();
|
|
}
|
|
}
|
|
|
|
// reset the selected fields
|
|
if (action->isChecked()) {
|
|
if (on != "") on += "|";
|
|
on += action->text();
|
|
}
|
|
|
|
i++;
|
|
}
|
|
appsettings->setCValue(context->athlete->cyclist, GC_LTM_AUTOFILTERS, on);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::filterTreeWidgetSelectionChanged()
|
|
{
|
|
int selected = filterTree->selectedItems().count();
|
|
|
|
if (selected) {
|
|
|
|
QStringList errors, files; // results of all the selections
|
|
bool first = true;
|
|
|
|
foreach (QTreeWidgetItem *item, filterTree->selectedItems()) {
|
|
|
|
int index = filterTree->invisibleRootItem()->indexOfChild(item);
|
|
|
|
NamedSearch ns = context->athlete->namedSearches->get(index);
|
|
QStringList errors, results;
|
|
|
|
switch(ns.type) {
|
|
|
|
case NamedSearch::filter :
|
|
{
|
|
// use a data filter
|
|
DataFilter f(this, context);
|
|
errors = f.parseFilter(ns.text, &results);
|
|
}
|
|
break;
|
|
|
|
case NamedSearch::search :
|
|
{
|
|
// use clucence
|
|
FreeSearch s(this, context);
|
|
results = s.search(ns.text);
|
|
}
|
|
|
|
}
|
|
|
|
// lets filter the results!
|
|
if (first) files = results;
|
|
else {
|
|
QStringList filtered;
|
|
foreach(QString file, files)
|
|
if (results.contains(file))
|
|
filtered << file;
|
|
files = filtered;
|
|
}
|
|
|
|
first = false;
|
|
}
|
|
|
|
queryFilterFiles = files;
|
|
isqueryfilter = true;
|
|
|
|
} else {
|
|
|
|
queryFilterFiles.clear();
|
|
isqueryfilter = false;
|
|
}
|
|
|
|
// tell the world
|
|
filterNotify();
|
|
}
|
|
|
|
void
|
|
LTMSidebar::filterNotify()
|
|
{
|
|
// either the auto filter or query filter
|
|
// has been updated, so we need to merge the results
|
|
// of both and then notify via context
|
|
|
|
// no filter so clear
|
|
if (isqueryfilter == false && isautofilter == false) {
|
|
context->clearHomeFilter();
|
|
} else {
|
|
|
|
if (isqueryfilter == false) {
|
|
|
|
// autofilter only
|
|
context->setHomeFilter(autoFilterFiles);
|
|
|
|
} else if (isautofilter == false) {
|
|
|
|
// queryfilter only
|
|
context->setHomeFilter(queryFilterFiles);
|
|
|
|
} else {
|
|
|
|
// both are set, so merge results
|
|
QStringList merged = autoFilterFiles.toSet().intersect(queryFilterFiles.toSet()).toList();
|
|
context->setHomeFilter(merged);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::autoFilterRefresh()
|
|
{
|
|
// the data has changed so refresh the trees
|
|
for (int i=1; i<filterSplitter->count(); i++) {
|
|
|
|
GcSplitterItem *item = static_cast<GcSplitterItem*>(filterSplitter->widget(i));
|
|
QTreeWidget *tree = static_cast<QTreeWidget*>(item->content);
|
|
|
|
qDeleteAll(tree->invisibleRootItem()->takeChildren());
|
|
|
|
// translate fields back from Display Name to internal Name !
|
|
SpecialFields sp;
|
|
|
|
// what is the field?
|
|
QString fieldname = sp.internalName(item->splitterHandle->title());
|
|
|
|
// update the values available in the tree
|
|
foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
|
|
if (field.name == fieldname) {
|
|
foreach (QString value, context->athlete->rideCache->getDistinctValues(field.name)) {
|
|
if (value == "") value = tr("(blank)");
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(tree->invisibleRootItem(), 0);
|
|
|
|
// No Drag/Drop for autofilters
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
|
add->setText(0, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::autoFilterSelectionChanged()
|
|
{
|
|
// only fetch when we know we need to filter ..
|
|
QSet<QString> matched;
|
|
|
|
// assume nothing to do...
|
|
isautofilter = false;
|
|
|
|
// are any auto filters applied?
|
|
for (int i=1; i<filterSplitter->count(); i++) {
|
|
|
|
GcSplitterItem *item = static_cast<GcSplitterItem*>(filterSplitter->widget(i));
|
|
QTreeWidget *tree = static_cast<QTreeWidget*>(item->content);
|
|
|
|
// are some values selected?
|
|
if (tree->selectedItems().count() > 0) {
|
|
|
|
// we have a selection!
|
|
if (isautofilter == false) {
|
|
isautofilter = true;
|
|
foreach(RideItem *x, context->athlete->rideCache->rides()) matched << x->fileName;
|
|
}
|
|
|
|
// what is the field?
|
|
QString fieldname = item->splitterHandle->title();
|
|
|
|
// what values are highlighted
|
|
QStringList values;
|
|
foreach (QTreeWidgetItem *wi, tree->selectedItems())
|
|
values << wi->text(0);
|
|
|
|
// get a set of filenames that match
|
|
QSet<QString> matches;
|
|
foreach(RideItem *x, context->athlete->rideCache->rides()) {
|
|
|
|
// we use XXX___XXX___XXX because it is not likely to exist
|
|
QString value = x->getText(fieldname, "XXX___XXX___XXX");
|
|
if (value == "") value = tr("(blank)"); // match blanks!
|
|
|
|
if (values.contains(value)) matches << x->fileName;
|
|
}
|
|
|
|
// now remove items from the matched list that
|
|
// are not in my list of matches
|
|
matched = matched.intersect(matches);
|
|
}
|
|
}
|
|
|
|
// all done
|
|
autoFilterFiles = matched.toList();
|
|
|
|
// tell the world
|
|
filterNotify();
|
|
}
|
|
|
|
void
|
|
LTMSidebar::resetFilters()
|
|
{
|
|
if (active == true) return;
|
|
|
|
active = true;
|
|
int i;
|
|
for (i=allFilters->childCount(); i > 0; i--) {
|
|
delete allFilters->takeChild(0);
|
|
}
|
|
|
|
foreach(NamedSearch ns, context->athlete->namedSearches->getList()) {
|
|
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(allFilters, 0);
|
|
|
|
// No Drag/Drop for filters
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
|
add->setText(0, ns.name);
|
|
}
|
|
|
|
active = false;
|
|
}
|
|
|
|
void
|
|
LTMSidebar::filterPopup()
|
|
{
|
|
// is one selected for deletion?
|
|
int selected = filterTree->selectedItems().count();
|
|
|
|
QMenu menu(filterTree);
|
|
|
|
// we can always add, regardless of any event being selected...
|
|
QAction *addEvent = new QAction(tr("Manage Filters"), filterTree);
|
|
menu.addAction(addEvent);
|
|
connect(addEvent, SIGNAL(triggered(void)), this, SLOT(manageFilters(void)));
|
|
|
|
if (selected) {
|
|
|
|
QAction *del = new QAction(QString(tr("Delete Filter%1")).arg(selected>1 ? "s" : ""), filterTree);
|
|
menu.addAction(del);
|
|
|
|
// connect menu to functions
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteFilter(void)));
|
|
}
|
|
|
|
menu.addSeparator();
|
|
menu.addMenu(autoFilterMenu);
|
|
|
|
// execute the menu
|
|
menu.exec(splitter->mapToGlobal(QPoint(filtersWidget->pos().x()+filtersWidget->width()-20, filtersWidget->pos().y())));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::deleteFilter()
|
|
{
|
|
if (filterTree->selectedItems().count() <= 0) return;
|
|
|
|
active = true; // no need to reset tree when items deleted from model!
|
|
while (filterTree->selectedItems().count()) {
|
|
int index = allFilters->indexOfChild(filterTree->selectedItems().first());
|
|
|
|
// now delete!
|
|
delete allFilters->takeChild(index);
|
|
context->athlete->namedSearches->deleteNamedSearch(index);
|
|
}
|
|
active = false;
|
|
}
|
|
|
|
void
|
|
LTMSidebar::dateRangeChanged(QTreeWidgetItem*item, int)
|
|
{
|
|
if (active == true) return;
|
|
|
|
int index = allDateRanges->indexOfChild(item);
|
|
seasons->seasons[index].setName(item->text(0));
|
|
|
|
// save changes away
|
|
active = true;
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
// signal date selected changed
|
|
//dateRangeSelected(&seasons->seasons[index]);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::dateRangeMoved(QTreeWidgetItem*item, int oldposition, int newposition)
|
|
{
|
|
// report the move in the seasons
|
|
seasons->seasons.move(oldposition, newposition);
|
|
|
|
// save changes away
|
|
active = true;
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
// deselect actual selection
|
|
dateRangeTree->selectedItems().first()->setSelected(false);
|
|
// select the move/drop item
|
|
item->setSelected(true);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::addRange()
|
|
{
|
|
Season newOne;
|
|
|
|
EditSeasonDialog dialog(context, &newOne);
|
|
|
|
if (dialog.exec()) {
|
|
|
|
active = true;
|
|
|
|
// check dates are right way round...
|
|
if (newOne.start > newOne.end) {
|
|
QDate temp = newOne.start;
|
|
newOne.start = newOne.end;
|
|
newOne.end = temp;
|
|
}
|
|
|
|
// save
|
|
seasons->seasons.insert(0, newOne);
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
// signal its changed!
|
|
resetSeasons();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::editRange()
|
|
{
|
|
// throw up modal dialog box to edit all the season
|
|
if (dateRangeTree->selectedItems().count() != 1) return;
|
|
|
|
int index = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
|
|
if (seasons->seasons[index].getType() == Season::temporary) {
|
|
QMessageBox::warning(this, tr("Edit Season"), tr("You can only edit user defined seasons. Please select a season you have created for editing."));
|
|
return; // must be a user season
|
|
}
|
|
EditSeasonDialog dialog(context, &seasons->seasons[index]);
|
|
|
|
if (dialog.exec()) {
|
|
|
|
active = true;
|
|
|
|
// check dates are right way round...
|
|
if (seasons->seasons[index].start > seasons->seasons[index].end) {
|
|
QDate temp = seasons->seasons[index].start;
|
|
seasons->seasons[index].start = seasons->seasons[index].end;
|
|
seasons->seasons[index].end = temp;
|
|
}
|
|
|
|
// update name
|
|
dateRangeTree->selectedItems().first()->setText(0, seasons->seasons[index].getName());
|
|
|
|
// save changes away
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::deleteRange()
|
|
{
|
|
if (dateRangeTree->selectedItems().count() != 1) return;
|
|
int index = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
|
|
if (seasons->seasons[index].getType() == Season::temporary) {
|
|
QMessageBox::warning(this, tr("Delete Season"), tr("You can only delete user defined seasons. Please select a season you have created for deletion."));
|
|
return; // must be a user season
|
|
}
|
|
|
|
// now delete!
|
|
delete allDateRanges->takeChild(index);
|
|
seasons->deleteSeason(index);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::addEvent()
|
|
{
|
|
if (dateRangeTree->selectedItems().count() == 0) {
|
|
QMessageBox::warning(this, tr("Add Event"), tr("You can only add events to user defined seasons. Please select a season you have created before adding an event."));
|
|
return; // need a season selected!
|
|
}
|
|
|
|
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
|
|
if (seasons->seasons[seasonindex].getType() == Season::temporary) {
|
|
QMessageBox::warning(this, tr("Add Event"), tr("You can only add events to user defined seasons. Please select a season you have created before adding an event."));
|
|
return; // must be a user season
|
|
}
|
|
|
|
SeasonEvent myevent("", QDate());
|
|
EditSeasonEventDialog dialog(context, &myevent);
|
|
|
|
if (dialog.exec()) {
|
|
|
|
active = true;
|
|
seasons->seasons[seasonindex].events.append(myevent);
|
|
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(allEvents);
|
|
add->setText(0, myevent.name);
|
|
add->setText(1, myevent.date.toString("MMM d, yyyy"));
|
|
|
|
// make sure they fit
|
|
eventTree->header()->resizeSections(QHeaderView::ResizeToContents);
|
|
|
|
// save changes away
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::deleteEvent()
|
|
{
|
|
active = true;
|
|
|
|
if (dateRangeTree->selectedItems().count()) {
|
|
|
|
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
|
|
// only delete those that are selected
|
|
if (eventTree->selectedItems().count() > 0) {
|
|
|
|
// wipe them away
|
|
foreach(QTreeWidgetItem *d, eventTree->selectedItems()) {
|
|
int index = allEvents->indexOfChild(d);
|
|
|
|
delete allEvents->takeChild(index);
|
|
seasons->seasons[seasonindex].events.removeAt(index);
|
|
}
|
|
}
|
|
|
|
// save changes away
|
|
seasons->writeSeasons();
|
|
|
|
}
|
|
active = false;
|
|
}
|
|
|
|
void
|
|
LTMSidebar::editEvent()
|
|
{
|
|
active = true;
|
|
|
|
if (dateRangeTree->selectedItems().count()) {
|
|
|
|
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
|
|
|
|
// only delete those that are selected
|
|
if (eventTree->selectedItems().count() == 1) {
|
|
|
|
QTreeWidgetItem *ours = eventTree->selectedItems().first();
|
|
int index = allEvents->indexOfChild(ours);
|
|
|
|
EditSeasonEventDialog dialog(context, &seasons->seasons[seasonindex].events[index]);
|
|
|
|
if (dialog.exec()) {
|
|
|
|
// update name
|
|
ours->setText(0, seasons->seasons[seasonindex].events[index].name);
|
|
ours->setText(1, seasons->seasons[seasonindex].events[index].date.toString("MMM d, yyyy"));
|
|
|
|
// save changes away
|
|
seasons->writeSeasons();
|
|
}
|
|
}
|
|
}
|
|
active = false;
|
|
}
|
|
|
|
void
|
|
LTMSidebar::setSummary(DateRange dateRange)
|
|
{
|
|
// where we construct the text
|
|
QString summaryText("");
|
|
|
|
// main totals
|
|
static const QStringList totalColumn = QStringList()
|
|
<< "workout_time"
|
|
<< "time_riding"
|
|
<< "total_distance"
|
|
<< "total_work"
|
|
<< "elevation_gain";
|
|
|
|
static const QStringList averageColumn = QStringList()
|
|
<< "average_speed"
|
|
<< "average_power"
|
|
<< "average_hr"
|
|
<< "average_cad";
|
|
|
|
static const QStringList maximumColumn = QStringList()
|
|
<< "max_speed"
|
|
<< "max_power"
|
|
<< "max_heartrate"
|
|
<< "max_cadence";
|
|
|
|
// user defined
|
|
QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString();
|
|
|
|
// in case they were set tand then unset
|
|
if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT;
|
|
QStringList metricColumn = s.split(",");
|
|
|
|
// what date range should we use?
|
|
QDate newFrom = dateRange.from;
|
|
QDate newTo = dateRange.to;
|
|
|
|
if (newFrom == from && newTo == to) return;
|
|
else {
|
|
|
|
// date range changed lets refresh
|
|
from = newFrom;
|
|
to = newTo;
|
|
|
|
Specification spec;
|
|
spec.setDateRange(DateRange(from,to));
|
|
|
|
// foreach of the metrics get an aggregated value
|
|
// header of summary
|
|
summaryText = QString("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"
|
|
"%1"
|
|
"<html>"
|
|
"<head>"
|
|
"<title></title>"
|
|
"</head>"
|
|
"<body>"
|
|
"<center>").arg(GCColor::css());
|
|
|
|
for (int i=0; i<4; i++) {
|
|
|
|
QString aggname;
|
|
QStringList list;
|
|
|
|
switch(i) {
|
|
case 0 : // Totals
|
|
aggname = tr("Totals");
|
|
list = totalColumn;
|
|
break;
|
|
|
|
case 1 : // Averages
|
|
aggname = tr("Averages");
|
|
list = averageColumn;
|
|
break;
|
|
|
|
case 3 : // Maximums
|
|
aggname = tr("Maximums");
|
|
list = maximumColumn;
|
|
break;
|
|
|
|
case 2 : // User defined..
|
|
aggname = tr("Metrics");
|
|
list = metricColumn;
|
|
break;
|
|
|
|
}
|
|
|
|
summaryText += QString("<p><table width=\"85%\">"
|
|
"<tr>"
|
|
"<td align=\"center\" colspan=\"2\">"
|
|
"<b>%1</b>"
|
|
"</td>"
|
|
"</tr>").arg(aggname);
|
|
|
|
foreach(QString metricname, list) {
|
|
|
|
const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname);
|
|
|
|
QStringList empty; // filter list not used at present
|
|
QString value = context->athlete->rideCache->getAggregate(metricname, spec, context->athlete->useMetricUnits);
|
|
|
|
// Maximum Max and Average Average looks nasty, remove from name for display
|
|
QString s = metric ? metric->name().replace(QRegExp(tr("^(Average|Max) ")), "") : "unknown";
|
|
|
|
// don't show units for time values
|
|
if (metric && (metric->units(context->athlete->useMetricUnits) == "seconds" ||
|
|
metric->units(context->athlete->useMetricUnits) == tr("seconds") ||
|
|
metric->units(context->athlete->useMetricUnits) == "")) {
|
|
|
|
summaryText += QString("<tr><td>%1:</td><td align=\"right\"> %2</td>")
|
|
.arg(s)
|
|
.arg(value);
|
|
|
|
} else {
|
|
summaryText += QString("<tr><td>%1(%2):</td><td align=\"right\"> %3</td>")
|
|
.arg(s)
|
|
.arg(metric ? metric->units(context->athlete->useMetricUnits) : "unknown")
|
|
.arg(value);
|
|
}
|
|
}
|
|
summaryText += QString("</tr>" "</table>");
|
|
|
|
}
|
|
|
|
// finish off the html document
|
|
summaryText += QString("</center>"
|
|
"</body>"
|
|
"</html>");
|
|
|
|
// set webview contents
|
|
summary->page()->mainFrame()->setHtml(summaryText);
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Preset chart functions
|
|
//
|
|
void
|
|
LTMSidebar::presetPopup(QPoint)
|
|
{
|
|
}
|
|
|
|
void
|
|
LTMSidebar::presetPopup()
|
|
{
|
|
// item points to selection, if it exists
|
|
QTreeWidgetItem *item = chartTree->selectedItems().count() ? dateRangeTree->selectedItems().at(0) : NULL;
|
|
|
|
// OK - we are working with a specific event..
|
|
QMenu menu(chartTree);
|
|
QAction *add = new QAction(tr("Add Chart"), chartTree);
|
|
menu.addAction(add);
|
|
connect(add, SIGNAL(triggered(void)), this, SLOT(addPreset(void)));
|
|
|
|
if (item != NULL) {
|
|
|
|
if (chartTree->selectedItems().count() == 1) {
|
|
QAction *edit = new QAction(tr("Edit Chart"), chartTree);
|
|
QAction *del = new QAction(tr("Delete Chart"), chartTree);
|
|
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editPreset(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deletePreset(void)));
|
|
|
|
} else {
|
|
QAction *del = new QAction(tr("Delete Selected Charts"), chartTree);
|
|
menu.addAction(del);
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deletePreset(void)));
|
|
}
|
|
|
|
menu.addSeparator();
|
|
QAction *exp = NULL;
|
|
if (chartTree->selectedItems().count() == 1) {
|
|
exp = new QAction(tr("Export Chart"), chartTree);
|
|
} else {
|
|
exp = new QAction(tr("Export Selected Charts"), chartTree);
|
|
}
|
|
// connect menu to functions
|
|
menu.addAction(exp);
|
|
connect(exp, SIGNAL(triggered(void)), this, SLOT(exportPreset(void)));
|
|
|
|
} else {
|
|
|
|
// for the import/reset options
|
|
menu.addSeparator();
|
|
}
|
|
|
|
QAction *import = new QAction(tr("Import Charts"), chartTree);
|
|
menu.addAction(import);
|
|
connect(import, SIGNAL(triggered(void)), this, SLOT(importPreset(void)));
|
|
|
|
// this one needs to be in a corner away from the crowd
|
|
menu.addSeparator();
|
|
|
|
QAction *reset = new QAction(tr("Reset to default"), chartTree);
|
|
menu.addAction(reset);
|
|
connect(reset, SIGNAL(triggered(void)), this, SLOT(resetPreset(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(splitter->mapToGlobal(QPoint(chartsWidget->pos().x()+chartsWidget->width()-20,
|
|
chartsWidget->pos().y())));
|
|
}
|
|
|
|
void
|
|
LTMSidebar::presetMoved(QTreeWidgetItem *, int, int)
|
|
{
|
|
}
|
|
|
|
void
|
|
LTMSidebar::addPreset()
|
|
{
|
|
GcWindow *newone = NULL;
|
|
|
|
// GcWindowDialog is delete on close, so no need to delete
|
|
GcWindowDialog *f = new GcWindowDialog(GcWindowTypes::LTM, context, &newone, true);
|
|
f->exec();
|
|
|
|
// returns null if cancelled or closed
|
|
if (newone) {
|
|
// append to the chart list ...
|
|
LTMSettings set = static_cast<LTMWindow*>(newone)->getSettings();
|
|
set.name = set.title = newone->property("title").toString();
|
|
context->athlete->presets.append(set);
|
|
|
|
// newone
|
|
newone->close();
|
|
newone->deleteLater();
|
|
|
|
// now wipe it
|
|
f->hide();
|
|
f->deleteLater();
|
|
|
|
// tell the world
|
|
context->notifyPresetsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::editPreset()
|
|
{
|
|
GcWindow *newone = NULL;
|
|
|
|
int index = allCharts->indexOfChild(chartTree->selectedItems()[0]);
|
|
|
|
// GcWindowDialog is delete on close, so no need to delete
|
|
GcWindowDialog *f = new GcWindowDialog(GcWindowTypes::LTM, context, &newone, true, &context->athlete->presets[index]);
|
|
f->exec();
|
|
|
|
// returns null if cancelled or closed
|
|
if (newone) {
|
|
|
|
// append to the chart list ...
|
|
LTMSettings set = static_cast<LTMWindow*>(newone)->getSettings();
|
|
set.name = set.title = newone->property("title").toString();
|
|
context->athlete->presets[index] = set;
|
|
|
|
// newone
|
|
newone->close();
|
|
newone->deleteLater();
|
|
|
|
// now wipe it
|
|
f->hide();
|
|
f->deleteLater();
|
|
|
|
// tell the world
|
|
context->notifyPresetsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::deletePreset()
|
|
{
|
|
// zap all selected
|
|
for(int index=allCharts->childCount()-1; index>0; index--)
|
|
if (allCharts->child(index)->isSelected())
|
|
context->athlete->presets.removeAt(index);
|
|
|
|
context->notifyPresetsChanged();
|
|
}
|
|
|
|
void
|
|
LTMSidebar::exportPreset()
|
|
{
|
|
// get a filename to export to...
|
|
QString filename = QFileDialog::getSaveFileName(this, tr("Export Charts"), QDir::homePath() + "/charts.xml", tr("Chart File (*.xml)"));
|
|
|
|
// nothing given
|
|
if (filename.length() == 0) return;
|
|
|
|
// get a list
|
|
QList<LTMSettings> these;
|
|
|
|
for(int index=0; index<allCharts->childCount(); index++)
|
|
if (allCharts->child(index)->isSelected())
|
|
these << context->athlete->presets[index];
|
|
|
|
LTMChartParser::serialize(filename, these);
|
|
}
|
|
|
|
void
|
|
LTMSidebar::importPreset()
|
|
{
|
|
QFileDialog existing(this);
|
|
existing.setFileMode(QFileDialog::ExistingFile);
|
|
existing.setNameFilter(tr("Chart File (*.xml)"));
|
|
|
|
if (existing.exec()){
|
|
|
|
// we will only get one (ExistingFile not ExistingFiles)
|
|
QStringList filenames = existing.selectedFiles();
|
|
|
|
if (QFileInfo(filenames[0]).exists()) {
|
|
|
|
QList<LTMSettings> imported;
|
|
QFile chartsFile(filenames[0]);
|
|
|
|
// setup XML processor
|
|
QXmlInputSource source( &chartsFile );
|
|
QXmlSimpleReader xmlReader;
|
|
LTMChartParser handler;
|
|
xmlReader.setContentHandler(&handler);
|
|
xmlReader.setErrorHandler(&handler);
|
|
|
|
// parse and get return values
|
|
xmlReader.parse(source);
|
|
imported = handler.getSettings();
|
|
|
|
// now append to the QList and QTreeWidget
|
|
context->athlete->presets += imported;
|
|
|
|
// notify we changed and tree updates
|
|
context->notifyPresetsChanged();
|
|
|
|
} else {
|
|
// oops non existent - does this ever happen?
|
|
QMessageBox::warning( 0, tr("Entry Error"), QString(tr("Selected file (%1) does not exist")).arg(filenames[0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMSidebar::resetPreset()
|
|
{
|
|
// confirm user is committed to this !
|
|
QMessageBox msgBox;
|
|
msgBox.setText(tr("You are about to reset the chart sidebar to the default setup"));
|
|
msgBox.setInformativeText(tr("Do you want to continue?"));
|
|
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
|
msgBox.setDefaultButton(QMessageBox::Cancel);
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.exec();
|
|
|
|
if(msgBox.clickedButton() != msgBox.button(QMessageBox::Ok)) return;
|
|
|
|
// for getting config
|
|
QNetworkAccessManager nam;
|
|
QString content;
|
|
|
|
// remove the current saved version
|
|
QFile::remove(context->athlete->home->config().canonicalPath() + "/charts.xml");
|
|
|
|
// fetch from the goldencheetah.org website
|
|
QString request = QString("%1/charts.xml").arg(VERSION_CONFIG_PREFIX);
|
|
QNetworkReply *reply = nam.get(QNetworkRequest(QUrl(request)));
|
|
|
|
// request submitted ok
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
|
|
// lets wait for a response with an event loop
|
|
// it quits on a 5s timeout or reply coming back
|
|
QEventLoop loop;
|
|
QTimer timer;
|
|
timer.setSingleShot(true);
|
|
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
|
|
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
timer.start(5000);
|
|
|
|
// lets block until signal received
|
|
loop.exec(QEventLoop::WaitForMoreEvents);
|
|
|
|
// all good?
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
content = reply->readAll();
|
|
}
|
|
}
|
|
|
|
// if we don't have content read from resource
|
|
if (content == "") {
|
|
|
|
QFile file(":xml/charts.xml");
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
content = file.readAll();
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
// still nowt? give up.
|
|
if (content == "") return;
|
|
|
|
// we should have content now !
|
|
QFile chartsxml(context->athlete->home->config().canonicalPath() + "/charts.xml");
|
|
chartsxml.open(QIODevice::WriteOnly);
|
|
QTextStream out(&chartsxml);
|
|
out << content;
|
|
chartsxml.close();
|
|
|
|
// following 2 lines could be managed by athlete, but its just a container really
|
|
// load and tell the world to reset
|
|
context->athlete->loadCharts();
|
|
context->notifyPresetsChanged();
|
|
}
|