mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
452 lines
14 KiB
C++
452 lines
14 KiB
C++
/*
|
|
* 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 "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 Activities 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(qint32)), this, SLOT(configChanged(qint32)));
|
|
}
|
|
|
|
TabView::~TabView()
|
|
{
|
|
if (page_) page_->saveState();
|
|
}
|
|
|
|
void
|
|
TabView::setRide(RideItem*ride)
|
|
{
|
|
page()->setProperty("ride", QVariant::fromValue<RideItem*>(dynamic_cast<RideItem*>(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<int> 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(CONFIG_APPEARANCE);
|
|
}
|
|
|
|
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(qint32)
|
|
{
|
|
#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<int> 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, SIGNAL(rideAdded(RideItem*)), this, SLOT(checkBlank()));
|
|
connect(context, SIGNAL(rideDeleted(RideItem*)), this, SLOT(checkBlank()));
|
|
connect(context, SIGNAL(configChanged(qint32)), 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<int> 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
|
|
}
|