/*
 * 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 <QNetworkProxyQuery>
#include <QMenuBar>
#include <QStyle>
#include <QTabBar>
#include <QStyleFactory>
#include <QRect>
#include <QWebEngineProfile>

// DATA STRUCTURES
#include "MainWindow.h"
#include "Context.h"
#include "Athlete.h"
#include "AthleteView.h"
#include "AthleteBackup.h"

#include "Colors.h"
#include "RideCache.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "RideFile.h"
#include "Settings.h"
#include "TrainerDay.h"
#include "StravaRoutesDownload.h"
#include "Library.h"
#include "LibraryParser.h"
#include "TrainDB.h"
#include "GcUpgrade.h"
#include "HelpWhatsThis.h"
#include "CsvRideFile.h"

// DIALOGS / DOWNLOADS / UPLOADS
#include "AboutDialog.h"
#include "ChooseCyclistDialog.h"
#include "ConfigDialog.h"
#include "AthleteConfigDialog.h"
#include "DownloadRideDialog.h"
#include "ManualActivityWizard.h"
#include "RideImportWizard.h"
#include "EstimateCPDialog.h"
#include "SolveCPDialog.h"
#include "ToolsRhoEstimator.h"
#include "VDOTCalculator.h"
#include "SplitActivityWizard.h"
#include "MergeActivityWizard.h"
#include "GenerateHeatMapDialog.h"
#include "BatchProcessingDialog.h"
#include "MeasuresDownload.h"
#include "WorkoutWizard.h"
#include "TrainerDayDownloadDialog.h"
#include "AddDeviceWizard.h"
#include "Dropbox.h"
#include "SixCycle.h"
#include "OpenData.h"
#include "AddCloudWizard.h"
#include "LocalFileStore.h"
#include "CloudService.h"

// GUI Widgets
#include "AthleteTab.h"
#include "GcToolBar.h"
#include "HelpWindow.h"
#include "Perspective.h"
#include "PerspectiveDialog.h"

#if !defined(Q_OS_MAC)
#include "QTFullScreen.h" // not mac!
#endif

// SEARCH / FILTER
#include "NamedSearch.h"
#include "SearchFilterBox.h"
#include "WorkoutFilterBox.h"

// LTM CHART DRAG/DROP PARSE
#include "LTMChartParser.h"

// CloudDB
#ifdef GC_HAS_CLOUD_DB
#include "CloudDBCommon.h"
#include "CloudDBChart.h"
#include "CloudDBUserMetric.h"
#include "CloudDBCurator.h"
#include "CloudDBStatus.h"
#include "CloudDBTelemetry.h"
#include "CloudDBVersion.h"
#include "GcUpgrade.h"
#endif
#include "Secrets.h"

#if defined(_MSC_VER) && defined(_WIN64)
#include "WindowsCrashHandler.cpp"
#endif

// The order of the GcViewStackIdx values below must match the viewStack widget's tab order, see the MainWindow's constructor below.
namespace GcViewStackIdx {
// use constexpr instead of enum class to prevent unecessary casting
constexpr int SELECT_ATHLETE_VIEW = 0;
constexpr int ATHLETE_TAB_STACK = 1;
};

// We keep track of all theopen mainwindows
QList<MainWindow *> mainwindows;
extern ConfigDialog *configdialog_ptr;
extern QString gl_version;
extern double gl_major; // 1.x 2.x 3.x - we insist on 2.x or higher to enable OpenGL

// constants for gui
static int gl_toolheight=28;

MainWindow::MainWindow(const QDir &home)
{
    /*----------------------------------------------------------------------
     *  Bootstrap
     *--------------------------------------------------------------------*/
    setAttribute(Qt::WA_DeleteOnClose);
    mainwindows.append(this);  // add us to the list of open windows
    pactive = init = false;

    // create a splash to keep user informed on first load
    // first one in middle of display, not middle of window
    splash = new SplashScreen();

#if defined(_MSC_VER) && defined(_WIN64)
    // set dbg/stacktrace directory for Windows to the athlete directory
    // don't use the GC_HOMEDIR .ini value, since we want to have a proper path
    // even if default athlete dirs are used.
    QDir varHome = home;
    varHome.cdUp();
    setCrashFilePath(varHome.canonicalPath().toStdString());

#endif

    // bootstrap
    Context *context = new Context(this);
    context->athlete = new Athlete(context, home);
    QString temp = const_cast<AthleteDirectoryStructure*>(context->athlete->directoryStructure())->temp().absolutePath();
    context->webEngineProfile->setCachePath(temp);
    context->webEngineProfile->setPersistentStoragePath(temp);
    currentAthleteTab = new AthleteTab(context);

    setWindowIcon(QIcon(":images/gc.png"));
    setWindowTitle(context->athlete->home->root().dirName());
    setContentsMargins(0,0,0,0);
    setAcceptDrops(true);

    Library::initialise(context->athlete->home->root());
    QNetworkProxyQuery npq(QUrl("http://www.google.com"));
    QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(npq);
    if (listOfProxies.count() > 0) {
        QNetworkProxy::setApplicationProxy(listOfProxies.first());
    }

#ifndef Q_OS_MAC
    fullScreen = new QTFullScreen(this);
