Files
GoldenCheetah/src/MainWindow.cpp
Mark Liversedge b5a50bcccf Tone down the toolbar shading
.. on QT 5.2.1
2014-02-15 15:15:20 +00:00

1900 lines
60 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 <QTabBar>
#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
#ifdef Q_OS_MAC
head = NULL; // early resize event causes a crash
#endif
// bootstrap
Context *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 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());
}
/*----------------------------------------------------------------------
* 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()));
#if 0
// 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*)));
#endif
/*----------------------------------------------------------------------
* Mac Toolbar
*--------------------------------------------------------------------*/
#ifdef Q_OS_MAC
#if QT_VERSION > 0x50000
#if QT_VERSION >= 0x50201
setUnifiedTitleAndToolBarOnMac(true);
#endif
head = addToolBar(context->athlete->cyclist);
head->setContentsMargins(20,0,20,0);
head->setFloatable(false);
head->setMovable(false);
// make the normal toolbar in QT5 have same colors as the tabs and when inactive
// make it the same 'light' colour as the other widgets do.
#if QT_VERSION < 0x50201
head->setStyleSheet(" QToolBar:active { border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #C6C6C6, stop: 1 #A5A5A5 ); } "
" QToolBar:!active { border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #D9D9D9, stop: 1 #D6D6D6 ); } ");
#else
head->setStyleSheet(" QToolBar:!active { border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #F0F0F0, stop: 1 #E8E8E8 ); } "
" QToolBar:active { border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #D9D9D9, stop: 1 #B5B5B5 ); } ");
#endif
// widgets
QWidget *macAnalButtons = new QWidget(this);
macAnalButtons->setContentsMargins(20,5,20,0);
#else
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);
#endif
// lhs buttons
QHBoxLayout *lb = new QHBoxLayout(macAnalButtons);
lb->setContentsMargins(0,0,10,8);
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);
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->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(scopebar);
head->addWidget(new Spacer(this));
head->addWidget(viewsel);
#ifdef GC_HAVE_LUCENE
searchBox = new SearchFilterBox(this,context,false);
#if QT_VERSION > 0x50000
QStyle *toolStyle = QStyleFactory::create("fusion");
#else
QStyle *toolStyle = QStyleFactory::create("Cleanlooks");
#endif
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);
#if QT_VERSION > 0x50000
QStyle *toolStyle = QStyleFactory::create("fusion");
#else
QStyle *toolStyle = QStyleFactory::create("Cleanlooks");
#endif
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 Ride"));
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 Ride..."));
actbuttons->setSegmentToolTip(2, tr("Delete Ride"));
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(scopebar);
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
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
/*----------------------------------------------------------------------
* Central Widget
*--------------------------------------------------------------------*/
tabbar = new DragBar(this);
tabbar->setTabsClosable(true);
#ifdef Q_OS_MAC
tabbar->setDocumentMode(true);
#else
QPalette tabbarPalette;
tabbar->setAutoFillBackground(true);
tabbar->setShape(QTabBar::RoundedSouth);
tabbar->setDrawBase(false);
tabbarPalette.setBrush(backgroundRole(), QColor("#B3B4B6"));
tabbar->setPalette(tabbarPalette);
#endif
tabStack = new QStackedWidget(this);
currentTab = new Tab(context);
// first tab
tabs.insert(currentTab->context->athlete->home.dirName(), currentTab);
// stack, list and bar all share a common index
tabList.append(currentTab);
tabbar->addTab(currentTab->context->athlete->home.dirName());
tabStack->addWidget(currentTab);
tabStack->setCurrentIndex(0);
connect(tabbar, SIGNAL(dragTab(int)), this, SLOT(switchTab(int)));
connect(tabbar, SIGNAL(currentChanged(int)), this, SLOT(switchTab(int)));
connect(tabbar, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTabClicked(int)));
/*----------------------------------------------------------------------
* 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(tabbar);
#if (defined Q_OS_MAC) && (QT_VERSION >= 0x50201)
blackline = new QWidget(this);
blackline->setContentsMargins(0,0,0,0);
blackline->setFixedHeight(1);
QPalette linePalette;
linePalette.setBrush(backgroundRole(), Qt::darkGray);
blackline->setPalette(linePalette);
blackline->setAutoFillBackground(true);
mainLayout->addWidget(blackline);
#endif
mainLayout->addWidget(tabStack);
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
// ATHLETE (FILE) MENU
QMenu *fileMenu = menuBar()->addMenu(tr("&Athlete"));
openWindowMenu = fileMenu->addMenu(tr("Open &Window"));
openTabMenu = fileMenu->addMenu(tr("Open &Tab"));
connect(openWindowMenu, SIGNAL(aboutToShow()), this, SLOT(setOpenWindowMenu()));
connect(openTabMenu, SIGNAL(aboutToShow()), this, SLOT(setOpenTabMenu()));
windowMapper = new QSignalMapper(this); // maps each option
connect(windowMapper, SIGNAL(mapped(const QString &)), this, SLOT(openWindow(const QString &)));
tabMapper = new QSignalMapper(this); // maps each option
connect(tabMapper, SIGNAL(mapped(const QString &)), this, SLOT(openTab(const QString &)));
fileMenu->addSeparator();
fileMenu->addAction(tr("Close Window"), this, SLOT(closeWindow()));
fileMenu->addAction(tr("&Close Tab"), this, SLOT(closeTab()));
fileMenu->addAction(tr("&Quit All Windows"), this, SLOT(closeAll()), tr("Ctrl+Q"));
// ACTIVITY MENU
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 ride 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 Ride"), 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 ride"), this, SLOT(saveRide()), tr("Ctrl+S"));
rideMenu->addAction(tr("D&elete ride..."), this, SLOT(deleteRide()));
rideMenu->addAction(tr("Split &ride..."), this, SLOT(splitRide()));
rideMenu->addAction(tr("Merge rides..."), this, SLOT(mergeRide()));
rideMenu->addSeparator ();
// TOOLS MENU
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 Ride 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());
}
}
// VIEW MENU
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);
#if (!defined Q_OS_MAC) || (QT_VERSION >= 0x50201) // not on a Mac
showhideToolbar = viewMenu->addAction(tr("Show Toolbar"), this, SLOT(showToolbar(bool)));
showhideToolbar->setCheckable(true);
showhideToolbar->setChecked(true);
#endif
showhideTabbar = viewMenu->addAction(tr("Show Athlete Tabs"), this, SLOT(showTabbar(bool)));
showhideTabbar->setCheckable(true);
showhideTabbar->setChecked(true);
//connect(showhideSidebar, SIGNAL(triggered(bool)), this, SLOT(showSidebar(bool)));
viewMenu->addSeparator();
viewMenu->addAction(tr("Rides"), this, SLOT(selectAnalysis()));
viewMenu->addAction(tr("Trends"), 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()));
styleAction = viewMenu->addAction(tr("Tabbed not Tiled"), this, SLOT(toggleStyle()));
styleAction->setCheckable(true);
styleAction->setChecked(true);
connect(rideMenu, SIGNAL(aboutToShow()), this, SLOT(setActivityMenu()));
connect(subChartMenu, SIGNAL(aboutToShow()), this, SLOT(setSubChartMenu()));
connect(subChartMenu, SIGNAL(triggered(QAction*)), this, SLOT(addChart(QAction*)));
// HELP MENU
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
*--------------------------------------------------------------------*/
showTabbar(appsettings->value(NULL, GC_TABBAR, "0").toBool());
//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()));
saveState(currentTab->context); // set to whatever we started with
selectAnalysis();
//grab focus
currentTab->setFocus();
}
/*----------------------------------------------------------------------
* GUI
*--------------------------------------------------------------------*/
void
MainWindow::toggleSidebar()
{
currentTab->toggleSidebar();
setToolButtons();
}
void
MainWindow::showSidebar(bool want)
{
currentTab->setSidebarEnabled(want);
showhideSidebar->setChecked(want);
setToolButtons();
}
void
MainWindow::toggleLowbar()
{
if (currentTab->hasBottom()) currentTab->setShowBottom(!currentTab->isShowBottom());
setToolButtons();
}
void
MainWindow::showLowbar(bool want)
{
if (currentTab->hasBottom()) currentTab->setShowBottom(want);
showhideLowbar->setChecked(want);
setToolButtons();
}
void
MainWindow::showTabbar(bool want)
{
setUpdatesEnabled(false);
showhideTabbar->setChecked(want);
if (want) {
#ifdef Q_OS_MAC
setDocumentMode(true);
tabbar->setDocumentMode(true);
#if QT_VERSION >= 0x50201
blackline->hide();
#endif
#endif
tabbar->show();
}
else {
#ifdef Q_OS_MAC
setDocumentMode(false);
tabbar->setDocumentMode(false);
#if QT_VERSION >= 0x50201
blackline->show();
#endif
#endif
tabbar->hide();
}
setUpdatesEnabled(true);
}
void
MainWindow::showToolbar(bool want)
{
#if (!defined Q_OS_MAC) || (QT_VERSION >= 0x50201)
setUpdatesEnabled(false);
showhideToolbar->setChecked(want);
if (want) {
head->show();
}
else {
head->hide();
}
setUpdatesEnabled(true);
#endif
}
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(currentTab->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(currentTab->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 (currentTab->context->ride && currentTab->context->ride->ride()) {
QString activityId = currentTab->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)
currentTab->addChart(id); // called from MainWindow to inset chart
}
void
MainWindow::setStyleFromSegment(int segment)
{
currentTab->setTiled(segment);
styleAction->setChecked(!segment);
}
void
MainWindow::toggleStyle()
{
currentTab->toggleTile();
styleAction->setChecked(currentTab->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*)
{
#ifdef Q_OS_MAC
if (head) {
appsettings->setValue(GC_SETTINGS_MAIN_GEOM, geometry());
head->updateGeometry();
repaint();
}
#endif
}
void
MainWindow::showOptions()
{
ConfigDialog *cd = new ConfigDialog(currentTab->context->athlete->home, currentTab->context->athlete->zones_, currentTab->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)
{
QList<Tab*> closing = tabList;
bool needtosave = false;
// close all the tabs .. if any refuse we need to ignore
// the close event
foreach(Tab *tab, closing) {
// do we need to save?
if (tab->context->mainWindow->saveRideExitDialog(tab->context) == true)
removeTab(tab);
else
needtosave = true;
}
// were any left hanging around?
if (needtosave) event->ignore();
else {
// finish off the job and leave
// clear the clipboard if neccessary
QApplication::clipboard()->setText("");
// now remove from the list
if(mainwindows.removeOne(this) == false)
qDebug()<<"closeEvent: mainwindows list error";
// save global mainwindow settings
appsettings->setValue(GC_TABBAR, showhideTabbar->isChecked());
}
}
MainWindow::~MainWindow()
{
// aside from the tabs, we may need to clean
// up any dangling widgets created in MainWindow::MainWindow (?)
}
// global search/data filter
void MainWindow::setFilter(QStringList f) { currentTab->context->setFilter(f); }
void MainWindow::clearFilter() { currentTab->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->closeWindow();
// now close us down!
closeWindow();
}
void
MainWindow::aboutDialog()
{
AboutDialog *ad = new AboutDialog(currentTab->context, currentTab->context->athlete->home);
ad->exec();
}
void MainWindow::showTools()
{
ToolsDialog *td = new ToolsDialog();
td->show();
}
void MainWindow::showRhoEstimator()
{
ToolsRhoEstimator *tre = new ToolsRhoEstimator(currentTab->context);
tre->show();
}
void MainWindow::showWorkoutWizard()
{
WorkoutWizard *ww = new WorkoutWizard(currentTab->context);
ww->show();
}
void MainWindow::resetWindowLayout()
{
currentTab->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*)currentTab->context->currentRideItem();
if (rideitem) {
ManualDataProcessorDialog *p = new ManualDataProcessorDialog(currentTab->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()
{
currentTab->selectView(1);
setToolButtons();
}
void
MainWindow::selectTrain()
{
currentTab->selectView(3);
setToolButtons();
}
void
MainWindow::selectDiary()
{
currentTab->selectView(2);
setToolButtons();
}
void
MainWindow::selectHome()
{
currentTab->selectView(0);
setToolButtons();
}
void
MainWindow::setToolButtons()
{
int select = currentTab->isTiled() ? 1 : 0;
int lowselected = currentTab->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
int index = currentTab->currentView();
//XXX WTAF! The index used is fucked up XXX
// hack around this and then come back
// and look at this as a separate fixup
#ifdef GC_HAVE_ICAL
switch (index) {
case 0: // home no change
case 3: // train no change
default:
break;
case 1:
index = 2; // analysis
break;
case 2:
index = 1; // diary
break;
}
#else
switch (index) {
case 0: // home no change
case 1:
default:
break;
case 3:
index = 2; // train
}
#endif
scopebar->setSelected(index);
}
/*----------------------------------------------------------------------
* 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!
raise();
} else event->ignore();
}
void
MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty()) return;
if (currentTab->currentView() != 3) { // we're not on train view
// We have something to process then
RideImportWizard *dialog = new RideImportWizard (&urls, currentTab->context->athlete->home, currentTab->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(currentTab->context, filenames);
}
return;
}
/*----------------------------------------------------------------------
* Ride Library Functions
*--------------------------------------------------------------------*/
void
MainWindow::downloadRide()
{
(new DownloadRideDialog(currentTab->context, currentTab->context->athlete->home))->show();
}
void
MainWindow::manualRide()
{
(new ManualRideDialog(currentTab->context))->show();
}
void
MainWindow::exportBatch()
{
BatchExportDialog *d = new BatchExportDialog(currentTab->context);
d->exec();
}
void
MainWindow::exportRide()
{
if ((currentTab->context->athlete->treeWidget->selectedItems().size() != 1)
|| (currentTab->context->athlete->treeWidget->selectedItems().first()->type() != RIDE_TYPE)) {
QMessageBox::critical(this, tr("Select Ride"), tr("No ride 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 Ride"), 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(currentTab->context, currentTab->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, currentTab->context->athlete->home, currentTab->context);
import->process();
}
}
void
MainWindow::saveRide()
{
if (currentTab->context->ride)
saveRideSingleDialog(currentTab->context, currentTab->context->ride); // will signal save to everyone
else {
QMessageBox oops(QMessageBox::Critical, tr("No Ride To Save"),
tr("There is no currently selected ride to save."));
oops.exec();
return;
}
}
void
MainWindow::revertRide()
{
currentTab->context->ride->freeMemory();
currentTab->context->ride->ride(); // force re-load
// in case reverted ride has different starttime
currentTab->context->ride->setStartTime(currentTab->context->ride->ride()->startTime()); // Note: this will also signal rideSelected()
currentTab->context->ride->ride()->emitReverted();
}
void
MainWindow::splitRide()
{
if (currentTab->context->ride && currentTab->context->ride->ride() && currentTab->context->ride->ride()->dataPoints().count()) (new SplitActivityWizard(currentTab->context))->exec();
else {
if (!currentTab->context->ride || !currentTab->context->ride->ride())
QMessageBox::critical(this, tr("Split Ride"), tr("No ride selected"));
else
QMessageBox::critical(this, tr("Split Ride"), tr("Current ride contains no data to split"));
}
}
void
MainWindow::mergeRide()
{
if (currentTab->context->ride && currentTab->context->ride->ride() && currentTab->context->ride->ride()->dataPoints().count()) (new MergeActivityWizard(currentTab->context))->exec();
else {
if (!currentTab->context->ride || !currentTab->context->ride->ride())
QMessageBox::critical(this, tr("Split Ride"), tr("No ride selected"));
else
QMessageBox::critical(this, tr("Split Ride"), tr("Current ride contains no data to merge"));
}
}
void
MainWindow::deleteRide()
{
QTreeWidgetItem *_item = currentTab->context->athlete->treeWidget->currentItem();
if (_item==NULL || _item->type() != RIDE_TYPE) {
QMessageBox::critical(this, tr("Delete Ride"), tr("No ride selected!"));
return;
}
RideItem *item = static_cast<RideItem*>(_item);
QMessageBox msgBox;
msgBox.setText(tr("Are you sure you want to delete the ride:"));
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)
currentTab->context->athlete->removeCurrentRide();
}
/*----------------------------------------------------------------------
* Realtime Devices and Workouts
*--------------------------------------------------------------------*/
void
MainWindow::addDevice()
{
// lets get a new one
AddDeviceWizard *p = new AddDeviceWizard(currentTab->context);
p->show();
}
/*----------------------------------------------------------------------
* Cyclists
*--------------------------------------------------------------------*/
void
MainWindow::newCyclistWindow()
{
QDir newHome = currentTab->context->athlete->home;
newHome.cdUp();
QString name = ChooseCyclistDialog::newCyclistDialog(newHome, this);
if (!name.isEmpty()) openWindow(name);
}
void
MainWindow::newCyclistTab()
{
QDir newHome = currentTab->context->athlete->home;
newHome.cdUp();
QString name = ChooseCyclistDialog::newCyclistDialog(newHome, this);
if (!name.isEmpty()) openTab(name);
}
void
MainWindow::openWindow(QString name)
{
// open window...
QDir home(gcroot);
home.cd(name);
if (!home.exists()) return;
// main window will register itself
MainWindow *main = new MainWindow(home);
main->show();
}
void
MainWindow::closeWindow()
{
// just call close, we might do more later
close();
}
void
MainWindow::openTab(QString name)
{
QDir home(gcroot);
home.cd(name);
if (!home.exists()) return;
setUpdatesEnabled(false);
// bootstrap
Context *context = new Context(this);
context->athlete = new Athlete(context, home);
// now open up a new tab
currentTab = new Tab(context);
// first tab
tabs.insert(currentTab->context->athlete->home.dirName(), currentTab);
// stack, list and bar all share a common index
tabList.append(currentTab);
tabbar->addTab(currentTab->context->athlete->home.dirName());
tabStack->addWidget(currentTab);
// switch to newly created athlete
tabbar->setCurrentIndex(tabList.count()-1);
// kick off on analysis
setWindowTitle(currentTab->context->athlete->home.dirName());
selectAnalysis(); // sets scope bar ..
// now apply current
saveState(currentTab->context);
restoreState(currentTab->context);
// show the tabbar if we're gonna open tabs -- but wait till the last second
// to show it to avoid crappy paint artefacts
showTabbar(true);
setUpdatesEnabled(true);
}
void
MainWindow::closeTabClicked(int index)
{
Tab *tab = tabList[index];
if (saveRideExitDialog(tab->context) == false) return;
// lets wipe it
removeTab(tab);
}
bool
MainWindow::closeTab()
{
// wipe it down ...
if (saveRideExitDialog(currentTab->context) == false) return false;
// if its the last tab we close the window
if (tabList.count() == 1)
closeWindow();
else {
removeTab(currentTab);
}
// we did it
return true;
}
// no questions asked just wipe away the current tab
void
MainWindow::removeTab(Tab *tab)
{
setUpdatesEnabled(false);
if (tabList.count() == 2) showTabbar(false); // don't need it for one!
#ifdef GC_HAVE_LUCENE
// save the named searches
tab->context->athlete->namedSearches->write();
#endif
// clear the clipboard if neccessary
QApplication::clipboard()->setText("");
// Remember where we were
QString name = tab->context->athlete->cyclist;
// switch to neighbour (currentTab will change)
int index = tabList.indexOf(tab);
// if we're not the last then switch
// before removing so the GUI is clean
if (tabList.count() > 1) {
if (index) switchTab(index-1);
else switchTab(index+1);
}
// close gracefully
tab->close();
tab->context->athlete->close();
// remove from state
tabs.remove(name);
tabList.removeAt(index);
tabbar->removeTab(index);
tabStack->removeWidget(tab);
// delete the objects
Context *context = tab->context;
Athlete *athlete = tab->context->athlete;
delete tab;
delete athlete;
delete context;
setUpdatesEnabled(true);
return;
}
void
MainWindow::setOpenWindowMenu()
{
// wipe existing
openWindowMenu->clear();
// get a list of all cyclists
QStringListIterator i(QDir(gcroot).entryList(QDir::Dirs | QDir::NoDotAndDotDot));
while (i.hasNext()) {
QString name = i.next();
// new action
QAction *action = new QAction(QString("%1").arg(name), this);
// only allow selection of cyclists which are not already open
foreach (MainWindow *x, mainwindows) {
QMapIterator<QString, Tab*> t(x->tabs);
while (t.hasNext()) {
t.next();
if (t.key() == name)
action->setEnabled(false);
}
}
// add to menu
openWindowMenu->addAction(action);
connect(action, SIGNAL(triggered()), windowMapper, SLOT(map()));
windowMapper->setMapping(action, name);
}
// add create new option
openWindowMenu->addSeparator();
openWindowMenu->addAction(tr("&New Athlete..."), this, SLOT(newCyclistWindow()), tr("Ctrl+N"));
}
void
MainWindow::setOpenTabMenu()
{
// wipe existing
openTabMenu->clear();
// get a list of all cyclists
QStringListIterator i(QDir(gcroot).entryList(QDir::Dirs | QDir::NoDotAndDotDot));
while (i.hasNext()) {
QString name = i.next();
// new action
QAction *action = new QAction(QString("%1").arg(name), this);
// only allow selection of cyclists which are not already open
foreach (MainWindow *x, mainwindows) {
QMapIterator<QString, Tab*> t(x->tabs);
while (t.hasNext()) {
t.next();
if (t.key() == name)
action->setEnabled(false);
}
}
// add to menu
openTabMenu->addAction(action);
connect(action, SIGNAL(triggered()), tabMapper, SLOT(map()));
tabMapper->setMapping(action, name);
}
// add create new option
openTabMenu->addSeparator();
openTabMenu->addAction(tr("&New Athlete..."), this, SLOT(newCyclistTab()), tr("Ctrl+N"));
}
void
MainWindow::saveState(Context *context)
{
// save all the current state to the supplied context
context->showSidebar = showhideSidebar->isChecked();
//context->showTabbar = showhideTabbar->isChecked();
context->showLowbar = showhideLowbar->isChecked();
#ifndef Q_OS_MAC // not on a Mac
context->showToolbar = showhideToolbar->isChecked();
#endif
#ifdef GC_HAVE_LUCENE
context->searchText = searchBox->text();
#endif
context->viewIndex = scopebar->selected();
context->style = styleAction->isChecked();
context->viewIndex = scopebar->selected();
}
void
MainWindow::restoreState(Context *context)
{
// restore window state from the supplied context
showSidebar(context->showSidebar);
#ifndef Q_OS_MAC // not on a Mac
showToolbar(context->showToolbar);
#endif
//showTabbar(context->showTabbar);
showLowbar(context->showLowbar);
scopebar->setSelected(context->viewIndex);
scopebar->setContext(context);
scopebar->setHighlighted(); // to reflect context
#ifdef GC_HAVE_LUCENE
searchBox->setContext(context);
searchBox->setText(context->searchText);
#endif
}
void
MainWindow::switchTab(int index)
{
if (index < 0) return;
setUpdatesEnabled(false);
#ifdef Q_OS_MAC // close buttons on the left on Mac
// Only have close button on current tab (prettier)
for(int i=0; i<tabbar->count(); i++) tabbar->tabButton(i, QTabBar::LeftSide)->hide();
tabbar->tabButton(index, QTabBar::LeftSide)->show();
#else
// Only have close button on current tab (prettier)
for(int i=0; i<tabbar->count(); i++) tabbar->tabButton(i, QTabBar::RightSide)->hide();
tabbar->tabButton(index, QTabBar::RightSide)->show();
#endif
// save how we are
saveState(currentTab->context);
currentTab = tabList[index];
tabStack->setCurrentIndex(index);
// restore back
restoreState(currentTab->context);
setWindowTitle(currentTab->context->athlete->home.dirName());
setUpdatesEnabled(true);
}
/*----------------------------------------------------------------------
* MetricDB
*--------------------------------------------------------------------*/
void
MainWindow::exportMetrics()
{
QString fileName = QFileDialog::getSaveFileName( this, tr("Export Metrics"), QDir::homePath(), tr("Comma Separated Variables (*.csv)"));
if (fileName.length() == 0)
return;
currentTab->context->athlete->metricDB->writeAsCSV(fileName);
}
/*----------------------------------------------------------------------
* Twitter
*--------------------------------------------------------------------*/
#ifdef GC_HAVE_LIBOAUTH
void
MainWindow::tweetRide()
{
QTreeWidgetItem *_item = currentTab->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(currentTab->context, item);
twitterDialog->setWindowModality(Qt::ApplicationModal);
twitterDialog->exec();
}
}
/*----------------------------------------------------------------------
* Share : Twitter, Strava, RideWithGPS
*--------------------------------------------------------------------*/
void
MainWindow::share()
{
QTreeWidgetItem *_item = currentTab->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(currentTab->context, item);
d.exec();
}
}
#endif
/*----------------------------------------------------------------------
* trainingstagebuch.org
*--------------------------------------------------------------------*/
void
MainWindow::uploadTtb()
{
QTreeWidgetItem *_item = currentTab->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(currentTab->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(currentTab->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(currentTab->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(currentTab->context);
search->exec();
}
/*----------------------------------------------------------------------
* TrainingPeaks.com
*--------------------------------------------------------------------*/
#ifdef GC_HAVE_SOAP
void
MainWindow::uploadTP()
{
if (currentTab->context->ride) {
TPUploadDialog uploader(currentTab->context->athlete->cyclist, currentTab->context->currentRide(), currentTab->context);
uploader.exec();
}
}
void
MainWindow::downloadTP()
{
TPDownloadDialog downloader(currentTab->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()
{
currentTab->context->athlete->withingsDownload->download();
}
void
MainWindow::downloadMeasuresFromZeo()
{
currentTab->context->athlete->zeoDownload->download();
}
void
MainWindow::refreshCalendar()
{
#ifdef GC_HAVE_ICAL
currentTab->context->athlete->davCalendar->download();
currentTab->context->athlete->calendarDownload->download();
#endif
}
/*----------------------------------------------------------------------
* Calendar
*--------------------------------------------------------------------*/
#ifdef GC_HAVE_ICAL
void
MainWindow::uploadCalendar()
{
currentTab->context->athlete->davCalendar->upload((RideItem*)currentTab->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: currentTab->addIntervals();
break;
case 1 : splitRide();
break;
case 2 : deleteRide();
break;
}
}
void
MainWindow::addIntervals()
{
currentTab->addIntervals();
}