/* * Copyright (c) 2013 Mark Liversedge (liversedge@gmail.com) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "TabView.h" #include "Tab.h" #include "Context.h" #include "Athlete.h" #include "RideItem.h" #include "BlankState.h" #include "HomeWindow.h" #include "GcWindowRegistry.h" #include "TrainDB.h" #include "MetricAggregator.h" #include "RideNavigator.h" #include "IntervalNavigator.h" #include "MainWindow.h" #include "Settings.h" TabView::TabView(Context *context, int type) : QWidget(context->tab), context(context), type(type), _sidebar(true), _tiled(false), _selected(false), lastHeight(130), sidewidth(0), active(false), stack(NULL), splitter(NULL), mainSplitter(NULL), sidebar_(NULL), bottom_(NULL), page_(NULL), blank_(NULL) { // setup the basic widget QVBoxLayout *layout = new QVBoxLayout(this); setContentsMargins(0,0,0,0); layout->setContentsMargins(0,0,0,0); layout->setSpacing(0); stack = new QStackedWidget(this); stack->setContentsMargins(0,0,0,0); stack->setFrameStyle(QFrame::Plain | QFrame::NoFrame); stack->setMinimumWidth(500); stack->setMinimumHeight(500); layout->addWidget(stack); // the splitter splitter = new QSplitter(this); splitter->setHandleWidth(1); splitter->setStyleSheet(" QSplitter::handle { background-color: rgb(120,120,120); color: darkGray; }"); splitter->setFrameStyle(QFrame::NoFrame); splitter->setContentsMargins(0, 0, 0, 0); // attempting to follow some UI guides splitter->setOpaqueResize(true); // redraw when released, snappier UI stack->insertWidget(0, splitter); // splitter always at index 0 QString heading = tr("Compare Rides and Intervals"); if (type == VIEW_HOME) heading = tr("Compare Date Ranges"); mainSplitter = new ViewSplitter(Qt::Vertical, heading, this); mainSplitter->setHandleWidth(23); mainSplitter->setFrameStyle(QFrame::NoFrame); mainSplitter->setContentsMargins(0, 0, 0, 0); // attempting to follow some UI guides mainSplitter->setOpaqueResize(true); // redraw when released, snappier UI // the animator anim = new QPropertyAnimation(mainSplitter, "hpos"); connect(splitter,SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved(int,int))); connect(context,SIGNAL(configChanged()), this, SLOT(configChanged())); } TabView::~TabView() { if (page_) page_->saveState(); } void TabView::setRide(RideItem*ride) { page()->setProperty("ride", QVariant::fromValue(dynamic_cast(ride))); } void TabView::splitterMoved(int pos,int) { if (sidebar_ == NULL) return; // we haven't set sidebar yet. // show / hide sidebar as dragged.. if ((pos == 0 && sidebarEnabled())) setSidebarEnabled(false); // remember the size the user selected sidewidth = splitter->sizes()[0]; // we now have splitter settings for each view QString setting = QString("%1/%2").arg(GC_SETTINGS_SPLITTER_SIZES).arg(type); appsettings->setCValue(context->athlete->cyclist, setting, splitter->saveState()); // if user moved us then tell ride navigator if // we are the analysis view // all a bit of a hack to stop the column widths from // being adjusted as the splitter gets resized and reset if (type == VIEW_ANALYSIS && active == false && context->tab->rideNavigator()->geometry().width() != 100) context->tab->rideNavigator()->setWidth(context->tab->rideNavigator()->geometry().width()); } void TabView::resizeEvent(QResizeEvent *) { active = true; // we're mucking about, so ignore in splitterMoved ... if (sidewidth < 200) { sidewidth = splitter->sizes()[0]; } else { // splitter sizes will have changed, so lets get the new // total width and set the handle back to where it was QList csizes = splitter->sizes(); if (csizes.count() == 2) { // total will have changed int tot = csizes[0] + csizes[1]; if (tot >= sidewidth) { // set left back to where it was before! csizes[0] = sidewidth; csizes[1] = tot-sidewidth; splitter->setSizes(csizes); sidewidth = splitter->sizes()[0]; } } } active = false; } void TabView::setSidebar(QWidget *sidebar) { sidebar_ = sidebar; splitter->insertWidget(0, sidebar); configChanged(); } QString TabView::ourStyleSheet() { return QString::fromUtf8("QScrollBar { background-color: %1; border: 0px; }" #ifndef Q_OS_MAC "QTabWidget { background: %1; }" "QTabWidget::pane { border: 1px solid %2; } " "QTextEdit { background: %1; }" "QTextEdit#metadata { background: %3; }" #endif "QTreeView { background: %1; }" "QScrollBar:vertical {" " border: 0px solid darkGray; " " background:%1;" " width: 8px; " " margin: 1px 1px 1px 1px;" "}" "QScrollBar::handle:vertical:hover {" " background: lightGray; " " min-height: 0px;" "" "}" "QScrollBar::handle:vertical {" " background: darkGray; " " min-height: 0px;" "" "}" "QScrollBar::add-line:vertical {" " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," " stop: 0 rgb(32, 47, 130), stop: 0.5 rgb(32, 47, 130), stop:1 rgb(32, 47, 130));" " height: px;" " subcontrol-position: bottom;" " subcontrol-origin: margin;" "}" "QScrollBar::sub-line:vertical {" " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," " stop: 0 rgb(32, 47, 130), stop: 0.5 rgb(32, 47, 130), stop:1 rgb(32, 47, 130));" " height: 0px;" " subcontrol-position: top;" " subcontrol-origin: margin;" "}" "QScrollBar:horizontal {" " border: 0px solid darkGray; " " background:%1;" " width: 8px; " " margin: 1px 1px 1px 1px;" "}" "QScrollBar::handle:horizontal {" " background: darkGray; " " min-height: 0px;" "" "}" "QScrollBar::add-line:horizontal {" " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," " stop: 0 rgb(32, 47, 130), stop: 0.5 rgb(32, 47, 130), stop:1 rgb(32, 47, 130));" " height: px;" " subcontrol-position: bottom;" " subcontrol-origin: margin;" "}" "QScrollBar::sub-line:horizontal {" " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," " stop: 0 rgb(32, 47, 130), stop: 0.5 rgb(32, 47, 130), stop:1 rgb(32, 47, 130));" " height: 0px;" " subcontrol-position: top;" " subcontrol-origin: margin;" "}" "QTableWidget::item:hover { color: black; background: lightGray; }" "QTreeView::item:hover { color: black; background: lightGray; }" "").arg(GColor(CPLOTBACKGROUND).name()) #ifndef Q_OS_MAC .arg(GColor(CPLOTGRID).name()) .arg(GCColor::alternateColor(GColor(CPLOTBACKGROUND)).name()) #endif ; } void TabView::configChanged() { #if (defined Q_OS_LINUX) || (defined Q_OS_WIN) // style that sucker if (sidebar_) { sidebar_->setStyleSheet(ourStyleSheet()); } #endif } void TabView::setPage(HomeWindow *page) { page_ = page; // add to mainSplitter // now reset the splitter mainSplitter->insertWidget(-1, page); mainSplitter->setStretchFactor(0,0); mainSplitter->setCollapsible(0, false); splitter->insertWidget(-1, mainSplitter); // restore sizes QString setting = QString("%1/%2").arg(GC_SETTINGS_SPLITTER_SIZES).arg(type); QVariant splitterSizes = appsettings->cvalue(context->athlete->cyclist, setting); // new (3.1) mechanism if (splitterSizes.toByteArray().size() > 1 ) { splitter->restoreState(splitterSizes.toByteArray()); } else { // use old (v3 or earlier) mechanism QVariant splitterSizes = appsettings->cvalue(context->athlete->cyclist, GC_SETTINGS_SPLITTER_SIZES); if (splitterSizes.toByteArray().size() > 1 ) { splitter->restoreState(splitterSizes.toByteArray()); } else { // sensible default as never run before! QList sizes; sizes.append(200); sizes.append(context->mainWindow->width()-200); splitter->setSizes(sizes); } } } void TabView::setBottom(QWidget *widget) { bottom_ = widget; bottom_->hide(); mainSplitter->insertWidget(-1, bottom_); mainSplitter->setCollapsible(1, true); // XXX we need a ComparePane widget ... mainSplitter->setStretchFactor(1,1); } void TabView::dragEvent(bool x) { setShowBottom(x); context->mainWindow->setToolButtons(); // toolbuttons reflect show/hide status } // hide and show bottom - but with a little animation ... void TabView::setShowBottom(bool x) { // remember last height used when hidind if (!x && bottom_) lastHeight = bottom_->height(); // basic version for now .. remembers and sets horizontal position precisely // adding animation should be easy from here if (bottom_) { if (x) { // set to the last value.... bottom_->show(); anim->setDuration(lastHeight * 3); anim->setEasingCurve(QEasingCurve(QEasingCurve::Linear)); anim->setKeyValueAt(0,mainSplitter->maxhpos()-22); anim->setKeyValueAt(1,mainSplitter->maxhpos()-(lastHeight+22)); anim->start(); } else { // need a hide animator to hide on timeout //anim->setDuration(200); //anim->setEasingCurve(QEasingCurve(QEasingCurve::Linear)); //anim->setKeyValueAt(0,mainSplitter->maxhpos()-(lastHeight+22)); //anim->setKeyValueAt(1,mainSplitter->maxhpos()-22); //anim->start(); bottom_->hide(); } } } void TabView::setBlank(BlankStatePage *blank) { blank_ = blank; blank->hide(); stack->insertWidget(1, blank); // blank state always at index 1 // and when stuff happens lets check connect(blank, SIGNAL(closeClicked()), this, SLOT(checkBlank())); connect(context->athlete->metricDB, SIGNAL(dataChanged()), this, SLOT(checkBlank())); connect(context, SIGNAL(configChanged()), this, SLOT(checkBlank())); connect(trainDB, SIGNAL(dataChanged()), this, SLOT(checkBlank())); } void TabView::sidebarChanged() { if (sidebar_ == NULL) return; if (sidebarEnabled()) { setUpdatesEnabled(false); sidebar_->show(); // Restore sizes QString setting = QString("%1/%2").arg(GC_SETTINGS_SPLITTER_SIZES).arg(type); QVariant splitterSizes = appsettings->cvalue(context->athlete->cyclist, setting); if (splitterSizes.toByteArray().size() > 1 ) { splitter->restoreState(splitterSizes.toByteArray()); splitter->setOpaqueResize(true); // redraw when released, snappier UI } // if it was collapsed we need set to at least 200 // unless the mainwindow isn't big enough if (sidebar_->width()<10) { int size = width() - 200; if (size>200) size = 200; QList sizes; sizes.append(size); sizes.append(width()-size); splitter->setSizes(sizes); } // if user moved us then tell ride navigator if // we are the analysis view // all a bit of a hack to stop the column widths from // being adjusted as the splitter gets resized and reset if (type == VIEW_ANALYSIS && active == false && context->tab->rideNavigator()->geometry().width() != 100) context->tab->rideNavigator()->setWidth(context->tab->rideNavigator()->geometry().width()); if (type == VIEW_INTERVAL && active == false && context->tab->routeNavigator()->geometry().width() != 100) { context->tab->routeNavigator()->setWidth(context->tab->routeNavigator()->geometry().width()); } if (type == VIEW_INTERVAL && active == false && context->tab->bestNavigator()->geometry().width() != 100) { context->tab->bestNavigator()->setWidth(context->tab->bestNavigator()->geometry().width()); } setUpdatesEnabled(true); } else sidebar_->hide(); } void TabView::tileModeChanged() { if (page_) page_->setStyle(isTiled() ? 2 : 0); } void TabView::selectionChanged() { // we got selected.. if (isSelected()) { // or do we need to show blankness? if (isBlank() && blank_ && page_ && blank_->canShow()) { splitter->hide(); blank()->show(); stack->setCurrentIndex(1); } else if (blank_ && page_) { blank()->hide(); splitter->show(); emit onSelectionChanged(); // give view a change to prepare page()->selected(); // select the view stack->setCurrentIndex(0); } } else { emit onSelectionChanged(); // give view a change to clear } } void TabView::resetLayout() { if (page_) page_->resetLayout(); } void TabView::addChart(GcWinID id) { if (page_) page_->appendChart(id); } void TabView::checkBlank() { selectionChanged(); // run through the code again }