#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->root().canonicalPath()).canonicalPath());
    }

    /*----------------------------------------------------------------------
     *  GUI setup
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI..."));
    if (appsettings->contains(GC_SETTINGS_MAIN_GEOM)) {
         restoreGeometry(appsettings->value(this, GC_SETTINGS_MAIN_GEOM).toByteArray());
         restoreState(appsettings->value(this, GC_SETTINGS_MAIN_STATE).toByteArray());
    } else {

         AppearanceSettings defaults = GSettings::defaultAppearanceSettings();

         // center on the available screen (minus toolbar/sidebar)
         move(defaults.windowsize.x(), defaults.windowsize.y());
         resize(defaults.windowsize.width(), defaults.windowsize.height());

     }

     // store "last_openend" athlete for next time
     appsettings->setValue(GC_SETTINGS_LAST, context->athlete->home->root().dirName());

    /*----------------------------------------------------------------------
     * ScopeBar as sidebar from v3.6
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI: Scopebar..."));

    sidebar = new NewSideBar(context, this);
    HelpWhatsThis *helpNewSideBar = new HelpWhatsThis(sidebar);
    sidebar->setWhatsThis(helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar));

    // The ids of the sidebar buttons below are defined in NewSideBar.h
    sidebar->addItem(QImage(":sidebar/athlete.png"), tr("athletes"), GcSideBarBtnId::SELECT_ATHLETE_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Athletes));

    sidebar->addItem(QImage(":sidebar/plan.png"), tr("plan"), GcSideBarBtnId::PLAN_BTN, tr("Feature not implemented yet"));
    sidebar->setItemEnabled(GcSideBarBtnId::PLAN_BTN, false);

    sidebar->addItem(QImage(":sidebar/trends.png"), tr("trends"), GcSideBarBtnId::TRENDS_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Trends));

    sidebar->addItem(QImage(":sidebar/assess.png"), tr("activities"), GcSideBarBtnId::ACTIVITIES_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Rides));

    sidebar->addItem(QImage(":sidebar/reflect.png"), tr("reflect"), GcSideBarBtnId::REFLECT_BTN, tr("Feature not implemented yet"));
    sidebar->setItemEnabled(GcSideBarBtnId::REFLECT_BTN, false);

    sidebar->addItem(QImage(":sidebar/train.png"), tr("train"), GcSideBarBtnId::TRAIN_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Train));

    sidebar->addStretch();
    sidebar->addItem(QImage(":sidebar/apps.png"), tr("apps"), GcSideBarBtnId::APPS_BTN, tr("Feature not implemented yet"));
    sidebar->setItemEnabled(GcSideBarBtnId::APPS_BTN, false);
    sidebar->addStretch();

    // we can click on the quick icons, but they aren't selectable views
    sidebar->addItem(QImage(":sidebar/sync.png"), tr("sync"), GcSideBarBtnId::SYNC_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Sync));
    sidebar->setItemSelectable(GcSideBarBtnId::SYNC_BTN, false);
    sidebar->addItem(QImage(":sidebar/prefs.png"), tr("options"), GcSideBarBtnId::OPTIONS_BTN, helpNewSideBar->getWhatsThisText(HelpWhatsThis::ScopeBar_Options));
    sidebar->setItemSelectable(GcSideBarBtnId::OPTIONS_BTN, false);

    connect(sidebar, SIGNAL(itemClicked(GcSideBarBtnId)), this, SLOT(sidebarClicked(GcSideBarBtnId)));
    connect(sidebar, SIGNAL(itemSelected(GcSideBarBtnId)), this, SLOT(sidebarSelected(GcSideBarBtnId)));

    /*----------------------------------------------------------------------
     * What's this Context Help
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI: Context Help..."));

    // Help for the whole window
    HelpWhatsThis *help = new HelpWhatsThis(this);
    this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Default));
    // add Help Button
    QAction *myHelper = QWhatsThis::createAction (this);
    this->addAction(myHelper);


    /*----------------------------------------------------------------------
     *  Toolbar
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI: Toolbar..."));
    head = new GcToolBar(this);

    QStyle *toolStyle = QStyleFactory::create("fusion");

    // get those icons
    sidebarIcon = iconFromPNG(":images/titlebar/sidebar.png");
    lowbarIcon = iconFromPNG(":images/titlebar/bottombar.png");
    tiledIcon = iconFromPNG(":images/titlebar/tile.png");
    backIcon = iconFromPNG(":images/mac/back.png");
    whatIcon = iconFromPNG(":images/titlebar/whatsthis.png");
    forwardIcon = iconFromPNG(":images/mac/forward.png");
    QSize isize(16 *dpiXFactor,16 *dpiYFactor);

    back = new QPushButton(this);
    back->setIcon(backIcon);
    back->setFixedHeight(gl_toolheight *dpiYFactor);
    back->setFixedWidth(gl_toolheight *dpiYFactor);
    back->setIconSize(isize);
    back->setStyle(toolStyle);
    connect(back, SIGNAL(clicked(bool)), this, SIGNAL(backClicked()));
    HelpWhatsThis *helpBack = new HelpWhatsThis(back);
    back->setWhatsThis(helpBack->getWhatsThisText(HelpWhatsThis::ToolBar_Back));

    forward = new QPushButton(this);
    forward->setIcon(forwardIcon);
    forward->setFixedHeight(gl_toolheight *dpiYFactor);
    forward->setFixedWidth(gl_toolheight *dpiYFactor);
    forward->setIconSize(isize);
    forward->setStyle(toolStyle);
    connect(forward, SIGNAL(clicked(bool)), this, SIGNAL(forwardClicked()));
    HelpWhatsThis *helpForward = new HelpWhatsThis(forward);
    forward->setWhatsThis(helpForward->getWhatsThisText(HelpWhatsThis::ToolBar_Forward));

    lowbar = new QPushButton(this);
    lowbar->setIcon(lowbarIcon);
    lowbar->setFixedHeight(gl_toolheight *dpiYFactor);
    lowbar->setFixedWidth(gl_toolheight *dpiYFactor);
    lowbar->setIconSize(isize);
    lowbar->setStyle(toolStyle);
    lowbar->setToolTip(tr("Toggle Compare Pane"));
    connect(lowbar, SIGNAL(clicked(bool)), this, SLOT(toggleLowbar()));
    HelpWhatsThis *helpLowBar = new HelpWhatsThis(lowbar);
    lowbar->setWhatsThis(helpLowBar->getWhatsThisText(HelpWhatsThis::ToolBar_ToggleComparePane));

    sidelist = new QPushButton(this);
    sidelist->setIcon(sidebarIcon);
    sidelist->setFixedHeight(gl_toolheight * dpiYFactor);
    sidelist->setFixedWidth(gl_toolheight *dpiYFactor);
    sidelist->setIconSize(isize);
    sidelist->setStyle(toolStyle);
    sidelist->setToolTip(tr("Toggle Sidebar"));
    connect(sidelist, SIGNAL(clicked(bool)), this, SLOT(toggleSidebar()));
    HelpWhatsThis *helpSideBar = new HelpWhatsThis(sidelist);
    sidelist->setWhatsThis(helpSideBar->getWhatsThisText(HelpWhatsThis::ToolBar_ToggleSidebar));

    tabtile = new QPushButton(this);
    tabtile->setIcon(tiledIcon);
    tabtile->setFixedHeight(gl_toolheight *dpiYFactor);
    tabtile->setFixedWidth(gl_toolheight *dpiYFactor);
    tabtile->setIconSize(isize);
    lowbar->setStyle(toolStyle);
    tabtile->setToolTip(tr("Toggle Tab/Tile"));
    connect(tabtile, SIGNAL(clicked(bool)), this, SLOT(toggleStyle()));
    HelpWhatsThis *helpTabtile = new HelpWhatsThis(tabtile);
    tabtile->setWhatsThis(helpTabtile->getWhatsThisText(HelpWhatsThis::ToolBar_TabTile));

    // What's this button
    whatsthis = new QPushButton(this);
    whatsthis->setIcon(whatIcon);
    whatsthis->setFixedHeight(gl_toolheight * dpiYFactor);
    whatsthis->setFixedWidth(gl_toolheight * dpiYFactor);
    whatsthis->setIconSize(isize);
    whatsthis->setStyle(toolStyle);
    whatsthis->setToolTip(tr("What's This?"));
    connect(whatsthis, SIGNAL(clicked(bool)), this, SLOT(enterWhatsThisMode()));

    // Perspective selector
    perspectiveSelector = new QComboBox(this);
    perspectiveSelector->setStyle(toolStyle);
    perspectiveSelector->setFixedWidth(200 * dpiXFactor);
    perspectiveSelector->setFixedHeight(gl_toolheight * dpiYFactor);
    connect(perspectiveSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(perspectiveSelected(int)));
    HelpWhatsThis *helpPerspectiveSelector = new HelpWhatsThis(perspectiveSelector);
    perspectiveSelector->setWhatsThis(helpPerspectiveSelector->getWhatsThisText(HelpWhatsThis::ToolBar_PerspectiveSelector));

    // Search/Filter box
    searchBox = new SearchFilterBox(this,context,false);

    searchBox->setStyle(toolStyle);
    searchBox->setFixedWidth(400 * dpiXFactor);
    searchBox->setFixedHeight(gl_toolheight * dpiYFactor);

    // Workout Filter Box
    workoutFilterBox = new WorkoutFilterBox(this, context);

    workoutFilterBox->setStyle(toolStyle);
    workoutFilterBox->setFixedWidth(400 * dpiXFactor);
    workoutFilterBox->setFixedHeight(gl_toolheight * dpiYFactor);
    HelpWhatsThis *helpWorkoutFilterBox = new HelpWhatsThis(workoutFilterBox);
    workoutFilterBox->setWhatsThis(helpWorkoutFilterBox->getWhatsThisText(HelpWhatsThis::ToolBar_WorkoutFilterBox));

    QWidget *space = new QWidget(this);
    space->setAutoFillBackground(false);
    space->setFixedWidth(5 * dpiYFactor);

    head->addWidget(space);
    head->addWidget(back);
    head->addWidget(forward);
    head->addWidget(perspectiveSelector);
    head->addStretch();
#ifdef Q_OS_MAC // no menu on mac, so lets have some breathing space
    head->setFixedHeight(searchBox->height() + (20 *dpiXFactor * 2));
#else
    head->setFixedHeight(searchBox->height() + (16 *dpiXFactor));
#endif

    connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList)));
    connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter()));
    HelpWhatsThis *helpSearchBox = new HelpWhatsThis(searchBox);
    searchBox->setWhatsThis(helpSearchBox->getWhatsThisText(HelpWhatsThis::SearchFilterBox));

    head->addWidget(searchBox);
    head->addWidget(workoutFilterBox);
    space = new Spacer(this);
    space->setFixedWidth(5 *dpiYFactor);
    head->addWidget(space);
    head->addWidget(sidelist);
    head->addWidget(lowbar);
    head->addWidget(tabtile);
    space = new Spacer(this);
    space->setFixedWidth(5 *dpiYFactor);
    head->addWidget(space);
    head->addWidget(whatsthis);
    space = new Spacer(this);
    space->setFixedWidth(5 *dpiYFactor);
    head->addWidget(space);

#ifdef Q_OS_LINUX
    // check opengl is available with version 2 or higher
    // only do this on Linux since Windows and MacOS have opengl "issues"
    QOffscreenSurface surf;
    surf.create();

    QOpenGLContext ctx;
    ctx.create();
    ctx.makeCurrent(&surf);

    // OpenGL version number
    gl_version = QString::fromUtf8((char *)(ctx.functions()->glGetString(GL_VERSION)));
    gl_major = Utils::number(gl_version);
#endif

    /*----------------------------------------------------------------------
     * Central Widget
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI: Central Widget..."));

    tabbar = new DragBar(this);
    tabbar->setTabsClosable(false); // use athlete view
#ifdef Q_OS_MAC
    tabbar->setDocumentMode(true);
#endif

    // Note: The order of the viewStack tabs below, must match the GcViewStackIdx definitions above.
    athleteView = new AthleteView(context);
    viewStack = new QStackedWidget(this);
    viewStack->addWidget(athleteView);

    tabStack = new QStackedWidget(this);
    viewStack->addWidget(tabStack);

    // first tab
    athletetabs.insert(currentAthleteTab->context->athlete->home->root().dirName(), currentAthleteTab);

    // stack, list and bar all share a common index
    tabList.append(currentAthleteTab);
    tabbar->addTab(currentAthleteTab->context->athlete->home->root().dirName());
    tabStack->addWidget(currentAthleteTab);
    tabStack->setCurrentIndex(0);

    connect(tabbar, SIGNAL(dragTab(int)), this, SLOT(switchAthleteTab(int)));
    connect(tabbar, SIGNAL(currentChanged(int)), this, SLOT(switchAthleteTab(int)));
    //connect(tabbar, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTabClicked(int))); // use athlete view

    /*----------------------------------------------------------------------
     * 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);
    mainLayout->addWidget(head);
    QHBoxLayout *lrlayout = new QHBoxLayout();
    mainLayout->addLayout(lrlayout);
    lrlayout->setSpacing(0);
    lrlayout->setContentsMargins(0,0,0,0);
    lrlayout->addWidget(sidebar);
    QVBoxLayout *tablayout = new QVBoxLayout();
    tablayout->setSpacing(0);
    tablayout->setContentsMargins(0,0,0,0);
    lrlayout->addLayout(tablayout);
    tablayout->addWidget(tabbar);
    tablayout->addWidget(viewStack);
    setCentralWidget(central);

#if QT_VERSION >= 0x060000
    /*----------------------------------------------------------------------
     * Hack to avoid a flickering MainWindow when showing a QWebEngineView in a chart, e.g. a Map:
     * Temporarily add a dummy QWebEngineView with some random content before the MainWindow is shown
     * https://forum.qt.io/topic/141398/qwebengineview-closes-reopens-window-when-added-dynamically
     *--------------------------------------------------------------------*/
    QWebEngineView *dummywev = new QWebEngineView();
    dummywev->page()->setHtml("<html></html>");
    mainLayout->addWidget(dummywev);
    mainLayout->removeWidget(dummywev);
    delete dummywev;
