/* * 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 #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" // // 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")); // 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 langCombo->setCurrentIndex(0); configLayout->addWidget(langLabel, 0,0, Qt::AlignRight); configLayout->addWidget(langCombo, 0,1, Qt::AlignLeft); // // Crank length - only used by PfPv chart (should move there!) // QLabel *crankLengthLabel = new QLabel(tr("Crank Length:")); QVariant crankLength = appsettings->value(this, GC_CRANKLENGTH); crankLengthCombo = new QComboBox(); 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() == "160") crankLengthCombo->setCurrentIndex(0); if(crankLength.toString() == "162.5") crankLengthCombo->setCurrentIndex(1); if(crankLength.toString() == "165") crankLengthCombo->setCurrentIndex(2); if(crankLength.toString() == "167.5") crankLengthCombo->setCurrentIndex(3); if(crankLength.toString() == "170") crankLengthCombo->setCurrentIndex(4); if(crankLength.toString() == "172.5") crankLengthCombo->setCurrentIndex(5); if(crankLength.toString() == "175") crankLengthCombo->setCurrentIndex(6); if(crankLength.toString() == "177.5") crankLengthCombo->setCurrentIndex(7); if(crankLength.toString() == "180") crankLengthCombo->setCurrentIndex(8); if(crankLength.toString() == "182.5") crankLengthCombo->setCurrentIndex(9); if(crankLength.toString() == "185") crankLengthCombo->setCurrentIndex(10); configLayout->addWidget(crankLengthLabel, 1,0, Qt::AlignRight); configLayout->addWidget(crankLengthCombo, 1,1, Qt::AlignLeft); // // Wheel size // QLabel *wheelSizeLabel = new QLabel(tr("Wheelsize:"), this); int wheelSize = appsettings->value(this, GC_WHEELSIZE, 2100).toInt(); wheelSizeCombo = new QComboBox(); wheelSizeCombo->addItem("Road/Cross (700C/622)"); // 2100mm wheelSizeCombo->addItem("Tri/TT (650C)"); // 1960mm wheelSizeCombo->addItem("Mountain (26\")"); // 1985mm wheelSizeCombo->addItem("BMX (20\")"); // 1750mm switch (wheelSize) { default: case 2100 : wheelSizeCombo->setCurrentIndex(0); break; case 1960 : wheelSizeCombo->setCurrentIndex(1); break; case 1985 : wheelSizeCombo->setCurrentIndex(2); break; case 1750 : wheelSizeCombo->setCurrentIndex(3); break; } configLayout->addWidget(wheelSizeLabel, 2,0, Qt::AlignRight); configLayout->addWidget(wheelSizeCombo, 2,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("Threshold (secs):")); garminHWMarkedit = new QLineEdit(garminHWMark.toString(),this); garminHWMarkedit->setInputMask("009"); configLayout->addWidget(garminSmartRecord, 3,1, Qt::AlignLeft); configLayout->addWidget(garminHWLabel, 4,0, Qt::AlignRight); configLayout->addWidget(garminHWMarkedit, 4,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, 7,0, Qt::AlignRight); configLayout->addWidget(hystedit, 7,1, Qt::AlignLeft); // // 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()); configLayout->addWidget(perfManSTSLabel, 9,0, Qt::AlignRight); configLayout->addWidget(perfManSTSavg, 9,1, Qt::AlignLeft); configLayout->addWidget(perfManLTSLabel, 10,0, Qt::AlignRight); configLayout->addWidget(perfManLTSavg, 10,1, Qt::AlignLeft); configLayout->addWidget(showSBToday, 11,1, Qt::AlignLeft); // // Workout directory (train view) // QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); workoutLabel = new QLabel(tr("Workout Library:")); workoutDirectory = new QLineEdit; workoutDirectory->setText(workoutDir.toString()); workoutBrowseButton = new QPushButton(tr("Browse")); workoutBrowseButton->setFixedWidth(70); configLayout->addWidget(workoutLabel, 12,0, Qt::AlignRight); configLayout->addWidget(workoutDirectory, 12,1); configLayout->addWidget(workoutBrowseButton, 13,1); connect(workoutBrowseButton, SIGNAL(clicked()), this, SLOT(browseWorkoutDir())); } void GeneralPage::saveClicked() { // Language static const QString langs[] = { "en", "fr", "ja", "pt-br", "it", "de", "ru", "cs", "es", "pt" }; appsettings->setValue(GC_LANG, langs[langCombo->currentIndex()]); // Garmon and cranks appsettings->setValue(GC_GARMIN_HWMARK, garminHWMarkedit->text().toInt()); appsettings->setValue(GC_GARMIN_SMARTRECORD, garminSmartRecord->checkState()); appsettings->setValue(GC_CRANKLENGTH, crankLengthCombo->currentText()); // save wheel size static const int wheelSizes[] = { 2100, 1960, 1985, 1750 }; appsettings->setValue(GC_WHEELSIZE, wheelSizes[wheelSizeCombo->currentIndex()]); // Bike score estimation appsettings->setValue(GC_WORKOUTDIR, workoutDirectory->text()); appsettings->setValue(GC_ELEVATION_HYSTERESIS, hystedit->text()); // 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()); // set default stress names if not set: appsettings->setValue(GC_STS_NAME, appsettings->value(this, GC_STS_NAME,tr("Short Term Stress"))); appsettings->setValue(GC_STS_ACRONYM, appsettings->value(this, GC_STS_ACRONYM,tr("STS"))); appsettings->setValue(GC_LTS_NAME, appsettings->value(this, GC_LTS_NAME,tr("Long Term Stress"))); appsettings->setValue(GC_LTS_ACRONYM, appsettings->value(this, GC_LTS_ACRONYM,tr("LTS"))); appsettings->setValue(GC_SB_NAME, appsettings->value(this, GC_SB_NAME,tr("Stress Balance"))); appsettings->setValue(GC_SB_ACRONYM, appsettings->value(this, GC_SB_ACRONYM,tr("SB"))); } void GeneralPage::browseWorkoutDir() { QString dir = QFileDialog::getExistingDirectory(this, tr("Select Workout Library"), "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); workoutDirectory->setText(dir); } // // 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; QLabel *gc = new QLabel(tr("Golden Cheetah Racing")); QFont current; current.setWeight(QFont::Black); gc->setFont(current); QLabel *gcurlLabel = new QLabel(tr("Website")); QLabel *gcuserLabel = new QLabel(tr("Username")); QLabel *gcpassLabel = new QLabel(tr("Password")); QLabel *tp = new QLabel(tr("TrainingPeaks")); tp->setFont(current); QLabel *urlLabel = new QLabel(tr("Website")); QLabel *userLabel = new QLabel(tr("Username")); QLabel *passLabel = new QLabel(tr("Password")); QLabel *typeLabel = new QLabel(tr("Account Type")); QLabel *twp = new QLabel(tr("Twitter")); twp->setFont(current); QLabel *twurlLabel = new QLabel(tr("Website")); QLabel *twauthLabel = new QLabel(tr("Authorise")); QLabel *twpinLabel = new QLabel(tr("PIN")); QLabel *str = new QLabel(tr("Strava")); str->setFont(current); //QLabel *struserLabel = new QLabel(tr("Username")); //QLabel *strpassLabel = new QLabel(tr("Password")); QLabel *strauthLabel = new QLabel(tr("Authorise")); QLabel *can = new QLabel(tr("Cycling Analytics")); can->setFont(current); QLabel *canauthLabel = new QLabel(tr("Authorise")); QLabel *rwgps = new QLabel(tr("RideWithGPS")); rwgps->setFont(current); QLabel *rwgpsuserLabel = new QLabel(tr("Username")); QLabel *rwgpspassLabel = new QLabel(tr("Password")); QLabel *ttb = new QLabel(tr("Trainingstagebuch")); ttb->setFont(current); QLabel *ttbuserLabel = new QLabel(tr("Username")); QLabel *ttbpassLabel = new QLabel(tr("Password")); QLabel *sel = new QLabel(tr("Selfloops")); sel->setFont(current); QLabel *seluserLabel = new QLabel(tr("Username")); QLabel *selpassLabel = new QLabel(tr("Password")); 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")); QLabel *zeo = new QLabel(tr("Zeo Sleep Data")); zeo->setFont(current); QLabel *zeourlLabel = new QLabel(tr("Website")); QLabel *zeouserLabel = new QLabel(tr("User")); QLabel *zeopassLabel = new QLabel(tr("Password")); QLabel *webcal = new QLabel(tr("Web Calendar")); webcal->setFont(current); QLabel *wcurlLabel = new QLabel(tr("Webcal URL")); QLabel *dv = new QLabel(tr("CalDAV Calendar")); 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")); gcURL = new QLineEdit(this); gcURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_GCURL, "http://race.goldencheetah.org").toString()); gcUser = new QLineEdit(this); gcUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_GCUSER, "").toString()); gcPass = new QLineEdit(this); gcPass->setEchoMode(QLineEdit::Password); gcPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_GCPASS, "").toString()); tpURL = new QLineEdit(this); tpURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_TPURL, "http://www.trainingpeaks.com").toString()); 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()); twitterURL = new QLineEdit(this); twitterURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_TWURL, "http://www.twitter.com").toString()); twitterAuthorise = new QPushButton("Authorise", this); twitterPIN = new QLineEdit(this); twitterPIN->setText(""); stravaAuthorise = new QPushButton("Authorise", this); #ifndef GC_STRAVA_CLIENT_SECRET stravaAuthorise->setEnabled(false); #endif QPixmap passwords = QPixmap(":/images/toolbar/passwords.png"); 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); cyclingAnalyticsAuthorise = new QPushButton("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); 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()); 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()); 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()); 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()); zeoURL = new QLineEdit(this); zeoURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_ZEOURL, "http://app-pro.myzeo.com:8080/").toString()); zeoUser = new QLineEdit(this); zeoUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_ZEOUSER, "").toString()); zeoPass = new QLineEdit(this); zeoPass->setEchoMode(QLineEdit::Password); zeoPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_ZEOPASS, "").toString()); webcalURL = new QLineEdit(this); webcalURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_WEBCAL_URL, "").toString()); 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()); grid->addWidget(tp, 0,0); grid->addWidget(urlLabel, 1,0); grid->addWidget(userLabel, 2,0); grid->addWidget(passLabel, 3,0); grid->addWidget(typeLabel, 4,0); grid->addWidget(gc, 5,0); grid->addWidget(gcurlLabel, 6,0); grid->addWidget(gcuserLabel, 7,0); grid->addWidget(gcpassLabel, 8,0); grid->addWidget(twp, 9,0); grid->addWidget(twurlLabel, 10,0); grid->addWidget(twauthLabel, 11,0); grid->addWidget(twpinLabel, 12,0); grid->addWidget(str, 13,0); grid->addWidget(strauthLabel, 14,0); grid->addWidget(can, 15,0); grid->addWidget(canauthLabel, 16,0); grid->addWidget(rwgps, 18,0); grid->addWidget(rwgpsuserLabel, 19,0); grid->addWidget(rwgpspassLabel, 20,0); grid->addWidget(wip, 21,0); grid->addWidget(wiurlLabel, 22,0); grid->addWidget(wiuserLabel, 23,0); grid->addWidget(wipassLabel, 24,0); grid->addWidget(zeo, 25,0); grid->addWidget(zeourlLabel, 26,0); grid->addWidget(zeouserLabel, 27,0); grid->addWidget(zeopassLabel, 28,0); grid->addWidget(webcal, 29, 0); grid->addWidget(wcurlLabel, 30, 0); grid->addWidget(dv, 31,0); grid->addWidget(dvurlLabel, 32,0); grid->addWidget(dvuserLabel, 33,0); grid->addWidget(dvpassLabel, 34,0); grid->addWidget(ttb, 35,0); grid->addWidget(ttbuserLabel, 36,0); grid->addWidget(ttbpassLabel, 37,0); grid->addWidget(sel, 38,0); grid->addWidget(seluserLabel, 39,0); grid->addWidget(selpassLabel, 40,0); grid->addWidget(tpURL, 1, 1, 0); grid->addWidget(tpUser, 2, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(tpPass, 3, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(tpType, 4, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(gcURL, 6, 1, 0); grid->addWidget(gcUser, 7, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(gcPass, 8, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(twitterURL, 10, 1, 0); grid->addWidget(twitterAuthorise, 11, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(twitterPIN, 12, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(stravaAuthorise, 14, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_STRAVA_TOKEN, "")!="") grid->addWidget(stravaAuthorised, 14, 1, Qt::AlignLeft | Qt::AlignVCenter); else stravaAuthorised->hide(); // if no token no show grid->addWidget(cyclingAnalyticsAuthorise, 16, 1, Qt::AlignLeft | Qt::AlignVCenter); if (appsettings->cvalue(context->athlete->cyclist, GC_CYCLINGANALYTICS_TOKEN, "")!="") grid->addWidget(cyclingAnalyticsAuthorised, 16, 1, Qt::AlignLeft | Qt::AlignVCenter); else cyclingAnalyticsAuthorised->hide(); grid->addWidget(rideWithGPSUser, 19, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(rideWithGPSPass, 20, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(wiURL, 22, 1, 0); grid->addWidget(wiUser, 23, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(wiPass, 24, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(zeoURL, 26, 1, 0); grid->addWidget(zeoUser, 27, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(zeoPass, 28, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(webcalURL, 30, 1, 0); grid->addWidget(dvURL, 32, 1, 0); grid->addWidget(dvUser, 33, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(dvPass, 34, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(ttbUser, 36, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(ttbPass, 37, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(selUser, 39, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->addWidget(selPass, 40, 1, Qt::AlignLeft | Qt::AlignVCenter); grid->setColumnStretch(0,0); grid->setColumnStretch(1,3); main->setLayout(grid); setFrameStyle(QFrame::NoFrame); setWidgetResizable(true); setWidget(main); connect(twitterAuthorise, SIGNAL(clicked()), this, SLOT(authoriseTwitter())); connect(stravaAuthorise, SIGNAL(clicked()), this, SLOT(authoriseStrava())); connect(cyclingAnalyticsAuthorise, SIGNAL(clicked()), this, SLOT(authoriseCyclingAnalytics())); } void CredentialsPage::authoriseTwitter() { #ifdef GC_HAVE_LIBOAUTH int rc; char **rv = NULL; QString token; QString url = QString(); t_key = NULL; t_secret = NULL; const char *request_token_uri = "http://api.twitter.com/oauth/request_token"; char *req_url = NULL; char *postarg = NULL; char *reply = NULL; req_url = oauth_sign_url2(request_token_uri, NULL, OA_HMAC, NULL, GC_TWITTER_CONSUMER_KEY, GC_TWITTER_CONSUMER_SECRET, NULL, NULL); reply = oauth_http_get(req_url,postarg); rc = oauth_split_url_parameters(reply, &rv); qsort(rv, rc, sizeof(char *), oauth_cmpstringp); token = QString(rv[1]); t_key =strdup(&(rv[1][12])); t_secret =strdup(&(rv[2][19])); url = QString("http://api.twitter.com/oauth/authorize?"); url.append(token); QDesktopServices::openUrl(QUrl(url)); if(rv) free(rv); #endif } void CredentialsPage::saveTwitter() { #ifdef GC_HAVE_LIBOAUTH char *reply; char *req_url; char **rv = NULL; char *postarg = NULL; QString url = QString("http://api.twitter.com/oauth/access_token?a=b&oauth_verifier="); QString strPin = twitterPIN->text(); if(strPin.size() == 0) return; url.append(strPin); req_url = oauth_sign_url2(url.toLatin1(), NULL, OA_HMAC, NULL, GC_TWITTER_CONSUMER_KEY, GC_TWITTER_CONSUMER_SECRET, t_key, t_secret); reply = oauth_http_get(req_url,postarg); int rc = oauth_split_url_parameters(reply, &rv); if(rc ==4) { qsort(rv, rc, sizeof(char *), oauth_cmpstringp); const char *oauth_token = strdup(&(rv[0][12])); const char *oauth_secret = strdup(&(rv[1][19])); //Save Twitter oauth_token and oauth_secret; appsettings->setValue(GC_TWITTER_TOKEN, oauth_token); appsettings->setValue(GC_TWITTER_SECRET, oauth_secret); } #endif } void CredentialsPage::authoriseStrava() { #ifdef GC_HAVE_LIBOAUTH OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::STRAVA); oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); #endif } void CredentialsPage::authoriseCyclingAnalytics() { #ifdef GC_HAVE_LIBOAUTH OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::CYCLING_ANALYTICS); oauthDialog->setWindowModality(Qt::ApplicationModal); oauthDialog->exec(); #endif } void CredentialsPage::saveClicked() { appsettings->setCValue(context->athlete->cyclist, GC_GCURL, gcURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_GCUSER, gcUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_GCPASS, gcPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_TPURL, tpURL->text()); 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_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_ZEOURL, zeoURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_ZEOUSER, zeoUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_ZEOPASS, zeoPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_WEBCAL_URL, webcalURL->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()); } // // About me // RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), context(context) { QVBoxLayout *all = new QVBoxLayout(this); QGridLayout *grid = new QGridLayout; QLabel *nicklabel = new QLabel(tr("Nickname")); QLabel *doblabel = new QLabel(tr("Date of Birth")); QLabel *sexlabel = new QLabel(tr("Sex")); QLabel *unitlabel = new QLabel(tr("Unit")); QLabel *biolabel = new QLabel(tr("Bio")); QString weighttext = QString(tr("Weight (%1)")).arg(context->athlete->useMetricUnits ? tr("kg") : tr("lb")); weightlabel = new QLabel(weighttext); nickname = new QLineEdit(this); nickname->setText(appsettings->cvalue(context->athlete->cyclist, GC_NICKNAME).toString()); 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()); unitCombo = new QComboBox(); unitCombo->addItem(tr("Metric")); unitCombo->addItem(tr("Imperial")); if (context->athlete->useMetricUnits) unitCombo->setCurrentIndex(0); else unitCombo->setCurrentIndex(1); 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)); bio = new QTextEdit(this); bio->setText(appsettings->cvalue(context->athlete->cyclist, GC_BIO).toString()); if (QFileInfo(context->athlete->home.absolutePath() + "/" + "avatar.png").exists()) avatar = QPixmap(context->athlete->home.absolutePath() + "/" + "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); 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(unitlabel, 3, 0, alignment); grid->addWidget(weightlabel, 4, 0, alignment); grid->addWidget(biolabel, 5, 0, alignment); grid->addWidget(nickname, 0, 1, alignment); grid->addWidget(dob, 1, 1, alignment); grid->addWidget(sex, 2, 1, alignment); grid->addWidget(unitCombo, 3, 1, alignment); grid->addWidget(weight, 4, 1, alignment); grid->addWidget(bio, 6, 0, 1, 4); grid->addWidget(avatarButton, 0, 2, 4, 2, Qt::AlignRight|Qt::AlignVCenter); all->addLayout(grid); all->addStretch(); connect (avatarButton, SIGNAL(clicked()), this, SLOT(chooseAvatar())); connect (unitCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int))); } void RiderPage::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 RiderPage::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); } else { QString weighttext = QString(tr("Weight (%1)")).arg(tr("lb")); weightlabel->setText(weighttext); weight->setValue(weight->value() * LB_PER_KG); } } void RiderPage::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_WEIGHT, weight->value() * (unitCombo->currentIndex() ? KG_PER_LB : 1.0)); if (unitCombo->currentIndex()==0) appsettings->setCValue(context->athlete->cyclist, GC_UNIT, GC_UNIT_METRIC); else if (unitCombo->currentIndex()==1) appsettings->setCValue(context->athlete->cyclist, GC_UNIT, GC_UNIT_IMPERIAL); appsettings->setCValue(context->athlete->cyclist, GC_SEX, sex->currentIndex()); appsettings->setCValue(context->athlete->cyclist, GC_BIO, bio->toPlainText()); avatar.save(context->athlete->home.absolutePath() + "/" + "avatar.png", "PNG"); } // // 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 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("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())); } void DevicePage::saveClicked() { // Save the device configuration... DeviceConfigurations all; all.writeConfig(deviceListModel->Configuration); appsettings->setValue(TRAIN_MULTI, multiCheck->isChecked()); } 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(); } 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); colors = new QTreeWidget; colors->headerItem()->setText(0, tr("Color")); colors->headerItem()->setText(1, tr("Select")); colors->setColumnCount(2); colors->setColumnWidth(0,310); colors->setSelectionMode(QAbstractItemView::NoSelection); //colors->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit colors->setUniformRowHeights(true); colors->setIndentation(0); //colors->header()->resizeSection(0,300); shadeZones = new QCheckBox; shadeZones->setChecked(appsettings->value(this, GC_SHADEZONES, true).toBool()); antiAliased = new QCheckBox; antiAliased->setChecked(appsettings->value(this, GC_ANTIALIAS, false).toBool()); lineWidth = new QDoubleSpinBox; lineWidth->setMaximum(5); lineWidth->setMinimum(0.5); lineWidth->setSingleStep(0.5); reset = new QPushButton("Reset"); lineWidth->setValue(appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble()); QLabel *lineWidthLabel = new QLabel(tr("Line Width")); QLabel *antialiasLabel = new QLabel(tr("Antialias" )); QLabel *shadeZonesLabel = new QLabel(tr("Shade Zones" )); 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" )); QLabel *popupLabel = new QLabel(tr("Popup Text" )); def = new QFontComboBox(this); titles = new QFontComboBox(this); chartmarkers = new QFontComboBox(this); chartlabels = new QFontComboBox(this); calendar = new QFontComboBox(this); popup = 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); popupSize = new QComboBox(this); setSizes(popupSize); // 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()); popup->setCurrentIndex(0); popup->setCurrentIndex(1); popup->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); font.fromString(appsettings->value(this, GC_FONT_POPUP, QFont().toString()).toString()); popup->setCurrentFont(font); #ifdef Q_OS_MAC defaultSize->setCurrentIndex((appsettings->value(this, GC_FONT_DEFAULT_SIZE, 12).toInt() -7) / 2); titlesSize->setCurrentIndex((appsettings->value(this, GC_FONT_TITLES_SIZE, 12).toInt() -7) / 2); chartmarkersSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTMARKERS_SIZE, 12).toInt() -7) / 2); chartlabelsSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTLABELS_SIZE, 12).toInt() -7) / 2); calendarSize->setCurrentIndex((appsettings->value(this, GC_FONT_CALENDAR_SIZE, 12).toInt() -7) / 2); popupSize->setCurrentIndex((appsettings->value(this, GC_FONT_POPUP_SIZE, 10).toInt() -7) / 2); #else defaultSize->setCurrentIndex((appsettings->value(this, GC_FONT_DEFAULT_SIZE, 12).toInt() -6) / 2); titlesSize->setCurrentIndex((appsettings->value(this, GC_FONT_TITLES_SIZE, 12).toInt() -6) / 2); chartmarkersSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTMARKERS_SIZE, 12).toInt() -6) / 2); chartlabelsSize->setCurrentIndex((appsettings->value(this, GC_FONT_CHARTLABELS_SIZE, 12).toInt() -6) / 2); calendarSize->setCurrentIndex((appsettings->value(this, GC_FONT_CALENDAR_SIZE, 12).toInt() -6) / 2); popupSize->setCurrentIndex((appsettings->value(this, GC_FONT_POPUP_SIZE, 10).toInt() -6) / 2); #endif QGridLayout *grid = new QGridLayout; grid->setSpacing(5); QHBoxLayout *misc = new QHBoxLayout; misc->addWidget(lineWidthLabel); misc->addWidget(lineWidth); misc->addStretch(); misc->addWidget(antialiasLabel); misc->addWidget(antiAliased); misc->addWidget(shadeZonesLabel); misc->addWidget(shadeZones); misc->addStretch(); misc->addWidget(reset); grid->addWidget(defaultLabel, 0,0); grid->addWidget(titlesLabel, 1,0); grid->addWidget(markerLabel, 2,0); grid->addWidget(chartLabel, 3,0); grid->addWidget(calendarLabel, 4,0); grid->addWidget(popupLabel, 5,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(popup, 5,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(popupSize, 5,2, Qt::AlignVCenter|Qt::AlignLeft); grid->setColumnStretch(0,1); grid->setColumnStretch(1,5); grid->setColumnStretch(2,1); grid->setColumnStretch(3,1); grid->setColumnStretch(4,3); mainLayout->addLayout(grid); mainLayout->addLayout(misc); mainLayout->addWidget(colors); 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(reset, SIGNAL(clicked()), this, SLOT(resetClicked())); } void ColorsPage::resetClicked() { colorSet = GCColor::defaultColorSet(); colors->clear(); 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); } } void ColorsPage::saveClicked() { appsettings->setValue(GC_LINEWIDTH, lineWidth->value()); appsettings->setValue(GC_ANTIALIAS, antiAliased->isChecked()); appsettings->setValue(GC_SHADEZONES, shadeZones->isChecked()); // 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()); appsettings->setValue(GC_FONT_POPUP, popup->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)); appsettings->setValue(GC_FONT_POPUP_SIZE, 7+(popupSize->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)); appsettings->setValue(GC_FONT_POPUP_SIZE, 6+(popupSize->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); } IntervalMetricsPage::IntervalMetricsPage(QWidget *parent) : QWidget(parent), changed(false) { 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)); QString name = m->name(); name.replace(tr("™"), tr(" (TM)")); QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, symbol); availList->addItem(item); } foreach (QString symbol, selectedMetrics) { if (!factory.haveMetric(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QString name = m->name(); name.replace(tr("™"), tr(" (TM)")); QListWidgetItem *item = new QListWidgetItem(name); 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; } 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); } void IntervalMetricsPage::saveClicked() { if (!changed) return; 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(",")); } SummaryMetricsPage::SummaryMetricsPage(QWidget *parent) : QWidget(parent), changed(false) { 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)); QString name = m->name(); name.replace(tr("™"), tr(" (TM)")); QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, symbol); availList->addItem(item); } foreach (QString symbol, selectedMetrics) { if (!factory.haveMetric(symbol)) continue; QSharedPointer m(factory.newMetric(symbol)); QString name = m->name(); name.replace(tr("™"), tr(" (TM)")); QListWidgetItem *item = new QListWidgetItem(name); 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; } 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); } void SummaryMetricsPage::saveClicked() { if (!changed) return; 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(",")); } 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(); // setup maintenance pages using current config fieldsPage = new FieldsPage(this, fieldDefinitions); keywordsPage = new KeywordsPage(this, keywordDefinitions); processorPage = new ProcessorPage(context); tabs = new QTabWidget(this); tabs->addTab(fieldsPage, tr("Fields")); tabs->addTab(keywordsPage, tr("Notes Keywords")); 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); } void MetadataPage::saveClicked() { // get current state fieldsPage->getDefinitions(fieldDefinitions); keywordsPage->getDefinitions(keywordDefinitions); // write to metadata.xml RideMetadata::serialize(context->athlete->home.absolutePath() + "/metadata.xml", keywordDefinitions, fieldDefinitions, colorfield); // save processors config processorPage->saveClicked(); } // 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); 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); 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 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); 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 defiitions 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); 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 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); 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 = "New"; for (int i=0; fields->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString("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->setColumnCount(3); processorTree->setSelectionMode(QAbstractItemView::NoSelection); processorTree->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit processorTree->setUniformRowHeights(true); processorTree->setIndentation(0); //processorTree->header()->resizeSection(0,150); // 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()); // Auto or Manual run? QComboBox *comboButton = new QComboBox(this); comboButton->addItem(tr("Manual")); comboButton->addItem(tr("Auto")); processorTree->setItemWidget(add, 1, comboButton); QString configsetting = QString("dp/%1/apply").arg(i.key()); if (appsettings->value(this, 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); } mainLayout->addWidget(processorTree, 0,0); } void ProcessorPage::saveClicked() { // call each processor config widget's saveConfig() to // write away separately for (int i=0; iinvisibleRootItem()->childCount(); i++) { // auto or manual? QString configsetting = QString("dp/%1/apply").arg(processorTree->invisibleRootItem()->child(i)->text(0)); QString apply = ((QComboBox*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 1)))->currentIndex() ? "Auto" : "Manual"; appsettings->setValue(configsetting, apply); ((DataProcessorConfig*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 2)))->saveConfig(); } } // // 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.absolutePath() + "/power.zones"); if (zonesFile.exists()) { zones.read(zonesFile); zonesFile.close(); } // 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); } void ZonePage::saveClicked() { zones.setScheme(schemePage->getScheme()); zones.write(context->athlete->home); } 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); #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("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 = "New"; for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("New (%1)").arg(i+1); } add->setText(0, text); // long text = "New"; for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("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); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #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); #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 Power")); QLabel *wLabel = new QLabel(tr("W'")); dateEdit = new QDateEdit; dateEdit->setDate(QDate::currentDate()); cpEdit = new QDoubleSpinBox; cpEdit->setMinimum(0); cpEdit->setMaximum(1000); cpEdit->setSingleStep(1.0); cpEdit->setDecimals(0); wEdit = new QDoubleSpinBox; wEdit->setMinimum(0); wEdit->setMaximum(40000); wEdit->setSingleStep(100); wEdit->setDecimals(0); QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addWidget(cpLabel); actionButtons->addWidget(cpEdit); actionButtons->addWidget(wLabel); actionButtons->addWidget(wEdit); 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 Power")); ranges->headerItem()->setText(2, tr("W'")); ranges->setColumnCount(3); 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); // date add->setText(0, zonePage->zones.getStartDate(i).toString("MMM d, yyyy")); add->setFont(0, font); // CP add->setText(1, QString("%1").arg(zonePage->zones.getCP(i))); add->setFont(1, font); // W' add->setText(2, QString("%1").arg(zonePage->zones.getWprime(i))); add->setFont(2, 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); // 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())); } void CPPage::addClicked() { // get current scheme zonePage->zones.setScheme(zonePage->schemePage->getScheme()); int cp = cpEdit->value(); if( cp <= 0 ){ QMessageBox err; err.setText("CP must be > 0"); err.setIcon(QMessageBox::Warning); err.exec(); return; } //int index = ranges->invisibleRootItem()->childCount(); int index = zonePage->zones.addZoneRange(dateEdit->date(), cpEdit->value(), wEdit->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("MMM d, yyyy")); // CP add->setText(1, QString("%1").arg(cpEdit->value())); // W' add->setText(2, QString("%1").arg(wEdit->value())); } 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); // 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::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); 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("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 = "New"; for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("New (%1)").arg(i+1); } add->setText(0, text); // long text = "New"; for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("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); // 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.absolutePath() + "/hr.zones"); if (zonesFile.exists()) { zones.read(zonesFile); zonesFile.close(); } // setup maintenance pages using current config schemePage = new HrSchemePage(this); ltPage = new LTPage(this); tabs = new QTabWidget(this); tabs->addTab(ltPage, tr("Lactic Threshold")); tabs->addTab(schemePage, tr("Default")); layout->addWidget(tabs); } void HrZonePage::saveClicked() { zones.setScheme(schemePage->getScheme()); zones.write(context->athlete->home); } HrSchemePage::HrSchemePage(HrZonePage* 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); #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("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 = "New"; for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("New (%1)").arg(i+1); } add->setText(0, text); // long text = "New"; for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("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); addButton = new QPushButton(tr("+")); deleteButton = new QPushButton(tr("-")); #ifndef Q_OS_MAC addButton->setFixedSize(20,20); deleteButton->setFixedSize(20,20); #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); #endif QHBoxLayout *actionButtons = new QHBoxLayout; actionButtons->setSpacing(2); actionButtons->addStretch(); actionButtons->addWidget(addButton); actionButtons->addWidget(deleteButton); actionButtons->addWidget(defaultButton); QHBoxLayout *zoneButtons = new QHBoxLayout; zoneButtons->addStretch(); zoneButtons->addWidget(addZoneButton); zoneButtons->addWidget(deleteZoneButton); QHBoxLayout *addLayout = new QHBoxLayout; QLabel *dateLabel = new QLabel(tr("From Date")); QLabel *ltLabel = new QLabel(tr("Lactic 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("Lactic 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("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); // 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())); } 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("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::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); // 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::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); 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("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 = "New"; for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("New (%1)").arg(i+1); } add->setText(0, text); // long text = "New"; for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { text = QString("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); // 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); } } } // // Ride metadata page // MeasuresPage::MeasuresPage(Context *context) : context(context) { QGridLayout *mainLayout = new QGridLayout(this); 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 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("Measure")); fields->headerItem()->setText(2, tr("Type")); fields->setColumnCount(3); fields->setSelectionMode(QAbstractItemView::SingleSelection); fields->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit fields->setUniformRowHeights(true); fields->setIndentation(0); //fields->header()->resizeSection(0,130); //fields->header()->resizeSection(1,140); // temporary storage for measures config QList fieldDefinitions; QList keywordDefinitions; //NOTE: not used in measures.xml // check we have one and use built in if not there QString filename = context->athlete->home.absolutePath()+"/measures.xml"; QString colorfield; if (!QFile(filename).exists()) filename = ":/xml/measures.xml"; RideMetadata::readXML(filename, keywordDefinitions, fieldDefinitions, colorfield); // iterate over the fields and setup the editable items foreach(FieldDefinition field, fieldDefinitions) { QTreeWidgetItem *add; QComboBox *comboButton = new QComboBox(this); FieldsPage::addFieldTypes(comboButton); comboButton->setCurrentIndex(field.type); add = new QTreeWidgetItem(fields->invisibleRootItem()); add->setFlags(add->flags() | Qt::ItemIsEditable); // tab name add->setText(0, field.tab); // field name add->setText(1, field.name); // type button add->setTextAlignment(2, Qt::AlignHCenter); fields->setItemWidget(add, 2, comboButton); } 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 MeasuresPage::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); QComboBox *comboButton = new QComboBox(this); FieldsPage::addFieldTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index-1, moved); fields->setItemWidget(moved, 2, comboButton); fields->setCurrentItem(moved); } } void MeasuresPage::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); QComboBox *comboButton = new QComboBox(this); FieldsPage::addFieldTypes(comboButton); comboButton->setCurrentIndex(((QComboBox*)button)->currentIndex()); QTreeWidgetItem* moved = fields->invisibleRootItem()->takeChild(index); fields->invisibleRootItem()->insertChild(index+1, moved); fields->setItemWidget(moved, 2, comboButton); fields->setCurrentItem(moved); } } void MeasuresPage::renameClicked() { // which one is selected? if (fields->currentItem()) fields->editItem(fields->currentItem(), 0); } void MeasuresPage::addClicked() { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); if (index < 0) index = 0; QTreeWidgetItem *add; QComboBox *comboButton = new QComboBox(this); FieldsPage::addFieldTypes(comboButton); add = new QTreeWidgetItem; fields->invisibleRootItem()->insertChild(index, add); add->setFlags(add->flags() | Qt::ItemIsEditable); // field QString text = "New"; for (int i=0; fields->findItems(text, Qt::MatchExactly, 1).count() > 0; i++) { text = QString("New (%1)").arg(i+1); } add->setText(1, text); // type button add->setTextAlignment(2, Qt::AlignHCenter); fields->setItemWidget(add, 2, comboButton); } void MeasuresPage::deleteClicked() { if (fields->currentItem()) { int index = fields->invisibleRootItem()->indexOfChild(fields->currentItem()); // zap! delete fields->invisibleRootItem()->takeChild(index); } } void MeasuresPage::getDefinitions(QList &fieldList) { 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 = item->text(0); add.name = item->text(1); add.type = ((QComboBox*)fields->itemWidget(item, 2))->currentIndex(); fieldList.append(add); } } void MeasuresPage::saveClicked() { QList current; getDefinitions(current); // write to measures.xml (uses same format as metadata.xml) RideMetadata::serialize(context->athlete->home.absolutePath() + "/measures.xml", QList(), 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("Name"), nameEdit); editLayout->addRow(new QLabel("Type"), typeEdit); editLayout->addRow(new QLabel("From"), fromEdit); editLayout->addRow(new QLabel("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 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("ddd MMM d, yyyy")); // to add->setText(3, season.end.toString("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("ddd MMM d, yyyy")); // to add->setText(3, toEdit->date().toString("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); } } void 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.absolutePath() + "/seasons.xml"); SeasonParser::serialize(file, array); // re-read context->athlete->seasons->readSeasons(); }