/* * Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "Athlete.h" #include #include #include #include "Pages.h" #include "Units.h" #include "Settings.h" #include "Units.h" #include "Colors.h" #include "AddDeviceWizard.h" #include "DeviceTypes.h" #include "DeviceConfiguration.h" #include "ColorButton.h" #include "SpecialFields.h" #include "DataProcessor.h" #include "OAuthDialog.h" #include "RideAutoImportConfig.h" #include "HelpWhatsThis.h" #if QT_VERSION >= 0x050000 #include "Dropbox.h" #endif #include "LocalFileStore.h" // // Main Config Page - tabs for each sub-page // GeneralPage::GeneralPage(Context *context) : context(context) { // layout without too wide margins QVBoxLayout *mainLayout = new QVBoxLayout(this); QGridLayout *configLayout = new QGridLayout; mainLayout->addLayout(configLayout); mainLayout->addStretch(); setContentsMargins(0,0,0,0); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); configLayout->setSpacing(10); // // Language Selection // langLabel = new QLabel(tr("Language:")); langCombo = new QComboBox(); langCombo->addItem(tr("English")); langCombo->addItem(tr("French")); langCombo->addItem(tr("Japanese")); langCombo->addItem(tr("Portugese (Brazil)")); langCombo->addItem(tr("Italian")); langCombo->addItem(tr("German")); langCombo->addItem(tr("Russian")); langCombo->addItem(tr("Czech")); langCombo->addItem(tr("Spanish")); langCombo->addItem(tr("Portugese")); langCombo->addItem(tr("Chinese (Traditional)")); // Default to system locale QVariant lang = appsettings->value(this, GC_LANG, QLocale::system().name()); if(lang.toString().startsWith("en")) langCombo->setCurrentIndex(0); else if(lang.toString().startsWith("fr")) langCombo->setCurrentIndex(1); else if(lang.toString().startsWith("ja")) langCombo->setCurrentIndex(2); else if(lang.toString().startsWith("pt-br")) langCombo->setCurrentIndex(3); else if(lang.toString().startsWith("it")) langCombo->setCurrentIndex(4); else if(lang.toString().startsWith("de")) langCombo->setCurrentIndex(5); else if(lang.toString().startsWith("ru")) langCombo->setCurrentIndex(6); else if(lang.toString().startsWith("cs")) langCombo->setCurrentIndex(7); else if(lang.toString().startsWith("es")) langCombo->setCurrentIndex(8); else if(lang.toString().startsWith("pt")) langCombo->setCurrentIndex(9); else if (lang.toString().startsWith("zh-tw")) langCombo->setCurrentIndex(10); else langCombo->setCurrentIndex(0); configLayout->addWidget(langLabel, 0,0, Qt::AlignRight); configLayout->addWidget(langCombo, 0,1, Qt::AlignLeft); QLabel *unitlabel = new QLabel(tr("Unit")); unitCombo = new QComboBox(); unitCombo->addItem(tr("Metric")); unitCombo->addItem(tr("Imperial")); if (context->athlete->useMetricUnits) unitCombo->setCurrentIndex(0); else unitCombo->setCurrentIndex(1); configLayout->addWidget(unitlabel, 1,0, Qt::AlignRight); configLayout->addWidget(unitCombo, 1,1, Qt::AlignLeft); // // Garmin crap // // garmin Smart Recording options QVariant garminHWMark = appsettings->value(this, GC_GARMIN_HWMARK); garminSmartRecord = new QCheckBox(tr("Use Garmin Smart Recording"), this); QVariant isGarminSmartRecording = appsettings->value(this, GC_GARMIN_SMARTRECORD, Qt::Checked); garminSmartRecord->setCheckState(isGarminSmartRecording.toInt() > 0 ? Qt::Checked : Qt::Unchecked); // by default, set the threshold to 25 seconds if (garminHWMark.isNull() || garminHWMark.toInt() == 0) garminHWMark.setValue(25); QLabel *garminHWLabel = new QLabel(tr("Smart Recording Threshold (secs):")); garminHWMarkedit = new QLineEdit(garminHWMark.toString(),this); garminHWMarkedit->setInputMask("009"); configLayout->addWidget(garminSmartRecord, 2,1, Qt::AlignLeft); configLayout->addWidget(garminHWLabel, 3,0, Qt::AlignRight); configLayout->addWidget(garminHWMarkedit, 3,1, Qt::AlignLeft); // Elevation hysterisis GC_ELEVATION_HYSTERISIS QVariant elevationHysteresis = appsettings->value(this, GC_ELEVATION_HYSTERESIS); if (elevationHysteresis.isNull() || elevationHysteresis.toFloat() == 0.0) elevationHysteresis.setValue(3.0); // default is 1 meter QLabel *hystlabel = new QLabel(tr("Elevation hysteresis (meters):")); hystedit = new QLineEdit(elevationHysteresis.toString(),this); hystedit->setInputMask("9.00"); configLayout->addWidget(hystlabel, 4,0, Qt::AlignRight); configLayout->addWidget(hystedit, 4,1, Qt::AlignLeft); // wbal formula preference QLabel *wbalFormLabel = new QLabel(tr("W' bal formula:")); wbalForm = new QComboBox(this); wbalForm->addItem(tr("Differential")); wbalForm->addItem(tr("Integral")); if (appsettings->value(this, GC_WBALFORM, "diff").toString() == "diff") wbalForm->setCurrentIndex(0); else wbalForm->setCurrentIndex(1); configLayout->addWidget(wbalFormLabel, 5,0, Qt::AlignRight); configLayout->addWidget(wbalForm, 5,1, Qt::AlignLeft); // // Warn to save on exit warnOnExit = new QCheckBox(tr("Warn for unsaved activities on exit"), this); warnOnExit->setChecked(appsettings->value(NULL, GC_WARNEXIT, true).toBool()); configLayout->addWidget(warnOnExit, 6,1, Qt::AlignLeft); // // Run API web services when running // int offset=0; #ifdef GC_WANT_HTTP offset += 1; startHttp = new QCheckBox(tr("Enable API Web Services"), this); startHttp->setChecked(appsettings->value(NULL, GC_START_HTTP, true).toBool()); configLayout->addWidget(startHttp, 7,1, Qt::AlignLeft); #endif // // Athlete directory (home of athletes) // QVariant athleteDir = appsettings->value(this, GC_HOMEDIR); athleteLabel = new QLabel(tr("Athlete Library:")); athleteDirectory = new QLineEdit; athleteDirectory->setText(athleteDir.toString() == "0" ? "" : athleteDir.toString()); athleteWAS = athleteDirectory->text(); // remember what we started with ... athleteBrowseButton = new QPushButton(tr("Browse")); athleteBrowseButton->setFixedWidth(120); configLayout->addWidget(athleteLabel, 7 + offset,0, Qt::AlignRight); configLayout->addWidget(athleteDirectory, 7 + offset,1); configLayout->addWidget(athleteBrowseButton, 7 + offset,2); connect(athleteBrowseButton, SIGNAL(clicked()), this, SLOT(browseAthleteDir())); // // Workout directory (train view) // QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR, ""); // fix old bug.. if (workoutDir == "0") workoutDir = ""; workoutLabel = new QLabel(tr("Workout Library:")); workoutDirectory = new QLineEdit; workoutDirectory->setText(workoutDir.toString()); workoutBrowseButton = new QPushButton(tr("Browse")); workoutBrowseButton->setFixedWidth(120); configLayout->addWidget(workoutLabel, 8 + offset,0, Qt::AlignRight); configLayout->addWidget(workoutDirectory, 8 + offset,1); configLayout->addWidget(workoutBrowseButton, 8 + offset,2); connect(workoutBrowseButton, SIGNAL(clicked()), this, SLOT(browseWorkoutDir())); // // Videosync directory (train view) // QVariant videosyncDir = appsettings->value(this, GC_VIDEOSYNCDIR, ""); videosyncLabel = new QLabel(tr("VideoSync Library:")); videosyncDirectory = new QLineEdit; videosyncDirectory->setText(videosyncDir.toString()); videosyncBrowseButton = new QPushButton(tr("Browse")); videosyncBrowseButton->setFixedWidth(120); configLayout->addWidget(videosyncLabel, 9 + offset,0, Qt::AlignRight); configLayout->addWidget(videosyncDirectory, 9 + offset,1); configLayout->addWidget(videosyncBrowseButton, 9 + offset,2); connect(videosyncBrowseButton, SIGNAL(clicked()), this, SLOT(browseVideoSyncDir())); // save away initial values b4.unit = unitCombo->currentIndex(); b4.hyst = elevationHysteresis.toFloat(); b4.wbal = wbalForm->currentIndex(); b4.warn = warnOnExit->isChecked(); #ifdef GC_WANT_HTTP b4.starthttp = startHttp->isChecked(); #endif } qint32 GeneralPage::saveClicked() { // Language static const QString langs[] = { "en", "fr", "ja", "pt-br", "it", "de", "ru", "cs", "es", "pt", "zh-tw" }; appsettings->setValue(GC_LANG, langs[langCombo->currentIndex()]); // Garmin and cranks appsettings->setValue(GC_GARMIN_HWMARK, garminHWMarkedit->text().toInt()); appsettings->setValue(GC_GARMIN_SMARTRECORD, garminSmartRecord->checkState()); // save on exit appsettings->setValue(GC_WARNEXIT, warnOnExit->isChecked()); // Directories appsettings->setValue(GC_WORKOUTDIR, workoutDirectory->text()); appsettings->setValue(GC_HOMEDIR, athleteDirectory->text()); appsettings->setValue(GC_VIDEOSYNCDIR, videosyncDirectory->text()); // Elevation appsettings->setValue(GC_ELEVATION_HYSTERESIS, hystedit->text()); // wbal formula appsettings->setValue(GC_WBALFORM, wbalForm->currentIndex() ? "int" : "diff"); if (unitCombo->currentIndex()==0) appsettings->setValue(GC_UNIT, GC_UNIT_METRIC); else if (unitCombo->currentIndex()==1) appsettings->setValue(GC_UNIT, GC_UNIT_IMPERIAL); #ifdef GC_WANT_HTTP // start http appsettings->setValue(GC_START_HTTP, startHttp->isChecked()); #endif qint32 state=0; // general stuff changed ? #ifdef GC_WANT_HTTP if (b4.hyst != hystedit->text().toFloat() || b4.starthttp != startHttp->isChecked()) #else if (b4.hyst != hystedit->text().toFloat()) #endif state += CONFIG_GENERAL; if (b4.wbal != wbalForm->currentIndex()) state += CONFIG_WBAL; // units prefs changed? if (b4.unit != unitCombo->currentIndex()) state += CONFIG_UNITS; return state; } void GeneralPage::browseWorkoutDir() { QString currentDir = workoutDirectory->text(); if (!QDir(currentDir).exists()) currentDir = ""; QString dir = QFileDialog::getExistingDirectory(this, tr("Select Workout Library"), currentDir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir != "") workoutDirectory->setText(dir); //only overwrite current dir, if a new was selected } void GeneralPage::browseVideoSyncDir() { QString currentDir = videosyncDirectory->text(); if (!QDir(currentDir).exists()) currentDir = ""; QString dir = QFileDialog::getExistingDirectory(this, tr("Select Workout Library"), currentDir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir != "") workoutDirectory->setText(dir); //only overwrite current dir, if a new was selected } void GeneralPage::browseAthleteDir() { QString currentDir = athleteDirectory->text(); if (!QDir(currentDir).exists()) currentDir = ""; QString dir = QFileDialog::getExistingDirectory(this, tr("Select Athlete Library"), currentDir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir != "") athleteDirectory->setText(dir); //only overwrite current dir, if a new was selected } // // Passwords page // CredentialsPage::CredentialsPage(QWidget *parent, Context *context) : QScrollArea(parent), context(context) { QWidget *main = new QWidget(this); main->setContentsMargins(0,0,0,0); QGridLayout *grid = new QGridLayout; unsigned int row = 0; QFont current; current.setWeight(QFont::Black); QPixmap passwords = QPixmap(":/images/toolbar/passwords.png"); ////////////////////////////////////////////////// // TrainingPeaks QLabel *tp = new QLabel(tr("TrainingPeaks")); tp->setFont(current); QLabel *userLabel = new QLabel(tr("Username")); QLabel *passLabel = new QLabel(tr("Password")); QLabel *typeLabel = new QLabel(tr("Account Type")); tpUser = new QLineEdit(this); tpUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_TPUSER, "").toString()); tpPass = new QLineEdit(this); tpPass->setEchoMode(QLineEdit::Password); tpPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_TPPASS, "").toString()); tpType = new QComboBox(this); tpType->addItem("Shared Free"); tpType->addItem("Coached Free"); tpType->addItem("Self Coached Premium"); tpType->addItem("Shared Self Coached Premium"); tpType->addItem("Coached Premium"); tpType->addItem("Shared Coached Premium"); tpType->setCurrentIndex(appsettings->cvalue(context->athlete->cyclist, GC_TPTYPE, "0").toInt()); grid->addWidget(tp, row, 0); grid->addWidget(userLabel, ++row, 0); grid->addWidget(tpUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(passLabel, ++row, 0); grid->addWidget(tpPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(typeLabel, ++row, 0); grid->addWidget(tpType, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Twitter #ifdef GC_HAVE_KQOAUTH QLabel *twp = new QLabel(tr("Twitter")); twp->setFont(current); QLabel *twauthLabel = new QLabel(tr("Authorise")); twitterAuthorise = new QPushButton(tr("Authorise"), this); twitterAuthorised = new QPushButton(this); twitterAuthorised->setContentsMargins(0,0,0,0); twitterAuthorised->setIcon(passwords.scaled(16,16)); twitterAuthorised->setIconSize(QSize(16,16)); twitterAuthorised->setFixedHeight(16); twitterAuthorised->setFixedWidth(16); grid->addWidget(twp, ++row, 0); grid->addWidget(twauthLabel, ++row, 0); grid->addWidget(twitterAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_TWITTER_TOKEN, "")!="") grid->addWidget(twitterAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); else twitterAuthorised->hide(); // if no token no show connect(twitterAuthorise, SIGNAL(clicked()), this, SLOT(authoriseTwitter())); #endif #if QT_VERSION >= 0x050000 // only in QT5 or higher // // Authorising Dropbox via an OAuthDialog... QLabel *dwp = new QLabel(tr("Dropbox")); dwp->setFont(current); QLabel *dropauthLabel = new QLabel(tr("Authorise")); dropboxAuthorise = new QPushButton(tr("Authorise"), this); dropboxAuthorised = new QPushButton(this); dropboxAuthorised->setContentsMargins(0,0,0,0); dropboxAuthorised->setIcon(passwords.scaled(16,16)); dropboxAuthorised->setIconSize(QSize(16,16)); dropboxAuthorised->setFixedHeight(16); dropboxAuthorised->setFixedWidth(16); grid->addWidget(dwp, ++row, 0); grid->addWidget(dropauthLabel, ++row, 0); grid->addWidget(dropboxAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_DROPBOX_TOKEN, "")!="") grid->addWidget(dropboxAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); else dropboxAuthorised->hide(); // if no token no show connect(dropboxAuthorise, SIGNAL(clicked()), this, SLOT(authoriseDropbox())); // // Selecting the athlete folder in dropbox QLabel *dropfolderLabel = new QLabel(tr("Athlete Folder")); dropboxFolder = new QLineEdit(this); dropboxFolder->setText(appsettings->cvalue(context->athlete->cyclist, GC_DROPBOX_FOLDER, "").toString()); dropboxBrowse = new QPushButton(tr("Browse")); connect(dropboxBrowse, SIGNAL(clicked()), this, SLOT(chooseDropboxFolder())); QHBoxLayout *dfchoose = new QHBoxLayout; dfchoose->addWidget(dropboxFolder); dfchoose->addWidget(dropboxBrowse); grid->addWidget(dropfolderLabel, ++row, 0); grid->addLayout(dfchoose, row, 1); //grid->addWidget(twpinLabel, ++row, 0); #endif ////////////////////////////////////////////////// // Local FileStore QLabel *nfs = new QLabel(tr("Shared Local Folder")); nfs->setFont(current); grid->addWidget(nfs, ++row, 0); // Selecting the storage folder folder of the Local File Store QLabel *networkFileStoreFolderLabel = new QLabel(tr("Shared Local Athlete Folder")); networkFileStoreFolder = new QLineEdit(this); networkFileStoreFolder->setText(appsettings->cvalue(context->athlete->cyclist, GC_NETWORKFILESTORE_FOLDER, "").toString()); networkFileStoreFolderBrowse = new QPushButton(tr("Browse")); connect(networkFileStoreFolderBrowse, SIGNAL(clicked()), this, SLOT(chooseLocalFileStoreFolder())); QHBoxLayout *nwfsfchoose = new QHBoxLayout; nwfsfchoose->addWidget(networkFileStoreFolder); nwfsfchoose->addWidget(networkFileStoreFolderBrowse); grid->addWidget(networkFileStoreFolderLabel, ++row, 0); grid->addLayout(nwfsfchoose, row, 1); ////////////////////////////////////////////////// // Strava QLabel *str = new QLabel(tr("Strava")); str->setFont(current); QLabel *strauthLabel = new QLabel(tr("Authorise")); stravaAuthorise = new QPushButton(tr("Authorise"), this); #ifndef GC_STRAVA_CLIENT_SECRET stravaAuthorise->setEnabled(false); #endif stravaAuthorised = new QPushButton(this); stravaAuthorised->setContentsMargins(0,0,0,0); stravaAuthorised->setIcon(passwords.scaled(16,16)); stravaAuthorised->setIconSize(QSize(16,16)); stravaAuthorised->setFixedHeight(16); stravaAuthorised->setFixedWidth(16); grid->addWidget(str, ++row, 0); grid->addWidget(strauthLabel, ++row, 0); grid->addWidget(stravaAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_STRAVA_TOKEN, "")!="") grid->addWidget(stravaAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); else stravaAuthorised->hide(); // if no token no show connect(stravaAuthorise, SIGNAL(clicked()), this, SLOT(authoriseStrava())); ////////////////////////////////////////////////// // Cycling Analytics QLabel *can = new QLabel(tr("Cycling Analytics")); can->setFont(current); QLabel *canauthLabel = new QLabel(tr("Authorise")); cyclingAnalyticsAuthorise = new QPushButton(tr("Authorise"), this); #ifndef GC_CYCLINGANALYTICS_CLIENT_SECRET cyclingAnalyticsAuthorise->setEnabled(false); #endif cyclingAnalyticsAuthorised = new QPushButton(this); cyclingAnalyticsAuthorised->setContentsMargins(0,0,0,0); cyclingAnalyticsAuthorised->setIcon(passwords.scaled(16,16)); cyclingAnalyticsAuthorised->setIconSize(QSize(16,16)); cyclingAnalyticsAuthorised->setFixedHeight(16); cyclingAnalyticsAuthorised->setFixedWidth(16); grid->addWidget(can, ++row, 0); grid->addWidget(canauthLabel, ++row, 0); grid->addWidget(cyclingAnalyticsAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_CYCLINGANALYTICS_TOKEN, "")!="") grid->addWidget(cyclingAnalyticsAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); else cyclingAnalyticsAuthorised->hide(); connect(cyclingAnalyticsAuthorise, SIGNAL(clicked()), this, SLOT(authoriseCyclingAnalytics())); ////////////////////////////////////////////////// // RideWithGPS QLabel *rwgps = new QLabel(tr("RideWithGPS")); rwgps->setFont(current); QLabel *rwgpsuserLabel = new QLabel(tr("Username")); QLabel *rwgpspassLabel = new QLabel(tr("Password")); rideWithGPSUser = new QLineEdit(this); rideWithGPSUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_RWGPSUSER, "").toString()); rideWithGPSPass = new QLineEdit(this); rideWithGPSPass->setEchoMode(QLineEdit::Password); rideWithGPSPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_RWGPSPASS, "").toString()); grid->addWidget(rwgps, ++row, 0); grid->addWidget(rwgpsuserLabel, ++row, 0); grid->addWidget(rideWithGPSUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(rwgpspassLabel, ++row, 0); grid->addWidget(rideWithGPSPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Withings Wifi Scales QLabel *wip = new QLabel(tr("Withings Wifi Scales")); wip->setFont(current); QLabel *wiurlLabel = new QLabel(tr("Website")); QLabel *wiuserLabel = new QLabel(tr("User Id")); QLabel *wipassLabel = new QLabel(tr("Public Key")); wiURL = new QLineEdit(this); wiURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIURL, "http://wbsapi.withings.net/").toString()); wiUser = new QLineEdit(this); wiUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIUSER, "").toString()); wiPass = new QLineEdit(this); wiPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIKEY, "").toString()); grid->addWidget(wip, ++row, 0); grid->addWidget(wiurlLabel, ++row, 0); grid->addWidget(wiURL, row, 1, 0); grid->addWidget(wiuserLabel, ++row, 0); grid->addWidget(wiUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(wipassLabel, ++row, 0); grid->addWidget(wiPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Web Calendar QLabel *webcal = new QLabel(tr("Web Calendar")); webcal->setFont(current); QLabel *wcurlLabel = new QLabel(tr("Webcal URL")); webcalURL = new QLineEdit(this); webcalURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_WEBCAL_URL, "").toString()); grid->addWidget(webcal, ++row, 0); grid->addWidget(wcurlLabel, ++row, 0); grid->addWidget(webcalURL, row, 1, 0); ////////////////////////////////////////////////// // CalDAV Calendar QLabel *dv = new QLabel(tr("CalDAV Calendar")); dvCALDAVType = new QComboBox(this); dvCALDAVType->addItem(tr("Generic CalDAV")); dvCALDAVType->addItem(tr("Google Calendar")); dvCALDAVType->setCurrentIndex(appsettings->cvalue(context->athlete->cyclist, GC_DVCALDAVTYPE, "0").toInt()); // setup and fill all fields dv->setFont(current); QLabel *dvurlLabel = new QLabel(tr("CalDAV URL")); QLabel *dvuserLabel = new QLabel(tr("CalDAV User Id")); QLabel *dvpassLabel = new QLabel(tr("CalDAV Password")); QLabel *dvTypeLabel = new QLabel(tr("Calendar Type")); QLabel *dvGoogleLabel = new QLabel(tr("Google CalID")); dvURL = new QLineEdit(this); QString url = appsettings->cvalue(context->athlete->cyclist, GC_DVURL, "").toString(); url.replace("%40", "@"); // remove escape of @ character dvURL->setText(url); dvUser = new QLineEdit(this); dvUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_DVUSER, "").toString()); dvPass = new QLineEdit(this); dvPass->setEchoMode(QLineEdit::Password); dvPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_DVPASS, "").toString()); dvGoogleCalid = new QLineEdit(this); dvGoogleCalid->setText(appsettings->cvalue(context->athlete->cyclist, GC_DVGOOGLE_CALID, "").toString()); QLabel *googleCalendarAuthLabel = new QLabel(tr("Google Calendar")); googleCalendarAuthorise = new QPushButton(tr("Authorise"), this); googleCalendarAuthorised = new QPushButton(this); googleCalendarAuthorised->setContentsMargins(0,0,0,0); googleCalendarAuthorised->setIcon(passwords.scaled(16,16)); googleCalendarAuthorised->setIconSize(QSize(16,16)); googleCalendarAuthorised->setFixedHeight(16); googleCalendarAuthorised->setFixedWidth(16); grid->addWidget(dv, ++row, 0); grid->addWidget(dvTypeLabel, ++row, 0); grid->addWidget(dvCALDAVType, row, 1, 0); grid->addWidget(dvurlLabel, ++row, 0); grid->addWidget(dvURL, row, 1, 0); grid->addWidget(dvuserLabel, ++row, 0); grid->addWidget(dvUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(dvpassLabel, ++row, 0); grid->addWidget(dvPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(dvGoogleLabel, ++row, 0); grid->addWidget(dvGoogleCalid, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(googleCalendarAuthLabel, ++row, 0); grid->addWidget(googleCalendarAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_GOOGLE_CALENDAR_REFRESH_TOKEN, "")!="") grid->addWidget(googleCalendarAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); else googleCalendarAuthorised->hide(); // if no token no show connect(googleCalendarAuthorise, SIGNAL(clicked()), this, SLOT(authoriseGoogleCalendar())); connect (dvCALDAVType, SIGNAL(currentIndexChanged(int)), this, SLOT(dvCALDAVTypeChanged(int))); // activate/deactivate the input fields according to the type selected dvCALDAVTypeChanged(dvCALDAVType->currentIndex()); ////////////////////////////////////////////////// // Trainingstagebuch QLabel *ttb = new QLabel(tr("Trainingstagebuch")); ttb->setFont(current); QLabel *ttbuserLabel = new QLabel(tr("Username")); QLabel *ttbpassLabel = new QLabel(tr("Password")); ttbUser = new QLineEdit(this); ttbUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_TTBUSER, "").toString()); ttbPass = new QLineEdit(this); ttbPass->setEchoMode(QLineEdit::Password); ttbPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_TTBPASS, "").toString()); grid->addWidget(ttb, ++row, 0); grid->addWidget(ttbuserLabel, ++row, 0); grid->addWidget(ttbUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(ttbpassLabel, ++row, 0); grid->addWidget(ttbPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Selfloops QLabel *sel = new QLabel(tr("Selfloops")); sel->setFont(current); QLabel *seluserLabel = new QLabel(tr("Username")); QLabel *selpassLabel = new QLabel(tr("Password")); selUser = new QLineEdit(this); selUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_SELUSER, "").toString()); selPass = new QLineEdit(this); selPass->setEchoMode(QLineEdit::Password); selPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_SELPASS, "").toString()); grid->addWidget(sel, ++row, 0); grid->addWidget(seluserLabel, ++row, 0); grid->addWidget(selUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(selpassLabel, ++row, 0); grid->addWidget(selPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Velo Hero QLabel *velohero = new QLabel(tr("Velo Hero")); velohero->setFont(current); QLabel *veloherouserLabel = new QLabel(tr("Username")); QLabel *veloheropassLabel = new QLabel(tr("Password")); veloHeroUser = new QLineEdit(this); veloHeroUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_VELOHEROUSER, "").toString()); veloHeroPass = new QLineEdit(this); veloHeroPass->setEchoMode(QLineEdit::Password); veloHeroPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_VELOHEROPASS, "").toString()); grid->addWidget(velohero, ++row, 0); grid->addWidget(veloherouserLabel, ++row, 0); grid->addWidget(veloHeroUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(veloheropassLabel, ++row, 0); grid->addWidget(veloHeroPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // End of grid grid->setColumnStretch(0,0); grid->setColumnStretch(1,3); main->setLayout(grid); setFrameStyle(QFrame::NoFrame); setWidgetResizable(true); setWidget(main); } #if QT_VERSION >= 0x050000 // only in QT5 or higher void CredentialsPage::chooseDropboxFolder() { Dropbox dropbox(context); // open the connection QStringList errors; if (dropbox.open(errors) == false) { QMessageBox err; err.setText(tr("Dropbox Connection Failed")); err.setDetailedText(errors.join("\n\n")); err.setIcon(QMessageBox::Warning); err.exec(); return; } // did the user type something ? QString path = dropboxFolder->text(); if (path == "") path = appsettings->cvalue(context->athlete->cyclist, GC_DROPBOX_FOLDER, "/").toString(); FileStoreDialog dialog(this, &dropbox, tr("Choose Athlete Directory"), path, true); int ret = dialog.exec(); // did we actually select something? if (ret == QDialog::Accepted) dropboxFolder->setText(dialog.pathnameSelected()); } #endif void CredentialsPage::chooseLocalFileStoreFolder() { // did the user type something ? if not, get it from the Settings QString path = networkFileStoreFolder->text(); if (path == "") path = appsettings->cvalue(context->athlete->cyclist, GC_NETWORKFILESTORE_FOLDER, "").toString(); QString dir = QFileDialog::getExistingDirectory(this, tr("Choose Shared Local Folder Athlete Directory"), path, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir != "") networkFileStoreFolder->setText(dir); //only overwrite current dir, if a new was selected } #ifdef GC_HAVE_KQOAUTH void CredentialsPage::authoriseTwitter() { OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::TWITTER); if (oauthDialog->sslLibMissing()) { delete oauthDialog; } else { oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); } } #endif #if QT_VERSION >= 0x050000 // only in QT5 or higher void CredentialsPage::authoriseDropbox() { OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::DROPBOX); if (oauthDialog->sslLibMissing()) { delete oauthDialog; } else { oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); } } #endif void CredentialsPage::authoriseStrava() { OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::STRAVA); if (oauthDialog->sslLibMissing()) { delete oauthDialog; } else { oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); } } void CredentialsPage::authoriseCyclingAnalytics() { OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::CYCLING_ANALYTICS); if (oauthDialog->sslLibMissing()) { delete oauthDialog; } else { oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); } } void CredentialsPage::authoriseGoogleCalendar() { OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::GOOGLE_CALENDAR); if (oauthDialog->sslLibMissing()) { delete oauthDialog; } else { oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); } } void CredentialsPage::dvCALDAVTypeChanged(int type) { if (type == 0) { // normal CALDAV selected googleCalendarAuthorise->setEnabled(false); dvGoogleCalid->setEnabled(false); dvURL->setEnabled(true); dvUser->setEnabled(true); dvPass->setEnabled(true); } else { // Google Selected googleCalendarAuthorise->setEnabled(true); dvGoogleCalid->setEnabled(true); dvURL->setEnabled(false); dvUser->setEnabled(false); dvPass->setEnabled(false); } // set always inactive if authorization process not feasible #ifndef GC_GOOGLE_CALENDAR_CLIENT_SECRET googleCalendarAuthorise->setEnabled(false); #endif } qint32 CredentialsPage::saveClicked() { appsettings->setCValue(context->athlete->cyclist, GC_TPUSER, tpUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_TPPASS, tpPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_RWGPSUSER, rideWithGPSUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_RWGPSPASS, rideWithGPSPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_TTBUSER, ttbUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_TTBPASS, ttbPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_VELOHEROUSER, veloHeroUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_VELOHEROPASS, veloHeroPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_SELUSER, selUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_SELPASS, selPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_TPTYPE, tpType->currentIndex()); appsettings->setCValue(context->athlete->cyclist, GC_WIURL, wiURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_WIUSER, wiUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_WIKEY, wiPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_WEBCAL_URL, webcalURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_WEBCAL_URL, webcalURL->text()); #if QT_VERSION >= 0x050000 // only in QT5 or higher appsettings->setCValue(context->athlete->cyclist, GC_DROPBOX_FOLDER, dropboxFolder->text()); #endif appsettings->setCValue(context->athlete->cyclist, GC_NETWORKFILESTORE_FOLDER, networkFileStoreFolder->text()); // escape the at character QString url = dvURL->text(); url.replace("@", "%40"); appsettings->setCValue(context->athlete->cyclist, GC_DVURL, url); appsettings->setCValue(context->athlete->cyclist, GC_DVUSER, dvUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_DVPASS, dvPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_DVGOOGLE_CALID, dvGoogleCalid->text()); appsettings->setCValue(context->athlete->cyclist, GC_DVCALDAVTYPE, dvCALDAVType->currentIndex()); return 0; } // // About me // AboutRiderPage::AboutRiderPage(QWidget *parent, Context *context) : QWidget(parent), context(context) { QVBoxLayout *all = new QVBoxLayout(this); QGridLayout *grid = new QGridLayout; #ifdef Q_OS_MAX setContentsMargins(10,10,10,10); grid->setSpacing(5); all->setSpacing(5); #endif QLabel *nicklabel = new QLabel(tr("Nickname")); QLabel *doblabel = new QLabel(tr("Date of Birth")); QLabel *sexlabel = new QLabel(tr("Sex")); nickname = new QLineEdit(this); nickname->setText(appsettings->cvalue(context->athlete->cyclist, GC_NICKNAME, "").toString()); if (nickname->text() == "0") nickname->setText(""); dob = new QDateEdit(this); dob->setDate(appsettings->cvalue(context->athlete->cyclist, GC_DOB).toDate()); sex = new QComboBox(this); sex->addItem(tr("Male")); sex->addItem(tr("Female")); // we set to 0 or 1 for male or female since this // is language independent (and for once the code is easier!) sex->setCurrentIndex(appsettings->cvalue(context->athlete->cyclist, GC_SEX).toInt()); if (QFileInfo(context->athlete->home->config().canonicalPath() + "/" + "avatar.png").exists()) avatar = QPixmap(context->athlete->home->config().canonicalPath() + "/" + "avatar.png"); else avatar = QPixmap(":/images/noavatar.png"); avatarButton = new QPushButton(this); avatarButton->setContentsMargins(0,0,0,0); avatarButton->setFlat(true); avatarButton->setIcon(avatar.scaled(140,140)); avatarButton->setIconSize(QSize(140,140)); avatarButton->setFixedHeight(140); avatarButton->setFixedWidth(140); // // Crank length - only used by PfPv chart (should move there!) // QLabel *crankLengthLabel = new QLabel(tr("Crank Length")); QVariant crankLength = appsettings->cvalue(context->athlete->cyclist, GC_CRANKLENGTH); crankLengthCombo = new QComboBox(); crankLengthCombo->addItem("150"); crankLengthCombo->addItem("155"); crankLengthCombo->addItem("160"); crankLengthCombo->addItem("162.5"); crankLengthCombo->addItem("165"); crankLengthCombo->addItem("167.5"); crankLengthCombo->addItem("170"); crankLengthCombo->addItem("172.5"); crankLengthCombo->addItem("175"); crankLengthCombo->addItem("177.5"); crankLengthCombo->addItem("180"); crankLengthCombo->addItem("182.5"); crankLengthCombo->addItem("185"); if(crankLength.toString() == "150") crankLengthCombo->setCurrentIndex(0); if(crankLength.toString() == "155") crankLengthCombo->setCurrentIndex(1); if(crankLength.toString() == "160") crankLengthCombo->setCurrentIndex(2); if(crankLength.toString() == "162.5") crankLengthCombo->setCurrentIndex(3); if(crankLength.toString() == "165") crankLengthCombo->setCurrentIndex(4); if(crankLength.toString() == "167.5") crankLengthCombo->setCurrentIndex(5); if(crankLength.toString() == "170") crankLengthCombo->setCurrentIndex(6); if(crankLength.toString() == "172.5") crankLengthCombo->setCurrentIndex(7); if(crankLength.toString() == "175") crankLengthCombo->setCurrentIndex(8); if(crankLength.toString() == "177.5") crankLengthCombo->setCurrentIndex(9); if(crankLength.toString() == "180") crankLengthCombo->setCurrentIndex(10); if(crankLength.toString() == "182.5") crankLengthCombo->setCurrentIndex(11); if(crankLength.toString() == "185") crankLengthCombo->setCurrentIndex(12); // // Wheel size // QLabel *wheelSizeLabel = new QLabel(tr("Wheelsize"), this); int wheelSize = appsettings->cvalue(context->athlete->cyclist, GC_WHEELSIZE, 2100).toInt(); rimSizeCombo = new QComboBox(); rimSizeCombo->addItems(WheelSize::RIM_SIZES); tireSizeCombo = new QComboBox(); tireSizeCombo->addItems(WheelSize::TIRE_SIZES); wheelSizeEdit = new QLineEdit(QString("%1").arg(wheelSize),this); wheelSizeEdit->setInputMask("0000"); wheelSizeEdit->setFixedWidth(40); QLabel *wheelSizeUnitLabel = new QLabel(tr("mm"), this); QHBoxLayout *wheelSizeLayout = new QHBoxLayout(); wheelSizeLayout->addWidget(rimSizeCombo); wheelSizeLayout->addWidget(tireSizeCombo); wheelSizeLayout->addWidget(wheelSizeEdit); wheelSizeLayout->addWidget(wheelSizeUnitLabel); connect(rimSizeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(calcWheelSize())); connect(tireSizeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(calcWheelSize())); connect(wheelSizeEdit, SIGNAL(textEdited(QString)), this, SLOT(resetWheelSize())); // // Auto Backup // // Selecting the storage folder folder of the Local File Store QLabel *autoBackupFolderLabel = new QLabel(tr("Auto Backup Folder")); autoBackupFolder = new QLineEdit(this); autoBackupFolder->setText(appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, "").toString()); autoBackupFolderBrowse = new QPushButton(tr("Browse")); connect(autoBackupFolderBrowse, SIGNAL(clicked()), this, SLOT(chooseAutoBackupFolder())); autoBackupPeriod = new QSpinBox(this); autoBackupPeriod->setMinimum(0); autoBackupPeriod->setMaximum(9999); autoBackupPeriod->setSingleStep(1); QLabel *autoBackupPeriodLabel = new QLabel(tr("Auto Backup Period")); autoBackupPeriod->setValue(appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, 0).toInt()); Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter; grid->addWidget(nicklabel, 0, 0, alignment); grid->addWidget(doblabel, 1, 0, alignment); grid->addWidget(sexlabel, 2, 0, alignment); grid->addWidget(nickname, 0, 1, alignment); grid->addWidget(dob, 1, 1, alignment); grid->addWidget(sex, 2, 1, alignment); grid->addWidget(crankLengthLabel, 4, 0, alignment); grid->addWidget(crankLengthCombo, 4, 1, alignment); grid->addWidget(wheelSizeLabel, 5, 0, alignment); grid->addLayout(wheelSizeLayout, 5, 1, 1, 2, alignment); grid->addWidget(autoBackupFolderLabel, 7,0, alignment); grid->addWidget(autoBackupFolder, 7, 1, alignment); grid->addWidget(autoBackupFolderBrowse, 7, 2, alignment); grid->addWidget(autoBackupPeriodLabel, 8, 0,alignment); grid->addWidget(autoBackupPeriod, 8, 1, alignment); grid->addWidget(avatarButton, 0, 1, 4, 2, Qt::AlignRight|Qt::AlignVCenter); all->addLayout(grid); all->addStretch(); // save initial values for things we care about // note we don't worry about age or sex at this point // since they are not used, nor the W'bal tau used in // the realtime code. This is limited to stuff we // care about tracking as it is used by metrics // height and weight as stored (always metric) b4.wheel = wheelSize; b4.crank = crankLengthCombo->currentIndex(); connect (avatarButton, SIGNAL(clicked()), this, SLOT(chooseAvatar())); } void AboutRiderPage::chooseAvatar() { QString filename = QFileDialog::getOpenFileName(this, tr("Choose Picture"), "", tr("Images (*.png *.jpg *.bmp)")); if (filename != "") { avatar = QPixmap(filename); avatarButton->setIcon(avatar.scaled(140,140)); avatarButton->setIconSize(QSize(140,140)); } } void AboutRiderPage::chooseAutoBackupFolder() { // did the user type something ? if not, get it from the Settings QString path = autoBackupFolder->text(); if (path == "") path = appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, "").toString(); QString dir = QFileDialog::getExistingDirectory(this, tr("Choose Backup Directory"), path, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir != "") autoBackupFolder->setText(dir); //only overwrite current dir, if a new was selected } void RiderPhysPage::unitChanged(int currentIndex) { if (currentIndex == 0) { QString weighttext = QString(tr("Weight (%1)")).arg(tr("kg")); weightlabel->setText(weighttext); weight->setValue(weight->value() / LB_PER_KG); QString heighttext = QString(tr("Height (%1)")).arg(tr("cm")); heightlabel->setText(heighttext); height->setValue(height->value() * CM_PER_INCH); } else { QString weighttext = QString(tr("Weight (%1)")).arg(tr("lb")); weightlabel->setText(weighttext); weight->setValue(weight->value() * LB_PER_KG); QString heighttext = QString(tr("Height (%1)")).arg(tr("in")); heightlabel->setText(heighttext); height->setValue(height->value() / CM_PER_INCH); } } void AboutRiderPage::calcWheelSize() { int diameter = WheelSize::calcPerimeter(rimSizeCombo->currentIndex(), tireSizeCombo->currentIndex()); if (diameter>0) wheelSizeEdit->setText(QString("%1").arg(diameter)); } void AboutRiderPage::resetWheelSize() { rimSizeCombo->setCurrentIndex(0); tireSizeCombo->setCurrentIndex(0); } qint32 AboutRiderPage::saveClicked() { appsettings->setCValue(context->athlete->cyclist, GC_NICKNAME, nickname->text()); appsettings->setCValue(context->athlete->cyclist, GC_DOB, dob->date()); appsettings->setCValue(context->athlete->cyclist, GC_SEX, sex->currentIndex()); avatar.save(context->athlete->home->config().canonicalPath() + "/" + "avatar.png", "PNG"); appsettings->setCValue(context->athlete->cyclist, GC_CRANKLENGTH, crankLengthCombo->currentText()); appsettings->setCValue(context->athlete->cyclist, GC_WHEELSIZE, wheelSizeEdit->text().toInt()); // Auto Backup appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, autoBackupFolder->text()); appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, autoBackupPeriod->value()); qint32 state=0; // general stuff changed ? if (b4.wheel != wheelSizeEdit->text().toInt() || b4.crank != crankLengthCombo->currentIndex() ) state += CONFIG_GENERAL; return state; } // // About me - physiology settings // RiderPhysPage::RiderPhysPage(QWidget *parent, Context *context) : QWidget(parent), context(context) { QVBoxLayout *all = new QVBoxLayout(this); QGridLayout *grid = new QGridLayout; #ifdef Q_OS_MAX setContentsMargins(10,10,10,10); grid->setSpacing(5); all->setSpacing(5); #endif QString weighttext = QString(tr("Weight (%1)")).arg(context->athlete->useMetricUnits ? tr("kg") : tr("lb")); weightlabel = new QLabel(weighttext); QString heighttext = QString(tr("Height (%1)")).arg(context->athlete->useMetricUnits ? tr("cm") : tr("in")); heightlabel = new QLabel(heighttext); wbaltaulabel = new QLabel(tr("W'bal tau (s)")); weight = new QDoubleSpinBox(this); weight->setMaximum(999.9); weight->setMinimum(0.0); weight->setDecimals(1); weight->setValue(appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT).toDouble() * (context->athlete->useMetricUnits ? 1.0 : LB_PER_KG)); height = new QDoubleSpinBox(this); height->setMaximum(999.9); height->setMinimum(0.0); height->setDecimals(1); height->setValue(appsettings->cvalue(context->athlete->cyclist, GC_HEIGHT).toDouble() * (context->athlete->useMetricUnits ? 100.0 : 100.0/CM_PER_INCH)); wbaltau = new QSpinBox(this); wbaltau->setMinimum(30); wbaltau->setMaximum(1200); wbaltau->setSingleStep(10); wbaltau->setValue(appsettings->cvalue(context->athlete->cyclist, GC_WBALTAU, 300).toInt()); // // Performance manager // perfManSTSLabel = new QLabel(tr("STS average (days)")); perfManLTSLabel = new QLabel(tr("LTS average (days)")); perfManSTSavgValidator = new QIntValidator(1,21,this); perfManLTSavgValidator = new QIntValidator(7,56,this); // get config or set to defaults QVariant perfManSTSVal = appsettings->cvalue(context->athlete->cyclist, GC_STS_DAYS); if (perfManSTSVal.isNull() || perfManSTSVal.toInt() == 0) perfManSTSVal = 7; QVariant perfManLTSVal = appsettings->cvalue(context->athlete->cyclist, GC_LTS_DAYS); if (perfManLTSVal.isNull() || perfManLTSVal.toInt() == 0) perfManLTSVal = 42; perfManSTSavg = new QLineEdit(perfManSTSVal.toString(),this); perfManSTSavg->setValidator(perfManSTSavgValidator); perfManLTSavg = new QLineEdit(perfManLTSVal.toString(),this); perfManLTSavg->setValidator(perfManLTSavgValidator); showSBToday = new QCheckBox(tr("PMC Stress Balance Today"), this); showSBToday->setChecked(appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt()); Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter; grid->addWidget(weightlabel, 1, 0, alignment); grid->addWidget(heightlabel, 2, 0, alignment); grid->addWidget(wbaltaulabel, 3, 0, alignment); grid->addWidget(weight, 1, 1, alignment); grid->addWidget(height, 2, 1, alignment); grid->addWidget(wbaltau, 3, 1, alignment); grid->addWidget(perfManSTSLabel, 4, 0, alignment); grid->addWidget(perfManSTSavg, 4, 1, alignment); grid->addWidget(perfManLTSLabel, 5, 0, alignment); grid->addWidget(perfManLTSavg, 5, 1, alignment); grid->addWidget(showSBToday, 6, 1, alignment); all->addLayout(grid); all->addStretch(); // save initial values for things we care about // note we don't worry about age or sex at this point // since they are not used, nor the W'bal tau used in // the realtime code. This is limited to stuff we // care about tracking as it is used by metrics // height and weight as stored (always metric) b4.height = appsettings->cvalue(context->athlete->cyclist, GC_HEIGHT).toDouble(); b4.weight = appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT).toDouble(); b4.lts = perfManLTSVal.toInt(); b4.sts = perfManSTSVal.toInt(); } qint32 RiderPhysPage::saveClicked() { appsettings->setCValue(context->athlete->cyclist, GC_WEIGHT, weight->value() * (context->athlete->useMetricUnits ? 1.0 : KG_PER_LB)); appsettings->setCValue(context->athlete->cyclist, GC_HEIGHT, height->value() * (context->athlete->useMetricUnits ? 1.0/100.0 : CM_PER_INCH/100.0)); appsettings->setCValue(context->athlete->cyclist, GC_WBALTAU, wbaltau->value()); // Performance Manager appsettings->setCValue(context->athlete->cyclist, GC_STS_DAYS, perfManSTSavg->text()); appsettings->setCValue(context->athlete->cyclist, GC_LTS_DAYS, perfManLTSavg->text()); appsettings->setCValue(context->athlete->cyclist, GC_SB_TODAY, (int) showSBToday->isChecked()); qint32 state=0; // PMC constants changed ? if(b4.lts != perfManLTSavg->text().toInt() || b4.sts != perfManSTSavg->text().toInt()) state += CONFIG_PMC; // default height or weight changed ? if (b4.height != appsettings->cvalue(context->athlete->cyclist, GC_HEIGHT).toDouble() || b4.weight != appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT).toDouble()) { state += CONFIG_ATHLETE; } return state; } // // Realtime devices page // DevicePage::DevicePage(QWidget *parent, Context *context) : QWidget(parent), context(context) { QVBoxLayout *mainLayout = new QVBoxLayout(this); DeviceTypes all; devices = all.getList(); addButton = new QPushButton(tr("+"),this); delButton = new QPushButton(tr("-"),this); deviceList = new QTableView(this); #ifdef Q_OS_MAC addButton->setText(tr("Add")); delButton->setText(tr("Delete")); deviceList->setAttribute(Qt::WA_MacShowFocusRect, 0); #else addButton->setFixedSize(20,20); delButton->setFixedSize(20,20); #endif deviceListModel = new deviceModel(this); // replace standard model with ours QItemSelectionModel *stdmodel = deviceList->selectionModel(); deviceList->setModel(deviceListModel); delete stdmodel; deviceList->setSortingEnabled(false); deviceList->setSelectionBehavior(QAbstractItemView::SelectRows); deviceList->horizontalHeader()->setStretchLastSection(true); deviceList->verticalHeader()->hide(); deviceList->setEditTriggers(QAbstractItemView::NoEditTriggers); deviceList->setSelectionMode(QAbstractItemView::SingleSelection); multiCheck = new QCheckBox(tr("Allow multiple devices in Train View"), this); multiCheck->setChecked(appsettings->value(this, TRAIN_MULTI, false).toBool()); mainLayout->addWidget(deviceList); QHBoxLayout *bottom = new QHBoxLayout; bottom->setSpacing(2); bottom->addWidget(multiCheck); bottom->addStretch(); bottom->addWidget(addButton); bottom->addWidget(delButton); mainLayout->addLayout(bottom); connect(addButton, SIGNAL(clicked()), this, SLOT(devaddClicked())); connect(delButton, SIGNAL(clicked()), this, SLOT(devdelClicked())); connect(context, SIGNAL(configChanged(qint32)), deviceListModel, SLOT(doReset())); } qint32 DevicePage::saveClicked() { // Save the device configuration... DeviceConfigurations all; all.writeConfig(deviceListModel->Configuration); appsettings->setValue(TRAIN_MULTI, multiCheck->isChecked()); return 0; } void DevicePage::devaddClicked() { DeviceConfiguration add; AddDeviceWizard *p = new AddDeviceWizard(context); p->show(); } void DevicePage::devdelClicked() { deviceListModel->del(); } // add a new configuration void deviceModel::add(DeviceConfiguration &newone) { insertRows(0,1, QModelIndex()); // insert name QModelIndex index = deviceModel::index(0,0, QModelIndex()); setData(index, newone.name, Qt::EditRole); // insert type index = deviceModel::index(0,1, QModelIndex()); setData(index, newone.type, Qt::EditRole); // insert portSpec index = deviceModel::index(0,2, QModelIndex()); setData(index, newone.portSpec, Qt::EditRole); // insert Profile index = deviceModel::index(0,3, QModelIndex()); setData(index, newone.deviceProfile, Qt::EditRole); // insert postProcess index = deviceModel::index(0,4, QModelIndex()); setData(index, newone.postProcess, Qt::EditRole); } // delete an existing configuration void deviceModel::del() { // which row is selected in the table? DevicePage *temp = static_cast(parent); QItemSelectionModel *selectionModel = temp->deviceList->selectionModel(); QModelIndexList indexes = selectionModel->selectedRows(); QModelIndex index; foreach (index, indexes) { //int row = this->mapToSource(index).row(); removeRows(index.row(), 1, QModelIndex()); } } deviceModel::deviceModel(QObject *parent) : QAbstractTableModel(parent) { this->parent = parent; // get current configuration DeviceConfigurations all; Configuration = all.getList(); } void deviceModel::doReset() { beginResetModel(); DeviceConfigurations all; Configuration = all.getList(); endResetModel(); } int deviceModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return Configuration.size(); } int deviceModel::columnCount(const QModelIndex &) const { return 5; } // setup the headings! QVariant deviceModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); // no display, no game! if (orientation == Qt::Horizontal) { switch (section) { case 0: return tr("Device Name"); case 1: return tr("Device Type"); case 2: return tr("Port Spec"); case 3: return tr("Profile"); case 4: return tr("Virtual"); default: return QVariant(); } } return QVariant(); } // return data item for row/col specified in index QVariant deviceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= Configuration.size() || index.row() < 0) return QVariant(); if (role == Qt::DisplayRole) { DeviceConfiguration Entry = Configuration.at(index.row()); switch(index.column()) { case 0 : return Entry.name; break; case 1 : { DeviceTypes all; DeviceType lookupType = all.getType (Entry.type); return lookupType.name; } break; case 2 : return Entry.portSpec; break; case 3 : return Entry.deviceProfile; case 4 : return Entry.postProcess; } } // how did we get here!? return QVariant(); } // update the model with new data bool deviceModel::insertRows(int position, int rows, const QModelIndex &index) { Q_UNUSED(index); beginInsertRows(QModelIndex(), position, position+rows-1); for (int row=0; row < rows; row++) { DeviceConfiguration emptyEntry; Configuration.insert(position, emptyEntry); } endInsertRows(); return true; } // delete a row! bool deviceModel::removeRows(int position, int rows, const QModelIndex &index) { Q_UNUSED(index); beginRemoveRows(QModelIndex(), position, position+rows-1); for (int row=0; row < rows; ++row) { Configuration.removeAt(position); } endRemoveRows(); return true; } bool deviceModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { int row = index.row(); DeviceConfiguration p = Configuration.value(row); switch (index.column()) { case 0 : //name p.name = value.toString(); break; case 1 : //type p.type = value.toInt(); break; case 2 : // spec p.portSpec = value.toString(); break; case 3 : // Profile p.deviceProfile = value.toString(); break; case 4 : // Profile p.postProcess = value.toInt(); break; } Configuration.replace(row,p); emit(dataChanged(index, index)); return true; } return false; } static void setSizes(QComboBox *p) { #ifdef Q_OS_MAC p->addItem("7"); p->addItem("9"); p->addItem("11"); p->addItem("13"); p->addItem("15"); p->addItem("17"); p->addItem("19"); p->addItem("21"); #else p->addItem("6"); p->addItem("8"); p->addItem("10"); p->addItem("12"); p->addItem("14"); p->addItem("16"); p->addItem("18"); p->addItem("20"); #endif } // // Appearances page // ColorsPage::ColorsPage(QWidget *parent) : QWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); // chrome style -- metal or flat currently offered QLabel *chromeLabel = new QLabel(tr("Styling" )); chromeCombo = new QComboBox(this); chromeCombo->addItem(tr("Metallic (Mac)")); chromeCombo->addItem(tr("Flat Color (Windows)")); #ifdef Q_OS_MAC // gets reset on yosemite anyway QString chrome = appsettings->value(this, GC_CHROME, "Mac").toString(); #else QString chrome = appsettings->value(this, GC_CHROME, "Flat").toString(); #endif if (chrome == "Mac") chromeCombo->setCurrentIndex(0); if (chrome == "Flat") chromeCombo->setCurrentIndex(1); themes = new QTreeWidget; themes->headerItem()->setText(0, tr("Swatch")); themes->headerItem()->setText(1, tr("Name")); themes->setColumnCount(2); themes->setColumnWidth(0,240); themes->setSelectionMode(QAbstractItemView::SingleSelection); //colors->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit themes->setUniformRowHeights(true); // causes height problems when adding - in case of non-text fields themes->setIndentation(0); //colors->header()->resizeSection(0,300); colors = new QTreeWidget; colors->headerItem()->setText(0, tr("Color")); colors->headerItem()->setText(1, tr("Select")); colors->setColumnCount(2); colors->setColumnWidth(0,350); colors->setSelectionMode(QAbstractItemView::NoSelection); //colors->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit colors->setUniformRowHeights(true); // causes height problems when adding - in case of non-text fields colors->setIndentation(0); //colors->header()->resizeSection(0,300); QLabel *antiAliasLabel = new QLabel(tr("Antialias")); antiAliased = new QCheckBox; antiAliased->setChecked(appsettings->value(this, GC_ANTIALIAS, true).toBool()); #ifndef Q_OS_MAC QLabel *rideScrollLabel = new QLabel(tr("Activity Scrollbar")); rideScroll = new QCheckBox; rideScroll->setChecked(appsettings->value(this, GC_RIDESCROLL, true).toBool()); QLabel *rideHeadLabel = new QLabel(tr("Activity Headings")); rideHead = new QCheckBox; rideHead->setChecked(appsettings->value(this, GC_RIDEHEAD, true).toBool()); #endif lineWidth = new QDoubleSpinBox; lineWidth->setMaximum(5); lineWidth->setMinimum(0.5); lineWidth->setSingleStep(0.5); applyTheme = new QPushButton(tr("Apply Theme")); lineWidth->setValue(appsettings->value(this, GC_LINEWIDTH, 0.5).toDouble()); QLabel *lineWidthLabel = new QLabel(tr("Line Width")); QLabel *defaultLabel = new QLabel(tr("Default")); QLabel *titlesLabel = new QLabel(tr("Title" )); QLabel *markerLabel = new QLabel(tr("Chart Markers" )); QLabel *chartLabel = new QLabel(tr("Chart Labels" )); QLabel *calendarLabel = new QLabel(tr("Calendar Text" )); def = new QFontComboBox(this); titles = new QFontComboBox(this); chartmarkers = new QFontComboBox(this); chartlabels = new QFontComboBox(this); calendar = new QFontComboBox(this); defaultSize = new QComboBox(this); setSizes(defaultSize); titlesSize = new QComboBox(this); setSizes(titlesSize); chartmarkersSize = new QComboBox(this); setSizes(chartmarkersSize); chartlabelsSize = new QComboBox(this); setSizes(chartlabelsSize); calendarSize = new QComboBox(this); setSizes(calendarSize); // get round QTBUG def->setCurrentIndex(0); def->setCurrentIndex(1); def->setCurrentFont(QFont()); titles->setCurrentIndex(0); titles->setCurrentIndex(1); titles->setCurrentFont(QFont()); chartmarkers->setCurrentIndex(0); chartmarkers->setCurrentIndex(1); chartmarkers->setCurrentFont(QFont()); chartlabels->setCurrentIndex(0); chartlabels->setCurrentIndex(1); chartlabels->setCurrentFont(QFont()); calendar->setCurrentIndex(0); calendar->setCurrentIndex(1); calendar->setCurrentFont(QFont()); QFont font; font.fromString(appsettings->value(this, GC_FONT_DEFAULT, QFont().toString()).toString()); def->setCurrentFont(font); font.fromString(appsettings->value(this, GC_FONT_TITLES, QFont().toString()).toString()); titles->setCurrentFont(font); font.fromString(appsettings->value(this, GC_FONT_CHARTMARKERS, QFont().toString()).toString()); chartmarkers->setCurrentFont(font); font.fromString(appsettings->value(this, GC_FONT_CHARTLABELS, QFont().toString()).toString()); chartlabels->setCurrentFont(font); font.fromString(appsettings->value(this, GC_FONT_CALENDAR, QFont().toString()).toString()); calendar->setCurrentFont(font); #ifdef Q_OS_MAC defaultSize->setCurrentIndex((appsettings->value(this, GC_FONT_DEFAULT_SIZE, 10).toInt() -7) / 2); titlesSize->setCurrentIndex((appsettings->value(this, GC_FONT_TITLES_SIZE, 10).toInt() -7) / 2); chartmarkersSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTMARKERS_SIZE, 8).toInt() -7) / 2); chartlabelsSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTLABELS_SIZE, 8).toInt() -7) / 2); calendarSize->setCurrentIndex((appsettings->value(this, GC_FONT_CALENDAR_SIZE, 8).toInt() -7) / 2); #else defaultSize->setCurrentIndex((appsettings->value(this, GC_FONT_DEFAULT_SIZE, 10).toInt() -6) / 2); titlesSize->setCurrentIndex((appsettings->value(this, GC_FONT_TITLES_SIZE, 10).toInt() -6) / 2); chartmarkersSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTMARKERS_SIZE, 8).toInt() -6) / 2); chartlabelsSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTLABELS_SIZE, 8).toInt() -6) / 2); calendarSize->setCurrentIndex((appsettings->value(this, GC_FONT_CALENDAR_SIZE, 8).toInt() -6) / 2); #endif QGridLayout *grid = new QGridLayout; grid->setSpacing(5); grid->addWidget(defaultLabel, 0,0); grid->addWidget(titlesLabel, 1,0); grid->addWidget(lineWidthLabel, 0,3); grid->addWidget(lineWidth, 0,4); grid->addWidget(antiAliasLabel, 1,3); grid->addWidget(antiAliased, 1,4); #ifndef Q_OS_MAC grid->addWidget(rideScrollLabel, 2,3); grid->addWidget(rideScroll, 2,4); grid->addWidget(rideHeadLabel, 3,3); grid->addWidget(rideHead, 3,4); #endif grid->addWidget(markerLabel, 2,0); grid->addWidget(chartLabel, 3,0); grid->addWidget(calendarLabel, 4,0); grid->addWidget(def, 0,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(titles, 1,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(chartmarkers, 2,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(chartlabels, 3,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(calendar, 4,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(defaultSize, 0,2, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(titlesSize, 1,2, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(chartmarkersSize, 2,2, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(chartlabelsSize, 3,2, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(calendarSize, 4,2, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget(chromeLabel, 5, 0); grid->addWidget(chromeCombo, 5, 1, Qt::AlignVCenter|Qt::AlignLeft); grid->setColumnStretch(0,1); grid->setColumnStretch(1,4); grid->setColumnStretch(2,1); grid->setColumnStretch(3,1); grid->setColumnStretch(4,4); mainLayout->addLayout(grid); colorTab = new QTabWidget(this); colorTab->addTab(themes, tr("Theme")); colorTab->addTab(colors, tr("Colors")); colorTab->setCornerWidget(applyTheme); mainLayout->addWidget(colorTab); colorSet = GCColor::colorSet(); for (int i=0; colorSet[i].name != ""; i++) { QTreeWidgetItem *add; ColorButton *colorButton = new ColorButton(this, colorSet[i].name, colorSet[i].color); add = new QTreeWidgetItem(colors->invisibleRootItem()); add->setText(0, colorSet[i].name); colors->setItemWidget(add, 1, colorButton); } connect(applyTheme, SIGNAL(clicked()), this, SLOT(applyThemeClicked())); foreach(ColorTheme theme, GCColor::themes().themes) { QTreeWidgetItem *add; ColorLabel *swatch = new ColorLabel(theme); swatch->setFixedHeight(30); add = new QTreeWidgetItem(themes->invisibleRootItem()); themes->setItemWidget(add, 0, swatch); add->setText(1, theme.name); } connect(colorTab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged())); // save initial values b4.alias = antiAliased->isChecked(); #ifndef Q_OS_MAC b4.scroll = rideScroll->isChecked(); b4.head = rideHead->isChecked(); #endif b4.line = lineWidth->value(); b4.chrome = chromeCombo->currentIndex(); b4.fingerprint = Colors::fingerprint(colorSet); } void ColorsPage::tabChanged() { // are we on the them page if (colorTab->currentIndex() == 0) applyTheme->show(); else applyTheme->hide(); } void ColorsPage::applyThemeClicked() { int index=0; // first check we have a selection! if (themes->currentItem() && (index=themes->invisibleRootItem()->indexOfChild(themes->currentItem())) >= 0) { // now get the theme selected ColorTheme theme = GCColor::themes().themes[index]; // reset to base colorSet = GCColor::defaultColorSet(); // reset the color selection tools colors->clear(); for (int i=0; colorSet[i].name != ""; i++) { QColor color; // apply theme to color switch(i) { case CPLOTBACKGROUND: case CRIDEPLOTBACKGROUND: case CTRENDPLOTBACKGROUND: case CTRAINPLOTBACKGROUND: color = theme.colors[0]; // background color break; // fg color theme.colors[1] not used YET XXX case CPLOTSYMBOL: case CRIDEPLOTXAXIS: case CRIDEPLOTYAXIS: case CPLOTMARKER: color = theme.colors[2]; // accent color break; case CPLOTSELECT: case CPLOTTRACKER: case CINTERVALHIGHLIGHTER: color = theme.colors[3]; // select color break; case CPLOTGRID: // grid doesn't have a theme color // we make it barely distinguishable from background { QColor bg = theme.colors[0]; if(bg == QColor(Qt::black)) color = QColor(30,30,30); else color = bg.darker(110); } break; case CCP: case CWBAL: case CRIDECP: color = theme.colors[4]; break; case CHEARTRATE: color = theme.colors[5]; break; case CSPEED: color = theme.colors[6]; break; case CPOWER: color = theme.colors[7]; break; case CCADENCE: color = theme.colors[8]; break; case CTORQUE: color = theme.colors[9]; break; default: color = colorSet[i].color; } QTreeWidgetItem *add; ColorButton *colorButton = new ColorButton(this, colorSet[i].name, color); add = new QTreeWidgetItem(colors->invisibleRootItem()); add->setText(0, colorSet[i].name); colors->setItemWidget(add, 1, colorButton); } } } qint32 ColorsPage::saveClicked() { // chrome style only has 2 types for now switch(chromeCombo->currentIndex()) { default: case 0: appsettings->setValue(GC_CHROME, "Mac"); #ifdef Q_OS_MAC // if on yosemite set ALWAYS do flat chrome if (QSysInfo::MacintoshVersion == 12) { appsettings->setValue(GC_CHROME, "Flat"); } #endif break; case 1: appsettings->setValue(GC_CHROME, "Flat"); break; } appsettings->setValue(GC_LINEWIDTH, lineWidth->value()); appsettings->setValue(GC_ANTIALIAS, antiAliased->isChecked()); #ifndef Q_OS_MAC appsettings->setValue(GC_RIDESCROLL, rideScroll->isChecked()); appsettings->setValue(GC_RIDEHEAD, rideHead->isChecked()); #endif // run down and get the current colors and save for (int i=0; colorSet[i].name != ""; i++) { QTreeWidgetItem *current = colors->invisibleRootItem()->child(i); QColor newColor = ((ColorButton*)colors->itemWidget(current, 1))->getColor(); QString colorstring = QString("%1:%2:%3").arg(newColor.red()) .arg(newColor.green()) .arg(newColor.blue()); appsettings->setValue(colorSet[i].setting, colorstring); } // Font appsettings->setValue(GC_FONT_DEFAULT, def->currentFont().toString()); appsettings->setValue(GC_FONT_TITLES, titles->currentFont().toString()); appsettings->setValue(GC_FONT_CHARTMARKERS, chartmarkers->currentFont().toString()); appsettings->setValue(GC_FONT_CHARTLABELS, chartlabels->currentFont().toString()); appsettings->setValue(GC_FONT_CALENDAR, calendar->currentFont().toString()); #ifdef Q_OS_MAC appsettings->setValue(GC_FONT_DEFAULT_SIZE, 7+(defaultSize->currentIndex()*2)); appsettings->setValue(GC_FONT_TITLES_SIZE, 7+(titlesSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CHARTMARKERS_SIZE, 7+(chartmarkersSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CHARTLABELS_SIZE, 7+(chartlabelsSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CALENDAR_SIZE, 7+(calendarSize->currentIndex()*2)); #else appsettings->setValue(GC_FONT_DEFAULT_SIZE, 6+(defaultSize->currentIndex()*2)); appsettings->setValue(GC_FONT_TITLES_SIZE, 6+(titlesSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CHARTMARKERS_SIZE, 6+(chartmarkersSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CHARTLABELS_SIZE, 6+(chartlabelsSize->currentIndex()*2)); appsettings->setValue(GC_FONT_CALENDAR_SIZE, 6+(calendarSize->currentIndex()*2)); #endif QFont font; font.fromString(appsettings->value(this, GC_FONT_DEFAULT, QFont().toString()).toString()); font.setPointSize(appsettings->value(this, GC_FONT_DEFAULT_SIZE, 13).toInt()); QApplication::setFont(font); // reread into colorset so we can check for changes GCColor::readConfig(); // did we change anything ? if(b4.alias != antiAliased->isChecked() || #ifndef Q_OS_MAC // not needed on mac b4.scroll != rideScroll->isChecked() || b4.head != rideHead->isChecked() || #endif b4.line != lineWidth->value() || b4.chrome != chromeCombo->currentIndex() || b4.fingerprint != Colors::fingerprint(colorSet)) return CONFIG_APPEARANCE; else return 0; } IntervalMetricsPage::IntervalMetricsPage(QWidget *parent) : QWidget(parent), changed(false) { HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_Metrics_Intervals)); availList = new QListWidget; availList->setSortingEnabled(true); availList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *availLayout = new QVBoxLayout; availLayout->addWidget(new QLabel(tr("Available Metrics"))); availLayout->addWidget(availList); selectedList = new QListWidget; selectedList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *selectedLayout = new QVBoxLayout; selectedLayout->addWidget(new QLabel(tr("Selected Metrics"))); selectedLayout->addWidget(selectedList); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); leftButton = new QToolButton(this); rightButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); leftButton->setArrowType(Qt::LeftArrow); rightButton->setArrowType(Qt::RightArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); leftButton->setFixedSize(20,20); rightButton->setFixedSize(20,20); #else upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); leftButton = new QPushButton("<"); rightButton = new QPushButton(">"); #endif QVBoxLayout *buttonGrid = new QVBoxLayout; QHBoxLayout *upLayout = new QHBoxLayout; QHBoxLayout *inexcLayout = new QHBoxLayout; QHBoxLayout *downLayout = new QHBoxLayout; upLayout->addStretch(); upLayout->addWidget(upButton); upLayout->addStretch(); inexcLayout->addStretch(); inexcLayout->addWidget(leftButton); inexcLayout->addWidget(rightButton); inexcLayout->addStretch(); downLayout->addStretch(); downLayout->addWidget(downButton); downLayout->addStretch(); buttonGrid->addStretch(); buttonGrid->addLayout(upLayout); buttonGrid->addLayout(inexcLayout); buttonGrid->addLayout(downLayout); buttonGrid->addStretch(); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->addLayout(availLayout); hlayout->addLayout(buttonGrid); hlayout->addLayout(selectedLayout); setLayout(hlayout); QString s; if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS)) s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString(); else s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT; QStringList selectedMetrics = s.split(","); const RideMetricFactory &factory = RideMetricFactory::instance(); for (int i = 0; i < factory.metricCount(); ++i) { QString symbol = factory.metricName(i); if (selectedMetrics.contains(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); availList->addItem(item); } foreach (QString symbol, selectedMetrics) { if (!factory.haveMetric(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); selectedList->addItem(item); } upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); rightButton->setEnabled(false); connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(leftButton, SIGNAL(clicked()), this, SLOT(leftClicked())); connect(rightButton, SIGNAL(clicked()), this, SLOT(rightClicked())); connect(availList, SIGNAL(itemSelectionChanged()), this, SLOT(availChanged())); connect(selectedList, SIGNAL(itemSelectionChanged()), this, SLOT(selectedChanged())); } void IntervalMetricsPage::upClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row > 0); selectedList->takeItem(row); selectedList->insertItem(row - 1, item); selectedList->setCurrentItem(item); changed = true; } void IntervalMetricsPage::downClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row < selectedList->count() - 1); selectedList->takeItem(row); selectedList->insertItem(row + 1, item); selectedList->setCurrentItem(item); changed = true; } void IntervalMetricsPage::leftClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); selectedList->takeItem(selectedList->row(item)); availList->addItem(item); changed = true; selectedChanged(); } void IntervalMetricsPage::rightClicked() { assert(!availList->selectedItems().isEmpty()); QListWidgetItem *item = availList->selectedItems().first(); availList->takeItem(availList->row(item)); selectedList->addItem(item); changed = true; } void IntervalMetricsPage::availChanged() { rightButton->setEnabled(!availList->selectedItems().isEmpty()); } void IntervalMetricsPage::selectedChanged() { if (selectedList->selectedItems().isEmpty()) { upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); return; } QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); if (row == 0) upButton->setEnabled(false); else upButton->setEnabled(true); if (row == selectedList->count() - 1) downButton->setEnabled(false); else downButton->setEnabled(true); leftButton->setEnabled(true); } qint32 IntervalMetricsPage::saveClicked() { if (!changed) return 0; QStringList metrics; for (int i = 0; i < selectedList->count(); ++i) metrics << selectedList->item(i)->data(Qt::UserRole).toString(); appsettings->setValue(GC_SETTINGS_INTERVAL_METRICS, metrics.join(",")); return 0; } BestsMetricsPage::BestsMetricsPage(QWidget *parent) : QWidget(parent), changed(false) { HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_Metrics_Best)); availList = new QListWidget; availList->setSortingEnabled(true); availList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *availLayout = new QVBoxLayout; availLayout->addWidget(new QLabel(tr("Available Metrics"))); availLayout->addWidget(availList); selectedList = new QListWidget; selectedList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *selectedLayout = new QVBoxLayout; selectedLayout->addWidget(new QLabel(tr("Selected Metrics"))); selectedLayout->addWidget(selectedList); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); leftButton = new QToolButton(this); rightButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); leftButton->setArrowType(Qt::LeftArrow); rightButton->setArrowType(Qt::RightArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); leftButton->setFixedSize(20,20); rightButton->setFixedSize(20,20); #else upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); leftButton = new QPushButton("<"); rightButton = new QPushButton(">"); #endif QVBoxLayout *buttonGrid = new QVBoxLayout; QHBoxLayout *upLayout = new QHBoxLayout; QHBoxLayout *inexcLayout = new QHBoxLayout; QHBoxLayout *downLayout = new QHBoxLayout; upLayout->addStretch(); upLayout->addWidget(upButton); upLayout->addStretch(); inexcLayout->addStretch(); inexcLayout->addWidget(leftButton); inexcLayout->addWidget(rightButton); inexcLayout->addStretch(); downLayout->addStretch(); downLayout->addWidget(downButton); downLayout->addStretch(); buttonGrid->addStretch(); buttonGrid->addLayout(upLayout); buttonGrid->addLayout(inexcLayout); buttonGrid->addLayout(downLayout); buttonGrid->addStretch(); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->addLayout(availLayout); hlayout->addLayout(buttonGrid); hlayout->addLayout(selectedLayout); setLayout(hlayout); QString s; if (appsettings->contains(GC_SETTINGS_BESTS_METRICS)) s = appsettings->value(this, GC_SETTINGS_BESTS_METRICS).toString(); else s = GC_SETTINGS_BESTS_METRICS_DEFAULT; QStringList selectedMetrics = s.split(","); const RideMetricFactory &factory = RideMetricFactory::instance(); for (int i = 0; i < factory.metricCount(); ++i) { QString symbol = factory.metricName(i); if (selectedMetrics.contains(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); availList->addItem(item); } foreach (QString symbol, selectedMetrics) { if (!factory.haveMetric(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); selectedList->addItem(item); } upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); rightButton->setEnabled(false); connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(leftButton, SIGNAL(clicked()), this, SLOT(leftClicked())); connect(rightButton, SIGNAL(clicked()), this, SLOT(rightClicked())); connect(availList, SIGNAL(itemSelectionChanged()), this, SLOT(availChanged())); connect(selectedList, SIGNAL(itemSelectionChanged()), this, SLOT(selectedChanged())); } void BestsMetricsPage::upClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row > 0); selectedList->takeItem(row); selectedList->insertItem(row - 1, item); selectedList->setCurrentItem(item); changed = true; } void BestsMetricsPage::downClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row < selectedList->count() - 1); selectedList->takeItem(row); selectedList->insertItem(row + 1, item); selectedList->setCurrentItem(item); changed = true; } void BestsMetricsPage::leftClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); selectedList->takeItem(selectedList->row(item)); availList->addItem(item); changed = true; selectedChanged(); } void BestsMetricsPage::rightClicked() { assert(!availList->selectedItems().isEmpty()); QListWidgetItem *item = availList->selectedItems().first(); availList->takeItem(availList->row(item)); selectedList->addItem(item); changed = true; } void BestsMetricsPage::availChanged() { rightButton->setEnabled(!availList->selectedItems().isEmpty()); } void BestsMetricsPage::selectedChanged() { if (selectedList->selectedItems().isEmpty()) { upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); return; } QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); if (row == 0) upButton->setEnabled(false); else upButton->setEnabled(true); if (row == selectedList->count() - 1) downButton->setEnabled(false); else downButton->setEnabled(true); leftButton->setEnabled(true); } qint32 BestsMetricsPage::saveClicked() { if (!changed) return 0; QStringList metrics; for (int i = 0; i < selectedList->count(); ++i) metrics << selectedList->item(i)->data(Qt::UserRole).toString(); appsettings->setValue(GC_SETTINGS_BESTS_METRICS, metrics.join(",")); return 0; } SummaryMetricsPage::SummaryMetricsPage(QWidget *parent) : QWidget(parent), changed(false) { HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_Metrics_Summary)); availList = new QListWidget; availList->setSortingEnabled(true); availList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *availLayout = new QVBoxLayout; availLayout->addWidget(new QLabel(tr("Available Metrics"))); availLayout->addWidget(availList); selectedList = new QListWidget; selectedList->setSelectionMode(QAbstractItemView::SingleSelection); QVBoxLayout *selectedLayout = new QVBoxLayout; selectedLayout->addWidget(new QLabel(tr("Selected Metrics"))); selectedLayout->addWidget(selectedList); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); leftButton = new QToolButton(this); rightButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); leftButton->setArrowType(Qt::LeftArrow); rightButton->setArrowType(Qt::RightArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); leftButton->setFixedSize(20,20); rightButton->setFixedSize(20,20); #else upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); leftButton = new QPushButton("<"); rightButton = new QPushButton(">"); #endif QVBoxLayout *buttonGrid = new QVBoxLayout; QHBoxLayout *upLayout = new QHBoxLayout; QHBoxLayout *inexcLayout = new QHBoxLayout; QHBoxLayout *downLayout = new QHBoxLayout; upLayout->addStretch(); upLayout->addWidget(upButton); upLayout->addStretch(); inexcLayout->addStretch(); inexcLayout->addWidget(leftButton); inexcLayout->addWidget(rightButton); inexcLayout->addStretch(); downLayout->addStretch(); downLayout->addWidget(downButton); downLayout->addStretch(); buttonGrid->addStretch(); buttonGrid->addLayout(upLayout); buttonGrid->addLayout(inexcLayout); buttonGrid->addLayout(downLayout); buttonGrid->addStretch(); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->addLayout(availLayout); hlayout->addLayout(buttonGrid); hlayout->addLayout(selectedLayout); setLayout(hlayout); QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString(); QStringList selectedMetrics = s.split(","); const RideMetricFactory &factory = RideMetricFactory::instance(); for (int i = 0; i < factory.metricCount(); ++i) { QString symbol = factory.metricName(i); if (selectedMetrics.contains(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); availList->addItem(item); } foreach (QString symbol, selectedMetrics) { if (!factory.haveMetric(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QTextEdit name(m->name()); // process html encoding of(TM) QListWidgetItem *item = new QListWidgetItem(name.toPlainText()); item->setData(Qt::UserRole, symbol); selectedList->addItem(item); } upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); rightButton->setEnabled(false); connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(leftButton, SIGNAL(clicked()), this, SLOT(leftClicked())); connect(rightButton, SIGNAL(clicked()), this, SLOT(rightClicked())); connect(availList, SIGNAL(itemSelectionChanged()), this, SLOT(availChanged())); connect(selectedList, SIGNAL(itemSelectionChanged()), this, SLOT(selectedChanged())); } void SummaryMetricsPage::upClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row > 0); selectedList->takeItem(row); selectedList->insertItem(row - 1, item); selectedList->setCurrentItem(item); changed = true; } void SummaryMetricsPage::downClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); assert(row < selectedList->count() - 1); selectedList->takeItem(row); selectedList->insertItem(row + 1, item); selectedList->setCurrentItem(item); changed = true; } void SummaryMetricsPage::leftClicked() { assert(!selectedList->selectedItems().isEmpty()); QListWidgetItem *item = selectedList->selectedItems().first(); selectedList->takeItem(selectedList->row(item)); availList->addItem(item); changed = true; selectedChanged(); } void SummaryMetricsPage::rightClicked() { assert(!availList->selectedItems().isEmpty()); QListWidgetItem *item = availList->selectedItems().first(); availList->takeItem(availList->row(item)); selectedList->addItem(item); changed = true; } void SummaryMetricsPage::availChanged() { rightButton->setEnabled(!availList->selectedItems().isEmpty()); } void SummaryMetricsPage::selectedChanged() { if (selectedList->selectedItems().isEmpty()) { upButton->setEnabled(false); downButton->setEnabled(false); leftButton->setEnabled(false); return; } QListWidgetItem *item = selectedList->selectedItems().first(); int row = selectedList->row(item); if (row == 0) upButton->setEnabled(false); else upButton->setEnabled(true); if (row == selectedList->count() - 1) downButton->setEnabled(false); else downButton->setEnabled(true); leftButton->setEnabled(true); } qint32 SummaryMetricsPage::saveClicked() { if (!changed) return 0; QStringList metrics; for (int i = 0; i < selectedList->count(); ++i) metrics << selectedList->item(i)->data(Qt::UserRole).toString(); appsettings->setValue(GC_SETTINGS_SUMMARY_METRICS, metrics.join(",")); return 0; } MetadataPage::MetadataPage(Context *context) : context(context) { QVBoxLayout *layout = new QVBoxLayout(this); // get current config using default file keywordDefinitions = context->athlete->rideMetadata()->getKeywords(); fieldDefinitions = context->athlete->rideMetadata()->getFields(); colorfield = context->athlete->rideMetadata()->getColorField(); defaultDefinitions = context->athlete->rideMetadata()->getDefaults(); // setup maintenance pages using current config fieldsPage = new FieldsPage(this, fieldDefinitions); keywordsPage = new KeywordsPage(this, keywordDefinitions); defaultsPage = new DefaultsPage(this, defaultDefinitions); processorPage = new ProcessorPage(context); tabs = new QTabWidget(this); tabs->addTab(fieldsPage, tr("Fields")); tabs->addTab(keywordsPage, tr("Notes Keywords")); tabs->addTab(defaultsPage, tr("Defaults")); tabs->addTab(processorPage, tr("Processing")); // refresh the keywords combo when change tabs .. will do more often than // needed but better that than less often than needed connect (tabs, SIGNAL(currentChanged(int)), keywordsPage, SLOT(pageSelected())); layout->addWidget(tabs); // save initial values b4.keywordFingerprint = KeywordDefinition::fingerprint(keywordDefinitions); b4.colorfield = colorfield; b4.fieldFingerprint = FieldDefinition::fingerprint(fieldDefinitions); } qint32 MetadataPage::saveClicked() { // get current state fieldsPage->getDefinitions(fieldDefinitions); keywordsPage->getDefinitions(keywordDefinitions); defaultsPage->getDefinitions(defaultDefinitions); // save settings appsettings->setValue(GC_RIDEBG, keywordsPage->rideBG->isChecked()); // write to metadata.xml RideMetadata::serialize(context->athlete->home->config().canonicalPath() + "/metadata.xml", keywordDefinitions, fieldDefinitions, colorfield, defaultDefinitions); // save processors config processorPage->saveClicked(); qint32 state = 0; if (b4.keywordFingerprint != KeywordDefinition::fingerprint(keywordDefinitions) || b4.colorfield != colorfield) state += CONFIG_NOTECOLOR; if (b4.fieldFingerprint != FieldDefinition::fingerprint(fieldDefinitions)) state += CONFIG_FIELDS; return state; } // little helper since we create/recreate combos // for field types all over the place (init, move up, move down) void FieldsPage::addFieldTypes(QComboBox *p) { p->addItem(tr("Text")); p->addItem(tr("Textbox")); p->addItem(tr("ShortText")); p->addItem(tr("Integer")); p->addItem(tr("Double")); p->addItem(tr("Date")); p->addItem(tr("Time")); p->addItem(tr("Checkbox")); } // // Calendar coloring page // KeywordsPage::KeywordsPage(MetadataPage *parent, QListkeywordDefinitions) : QWidget(parent), parent(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_DataFields_Notes_Keywords)); QHBoxLayout *field = new QHBoxLayout(); fieldLabel = new QLabel(tr("Field"),this); fieldChooser = new QComboBox(this); field->addWidget(fieldLabel); field->addWidget(fieldChooser); field->addStretch(); mainLayout->addLayout(field); rideBG = new QCheckBox(tr("Use for Background")); rideBG->setChecked(appsettings->value(this, GC_RIDEBG, false).toBool()); field->addWidget(rideBG); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(upButton); actionButtons->addWidget(downButton); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); keywords = new QTreeWidget; keywords->headerItem()->setText(0, tr("Keyword")); keywords->headerItem()->setText(1, tr("Color")); keywords->headerItem()->setText(2, tr("Related Notes Words")); keywords->setColumnCount(3); keywords->setSelectionMode(QAbstractItemView::SingleSelection); keywords->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit //keywords->setUniformRowHeights(true); // causes height problems when adding - in case of non-text fields keywords->setIndentation(0); //keywords->header()->resizeSection(0,100); //keywords->header()->resizeSection(1,45); foreach(KeywordDefinition keyword, keywordDefinitions) { QTreeWidgetItem *add; ColorButton *colorButton = new ColorButton(this, keyword.name, keyword.color); add = new QTreeWidgetItem(keywords->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // keyword add->setText(0, keyword.name); // color button add->setTextAlignment(1, Qt::AlignHCenter); keywords->setItemWidget(add, 1, colorButton); QString text; for (int i=0; i< keyword.tokens.count(); i++) { if (i != keyword.tokens.count()-1) text += keyword.tokens[i] + ","; else text += keyword.tokens[i]; } // notes texts add->setText(2, text); } keywords->setCurrentItem(keywords->invisibleRootItem()->child(0)); mainLayout->addWidget(keywords); mainLayout->addLayout(actionButtons); // connect up slots connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(fieldChooser, SIGNAL(currentIndexChanged(int)), this, SLOT(colorfieldChanged())); } void KeywordsPage::pageSelected() { SpecialFields sp; QString prev = ""; // remember what was selected, if anything? if (fieldChooser->count()) { prev = sp.internalName(fieldChooser->itemText(fieldChooser->currentIndex())); parent->colorfield = prev; } else prev = parent->colorfield; // load in texts from metadata fieldChooser->clear(); // get the current fields definitions QList fromFieldsPage; parent->fieldsPage->getDefinitions(fromFieldsPage); foreach(FieldDefinition x, fromFieldsPage) { if (x.type < 3) fieldChooser->addItem(sp.displayName(x.name)); } fieldChooser->setCurrentIndex(fieldChooser->findText(sp.displayName(prev))); } void KeywordsPage::colorfieldChanged() { SpecialFields sp; int index = fieldChooser->currentIndex(); if (index >=0) parent->colorfield = sp.internalName(fieldChooser->itemText(fieldChooser->currentIndex())); } void KeywordsPage::upClicked() { if (keywords->currentItem()) { int index = keywords->invisibleRootItem()->indexOfChild(keywords->currentItem()); if (index == 0) return; // its at the top already // movin on up! QWidget *button = keywords->itemWidget(keywords->currentItem(),1); ColorButton *colorButton = new ColorButton(this, ((ColorButton*)button)->getName(), ((ColorButton*)button)->getColor()); QTreeWidgetItem* moved = keywords->invisibleRootItem()->takeChild(index); keywords->invisibleRootItem()->insertChild(index-1, moved); keywords->setItemWidget(moved, 1, colorButton); keywords->setCurrentItem(moved); //LTMSettings save = (*presets)[index]; //presets->removeAt(index); //presets->insert(index-1, save); } } void KeywordsPage::downClicked() { if (keywords->currentItem()) { int index = keywords->invisibleRootItem()->indexOfChild(keywords->currentItem()); if (index == (keywords->invisibleRootItem()->childCount()-1)) return; // its at the bottom already // movin on up! QWidget *button = keywords->itemWidget(keywords->currentItem(),1); ColorButton *colorButton = new ColorButton(this, ((ColorButton*)button)->getName(), ((ColorButton*)button)->getColor()); QTreeWidgetItem* moved = keywords->invisibleRootItem()->takeChild(index); keywords->invisibleRootItem()->insertChild(index+1, moved); keywords->setItemWidget(moved, 1, colorButton); keywords->setCurrentItem(moved); } } void KeywordsPage::renameClicked() { // which one is selected? if (keywords->currentItem()) keywords->editItem(keywords->currentItem(), 0); } void KeywordsPage::addClicked() { int index = keywords->invisibleRootItem()->indexOfChild(keywords->currentItem()); if (index < 0) index = 0; QTreeWidgetItem *add; ColorButton *colorButton = new ColorButton(this, tr("New"), QColor(Qt::blue)); add = new QTreeWidgetItem; keywords->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); // keyword QString text = tr("New"); for (int i=0; keywords->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // color button add->setTextAlignment(1, Qt::AlignHCenter); keywords->setItemWidget(add, 1, colorButton); // notes texts add->setText(2, ""); } void KeywordsPage::deleteClicked() { if (keywords->currentItem()) { int index = keywords->invisibleRootItem()->indexOfChild(keywords->currentItem()); // zap! delete keywords->invisibleRootItem()->takeChild(index); } } void KeywordsPage::getDefinitions(QList &keywordList) { // clear current just in case keywordList.clear(); for (int idx =0; idx < keywords->invisibleRootItem()->childCount(); idx++) { KeywordDefinition add; QTreeWidgetItem *item = keywords->invisibleRootItem()->child(idx); add.name = item->text(0); add.color = ((ColorButton*)keywords->itemWidget(item, 1))->getColor(); add.tokens = item->text(2).split(",", QString::SkipEmptyParts); keywordList.append(add); } } // // Ride metadata page // FieldsPage::FieldsPage(QWidget *parent, QListfieldDefinitions) : QWidget(parent) { QGridLayout *mainLayout = new QGridLayout(this); HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_DataFields_Fields)); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(upButton); actionButtons->addWidget(downButton); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); fields = new QTreeWidget; fields->headerItem()->setText(0, tr("Screen Tab")); fields->headerItem()->setText(1, tr("Field")); fields->headerItem()->setText(2, tr("Type")); fields->headerItem()->setText(3, tr("Values")); fields->headerItem()->setText(4, tr("Diary")); fields->setColumnWidth(0,80); fields->setColumnWidth(1,100); fields->setColumnWidth(2,100); fields->setColumnWidth(3,80); fields->setColumnWidth(4,20); fields->setColumnCount(5); fields->setSelectionMode(QAbstractItemView::SingleSelection); fields->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit //fields->setUniformRowHeights(true); // causes height problems when adding - in case of non-text fields fields->setIndentation(0); SpecialFields specials; SpecialTabs specialTabs; foreach(FieldDefinition field, fieldDefinitions) { QTreeWidgetItem *add; QComboBox *comboButton = new QComboBox(this); QCheckBox *checkBox = new QCheckBox("", this); checkBox->setChecked(field.diary); addFieldTypes(comboButton); comboButton->setCurrentIndex(field.type); add = new QTreeWidgetItem(fields->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, specialTabs.displayName(field.tab)); // field name add->setText(1, specials.displayName(field.name)); // values add->setText(3, field.values.join(",")); // type button add->setTextAlignment(2, Qt::AlignHCenter); fields->setItemWidget(add, 2, comboButton); fields->setItemWidget(add, 4, checkBox); } fields->setCurrentItem(fields->invisibleRootItem()->child(0)); mainLayout->addWidget(fields, 0,0); mainLayout->addLayout(actionButtons, 1,0); // connect up slots connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void FieldsPage::upClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index == 0) return; // its at the top already // movin on up! QWidget *button = fields->itemWidget(fields->currentItem(),2); QWidget *check = fields->itemWidget(fields->currentItem(),4); QComboBox *comboButton = new QComboBox(this); addFieldTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QCheckBox *checkBox = new QCheckBox("", this); checkBox->setChecked(((QCheckBox*)check)->isChecked()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index-1, moved); fields->setItemWidget(moved, 2, comboButton); fields->setItemWidget(moved, 4, checkBox); fields->setCurrentItem(moved); } } void FieldsPage::downClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index == (fields->invisibleRootItem()->childCount()-1)) return; // its at the bottom already QWidget *button = fields->itemWidget(fields->currentItem(),2); QWidget *check = fields->itemWidget(fields->currentItem(),4); QComboBox *comboButton = new QComboBox(this); addFieldTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QCheckBox *checkBox = new QCheckBox("", this); checkBox->setChecked(((QCheckBox*)check)->isChecked()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index+1, moved); fields->setItemWidget(moved, 2, comboButton); fields->setItemWidget(moved, 4, checkBox); fields->setCurrentItem(moved); } } void FieldsPage::renameClicked() { // which one is selected? if (fields->currentItem()) fields->editItem(fields->currentItem(), 0); } void FieldsPage::addClicked() { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index < 0) index = 0; QTreeWidgetItem *add; QComboBox *comboButton = new QComboBox(this); addFieldTypes(comboButton); QCheckBox *checkBox = new QCheckBox("", this); add = new QTreeWidgetItem; fields->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); // field QString text = tr("New"); for (int i=0; fields->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); // type button add->setTextAlignment(2, Qt::AlignHCenter); fields->setItemWidget(add, 2, comboButton); fields->setItemWidget(add, 4, checkBox); } void FieldsPage::deleteClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); // zap! delete fields->invisibleRootItem()->takeChild(index); } } void FieldsPage::getDefinitions(QList &fieldList) { SpecialFields sp; SpecialTabs st; QStringList checkdups; // clear current just in case fieldList.clear(); for (int idx =0; idx < fields->invisibleRootItem()->childCount(); idx++) { FieldDefinition add; QTreeWidgetItem *item = fields->invisibleRootItem()->child(idx); // silently ignore duplicates if (checkdups.contains(item->text(1))) continue; else checkdups << item->text(1); add.tab = st.internalName(item->text(0)); add.name = sp.internalName(item->text(1)); add.values = item->text(3).split(QRegExp("(, *|,)"), QString::KeepEmptyParts); add.diary = ((QCheckBox*)fields->itemWidget(item, 4))->isChecked(); if (sp.isMetric(add.name)) add.type = 4; else add.type = ((QComboBox*)fields->itemWidget(item, 2))->currentIndex(); fieldList.append(add); } } // // Data processors config page // ProcessorPage::ProcessorPage(Context *context) : context(context) { // get the available processors const DataProcessorFactory &factory = DataProcessorFactory::instance(); processors = factory.getProcessors(); QGridLayout *mainLayout = new QGridLayout(this); processorTree = new QTreeWidget; processorTree->headerItem()->setText(0, tr("Processor")); processorTree->headerItem()->setText(1, tr("Apply")); processorTree->headerItem()->setText(2, tr("Settings")); processorTree->headerItem()->setText(3, "Technical Name of Processor"); // add invisible Column with technical name processorTree->setColumnCount(4); processorTree->setSelectionMode(QAbstractItemView::NoSelection); processorTree->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit processorTree->setUniformRowHeights(true); processorTree->setIndentation(0); //processorTree->header()->resizeSection(0,150); HelpWhatsThis *help = new HelpWhatsThis(processorTree); processorTree->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_DataFields_Processing)); // iterate over all the processors and add an entry to the QMapIterator i(processors); i.toFront(); while (i.hasNext()) { i.next(); QTreeWidgetItem *add; add = new QTreeWidgetItem(processorTree->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); // Processor Name: it shows the localized name add->setText(0, i.value()->name()); // Processer Key - for Settings add->setText(3, i.key()); // Auto or Manual run? QComboBox *comboButton = new QComboBox(this); comboButton->addItem(tr("Manual")); comboButton->addItem(tr("Import")); processorTree->setItemWidget(add, 1, comboButton); QString configsetting = QString("dp/%1/apply").arg(i.key()); if (appsettings->value(NULL, GC_QSETTINGS_GLOBAL_GENERAL+configsetting, "Manual").toString() == "Manual") comboButton->setCurrentIndex(0); else comboButton->setCurrentIndex(1); // Get and Set the Config Widget DataProcessorConfig *config = i.value()->processorConfig(this); config->readConfig(); processorTree->setItemWidget(add, 2, config); } processorTree->setColumnHidden(3, true); mainLayout->addWidget(processorTree, 0,0); } qint32 ProcessorPage::saveClicked() { // call each processor config widget's saveConfig() to // write away separately for (int i=0; iinvisibleRootItem()->childCount(); i++) { // auto (which means run on import) or manual? QString configsetting = QString("dp/%1/apply").arg(processorTree->invisibleRootItem()->child(i)->text(3)); QString apply = ((QComboBox*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 1)))->currentIndex() ? "Auto" : "Manual"; appsettings->setValue(GC_QSETTINGS_GLOBAL_GENERAL+configsetting, apply); ((DataProcessorConfig*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 2)))->saveConfig(); } return 0; } // // Default values page // DefaultsPage::DefaultsPage(QWidget *parent, QListdefaultDefinitions) : QWidget(parent) { QGridLayout *mainLayout = new QGridLayout(this); HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::Preferences_DataFields_Defaults)); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(upButton); actionButtons->addWidget(downButton); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); defaults = new QTreeWidget; defaults->headerItem()->setText(0, tr("Field")); defaults->headerItem()->setText(1, tr("Value")); defaults->headerItem()->setText(2, tr("Linked field")); defaults->headerItem()->setText(3, tr("Default Value")); defaults->setColumnWidth(0,80); defaults->setColumnWidth(1,100); defaults->setColumnWidth(2,80); defaults->setColumnWidth(3,100); defaults->setColumnCount(4); defaults->setSelectionMode(QAbstractItemView::SingleSelection); defaults->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit //defaults->setUniformRowHeights(true); // causes height problems when adding - in case of non-text fields defaults->setIndentation(0); SpecialFields specials; foreach(DefaultDefinition adefault, defaultDefinitions) { QTreeWidgetItem *add; add = new QTreeWidgetItem(defaults->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // field name add->setText(0, specials.displayName(adefault.field)); // value add->setText(1, adefault.value); // Linked field add->setText(2, adefault.linkedField); // Default Value add->setText(3, adefault.linkedValue); } defaults->setCurrentItem(defaults->invisibleRootItem()->child(0)); mainLayout->addWidget(defaults, 0,0); mainLayout->addLayout(actionButtons, 1,0); // connect up slots connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void DefaultsPage::upClicked() { if (defaults->currentItem()) { int index = defaults->invisibleRootItem()->indexOfChild(defaults->currentItem()); if (index == 0) return; // its at the top already QTreeWidgetItem* moved = defaults->invisibleRootItem()->takeChild(index); defaults->invisibleRootItem()->insertChild(index-1, moved); defaults->setCurrentItem(moved); } } void DefaultsPage::downClicked() { if (defaults->currentItem()) { int index = defaults->invisibleRootItem()->indexOfChild(defaults->currentItem()); if (index == (defaults->invisibleRootItem()->childCount()-1)) return; // its at the bottom already QTreeWidgetItem* moved = defaults->invisibleRootItem()->takeChild(index); defaults->invisibleRootItem()->insertChild(index+1, moved); defaults->setCurrentItem(moved); } } void DefaultsPage::addClicked() { int index = defaults->invisibleRootItem()->indexOfChild(defaults->currentItem()); if (index < 0) index = 0; QTreeWidgetItem *add; add = new QTreeWidgetItem; defaults->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); // field QString text = tr("New"); for (int i=0; defaults->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); } void DefaultsPage::deleteClicked() { if (defaults->currentItem()) { int index = defaults->invisibleRootItem()->indexOfChild(defaults->currentItem()); // zap! delete defaults->invisibleRootItem()->takeChild(index); } } void DefaultsPage::getDefinitions(QList &defaultList) { SpecialFields sp; // clear current just in case defaultList.clear(); for (int idx =0; idx < defaults->invisibleRootItem()->childCount(); idx++) { DefaultDefinition add; QTreeWidgetItem *item = defaults->invisibleRootItem()->child(idx); add.field = sp.internalName(item->text(0)); add.value = item->text(1); add.linkedField = sp.internalName(item->text(2)); add.linkedValue = item->text(3); defaultList.append(add); } } // // Power Zone Config page // ZonePage::ZonePage(Context *context) : context(context) { QVBoxLayout *layout = new QVBoxLayout(this); // get current config by reading it in (leave mainwindow zones alone) QFile zonesFile(context->athlete->home->config().canonicalPath() + "/power.zones"); if (zonesFile.exists()) { zones.read(zonesFile); zonesFile.close(); b4Fingerprint = zones.getFingerprint(); // remember original state } // setup maintenance pages using current config schemePage = new SchemePage(this); cpPage = new CPPage(this); tabs = new QTabWidget(this); tabs->addTab(cpPage, tr("Critical Power")); tabs->addTab(schemePage, tr("Default")); layout->addWidget(tabs); } qint32 ZonePage::saveClicked() { // write zones.setScheme(schemePage->getScheme()); zones.write(context->athlete->home->config()); // re-read Zones in case it changed QFile zonesFile(context->athlete->home->config().canonicalPath() + "/power.zones"); context->athlete->zones_->read(zonesFile); // use CP for FTP? appsettings->setCValue(context->athlete->cyclist, GC_USE_CP_FOR_FTP, cpPage->useCPForFTPCheckBox->checkState()); // did we change ? if (zones.getFingerprint() != b4Fingerprint) return CONFIG_ZONES; else return 0; } SchemePage::SchemePage(ZonePage* zonePage) : zonePage(zonePage) { QVBoxLayout *mainLayout = new QVBoxLayout(this); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); scheme = new QTreeWidget; scheme->headerItem()->setText(0, tr("Short")); scheme->headerItem()->setText(1, tr("Long")); scheme->headerItem()->setText(2, tr("Percent of CP")); scheme->setColumnCount(3); scheme->setSelectionMode(QAbstractItemView::SingleSelection); scheme->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit scheme->setUniformRowHeights(true); scheme->setIndentation(0); //scheme->header()->resizeSection(0,90); //scheme->header()->resizeSection(1,200); //scheme->header()->resizeSection(2,80); // setup list for (int i=0; i< zonePage->zones.getScheme().nzones_default; i++) { QTreeWidgetItem *add = new QTreeWidgetItem(scheme->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, zonePage->zones.getScheme().zone_default_name[i]); // field name add->setText(1, zonePage->zones.getScheme().zone_default_desc[i]); // low QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(zonePage->zones.getScheme().zone_default[i]); scheme->setItemWidget(add, 2, loedit); } mainLayout->addWidget(scheme); mainLayout->addLayout(actionButtons); // button connect connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void SchemePage::addClicked() { // are we at maximum already? if (scheme->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } int index = scheme->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(100); scheme->invisibleRootItem()->insertChild(index, add); scheme->setItemWidget(add, 2, loedit); // Short QString text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); } void SchemePage::renameClicked() { // which one is selected? if (scheme->currentItem()) scheme->editItem(scheme->currentItem(), 0); } void SchemePage::deleteClicked() { if (scheme->currentItem()) { int index = scheme->invisibleRootItem()->indexOfChild(scheme->currentItem()); delete scheme->invisibleRootItem()->takeChild(index); } } // just for qSorting struct schemeitem { QString name, desc; int lo; double trimp; bool operator<(schemeitem right) const { return lo < right.lo; } }; ZoneScheme SchemePage::getScheme() { // read the scheme widget and return a scheme object QList table; ZoneScheme results; // read back the details from the table for (int i=0; iinvisibleRootItem()->childCount(); i++) { schemeitem add; add.name = scheme->invisibleRootItem()->child(i)->text(0); add.desc = scheme->invisibleRootItem()->child(i)->text(1); add.lo = ((QDoubleSpinBox *)(scheme->itemWidget(scheme->invisibleRootItem()->child(i), 2)))->value(); table.append(add); } // sort the list into ascending order qSort(table); // now update the results results.nzones_default = 0; foreach(schemeitem zone, table) { results.nzones_default++; results.zone_default.append(zone.lo); results.zone_default_is_pct.append(true); results.zone_default_name.append(zone.name); results.zone_default_desc.append(zone.desc); } return results; } CPPage::CPPage(ZonePage* zonePage) : zonePage(zonePage) { active = false; QGridLayout *mainLayout = new QGridLayout(this); mainLayout->setSpacing(10); updateButton = new QPushButton(tr("Update")); updateButton->hide(); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); updateButton->setFixedSize(60,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); updateButton->setText(tr("Update")); deleteButton->setText(tr("Delete")); #endif defaultButton = new QPushButton(tr("Def")); defaultButton->hide(); addZoneButton = new QPushButton(tr("+")); deleteZoneButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addZoneButton->setFixedSize(20,20); deleteZoneButton->setFixedSize(20,20); #else addZoneButton->setText(tr("Add")); deleteZoneButton->setText(tr("Delete")); #endif QHBoxLayout *zoneButtons = new QHBoxLayout; zoneButtons->addStretch(); zoneButtons->setSpacing(0); zoneButtons->addWidget(addZoneButton); zoneButtons->addWidget(deleteZoneButton); zoneButtons->addWidget(defaultButton); QHBoxLayout *addLayout = new QHBoxLayout; QLabel *dateLabel = new QLabel(tr("From Date")); QLabel *cpLabel = new QLabel(tr("Critical Power")); QLabel *ftpLabel = new QLabel(tr("FTP")); QLabel *wLabel = new QLabel(tr("W'")); QLabel *pmaxLabel = new QLabel(tr("Pmax")); dateEdit = new QDateEdit; dateEdit->setDate(QDate::currentDate()); // Use CP for FTP bool useCPForFTP = appsettings->cvalue(zonePage->context->athlete->cyclist, GC_USE_CP_FOR_FTP, Qt::Checked).toBool(); useCPForFTPCheckBox = new QCheckBox(tr("Use CP for FTP"), this); useCPForFTPCheckBox->setCheckState(useCPForFTP ? Qt::Checked : Qt::Unchecked); cpEdit = new QDoubleSpinBox; cpEdit->setMinimum(0); cpEdit->setMaximum(1000); cpEdit->setSingleStep(1.0); cpEdit->setDecimals(0); ftpEdit = new QDoubleSpinBox; ftpEdit->setMinimum(0); ftpEdit->setMaximum(100000); ftpEdit->setSingleStep(100); ftpEdit->setDecimals(0); wEdit = new QDoubleSpinBox; wEdit->setMinimum(0); wEdit->setMaximum(100000); wEdit->setSingleStep(100); wEdit->setDecimals(0); pmaxEdit = new QDoubleSpinBox; pmaxEdit->setMinimum(0); pmaxEdit->setMaximum(3000); pmaxEdit->setSingleStep(1.0); pmaxEdit->setDecimals(0); QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(cpLabel); actionButtons->addWidget(cpEdit); actionButtons->addWidget(ftpLabel); actionButtons->addWidget(ftpEdit); actionButtons->addWidget(wLabel); actionButtons->addWidget(wEdit); actionButtons->addWidget(pmaxLabel); actionButtons->addWidget(pmaxEdit); actionButtons->addStretch(); actionButtons->addWidget(updateButton); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); //actionButtons->addWidget(defaultButton); // moved to zoneButtons addLayout->addWidget(dateLabel); addLayout->addWidget(dateEdit); addLayout->addStretch(); addLayout->addWidget(useCPForFTPCheckBox); ranges = new QTreeWidget; int column = 0; ranges->headerItem()->setText(column++, tr("From Date")); ranges->headerItem()->setText(column++, tr("Critical Power")); if (useCPForFTP) { ranges->headerItem()->setText(column++, tr("FTP")); } ranges->headerItem()->setText(column++, tr("W'")); ranges->headerItem()->setText(column++, tr("Pmax")); ranges->setColumnCount(column); ranges->setSelectionMode(QAbstractItemView::SingleSelection); //ranges->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit ranges->setUniformRowHeights(true); ranges->setIndentation(0); //ranges->header()->resizeSection(0,180); // setup list of ranges for (int i=0; i< zonePage->zones.getRangeSize(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(ranges->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); // Embolden ranges with manually configured zones QFont font; font.setWeight(zonePage->zones.getZoneRange(i).zonesSetFromCP ? QFont::Normal : QFont::Black); int column = 0; // date add->setText(column, zonePage->zones.getStartDate(i).toString(tr("MMM d, yyyy"))); add->setFont(column++, font); // CP add->setText(column, QString("%1").arg(zonePage->zones.getCP(i))); add->setFont(column++, font); if (useCPForFTP) { // FTP add->setText(column, QString("%1").arg(zonePage->zones.getFTP(i))); add->setFont(column++, font); } // W' add->setText(column, QString("%1").arg(zonePage->zones.getWprime(i))); add->setFont(column++, font); // Pmax add->setText(column, QString("%1").arg(zonePage->zones.getPmax(i))); add->setFont(column++, font); } zones = new QTreeWidget; zones->headerItem()->setText(0, tr("Short")); zones->headerItem()->setText(1, tr("Long")); zones->headerItem()->setText(2, tr("From Watts")); zones->setColumnCount(3); zones->setSelectionMode(QAbstractItemView::SingleSelection); zones->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit zones->setUniformRowHeights(true); zones->setIndentation(0); //zones->header()->resizeSection(0,80); //zones->header()->resizeSection(1,150); mainLayout->addLayout(addLayout, 0,0); mainLayout->addLayout(actionButtons, 1,0); mainLayout->addWidget(ranges, 2,0); mainLayout->addLayout(zoneButtons, 3,0); mainLayout->addWidget(zones, 4,0); // edit connect connect(dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(rangeEdited())); connect(cpEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); connect(ftpEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); connect(wEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); connect(pmaxEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); // button connect connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(updateButton, SIGNAL(clicked()), this, SLOT(editClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(defaultButton, SIGNAL(clicked()), this, SLOT(defaultClicked())); connect(addZoneButton, SIGNAL(clicked()), this, SLOT(addZoneClicked())); connect(deleteZoneButton, SIGNAL(clicked()), this, SLOT(deleteZoneClicked())); connect(ranges, SIGNAL(itemSelectionChanged()), this, SLOT(rangeSelectionChanged())); connect(zones, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(zonesChanged())); } void CPPage::addClicked() { // get current scheme zonePage->zones.setScheme(zonePage->schemePage->getScheme()); int cp = cpEdit->value(); if( cp <= 0 ) { QMessageBox err; err.setText(tr("CP must be > 0")); err.setIcon(QMessageBox::Warning); err.exec(); return; } //int index = ranges->invisibleRootItem()->childCount(); int wp = wEdit->value() ? wEdit->value() : 20000; if (wp < 1000) wp *= 1000; // entered in kJ we want joules int pmax = pmaxEdit->value() ? pmaxEdit->value() : 1000; int index = zonePage->zones.addZoneRange(dateEdit->date(), cpEdit->value(), ftpEdit->value(), wp, pmax); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() & ~Qt::ItemIsEditable); ranges->invisibleRootItem()->insertChild(index, add); int column = 0; // date add->setText(column++, dateEdit->date().toString(tr("MMM d, yyyy"))); // CP add->setText(column++, QString("%1").arg(cpEdit->value())); // FTP add->setText(column++, QString("%1").arg(ftpEdit->value())); // W' add->setText(column++, QString("%1").arg(wp)); // Pmax add->setText(column++, QString("%1").arg(pmax)); } void CPPage::editClicked() { // get current scheme zonePage->zones.setScheme(zonePage->schemePage->getScheme()); int cp = cpEdit->value(); if( cp <= 0 ){ QMessageBox err; err.setText(tr("CP must be > 0")); err.setIcon(QMessageBox::Warning); err.exec(); return; } int ftp = ftpEdit->value() ? ftpEdit->value() : cp; int wp = wEdit->value() ? wEdit->value() : 20000; if (wp < 1000) wp *= 1000; // entered in kJ we want joules int pmax = pmaxEdit->value() ? pmaxEdit->value() : 1000; QTreeWidgetItem *edit = ranges->selectedItems().at(0); int index = ranges->indexOfTopLevelItem(edit); int columns = 0; // date zonePage->zones.setStartDate(index, dateEdit->date()); edit->setText(columns++, dateEdit->date().toString(tr("MMM d, yyyy"))); // CP zonePage->zones.setCP(index, cp); edit->setText(columns++, QString("%1").arg(cp)); zonePage->zones.setFTP(index, ftp); edit->setText(columns++, QString("%1").arg(ftp)); // W' zonePage->zones.setWprime(index, wp); edit->setText(columns++, QString("%1").arg(wp)); // Pmax zonePage->zones.setPmax(index, pmax); edit->setText(columns++, QString("%1").arg(pmax)); } void CPPage::deleteClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); delete ranges->invisibleRootItem()->takeChild(index); zonePage->zones.deleteRange(index); } } void CPPage::defaultClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); ZoneRange current = zonePage->zones.getZoneRange(index); // unbold QFont font; font.setWeight(QFont::Normal); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); // set the range to use defaults on the scheme page zonePage->zones.setScheme(zonePage->schemePage->getScheme()); zonePage->zones.setZonesFromCP(index); // hide the default button since we are now using defaults defaultButton->hide(); // update the zones display rangeSelectionChanged(); } } void CPPage::rangeEdited() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); QDate date = dateEdit->date(); QDate odate = zonePage->zones.getStartDate(index); int cp = cpEdit->value(); int ocp = zonePage->zones.getCP(index); int ftp = ftpEdit->value(); int oftp = zonePage->zones.getFTP(index); int wp = wEdit->value(); int owp = zonePage->zones.getWprime(index); int pmax = pmaxEdit->value(); int opmax = zonePage->zones.getPmax(index); if (date != odate || cp != ocp || ftp != oftp || wp != owp || pmax != opmax) updateButton->show(); else updateButton->hide(); } } void CPPage::rangeSelectionChanged() { active = true; // wipe away current contents of zones foreach (QTreeWidgetItem *item, zones->invisibleRootItem()->takeChildren()) { delete zones->itemWidget(item, 2); delete item; } // fill with current details if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); ZoneRange current = zonePage->zones.getZoneRange(index); dateEdit->setDate(zonePage->zones.getStartDate(index)); cpEdit->setValue(zonePage->zones.getCP(index)); ftpEdit->setValue(zonePage->zones.getFTP(index)); wEdit->setValue(zonePage->zones.getWprime(index)); pmaxEdit->setValue(zonePage->zones.getPmax(index)); if (current.zonesSetFromCP) { // reapply the scheme in case it has been changed zonePage->zones.setScheme(zonePage->schemePage->getScheme()); zonePage->zones.setZonesFromCP(index); current = zonePage->zones.getZoneRange(index); defaultButton->hide(); } else defaultButton->show(); for (int i=0; i< current.zones.count(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(zones->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, current.zones[i].name); // field name add->setText(1, current.zones[i].desc); // low QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(current.zones[i].lo); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(valueChanged(double)), this, SLOT(zonesChanged())); } } active = false; } void CPPage::addZoneClicked() { // no range selected if (!ranges->currentItem()) return; // are we at maximum already? if (zones->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } active = true; int index = zones->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(100); zones->invisibleRootItem()->insertChild(index, add); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); // Short QString text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); active = false; zonesChanged(); } void CPPage::deleteZoneClicked() { // no range selected if (ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()) == -1) return; active = true; if (zones->currentItem()) { int index = zones->invisibleRootItem()->indexOfChild(zones->currentItem()); delete zones->invisibleRootItem()->takeChild(index); } active = false; zonesChanged(); } void CPPage::zonesChanged() { // only take changes when they are not done programmatically // the active flag is set when the tree is being modified // programmatically, but not when users interact with the widgets if (active == false) { // get the current zone range if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); ZoneRange current = zonePage->zones.getZoneRange(index); // embolden that range on the list to show it has been edited QFont font; font.setWeight(QFont::Black); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); // show the default button to undo defaultButton->show(); // we manually edited so save in full current.zonesSetFromCP = false; // create the new zoneinfos for this range QList zoneinfos; for (int i=0; i< zones->invisibleRootItem()->childCount(); i++) { QTreeWidgetItem *item = zones->invisibleRootItem()->child(i); zoneinfos << ZoneInfo(item->text(0), item->text(1), ((QDoubleSpinBox*)zones->itemWidget(item, 2))->value(), 0); } // now sort the list qSort(zoneinfos); // now fill the highs for(int i=0; izones.setZoneRange(index, current); } } } // // Zone Config page // HrZonePage::HrZonePage(Context *context) : context(context) { QVBoxLayout *layout = new QVBoxLayout(this); // get current config by reading it in (leave mainwindow zones alone) QFile zonesFile(context->athlete->home->config().canonicalPath() + "/hr.zones"); if (zonesFile.exists()) { zones.read(zonesFile); zonesFile.close(); b4Fingerprint = zones.getFingerprint(); // remember original state } // setup maintenance pages using current config schemePage = new HrSchemePage(this); ltPage = new LTPage(this); tabs = new QTabWidget(this); tabs->addTab(ltPage, tr("Lactate Threshold")); tabs->addTab(schemePage, tr("Default")); layout->addWidget(tabs); } qint32 HrZonePage::saveClicked() { zones.setScheme(schemePage->getScheme()); zones.write(context->athlete->home->config()); // reread HR zones QFile hrzonesFile(context->athlete->home->config().canonicalPath() + "/hr.zones"); context->athlete->hrzones_->read(hrzonesFile); // did we change ? if (zones.getFingerprint() != b4Fingerprint) return CONFIG_ZONES; else return 0; } HrSchemePage::HrSchemePage(HrZonePage* zonePage) : zonePage(zonePage) { QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(5); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); scheme = new QTreeWidget; scheme->headerItem()->setText(0, tr("Short")); scheme->headerItem()->setText(1, tr("Long")); scheme->headerItem()->setText(2, tr("Percent of LT")); scheme->headerItem()->setText(3, tr("Trimp k")); scheme->setColumnCount(4); scheme->setSelectionMode(QAbstractItemView::SingleSelection); scheme->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit scheme->setUniformRowHeights(true); scheme->setIndentation(0); //scheme->header()->resizeSection(0,60); //scheme->header()->resizeSection(1,180); //scheme->header()->resizeSection(2,65); //scheme->header()->resizeSection(3,65); // setup list for (int i=0; i< zonePage->zones.getScheme().nzones_default; i++) { QTreeWidgetItem *add = new QTreeWidgetItem(scheme->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, zonePage->zones.getScheme().zone_default_name[i]); // field name add->setText(1, zonePage->zones.getScheme().zone_default_desc[i]); // low QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(zonePage->zones.getScheme().zone_default[i]); scheme->setItemWidget(add, 2, loedit); // trimp QDoubleSpinBox *trimpedit = new QDoubleSpinBox(this); trimpedit->setMinimum(0); trimpedit->setMaximum(10); trimpedit->setSingleStep(0.1); trimpedit->setDecimals(2); trimpedit->setValue(zonePage->zones.getScheme().zone_default_trimp[i]); scheme->setItemWidget(add, 3, trimpedit); } mainLayout->addWidget(scheme); mainLayout->addLayout(actionButtons); // button connect connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void HrSchemePage::addClicked() { // are we at maximum already? if (scheme->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } int index = scheme->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(100); scheme->invisibleRootItem()->insertChild(index, add); scheme->setItemWidget(add, 2, loedit); //trimp QDoubleSpinBox *trimpedit = new QDoubleSpinBox(this); trimpedit->setMinimum(0); trimpedit->setMaximum(10); trimpedit->setSingleStep(0.1); trimpedit->setDecimals(2); trimpedit->setValue(1); scheme->setItemWidget(add, 3, trimpedit); // Short QString text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); } void HrSchemePage::renameClicked() { // which one is selected? if (scheme->currentItem()) scheme->editItem(scheme->currentItem(), 0); } void HrSchemePage::deleteClicked() { if (scheme->currentItem()) { int index = scheme->invisibleRootItem()->indexOfChild(scheme->currentItem()); delete scheme->invisibleRootItem()->takeChild(index); } } HrZoneScheme HrSchemePage::getScheme() { // read the scheme widget and return a scheme object QList table; HrZoneScheme results; // read back the details from the table for (int i=0; iinvisibleRootItem()->childCount(); i++) { schemeitem add; add.name = scheme->invisibleRootItem()->child(i)->text(0); add.desc = scheme->invisibleRootItem()->child(i)->text(1); add.lo = ((QDoubleSpinBox *)(scheme->itemWidget(scheme->invisibleRootItem()->child(i), 2)))->value(); add.trimp = ((QDoubleSpinBox *)(scheme->itemWidget(scheme->invisibleRootItem()->child(i), 3)))->value(); table.append(add); } // sort the list into ascending order qSort(table); // now update the results results.nzones_default = 0; foreach(schemeitem zone, table) { results.nzones_default++; results.zone_default.append(zone.lo); results.zone_default_is_pct.append(true); results.zone_default_name.append(zone.name); results.zone_default_desc.append(zone.desc); results.zone_default_trimp.append(zone.trimp); } return results; } LTPage::LTPage(HrZonePage* zonePage) : zonePage(zonePage) { active = false; QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(5); updateButton = new QPushButton(tr("Update")); updateButton->hide(); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); updateButton->setFixedSize(60,20); deleteButton->setFixedSize(20,20); #else updateButton->setText(tr("Update")); addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); #endif defaultButton = new QPushButton(tr("Def")); defaultButton->hide(); addZoneButton = new QPushButton(tr("+")); deleteZoneButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addZoneButton->setFixedSize(20,20); deleteZoneButton->setFixedSize(20,20); #else addZoneButton->setText(tr("Add")); deleteZoneButton->setText(tr("Delete")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addStretch(); actionButtons->addWidget(updateButton); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); //actionButtons->addWidget(defaultButton); moved to zoneButtons QHBoxLayout *zoneButtons = new QHBoxLayout; zoneButtons->addStretch(); zoneButtons->addWidget(addZoneButton); zoneButtons->addWidget(deleteZoneButton); zoneButtons->addWidget(defaultButton); QHBoxLayout *addLayout = new QHBoxLayout; QLabel *dateLabel = new QLabel(tr("From Date")); QLabel *ltLabel = new QLabel(tr("Lactate Threshold")); dateEdit = new QDateEdit; dateEdit->setDate(QDate::currentDate()); ltEdit = new QDoubleSpinBox; ltEdit->setMinimum(0); ltEdit->setMaximum(240); ltEdit->setSingleStep(1.0); ltEdit->setDecimals(0); addLayout->addWidget(dateLabel); addLayout->addWidget(dateEdit); addLayout->addWidget(ltLabel); addLayout->addWidget(ltEdit); addLayout->addStretch(); QHBoxLayout *addLayout2 = new QHBoxLayout; QLabel *restHrLabel = new QLabel(tr("Rest HR")); QLabel *maxHrLabel = new QLabel(tr("Max HR")); restHrEdit = new QDoubleSpinBox; restHrEdit->setMinimum(0); restHrEdit->setMaximum(240); restHrEdit->setSingleStep(1.0); restHrEdit->setDecimals(0); maxHrEdit = new QDoubleSpinBox; maxHrEdit->setMinimum(0); maxHrEdit->setMaximum(240); maxHrEdit->setSingleStep(1.0); maxHrEdit->setDecimals(0); addLayout2->addWidget(restHrLabel); addLayout2->addWidget(restHrEdit); addLayout2->addWidget(maxHrLabel); addLayout2->addWidget(maxHrEdit); addLayout2->addStretch(); ranges = new QTreeWidget; ranges->headerItem()->setText(0, tr("From Date")); ranges->headerItem()->setText(1, tr("Lactate Threshold")); ranges->headerItem()->setText(2, tr("Rest HR")); ranges->headerItem()->setText(3, tr("Max HR")); ranges->setColumnCount(4); ranges->setSelectionMode(QAbstractItemView::SingleSelection); //ranges->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit ranges->setUniformRowHeights(true); ranges->setIndentation(0); //ranges->header()->resizeSection(0,180); // setup list of ranges for (int i=0; i< zonePage->zones.getRangeSize(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(ranges->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); // Embolden ranges with manually configured zones QFont font; font.setWeight(zonePage->zones.getHrZoneRange(i).hrZonesSetFromLT ? QFont::Normal : QFont::Black); // date add->setText(0, zonePage->zones.getStartDate(i).toString(tr("MMM d, yyyy"))); add->setFont(0, font); // LT add->setText(1, QString("%1").arg(zonePage->zones.getLT(i))); add->setFont(1, font); // Rest HR add->setText(2, QString("%1").arg(zonePage->zones.getRestHr(i))); add->setFont(2, font); // Max HR add->setText(3, QString("%1").arg(zonePage->zones.getMaxHr(i))); add->setFont(3, font); } zones = new QTreeWidget; zones->headerItem()->setText(0, tr("Short")); zones->headerItem()->setText(1, tr("Long")); zones->headerItem()->setText(2, tr("From BPM")); zones->headerItem()->setText(3, tr("Trimp k")); zones->setColumnCount(4); zones->setSelectionMode(QAbstractItemView::SingleSelection); zones->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit zones->setUniformRowHeights(true); zones->setIndentation(0); //zones->header()->resizeSection(0,50); //zones->header()->resizeSection(1,150); //zones->header()->resizeSection(2,65); //zones->header()->resizeSection(3,65); mainLayout->addLayout(addLayout); mainLayout->addLayout(addLayout2); mainLayout->addLayout(actionButtons); mainLayout->addWidget(ranges); mainLayout->addLayout(zoneButtons); mainLayout->addWidget(zones); // edit connect connect(dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(rangeEdited())); connect(ltEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); connect(restHrEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); connect(maxHrEdit, SIGNAL(valueChanged(double)), this, SLOT(rangeEdited())); // button connect connect(updateButton, SIGNAL(clicked()), this, SLOT(editClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(defaultButton, SIGNAL(clicked()), this, SLOT(defaultClicked())); connect(addZoneButton, SIGNAL(clicked()), this, SLOT(addZoneClicked())); connect(deleteZoneButton, SIGNAL(clicked()), this, SLOT(deleteZoneClicked())); connect(ranges, SIGNAL(itemSelectionChanged()), this, SLOT(rangeSelectionChanged())); connect(zones, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(zonesChanged())); } void LTPage::addClicked() { // get current scheme zonePage->zones.setScheme(zonePage->schemePage->getScheme()); //int index = ranges->invisibleRootItem()->childCount(); int index = zonePage->zones.addHrZoneRange(dateEdit->date(), ltEdit->value(), restHrEdit->value(), maxHrEdit->value()); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() & ~Qt::ItemIsEditable); ranges->invisibleRootItem()->insertChild(index, add); // date add->setText(0, dateEdit->date().toString(tr("MMM d, yyyy"))); // LT add->setText(1, QString("%1").arg(ltEdit->value())); // Rest HR add->setText(2, QString("%1").arg(restHrEdit->value())); // Max HR add->setText(3, QString("%1").arg(maxHrEdit->value())); } void LTPage::editClicked() { // get current scheme zonePage->zones.setScheme(zonePage->schemePage->getScheme()); QTreeWidgetItem *edit = ranges->selectedItems().at(0); int index = ranges->indexOfTopLevelItem(edit); // date edit->setText(0, dateEdit->date().toString(tr("MMM d, yyyy"))); // LT zonePage->zones.setLT(index, ltEdit->value()); edit->setText(1, QString("%1").arg(ltEdit->value())); // Rest HR zonePage->zones.setRestHr(index, restHrEdit->value()); edit->setText(2, QString("%1").arg(restHrEdit->value())); // Max HR zonePage->zones.setMaxHr(index, maxHrEdit->value()); edit->setText(3, QString("%1").arg(maxHrEdit->value())); } void LTPage::deleteClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); delete ranges->invisibleRootItem()->takeChild(index); zonePage->zones.deleteRange(index); } } void LTPage::defaultClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); HrZoneRange current = zonePage->zones.getHrZoneRange(index); // unbold QFont font; font.setWeight(QFont::Normal); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); ranges->currentItem()->setFont(3, font); // set the range to use defaults on the scheme page zonePage->zones.setScheme(zonePage->schemePage->getScheme()); zonePage->zones.setHrZonesFromLT(index); // hide the default button since we are now using defaults defaultButton->hide(); // update the zones display rangeSelectionChanged(); } } void LTPage::rangeEdited() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); QDate date = dateEdit->date(); QDate odate = zonePage->zones.getStartDate(index); int lt = ltEdit->value(); int olt = zonePage->zones.getLT(index); int maxhr = maxHrEdit->value(); int omaxhr = zonePage->zones.getMaxHr(index); int resthr = restHrEdit->value(); int oresthr = zonePage->zones.getRestHr(index); if (date != odate || lt != olt || maxhr != omaxhr || resthr != oresthr) updateButton->show(); else updateButton->hide(); } } void LTPage::rangeSelectionChanged() { active = true; // wipe away current contents of zones foreach (QTreeWidgetItem *item, zones->invisibleRootItem()->takeChildren()) { delete zones->itemWidget(item, 2); delete item; } // fill with current details if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); HrZoneRange current = zonePage->zones.getHrZoneRange(index); dateEdit->setDate(zonePage->zones.getStartDate(index)); ltEdit->setValue(zonePage->zones.getLT(index)); maxHrEdit->setValue(zonePage->zones.getMaxHr(index)); restHrEdit->setValue(zonePage->zones.getRestHr(index)); if (current.hrZonesSetFromLT) { // reapply the scheme in case it has been changed zonePage->zones.setScheme(zonePage->schemePage->getScheme()); zonePage->zones.setHrZonesFromLT(index); current = zonePage->zones.getHrZoneRange(index); defaultButton->hide(); } else defaultButton->show(); for (int i=0; i< current.zones.count(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(zones->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, current.zones[i].name); // field name add->setText(1, current.zones[i].desc); // low QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(current.zones[i].lo); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); //trimp QDoubleSpinBox *trimpedit = new QDoubleSpinBox(this); trimpedit->setMinimum(0); trimpedit->setMaximum(10); trimpedit->setSingleStep(0.1); trimpedit->setDecimals(2); trimpedit->setValue(current.zones[i].trimp); zones->setItemWidget(add, 3, trimpedit); connect(trimpedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); } } active = false; } void LTPage::addZoneClicked() { // no range selected if (!ranges->currentItem()) return; // are we at maximum already? if (zones->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } active = true; int index = zones->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(100); zones->invisibleRootItem()->insertChild(index, add); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); //trimp QDoubleSpinBox *trimpedit = new QDoubleSpinBox(this); trimpedit->setMinimum(0); trimpedit->setMaximum(10); trimpedit->setSingleStep(0.1); trimpedit->setDecimals(2); trimpedit->setValue(1); zones->setItemWidget(add, 3, trimpedit); connect(trimpedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); // Short QString text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); active = false; zonesChanged(); } void LTPage::deleteZoneClicked() { // no range selected if (ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()) == -1) return; active = true; if (zones->currentItem()) { int index = zones->invisibleRootItem()->indexOfChild(zones->currentItem()); delete zones->invisibleRootItem()->takeChild(index); } active = false; zonesChanged(); } void LTPage::zonesChanged() { // only take changes when they are not done programmatically // the active flag is set when the tree is being modified // programmatically, but not when users interact with the widgets if (active == false) { // get the current zone range if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); HrZoneRange current = zonePage->zones.getHrZoneRange(index); // embolden that range on the list to show it has been edited QFont font; font.setWeight(QFont::Black); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); ranges->currentItem()->setFont(3, font); // show the default button to undo defaultButton->show(); // we manually edited so save in full current.hrZonesSetFromLT = false; // create the new zoneinfos for this range QList zoneinfos; for (int i=0; i< zones->invisibleRootItem()->childCount(); i++) { QTreeWidgetItem *item = zones->invisibleRootItem()->child(i); zoneinfos << HrZoneInfo(item->text(0), item->text(1), ((QDoubleSpinBox*)zones->itemWidget(item, 2))->value(), 0, ((QDoubleSpinBox*)zones->itemWidget(item, 3))->value()); } // now sort the list qSort(zoneinfos); // now fill the highs for(int i=0; izones.setHrZoneRange(index, current); } } } // // Pace Zone Config page // PaceZonePage::PaceZonePage(Context *context) : context(context) { QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *hlayout = new QHBoxLayout; sportLabel = new QLabel(tr("Sport")); sportCombo = new QComboBox(); sportCombo->addItem(tr("Run")); sportCombo->addItem(tr("Swim")); sportCombo->setCurrentIndex(0); hlayout->addStretch(); hlayout->addWidget(sportLabel); hlayout->addWidget(sportCombo); hlayout->addStretch(); layout->addLayout(hlayout); connect(sportCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSport(int))); tabs = new QTabWidget(this); layout->addWidget(tabs); for (int i=0; i < nSports; i++) { paceZones[i] = new PaceZones(i > 0); // get current config by reading it in (leave mainwindow zones alone) QFile zonesFile(context->athlete->home->config().canonicalPath() + "/" + paceZones[i]->fileName()); if (zonesFile.exists()) { paceZones[i]->read(zonesFile); zonesFile.close(); b4Fingerprint[i] = paceZones[i]->getFingerprint(); // remember original state } // setup maintenance pages using current config schemePages[i] = new PaceSchemePage(paceZones[i]); cvPages[i] = new CVPage(paceZones[i], schemePages[i]); } // finish setup for the default sport changeSport(sportCombo->currentIndex()); } PaceZonePage::~PaceZonePage() { for (int i=0; iclear(); tabs->addTab(cvPages[i], tr("Critical Velocity")); tabs->addTab(schemePages[i], tr("Default")); } qint32 PaceZonePage::saveClicked() { qint32 changed = 0; // write it for (int i=0; i < nSports; i++) { appsettings->setValue(paceZones[i]->paceSetting(), cvPages[i]->metric->isChecked()); paceZones[i]->setScheme(schemePages[i]->getScheme()); paceZones[i]->write(context->athlete->home->config()); // reread Pace zones QFile pacezonesFile(context->athlete->home->config().canonicalPath() + "/" + context->athlete->pacezones_[i]->fileName()); context->athlete->pacezones_[i]->read(pacezonesFile); // did we change ? if (paceZones[i]->getFingerprint() != b4Fingerprint[i]) changed = CONFIG_ZONES; } return changed; } PaceSchemePage::PaceSchemePage(PaceZones* paceZones) : paceZones(paceZones) { QVBoxLayout *mainLayout = new QVBoxLayout(this); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); scheme = new QTreeWidget; scheme->headerItem()->setText(0, tr("Short")); scheme->headerItem()->setText(1, tr("Long")); scheme->headerItem()->setText(2, tr("Percent of CV")); scheme->setColumnCount(3); scheme->setSelectionMode(QAbstractItemView::SingleSelection); scheme->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit scheme->setUniformRowHeights(true); scheme->setIndentation(0); //scheme->header()->resizeSection(0,90); //scheme->header()->resizeSection(1,200); //scheme->header()->resizeSection(2,80); // setup list for (int i=0; i< paceZones->getScheme().nzones_default; i++) { QTreeWidgetItem *add = new QTreeWidgetItem(scheme->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, paceZones->getScheme().zone_default_name[i]); // field name add->setText(1, paceZones->getScheme().zone_default_desc[i]); // low QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(paceZones->getScheme().zone_default[i]); scheme->setItemWidget(add, 2, loedit); } mainLayout->addWidget(scheme); mainLayout->addLayout(actionButtons); // button connect connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void PaceSchemePage::addClicked() { // are we at maximum already? if (scheme->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } int index = scheme->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QDoubleSpinBox *loedit = new QDoubleSpinBox(this); loedit->setMinimum(0); loedit->setMaximum(1000); loedit->setSingleStep(1.0); loedit->setDecimals(0); loedit->setValue(100); scheme->invisibleRootItem()->insertChild(index, add); scheme->setItemWidget(add, 2, loedit); // Short QString text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; scheme->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); } void PaceSchemePage::renameClicked() { // which one is selected? if (scheme->currentItem()) scheme->editItem(scheme->currentItem(), 0); } void PaceSchemePage::deleteClicked() { if (scheme->currentItem()) { int index = scheme->invisibleRootItem()->indexOfChild(scheme->currentItem()); delete scheme->invisibleRootItem()->takeChild(index); } } // just for qSorting struct paceschemeitem { QString name, desc; int lo; double trimp; bool operator<(paceschemeitem right) const { return lo < right.lo; } }; PaceZoneScheme PaceSchemePage::getScheme() { // read the scheme widget and return a scheme object QList table; PaceZoneScheme results; // read back the details from the table for (int i=0; iinvisibleRootItem()->childCount(); i++) { paceschemeitem add; add.name = scheme->invisibleRootItem()->child(i)->text(0); add.desc = scheme->invisibleRootItem()->child(i)->text(1); add.lo = ((QDoubleSpinBox *)(scheme->itemWidget(scheme->invisibleRootItem()->child(i), 2)))->value(); table.append(add); } // sort the list into ascending order qSort(table); // now update the results results.nzones_default = 0; foreach(paceschemeitem zone, table) { results.nzones_default++; results.zone_default.append(zone.lo); results.zone_default_is_pct.append(true); results.zone_default_name.append(zone.name); results.zone_default_desc.append(zone.desc); } return results; } CVPage::CVPage(PaceZones* paceZones, PaceSchemePage *schemePage) : paceZones(paceZones), schemePage(schemePage) { active = false; QGridLayout *mainLayout = new QGridLayout(this); mainLayout->setSpacing(10); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); #endif defaultButton = new QPushButton(tr("Def")); defaultButton->hide(); addZoneButton = new QPushButton(tr("+")); deleteZoneButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addZoneButton->setFixedSize(20,20); deleteZoneButton->setFixedSize(20,20); #else addZoneButton->setText(tr("Add")); deleteZoneButton->setText(tr("Delete")); #endif QHBoxLayout *zoneButtons = new QHBoxLayout; zoneButtons->addStretch(); zoneButtons->setSpacing(0); zoneButtons->addWidget(addZoneButton); zoneButtons->addWidget(deleteZoneButton); QHBoxLayout *addLayout = new QHBoxLayout; QLabel *dateLabel = new QLabel(tr("From Date")); QLabel *cpLabel = new QLabel(tr("Critical Velocity")); dateEdit = new QDateEdit; dateEdit->setDate(QDate::currentDate()); // CV default is 4min/km for Running a round number inline with // CP default and 1:36min/100 for swimming (4:1 relation) cvEdit = new QTimeEdit(QTime::fromString(paceZones->isSwim() ? "01:36" : "04:00", "mm:ss")); cvEdit->setMinimumTime(QTime::fromString("01:00", "mm:ss")); cvEdit->setMaximumTime(QTime::fromString("20:00", "mm:ss")); cvEdit->setDisplayFormat("mm:ss"); per = new QLabel(this); metric = new QCheckBox(tr("Metric Pace")); metric->setChecked(appsettings->value(this, paceZones->paceSetting(), true).toBool()); per->setText(paceZones->paceUnits(metric->isChecked())); if (!metric->isChecked()) metricChanged(); // default is metric QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(cpLabel); actionButtons->addWidget(cvEdit); actionButtons->addWidget(per); actionButtons->addStretch(); actionButtons->addWidget(metric); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); actionButtons->addWidget(defaultButton); addLayout->addWidget(dateLabel); addLayout->addWidget(dateEdit); addLayout->addStretch(); ranges = new QTreeWidget; ranges->headerItem()->setText(0, tr("From Date")); ranges->headerItem()->setText(1, tr("Critical Velocity")); ranges->setColumnCount(2); ranges->setSelectionMode(QAbstractItemView::SingleSelection); //ranges->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit ranges->setUniformRowHeights(true); ranges->setIndentation(0); //ranges->header()->resizeSection(0,180); // setup list of ranges for (int i=0; i< paceZones->getRangeSize(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(ranges->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); // Embolden ranges with manually configured zones QFont font; font.setWeight(paceZones->getZoneRange(i).zonesSetFromCV ? QFont::Normal : QFont::Black); // date add->setText(0, paceZones->getStartDate(i).toString(tr("MMM d, yyyy"))); add->setFont(0, font); // CV double kph = paceZones->getCV(i); add->setText(1, QString("%1 %2 %3 %4") .arg(paceZones->kphToPaceString(kph, true)) .arg(paceZones->paceUnits(true)) .arg(paceZones->kphToPaceString(kph, false)) .arg(paceZones->paceUnits(false))); add->setFont(1, font); } zones = new QTreeWidget; zones->headerItem()->setText(0, tr("Short")); zones->headerItem()->setText(1, tr("Long")); zones->headerItem()->setText(2, tr("From")); zones->setColumnCount(3); zones->setSelectionMode(QAbstractItemView::SingleSelection); zones->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit zones->setUniformRowHeights(true); zones->setIndentation(0); //zones->header()->resizeSection(0,80); //zones->header()->resizeSection(1,150); mainLayout->addLayout(addLayout, 0,0); mainLayout->addLayout(actionButtons, 1,0); mainLayout->addWidget(ranges, 2,0); mainLayout->addLayout(zoneButtons, 3,0); mainLayout->addWidget(zones, 4,0); // button connect connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(defaultButton, SIGNAL(clicked()), this, SLOT(defaultClicked())); connect(addZoneButton, SIGNAL(clicked()), this, SLOT(addZoneClicked())); connect(deleteZoneButton, SIGNAL(clicked()), this, SLOT(deleteZoneClicked())); connect(ranges, SIGNAL(itemSelectionChanged()), this, SLOT(rangeSelectionChanged())); connect(zones, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(zonesChanged())); connect(metric, SIGNAL(stateChanged(int)), this, SLOT(metricChanged())); } void CVPage::metricChanged() { // Switch between metric and imperial! per->setText(paceZones->paceUnits(metric->isChecked())); double kphCV = paceZones->kphFromTime(cvEdit, !metric->isChecked()); cvEdit->setTime(QTime::fromString(paceZones->kphToPaceString(kphCV, metric->isChecked()), "mm:ss")); } void CVPage::addClicked() { // get current scheme paceZones->setScheme(schemePage->getScheme()); int cp = paceZones->kphFromTime(cvEdit, metric->isChecked()); if( cp <= 0 ){ QMessageBox err; err.setText(tr("CV must be > 0")); err.setIcon(QMessageBox::Warning); err.exec(); return; } int index = paceZones->addZoneRange(dateEdit->date(), paceZones->kphFromTime(cvEdit, metric->isChecked())); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() & ~Qt::ItemIsEditable); ranges->invisibleRootItem()->insertChild(index, add); // date add->setText(0, dateEdit->date().toString(tr("MMM d, yyyy"))); // CV double kph = paceZones->kphFromTime(cvEdit, metric->isChecked()); add->setText(1, QString("%1 %2 %3 %4") .arg(paceZones->kphToPaceString(kph, true)) .arg(paceZones->paceUnits(true)) .arg(paceZones->kphToPaceString(kph, false)) .arg(paceZones->paceUnits(false))); } void CVPage::deleteClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); delete ranges->invisibleRootItem()->takeChild(index); paceZones->deleteRange(index); } } void CVPage::defaultClicked() { if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); PaceZoneRange current = paceZones->getZoneRange(index); // unbold QFont font; font.setWeight(QFont::Normal); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); // set the range to use defaults on the scheme page paceZones->setScheme(schemePage->getScheme()); paceZones->setZonesFromCV(index); // hide the default button since we are now using defaults defaultButton->hide(); // update the zones display rangeSelectionChanged(); } } void CVPage::rangeSelectionChanged() { active = true; // wipe away current contents of zones foreach (QTreeWidgetItem *item, zones->invisibleRootItem()->takeChildren()) { delete zones->itemWidget(item, 2); delete item; } // fill with current details if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); PaceZoneRange current = paceZones->getZoneRange(index); if (current.zonesSetFromCV) { // reapply the scheme in case it has been changed paceZones->setScheme(schemePage->getScheme()); paceZones->setZonesFromCV(index); current = paceZones->getZoneRange(index); defaultButton->hide(); } else defaultButton->show(); for (int i=0; i< current.zones.count(); i++) { QTreeWidgetItem *add = new QTreeWidgetItem(zones->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, current.zones[i].name); // field name add->setText(1, current.zones[i].desc); // low QTimeEdit *loedit = new QTimeEdit(QTime::fromString(paceZones->kphToPaceString(current.zones[i].lo, metric->isChecked()), "mm:ss"), this); loedit->setMinimumTime(QTime::fromString("00:00", "mm:ss")); loedit->setMaximumTime(QTime::fromString("20:00", "mm:ss")); loedit->setDisplayFormat("mm:ss"); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); } } active = false; } void CVPage::addZoneClicked() { // no range selected if (!ranges->currentItem()) return; // are we at maximum already? if (zones->invisibleRootItem()->childCount() == 10) { QMessageBox err; err.setText(tr("Maximum of 10 zones reached.")); err.setIcon(QMessageBox::Warning); err.exec(); return; } active = true; int index = zones->invisibleRootItem()->childCount(); // new item QTreeWidgetItem *add = new QTreeWidgetItem; add->setFlags(add->flags() | Qt::ItemIsEditable); QTimeEdit *loedit = new QTimeEdit(QTime::fromString("00:00", "mm:ss"), this); loedit->setMinimumTime(QTime::fromString("00:00", "mm:ss")); loedit->setMaximumTime(QTime::fromString("20:00", "mm:ss")); loedit->setDisplayFormat("mm:ss"); zones->invisibleRootItem()->insertChild(index, add); zones->setItemWidget(add, 2, loedit); connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); // Short QString text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(0, text); // long text = tr("New"); for (int i=0; zones->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString(tr("New (%1)")).arg(i+1); } add->setText(1, text); active = false; zonesChanged(); } void CVPage::deleteZoneClicked() { // no range selected if (ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()) == -1) return; active = true; if (zones->currentItem()) { int index = zones->invisibleRootItem()->indexOfChild(zones->currentItem()); delete zones->invisibleRootItem()->takeChild(index); } active = false; zonesChanged(); } void CVPage::zonesChanged() { // only take changes when they are not done programmatically // the active flag is set when the tree is being modified // programmatically, but not when users interact with the widgets if (active == false) { // get the current zone range if (ranges->currentItem()) { int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); PaceZoneRange current = paceZones->getZoneRange(index); // embolden that range on the list to show it has been edited QFont font; font.setWeight(QFont::Black); ranges->currentItem()->setFont(0, font); ranges->currentItem()->setFont(1, font); ranges->currentItem()->setFont(2, font); // show the default button to undo defaultButton->show(); // we manually edited so save in full current.zonesSetFromCV = false; // create the new zoneinfos for this range QList zoneinfos; for (int i=0; i< zones->invisibleRootItem()->childCount(); i++) { QTreeWidgetItem *item = zones->invisibleRootItem()->child(i); QTimeEdit *loTimeEdit = (QTimeEdit*)zones->itemWidget(item, 2); double kph = loTimeEdit->time() == QTime(0,0,0) ? 0.0 : paceZones->kphFromTime(loTimeEdit, metric->isChecked()); zoneinfos << PaceZoneInfo(item->text(0), item->text(1), kph, 0); } // now sort the list qSort(zoneinfos); // now fill the highs for(int i=0; isetZoneRange(index, current); } } } // // Season Editor // SeasonsPage::SeasonsPage(QWidget *parent, Context *context) : QWidget(parent), context(context) { QGridLayout *mainLayout = new QGridLayout(this); QFormLayout *editLayout = new QFormLayout; editLayout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint); // get the list array = context->athlete->seasons->seasons; // Edit widgets nameEdit = new QLineEdit(this); typeEdit = new QComboBox(this); foreach(QString t, Season::types) typeEdit->addItem(t); typeEdit->setCurrentIndex(0); fromEdit = new QDateEdit(this); fromEdit->setCalendarPopup(true); toEdit = new QDateEdit(this); toEdit->setCalendarPopup(true); // set form editLayout->addRow(new QLabel(tr("Name")), nameEdit); editLayout->addRow(new QLabel(tr("Type")), typeEdit); editLayout->addRow(new QLabel(tr("From")), fromEdit); editLayout->addRow(new QLabel(tr("To")), toEdit); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); #endif QVBoxLayout *actionButtons = new QVBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(deleteButton); actionButtons->addWidget(upButton); actionButtons->addWidget(downButton); actionButtons->addStretch(); seasons = new QTreeWidget; seasons->headerItem()->setText(0, tr("Name")); seasons->headerItem()->setText(1, tr("Type")); seasons->headerItem()->setText(2, tr("From")); seasons->headerItem()->setText(3, tr("To")); seasons->setColumnCount(4); seasons->setSelectionMode(QAbstractItemView::SingleSelection); //seasons->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit seasons->setUniformRowHeights(true); seasons->setIndentation(0); #ifdef Q_OS_MAC //seasons->header()->resizeSection(0,160); // tab //seasons->header()->resizeSection(1,80); // name //seasons->header()->resizeSection(2,130); // type //seasons->header()->resizeSection(3,130); // values #else //seasons->header()->resizeSection(0,160); // tab //seasons->header()->resizeSection(1,80); // name //seasons->header()->resizeSection(2,130); // type //seasons->header()->resizeSection(3,130); // values #endif foreach(Season season, array) { QTreeWidgetItem *add; if (season.type == Season::temporary) continue; add = new QTreeWidgetItem(seasons->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); // tab name add->setText(0, season.name); // type add->setText(1, Season::types[static_cast(season.type)]); // from add->setText(2, season.start.toString(tr("ddd MMM d, yyyy"))); // to add->setText(3, season.end.toString(tr("ddd MMM d, yyyy"))); // guid -- hidden add->setText(4, season._id.toString()); } seasons->setCurrentItem(seasons->invisibleRootItem()->child(0)); mainLayout->addLayout(editLayout, 0,0); mainLayout->addWidget(addButton, 0,1, Qt::AlignTop); mainLayout->addWidget(seasons, 1,0); mainLayout->addLayout(actionButtons, 1,1); // set all the edits to a default value clearEdit(); // connect up slots connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); } void SeasonsPage::clearEdit() { typeEdit->setCurrentIndex(0); nameEdit->setText(""); fromEdit->setDate(QDate::currentDate()); toEdit->setDate(QDate::currentDate().addMonths(3)); } void SeasonsPage::upClicked() { if (seasons->currentItem()) { int index = seasons->invisibleRootItem()->indexOfChild(seasons->currentItem()); if (index == 0) return; // its at the top already // movin on up! QTreeWidgetItem* moved = seasons->invisibleRootItem()->takeChild(index); seasons->invisibleRootItem()->insertChild(index-1, moved); seasons->setCurrentItem(moved); // and move the array too array.move(index, index-1); } } void SeasonsPage::downClicked() { if (seasons->currentItem()) { int index = seasons->invisibleRootItem()->indexOfChild(seasons->currentItem()); if (index == (seasons->invisibleRootItem()->childCount()-1)) return; // its at the bottom already QTreeWidgetItem* moved = seasons->invisibleRootItem()->takeChild(index); seasons->invisibleRootItem()->insertChild(index+1, moved); seasons->setCurrentItem(moved); array.move(index, index+1); } } void SeasonsPage::renameClicked() { // which one is selected? if (seasons->currentItem()) seasons->editItem(seasons->currentItem(), 0); } void SeasonsPage::addClicked() { if (nameEdit->text() == "") return; // just ignore it // swap if not right way around if (fromEdit->date() > toEdit->date()) { QDate temp = fromEdit->date(); fromEdit->setDate(toEdit->date()); toEdit->setDate(temp); } QTreeWidgetItem *add = new QTreeWidgetItem(seasons->invisibleRootItem()); add->setFlags(add->flags() & ~Qt::ItemIsEditable); QString id; // tab name add->setText(0, nameEdit->text()); // type add->setText(1, Season::types[typeEdit->currentIndex()]); // from add->setText(2, fromEdit->date().toString(tr("ddd MMM d, yyyy"))); // to add->setText(3, toEdit->date().toString(tr("ddd MMM d, yyyy"))); // guid -- hidden add->setText(4, (id=QUuid::createUuid().toString())); // now clear the edits clearEdit(); Season addSeason; addSeason.setStart(fromEdit->date()); addSeason.setEnd(toEdit->date()); addSeason.setName(nameEdit->text()); addSeason.setType(typeEdit->currentIndex()); addSeason.setId(id); array.append(Season()); } void SeasonsPage::deleteClicked() { if (seasons->currentItem()) { int index = seasons->invisibleRootItem()->indexOfChild(seasons->currentItem()); // zap! delete seasons->invisibleRootItem()->takeChild(index); array.removeAt(index); } } qint32 SeasonsPage::saveClicked() { // get any edits to the names and dates // since we don't trap these as they are made for(int i=0; iinvisibleRootItem()->childCount(); i++) { QTreeWidgetItem *item = seasons->invisibleRootItem()->child(i); array[i].setName(item->text(0)); array[i].setType(Season::types.indexOf(item->text(1))); array[i].setStart(QDate::fromString(item->text(2), "ddd MMM d, yyyy")); array[i].setEnd(QDate::fromString(item->text(3), "ddd MMM d, yyyy")); array[i]._id = QUuid(item->text(4)); } // write to disk QString file = QString(context->athlete->home->config().canonicalPath() + "/seasons.xml"); SeasonParser::serialize(file, array); // re-read context->athlete->seasons->readSeasons(); return 0; } AutoImportPage::AutoImportPage(Context *context) : context(context) { QGridLayout *mainLayout = new QGridLayout(this); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); browseButton = new QPushButton(tr("Browse")); #ifndef Q_OS_MAC upButton = new QToolButton(this); downButton = new QToolButton(this); upButton->setArrowType(Qt::UpArrow); downButton->setArrowType(Qt::DownArrow); upButton->setFixedSize(20,20); downButton->setFixedSize(20,20); addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #else addButton->setText(tr("Add")); deleteButton->setText(tr("Delete")); upButton = new QPushButton(tr("Up")); downButton = new QPushButton(tr("Down")); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(upButton); actionButtons->addWidget(downButton); actionButtons->addStretch(); actionButtons->addWidget(browseButton); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); fields = new QTreeWidget; fields->headerItem()->setText(0, tr("Directory")); fields->headerItem()->setText(1, tr("Import Rule")); fields->setColumnWidth(0,400); fields->setColumnWidth(1,100); fields->setColumnCount(2); fields->setSelectionMode(QAbstractItemView::SingleSelection); //fields->setUniformRowHeights(true); fields->setIndentation(0); fields->setCurrentItem(fields->invisibleRootItem()->child(0)); mainLayout->addWidget(fields, 0,0); mainLayout->addLayout(actionButtons, 1,0); context->athlete->autoImportConfig->readConfig(); QList rules = context->athlete->autoImportConfig->getConfig(); int index = 0; foreach (RideAutoImportRule rule, rules) { QComboBox *comboButton = new QComboBox(this); addRuleTypes(comboButton); QTreeWidgetItem *add = new QTreeWidgetItem; fields->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); add->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); add->setText(0, rule.getDirectory()); add->setTextAlignment(1, Qt::AlignHCenter | Qt::AlignVCenter); comboButton->setCurrentIndex(rule.getImportRule()); fields->setItemWidget(add, 1, comboButton); index++; } // connect up slots connect(upButton, SIGNAL(clicked()), this, SLOT(upClicked())); connect(downButton, SIGNAL(clicked()), this, SLOT(downClicked())); connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(browseButton, SIGNAL(clicked()), this, SLOT(browseImportDir())); } void AutoImportPage::upClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index == 0) return; // its at the top already //movin on up! QWidget *button = fields->itemWidget(fields->currentItem(),1); QComboBox *comboButton = new QComboBox(this); addRuleTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index-1, moved); fields->setItemWidget(moved, 1, comboButton); fields->setCurrentItem(moved); } } void AutoImportPage::downClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index == (fields->invisibleRootItem()->childCount()-1)) return; // its at the bottom already QWidget *button = fields->itemWidget(fields->currentItem(),1); QComboBox *comboButton = new QComboBox(this); addRuleTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index+1, moved); fields->setItemWidget(moved, 1, comboButton); fields->setCurrentItem(moved); } } void AutoImportPage::addClicked() { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index < 0) index = 0; QComboBox *comboButton = new QComboBox(this); addRuleTypes(comboButton); QTreeWidgetItem *add = new QTreeWidgetItem; fields->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); add->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); add->setText(0, tr("Enter directory or press [Browse] to select")); add->setTextAlignment(1, Qt::AlignHCenter | Qt::AlignVCenter); fields->setItemWidget(add, 1, comboButton); } void AutoImportPage::deleteClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); // zap! delete fields->invisibleRootItem()->takeChild(index); } } qint32 AutoImportPage::saveClicked() { rules.clear(); for(int i=0; iinvisibleRootItem()->childCount(); i++) { RideAutoImportRule rule; rule.setDirectory(fields->invisibleRootItem()->child(i)->text(0)); QWidget *button = fields->itemWidget(fields->invisibleRootItem()->child(i),1); rule.setImportRule(((QComboBox*)button)->currentIndex()); rules.append(rule); } // write to disk QString file = QString(context->athlete->home->config().canonicalPath() + "/autoimport.xml"); RideAutoImportConfigParser::serialize(file, rules); // re-read context->athlete->autoImportConfig->readConfig(); return 0; } void AutoImportPage::addRuleTypes(QComboBox *p) { RideAutoImportRule* config = new RideAutoImportRule(); QList descriptions = config->getRuleDescriptions(); foreach(QString description, descriptions) { p->addItem(description); } } void AutoImportPage::browseImportDir() { QStringList selectedDirs; if (fields->currentItem()) { QFileDialog fileDialog(this); fileDialog.setFileMode(QFileDialog::Directory); fileDialog.setOptions(QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (fileDialog.exec()) { selectedDirs = fileDialog.selectedFiles(); } if (selectedDirs.count() > 0) { QString dir = selectedDirs.at(0); if (dir != "") { fields->currentItem()->setText(0, dir); } } } } IntervalsPage::IntervalsPage(Context *context) : context(context) { // get config b4.discovery = appsettings->cvalue(context->athlete->cyclist, GC_DISCOVERY, 57).toInt(); // 57 does not include search for PEAKs QVBoxLayout *mainLayout = new QVBoxLayout(this); QGridLayout *layout = new QGridLayout(); mainLayout->addLayout(layout); mainLayout->addStretch(); QLabel *heading = new QLabel(tr("Enable interval auto-discovery:")); heading->setFixedHeight(QFontMetrics(heading->font()).height() + 4); int row = 0; layout->addWidget(heading, row, 0, Qt::AlignRight); user = 99; for(int i=0; i<= static_cast(RideFileInterval::last()); i++) { // ignore until we get past user interval type if (i == static_cast(RideFileInterval::USER)) user=i; // if past user then add if (i>user) { QCheckBox *add = new QCheckBox(RideFileInterval::typeDescriptionLong(static_cast(i))); checkBoxes << add; add->setChecked(b4.discovery & RideFileInterval::intervalTypeBits(static_cast(i))); layout->addWidget(add, row++, 1, Qt::AlignLeft); } } } qint32 IntervalsPage::saveClicked() { int discovery = 0; // now update discovery ! for(int i=0; i< checkBoxes.count(); i++) if (checkBoxes[i]->isChecked()) discovery += RideFileInterval::intervalTypeBits(static_cast(i+user+1)); // new value returned appsettings->setCValue(context->athlete->cyclist, GC_DISCOVERY, discovery); // return change ! if (b4.discovery != discovery) return CONFIG_DISCOVERY; else return 0; }