#endif

    /*----------------------------------------------------------------------
     * Application Menus
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Setting up GUI: Application Menus..."));
#ifdef WIN32
    QString menuColorString = GColor(CTOOLBAR).name();
    menuBar()->setStyleSheet(QString("QMenuBar { color: black; background: %1; }"
                             "QMenuBar::item { color: black; background: %1; }").arg(menuColorString));
    menuBar()->setContentsMargins(0,0,0,0);
#endif

    // ATHLETE (FILE) MENU
    QMenu *fileMenu = menuBar()->addMenu(tr("&Athlete"));

    openTabMenu = fileMenu->addMenu(tr("Open..."));
    connect(openTabMenu, SIGNAL(aboutToShow()), this, SLOT(setOpenTabMenu()));

    tabMapper = new QSignalMapper(this); // maps each option
    connect(tabMapper, &QSignalMapper::mappedString, this, &MainWindow::openAthleteTab);

    fileMenu->addSeparator();
    backupAthleteMenu = fileMenu->addMenu(tr("Backup..."));
    connect(backupAthleteMenu, SIGNAL(aboutToShow()), this, SLOT(setBackupAthleteMenu()));
    backupMapper = new QSignalMapper(this); // maps each option
    connect(backupMapper, &QSignalMapper::mappedString, this, &MainWindow::backupAthlete);

    fileMenu->addSeparator();
    deleteAthleteMenu = fileMenu->addMenu(tr("Delete..."));
    connect(deleteAthleteMenu, SIGNAL(aboutToShow()), this, SLOT(setDeleteAthleteMenu()));
    deleteMapper = new QSignalMapper(this); // maps each option
    connect(deleteMapper, &QSignalMapper::mappedString, this, &MainWindow::deleteAthlete);

    fileMenu->addSeparator();
    fileMenu->addAction(tr("Settings..."), this, SLOT(athleteSettings()));
    fileMenu->addSeparator();
    fileMenu->addAction(tr("Save all modified activities"), this, SLOT(saveAllUnsavedRides()));
    fileMenu->addSeparator();
    QAction *actionQuit = new QAction(tr("&Quit"), fileMenu);
    actionQuit->setShortcuts(QKeySequence::Quit);
    actionQuit->setShortcutContext(Qt::ApplicationShortcut);
    connect(actionQuit, SIGNAL(triggered()), this, SLOT(closeWindow()));
    fileMenu->addAction(actionQuit);
    //fileMenu->addAction(tr("&Close Tab"), this, SLOT(closeTab())); use athlete view

    HelpWhatsThis *fileMenuHelp = new HelpWhatsThis(fileMenu);
    fileMenu->setWhatsThis(fileMenuHelp->getWhatsThisText(HelpWhatsThis::MenuBar_Athlete));

    // ACTIVITY MENU
    QMenu *rideMenu = menuBar()->addMenu(tr("A&ctivity"));
    rideMenu->addAction(tr("&Download from device..."), this, SLOT(downloadRide()), QKeySequence("Ctrl+D"));
    rideMenu->addAction(tr("&Import from file..."), this, SLOT (importFile()), QKeySequence("Ctrl+I"));
    rideMenu->addAction(tr("&Manual entry..."), this, SLOT(manualRide()), QKeySequence("Ctrl+M"));
#if defined(GC_ACTIVITY_PLAN)
    QAction *actionPlan = new QAction(tr("&Plan activity..."));
    connect(context, &Context::start, this, [=]() { actionPlan->setEnabled(false); }); // The dialog can change the contexts workout
    connect(context, &Context::stop, this, [=]() { actionPlan->setEnabled(true); });   // temporarily which might cause unwanted effects
    connect(actionPlan, &QAction::triggered, this, [=]() { planActivity(); });
    rideMenu->addAction(actionPlan);
#endif
    rideMenu->addSeparator ();
    rideMenu->addAction(tr("&Export..."), this, SLOT(exportRide()), QKeySequence("Ctrl+E"));
    rideMenu->addAction(tr("&Batch Processing..."), this, SLOT(batchProcessing()), QKeySequence("Ctrl+B"));

    rideMenu->addSeparator ();
    rideMenu->addAction(tr("&Save activity"), this, SLOT(saveRide()), QKeySequence("Ctrl+S"));
    rideMenu->addAction(tr("D&elete activity..."), this, SLOT(deleteRide()));
    rideMenu->addAction(tr("Split &activity..."), this, SLOT(splitRide()));
    rideMenu->addAction(tr("Combine activities..."), this, SLOT(mergeRide()));
    rideMenu->addSeparator ();
    rideMenu->addAction(tr("Find intervals..."), this, SLOT(addIntervals()), QKeySequence(""));

    HelpWhatsThis *helpRideMenu = new HelpWhatsThis(rideMenu);
    rideMenu->setWhatsThis(helpRideMenu->getWhatsThisText(HelpWhatsThis::MenuBar_Activity));

    // SHARE MENU
    QMenu *shareMenu = menuBar()->addMenu(tr("Sha&re"));

    // default options
    shareAction = new QAction(tr("Add Cloud Account..."), this);
    shareAction->setShortcut(QKeySequence("")); // Ctrl+A is already in use for select all
    connect(shareAction, SIGNAL(triggered(bool)), this, SLOT(addAccount()));
    shareMenu->addAction(shareAction);
    shareMenu->addSeparator();

    uploadMenu = shareMenu->addMenu(tr("Upload Activity..."));
    syncMenu = shareMenu->addMenu(tr("Synchronise Activities..."));
    measuresMenu = shareMenu->addMenu(tr("Get Measures..."));
    shareMenu->addSeparator();
    checkAction = new QAction(tr("Check For New Activities"), this);
    checkAction->setShortcut(QKeySequence("")); // Ctrl+C is already in use for clipboard copy
    connect(checkAction, SIGNAL(triggered(bool)), this, SLOT(checkCloud()));
    shareMenu->addAction(checkAction);


    // set the menus to reflect the configured accounts
    connect(uploadMenu, SIGNAL(aboutToShow()), this, SLOT(setUploadMenu()));
    connect(syncMenu, SIGNAL(aboutToShow()), this, SLOT(setSyncMenu()));

    connect(uploadMenu, SIGNAL(triggered(QAction*)), this, SLOT(uploadCloud(QAction*)));
    connect(syncMenu, SIGNAL(triggered(QAction*)), this, SLOT(syncCloud(QAction*)));
    connect(measuresMenu, SIGNAL(aboutToShow()), this, SLOT(setMeasuresMenu()));
    connect(measuresMenu, SIGNAL(triggered(QAction*)), this, SLOT(downloadMeasures(QAction*)));

    HelpWhatsThis *helpShare = new HelpWhatsThis(shareMenu);
    shareMenu->setWhatsThis(helpShare->getWhatsThisText(HelpWhatsThis::MenuBar_Share));

    // TOOLS MENU
    QMenu *optionsMenu = menuBar()->addMenu(tr("&Tools"));
    optionsMenu->addAction(tr("CP and W' Estimator..."), this, SLOT(showEstimateCP()));
    optionsMenu->addAction(tr("CP and W' Solver..."), this, SLOT(showSolveCP()));
    optionsMenu->addAction(tr("Air Density (Rho) Estimator..."), this, SLOT(showRhoEstimator()));
    optionsMenu->addAction(tr("VDOT and T-Pace Calculator..."), this, SLOT(showVDOTCalculator()));

    optionsMenu->addSeparator();
    optionsMenu->addAction(tr("Create a new workout..."), this, SLOT(showWorkoutWizard()));
    optionsMenu->addAction(tr("Download workouts from TrainerDay..."), this, SLOT(downloadTrainerDay()));
    optionsMenu->addAction(tr("Download workouts from Strava Routes..."), this, SLOT(downloadStravaRoutes()));
    optionsMenu->addAction(tr("Import workouts, videos, videoSyncs..."), this, SLOT(importWorkout()));
    optionsMenu->addAction(tr("Scan disk for workouts, videos, videoSyncs..."), this, SLOT(manageLibrary()));

    optionsMenu->addAction(tr("Create Heat Map..."), this, SLOT(generateHeatMap()));
    optionsMenu->addAction(tr("Export Metrics as CSV..."), this, SLOT(exportMetrics()));

#ifdef GC_HAS_CLOUD_DB
    // CloudDB options
    optionsMenu->addSeparator();
    optionsMenu->addAction(tr("Cloud Status..."), this, SLOT(cloudDBshowStatus()));

    QMenu *cloudDBMenu = optionsMenu->addMenu(tr("Cloud Contributions"));
    cloudDBMenu->addAction(tr("Maintain charts"), this, SLOT(cloudDBuserEditChart()));
    cloudDBMenu->addAction(tr("Maintain user metrics"), this, SLOT(cloudDBuserEditUserMetric()));

    if (CloudDBCommon::addCuratorFeatures) {
        QMenu *cloudDBCurator = optionsMenu->addMenu(tr("Cloud Curator"));
        cloudDBCurator->addAction(tr("Curate charts"), this, SLOT(cloudDBcuratorEditChart()));
        cloudDBCurator->addAction(tr("Curate user metrics"), this, SLOT(cloudDBcuratorEditUserMetric()));
    }

#endif
#ifndef Q_OS_MAC
    optionsMenu->addSeparator();
#endif
    // options are always at the end of the tools menu
    QAction *pref = optionsMenu->addAction(tr("&Options..."), this, SLOT(showOptions()));
    pref->setMenuRole(QAction:: PreferencesRole);

    HelpWhatsThis *optionsMenuHelp = new HelpWhatsThis(optionsMenu);
    optionsMenu->setWhatsThis(optionsMenuHelp->getWhatsThisText(HelpWhatsThis::MenuBar_Tools));


    editMenu = menuBar()->addMenu(tr("&Edit"));
    connect(editMenu, SIGNAL(aboutToShow()), this, SLOT(onEditMenuAboutToShow()));

    HelpWhatsThis *editMenuHelp = new HelpWhatsThis(editMenu);
    editMenu->setWhatsThis(editMenuHelp->getWhatsThisText(HelpWhatsThis::MenuBar_Edit));

    // VIEW MENU
    QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
#ifndef Q_OS_MAC
    viewMenu->addAction(tr("Toggle Full Screen"), this, SLOT(toggleFullScreen()), QKeySequence("F11"));
#endif
    showhideViewbar = viewMenu->addAction(tr("Show View Sidebar"), this, SLOT(showViewbar(bool)), QKeySequence("F2"));
    showhideViewbar->setCheckable(true);
    showhideViewbar->setChecked(true);
    showhideSidebar = viewMenu->addAction(tr("Show Left Sidebar"), this, SLOT(showSidebar(bool)), QKeySequence("F3"));
    showhideSidebar->setCheckable(true);
    showhideSidebar->setChecked(true);
    showhideLowbar = viewMenu->addAction(tr("Show Compare Pane"), this, SLOT(showLowbar(bool)), QKeySequence("F4"));
    showhideLowbar->setCheckable(true);
    showhideLowbar->setChecked(false);
    showhideToolbar = viewMenu->addAction(tr("Show Toolbar"), this, SLOT(showToolbar(bool)), QKeySequence("F5"));
    showhideToolbar->setCheckable(true);
    showhideToolbar->setChecked(true);
    showhideTabbar = viewMenu->addAction(tr("Show Athlete Tabs"), this, SLOT(showTabbar(bool)), QKeySequence("F6"));
    showhideTabbar->setCheckable(true);
    showhideTabbar->setChecked(true);

    viewMenu->addSeparator();
    viewMenu->addAction(tr("Trends"), this, SLOT(selectTrends()));
    viewMenu->addAction(tr("Activities"), this, SLOT(selectAnalysis()));
    viewMenu->addAction(tr("Train"), this, SLOT(selectTrain()));
    viewMenu->addSeparator();
    viewMenu->addAction(tr("Import Perspective..."), this, SLOT(importPerspective()));
    viewMenu->addAction(tr("Export Perspective..."), this, SLOT(exportPerspective()));
    viewMenu->addSeparator();
    subChartMenu = viewMenu->addMenu(tr("Add Chart"));
    viewMenu->addAction(tr("Import Chart..."), this, SLOT(importChart()));
#ifdef GC_HAS_CLOUD_DB
    viewMenu->addAction(tr("Upload Chart..."), this, SLOT(exportChartToCloudDB()));
    viewMenu->addAction(tr("Download Chart..."), this, SLOT(addChartFromCloudDB()));
    viewMenu->addSeparator();
#endif
    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(subChartMenu, SIGNAL(aboutToShow()), this, SLOT(setSubChartMenu()));
    connect(subChartMenu, SIGNAL(triggered(QAction*)), this, SLOT(addChart(QAction*)));

    HelpWhatsThis *viewMenuHelp = new HelpWhatsThis(viewMenu);
    viewMenu->setWhatsThis(viewMenuHelp->getWhatsThisText(HelpWhatsThis::MenuBar_View));

    // HELP MENU
    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
    helpMenu->addAction(tr("&Help Overview"), this, SLOT(helpWindow()));
    helpMenu->addAction(myHelper);
    helpMenu->addSeparator();
    helpMenu->addAction(tr("&User Guide"), this, SLOT(helpView()));
    helpMenu->addAction(tr("&Log a bug or feature request"), this, SLOT(logBug()));
    helpMenu->addAction(tr("&Discussion and Support Forum"), this, SLOT(support()));
    helpMenu->addSeparator();
    helpMenu->addAction(tr("&About GoldenCheetah"), this, SLOT(aboutDialog()));

    HelpWhatsThis *helpMenuHelp = new HelpWhatsThis(helpMenu);
    helpMenu->setWhatsThis(helpMenuHelp->getWhatsThisText(HelpWhatsThis::MenuBar_Help));

    /*----------------------------------------------------------------------
     * Lets go, choose latest ride and get GUI up and running
     *--------------------------------------------------------------------*/
    splash->showMessage(tr("Selecting ride..."));

    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()));

    saveGCState(currentAthleteTab->context); // set to whatever we started with

    // switch to the startup view, default is analysis.
    switch (appsettings->value(NULL, GC_STARTUP_VIEW, "1").toInt()) {
        case 0: selectTrends(); break;
        case 1: selectAnalysis(); break;
        case 2: selectDiary(); break;
        case 3: selectTrain(); break;
        default: selectAnalysis(); qDebug() << "Unknown startup view"; break;
    }

    //grab focus
    currentAthleteTab->setFocus();

    installEventFilter(this);

    // catch global config changes
    connect(GlobalContext::context(), SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
    configChanged(CONFIG_APPEARANCE);

    init = true;

    /*----------------------------------------------------------------------
     * Lets ask for telemetry and check for updates
     *--------------------------------------------------------------------*/

#if !defined(OPENDATA_DISABLE)
    splash->showMessage(tr("Checking for udates..."));
    OpenData::check(currentAthleteTab->context);
#else
    fprintf(stderr, "OpenData disabled, secret not defined.\n"); fflush(stderr);
#endif

#ifdef GC_HAS_CLOUD_DB
    splash->showMessage(tr("Asking for telemetry..."));
    telemetryClient = new CloudDBTelemetryClient();
    if (appsettings->value(NULL, GC_ALLOW_TELEMETRY, "undefined").toString() == "undefined" ) {
        // ask user if storing is allowed

        // check for Telemetry Storage acceptance
        CloudDBAcceptTelemetryDialog acceptDialog;
        acceptDialog.setModal(true);
        if (acceptDialog.exec() == QDialog::Accepted) {
            telemetryClient->upsertTelemetry();
        };
    } else if (appsettings->value(NULL, GC_ALLOW_TELEMETRY, false).toBool()) {
        telemetryClient->upsertTelemetry();
    }

    versionClient = new CloudDBVersionClient();
    versionClient->informUserAboutLatestVersions();



#endif

    // get rid of splash when currentTab is shown
    delete splash;
    splash = nullptr;
}


