mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
Porting the codebase to QT 5 (5.2) to get the latest bug fixes, performance and improved platform support. This first part is to fixup the codebase to compile on Qt 5, but some aspects have been broken (video). The second part is to migrate from Qwt 6.0.1 to the latest Qwt for multiaxis support. The third part will be to fixup any platform specific issues or issues identified at runtime.
1521 lines
49 KiB
C++
1521 lines
49 KiB
C++
/*
|
|
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
|
|
* Copyright (c) 2013 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
|
|
*/
|
|
|
|
// QT
|
|
#include <QApplication>
|
|
#include <QtGui>
|
|
#include <QRegExp>
|
|
#include <QDesktopWidget>
|
|
#include <QNetworkProxyQuery>
|
|
#include <QMenuBar>
|
|
#include <QStyle>
|
|
#include <QStyleFactory>
|
|
|
|
// DATA STRUCTURES
|
|
#include "MainWindow.h"
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
|
|
#include "Colors.h"
|
|
#include "RideItem.h"
|
|
#include "IntervalItem.h"
|
|
#include "RideFile.h"
|
|
#include "Settings.h"
|
|
#include "ErgDB.h"
|
|
#include "Library.h"
|
|
#include "LibraryParser.h"
|
|
#include "TrainDB.h"
|
|
#include "GcUpgrade.h"
|
|
|
|
// DIALOGS / DOWNLOADS / UPLOADS
|
|
#include "AboutDialog.h"
|
|
#include "ChooseCyclistDialog.h"
|
|
#include "ConfigDialog.h"
|
|
#include "DownloadRideDialog.h"
|
|
#include "ManualRideDialog.h"
|
|
#include "RideImportWizard.h"
|
|
#include "ToolsDialog.h"
|
|
#include "ToolsRhoEstimator.h"
|
|
#include "SplitActivityWizard.h"
|
|
#include "MergeActivityWizard.h"
|
|
#include "BatchExportDialog.h"
|
|
#include "TwitterDialog.h"
|
|
#include "ShareDialog.h"
|
|
#include "TtbDialog.h"
|
|
#include "WithingsDownload.h"
|
|
#include "ZeoDownload.h"
|
|
#include "WorkoutWizard.h"
|
|
#include "ErgDBDownloadDialog.h"
|
|
#include "AddDeviceWizard.h"
|
|
#ifdef GC_HAVE_SOAP
|
|
#include "TPUploadDialog.h"
|
|
#include "TPDownloadDialog.h"
|
|
#endif
|
|
#ifdef GC_HAVE_ICAL
|
|
#include "CalDAV.h"
|
|
#endif
|
|
#include "CalendarDownload.h"
|
|
|
|
// GUI Widgets
|
|
#include "Tab.h"
|
|
#include "GcToolBar.h"
|
|
#include "HelpWindow.h"
|
|
#include "HomeWindow.h"
|
|
#include "GcScopeBar.h"
|
|
#ifdef Q_OS_MAC
|
|
#include "QtMacButton.h" // mac
|
|
#include "QtMacSegmentedButton.h" // mac
|
|
#else
|
|
#include "QTFullScreen.h" // not mac!
|
|
#include "../qtsolutions/segmentcontrol/qtsegmentcontrol.h"
|
|
#endif
|
|
|
|
// SEARCH / FILTER
|
|
#ifdef GC_HAVE_LUCENE
|
|
#include "Lucene.h"
|
|
#include "NamedSearch.h"
|
|
#include "SearchFilterBox.h"
|
|
#endif
|
|
|
|
#ifdef GC_HAVE_WFAPI
|
|
#include "WFApi.h"
|
|
#endif
|
|
|
|
// We keep track of all theopen mainwindows
|
|
QList<MainWindow *> mainwindows;
|
|
QDesktopWidget *desktop = NULL;
|
|
|
|
MainWindow::MainWindow(const QDir &home)
|
|
{
|
|
/*----------------------------------------------------------------------
|
|
* Bootstrap
|
|
*--------------------------------------------------------------------*/
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
mainwindows.append(this); // add us to the list of open windows
|
|
context = new Context(this);
|
|
context->athlete = new Athlete(context, home);
|
|
|
|
setWindowIcon(QIcon(":images/gc.png"));
|
|
setWindowTitle(context->athlete->home.dirName());
|
|
setContentsMargins(0,0,0,0);
|
|
setAcceptDrops(true);
|
|
GCColor *GCColorSet = new GCColor(context); // get/keep colorset
|
|
GCColorSet->colorSet(); // shut up the compiler
|
|
|
|
#ifdef Q_OS_MAC
|
|
// get an autorelease pool setup
|
|
static CocoaInitializer cocoaInitializer;
|
|
#endif
|
|
#ifdef GC_HAVE_WFAPI
|
|
WFApi *w = WFApi::getInstance(); // ensure created on main thread
|
|
w->apiVersion();//shutup compiler
|
|
#endif
|
|
Library::initialise(context->athlete->home);
|
|
QNetworkProxyQuery npq(QUrl("http://www.google.com"));
|
|
QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(npq);
|
|
if (listOfProxies.count() > 0) {
|
|
QNetworkProxy::setApplicationProxy(listOfProxies.first());
|
|
}
|
|
|
|
if (desktop == NULL) desktop = QApplication::desktop();
|
|
static const QIcon hideIcon(":images/toolbar/main/hideside.png");
|
|
static const QIcon rhideIcon(":images/toolbar/main/hiderside.png");
|
|
static const QIcon showIcon(":images/toolbar/main/showside.png");
|
|
static const QIcon rshowIcon(":images/toolbar/main/showrside.png");
|
|
static const QIcon tabIcon(":images/toolbar/main/tab.png");
|
|
static const QIcon tileIcon(":images/toolbar/main/tile.png");
|
|
static const QIcon fullIcon(":images/toolbar/main/togglefull.png");
|
|
|
|
#ifndef Q_OS_MAC
|
|
fullScreen = new QTFullScreen(context);
|
|
#endif
|
|
|
|
// if no workout directory is configured, default to the
|
|
// top level GoldenCheetah directory
|
|
if (appsettings->value(NULL, GC_WORKOUTDIR).toString() == "")
|
|
appsettings->setValue(GC_WORKOUTDIR, QFileInfo(context->athlete->home.absolutePath() + "/../").absolutePath());
|
|
|
|
/*----------------------------------------------------------------------
|
|
* GUI setup
|
|
*--------------------------------------------------------------------*/
|
|
|
|
// need to restore geometry before setUnifiedToolBar.. on Mac
|
|
appsettings->setValue(GC_SETTINGS_LAST, context->athlete->home.dirName());
|
|
QVariant geom = appsettings->value(this, GC_SETTINGS_MAIN_GEOM);
|
|
if (geom == QVariant()) {
|
|
|
|
// first run -- lets set some sensible defaults...
|
|
// lets put it in the middle of screen 1
|
|
QRect size = desktop->availableGeometry();
|
|
struct SizeSettings app = GCColor::defaultSizes(size.height(), size.width());
|
|
|
|
// center on the available screen (minus toolbar/sidebar)
|
|
move((size.width()-size.x())/2 - app.width/2,
|
|
(size.height()-size.y())/2 - app.height/2);
|
|
|
|
// set to the right default
|
|
resize(app.width, app.height);
|
|
|
|
// set all the default font sizes
|
|
appsettings->setValue(GC_FONT_DEFAULT_SIZE, app.defaultFont);
|
|
appsettings->setValue(GC_FONT_TITLES_SIZE, app.titleFont);
|
|
appsettings->setValue(GC_FONT_CHARTMARKERS_SIZE, app.markerFont);
|
|
appsettings->setValue(GC_FONT_CHARTLABELS_SIZE, app.labelFont);
|
|
appsettings->setValue(GC_FONT_CALENDAR_SIZE, app.calendarFont);
|
|
appsettings->setValue(GC_FONT_POPUP_SIZE, app.popupFont);
|
|
|
|
// set the default fontsize
|
|
QFont font;
|
|
font.setPointSize(app.defaultFont);
|
|
QApplication::setFont(font);
|
|
|
|
} else {
|
|
|
|
QRect size = desktop->availableGeometry();
|
|
|
|
// ensure saved geometry isn't greater than current screen size
|
|
if ((geom.toRect().height() >= size.height()) || (geom.toRect().width() >= size.width()))
|
|
setGeometry(size.x()+30,size.y()+30,size.width()-60,size.height()-60);
|
|
else
|
|
setGeometry(geom.toRect());
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Mac Toolbar
|
|
*--------------------------------------------------------------------*/
|
|
#ifdef Q_OS_MAC
|
|
setUnifiedTitleAndToolBarOnMac(true);
|
|
head = addToolBar(context->athlete->cyclist);
|
|
head->setContentsMargins(0,0,0,0);
|
|
|
|
// widgets
|
|
QWidget *macAnalButtons = new QWidget(this);
|
|
macAnalButtons->setContentsMargins(0,0,20,0);
|
|
|
|
// lhs buttons
|
|
QHBoxLayout *lb = new QHBoxLayout(macAnalButtons);
|
|
lb->setContentsMargins(0,0,0,0);
|
|
lb->setSpacing(0);
|
|
import = new QtMacButton(this, QtMacButton::TexturedRounded);
|
|
QPixmap *importImg = new QPixmap(":images/mac/download.png");
|
|
import->setImage(importImg);
|
|
import->setToolTip("Download");
|
|
lb->addWidget(import);
|
|
lb->addWidget(new Spacer(this));
|
|
compose = new QtMacButton(this, QtMacButton::TexturedRounded);
|
|
QPixmap *composeImg = new QPixmap(":images/mac/compose.png");
|
|
compose->setImage(composeImg);
|
|
compose->setToolTip("Create");
|
|
lb->addWidget(compose);
|
|
|
|
// connect to actions
|
|
connect(import, SIGNAL(clicked(bool)), this, SLOT(downloadRide()));
|
|
connect(compose, SIGNAL(clicked(bool)), this, SLOT(manualRide()));
|
|
|
|
lb->addWidget(new Spacer(this));
|
|
|
|
// activity actions .. peaks, split, delete
|
|
QWidget *acts = new QWidget(this);
|
|
acts->setContentsMargins(0,0,0,0);
|
|
QHBoxLayout *pp = new QHBoxLayout(acts);
|
|
pp->setContentsMargins(0,0,0,0);
|
|
pp->setContentsMargins(0,0,0,0);
|
|
pp->setSpacing(5);
|
|
sidebar = new QtMacButton(this, QtMacButton::TexturedRounded);
|
|
QPixmap *sidebarImg = new QPixmap(":images/mac/sidebar.png");
|
|
sidebar->setImage(sidebarImg);
|
|
sidebar->setMinimumSize(25, 25);
|
|
sidebar->setMaximumSize(25, 25);
|
|
sidebar->setToolTip("Sidebar");
|
|
sidebar->setSelected(true); // assume always start up with sidebar selected
|
|
|
|
lowbar = new QtMacButton(this, QtMacButton::TexturedRounded);
|
|
QPixmap *lowbarImg = new QPixmap(":images/mac/lowbar.png");
|
|
lowbar->setImage(lowbarImg);
|
|
lowbar->setMinimumSize(25, 25);
|
|
lowbar->setMaximumSize(25, 25);
|
|
lowbar->setToolTip("Compare");
|
|
lowbar->setSelected(false); // assume always start up with lowbar deselected
|
|
|
|
actbuttons = new QtMacSegmentedButton(3, acts);
|
|
actbuttons->setWidth(115);
|
|
actbuttons->setNoSelect();
|
|
actbuttons->setImage(0, new QPixmap(":images/mac/stop.png"));
|
|
actbuttons->setImage(1, new QPixmap(":images/mac/split.png"));
|
|
actbuttons->setImage(2, new QPixmap(":images/mac/trash.png"));
|
|
pp->addWidget(actbuttons);
|
|
lb->addWidget(acts);
|
|
lb->addStretch();
|
|
connect(actbuttons, SIGNAL(clicked(int,bool)), this, SLOT(actionClicked(int)));
|
|
|
|
lb->addWidget(new Spacer(this));
|
|
|
|
QWidget *viewsel = new QWidget(this);
|
|
viewsel->setContentsMargins(0,0,0,0);
|
|
QHBoxLayout *pq = new QHBoxLayout(viewsel);
|
|
pq->setContentsMargins(0,0,0,0);
|
|
pq->setSpacing(5);
|
|
QHBoxLayout *ps = new QHBoxLayout;
|
|
ps->setContentsMargins(0,0,0,0);
|
|
ps->setSpacing (2); // low and sidebar button close together
|
|
ps->addStretch();
|
|
ps->addWidget(sidebar);
|
|
ps->addWidget(lowbar);
|
|
ps->addStretch();
|
|
pq->addLayout(ps);
|
|
|
|
styleSelector = new QtMacSegmentedButton(2, viewsel);
|
|
styleSelector->setWidth(80); // actually its 80 but we want a 30px space between is and the searchbox
|
|
styleSelector->setImage(0, new QPixmap(":images/mac/tabbed.png"), 24);
|
|
styleSelector->setImage(1, new QPixmap(":images/mac/tiled.png"), 24);
|
|
pq->addWidget(styleSelector);
|
|
connect(sidebar, SIGNAL(clicked(bool)), this, SLOT(toggleSidebar()));
|
|
connect(lowbar, SIGNAL(clicked(bool)), this, SLOT(toggleLowbar()));
|
|
connect(styleSelector, SIGNAL(clicked(int,bool)), this, SLOT(toggleStyle()));
|
|
|
|
// setup Mac thetoolbar
|
|
head->addWidget(macAnalButtons);
|
|
head->addWidget(new Spacer(this));
|
|
head->addWidget(new Spacer(this));
|
|
head->addWidget(viewsel);
|
|
|
|
#ifdef GC_HAVE_LUCENE
|
|
SearchFilterBox *searchBox = new SearchFilterBox(this,context,false);
|
|
QStyle *toolStyle = QStyleFactory::create("fusion");
|
|
searchBox->setStyle(toolStyle);
|
|
searchBox->setFixedWidth(200);
|
|
head->addWidget(searchBox);
|
|
connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList)));
|
|
connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter()));
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Windows and Linux Toolbar
|
|
*--------------------------------------------------------------------*/
|
|
#ifndef Q_OS_MAC
|
|
|
|
head = new GcToolBar(this);
|
|
|
|
QStyle *toolStyle = QStyleFactory::create("fusion");
|
|
QPalette metal;
|
|
metal.setColor(QPalette::Button, QColor(215,215,215));
|
|
|
|
// get those icons
|
|
importIcon = iconFromPNG(":images/mac/download.png");
|
|
composeIcon = iconFromPNG(":images/mac/compose.png");
|
|
intervalIcon = iconFromPNG(":images/mac/stop.png");
|
|
splitIcon = iconFromPNG(":images/mac/split.png");
|
|
deleteIcon = iconFromPNG(":images/mac/trash.png");
|
|
sidebarIcon = iconFromPNG(":images/mac/sidebar.png");
|
|
lowbarIcon = iconFromPNG(":images/mac/lowbar.png");
|
|
tabbedIcon = iconFromPNG(":images/mac/tabbed.png");
|
|
tiledIcon = iconFromPNG(":images/mac/tiled.png");
|
|
QSize isize(19,19);
|
|
|
|
Spacer *spacerl = new Spacer(this);
|
|
spacerl->setFixedWidth(5);
|
|
|
|
import = new QPushButton(this);
|
|
import->setIcon(importIcon);
|
|
import->setIconSize(isize);
|
|
import->setFixedHeight(25);
|
|
import->setStyle(toolStyle);
|
|
import->setToolTip(tr("Download from Device"));
|
|
import->setPalette(metal);
|
|
connect(import, SIGNAL(clicked(bool)), this, SLOT(downloadRide()));
|
|
|
|
compose = new QPushButton(this);
|
|
compose->setIcon(composeIcon);
|
|
compose->setIconSize(isize);
|
|
compose->setFixedHeight(25);
|
|
compose->setStyle(toolStyle);
|
|
compose->setToolTip(tr("Create Manual Activity"));
|
|
compose->setPalette(metal);
|
|
connect(compose, SIGNAL(clicked(bool)), this, SLOT(manualRide()));
|
|
|
|
lowbar = new QPushButton(this);
|
|
lowbar->setIcon(lowbarIcon);
|
|
lowbar->setIconSize(isize);
|
|
lowbar->setFixedHeight(25);
|
|
lowbar->setStyle(toolStyle);
|
|
lowbar->setToolTip(tr("Toggle Compare Pane"));
|
|
lowbar->setPalette(metal);
|
|
connect(lowbar, SIGNAL(clicked(bool)), this, SLOT(toggleLowbar()));
|
|
|
|
sidebar = new QPushButton(this);
|
|
sidebar->setIcon(sidebarIcon);
|
|
sidebar->setIconSize(isize);
|
|
sidebar->setFixedHeight(25);
|
|
sidebar->setStyle(toolStyle);
|
|
sidebar->setToolTip(tr("Toggle Sidebar"));
|
|
sidebar->setPalette(metal);
|
|
connect(sidebar, SIGNAL(clicked(bool)), this, SLOT(toggleSidebar()));
|
|
|
|
actbuttons = new QtSegmentControl(this);
|
|
actbuttons->setStyle(toolStyle);
|
|
actbuttons->setIconSize(isize);
|
|
actbuttons->setCount(3);
|
|
actbuttons->setSegmentIcon(0, intervalIcon);
|
|
actbuttons->setSegmentIcon(1, splitIcon);
|
|
actbuttons->setSegmentIcon(2, deleteIcon);
|
|
actbuttons->setSelectionBehavior(QtSegmentControl::SelectNone); //wince. spelling. ugh
|
|
actbuttons->setFixedHeight(25);
|
|
actbuttons->setSegmentToolTip(0, tr("Find Intervals..."));
|
|
actbuttons->setSegmentToolTip(1, tr("Split Activity..."));
|
|
actbuttons->setSegmentToolTip(2, tr("Delete Activity"));
|
|
actbuttons->setPalette(metal);
|
|
connect(actbuttons, SIGNAL(segmentSelected(int)), this, SLOT(actionClicked(int)));
|
|
|
|
styleSelector = new QtSegmentControl(this);
|
|
styleSelector->setStyle(toolStyle);
|
|
styleSelector->setIconSize(isize);
|
|
styleSelector->setCount(2);
|
|
styleSelector->setSegmentIcon(0, tabbedIcon);
|
|
styleSelector->setSegmentIcon(1, tiledIcon);
|
|
styleSelector->setSegmentToolTip(0, tr("Tabbed View"));
|
|
styleSelector->setSegmentToolTip(1, tr("Tiled View"));
|
|
styleSelector->setSelectionBehavior(QtSegmentControl::SelectOne); //wince. spelling. ugh
|
|
styleSelector->setFixedHeight(25);
|
|
styleSelector->setPalette(metal);
|
|
connect(styleSelector, SIGNAL(segmentSelected(int)), this, SLOT(setStyleFromSegment(int))); //avoid toggle infinitely
|
|
|
|
head->addWidget(spacerl);
|
|
head->addWidget(import);
|
|
head->addWidget(compose);
|
|
head->addWidget(actbuttons);
|
|
|
|
head->addStretch();
|
|
head->addWidget(sidebar);
|
|
head->addWidget(lowbar);
|
|
head->addWidget(styleSelector);
|
|
|
|
#ifdef GC_HAVE_LUCENE
|
|
// add a search box on far right, but with a little space too
|
|
SearchFilterBox *searchBox = new SearchFilterBox(this,context,false);
|
|
searchBox->setStyle(toolStyle);
|
|
searchBox->setFixedWidth(200);
|
|
head->addWidget(searchBox);
|
|
connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList)));
|
|
connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter()));
|
|
#endif
|
|
Spacer *spacer = new Spacer(this);
|
|
spacer->setFixedWidth(5);
|
|
head->addWidget(spacer);
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------
|
|
* ScopeBar
|
|
*--------------------------------------------------------------------*/
|
|
scopebar = new GcScopeBar(context);
|
|
connect(scopebar, SIGNAL(selectDiary()), this, SLOT(selectDiary()));
|
|
connect(scopebar, SIGNAL(selectHome()), this, SLOT(selectHome()));
|
|
connect(scopebar, SIGNAL(selectAnal()), this, SLOT(selectAnalysis()));
|
|
connect(scopebar, SIGNAL(selectTrain()), this, SLOT(selectTrain()));
|
|
|
|
// Add chart is on the scope bar
|
|
chartMenu = new QMenu(this);
|
|
QStyle *styler = QStyleFactory::create("fusion");
|
|
QPushButton *newchart = new QPushButton("+", this);
|
|
scopebar->addWidget(newchart);
|
|
newchart->setStyle(styler);
|
|
newchart->setFixedHeight(20);
|
|
newchart->setFixedWidth(24);
|
|
newchart->setFlat(true);
|
|
newchart->setFocusPolicy(Qt::NoFocus);
|
|
newchart->setToolTip(tr("Add Chart"));
|
|
newchart->setAutoFillBackground(false);
|
|
newchart->setAutoDefault(false);
|
|
newchart->setMenu(chartMenu);
|
|
connect(chartMenu, SIGNAL(aboutToShow()), this, SLOT(setChartMenu()));
|
|
connect(chartMenu, SIGNAL(triggered(QAction*)), this, SLOT(addChart(QAction*)));
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Central Widget
|
|
*--------------------------------------------------------------------*/
|
|
|
|
tab = new Tab(context);
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Central Widget
|
|
*--------------------------------------------------------------------*/
|
|
|
|
QWidget *central = new QWidget(this);
|
|
setContentsMargins(0,0,0,0);
|
|
central->setContentsMargins(0,0,0,0);
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(central);
|
|
mainLayout->setSpacing(0);
|
|
mainLayout->setContentsMargins(0,0,0,0);
|
|
#ifndef Q_OS_MAC // nonmac toolbar on main view -- its not
|
|
// unified with the title bar.
|
|
mainLayout->addWidget(head);
|
|
#endif
|
|
mainLayout->addWidget(scopebar);
|
|
mainLayout->addWidget(tab);
|
|
setCentralWidget(central);
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Application Menus
|
|
*--------------------------------------------------------------------*/
|
|
#ifdef WIN32
|
|
menuBar()->setStyleSheet("QMenuBar { background: rgba(225,225,225); }"
|
|
"QMenuBar::item { background: rgba(225,225,225); }");
|
|
menuBar()->setContentsMargins(0,0,0,0);
|
|
#endif
|
|
|
|
QMenu *fileMenu = menuBar()->addMenu(tr("&Athlete"));
|
|
fileMenu->addAction(tr("&New..."), this, SLOT(newCyclist()), tr("Ctrl+N"));
|
|
fileMenu->addAction(tr("&Open..."), this, SLOT(openCyclist()), tr("Ctrl+O"));
|
|
fileMenu->addAction(tr("&Close Window"), this, SLOT(close()), tr ("Ctrl+W"));
|
|
fileMenu->addAction(tr("&Quit All Windows"), this, SLOT(closeAll()), tr("Ctrl+Q"));
|
|
|
|
QMenu *rideMenu = menuBar()->addMenu(tr("A&ctivity"));
|
|
rideMenu->addAction(tr("&Download from device..."), this, SLOT(downloadRide()), tr("Ctrl+D"));
|
|
rideMenu->addAction(tr("&Import from file..."), this, SLOT (importFile()), tr ("Ctrl+I"));
|
|
rideMenu->addAction(tr("&Manual activity entry..."), this, SLOT(manualRide()), tr("Ctrl+M"));
|
|
rideMenu->addSeparator ();
|
|
rideMenu->addAction(tr("&Export..."), this, SLOT(exportRide()), tr("Ctrl+E"));
|
|
rideMenu->addAction(tr("&Batch export..."), this, SLOT(exportBatch()), tr("Ctrl+B"));
|
|
rideMenu->addAction(tr("Export Metrics as CSV..."), this, SLOT(exportMetrics()), tr(""));
|
|
#ifdef GC_HAVE_SOAP
|
|
rideMenu->addSeparator ();
|
|
rideMenu->addAction(tr("&Upload to TrainingPeaks"), this, SLOT(uploadTP()), tr("Ctrl+U"));
|
|
rideMenu->addAction(tr("Down&load from TrainingPeaks..."), this, SLOT(downloadTP()), tr("Ctrl+L"));
|
|
#endif
|
|
|
|
#ifdef GC_HAVE_LIBOAUTH
|
|
tweetAction = new QAction(tr("Tweet Activity"), this);
|
|
connect(tweetAction, SIGNAL(triggered(bool)), this, SLOT(tweetRide()));
|
|
rideMenu->addAction(tweetAction);
|
|
|
|
shareAction = new QAction(tr("Share (Strava, RideWithGPS, CyclingAnalytics)..."), this);
|
|
connect(shareAction, SIGNAL(triggered(bool)), this, SLOT(share()));
|
|
rideMenu->addAction(shareAction);
|
|
#endif
|
|
|
|
ttbAction = new QAction(tr("Upload to Trainingstagebuch..."), this);
|
|
connect(ttbAction, SIGNAL(triggered(bool)), this, SLOT(uploadTtb()));
|
|
rideMenu->addAction(ttbAction);
|
|
|
|
rideMenu->addSeparator ();
|
|
rideMenu->addAction(tr("&Save activity"), this, SLOT(saveRide()), tr("Ctrl+S"));
|
|
rideMenu->addAction(tr("D&elete activity..."), this, SLOT(deleteRide()));
|
|
rideMenu->addAction(tr("Split &activity..."), this, SLOT(splitRide()));
|
|
rideMenu->addAction(tr("Merge activities..."), this, SLOT(mergeRide()));
|
|
rideMenu->addSeparator ();
|
|
|
|
QMenu *optionsMenu = menuBar()->addMenu(tr("&Tools"));
|
|
optionsMenu->addAction(tr("&Options..."), this, SLOT(showOptions()));
|
|
optionsMenu->addAction(tr("CP and W' Estimator..."), this, SLOT(showTools()));
|
|
optionsMenu->addAction(tr("Air Density (Rho) Estimator..."), this, SLOT(showRhoEstimator()));
|
|
|
|
optionsMenu->addSeparator();
|
|
optionsMenu->addAction(tr("Get &Withings Data..."), this,
|
|
SLOT (downloadMeasures()));
|
|
optionsMenu->addAction(tr("Get &Zeo Data..."), this,
|
|
SLOT (downloadMeasuresFromZeo()));
|
|
optionsMenu->addSeparator();
|
|
optionsMenu->addAction(tr("Create a new workout..."), this, SLOT(showWorkoutWizard()));
|
|
optionsMenu->addAction(tr("Download workouts from ErgDB..."), this, SLOT(downloadErgDB()));
|
|
optionsMenu->addAction(tr("Import workouts or videos..."), this, SLOT(importWorkout()));
|
|
optionsMenu->addAction(tr("Scan disk for videos and workouts..."), this, SLOT(manageLibrary()));
|
|
|
|
#ifdef GC_HAVE_ICAL
|
|
optionsMenu->addSeparator();
|
|
optionsMenu->addAction(tr("Upload Activity to Calendar"), this, SLOT(uploadCalendar()), tr (""));
|
|
//optionsMenu->addAction(tr("Import Calendar..."), this, SLOT(importCalendar()), tr ("")); // planned for v3.1
|
|
//optionsMenu->addAction(tr("Export Calendar..."), this, SLOT(exportCalendar()), tr ("")); // planned for v3.1
|
|
optionsMenu->addAction(tr("Refresh Calendar"), this, SLOT(refreshCalendar()), tr (""));
|
|
#endif
|
|
optionsMenu->addSeparator();
|
|
optionsMenu->addAction(tr("Find intervals..."), this, SLOT(addIntervals()), tr (""));
|
|
|
|
// Add all the data processors to the tools menu
|
|
const DataProcessorFactory &factory = DataProcessorFactory::instance();
|
|
QMap<QString, DataProcessor*> processors = factory.getProcessors();
|
|
|
|
if (processors.count()) {
|
|
|
|
optionsMenu->addSeparator();
|
|
toolMapper = new QSignalMapper(this); // maps each option
|
|
QMapIterator<QString, DataProcessor*> i(processors);
|
|
connect(toolMapper, SIGNAL(mapped(const QString &)), this, SLOT(manualProcess(const QString &)));
|
|
|
|
i.toFront();
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
// The localized processor name is shown in menu
|
|
QAction *action = new QAction(QString("%1...").arg(i.value()->name()), this);
|
|
optionsMenu->addAction(action);
|
|
connect(action, SIGNAL(triggered()), toolMapper, SLOT(map()));
|
|
toolMapper->setMapping(action, i.key());
|
|
}
|
|
}
|
|
|
|
QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
|
|
#ifndef Q_OS_MAC
|
|
viewMenu->addAction(tr("Toggle Full Screen"), this, SLOT(toggleFullScreen()), QKeySequence("F11"));
|
|
#endif
|
|
showhideSidebar = viewMenu->addAction(tr("Show Left Sidebar"), this, SLOT(showSidebar(bool)));
|
|
showhideSidebar->setCheckable(true);
|
|
showhideSidebar->setChecked(true);
|
|
showhideLowbar = viewMenu->addAction(tr("Show Compare Pane"), this, SLOT(showLowbar(bool)));
|
|
showhideLowbar->setCheckable(true);
|
|
showhideLowbar->setChecked(false);
|
|
#ifndef Q_OS_MAC // not on a Mac
|
|
QAction *showhideToolbar = viewMenu->addAction(tr("Show Toolbar"), this, SLOT(showToolbar(bool)));
|
|
showhideToolbar->setCheckable(true);
|
|
showhideToolbar->setChecked(true);
|
|
#endif
|
|
|
|
styleAction = viewMenu->addAction(tr("Tabbed View"), this, SLOT(toggleStyle()));
|
|
styleAction->setCheckable(true);
|
|
styleAction->setChecked(true);
|
|
|
|
//connect(showhideSidebar, SIGNAL(triggered(bool)), this, SLOT(showSidebar(bool)));
|
|
viewMenu->addSeparator();
|
|
viewMenu->addAction(tr("Analysis"), this, SLOT(selectAnalysis()));
|
|
viewMenu->addAction(tr("Home"), this, SLOT(selectHome()));
|
|
viewMenu->addAction(tr("Train"), this, SLOT(selectTrain()));
|
|
#ifdef GC_HAVE_ICAL
|
|
viewMenu->addAction(tr("Diary"), this, SLOT(selectDiary()));
|
|
#endif
|
|
viewMenu->addSeparator();
|
|
subChartMenu = viewMenu->addMenu(tr("Add Chart"));
|
|
viewMenu->addAction(tr("Reset Layout"), this, SLOT(resetWindowLayout()));
|
|
|
|
windowMenu = menuBar()->addMenu(tr("&Window"));
|
|
connect(windowMenu, SIGNAL(aboutToShow()), this, SLOT(setWindowMenu()));
|
|
connect(rideMenu, SIGNAL(aboutToShow()), this, SLOT(setActivityMenu()));
|
|
connect(subChartMenu, SIGNAL(aboutToShow()), this, SLOT(setSubChartMenu()));
|
|
connect(subChartMenu, SIGNAL(triggered(QAction*)), this, SLOT(addChart(QAction*)));
|
|
connect(windowMenu, SIGNAL(triggered(QAction*)), this, SLOT(selectWindow(QAction*)));
|
|
|
|
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
|
|
helpMenu->addAction(tr("&User Guide"), this, SLOT(helpView()));
|
|
helpMenu->addAction(tr("&Log a bug or feature request"), this, SLOT(logBug()));
|
|
helpMenu->addSeparator();
|
|
helpMenu->addAction(tr("&About GoldenCheetah"), this, SLOT(aboutDialog()));
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Lets go, choose latest ride and get GUI up and running
|
|
*--------------------------------------------------------------------*/
|
|
|
|
// selects the latest ride in the list:
|
|
if (context->athlete->allRides->childCount() != 0)
|
|
context->athlete->treeWidget->setCurrentItem(context->athlete->allRides->child(context->athlete->allRides->childCount()-1));
|
|
|
|
//XXX!!! We really do need a mechanism for showing if a ride needs saving...
|
|
//connect(this, SIGNAL(rideDirty()), this, SLOT(enableSaveButton()));
|
|
//connect(this, SIGNAL(rideClean()), this, SLOT(enableSaveButton()));
|
|
|
|
// cpx aggregate cache check
|
|
connect(context,SIGNAL(rideSelected(RideItem*)), this, SLOT(rideSelected(RideItem*)));
|
|
|
|
// Kick off - select a ride and switch to Analysis View
|
|
context->athlete->rideTreeWidgetSelectionChanged();
|
|
selectAnalysis();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* GUI
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::toggleSidebar()
|
|
{
|
|
tab->toggleSidebar();
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::showSidebar(bool want)
|
|
{
|
|
tab->setSidebarEnabled(want);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::toggleLowbar()
|
|
{
|
|
if (tab->hasBottom()) tab->setShowBottom(!tab->isShowBottom());
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::showLowbar(bool want)
|
|
{
|
|
if (tab->hasBottom()) tab->setShowBottom(want);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::showToolbar(bool want)
|
|
{
|
|
if (want) {
|
|
head->show();
|
|
scopebar->show();
|
|
}
|
|
else {
|
|
head->hide();
|
|
scopebar->hide();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::setChartMenu()
|
|
{
|
|
unsigned int mask=0;
|
|
|
|
// called when chart menu about to be shown
|
|
// setup to only show charts that are relevant
|
|
// to this view
|
|
switch(tab->currentView()) {
|
|
case 0 : mask = VIEW_HOME; break;
|
|
default:
|
|
case 1 : mask = VIEW_ANALYSIS; break;
|
|
case 2 : mask = VIEW_DIARY; break;
|
|
case 3 : mask = VIEW_TRAIN; break;
|
|
}
|
|
|
|
chartMenu->clear();
|
|
if (!mask) return;
|
|
|
|
for(int i=0; GcWindows[i].relevance; i++) {
|
|
if (GcWindows[i].relevance & mask)
|
|
chartMenu->addAction(GcWindows[i].name);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::setSubChartMenu()
|
|
{
|
|
unsigned int mask=0;
|
|
// called when chart menu about to be shown
|
|
// setup to only show charts that are relevant
|
|
// to this view
|
|
switch(tab->currentView()) {
|
|
case 0 : mask = VIEW_HOME; break;
|
|
default:
|
|
case 1 : mask = VIEW_ANALYSIS; break;
|
|
case 2 : mask = VIEW_DIARY; break;
|
|
case 3 : mask = VIEW_TRAIN; break;
|
|
}
|
|
|
|
subChartMenu->clear();
|
|
if (!mask) return;
|
|
|
|
for(int i=0; GcWindows[i].relevance; i++) {
|
|
if (GcWindows[i].relevance & mask)
|
|
subChartMenu->addAction(GcWindows[i].name);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::setActivityMenu()
|
|
{
|
|
// enable/disable upload if already uploaded
|
|
if (context->ride && context->ride->ride()) {
|
|
|
|
|
|
QString activityId = context->ride->ride()->getTag("TtbExercise", "");
|
|
if (activityId == "") ttbAction->setEnabled(true);
|
|
else ttbAction->setEnabled(false);
|
|
|
|
} else {
|
|
ttbAction->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::addChart(QAction*action)
|
|
{
|
|
GcWinID id = GcWindowTypes::None;
|
|
for (int i=0; GcWindows[i].relevance; i++) {
|
|
if (GcWindows[i].name == action->text()) {
|
|
id = GcWindows[i].id;
|
|
break;
|
|
}
|
|
}
|
|
if (id != GcWindowTypes::None)
|
|
tab->addChart(id); // called from MainWindow to inset chart
|
|
}
|
|
|
|
void
|
|
MainWindow::setWindowMenu()
|
|
{
|
|
windowMenu->clear();
|
|
foreach (MainWindow *m, mainwindows) {
|
|
windowMenu->addAction(m->context->athlete->cyclist);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::selectWindow(QAction *act)
|
|
{
|
|
foreach (MainWindow *m, mainwindows) {
|
|
if (m->context->athlete->cyclist == act->text()) {
|
|
m->activateWindow();
|
|
m->raise();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::setStyleFromSegment(int segment)
|
|
{
|
|
tab->setTiled(segment);
|
|
styleAction->setChecked(!segment);
|
|
}
|
|
|
|
void
|
|
MainWindow::toggleStyle()
|
|
{
|
|
tab->toggleTile();
|
|
styleAction->setChecked(tab->isTiled());
|
|
setToolButtons();
|
|
}
|
|
|
|
#ifndef Q_OS_MAC
|
|
void
|
|
MainWindow::toggleFullScreen()
|
|
{
|
|
if (fullScreen) fullScreen->toggle();
|
|
else qDebug()<<"no fullscreen support compiled in.";
|
|
}
|
|
#endif
|
|
|
|
void
|
|
MainWindow::resizeEvent(QResizeEvent*)
|
|
{
|
|
appsettings->setValue(GC_SETTINGS_MAIN_GEOM, geometry());
|
|
#ifdef Q_OS_MAC
|
|
head->updateGeometry();
|
|
repaint();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
MainWindow::showOptions()
|
|
{
|
|
ConfigDialog *cd = new ConfigDialog(context->athlete->home, context->athlete->zones_, context);
|
|
|
|
// move to the centre of the screen
|
|
cd->move(geometry().center()-QPoint(cd->geometry().width()/2, cd->geometry().height()/2));
|
|
cd->show();
|
|
}
|
|
|
|
void
|
|
MainWindow::moveEvent(QMoveEvent*)
|
|
{
|
|
appsettings->setValue(GC_SETTINGS_MAIN_GEOM, geometry());
|
|
}
|
|
|
|
void
|
|
MainWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
if (saveRideExitDialog() == false) event->ignore();
|
|
else {
|
|
|
|
#ifdef GC_HAVE_LUCENE
|
|
// save the named searches
|
|
context->athlete->namedSearches->write();
|
|
#endif
|
|
|
|
// clear the clipboard if neccessary
|
|
QApplication::clipboard()->setText("");
|
|
|
|
// close the tab
|
|
tab->close();
|
|
|
|
// close athlete
|
|
context->athlete->close();
|
|
|
|
// now remove from the list
|
|
if(mainwindows.removeOne(this) == false)
|
|
qDebug()<<"closeEvent: mainwindows list error";
|
|
|
|
}
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
delete tab;
|
|
delete context->athlete;
|
|
}
|
|
|
|
// global search/data filter
|
|
void MainWindow::setFilter(QStringList f) { context->setFilter(f); }
|
|
void MainWindow::clearFilter() { context->clearFilter(); }
|
|
|
|
void
|
|
MainWindow::closeAll()
|
|
{
|
|
QList<MainWindow *> windows = mainwindows; // get a copy, since it is updated as closed
|
|
|
|
foreach(MainWindow *window, windows)
|
|
if (window != this) window->close();
|
|
|
|
// now close us down!
|
|
close();
|
|
}
|
|
|
|
void
|
|
MainWindow::aboutDialog()
|
|
{
|
|
AboutDialog *ad = new AboutDialog(context, context->athlete->home);
|
|
ad->exec();
|
|
}
|
|
|
|
void MainWindow::showTools()
|
|
{
|
|
ToolsDialog *td = new ToolsDialog();
|
|
td->show();
|
|
}
|
|
|
|
void MainWindow::showRhoEstimator()
|
|
{
|
|
ToolsRhoEstimator *tre = new ToolsRhoEstimator(context);
|
|
tre->show();
|
|
}
|
|
|
|
void MainWindow::showWorkoutWizard()
|
|
{
|
|
WorkoutWizard *ww = new WorkoutWizard(context);
|
|
ww->show();
|
|
}
|
|
|
|
void MainWindow::resetWindowLayout()
|
|
{
|
|
tab->resetLayout();
|
|
}
|
|
|
|
void MainWindow::manualProcess(QString name)
|
|
{
|
|
// open a dialog box and let the users
|
|
// configure the options to use
|
|
// and also show the explanation
|
|
// of what this function does
|
|
// then call it!
|
|
RideItem *rideitem = (RideItem*)context->currentRideItem();
|
|
if (rideitem) {
|
|
ManualDataProcessorDialog *p = new ManualDataProcessorDialog(context, name, rideitem);
|
|
p->setWindowModality(Qt::ApplicationModal); // don't allow select other ride or it all goes wrong!
|
|
p->exec();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::logBug()
|
|
{
|
|
QDesktopServices::openUrl(QUrl("http://www.goldencheetah.org/bug-tracker.html"));
|
|
}
|
|
|
|
void
|
|
MainWindow::helpView()
|
|
{
|
|
QDesktopServices::openUrl(QUrl("http://www.goldencheetah.org/wiki.html"));
|
|
}
|
|
|
|
void
|
|
MainWindow::selectAnalysis()
|
|
{
|
|
tab->selectView(1);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::selectTrain()
|
|
{
|
|
tab->selectView(3);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::selectDiary()
|
|
{
|
|
tab->selectView(2);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::selectHome()
|
|
{
|
|
tab->selectView(0);
|
|
setToolButtons();
|
|
}
|
|
|
|
void
|
|
MainWindow::setToolButtons()
|
|
{
|
|
int select = tab->isTiled() ? 1 : 0;
|
|
int lowselected = tab->isShowBottom() ? 1 : 0;
|
|
|
|
styleAction->setChecked(select);
|
|
showhideLowbar->setChecked(lowselected);
|
|
|
|
#ifdef Q_OS_MAC
|
|
styleSelector->setSelected(select, true);
|
|
lowbar->setSelected(lowselected);
|
|
#else
|
|
if (styleSelector->isSegmentSelected(select) == false)
|
|
styleSelector->setSegmentSelected(select, true);
|
|
#endif
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Drag and Drop
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
bool accept = true;
|
|
|
|
// we reject http, since we want a file!
|
|
foreach (QUrl url, event->mimeData()->urls())
|
|
if (url.toString().startsWith("http"))
|
|
accept = false;
|
|
|
|
if (accept) event->acceptProposedAction(); // whatever you wanna drop we will try and process!
|
|
else event->ignore();
|
|
}
|
|
|
|
void
|
|
MainWindow::dropEvent(QDropEvent *event)
|
|
{
|
|
QList<QUrl> urls = event->mimeData()->urls();
|
|
if (urls.isEmpty()) return;
|
|
|
|
if (tab->currentView() != 3) { // we're not on train view
|
|
// We have something to process then
|
|
RideImportWizard *dialog = new RideImportWizard (&urls, context->athlete->home, context);
|
|
dialog->process(); // do it!
|
|
} else {
|
|
QStringList filenames;
|
|
for (int i=0; i<urls.count(); i++)
|
|
filenames.append(QFileInfo(urls.value(i).toLocalFile()).absoluteFilePath());
|
|
Library::importFiles(context, filenames);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Ride Library Functions
|
|
*--------------------------------------------------------------------*/
|
|
|
|
|
|
void
|
|
MainWindow::downloadRide()
|
|
{
|
|
(new DownloadRideDialog(context, context->athlete->home))->show();
|
|
}
|
|
|
|
|
|
void
|
|
MainWindow::manualRide()
|
|
{
|
|
(new ManualRideDialog(context))->show();
|
|
}
|
|
|
|
void
|
|
MainWindow::exportBatch()
|
|
{
|
|
BatchExportDialog *d = new BatchExportDialog(context);
|
|
d->exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::exportRide()
|
|
{
|
|
if ((context->athlete->treeWidget->selectedItems().size() != 1)
|
|
|| (context->athlete->treeWidget->selectedItems().first()->type() != RIDE_TYPE)) {
|
|
QMessageBox::critical(this, tr("Select Activity"), tr("No activity selected!"));
|
|
return;
|
|
}
|
|
|
|
// what format?
|
|
const RideFileFactory &rff = RideFileFactory::instance();
|
|
QStringList allFormats;
|
|
foreach(QString suffix, rff.writeSuffixes())
|
|
allFormats << QString("%1 (*.%2)").arg(rff.description(suffix)).arg(suffix);
|
|
|
|
QString suffix; // what was selected?
|
|
QString fileName = QFileDialog::getSaveFileName(this, tr("Export Activity"), QDir::homePath(), allFormats.join(";;"), &suffix);
|
|
|
|
if (fileName.length() == 0) return;
|
|
|
|
// which file type was selected
|
|
// extract from the suffix returned
|
|
QRegExp getSuffix("^[^(]*\\(\\*\\.([^)]*)\\)$");
|
|
getSuffix.exactMatch(suffix);
|
|
|
|
QFile file(fileName);
|
|
bool result = RideFileFactory::instance().writeRideFile(context, context->currentRide(), file, getSuffix.cap(1));
|
|
|
|
if (result == false) {
|
|
QMessageBox oops(QMessageBox::Critical, tr("Export Failed"),
|
|
tr("Failed to export ride, please check permissions"));
|
|
oops.exec();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::importFile()
|
|
{
|
|
QVariant lastDirVar = appsettings->value(this, GC_SETTINGS_LAST_IMPORT_PATH);
|
|
QString lastDir = (lastDirVar != QVariant())
|
|
? lastDirVar.toString() : QDir::homePath();
|
|
|
|
const RideFileFactory &rff = RideFileFactory::instance();
|
|
QStringList suffixList = rff.suffixes();
|
|
suffixList.replaceInStrings(QRegExp("^"), "*.");
|
|
QStringList fileNames;
|
|
QStringList allFormats;
|
|
allFormats << QString("All Supported Formats (%1)").arg(suffixList.join(" "));
|
|
foreach(QString suffix, rff.suffixes())
|
|
allFormats << QString("%1 (*.%2)").arg(rff.description(suffix)).arg(suffix);
|
|
allFormats << "All files (*.*)";
|
|
fileNames = QFileDialog::getOpenFileNames( this, tr("Import from File"), lastDir, allFormats.join(";;"));
|
|
if (!fileNames.isEmpty()) {
|
|
lastDir = QFileInfo(fileNames.front()).absolutePath();
|
|
appsettings->setValue(GC_SETTINGS_LAST_IMPORT_PATH, lastDir);
|
|
QStringList fileNamesCopy = fileNames; // QT doc says iterate over a copy
|
|
RideImportWizard *import = new RideImportWizard(fileNamesCopy, context->athlete->home, context);
|
|
import->process();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::saveRide()
|
|
{
|
|
if (context->ride)
|
|
saveRideSingleDialog(context->ride); // will signal save to everyone
|
|
else {
|
|
QMessageBox oops(QMessageBox::Critical, tr("No Activity To Save"),
|
|
tr("There is no currently selected ride to save."));
|
|
oops.exec();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::revertRide()
|
|
{
|
|
context->ride->freeMemory();
|
|
context->ride->ride(); // force re-load
|
|
|
|
// in case reverted ride has different starttime
|
|
context->ride->setStartTime(context->ride->ride()->startTime()); // Note: this will also signal rideSelected()
|
|
context->ride->ride()->emitReverted();
|
|
}
|
|
|
|
void
|
|
MainWindow::splitRide()
|
|
{
|
|
if (context->ride && context->ride->ride() && context->ride->ride()->dataPoints().count()) (new SplitActivityWizard(context))->exec();
|
|
else {
|
|
if (!context->ride || !context->ride->ride())
|
|
QMessageBox::critical(this, tr("Split Activity"), tr("No activity selected"));
|
|
else
|
|
QMessageBox::critical(this, tr("Split Activity"), tr("Current activity contains no data to split"));
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::mergeRide()
|
|
{
|
|
if (context->ride && context->ride->ride() && context->ride->ride()->dataPoints().count()) (new MergeActivityWizard(context))->exec();
|
|
else {
|
|
if (!context->ride || !context->ride->ride())
|
|
QMessageBox::critical(this, tr("Split Activity"), tr("No activity selected"));
|
|
else
|
|
QMessageBox::critical(this, tr("Split Activity"), tr("Current activity contains no data to merge"));
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::deleteRide()
|
|
{
|
|
QTreeWidgetItem *_item = context->athlete->treeWidget->currentItem();
|
|
if (_item==NULL || _item->type() != RIDE_TYPE) {
|
|
QMessageBox::critical(this, tr("Delete Activity"), tr("No activity selected!"));
|
|
return;
|
|
}
|
|
RideItem *item = static_cast<RideItem*>(_item);
|
|
QMessageBox msgBox;
|
|
msgBox.setText(tr("Are you sure you want to delete the activity:"));
|
|
msgBox.setInformativeText(item->fileName);
|
|
QPushButton *deleteButton = msgBox.addButton(tr("Delete"),QMessageBox::YesRole);
|
|
msgBox.setStandardButtons(QMessageBox::Cancel);
|
|
msgBox.setDefaultButton(QMessageBox::Cancel);
|
|
msgBox.setIcon(QMessageBox::Critical);
|
|
msgBox.exec();
|
|
if(msgBox.clickedButton() == deleteButton)
|
|
context->athlete->removeCurrentRide();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Realtime Devices and Workouts
|
|
*--------------------------------------------------------------------*/
|
|
void
|
|
MainWindow::addDevice()
|
|
{
|
|
|
|
// lets get a new one
|
|
AddDeviceWizard *p = new AddDeviceWizard(context);
|
|
p->show();
|
|
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Cyclists
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::newCyclist()
|
|
{
|
|
QDir newHome = context->athlete->home;
|
|
newHome.cdUp();
|
|
QString name = ChooseCyclistDialog::newCyclistDialog(newHome, this);
|
|
if (!name.isEmpty()) {
|
|
newHome.cd(name);
|
|
if (!newHome.exists()) return;
|
|
MainWindow *main = new MainWindow(newHome);
|
|
main->show();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::openCyclist()
|
|
{
|
|
QDir newHome = context->athlete->home;
|
|
newHome.cdUp();
|
|
ChooseCyclistDialog d(newHome, false);
|
|
d.setModal(true);
|
|
if (d.exec() == QDialog::Accepted) {
|
|
newHome.cd(d.choice());
|
|
if (!newHome.exists()) return;
|
|
MainWindow *main = new MainWindow(newHome);
|
|
main->show();
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* MetricDB
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::exportMetrics()
|
|
{
|
|
QString fileName = QFileDialog::getSaveFileName( this, tr("Export Metrics"), QDir::homePath(), tr("Comma Separated Variables (*.csv)"));
|
|
if (fileName.length() == 0)
|
|
return;
|
|
context->athlete->metricDB->writeAsCSV(fileName);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Twitter
|
|
*--------------------------------------------------------------------*/
|
|
#ifdef GC_HAVE_LIBOAUTH
|
|
void
|
|
MainWindow::tweetRide()
|
|
{
|
|
QTreeWidgetItem *_item = context->athlete->treeWidget->currentItem();
|
|
if (_item==NULL || _item->type() != RIDE_TYPE) return;
|
|
|
|
RideItem *item = dynamic_cast<RideItem*>(_item);
|
|
|
|
if (item) { // menu is disabled anyway, but belt and braces
|
|
TwitterDialog *twitterDialog = new TwitterDialog(context, item);
|
|
twitterDialog->setWindowModality(Qt::ApplicationModal);
|
|
twitterDialog->exec();
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Share : Twitter, Strava, RideWithGPS
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::share()
|
|
{
|
|
QTreeWidgetItem *_item = context->athlete->treeWidget->currentItem();
|
|
if (_item==NULL || _item->type() != RIDE_TYPE) return;
|
|
|
|
RideItem *item = dynamic_cast<RideItem*>(_item);
|
|
|
|
if (item) { // menu is disabled anyway, but belt and braces
|
|
ShareDialog d(context, item);
|
|
d.exec();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------
|
|
* trainingstagebuch.org
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::uploadTtb()
|
|
{
|
|
QTreeWidgetItem *_item = context->athlete->treeWidget->currentItem();
|
|
if (_item==NULL || _item->type() != RIDE_TYPE) return;
|
|
|
|
RideItem *item = dynamic_cast<RideItem*>(_item);
|
|
|
|
if (item) { // menu is disabled anyway, but belt and braces
|
|
TtbDialog d(context, item);
|
|
d.exec();
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Import Workout from Disk
|
|
*--------------------------------------------------------------------*/
|
|
void
|
|
MainWindow::importWorkout()
|
|
{
|
|
// go look at last place we imported workouts from...
|
|
QVariant lastDirVar = appsettings->value(this, GC_SETTINGS_LAST_WORKOUT_PATH);
|
|
QString lastDir = (lastDirVar != QVariant())
|
|
? lastDirVar.toString() : QDir::homePath();
|
|
|
|
// anything for now, we could add filters later
|
|
QStringList allFormats;
|
|
allFormats << "All files (*.*)";
|
|
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Import from File"), lastDir, allFormats.join(";;"));
|
|
|
|
// lets process them
|
|
if (!fileNames.isEmpty()) {
|
|
|
|
// save away last place we looked
|
|
lastDir = QFileInfo(fileNames.front()).absolutePath();
|
|
appsettings->setValue(GC_SETTINGS_LAST_WORKOUT_PATH, lastDir);
|
|
|
|
QStringList fileNamesCopy = fileNames; // QT doc says iterate over a copy
|
|
|
|
// import them via the workoutimporter
|
|
Library::importFiles(context, fileNamesCopy);
|
|
}
|
|
}
|
|
/*----------------------------------------------------------------------
|
|
* ErgDB
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::downloadErgDB()
|
|
{
|
|
QString workoutDir = appsettings->value(this, GC_WORKOUTDIR).toString();
|
|
|
|
QFileInfo fi(workoutDir);
|
|
|
|
if (fi.exists() && fi.isDir()) {
|
|
ErgDBDownloadDialog *d = new ErgDBDownloadDialog(context);
|
|
d->exec();
|
|
} else{
|
|
QMessageBox::critical(this, tr("Workout Directory Invalid"),
|
|
"The workout directory is not configured, or the directory"
|
|
" selected no longer exists.\n\n"
|
|
"Please check your preference settings.");
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Workout/Media Library
|
|
*--------------------------------------------------------------------*/
|
|
void
|
|
MainWindow::manageLibrary()
|
|
{
|
|
LibrarySearchDialog *search = new LibrarySearchDialog(context);
|
|
search->exec();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* TrainingPeaks.com
|
|
*--------------------------------------------------------------------*/
|
|
|
|
#ifdef GC_HAVE_SOAP
|
|
void
|
|
MainWindow::uploadTP()
|
|
{
|
|
if (context->ride) {
|
|
TPUploadDialog uploader(context->athlete->cyclist, context->currentRide(), context);
|
|
uploader.exec();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::downloadTP()
|
|
{
|
|
TPDownloadDialog downloader(context);
|
|
downloader.exec();
|
|
}
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Utility
|
|
*--------------------------------------------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Notifiers - application level events
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
Context::notifyConfigChanged()
|
|
{
|
|
// now tell everyone else
|
|
configChanged();
|
|
}
|
|
|
|
void
|
|
Athlete::configChanged()
|
|
{
|
|
// re-read Zones in case it changed
|
|
QFile zonesFile(home.absolutePath() + "/power.zones");
|
|
if (zonesFile.exists()) {
|
|
if (!zones_->read(zonesFile)) {
|
|
QMessageBox::critical(context->mainWindow, tr("Zones File Error"),
|
|
zones_->errorString());
|
|
}
|
|
else if (! zones_->warningString().isEmpty())
|
|
QMessageBox::warning(context->mainWindow, tr("Reading Zones File"), zones_->warningString());
|
|
}
|
|
|
|
// reread HR zones
|
|
QFile hrzonesFile(home.absolutePath() + "/hr.zones");
|
|
if (hrzonesFile.exists()) {
|
|
if (!hrzones_->read(hrzonesFile)) {
|
|
QMessageBox::critical(context->mainWindow, tr("HR Zones File Error"),
|
|
hrzones_->errorString());
|
|
}
|
|
else if (! hrzones_->warningString().isEmpty())
|
|
QMessageBox::warning(context->mainWindow, tr("Reading HR Zones File"), hrzones_->warningString());
|
|
}
|
|
|
|
QVariant unit = appsettings->cvalue(cyclist, GC_UNIT);
|
|
useMetricUnits = (unit.toString() == GC_UNIT_METRIC);
|
|
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Measures
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
MainWindow::downloadMeasures()
|
|
{
|
|
context->athlete->withingsDownload->download();
|
|
}
|
|
|
|
void
|
|
MainWindow::downloadMeasuresFromZeo()
|
|
{
|
|
context->athlete->zeoDownload->download();
|
|
}
|
|
|
|
void
|
|
MainWindow::refreshCalendar()
|
|
{
|
|
#ifdef GC_HAVE_ICAL
|
|
context->athlete->davCalendar->download();
|
|
context->athlete->calendarDownload->download();
|
|
#endif
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Calendar
|
|
*--------------------------------------------------------------------*/
|
|
|
|
#ifdef GC_HAVE_ICAL
|
|
void
|
|
MainWindow::uploadCalendar()
|
|
{
|
|
context->athlete->davCalendar->upload((RideItem*)context->currentRideItem()); // remove const coz it updates the ride
|
|
// to set GID and upload date
|
|
}
|
|
#endif
|
|
|
|
void
|
|
MainWindow::actionClicked(int index)
|
|
{
|
|
switch(index) {
|
|
|
|
default:
|
|
case 0: tab->addIntervals();
|
|
break;
|
|
|
|
case 1 : splitRide();
|
|
break;
|
|
|
|
case 2 : deleteRide();
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::addIntervals()
|
|
{
|
|
tab->addIntervals();
|
|
}
|
|
|
|
void
|
|
MainWindow::rideSelected(RideItem*)
|
|
{
|
|
|
|
// update the ride property on all widgets
|
|
// to let them know they need to replot new
|
|
// selected ride
|
|
tab->setRide(context->ride);
|
|
|
|
if (!context->ride) return;
|
|
|
|
// refresh interval list for bottom left
|
|
// first lets wipe away the existing intervals
|
|
QList<QTreeWidgetItem *> intervals = context->athlete->allIntervals->takeChildren();
|
|
for (int i=0; i<intervals.count(); i++) delete intervals.at(i);
|
|
|
|
// now add the intervals for the current ride
|
|
if (context->ride) { // only if we have a ride pointer
|
|
RideFile *selected = context->ride->ride();
|
|
if (selected) {
|
|
// get all the intervals in the currently selected RideFile
|
|
QList<RideFileInterval> intervals = selected->intervals();
|
|
for (int i=0; i < intervals.count(); i++) {
|
|
// add as a child to context->athlete->allIntervals
|
|
IntervalItem *add = new IntervalItem(selected,
|
|
intervals.at(i).name,
|
|
intervals.at(i).start,
|
|
intervals.at(i).stop,
|
|
selected->timeToDistance(intervals.at(i).start),
|
|
selected->timeToDistance(intervals.at(i).stop),
|
|
context->athlete->allIntervals->childCount()+1);
|
|
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
|
context->athlete->allIntervals->addChild(add);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|