/* * Copyright (c) 2010 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 "Context.h" #include "Tab.h" #include "TabView.h" #include "HomeWindow.h" #include "LTMTool.h" #include "LTMSettings.h" #include "LTMWindow.h" #include "Settings.h" #include "ChartBar.h" #include #include #include #include static const int tileMargin = 20; static const int tileSpacing = 10; HomeWindow::HomeWindow(Context *context, QString name, QString /* windowtitle */) : GcWindow(context), context(context), name(name), active(false), clicked(NULL), dropPending(false), chartCursor(-2), loaded(false) { // setup control area QWidget *cw = new QWidget(this); cw->setContentsMargins(0,0,0,0); QVBoxLayout *cl = new QVBoxLayout(cw); cl->setSpacing(0); cl->setContentsMargins(0,0,0,0); QLabel *titleLabel = new QLabel(tr("Title"), this); QHBoxLayout *hl = new QHBoxLayout; hl->setSpacing(5); hl->setContentsMargins(0,0,0,0); titleEdit = new QLineEdit(this); titleEdit->setEnabled(false); controlStack = new QStackedWidget(this); controlStack->setContentsMargins(0,0,0,0); hl->addWidget(titleLabel); hl->addWidget(titleEdit); cl->addLayout(hl); cl->addSpacing(15); cl->addWidget(controlStack); setControls(cw); setProperty("isManager", true); setAcceptDrops(true); QVBoxLayout *layout = new QVBoxLayout(this); QFont bigandbold; bigandbold.setPointSize(bigandbold.pointSize() + 2); bigandbold.setWeight(QFont::Bold); style = new QStackedWidget(this); style->setAutoFillBackground(false); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); layout->addWidget(style); QPalette palette; palette.setBrush(backgroundRole(), QColor("#B3B4B6")); setAutoFillBackground(false); // each style has its own container widget QWidget *tabArea = new QWidget(this); tabArea->setContentsMargins(0,0,0,0); // no spacing now, used to be 20px QVBoxLayout *tabLayout = new QVBoxLayout(tabArea); tabLayout->setContentsMargins(0,0,0,0); tabLayout->setSpacing(0); tabbed = new QStackedWidget(this); chartbar = new ChartBar(context); tabLayout->addWidget(chartbar); tabLayout->addWidget(tabbed); style->addWidget(tabArea); // tiled tileWidget = new QWidget(this); tileWidget->setAutoFillBackground(false); tileWidget->setPalette(palette); tileWidget->setContentsMargins(0,0,0,0); tileGrid = new QGridLayout(tileWidget); tileGrid->setSpacing(0); tileArea = new QScrollArea(this); #ifdef Q_OS_WIN QStyle *cde = QStyleFactory::create(OS_STYLE); tileArea->setStyle(cde); #endif tileArea->setAutoFillBackground(false); tileArea->setPalette(palette); tileArea->setWidgetResizable(true); tileArea->setWidget(tileWidget); tileArea->setFrameStyle(QFrame::NoFrame); tileArea->setContentsMargins(0,0,0,0); style->addWidget(tileArea); winWidget = new QWidget(this); winWidget->setAutoFillBackground(false); winWidget->setContentsMargins(0,0,0,0); winWidget->setPalette(palette); winFlow = new GcWindowLayout(winWidget, 0, tileSpacing, tileSpacing); winFlow->setContentsMargins(tileMargin,tileMargin,tileMargin,tileMargin); winArea = new QScrollArea(this); winArea->setAutoFillBackground(false); winArea->setPalette(palette); winArea->setWidgetResizable(true); winArea->setWidget(winWidget); winArea->setFrameStyle(QFrame::NoFrame); winArea->setContentsMargins(0,0,0,0); style->addWidget(winArea); winWidget->installEventFilter(this); // to draw cursor winWidget->setMouseTracking(true); // to draw cursor // enable right click to add a chart winArea->setContextMenuPolicy(Qt::CustomContextMenu); tabArea->setContextMenuPolicy(Qt::CustomContextMenu); connect(winArea,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(rightClick(const QPoint &))); connect(tabArea,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(rightClick(const QPoint &))); currentStyle=-1; styleChanged(2); connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); connect(context, SIGNAL(configChanged()), this, SLOT(configChanged())); //connect(tabbed, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int))); //connect(tabbed, SIGNAL(tabCloseRequested(int)), this, SLOT(removeChart(int))); //connect(tb, SIGNAL(tabMoved(int,int)), this, SLOT(tabMoved(int,int))); connect(chartbar, SIGNAL(currentIndexChanged(int)), this, SLOT(tabSelected(int))); connect(titleEdit, SIGNAL(textChanged(const QString&)), SLOT(titleChanged())); installEventFilter(this); qApp->installEventFilter(this); } HomeWindow::~HomeWindow() { qApp->removeEventFilter(this); } void HomeWindow::rightClick(const QPoint & /*pos*/) { return; //deprecated right click on homewindow -- bad UX } void HomeWindow::addChartFromMenu(QAction*action) { GcWinID id = GcWindowTypes::None; for (int i=0; GcWindows[i].relevance; i++) { if (GcWindows[i].name == action->text()) { id = GcWindows[i].id; break; } } if (id != GcWindowTypes::None) { appendChart(id); // called from Context to inset chart } } void HomeWindow::configChanged() { // update scroll bar //#ifndef Q_OS_MAC tileArea->verticalScrollBar()->setStyleSheet(TabView::ourStyleSheet()); //#endif } void HomeWindow::selected() { setUpdatesEnabled(false); if (loaded == false) { restoreState(); loaded = true; if (!currentStyle && charts.count()) tabSelected(0); } resizeEvent(NULL); // force a relayout rideSelected(); dateRangeChanged(DateRange()); setUpdatesEnabled(true); update(); } void HomeWindow::titleChanged() { if (charts.count() == 0) return; if (titleEdit->text() != charts[controlStack->currentIndex()]->property("title").toString()) { // set the title property charts[controlStack->currentIndex()]->setProperty("title", titleEdit->text()); // rename the tab if (!currentStyle) { chartbar->setText(controlStack->currentIndex(), titleEdit->text()); } // repaint to reflect charts[controlStack->currentIndex()]->repaint(); } } void HomeWindow::rideSelected() { // we need to notify of null rides immediately if (!myRideItem || isVisible()) { for (int i=0; i < charts.count(); i++) { // show if its not a tab if (currentStyle) charts[i]->show(); // keep tabs hidden, show the rest // if we are tabbed AND its the current tab then mimic tabselected // to force the tabwidget to refresh AND set its ride property (see below) //otherwise just go ahead and notify it of a new ride if (!currentStyle && charts.count() && i==tabbed->currentIndex()) tabSelected(tabbed->currentIndex(), true); // for ride change else charts[i]->setProperty("ride", property("ride")); } } } void HomeWindow::dateRangeChanged(DateRange dr) { Q_UNUSED( dr ) if (isVisible()) { for (int i=0; i < charts.count(); i++) { // show if its not a tab if (currentStyle) charts[i]->show(); // keep tabs hidden, show the rest // if we are tabbed AND its the current tab then mimic tabselected // to force the tabwidget to refresh AND set its ride property (see below) //otherwise just go ahead and notify it of a new ride if (!currentStyle && charts.count() && i==tabbed->currentIndex()) tabSelected(tabbed->currentIndex(), false); // for date range else charts[i]->setProperty("dateRange", property("dateRange")); } } } void HomeWindow::tabSelected(int index) { // user switched tabs -- tell tabe to redraw! if (active || currentStyle != 0) return; active = true; if (index >= 0) { charts[index]->show(); charts[index]->setProperty("ride", property("ride")); charts[index]->setProperty("dateRange", property("dateRange")); controlStack->setCurrentIndex(index); titleEdit->setText(charts[index]->property("title").toString()); tabbed->setCurrentIndex(index); chartbar->setCurrentIndex(index); } active = false; } void HomeWindow::tabSelected(int index, bool forride) { if (active || currentStyle != 0) return; active = true; if (index >= 0) { charts[index]->show(); if (forride) charts[index]->setProperty("ride", property("ride")); else charts[index]->setProperty("dateRange", property("dateRange")); controlStack->setCurrentIndex(index); titleEdit->setText(charts[index]->property("title").toString()); } active = false; } void HomeWindow::tabMoved(int to, int from) { // re-order the tabs GcWindow *orig = charts[to]; charts[to] = charts[from]; charts[from] = orig; // re-order the controls controlStack->blockSignals(true); QWidget *w = controlStack->widget(from); controlStack->removeWidget(w); controlStack->insertWidget(to, w); controlStack->setCurrentIndex(to); controlStack->blockSignals(false); } void HomeWindow::styleChanged(int id) { // ignore if out of bounds or we're already using that style if (id > 2 || id < 0 || id == currentStyle) return; active = true; // block updates as it is butt ugly setUpdatesEnabled(false); tabbed->hide(); //This is a QTabWidget setUpdatesEnabled bug (?) chartbar->hide(); chartbar->clear(); // move the windows from there current // position to there new position for (int i=0; iindexOf(charts[i]); tabbed->removeWidget(tabbed->widget(tabnum)); } break; case 1 : // they are lists in a GridLayout { tileGrid->removeWidget(charts[i]); } break; case 2 : // they are in a FlowLayout { winFlow->removeWidget(charts[i]); } default: break; } // now put into new style switch (id) { case 0 : // they are tabs in a TabWidget tabbed->addWidget(charts[i]); chartbar->addWidget(charts[i]->property("title").toString()); charts[i]->setContentsMargins(0,25,0,0); charts[i]->setResizable(false); // we need to show on tab selection! charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->hide(); // we need to show on tab selection! break; case 1 : // they are lists in a GridLayout tileGrid->addWidget(charts[i], i,0); charts[i]->setContentsMargins(0,25,0,0); charts[i]->setResizable(false); // we need to show on tab selection! charts[i]->show(); charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->setProperty("ride", property("ride")); break; case 2 : // thet are in a FlowLayout winFlow->addWidget(charts[i]); charts[i]->setContentsMargins(0,15,0,0); charts[i]->setResizable(true); // we need to show on tab selection! charts[i]->show(); charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->setProperty("ride", property("ride")); default: break; } } currentStyle = id; style->setCurrentIndex(currentStyle); active = false; if (currentStyle == 0 && charts.count()) tabSelected(0); resizeEvent(NULL); // watch out in case resize event uses this!! // now refresh as we are done setUpdatesEnabled(true); tabbed->show(); // QTabWidget setUpdatesEnabled bug chartbar->show(); // and resize artefact too.. tread carefully. update(); } void HomeWindow::dragEnterEvent(QDragEnterEvent *) { #if 0 // drah and drop chart no longer part of the UX if (event->mimeData()->formats().contains("application/x-qabstractitemmodeldatalist")) { event->accept(); dropPending = true; } #endif } void HomeWindow::appendChart(GcWinID id) { GcWindow *newone = NULL; // GcWindowDialog is delete on close, so no need to delete GcWindowDialog *f = new GcWindowDialog(id, context, &newone); f->exec(); // returns null if cancelled or closed if (newone) { addChart(newone); newone->show(); //Crash on QT5 } // now wipe it f->hide(); f->deleteLater(); // before we return lets turn the cursor off chartCursor = -2; winWidget->repaint(); } void HomeWindow::dropEvent(QDropEvent *) { #if 0 // drah and drop chart no longer part of the UX QStandardItemModel model; model.dropMimeData(event->mimeData(), Qt::CopyAction, -1,-1, QModelIndex()); QString chart = model.data(model.index(0,0), Qt::DisplayRole).toString(); dropPending = false; // which one am i? for (int i = 0; GcWindows[i].id != GcWindowTypes::None; i++) { if (GcWindows[i].name == chart) { event->accept(); appendChart(GcWindows[i].id); return; } } // nope not one of ours event->ignore(); // turn off cursor chartCursor = -2; winWidget->repaint(); return; #endif } void HomeWindow::showControls() { context->tab->chartsettings()->adjustSize(); context->tab->chartsettings()->show(); } void HomeWindow::addChart(GcWindow* newone) { int chartnum = charts.count(); active = true; if (newone) { // add the controls QWidget *x = dynamic_cast(newone)->controls(); QWidget *c = (x != NULL) ? x : new QWidget(this); // link settings button to show controls connect(newone, SIGNAL(showControls()), this, SLOT(showControls())); connect(newone, SIGNAL(closeWindow(GcWindow*)), this, SLOT(closeWindow(GcWindow*))); if (currentStyle == 2 && chartCursor >= 0) controlStack->insertWidget(chartCursor, c); else controlStack->addWidget(c); // watch for enter events! newone->installEventFilter(this); RideItem *notconst = (RideItem*)context->currentRideItem(); newone->setProperty("ride", QVariant::fromValue(notconst)); newone->setProperty("dateRange", property("dateRange")); // add to tabs switch (currentStyle) { case 0 : newone->setContentsMargins(0,25,0,0); newone->setResizable(false); // we need to show on tab selection! //tabbed->addTab(newone, newone->property("title").toString()); tabbed->addWidget(newone); chartbar->addWidget(newone->property("title").toString()); break; case 1 : { // add to tiles // set geometry newone->setFixedWidth((tileArea->width()-50)); newone->setFixedHeight(newone->width() * 0.7); newone->setResizable(false); // we need to show on tab selection! int row = chartnum; // / 2; int column = 0; //chartnum % 2; newone->setContentsMargins(0,25,0,0); tileGrid->addWidget(newone, row, column); } break; case 2 : { double heightFactor = newone->property("heightFactor").toDouble(); double widthFactor = newone->property("widthFactor").toDouble(); // width of area minus content margins and spacing around each item // divided by the number of items double newwidth = (winArea->width() - 20 /* scrollbar */ - (tileMargin*2) /* left and right marings */ - ((widthFactor-1) * tileSpacing) /* internal spacing */ ) / widthFactor; double newheight = (winArea->height() - (tileMargin*2) /* top and bottom marings */ - ((heightFactor-1) * tileSpacing) /* internal spacing */ ) / heightFactor; int minWidth = 10; int minHeight = 10; if (newwidth < minWidth) newwidth = minWidth; newone->setFixedWidth(newwidth); if (newheight < minHeight) newheight = minHeight; else newone->setFixedHeight(newheight); newone->setContentsMargins(0,15,0,0); newone->setResizable(true); // we need to show on tab selection! if (currentStyle == 2 && chartCursor >= 0) winFlow->insert(chartCursor, newone); else winFlow->addWidget(newone); } break; } // add to the list if (currentStyle == 2 && chartCursor >= 0) charts.insert(chartCursor, newone); else charts.append(newone); newone->hide(); // watch for moves etc connect(newone, SIGNAL(resizing(GcWindow*)), this, SLOT(windowResizing(GcWindow*))); connect(newone, SIGNAL(moving(GcWindow*)), this, SLOT(windowMoving(GcWindow*))); connect(newone, SIGNAL(resized(GcWindow*)), this, SLOT(windowResized(GcWindow*))); connect(newone, SIGNAL(moved(GcWindow*)), this, SLOT(windowMoved(GcWindow*))); } // enable disable if (charts.count()) titleEdit->setEnabled(true); else { titleEdit->setEnabled(false); titleEdit->setText(""); } // select the last tab just added active = false; } bool HomeWindow::removeChart(int num, bool confirm) { if (num >= charts.count()) return false; // out of bounds (!) // better let the user confirm since this // is undoable etc - code swiped from delete // ride in Context, seems to work ok ;) if(confirm == true) { QMessageBox msgBox; msgBox.setText(tr("Are you sure you want to remove the chart?")); QPushButton *deleteButton = msgBox.addButton(tr("Remove"),QMessageBox::YesRole); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); msgBox.setIcon(QMessageBox::Critical); msgBox.exec(); if(msgBox.clickedButton() != deleteButton) return false; } charts[num]->hide(); // just in case its currently selected if (clicked == charts[num]) clicked=NULL; // remove the controls QWidget *m = controlStack->widget(num); if (m) controlStack->removeWidget(m); switch(currentStyle) { case 0 : // delete tab and widget chartbar->removeWidget(num); tabbed->removeWidget(tabbed->widget(num)); break; case 1 : // scrolled tileGrid->removeWidget(charts[num]); break; case 2 : // tiled winFlow->removeWidget(charts[num]); break; default: break; // never reached } ((GcWindow*)(charts[num]))->close(); // disconnect ((GcWindow*)(charts[num]))->deleteLater(); charts.removeAt(num); update(); if (charts.count()) titleEdit->setEnabled(true); else { titleEdit->setEnabled(false); titleEdit->setText(""); } return true; } void HomeWindow::resetLayout() { setUpdatesEnabled(false); int numCharts = charts.count(); for(int i = numCharts - 1; i >= 0; i--) { removeChart(i,false); } restoreState(true); for(int i = 0; i < charts.count(); i++) { RideItem *notconst = (RideItem*)context->currentRideItem(); charts[i]->setProperty("ride", QVariant::fromValue(notconst)); charts[i]->setProperty("dateRange", property("dateRange")); if (currentStyle != 0) charts[i]->show(); } setUpdatesEnabled(true); if (currentStyle == 0 && charts.count()) tabSelected(0); } void HomeWindow::showEvent(QShowEvent *) { resizeEvent(NULL); } void HomeWindow::resizeEvent(QResizeEvent * /* e */) { foreach (GcWindow *x, charts) { switch (currentStyle) { case 0 : // tabbed x->setMinimumSize(0,0); x->setMaximumSize(9999,9999); break; case 1 : // tiled x->setFixedWidth((tileArea->width()-50)); x->setFixedHeight(x->width() * 0.7); break; case 2 : // flow { double heightFactor = x->property("heightFactor").toDouble(); double widthFactor = x->property("widthFactor").toDouble(); double newwidth = (winArea->width() - 20 /* scrollbar */ - (tileMargin*2) /* left and right marings */ - ((widthFactor-1) * tileSpacing) /* internal spacing */ ) / widthFactor; double newheight = (winArea->height() - (tileMargin*2) /* top and bottom marings */ - ((heightFactor-1) * tileSpacing) /* internal spacing */ ) / heightFactor; int minWidth = 10; int minHeight = 10; if (newwidth < minWidth) newwidth = minWidth; x->setFixedWidth(newwidth); if (newheight < minHeight) newheight = minHeight; else x->setFixedHeight(newheight); } break; default: break; } } } bool HomeWindow::eventFilter(QObject *object, QEvent *e) { if (!isVisible()) return false; // ignore when we aren't visible // mouse moved and tabbed -- should we show/hide chart popup controls? if (e->type() == QEvent::MouseMove && currentStyle == 0 && tabbed->currentIndex() >= 0) { if (tabbed->currentIndex() >= charts.count()) return false; QPoint pos = tabbed->widget(tabbed->currentIndex())->mapFromGlobal(QCursor::pos()); GcWindow *us = charts[tabbed->currentIndex()]; // lots of nested if statements to breakout as quickly as possible // this code gets called A LOT, since mouse events are from the // application if (us->hasReveal()) { // does this chart have reveal controls? if (us->underMouse()) { // is it even under the cursor? // mouse towards top so reveal if (us->revealed == false && pos.y() < 40) { us->reveal(); us->revealed = true; } // hide as mouse moves away if (us->revealed == true && pos.y() > 80) { us->unreveal(); us->revealed = false; } } else if (us->revealed) { // not under cursor but revealed us->unreveal(); us->revealed = false; } } } // useful for tracking events across the charts //qDebug()<type(); // we watch the mouse when // dropping charts, to update // the cursor position // dropping and mouse move? if (object == this) { if (dropPending == true && e->type() == QEvent::DragMove) { QPoint pos = winWidget->mapFromGlobal(QCursor::pos()); winArea->ensureVisible(pos.x(), pos.y(), 20, 20); chartCursor = pointTile(pos); winWidget->repaint(); // show cursor } return false; } // draw a cursor when winWidget paint event kicks off if (object == winWidget) { if (e->type() == QEvent::Paint) drawCursor(); return false; } // From here on, we're looking at events // from the charts... // is it a mouse enter event? if (e->type() == QEvent::Enter) { // ok which one? for(int i=0; isetCurrentIndex(i); titleEdit->setText(charts[i]->property("title").toString()); } // paint the border! charts[i]->repaint(); return false; } } } else if (e->type() == QEvent::Leave) { // unpaint the border for(int i=0; irepaint(); return false; } } } else if (e->type() == QEvent::MouseButtonPress) { int y = ((QMouseEvent*)(e))->pos().y(); if (y<=15) { // on the title bar! -- which one? for(int i=0; isetProperty("active", false); clicked->repaint(); } charts[i]->setProperty("active", true); charts[i]->repaint(); clicked = charts[i]; controlStack->setCurrentIndex(i); titleEdit->setText(charts[i]->property("title").toString()); } else { // already clicked so unclick it (toggle) charts[i]->setProperty("active", false); charts[i]->repaint(); clicked = NULL; } } } } return false; } return false; } // which tile to place before, -1 means append int HomeWindow::pointTile(QPoint pos) { // find the window that is to the right of // the drop point // is the cursor above a widget? int i; int here = -1; // chart number to the right of cursor... for (i=0; igeometry().contains(pos)) { here = i; break; } } // is it to the right? if (here == -1) { int toRight = pos.x(); while (here == -1 && toRight < winArea->width()) { toRight += 20; QPoint right = QPoint(toRight, pos.y()); for (i=0; igeometry().contains(right)) { here = i; break; } } } } // is it to the left? if (here == -1) { int toLeft = pos.x(); while (here == -1 && toLeft > 0) { toLeft -= 20; QPoint left = QPoint(toLeft, pos.y()); for (i=0; igeometry().contains(left)) { here = i+1; break; } } } } // well it must be below, so if there is // anything at y+20, use that, otherwise // we can assume we are at the end and // append to the list... if (here == -1) { QPoint below(20, pos.y()+20); for (i=0; igeometry().contains(below)) { here = i; break; } } } // all done if (here == -1 || here >= charts.count()) return -1; return here; } void HomeWindow::windowMoved(GcWindow*w) { // fix single click error if (chartCursor == -2) return; // no it didn't! // now drop at the cursor if (currentStyle == 2) { // go find... int before=0; for (;before < charts.count(); before++) { if (charts[before] == w) { QWidget *m = winFlow->takeAt(before)->widget(); QWidget *c = controlStack->widget(before); controlStack->removeWidget(c); QWidget *l = charts.takeAt(before); if (chartCursor > before) chartCursor--; if (chartCursor >= 0) { controlStack->insertWidget(chartCursor, c); winFlow->insert(chartCursor, m); charts.insert(chartCursor, dynamic_cast(l)); } else { controlStack->addWidget(c); winFlow->addWidget(m); charts.append(dynamic_cast(l)); } break; } } winFlow->update(); chartCursor = -2; winWidget->repaint(); } // remove the cursor chartCursor = -2; // -2 means don't show } void HomeWindow::windowResized(GcWindow* /*w*/) { } void HomeWindow::windowMoving(GcWindow* /*w*/) { // ensure the mouse pointer is visible, scrolls // as we get near to the margins... QPoint pos = winWidget->mapFromGlobal(QCursor::pos()); winArea->ensureVisible(pos.x(), pos.y(), 20, 20); chartCursor = pointTile(pos); winWidget->repaint(); // show cursor } void HomeWindow::windowResizing(GcWindow* /*w*/) { QPoint pos = winWidget->mapFromGlobal(QCursor::pos()); winArea->ensureVisible(pos.x(), pos.y(), 20, 20); } void HomeWindow::drawCursor() { if (chartCursor == -2) return; QPainter painter(winWidget); // lets draw to the left of the chart... if (chartCursor > -1) { // background light gray for now? QRect line(charts[chartCursor]->geometry().x()-12, charts[chartCursor]->geometry().y(), 4, charts[chartCursor]->geometry().height()); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(Qt::white)); painter.drawRect(line); } // lets draw at to the right of the // last chart... if (chartCursor == -1 && charts.count()) { // background light gray for now? QRect line(charts[charts.count()-1]->geometry().x() + charts[charts.count()-1]->width() + 8, charts[charts.count()-1]->geometry().y(), 4, charts[charts.count()-1]->geometry().height()); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(Qt::white)); painter.drawRect(line); } } GcWindowDialog::GcWindowDialog(GcWinID type, Context *context, GcWindow **here) : context(context), type(type), here(here) { //setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(windowFlags()); setWindowTitle(tr("Chart Setup")); setMinimumHeight(500); setMinimumWidth(800); setWindowModality(Qt::ApplicationModal); mainLayout = new QVBoxLayout(this); layout = new QHBoxLayout(); mainLayout->addLayout(layout); chartLayout = new QVBoxLayout; layout->addLayout(chartLayout); title = new QLineEdit(this); chartLayout->addWidget(title); title->setText(GcWindowRegistry::title(type)); win = GcWindowRegistry::newGcWindow(type, context); chartLayout->addWidget(win); //win->setFrameStyle(QFrame::Box); // lets not have space for controls if there aren't any layout->setStretch(0, 100); if (win->controls()) { controlLayout = new QFormLayout; controlLayout->addRow(win->controls()); layout->addLayout(controlLayout); layout->setStretch(1, 50); } RideItem *notconst = (RideItem*)context->currentRideItem(); win->setProperty("ride", QVariant::fromValue(notconst)); DateRange dr = context->currentDateRange(); win->setProperty("dateRange", QVariant::fromValue(dr)); QHBoxLayout *buttons = new QHBoxLayout; mainLayout->addLayout(buttons); buttons->addStretch(); buttons->addWidget((cancel=new QPushButton(tr("Cancel"), this))); buttons->addWidget((ok=new QPushButton(tr("OK"), this))); connect(ok, SIGNAL(clicked()), this, SLOT(okClicked())); connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked())); hide(); } void GcWindowDialog::okClicked() { // give back to owner so we can re-use // note that in reject they are not and will // get deleted (this has been verfied with // some debug statements in ~GcWindow). // set its title property and geometry factors win->setProperty("title", title->text()); win->setProperty("widthFactor", 2.00); win->setProperty("heightFactor", 2.00); win->setProperty("GcWinID", type); accept(); } void GcWindowDialog::cancelClicked() { reject(); } int GcWindowDialog::exec() { if (QDialog::exec()) { *here = win; } else { *here = NULL; } return 0; } /*---------------------------------------------------------------------- * Save and restore state (xxxx-layout.xml) *--------------------------------------------------------------------*/ // static helper to protect special xml characters // ideally we would use XMLwriter to do this but // the file format is trivial and this implementation // is easier to follow and modify... for now. static QString xmlprotect(QString string) { QTextEdit trademark("™"); // process html encoding of(TM) QString tm = trademark.toPlainText(); QString s = string; s.replace( tm, "™" ); s.replace( "&", "&" ); s.replace( ">", ">" ); s.replace( "<", "<" ); s.replace( "\"", """ ); s.replace( "\'", "'" ); return s; } static QString unprotect(QString buffer) { // get local TM character code QTextEdit trademark("™"); // process html encoding of(TM) QString tm = trademark.toPlainText(); // remove quotes QString s = buffer.trimmed(); // replace html (TM) with local TM character s.replace( "™", tm ); // html special chars are automatically handled // NOTE: other special characters will not work // cross-platform but will work locally, so not a biggie // i.e. if thedefault charts.xml has a special character // in it it should be added here return s; } void HomeWindow::saveState() { // run through each chart and save its USER properties // we do not save all the other Qt properties since // we're not interested in them // NOTE: currently we support QString, int, double and bool types - beware custom types!! if (charts.count() == 0) return; // don't save empty, use default instead QString filename = context->athlete->home.absolutePath() + "/" + name + "-layout.xml"; QFile file(filename); file.open(QFile::WriteOnly); file.resize(0); QTextStream out(&file); out.setCodec("UTF-8"); out<<"\n"; // iterate over charts foreach (GcWindow *chart, charts) { GcWinID type = chart->property("type").value(); out<<"\t(type)<<"\" " <<"name=\""<property("instanceName").toString())<<"\" " <<"title=\""<property("title").toString())<<"\" >\n"; // iterate over chart properties const QMetaObject *m = chart->metaObject(); for (int i=0; ipropertyCount(); i++) { QMetaProperty p = m->property(i); if (p.isUser(chart)) { out<<"\t\t(); s << x; out<\n"; } } out<<"\t\n"; } out<<"\n"; file.close(); } void HomeWindow::restoreState(bool useDefault) { bool defaultUsed = false; // restore window state QString filename = context->athlete->home.absolutePath() + "/" + name + "-layout.xml"; QFileInfo finfo(filename); if (useDefault) QFile::remove(filename); // use a default if not there if (!finfo.exists()) { filename = QString(":xml/%1-layout.xml").arg(name); defaultUsed = true; } // now go read... QFile file(filename); // setup the handler QXmlInputSource source(&file); QXmlSimpleReader xmlReader; ViewParser handler(context); xmlReader.setContentHandler(&handler); xmlReader.setErrorHandler(&handler); // parse and instantiate the charts xmlReader.parse(source); // translate the titles translateChartTitles(handler.charts); // translate the metrics, but only if the built-in "default.XML"s are read (and only for LTM charts) if (defaultUsed) { // define and fill translation maps QMap nMap; // names QMap uMap; // unit of measurement LTMTool::getMetricsTranslationMap(nMap, uMap, context->athlete->useMetricUnits); // check all charts for LTMWindow(s) for (int i=0; i (handler.charts[i]); if (ltmW) { // the current chart is an LTMWindow, let's translate // now get the LTMMetrics LTMSettings workSettings = ltmW->getSettings(); for (int j=0; japplySettings(workSettings); } } } // layout the results styleChanged(handler.style); foreach(GcWindow *chart, handler.charts) addChart(chart); } // // view layout parser - reads in athletehome/xxx-layout.xml // bool ViewParser::startDocument() { return true; } bool ViewParser::endElement( const QString&, const QString&, const QString &qName ) { if (qName == "chart") { // add to the list charts.append(chart); } return true; } bool ViewParser::startElement( const QString&, const QString&, const QString &name, const QXmlAttributes &attrs ) { if (name == "layout") { for(int i=0; i(typeStr.toInt()); chart = GcWindowRegistry::newGcWindow(type, context); chart->hide(); chart->setProperty("title", QVariant(title)); } else if (name == "property") { QString name, type, value; // get attributes for(int i=0; isetProperty(name.toLatin1(), QVariant(value.toInt())); if (type == "double") chart->setProperty(name.toLatin1(), QVariant(value.toDouble())); // deprecate dateRange asa chart propert THAT IS DSAVED IN STATE if (type == "QString" && name != "dateRange") chart->setProperty(name.toLatin1(), QVariant(QString(value))); if (type == "QDate") chart->setProperty(name.toLatin1(), QVariant(QDate::fromString(value))); if (type == "bool") chart->setProperty(name.toLatin1(), QVariant(value.toInt() ? true : false)); if (type == "LTMSettings") { QByteArray base64(value.toLatin1()); QByteArray unmarshall = QByteArray::fromBase64(base64); QDataStream s(&unmarshall, QIODevice::ReadOnly); LTMSettings x; s >> x; chart->setProperty(name.toLatin1(), QVariant().fromValue(x)); } } return true; } bool ViewParser::characters( const QString&) { return true; } bool ViewParser::endDocument() { return true; } void HomeWindow::closeWindow(GcWindow*thisone) { if (charts.contains(thisone)) removeChart(charts.indexOf(thisone)); } void HomeWindow::translateChartTitles(QList charts) { // Map default (english) title to external (Localized) name, new default // charts in *layout.xml need to be added to this list to be translated QMap titleMap; titleMap.insert("Ride Log", tr("Ride Log")); titleMap.insert("Aerobic Power", tr("Aerobic Power")); titleMap.insert("Anaerobic Power", tr("Anaerobic Power")); titleMap.insert("Cadence", tr("Cadence")); titleMap.insert("Calendar", tr("Calendar")); titleMap.insert("CP", tr("CP")); titleMap.insert("Details", tr("Details")); titleMap.insert("Distance", tr("Distance")); titleMap.insert("Edit", tr("Edit")); titleMap.insert("Elapsed Time", tr("Elapsed Time")); titleMap.insert("Heartrate", tr("Heartrate")); titleMap.insert("Lap", tr("Lap")); titleMap.insert("Map", tr("Map")); titleMap.insert("Performance", tr("Performance")); titleMap.insert("PMC", tr("PMC")); titleMap.insert("Power", tr("Power")); titleMap.insert("QA", tr("QA")); titleMap.insert("Ride", tr("Ride")); titleMap.insert("Speed", tr("Speed")); titleMap.insert("Summary", tr("Summary")); titleMap.insert("Target Power", tr("Target Power")); titleMap.insert("Time and Distance", tr("Time and Distance")); titleMap.insert("Time In Zone", tr("Time In Zone")); titleMap.insert("Training Mix", tr("Training Mix")); titleMap.insert("W/kg", tr("W/kg")); titleMap.insert("Workout", tr("Workout")); titleMap.insert("Stress", tr("Stress")); titleMap.insert("Scatter", tr("Scatter")); titleMap.insert("HrPw", tr("HrPw")); titleMap.insert("Activity Log", tr("Activity Log")); titleMap.insert("HrPw", tr("HrPw")); titleMap.insert("Tracker", tr("Tracker")); titleMap.insert("CP History", tr("CP History")); titleMap.insert("Library", tr("Library")); foreach(GcWindow *chart, charts) { QString chartTitle = chart->property("title").toString(); chart->setProperty("title", titleMap.value(chartTitle, chartTitle)); } }