/*----------------------------------------------------------------------
 * GUI
 *--------------------------------------------------------------------*/

void
MainWindow::toggleSidebar()
{
    currentAthleteTab->toggleSidebar();
    setToolButtons();
}
void
MainWindow::showViewbar(bool want)
{
    want ? sidebar->show() : sidebar->hide();
    showhideViewbar->setChecked(want);
    setToolButtons();
}
void
MainWindow::showSidebar(bool want)
{
    currentAthleteTab->setSidebarEnabled(want);
    showhideSidebar->setChecked(want);
    setToolButtons();
}

void
MainWindow::toggleLowbar()
{
    if (currentAthleteTab->hasBottom()) currentAthleteTab->setBottomRequested(!currentAthleteTab->isBottomRequested());
    setToolButtons();
}

void
MainWindow::showLowbar(bool want)
{
    if (currentAthleteTab->hasBottom()) currentAthleteTab->setBottomRequested(want);
    showhideLowbar->setChecked(want);
    setToolButtons();
}

void
MainWindow::enterWhatsThisMode()
{
    QWhatsThis::enterWhatsThisMode();
}

void
MainWindow::showTabbar(bool want)
{
    setUpdatesEnabled(false);
    showhideTabbar->setChecked(want);
    if (want) {
        tabbar->show();
    }
    else {
        tabbar->hide();
    }
    setUpdatesEnabled(true);
}

void
MainWindow::showToolbar(bool want)
{
    setUpdatesEnabled(false);
    showhideToolbar->setChecked(want);
    if (want) {
        head->show();
    }
    else {
        head->hide();
    }
    setUpdatesEnabled(true);
}

void
MainWindow::setSubChartMenu()
{
    setChartMenu(subChartMenu);
}

void
MainWindow::setChartMenu(QMenu *menu)
{
    unsigned int mask=0;
    // called when chart menu about to be shown
    // setup to only show charts that are relevant
    // to this view
    switch(currentAthleteTab->currentView()) {
        case 0 : mask = VIEW_TRENDS; break;
        default:
        case 1 : mask = VIEW_ANALYSIS; break;
        case 2 : mask = VIEW_DIARY; break;
        case 3 : mask = VIEW_TRAIN; break;
    }

    menu->clear();
    if (!mask) return;

    for(int i=0; GcWindows[i].relevance; i++) {
        if (GcWindows[i].relevance & mask)
            menu->addAction(GcWindows[i].name);
    }
}

void
MainWindow::addChart(QAction*action)
{
    // & removed to avoid issues with kde AutoCheckAccelerators
    QString actionText = QString(action->text()).replace("&", "");
    GcWinID id = GcWindowTypes::None;
    for (int i=0; GcWindows[i].relevance; i++) {
        if (GcWindows[i].name == actionText) {
            id = GcWindows[i].id;
            break;
        }
    }
    if (id != GcWindowTypes::None)
        currentAthleteTab->addChart(id); // called from MainWindow to inset chart
}

void
MainWindow::importChart()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Select Chart file to import"), "", tr("GoldenCheetah Chart Files (*.gchart)"));

    if (fileName.isEmpty()) {
        QMessageBox::critical(this, tr("Import Chart"), tr("No chart file selected!"));
    } else {
        importCharts(QStringList()<<fileName);
    }
}

void
MainWindow::exportPerspective()
{
    int view = currentAthleteTab->currentView();
    AbstractView *current = NULL;

    QString typedesc;

    switch (view) {
    case 0:  current = currentAthleteTab->homeView; typedesc = "Trends"; break;
    case 1:  current = currentAthleteTab->analysisView; typedesc = "Analysis"; break;
    case 2:  current = currentAthleteTab->diaryView; typedesc = "Diary"; break;
    case 3:  current = currentAthleteTab->trainView; typedesc = "Train"; break;
    }

    // export the current perspective to a file
    QString suffix;
    QString fileName = QFileDialog::getSaveFileName(this, tr("Export Persepctive"),
                       QDir::homePath()+"/"+ typedesc + " " + current->perspective_->title() + ".gchartset",
                       ("*.gchartset;;"), &suffix, QFileDialog::DontUseNativeDialog); // native dialog hangs when threads in use (!)

    if (fileName.isEmpty()) {
        QMessageBox::critical(this, tr("Export Perspective"), tr("No perspective file selected!"));
    } else {
        current->exportPerspective(current->perspective_, fileName);
    }
}

void
MainWindow::importPerspective()
{
    int view = currentAthleteTab->currentView();
    AbstractView *current = NULL;

    switch (view) {
    case 0:  current = currentAthleteTab->homeView; break;
    case 1:  current = currentAthleteTab->analysisView; break;
    case 2:  current = currentAthleteTab->diaryView; break;
    case 3:  current = currentAthleteTab->trainView; break;
    }

    // import a new perspective from a file
    QString fileName = QFileDialog::getOpenFileName(this, tr("Select Perspective file to import"), "", tr("GoldenCheetah Perspective Files (*.gchartset)"));
    if (fileName.isEmpty()) {
        QMessageBox::critical(this, tr("Import Perspective"), tr("No perspective file selected!"));
    } else {

        // import and select it
        pactive = true;
        if (current->importPerspective(fileName)) {

            // on success we select the new one
            resetPerspective(view);
            //current->setPerspectives(perspectiveSelector);

            // and select remember pactive is true, so we do the heavy lifting here
            perspectiveSelector->setCurrentIndex(current->perspectives_.count()-1);
            current->perspectiveSelected(perspectiveSelector->currentIndex());
        }
        pactive = false;
    }

}

#ifdef GC_HAS_CLOUD_DB

void
MainWindow::exportChartToCloudDB()
{
    // upload the current chart selected to the chart db
    // called from the sidebar menu
    Perspective *page=currentAthleteTab->view(currentAthleteTab->currentView())->page();
    if (page->currentStyle == 0 && page->currentChart())
        page->currentChart()->exportChartToCloudDB();
}

void
MainWindow::addChartFromCloudDB()
{
    if (!(appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, GC_CLOUDDB_TC_ACCEPTANCE, false).toBool())) {
       CloudDBAcceptConditionsDialog acceptDialog(currentAthleteTab->context->athlete->cyclist);
       acceptDialog.setModal(true);
       if (acceptDialog.exec() == QDialog::Rejected) {
          return;
       };
    }

    if (currentAthleteTab->context->cdbChartListDialog == NULL) {
        currentAthleteTab->context->cdbChartListDialog = new CloudDBChartListDialog();
    }

    if (currentAthleteTab->context->cdbChartListDialog->prepareData(currentAthleteTab->context->athlete->cyclist, CloudDBCommon::UserImport, currentAthleteTab->currentView())) {
        if (currentAthleteTab->context->cdbChartListDialog->exec() == QDialog::Accepted) {

            // get selected chartDef
            QList<QString> chartDefs = currentAthleteTab->context->cdbChartListDialog->getSelectedSettings();

            // parse charts into property pairs
            foreach (QString chartDef, chartDefs) {
                QList<QMap<QString,QString> > properties = GcChartWindow::chartPropertiesFromString(chartDef);
                for (int i = 0; i< properties.size(); i++) {
                    currentAthleteTab->context->mainWindow->athleteTab()->view(currentAthleteTab->currentView())->importChart(properties.at(i), false);
                }
            }
        }
    }
}
#endif

void
MainWindow::toggleStyle()
{
    currentAthleteTab->toggleTile();
    styleAction->setChecked(currentAthleteTab->isTiled());
    setToolButtons();
}

void
MainWindow::toggleFullScreen()
{
#ifndef Q_OS_MAC
    if (fullScreen) fullScreen->toggle();
    else qDebug()<<"no fullscreen support compiled in.";
#endif
}

bool
MainWindow::eventFilter(QObject *o, QEvent *e)
{
    if (o == this) {
        if (e->type() == QEvent::WindowStateChange) {

            // if we are entering full screen mode we hide the sidebar
            if (windowState()&Qt::WindowFullScreen) showViewbar(false);
            else showViewbar(true);

            resizeEvent(NULL); // see below
        }
    }
    return false;
}

void
MainWindow::resizeEvent(QResizeEvent*)
{
    //appsettings->setValue(GC_SETTINGS_MAIN_GEOM, saveGeometry());
    //appsettings->setValue(GC_SETTINGS_MAIN_STATE, saveState());

}

void
MainWindow::showOptions()
{
    // Create a new config dialog only if it doesn't exist
    ConfigDialog *cd = configdialog_ptr ? configdialog_ptr
                                        : new ConfigDialog(currentAthleteTab->context->athlete->home->root(), currentAthleteTab->context);

    // move to the centre of the screen
    cd->move(geometry().center()-QPoint(cd->geometry().width()/2, cd->geometry().height()/2));
    cd->show();
    cd->raise();
}

void
MainWindow::moveEvent(QMoveEvent*)
{
    appsettings->setValue(GC_SETTINGS_MAIN_GEOM, saveGeometry());
}

void
MainWindow::closeEvent(QCloseEvent* event)
{
    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
    QList<AthleteTab*> closing = tabList;
    bool needtosave = false;
    bool importrunning = false;

    // close all the tabs .. if any refuse we need to ignore
    //                       the close event
    foreach(AthleteTab *tab, closing) {

        // check for if RideImport is is process and let it finalize / or be stopped by the user
        if (tab->context->athlete->autoImport) {
            if (tab->context->athlete->autoImport->importInProcess() ) {
                importrunning = true;
                QGuiApplication::restoreOverrideCursor();
                QMessageBox::information(this, tr("Activity Import"), tr("Closing of athlete window not possible while background activity import is in progress..."));
                QGuiApplication::setOverrideCursor(Qt::WaitCursor);
            }
        }

        // only check for unsaved if autoimport is not running any more
        if (!importrunning) {
            // do we need to save?
            if (tab->context->mainWindow->saveRideExitDialog(tab->context) == true)
                removeAthleteTab(tab);
            else
                needtosave = true;
        }
    }

    // were any left hanging around? or autoimport in action on any windows, then don't close any
    if (needtosave || importrunning) 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());
        // wait for threads.. max of 10 seconds before just exiting anyway
        for (int i=0; i<10 && QThreadPool::globalInstance()->activeThreadCount(); i++) {
            QThread::sleep(1);
        }
    }
    appsettings->setValue(GC_SETTINGS_MAIN_GEOM, saveGeometry());
    appsettings->setValue(GC_SETTINGS_MAIN_STATE, saveState());
    QGuiApplication::restoreOverrideCursor();
}

MainWindow::~MainWindow()
{
    // aside from the tabs, we may need to clean
    // up any dangling widgets created in MainWindow::MainWindow (?)
    if (configdialog_ptr) configdialog_ptr->close();
}

// global search/data filter
void MainWindow::setFilter(QStringList f) { currentAthleteTab->context->setFilter(f); }
void MainWindow::clearFilter() { currentAthleteTab->context->clearFilter(); }

void
MainWindow::aboutDialog()
{
    AboutDialog *ad = new AboutDialog(currentAthleteTab->context);
    ad->exec();
}

void MainWindow::showSolveCP()
{
   SolveCPDialog *td = new SolveCPDialog(this, currentAthleteTab->context);
   td->show();
}

void MainWindow::showEstimateCP()
{
   EstimateCPDialog *td = new EstimateCPDialog();
   td->show();
}

void MainWindow::showRhoEstimator()
{
   ToolsRhoEstimator *tre = new ToolsRhoEstimator(currentAthleteTab->context);
   tre->show();
}

void MainWindow::showVDOTCalculator()
{
   VDOTCalculator *VDOTcalculator = new VDOTCalculator();
   VDOTcalculator->show();
}

void MainWindow::showWorkoutWizard()
{
   WorkoutWizard *ww = new WorkoutWizard(currentAthleteTab->context);
   ww->show();
}

void MainWindow::resetWindowLayout()
{
    QMessageBox msgBox;
    msgBox.setText(tr("You are about to reset all charts to the default setup"));
    msgBox.setInformativeText(tr("Do you want to continue?"));
    msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Cancel);
    msgBox.setIcon(QMessageBox::Warning);
    msgBox.exec();

    if(msgBox.clickedButton() == msgBox.button(QMessageBox::Ok))
        currentAthleteTab->resetLayout(perspectiveSelector);
}

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*)currentAthleteTab->context->currentRideItem();
    if (rideitem) {
        ManualDataProcessorDialog *p = new ManualDataProcessorDialog(currentAthleteTab->context, name, rideitem);
        p->setWindowModality(Qt::ApplicationModal); // don't allow select other ride or it all goes wrong!
        p->exec();
    }
}


void
MainWindow::helpWindow()
{
    HelpWindow* help = new HelpWindow(currentAthleteTab->context);
    help->show();
}


void
MainWindow::logBug()
{
    QDesktopServices::openUrl(QUrl("https://github.com/GoldenCheetah/GoldenCheetah/issues"));
}

void
MainWindow::helpView()
{
    QDesktopServices::openUrl(QUrl("https://github.com/GoldenCheetah/GoldenCheetah/wiki"));
}


void
MainWindow::support()
{
    QDesktopServices::openUrl(QUrl("https://groups.google.com/forum/#!forum/golden-cheetah-users"));
}

void
MainWindow::sidebarClicked(GcSideBarBtnId id)
{
    switch (id) {
    case GcSideBarBtnId::SYNC_BTN: checkCloud(); break; // sync quick link
    case GcSideBarBtnId::OPTIONS_BTN: showOptions(); break; // prefs
    }
}

void
MainWindow::sidebarSelected(GcSideBarBtnId id)
{
    switch (id) {
    case GcSideBarBtnId::SELECT_ATHLETE_BTN: selectAthlete(); break;
    case GcSideBarBtnId::PLAN_BTN: break; // plan not written yet
    case GcSideBarBtnId::TRENDS_BTN: selectTrends(); break;
    case GcSideBarBtnId::ACTIVITIES_BTN: selectAnalysis(); break;
    case GcSideBarBtnId::REFLECT_BTN: break; // reflect not written yet
    case GcSideBarBtnId::TRAIN_BTN: selectTrain(); break;
    case GcSideBarBtnId::APPS_BTN: break;// apps not written yet
    }
}

void
MainWindow::selectAthlete()
{
    viewStack->setCurrentIndex(GcViewStackIdx::SELECT_ATHLETE_VIEW);
    back->hide();
    forward->hide();
    perspectiveSelector->hide();
    searchBox->hide();
    workoutFilterBox->hide();
}

void
MainWindow::selectAnalysis()
{
    resetPerspective(1);
    //currentTab->analysisView->setPerspectives(perspectiveSelector);
    viewStack->setCurrentIndex(GcViewStackIdx::ATHLETE_TAB_STACK);
    sidebar->setItemSelected(GcSideBarBtnId::ACTIVITIES_BTN, true);
    currentAthleteTab->selectView(1);
    back->show();
    forward->show();
    perspectiveSelector->show();
    searchBox->show();
    workoutFilterBox->hide();
    setToolButtons();
}

void
MainWindow::selectTrain()
{
    resetPerspective(3);
    //currentTab->trainView->setPerspectives(perspectiveSelector);
    viewStack->setCurrentIndex(GcViewStackIdx::ATHLETE_TAB_STACK);
    sidebar->setItemSelected(GcSideBarBtnId::TRAIN_BTN, true);
    currentAthleteTab->selectView(3);
    back->show();
    forward->show();
    perspectiveSelector->show();
    searchBox->hide();
    workoutFilterBox->show();
    setToolButtons();
}

void
MainWindow::selectDiary()
{
    resetPerspective(2);
    //currentTab->diaryView->setPerspectives(perspectiveSelector);
    viewStack->setCurrentIndex(GcViewStackIdx::ATHLETE_TAB_STACK);
    currentAthleteTab->selectView(2);
    back->show();
    forward->show();
    perspectiveSelector->show();
    searchBox->show();
    workoutFilterBox->hide();
    setToolButtons();
}

void
MainWindow::selectTrends()
{
    resetPerspective(0);
    //currentTab->homeView->setPerspectives(perspectiveSelector);
    viewStack->setCurrentIndex(GcViewStackIdx::ATHLETE_TAB_STACK);
    sidebar->setItemSelected(GcSideBarBtnId::TRENDS_BTN, true);
    currentAthleteTab->selectView(0);
    back->show();
    forward->show();
    perspectiveSelector->show();
    searchBox->show();
    workoutFilterBox->hide();
    setToolButtons();
}


bool
MainWindow::isStarting
() const
{
    return splash != nullptr;
}


void
MainWindow::setToolButtons()
{
    int select = currentAthleteTab->isTiled() ? 1 : 0;
    int lowselected = currentAthleteTab->isBottomRequested() ? 1 : 0;

    styleAction->setChecked(select);
    showhideLowbar->setChecked(lowselected);

    //if (styleSelector->isSegmentSelected(select) == false)
        //styleSelector->setSegmentSelected(select, true);

    int index = currentAthleteTab->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
#ifdef Q_OS_MAC // bizarre issue with searchbox focus on tab voew change
    searchBox->clearFocus();
#endif
}

void
MainWindow::switchPerspective(int index)
{
    if (pactive) return;

    if (index >=0 && index < perspectiveSelector->count())
        perspectiveSelector->setCurrentIndex(index);
}

void
MainWindow::resetPerspective(int view, bool force)
{
    static AthleteTab *lastathlete=NULL;
    static int lastview=-1;

    if (!force && lastview == view && lastathlete == currentAthleteTab) return;

    // remember who last updated it.
    lastathlete = currentAthleteTab;
    lastview = view;

    // don't argue just reset the perspective for this view
    AbstractView *current = NULL;
    switch (view) {

    case 0:  current = currentAthleteTab->homeView; break;
    case 1:  current = currentAthleteTab->analysisView; break;
    case 2:  current = currentAthleteTab->diaryView; break;
    case 3:  current = currentAthleteTab->trainView; break;
    }

    // set the perspective
    pactive=true;
    current->setPerspectives(perspectiveSelector);
    perspectiveSelector->setCurrentIndex(current->currentPerspective());
    pactive=false;
}

void
MainWindow::perspectiveSelected(int index)
{
    if (pactive) return;

    // set the perspective for the current view
    int view = currentAthleteTab->currentView();
    AbstractView *current = NULL;
    switch (view) {
    case 0:  current = currentAthleteTab->homeView; break;
    case 1:  current = currentAthleteTab->analysisView; break;
    case 2:  current = currentAthleteTab->diaryView; break;
    case 3:  current = currentAthleteTab->trainView; break;
    }

    // which perspective is currently being shown?
    int prior = current->perspectives_.indexOf(current->perspective_);

    if (index < current->perspectives_.count()) {

        // a perspectives was selected
        switch (view) {
        case 0:  current->perspectiveSelected(index); break;
        case 1:  current->perspectiveSelected(index); break;
        case 2:  current->perspectiveSelected(index); break;
        case 3:  current->perspectiveSelected(index); break;
        }

    } else {

        // manage or add perspectives selected
        pactive = true;

        // set the combo back to where it was
        perspectiveSelector->setCurrentIndex(prior);

        // now open dialog etc
        switch (index - current->perspectives_.count()) {
        case 1 : // add perspectives
            {
                QString name;
                QString expression;
                Perspective::switchenum trainswitch=Perspective::None;
                AddPerspectiveDialog *dialog= new AddPerspectiveDialog(this, currentAthleteTab->context, name, expression, current->type, trainswitch);
                int ret= dialog->exec();
                delete dialog;
                if (ret == QDialog::Accepted && name != "") {

                    // add...
                    Perspective *newone = current->addPerspective(name);
                    newone->setExpression(expression);
                    newone->setTrainSwitch(trainswitch);
                    current->setPerspectives(perspectiveSelector);

                    // and select remember pactive is true, so we do the heavy lifting here
                    perspectiveSelector->setCurrentIndex(current->perspectives_.count()-1);
                    current->perspectiveSelected(perspectiveSelector->currentIndex());
                }
            }
            break;
        case 2 : // manage perspectives
            PerspectiveDialog *dialog = new PerspectiveDialog(this, current);
            connect(dialog, SIGNAL(perspectivesChanged()), this, SLOT(perspectivesChanged())); // update the selector and view
            dialog->exec();
            break;
        }
        pactive = false;
    }
}

// manage perspectives has done something (remove/add/reorder perspectives)
// pactive MUST be true, see above
void
MainWindow::perspectivesChanged()
{
    int view = currentAthleteTab->currentView();
    AbstractView *current = NULL;

    switch (view) {
    case 0:  current = currentAthleteTab->homeView; break;
    case 1:  current = currentAthleteTab->analysisView; break;
    case 2:  current = currentAthleteTab->diaryView; break;
    case 3:  current = currentAthleteTab->trainView; break;
    }

    // which perspective is currently being selected (before we go setting the combobox)
    Perspective *prior = current->perspective_;

    // ok, so reset the combobox and force, since whilst it may have already
    // been set for this athlete+view combination the config was just changed
    // so it needs to be redone.
    resetPerspective(view, true);
    //current->setPerspectives(perspectiveSelector);

    // is the old selected perspective still available?
    int index = current->perspectives_.indexOf(prior);

    // pretend a selection was made if the index needs to change
    if (index >= 0 ) {
        // still exists, but not currently selected for some reason
        if (perspectiveSelector->currentIndex() != index)
            perspectiveSelector->setCurrentIndex(index);

        // no need to signal as its currently being shown
    } else {

        pactive = false; // dialog is active, but we need to force a change

        // need to choose first as current got deleted
        if (perspectiveSelector->currentIndex() != 0)
            perspectiveSelector->setCurrentIndex(0);
        else
            emit perspectiveSelector->currentIndexChanged(0);

        pactive = true;
    }
}

/*----------------------------------------------------------------------
 * 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;

    // is this a chart file ?
    QStringList filenames;
    QList<LTMSettings> imported;
    QStringList list, workouts, images;
    for(int i=0; i<urls.count(); i++) {

        QString filename = QFileInfo(urls.value(i).toLocalFile()).absoluteFilePath();
        //fprintf(stderr, "%s\n", filename.toStdString().c_str()); fflush(stderr);

        if (filename.endsWith(".gchart", Qt::CaseInsensitive)) {
            // add to the list of charts to import
            list << filename;

        } else if (filename.endsWith(".xml", Qt::CaseInsensitive)) {

            QFile chartsFile(filename);

            // setup XML processor
            QXmlInputSource source( &chartsFile );
            QXmlSimpleReader xmlReader;
            LTMChartParser handler;
            xmlReader.setContentHandler(&handler);
            xmlReader.setErrorHandler(&handler);

            // parse and get return values
            xmlReader.parse(source);
            imported += handler.getSettings();

        } else if (Utils::isImage(filename)) {
            images << filename;

        // Look for Workout files only in Train view
        } else if (currentAthleteTab->currentView() == 3 && ErgFile::isWorkout(filename)) {
            workouts << filename;
        } else {
            filenames.append(filename);
        }
    }

    // import any we may have extracted
    if (imported.count()) {

        // now append to the QList and QTreeWidget
        currentAthleteTab->context->athlete->presets += imported;

        // notify we changed and tree updates
        currentAthleteTab->context->notifyPresetsChanged();

        // tell the user
        QMessageBox::information(this, tr("Chart Import"), QString(tr("Imported %1 metric charts")).arg(imported.count()));

        // switch to trend view if we aren't on it
        selectTrends();

        // now select what was added
        currentAthleteTab->context->notifyPresetSelected(currentAthleteTab->context->athlete->presets.count()-1);
    }

    // are there any .gcharts to import?
    if (list.count())  importCharts(list);

    // import workouts
    if (workouts.count()) Library::importFiles(currentAthleteTab->context, workouts, LibraryBatchImportConfirmation::forcedDialog);

    // import images (these will be attached to the current ride)
    if (images.count()) importImages(images);

    // if there is anything left, process based upon view...
    if (filenames.count()) {

        // We have something to process then
        RideImportWizard *dialog = new RideImportWizard (filenames, currentAthleteTab->context);
        dialog->process(); // do it!
    }
    return;
}

void
MainWindow::importImages(QStringList list)
{
    // we need to be on activities view and with a current
    // ride otherwise we just ignore the list
    if (currentAthleteTab->currentView() != 1 || currentAthleteTab->context->ride == NULL) {
        QMessageBox::critical(this, tr("Import Images Failed"), tr("You can only import images on the activities view with an activity selected."));
        return;
    }

    // lets import them
    int count = currentAthleteTab->context->ride->importImages(list);
    QMessageBox::information(this, tr("Import Images to Activity"), QString(tr("%1 images imported.")).arg(count));
}

void
MainWindow::importCharts(QStringList list)
{
    QList<QMap<QString,QString> > charts;

    // parse charts into property pairs
    foreach(QString filename, list) {
        charts << GcChartWindow::chartPropertiesFromFile(filename);
    }

    // And import them with a dialog to select location
    ImportChartDialog importer(currentAthleteTab->context, charts, this);
    importer.exec();
}


/*----------------------------------------------------------------------
 * Ride Library Functions
 *--------------------------------------------------------------------*/


void
MainWindow::downloadRide()
{
    (new DownloadRideDialog(currentAthleteTab->context))->show();
}


void
MainWindow::manualRide()
{
    ManualActivityWizard wizard(currentAthleteTab->context);
    wizard.exec();
}


void
MainWindow::planActivity()
{
    ManualActivityWizard wizard(currentAthleteTab->context, true);
    wizard.exec();
}

void
MainWindow::batchProcessing()
{
    BatchProcessingDialog *d = new BatchProcessingDialog(currentAthleteTab->context);
    d->exec();
}

void
MainWindow::generateHeatMap()
{
    GenerateHeatMapDialog *d = new GenerateHeatMapDialog(currentAthleteTab->context);
    d->exec();
}

void
MainWindow::exportRide()
{
    if (currentAthleteTab->context->ride == NULL) {
        QMessageBox::critical(this, tr("Select Activity"), tr("No activity selected!"));
        return;
    }

    // what format?
    const RideFileFactory &rff = RideFileFactory::instance();
    QStringList allFormats;
    allFormats << "Export all data (*.csv)";
    allFormats << "Export W' balance (*.csv)";
    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);
    RideFile *currentRide = currentAthleteTab->context->ride ? currentAthleteTab->context->ride->ride() : NULL;
    bool result=false;

    // Extract suffix from chosen file name and convert to lower case
    QString fileNameSuffix;
    int lastDot = fileName.lastIndexOf(".");
    if (lastDot >=0) fileNameSuffix = fileName.mid(fileName.lastIndexOf(".")+1);
    fileNameSuffix = fileNameSuffix.toLower();

    // See if this suffix can be used to determine file type (not ok for csv since there are options)
    bool useFileTypeSuffix = false;
    if (!fileNameSuffix.isEmpty() && fileNameSuffix != "csv")
    {
        useFileTypeSuffix = rff.writeSuffixes().contains(fileNameSuffix);
    }

    if (useFileTypeSuffix)
    {
        result = RideFileFactory::instance().writeRideFile(currentAthleteTab->context, currentRide, file, fileNameSuffix);
    }
    else
    {
        // Use the value of drop down list to determine file type
        int idx = allFormats.indexOf(getSuffix.cap(0));

        if (idx>1) {

            result = RideFileFactory::instance().writeRideFile(currentAthleteTab->context, currentRide, file, getSuffix.cap(1));

        } else if (idx==0){

            CsvFileReader writer;
            result = writer.writeRideFile(currentAthleteTab->context, currentRide, file, CsvFileReader::gc);

        } else if (idx==1){

            CsvFileReader writer;
            result = writer.writeRideFile(currentAthleteTab->context, currentRide, file, CsvFileReader::wprime);

        }
    }

    if (result == false) {
        QMessageBox oops(QMessageBox::Critical, tr("Export Failed"),
                         tr("Failed to export activity, 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(QRegularExpression("^"), "*.");
    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, currentAthleteTab->context);
        import->process();
    }
}

void
MainWindow::saveRide()
{
    // no ride
    if (currentAthleteTab->context->ride == NULL) {
        QMessageBox oops(QMessageBox::Critical, tr("No Activity To Save"),
                         tr("There is no currently selected activity to save."));
        oops.exec();
        return;
    }

    // flush in-flight changes
    currentAthleteTab->context->notifyMetadataFlush();
    currentAthleteTab->context->ride->notifyRideMetadataChanged();

    // nothing to do if not dirty
    //XXX FORCE A SAVE if (currentTab->context->ride->isDirty() == false) return;

    // save
    if (currentAthleteTab->context->ride) {
        saveRideSingleDialog(currentAthleteTab->context, currentAthleteTab->context->ride); // will signal save to everyone
    }
}

void
MainWindow::athleteSettings()
{
    AthleteConfigDialog *dialog = new AthleteConfigDialog(currentAthleteTab->context->athlete->home->root(), currentAthleteTab->context);
    dialog->exec();
}

void
MainWindow::saveAllUnsavedRides()
{
    // flush in-flight changes
    currentAthleteTab->context->notifyMetadataFlush();
    currentAthleteTab->context->ride->notifyRideMetadataChanged();

    // save
    if (currentAthleteTab->context->ride) {
        saveAllFilesSilent(currentAthleteTab->context); // will signal save to everyone
    }
}

void
MainWindow::revertRide()
{
    currentAthleteTab->context->ride->close();
    currentAthleteTab->context->ride->ride(); // force re-load

    // in case reverted ride has different starttime
    currentAthleteTab->context->ride->setStartTime(currentAthleteTab->context->ride->ride()->startTime());
    currentAthleteTab->context->ride->ride()->emitReverted();

    // and notify everyone we changed which also has the side
    // effect of updating the cached values too
    currentAthleteTab->context->notifyRideSelected(currentAthleteTab->context->ride);
}

void
MainWindow::splitRide()
{
    if (currentAthleteTab->context->ride && currentAthleteTab->context->ride->ride() && currentAthleteTab->context->ride->ride()->dataPoints().count()) (new SplitActivityWizard(currentAthleteTab->context))->exec();
    else {
        if (!currentAthleteTab->context->ride || !currentAthleteTab->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 (currentAthleteTab->context->ride && currentAthleteTab->context->ride->ride() && currentAthleteTab->context->ride->ride()->dataPoints().count()) (new MergeActivityWizard(currentAthleteTab->context))->exec();
    else {
        if (!currentAthleteTab->context->ride || !currentAthleteTab->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()
{
    RideItem *_item = currentAthleteTab->context->ride;

    if (_item==NULL) {
        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)
        currentAthleteTab->context->athlete->removeCurrentRide();
}

/*----------------------------------------------------------------------
 * Realtime Devices and Workouts
 *--------------------------------------------------------------------*/
void
MainWindow::addDevice()
{

    // lets get a new one
    AddDeviceWizard *p = new AddDeviceWizard(currentAthleteTab->context);
    p->show();

}

/*----------------------------------------------------------------------
 * Cyclists
 *--------------------------------------------------------------------*/

void
MainWindow::newCyclistTab()
{
    QDir newHome = currentAthleteTab->context->athlete->home->root();
    newHome.cdUp();
    QString name = ChooseCyclistDialog::newAthleteWizard(newHome);
    if (!name.isEmpty()) {
        emit newAthlete(name);
        openAthleteTab(name);
    }
}

void
MainWindow::closeWindow()
{
    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
    // just call close, we might do more later
    appsettings->syncQSettings();
    QGuiApplication::restoreOverrideCursor();
    close();
}

void
MainWindow::openAthleteTab(QString name)
{
    QDir home(gcroot);
    appsettings->initializeQSettingsGlobal(gcroot);
    home.cd(name);

    if (!home.exists()) return;
    appsettings->initializeQSettingsAthlete(gcroot, name);

    GcUpgrade v3;
    if (!v3.upgradeConfirmedByUser(home)) return;

    // save how we are
    saveGCState(currentAthleteTab->context);

    Context *con= new Context(this);
    con->athlete = NULL;
    emit openingAthlete(name, con);

    connect(con, SIGNAL(loadCompleted(QString,Context*)), this, SLOT(loadCompleted(QString, Context*)));

    // will emit loadCompleted when done
    con->athlete = new Athlete(con, home);
}

void
MainWindow::loadCompleted(QString name, Context *context)
{
    // athlete loaded
    currentAthleteTab = new AthleteTab(context);

    // clear splash - progress whilst loading tab
    //clearSplash();

    // first tab
    athletetabs.insert(currentAthleteTab->context->athlete->home->root().dirName(), currentAthleteTab);

    // stack, list and bar all share a common index
    tabList.append(currentAthleteTab);
    tabbar->addTab(currentAthleteTab->context->athlete->home->root().dirName());
    tabStack->addWidget(currentAthleteTab);

    // switch to newly created athlete
    tabbar->setCurrentIndex(tabList.count()-1);

    // 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);

    // clear the workout filter box text
    workoutFilterBox->clear();

    // tell everyone
    currentAthleteTab->context->notifyLoadDone(name, context);

    // now do the automatic ride file import
    context->athlete->importFilesWhenOpeningAthlete();
}

void
MainWindow::closeTabClicked(int index)
{

    AthleteTab *tab = tabList[index];

    // check for autoimport and let it finalize
    if (tab->context->athlete->autoImport) {
        if (tab->context->athlete->autoImport->importInProcess() ) {
            QMessageBox::information(this, tr("Activity Import"), tr("Closing of athlete window not possible while background activity import is in progress..."));
            return;
        }
    }

    if (saveRideExitDialog(tab->context) == false) return;

    // lets wipe it
    removeAthleteTab(tab);
}

bool
MainWindow::closeAthleteTab(QString name)
{
    for(int i=0; i<tabbar->count(); i++) {
        if (name == tabbar->tabText(i)) {
            closeTabClicked(i);
            return true;
        }
    }
    return false;
}

bool
MainWindow::closeAthleteTab()
{
  // check for autoimport and let it finalize
    if (currentAthleteTab->context->athlete->autoImport) {
        if (currentAthleteTab->context->athlete->autoImport->importInProcess() ) {
            QMessageBox::information(this, tr("Activity Import"), tr("Closing of athlete window not possible while background activity import is in progress..."));
            return false;
        }
    }

    // wipe it down ...
    if (saveRideExitDialog(currentAthleteTab->context) == false) return false;

    // if its the last tab we close the window
    if (tabList.count() == 1)
        closeWindow();
    else {
        removeAthleteTab(currentAthleteTab);
    }
    appsettings->syncQSettings();
    // we did it
    return true;
}

// no questions asked just wipe away the current tab
void
MainWindow::removeAthleteTab(AthleteTab *tab)
{
    setUpdatesEnabled(false);

    if (tabList.count() == 2) showTabbar(false); // don't need it for one!

    // cancel ridecache refresh if its in progress
    tab->context->athlete->rideCache->cancel();

    // save the named searches
    tab->context->athlete->namedSearches->write();

    // 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) switchAthleteTab(index-1);
        else switchAthleteTab(index+1);
    }

    // close gracefully
    tab->close();
    tab->context->athlete->close();

    // remove from state
    athletetabs.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::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();
        SKIP_QTWE_CACHE  // skip Folder Names created by QTWebEngine on Windows
        if (name.startsWith(".")) continue; // ignore dot folders

        // new action
        QAction *action = new QAction(QString("%1").arg(name), this);

        // get the config directory
        AthleteDirectoryStructure subDirs(name);
        // icon / mugshot ?
        QString icon = QString("%1/%2/%3/avatar.png").arg(gcroot).arg(name).arg(subDirs.config().dirName());
        if (QFile(icon).exists()) action->setIcon(QIcon(icon));

        // only allow selection of cyclists which are not already open
        foreach (MainWindow *x, mainwindows) {
            QMapIterator<QString, AthleteTab*> t(x->athletetabs);
            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()), QKeySequence("Ctrl+N"));
}

void
MainWindow::setBackupAthleteMenu()
{
    // wipe existing
    backupAthleteMenu->clear();

    // get a list of all cyclists
    QStringListIterator i(QDir(gcroot).entryList(QDir::Dirs | QDir::NoDotAndDotDot));
    while (i.hasNext()) {

        QString name = i.next();
        SKIP_QTWE_CACHE  // skip Folder Names created by QTWebEngine on Windows
        if (name.startsWith(".")) continue; // ignore dot folders

        // new action
        QAction *action = new QAction(QString("%1").arg(name), this);

        // get the config directory
        AthleteDirectoryStructure subDirs(name);
        // icon / mugshot ?
        QString icon = QString("%1/%2/%3/avatar.png").arg(gcroot).arg(name).arg(subDirs.config().dirName());
        if (QFile(icon).exists()) action->setIcon(QIcon(icon));

        // add to menu
        backupAthleteMenu->addAction(action);
        connect(action, SIGNAL(triggered()), backupMapper, SLOT(map()));
        backupMapper->setMapping(action, name);
    }

}

void
MainWindow::backupAthlete(QString name)
{
    AthleteBackup *backup = new AthleteBackup(QDir(gcroot+"/"+name));
    backup->backupImmediate();
    delete backup;
}

void
MainWindow::setDeleteAthleteMenu()
{
    // wipe existing
    deleteAthleteMenu->clear();

    // get a list of all cyclists
    QStringListIterator i(QDir(gcroot).entryList(QDir::Dirs | QDir::NoDotAndDotDot));
    while (i.hasNext()) {

        QString name = i.next();
        SKIP_QTWE_CACHE  // skip Folder Names created by QTWebEngine on Windows
        if (name.startsWith(".")) continue; // ignore dot folders

        // new action
        QAction *action = new QAction(QString("%1").arg(name), this);

        // get the config directory
        AthleteDirectoryStructure subDirs(name);
        // icon / mugshot ?
        QString icon = QString("%1/%2/%3/avatar.png").arg(gcroot).arg(name).arg(subDirs.config().dirName());
        if (QFile(icon).exists()) action->setIcon(QIcon(icon));

        // only allow selection of cyclists which are not already open
        foreach (MainWindow *x, mainwindows) {
            QMapIterator<QString, AthleteTab*> t(x->athletetabs);
            while (t.hasNext()) {
                t.next();
                if (t.key() == name)
                    action->setEnabled(false);
            }
        }

        // add to menu
        deleteAthleteMenu->addAction(action);
        connect(action, SIGNAL(triggered()), deleteMapper, SLOT(map()));
        deleteMapper->setMapping(action, name);
    }
}

void
MainWindow::deleteAthlete(QString name)
{
    QDir home(gcroot);
    if(ChooseCyclistDialog::deleteAthlete(home, name, this)) {
        // notify deletion
        emit deletedAthlete(name);
    }
}

void
MainWindow::saveGCState(Context *context)
{
    // save all the current state to the supplied context
    context->showSidebar = showhideSidebar->isChecked();
    //context->showTabbar = showhideTabbar->isChecked();
    context->showLowbar = showhideLowbar->isChecked();
    context->showToolbar = showhideToolbar->isChecked();
    context->searchText = searchBox->text();
    context->workoutFilterText = workoutFilterBox->text();
    context->style = styleAction->isChecked();
}

void
MainWindow::restoreGCState(Context *context)
{
    if (viewStack->currentIndex() != GcViewStackIdx::SELECT_ATHLETE_VIEW) {

        // not on athlete view...
        resetPerspective(currentAthleteTab->currentView()); // will lazy load, hence doing it first

        // restore window state from the supplied context
            switch(currentAthleteTab->currentView()) {
            case 0: sidebar->setItemSelected(GcSideBarBtnId::TRENDS_BTN,true); break;
            case 1: sidebar->setItemSelected(GcSideBarBtnId::ACTIVITIES_BTN,true); break;
            case 2: break; // diary not an icon
            case 3: sidebar->setItemSelected(GcSideBarBtnId::TRAIN_BTN, true); break;
            default: sidebar->setItemSelected(GcSideBarBtnId::SELECT_ATHLETE_BTN, true); break;
        }
    }

    showSidebar(context->showSidebar);
    showToolbar(context->showToolbar);
    //showTabbar(context->showTabbar);
    showLowbar(context->showLowbar);
    searchBox->setContext(context);
    searchBox->setText(context->searchText);
    workoutFilterBox->setContext(context);
    workoutFilterBox->setText(context->workoutFilterText);
}

void
MainWindow::switchAthleteTab(int index)
{
    if (index < 0) return;

    setUpdatesEnabled(false);

#if 0 // use athlete view, these buttons don't exist
#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
#endif

    // save how we are
    saveGCState(currentAthleteTab->context);

    currentAthleteTab = tabList[index];
    tabStack->setCurrentIndex(index);

    // restore back
    restoreGCState(currentAthleteTab->context);

    setWindowTitle(currentAthleteTab->context->athlete->home->root().dirName());


    setUpdatesEnabled(true);
}


/*----------------------------------------------------------------------
 * MetricDB
 *--------------------------------------------------------------------*/

void
MainWindow::exportMetrics()
{
    // if the refresh process is running, try again when its completed
    if (currentAthleteTab->context->athlete->rideCache->isRunning()) {
        QMessageBox::warning(this, tr("Refresh in Progress"),
        "A metric refresh is currently running, please try again once that has completed.");
        return;
    }

    // all good lets choose a file
    QString fileName = QFileDialog::getSaveFileName( this, tr("Export Metrics"), QDir::homePath(), tr("Comma Separated Variables (*.csv)"));
    if (fileName.length() == 0) return;

    // export
    currentAthleteTab->context->athlete->rideCache->writeAsCSV(fileName);
}

/*----------------------------------------------------------------------
 * 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(currentAthleteTab->context, fileNamesCopy);
    }
}

void
MainWindow::clearWorkoutFilterBox()
{
    workoutFilterBox->clear();
}

void
MainWindow::fillinWorkoutFilterBox(const QString &filterText)
{
    workoutFilterBox->setText(filterText);
}

/*----------------------------------------------------------------------
 * TrainerDay
 *--------------------------------------------------------------------*/

void
MainWindow::downloadTrainerDay()
{
    QString workoutDir = appsettings->value(this, GC_WORKOUTDIR).toString();

    QFileInfo fi(workoutDir);

    if (fi.exists() && fi.isDir()) {
        TrainerDayDownloadDialog *d = new TrainerDayDownloadDialog(currentAthleteTab->context);
        d->exec();
    } else{
        QMessageBox::critical(this, tr("Workout Directory Invalid"),
        tr("The workout directory is not configured, or the directory selected no longer exists.\n\n"
        "Please check your preference settings."));
    }
}

/*----------------------------------------------------------------------
 * Strava Routes as Workouts
 *--------------------------------------------------------------------*/

void
MainWindow::downloadStravaRoutes()
{
    QString workoutDir = appsettings->value(this, GC_WORKOUTDIR).toString();

    QFileInfo fi(workoutDir);

    if (fi.exists() && fi.isDir()) {
        StravaRoutesDownload *d = new StravaRoutesDownload(currentAthleteTab->context);
        d->exec();
    } else{
        QMessageBox::critical(this, tr("Workout Directory Invalid"),
        tr("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(currentAthleteTab->context);
    search->exec();
}

/*----------------------------------------------------------------------------
 * Working with Cloud Services
 * --------------------------------------------------------------------------*/

void
MainWindow::addAccount()
{
    // lets get a new cloud service account
    AddCloudWizard *p = new AddCloudWizard(currentAthleteTab->context);
    p->show();
}

void
MainWindow::checkCloud()
{
    // kick off a check
    currentAthleteTab->context->athlete->cloudAutoDownload->checkDownload();

    // and auto import too whilst we're at it
    currentAthleteTab->context->athlete->importFilesWhenOpeningAthlete();
}

void
MainWindow::importCloud()
{
    // lets get a new cloud service account
    AddCloudWizard *p = new AddCloudWizard(currentAthleteTab->context, "", true);
    p->show();
}

void
MainWindow::uploadCloud(QAction *action)
{
    // upload current ride, if we have one
    if (currentAthleteTab->context->ride) {
        // & removed to avoid issues with kde AutoCheckAccelerators
        QString actionText = QString(action->text()).replace("&", "");

        CloudService *db = CloudServiceFactory::instance().newService(action->data().toString(), currentAthleteTab->context);
        CloudService::upload(this, currentAthleteTab->context, db, currentAthleteTab->context->ride);
    }
}

void
MainWindow::syncCloud(QAction *action)
{
    // sync with cloud
    CloudService *db = CloudServiceFactory::instance().newService(action->data().toString(), currentAthleteTab->context);
    CloudServiceSyncDialog sync(currentAthleteTab->context, db);
    sync.exec();
}


/*----------------------------------------------------------------------
 * Utility
 *--------------------------------------------------------------------*/

/*----------------------------------------------------------------------
 * Notifiers - application level events
 *--------------------------------------------------------------------*/

void
MainWindow::configChanged(qint32)
{
    // Windows and Linux menu bar should match chrome
    QColor textCol(Qt::black);
    if (GCColor::luminance(GColor(CTOOLBAR)) < 127)  textCol = QColor(Qt::white);
    QString menuColorString = GColor(CTOOLBAR).name();
    menuBar()->setStyleSheet(QString("QMenuBar { color: %1; background: %2; }"
                             "QMenuBar::item { color: %1; background: %2; }")
                             .arg(textCol.name()).arg(menuColorString));
    // search filter box match chrome color
    searchBox->setStyleSheet(QString("QLineEdit { background: %1; color: %2; }").arg(GColor(CTOOLBAR).name()).arg(GCColor::invertColor(GColor(CTOOLBAR)).name()));

    // perspective selector mimics sidebar colors
    QColor selected;
    if (GCColor::invertColor(GColor(CTOOLBAR)) == Qt::white) selected = QColor(Qt::lightGray);
    else selected = QColor(Qt::darkGray);
    perspectiveSelector->setStyleSheet(QString("QComboBox { background: %1; color: %2; }"
                                               "QComboBox::item { background: %1; color: %2; }"
                                               "QComboBox::item::selected { background: %3; color: %1; }").arg(GColor(CTOOLBAR).name()).arg(GCColor::invertColor(GColor(CTOOLBAR)).name()).arg(selected.name()));

    QString buttonstyle = QString("QPushButton { border: none; border-radius: %2px; background-color: %1; "
                                                "padding-left: 0px; padding-right: 0px; "
                                                "padding-top:  0px; padding-bottom: 0px; }"
                                  "QPushButton:hover { background-color: %3; }"
                                  "QPushButton:hover:pressed { background-color: %3; }"
                                ).arg(GColor(CTOOLBAR).name()).arg(3 * dpiXFactor).arg(GColor(CHOVER).name());

    back->setStyleSheet(buttonstyle);
    forward->setStyleSheet(buttonstyle);
    sidelist->setStyleSheet(buttonstyle);
    tabtile->setStyleSheet(buttonstyle);
    lowbar->setStyleSheet(buttonstyle);
    whatsthis->setStyleSheet(buttonstyle);

    // All platforms
    tabbar->setAutoFillBackground(true);
    tabbar->setShape(QTabBar::RoundedSouth);
    tabbar->setDrawBase(false);

    // on select
    QColor bg_select = GCColor::selectedColor(GColor(CTOOLBAR));
    QColor fg_select = GCColor::invertColor(bg_select);

    tabbar->setStyleSheet(QString("QTabBar::tab { background-color: %1; color: %2;}"
        "QTabBar::tab::selected { background-color: %3; color: %4; }").arg(GColor(CTOOLBAR).name())
                                                                        .arg(GCColor::invertColor(GColor(CTOOLBAR)).name())
                                                                        .arg(bg_select.name())
                                                                        .arg(fg_select.name()));
    tabbar->setDocumentMode(true);
    athleteView->setPalette(tabbar->palette());

    head->updateGeometry();
    repaint();

}

/*----------------------------------------------------------------------
 * Measures
 *--------------------------------------------------------------------*/

void
MainWindow::setMeasuresMenu()
{
    measuresMenu->clear();
    if (currentAthleteTab->context->athlete == nullptr) return;
    Measures *measures = currentAthleteTab->context->athlete->measures;
    int group = 0;
    foreach(QString name, measures->getGroupNames()) {

        // we use the group index to identify the measures group
        QAction *service = new QAction(NULL);
        service->setText(name);
        service->setData(group++);
        measuresMenu->addAction(service);
    }
}

void
MainWindow::downloadMeasures(QAction *action)
{
    // download or import from CSV file
    if (currentAthleteTab->context->athlete == nullptr) return;
    Measures *measures = currentAthleteTab->context->athlete->measures;
    int group = action->data().toInt();
    MeasuresDownload dialog(currentAthleteTab->context, measures->getGroup(group));
    dialog.exec();
}

void
MainWindow::loadProgress
(QString folder, double progress)
{
    Q_UNUSED(folder)
    if (splash) {
        splash->showMessage(QString(tr("Loading activities: %1\%")).arg(static_cast<int>(progress)));
    }
}


void
MainWindow::addIntervals()
{
    currentAthleteTab->addIntervals();
}

void
MainWindow::ridesAutoImport() {

    currentAthleteTab->context->athlete->importFilesWhenOpeningAthlete();

}

void MainWindow::onEditMenuAboutToShow()
{
    editMenu->clear();
    if (toolMapper != nullptr) {
        delete toolMapper;
    }

    // Add all the data processors to the tools menu
    const DataProcessorFactory &factory = DataProcessorFactory::instance();
    QList<DataProcessor*> processors = factory.getProcessorsSorted();
    toolMapper = new QSignalMapper(this); // maps each option
    connect(toolMapper, &QSignalMapper::mappedString, this, &MainWindow::manualProcess);

    for (QList<DataProcessor*>::iterator iter = processors.begin(); iter != processors.end(); ++iter) {
        if (! (*iter)->isAutomatedOnly()) {
            // The localized processor name is shown in menu
            QAction *action = new QAction(QString("%1...").arg((*iter)->name()), this);
            editMenu->addAction(action);
            connect(action, SIGNAL(triggered()), toolMapper, SLOT(map()));
            toolMapper->setMapping(action, (*iter)->id());
        }
    }
}

#ifdef GC_HAS_CLOUD_DB
void
MainWindow::cloudDBuserEditChart()
{
    if (!(appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, GC_CLOUDDB_TC_ACCEPTANCE, false).toBool())) {
       CloudDBAcceptConditionsDialog acceptDialog(currentAthleteTab->context->athlete->cyclist);
       acceptDialog.setModal(true);
       if (acceptDialog.exec() == QDialog::Rejected) {
          return;
       };
    }

    if (currentAthleteTab->context->cdbChartListDialog == NULL) {
        currentAthleteTab->context->cdbChartListDialog = new CloudDBChartListDialog();
    }

    // force refresh in prepare to allways get the latest data here
    if (currentAthleteTab->context->cdbChartListDialog->prepareData(currentAthleteTab->context->athlete->cyclist, CloudDBCommon::UserEdit)) {
        currentAthleteTab->context->cdbChartListDialog->exec(); // no action when closed
    }
}

void
MainWindow::cloudDBuserEditUserMetric()
{
    if (!(appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, GC_CLOUDDB_TC_ACCEPTANCE, false).toBool())) {
       CloudDBAcceptConditionsDialog acceptDialog(currentAthleteTab->context->athlete->cyclist);
       acceptDialog.setModal(true);
       if (acceptDialog.exec() == QDialog::Rejected) {
          return;
       };
    }

    if (currentAthleteTab->context->cdbUserMetricListDialog == NULL) {
        currentAthleteTab->context->cdbUserMetricListDialog = new CloudDBUserMetricListDialog();
    }

    // force refresh in prepare to allways get the latest data here
    if (currentAthleteTab->context->cdbUserMetricListDialog->prepareData(currentAthleteTab->context->athlete->cyclist, CloudDBCommon::UserEdit)) {
        currentAthleteTab->context->cdbUserMetricListDialog->exec(); // no action when closed
    }
}

void
MainWindow::cloudDBcuratorEditChart()
{
    // first check if the user is a curator
    CloudDBCuratorClient *curatorClient = new CloudDBCuratorClient;
    if (curatorClient->isCurator(appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, GC_ATHLETE_ID, "" ).toString())) {

        if (currentAthleteTab->context->cdbChartListDialog == NULL) {
            currentAthleteTab->context->cdbChartListDialog = new CloudDBChartListDialog();
        }

        // force refresh in prepare to allways get the latest data here
        if (currentAthleteTab->context->cdbChartListDialog->prepareData(currentAthleteTab->context->athlete->cyclist, CloudDBCommon::CuratorEdit)) {
            currentAthleteTab->context->cdbChartListDialog->exec(); // no action when closed
        }
    } else {
        QMessageBox::warning(0, tr("CloudDB"), QString(tr("Current athlete is not registered as curator - please contact the GoldenCheetah team")));

    }
}

void
MainWindow::cloudDBcuratorEditUserMetric()
{
    // first check if the user is a curator
    CloudDBCuratorClient *curatorClient = new CloudDBCuratorClient;
    if (curatorClient->isCurator(appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, GC_ATHLETE_ID, "" ).toString())) {

        if (currentAthleteTab->context->cdbUserMetricListDialog == NULL) {
            currentAthleteTab->context->cdbUserMetricListDialog = new CloudDBUserMetricListDialog();
        }

        // force refresh in prepare to allways get the latest data here
        if (currentAthleteTab->context->cdbUserMetricListDialog->prepareData(currentAthleteTab->context->athlete->cyclist, CloudDBCommon::CuratorEdit)) {
            currentAthleteTab->context->cdbUserMetricListDialog->exec(); // no action when closed
        }
    } else {
        QMessageBox::warning(0, tr("CloudDB"), QString(tr("Current athlete is not registered as curator - please contact the GoldenCheetah team")));

    }
}

void
MainWindow::cloudDBshowStatus() {

    CloudDBStatusClient::displayCloudDBStatus();
}


#endif

void
MainWindow::setUploadMenu()
{
    uploadMenu->clear();
    foreach(QString name, CloudServiceFactory::instance().serviceNames()) {

        const CloudService *s = CloudServiceFactory::instance().service(name);
        if (!s || appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, s->activeSettingName(), "false").toString() != "true") continue;

        if (s->capabilities() & CloudService::Upload) {

            // we need the technical name to identify the service to be called
            QAction *service = new QAction(NULL);
            service->setText(s->uiName());
            service->setData(name);
            uploadMenu->addAction(service);
        }
    }
}

void
MainWindow::setSyncMenu()
{
    syncMenu->clear();
    foreach(QString name, CloudServiceFactory::instance().serviceNames()) {

        const CloudService *s = CloudServiceFactory::instance().service(name);
        if (!s || appsettings->cvalue(currentAthleteTab->context->athlete->cyclist, s->activeSettingName(), "false").toString() != "true") continue;

        if (s->capabilities() & CloudService::Query)  {

            // we need the technical name to identify the service to be called
            QAction *service = new QAction(NULL);
            service->setText(s->uiName());
            service->setData(name);
            syncMenu->addAction(service);
        }
    }
}


