Files
GoldenCheetah/src/AnalysisSidebar.cpp
Mark Liversedge ba748461d7 User Intervals Tree always at top
.. in sidebar
2015-05-22 15:13:21 +01:00

901 lines
33 KiB
C++

/*
* 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 "AnalysisSidebar.h"
#include "MainWindow.h"
#include "Context.h"
#include "Athlete.h"
#include "Settings.h"
#include "Units.h"
#include "HelpWhatsThis.h"
#include <QtGui>
// metadata support
#include "RideMetadata.h"
#include "SpecialFields.h"
// working with intervals
#include "IntervalItem.h"
#include "AddIntervalDialog.h"
#include "BestIntervalDialog.h"
// working with routes
#include "Route.h"
AnalysisSidebar::AnalysisSidebar(Context *context) : QWidget(context->mainWindow), context(context)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0,0,0,0);
mainLayout->setSpacing(0);
setContentsMargins(0,0,0,0);
splitter = new GcSplitter(Qt::Vertical);
mainLayout->addWidget(splitter);
// calendar
calendarWidget = new GcMultiCalendar(context);
calendarItem = new GcSplitterItem(tr("Calendar"), iconFromPNG(":images/sidebar/calendar.png"), this);
calendarItem->addWidget(calendarWidget);
HelpWhatsThis *helpCalendar = new HelpWhatsThis(calendarWidget);
calendarWidget->setWhatsThis(helpCalendar->getWhatsThisText(HelpWhatsThis::SideBarRidesView_Calendar));
// Activity History
rideNavigator = new RideNavigator(context, true);
rideNavigator->setProperty("nomenu", true);
groupByMapper = NULL;
// retrieve settings (properties are saved when we close the window)
if (appsettings->cvalue(context->athlete->cyclist, GC_NAVHEADINGS, "").toString() != "") {
rideNavigator->setSortByIndex(appsettings->cvalue(context->athlete->cyclist, GC_SORTBY).toInt());
rideNavigator->setSortByOrder(appsettings->cvalue(context->athlete->cyclist, GC_SORTBYORDER).toInt());
rideNavigator->setGroupBy(appsettings->cvalue(context->athlete->cyclist, GC_NAVGROUPBY).toInt());
rideNavigator->setColumns(appsettings->cvalue(context->athlete->cyclist, GC_NAVHEADINGS).toString());
rideNavigator->setWidths(appsettings->cvalue(context->athlete->cyclist, GC_NAVHEADINGWIDTHS).toString());
}
QWidget *activityHistory = new QWidget(this);
activityHistory->setContentsMargins(0,0,0,0);
#ifndef Q_OS_MAC // not on mac thanks
activityHistory->setStyleSheet("padding: 0px; border: 0px; margin: 0px;");
#endif
QVBoxLayout *activityLayout = new QVBoxLayout(activityHistory);
activityLayout->setSpacing(0);
activityLayout->setContentsMargins(0,0,0,0);
activityLayout->addWidget(rideNavigator);
activityItem = new GcSplitterItem(tr("Activities"), iconFromPNG(":images/sidebar/folder.png"), this);
QAction *activityAction = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
activityItem->addAction(activityAction);
connect(activityAction, SIGNAL(triggered(void)), this, SLOT(analysisPopup()));
activityItem->addWidget(activityHistory);
// INTERVALS
intervalTree = new IntervalTreeView(context);
intervalTree->setAnimated(true);
intervalTree->setColumnCount(1);
intervalTree->setIndentation(5);
intervalTree->setSortingEnabled(false);
intervalTree->header()->hide();
intervalTree->setAlternatingRowColors (false);
intervalTree->setSelectionBehavior(QAbstractItemView::SelectRows);
intervalTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
intervalTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
intervalTree->setContextMenuPolicy(Qt::CustomContextMenu);
intervalTree->setFrameStyle(QFrame::NoFrame);
// create tree for user intervals, so its always at the top
QTreeWidgetItem *tree = new QTreeWidgetItem(intervalTree->invisibleRootItem(), RideFileInterval::USER);
tree->setData(0, Qt::UserRole, qVariantFromValue((void *)NULL)); // no intervalitem related
tree->setText(0, RideFileInterval::typeDescription(RideFileInterval::USER));
tree->setForeground(0, GColor(CPLOTMARKER));
QFont bold;
bold.setWeight(QFont::Bold);
tree->setFont(0, bold);
tree->setExpanded(true);
tree->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled);
tree->setHidden(true);
trees.insert(RideFileInterval::USER, tree);
intervalSummaryWindow = new IntervalSummaryWindow(context);
HelpWhatsThis *helpSummaryWindow = new HelpWhatsThis(intervalSummaryWindow);
intervalSummaryWindow->setWhatsThis(helpSummaryWindow->getWhatsThisText(HelpWhatsThis::SideBarRidesView_Intervals));
intervalSplitter = new QSplitter(this);
intervalSplitter->setHandleWidth(1);
intervalSplitter->setOrientation(Qt::Vertical);
intervalSplitter->addWidget(intervalTree);
activeInterval = NULL;
intervalSplitter->addWidget(intervalSummaryWindow);
intervalSplitter->setFrameStyle(QFrame::NoFrame);
intervalSplitter->setCollapsible(0, false);
intervalSplitter->setCollapsible(1, false);
HelpWhatsThis *helpIntervalSplitter = new HelpWhatsThis(intervalSplitter);
intervalSplitter->setWhatsThis(helpIntervalSplitter->getWhatsThisText(HelpWhatsThis::SideBarRidesView_Intervals));
intervalItem = new GcSplitterItem(tr("Intervals"), iconFromPNG(":images/mac/stop.png"), this);
QAction *moreIntervalAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
intervalItem->addAction(moreIntervalAct);
connect(moreIntervalAct, SIGNAL(triggered(void)), this, SLOT(intervalPopup()));
intervalItem->addWidget(intervalSplitter);
splitter->addWidget(calendarItem);
splitter->addWidget(activityItem);
splitter->addWidget(intervalItem);
splitter->prepare(context->athlete->cyclist, "analysis");
// GC signal
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
connect(context, SIGNAL(intervalsUpdate(RideItem*)), this, SLOT(intervalsUpdate(RideItem*)));
connect(context, SIGNAL(intervalItemSelectionChanged(IntervalItem*)), this, SLOT(intervalItemSelectionChanged(IntervalItem*)));
// right click menus...
connect(rideNavigator,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showActivityMenu(const QPoint &)));
connect(intervalTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showIntervalMenu(const QPoint &)));
connect(intervalTree,SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(clickZoomInterval(QTreeWidgetItem*)));
connect(intervalTree,SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged()));
connect (context, SIGNAL(filterChanged()), this, SLOT(filterChanged()));
configChanged(CONFIG_APPEARANCE);
}
void
AnalysisSidebar::intervalItemSelectionChanged(IntervalItem*x)
{
if (!x) return; // no null please.
// find it and select/deselect
QMapIterator<RideFileInterval::intervaltype, QTreeWidgetItem*> i(trees);
i.toFront();
while(i.hasNext()) {
i.next();
// loop through the intervals for this tree
for(int j=0; j<i.value()->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = i.value()->child(j)->data(0, Qt::UserRole);
if (static_cast<IntervalItem*>(v.value<void*>()) == x) {
i.value()->child(j)->setSelected(x->selected);
return;
}
}
}
}
void
AnalysisSidebar::intervalsUpdate(RideItem *changed)
{
// refresh the sidebar for ours
if (changed && changed == context->currentRideItem()) setRide(const_cast<RideItem*>(context->currentRideItem()));
}
void
AnalysisSidebar::setRide(RideItem*ride)
{
// FIRST REFRESH THE INTERVAL TREE
// stop SEGV in widgets watching for intervals being
// selected whilst we are deleting them from the tree
intervalTree->blockSignals(true);
// refresh interval list for bottom left
// clear each tree and hide it until items
// are added. That way we get to keep the state
// for expanded (users can unexpand it and that
// is kept as they choose different activities)
QMapIterator<RideFileInterval::intervaltype, QTreeWidgetItem*> i(trees);
i.toFront();
while(i.hasNext()) {
i.next();
i.value()->takeChildren();
// now clean and hide the tree
QList<QTreeWidgetItem *> items = i.value()->takeChildren();
for (int j=0; j<items.count(); j++) delete items.at(j);
i.value()->setHidden(true);
}
// now add the intervals for the current ride
if (ride) { // only if we have a ride pointer
// tree has bold font
QFont bold;
bold.setWeight(QFont::Bold);
// for each type add a tree structure
foreach(IntervalItem *interval, ride->intervals()) {
// create trees on demand, not all will be needed by all
// users and just as useful to create here than at startup
// also means we don't need to know what all the types are
// in advance, we create when we see one
// BUT the USER tree is always created
QTreeWidgetItem *tree = trees.value(interval->type, NULL);
if (tree == NULL) {
tree = new QTreeWidgetItem(intervalTree->invisibleRootItem(), interval->type);
tree->setData(0, Qt::UserRole, qVariantFromValue((void *)NULL)); // no intervalitem related
tree->setText(0, RideFileInterval::typeDescription(interval->type));
tree->setForeground(0, GColor(CPLOTMARKER));
tree->setFont(0, bold);
tree->setExpanded(true);
tree->setFlags(Qt::ItemIsEnabled);
trees.insert(interval->type, tree);
}
tree->setHidden(false); // we have items, so make sure it is visible
// add this interval to the tree
QTreeWidgetItem *add = new QTreeWidgetItem(tree, interval->type);
add->setText(0, interval->name);
add->setData(0, Qt::UserRole, qVariantFromValue((void*)interval));
add->setFlags(Qt::ItemIsEnabled
#if QT_VERSION > 0x50000
| Qt::ItemNeverHasChildren
#endif
| Qt::ItemIsSelectable
| Qt::ItemIsDragEnabled
| Qt::ItemIsEditable);
// set interval selected to current state since it is
// being maintained and we are called on resequencing etc
add->setSelected(interval->selected);
}
}
// all done, so connected widgets can receive signals now
intervalTree->blockSignals(false);
// now the other widgets
calendarWidget->setRide(ride);
rideNavigator->setRide(ride);
}
void
AnalysisSidebar::itemSelectionChanged()
{
// update RideItem::intervals to reflect user selection
QMapIterator<RideFileInterval::intervaltype, QTreeWidgetItem*> i(trees);
i.toFront();
while(i.hasNext()) {
i.next();
// loop through the intervals for this tree
for(int j=0; j<i.value()->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = i.value()->child(j)->data(0, Qt::UserRole);
// make the IntervalItem selected flag reflect the current selection state
static_cast<IntervalItem*>(v.value<void*>())->selected = i.value()->child(j)->isSelected();
}
}
// clear hover now
context->notifyIntervalHover(NULL);
// notify the world
context->notifyIntervalSelected();
}
void
AnalysisSidebar::close()
{
appsettings->setCValue(context->athlete->cyclist, GC_SORTBY, rideNavigator->sortByIndex());
appsettings->setCValue(context->athlete->cyclist, GC_SORTBYORDER, rideNavigator->sortByOrder());
appsettings->setCValue(context->athlete->cyclist, GC_NAVGROUPBY, rideNavigator->groupBy());
appsettings->setCValue(context->athlete->cyclist, GC_NAVHEADINGS, rideNavigator->columns());
appsettings->setCValue(context->athlete->cyclist, GC_NAVHEADINGWIDTHS, rideNavigator->widths());
}
void
AnalysisSidebar::configChanged(qint32)
{
//calendarWidget->setPalette(GCColor::palette());
//intervalSummaryWindow->setPalette(GCColor::palette());
//intervalSummaryWindow->setStyleSheet(GCColor::stylesheet());
rideNavigator->tableView->viewport()->setPalette(GCColor::palette());
rideNavigator->tableView->viewport()->setStyleSheet(QString("background: %1;").arg(GColor(CPLOTBACKGROUND).name()));
// interval tree
intervalTree->setPalette(GCColor::palette());
intervalTree->setStyleSheet(GCColor::stylesheet());
QMapIterator<RideFileInterval::intervaltype, QTreeWidgetItem*> i(trees);
i.toFront();
while(i.hasNext()) {
i.next();
i.value()->setForeground(0, GColor(CPLOTMARKER));
}
repaint();
}
void
AnalysisSidebar::filterChanged()
{
if (context->isfiltered) setFilter(context->filters);
else clearFilter();
}
void
AnalysisSidebar::setFilter(QStringList filter)
{
rideNavigator->searchStrings(filter);
calendarWidget->setFilter(filter);
}
void
AnalysisSidebar::clearFilter()
{
rideNavigator->clearSearch();
calendarWidget->clearFilter();
}
/***********************************************************************
* Sidebar Menus
**********************************************************************/
void
AnalysisSidebar::analysisPopup()
{
// set the point for the menu and call below
showActivityMenu(this->mapToGlobal(QPoint(activityItem->pos().x()+activityItem->width()-20, activityItem->pos().y())));
}
void
AnalysisSidebar::showActivityMenu(const QPoint &pos)
{
if (context->ride == 0) return; //none selected!
RideItem *rideItem = context->ride;
if (rideItem != NULL) {
QMenu menu(rideNavigator);
QAction *actSaveRide = new QAction(tr("Save Changes"), rideNavigator);
connect(actSaveRide, SIGNAL(triggered(void)), context->mainWindow, SLOT(saveRide()));
QAction *revertRide = new QAction(tr("Revert to Saved version"), rideNavigator);
connect(revertRide, SIGNAL(triggered(void)), context->mainWindow, SLOT(revertRide()));
QAction *actDeleteRide = new QAction(tr("Delete Activity"), rideNavigator);
connect(actDeleteRide, SIGNAL(triggered(void)), context->mainWindow, SLOT(deleteRide()));
QAction *actSplitRide = new QAction(tr("Split Activity"), rideNavigator);
connect(actSplitRide, SIGNAL(triggered(void)), context->mainWindow, SLOT(splitRide()));
if (rideItem->isDirty() == true) {
menu.addAction(actSaveRide);
menu.addAction(revertRide);
}
menu.addAction(actDeleteRide);
menu.addAction(actSplitRide);
#ifdef GC_HAVE_ICAL
QAction *actUploadCalendar = new QAction(tr("Upload Activity to Calendar"), rideNavigator);
connect(actUploadCalendar, SIGNAL(triggered(void)), context->mainWindow, SLOT(uploadCalendar()));
menu.addAction(actUploadCalendar);
#endif
QAction *actFindBest = new QAction(tr("Find Intervals..."), intervalItem);
connect(actFindBest, SIGNAL(triggered(void)), this, SLOT(addIntervals(void)));
menu.addAction(actFindBest);
menu.addSeparator();
// ride navigator stuff
QAction *colChooser = new QAction(tr("Show Column Chooser"), rideNavigator);
connect(colChooser, SIGNAL(triggered(void)), rideNavigator, SLOT(showColumnChooser()));
menu.addAction(colChooser);
if (rideNavigator->groupBy() >= 0) {
// already grouped lets ungroup
QAction *nogroups = new QAction(tr("Do Not Show In Groups"), rideNavigator);
connect(nogroups, SIGNAL(triggered(void)), rideNavigator, SLOT(noGroups()));
menu.addAction(nogroups);
} else {
QMenu *groupByMenu = new QMenu(tr("Group By"), rideNavigator);
groupByMenu->setEnabled(true);
menu.addMenu(groupByMenu);
// add menu options for each column
if (groupByMapper) delete groupByMapper;
groupByMapper = new QSignalMapper(this);
connect(groupByMapper, SIGNAL(mapped(const QString &)), rideNavigator, SLOT(setGroupByColumnName(QString)));
foreach(QString heading, rideNavigator->columnNames()) {
if (heading == "*") continue; // special hidden column
QAction *groupByAct = new QAction(heading, rideNavigator);
connect(groupByAct, SIGNAL(triggered()), groupByMapper, SLOT(map()));
groupByMenu->addAction(groupByAct);
// map action to column heading
groupByMapper->setMapping(groupByAct, heading);
}
}
// expand / collapse
QAction *expandAll = new QAction(tr("Expand All"), rideNavigator);
connect(expandAll, SIGNAL(triggered(void)), rideNavigator->tableView, SLOT(expandAll()));
menu.addAction(expandAll);
// expand / collapse
QAction *collapseAll = new QAction(tr("Collapse All"), rideNavigator);
connect(collapseAll, SIGNAL(triggered(void)), rideNavigator->tableView, SLOT(collapseAll()));
menu.addAction(collapseAll);
menu.exec(pos);
}
}
void
AnalysisSidebar::intervalPopup()
{
// Hamburger menu appears on the sidebar widget
// to manipulate the interval tree
// always show the 'find best' 'find peaks' options
QMenu menu(intervalItem);
RideItem *rideItem = context->ride;
if (rideItem && rideItem->samples) {
QAction *actFindBest = new QAction(tr("Find Intervals..."), intervalItem);
connect(actFindBest, SIGNAL(triggered(void)), this, SLOT(addIntervals(void)));
menu.addAction(actFindBest);
// sort but only if 2 or more intervals
if (rideItem->intervals(RideFileInterval::USER).count()>1) {
QAction *actSort = new QAction(tr("Sort User Intervals"), intervalItem);
connect(actSort, SIGNAL(triggered(void)), this, SLOT(sortIntervals(void)));
menu.addAction(actSort);
}
if (rideItem->intervalsSelected().count()) menu.addSeparator();
}
QAction *actZoomOut = new QAction(tr("Zoom out"), intervalItem);
connect(actZoomOut, SIGNAL(triggered(void)), this, SLOT(zoomOut(void)));
menu.addAction(actZoomOut);
if (rideItem && rideItem->intervalsSelected().count() == 1) {
// we can zoom, rename etc if only 1 interval is selected
QAction *actZoomInt = new QAction(tr("Zoom to interval"), intervalTree);
connect(actZoomInt, SIGNAL(triggered(void)), this, SLOT(zoomIntervalSelected(void)));
menu.addAction(actZoomInt);
}
// EDIT / DELETE SINGLE USER INTERVAL
if (rideItem && rideItem->intervalsSelected(RideFileInterval::USER).count() == 1) {
menu.addSeparator();
QAction *actEditInt = new QAction(tr("Edit interval"), intervalTree);
QAction *actDeleteInt = new QAction(tr("Delete interval"), intervalTree);
connect(actEditInt, SIGNAL(triggered(void)), this, SLOT(editIntervalSelected(void)));
connect(actDeleteInt, SIGNAL(triggered(void)), this, SLOT(deleteIntervalSelected(void)));
menu.addAction(actEditInt);
menu.addAction(actDeleteInt);
}
// RENAME DELETE LOTS OF USER INTERVALS
if (rideItem && rideItem->intervalsSelected(RideFileInterval::USER).count() > 1) {
menu.addSeparator();
QAction *actRenameInt = new QAction(tr("Rename selected intervals"), intervalTree);
connect(actRenameInt, SIGNAL(triggered(void)), this, SLOT(renameIntervalsSelected(void)));
QAction *actDeleteInt = new QAction(tr("Delete selected intervals"), intervalTree);
connect(actDeleteInt, SIGNAL(triggered(void)), this, SLOT(deleteIntervalSelected(void)));
menu.addAction(actRenameInt);
menu.addAction(actDeleteInt);
}
menu.exec(mapToGlobal((QPoint(intervalItem->pos().x()+intervalItem->width()-20, intervalItem->pos().y()))));
}
void
AnalysisSidebar::showIntervalMenu(const QPoint &pos)
{
// Right-Click 'Context' menu appears on an interval item
// to manipulate the specific interval, but we need to take
// care as we should only really operate on user intervals
QTreeWidgetItem *trItem = intervalTree->itemAt(pos);
QVariant v = trItem ? trItem->data(0, Qt::UserRole) : QVariant();
IntervalItem *interval = static_cast<IntervalItem*>(v.value<void*>());
if (trItem != NULL && interval) {
bool isUser = interval->rideInterval != NULL;
activeInterval = interval;
QMenu menu(intervalTree);
// ZOOM IN AND OUT FOR ALL
QAction *actZoomOut = new QAction(tr("Zoom Out"), intervalTree);
QAction *actZoomInt = new QAction(tr("Zoom to interval"), intervalTree);
connect(actZoomOut, SIGNAL(triggered(void)), this, SLOT(zoomOut(void)));
connect(actZoomInt, SIGNAL(triggered(void)), this, SLOT(zoomInterval(void)));
menu.addAction(actZoomOut);
menu.addAction(actZoomInt);
// EDIT / DELETE USER ONLY
if (isUser) {
QAction *actEditInt = new QAction(tr("Edit interval"), intervalTree);
QAction *actDeleteInt = new QAction(tr("Delete interval"), intervalTree);
connect(actEditInt, SIGNAL(triggered(void)), this, SLOT(editIntervalSelected(void)));
connect(actDeleteInt, SIGNAL(triggered(void)), this, SLOT(deleteInterval(void)));
menu.addAction(actEditInt);
menu.addAction(actDeleteInt);
}
// BACK / FRONT NOT AVAILABLE YET
//QAction *actFrontInt = new QAction(tr("Bring to Front"), intervalTree);
//QAction *actBackInt = new QAction(tr("Send to back"), intervalTree);
//connect(actFrontInt, SIGNAL(triggered(void)), this, SLOT(frontInterval(void)));
//connect(actBackInt, SIGNAL(triggered(void)), this, SLOT(backInterval(void)));
menu.addSeparator();
menu.exec(intervalTree->mapToGlobal(pos));
}
}
/*----------------------------------------------------------------------
* Intervals
*--------------------------------------------------------------------*/
void
AnalysisSidebar::addIntervals()
{
#if 0
if (context->ride && context->ride->ride() && context->ride->ride()->dataPoints().count()) {
AddIntervalDialog *p = new AddIntervalDialog(context);
p->setWindowModality(Qt::ApplicationModal); // don't allow select other ride or it all goes wrong!
p->exec();
} else {
if (!context->ride || !context->ride->ride())
QMessageBox::critical(this, tr("Find Intervals"), tr("No activity selected"));
else
QMessageBox::critical(this, tr("Find Intervals"), tr("Current activity contains no data"));
}
#endif
}
bool
lessItem(const IntervalItem *s1, const IntervalItem *s2) {
return s1->start < s2->start;
}
void
AnalysisSidebar::sortIntervals()
{
#if 0
// sort them chronologically
QList<IntervalItem*> intervals;
// set string to first interval selected
for (int i=0; i<context->athlete->allIntervals->childCount();i++)
intervals.append((IntervalItem*)(context->athlete->allIntervals->child(i)));
// now sort them into start time order
qStableSort(intervals.begin(), intervals.end(), lessItem);
// empty context->athlete->allIntervals
context->athlete->allIntervals->takeChildren();
// and put em back in chronological sequence
foreach(IntervalItem* item, intervals) {
context->athlete->allIntervals->addChild(item);
}
// now update the ridefile
context->athlete->updateRideFileIntervals(); // will emit intervalChanged() signal
#endif
}
// rename multiple intervals
void
AnalysisSidebar::renameIntervalsSelected()
{
QTreeWidgetItem *userIntervals = trees.value(RideFileInterval::USER, NULL);
// what are we renaming?
if (context->currentRideItem() && userIntervals && userIntervals->childCount() &&
context->currentRideItem()->intervalsSelected(RideFileInterval::USER).count()) {
QString string = context->currentRideItem()->intervalsSelected(RideFileInterval::USER).first()->name;
// type in a name and we will renumber all the intervals
// in the same fashion -- esp if the last characters are
RenameIntervalDialog dialog(string, this);
dialog.setFixedWidth(320);
if (dialog.exec()) {
int number = 1;
// does it end in a number?
// if so we use that to renumber from
QRegExp ends("^(.*)([0-9]+)$");
if (ends.exactMatch(string)) {
string = ends.cap(1);
number = ends.cap(2).toInt();
} else if (!string.endsWith(" ")) string += " ";
// now go and renumber from 'number' with prefix 'string'
for(int j=0; j<userIntervals->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = userIntervals->child(j)->data(0, Qt::UserRole);
// make the IntervalItem selected flag reflect the current selection state
IntervalItem *item = static_cast<IntervalItem*>(v.value<void*>());
// is it selected and linked ?
if (item && item->selected && item->rideInterval) {
// set item and ride
item->rideInterval->name = item->name =
QString("%1%2").arg(string).arg(number++);
// update tree to reflect changes!
userIntervals->child(j)->setText(0, item->name);
}
}
// mark dirty and tell the charts it changed
const_cast<RideItem*>(context->currentRideItem())->setDirty(true);
context->notifyIntervalsChanged();
}
}
}
void
AnalysisSidebar::deleteIntervalSelected()
{
// run down the USER tree looking for the selected intervals
// and delete them. If none selected we do nothing.
QTreeWidgetItem *userIntervals = trees.value(RideFileInterval::USER, NULL);
if (userIntervals) {
QList <QTreeWidgetItem*> deleteList;
// loop through the intervals for this tree
for(int j=0; j<userIntervals->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = userIntervals->child(j)->data(0, Qt::UserRole);
// make the IntervalItem selected flag reflect the current selection state
IntervalItem *item = static_cast<IntervalItem*>(v.value<void*>());
// is it selected and linked ?
if (item->selected && item->rideInterval) {
RideItem *rideItem = context->ride;
if (rideItem->removeInterval(item) == true) {
deleteList << userIntervals->child(j);
} else {
QMessageBox::warning(this, tr("Delete Interval"), tr("Unable to delete interval"));
}
}
}
// now wipe the trees
foreach (QTreeWidgetItem*item, deleteList) {
userIntervals->removeChild(item);
delete item;
}
context->notifyIntervalsChanged();
}
}
void
AnalysisSidebar::deleteInterval()
{
// delete the activeInterval
// run down the USER tree looking for it and delete it
QTreeWidgetItem *userIntervals = trees.value(RideFileInterval::USER, NULL);
if (userIntervals) {
// loop through the intervals for this tree
for(int j=0; j<userIntervals->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = userIntervals->child(j)->data(0, Qt::UserRole);
// make the IntervalItem selected flag reflect the current selection state
IntervalItem *item = static_cast<IntervalItem*>(v.value<void*>());
// is it selected and linked ?
if (item->selected && item == activeInterval && item->rideInterval) {
RideItem *rideItem = context->ride;
if (rideItem->removeInterval(item) == true) {
delete userIntervals->takeChild(j);
} else {
QMessageBox::warning(this, tr("Delete Interval"), tr("Unable to delete interval"));
}
}
}
context->notifyIntervalsChanged();
}
}
void
AnalysisSidebar::editIntervalSelected()
{
// run down the USER tree looking for it and delete it
QTreeWidgetItem *userIntervals = trees.value(RideFileInterval::USER, NULL);
if (userIntervals) {
// loop through the intervals for this tree
for(int j=0; j<userIntervals->childCount(); j++) {
// get pointer to the IntervalItem for this item
QVariant v = userIntervals->child(j)->data(0, Qt::UserRole);
// make the IntervalItem selected flag reflect the current selection state
IntervalItem *item = static_cast<IntervalItem*>(v.value<void*>());
// is it selected and linked ?
if (item && item->selected && item->rideInterval) {
activeInterval = item;
editInterval();
// update tree to reflect changes!
userIntervals->child(j)->setText(0, activeInterval->name);
// tell the charts !
context->notifyIntervalsChanged();
return;
}
}
}
}
void
AnalysisSidebar::editInterval()
{
IntervalItem temp;
temp.name = activeInterval->name;
temp.start = activeInterval->start;
temp.stop = activeInterval->stop;
EditIntervalDialog dialog(this, temp); // pass by reference
if (dialog.exec()) {
// update the interval item
activeInterval->name = temp.name;
activeInterval->start = temp.start;
activeInterval->stop = temp.stop;
activeInterval->startKM = activeInterval->rideItem()->ride()->timeToDistance(temp.start),
activeInterval->stopKM = activeInterval->rideItem()->ride()->timeToDistance(temp.stop),
// update the ridefile interval
activeInterval->rideInterval->name = activeInterval->name;
activeInterval->rideInterval->start = activeInterval->start;
activeInterval->rideInterval->stop = activeInterval->stop;
activeInterval->rideItem()->setDirty(true);
// refresh metrics!
activeInterval->refresh();
}
}
void
AnalysisSidebar::clickZoomInterval(QTreeWidgetItem*item)
{
if (item) {
// get interval reference for this tree item
QVariant v = item->data(0, Qt::UserRole);
IntervalItem *interval = static_cast<IntervalItem*>(v.value<void*>());
if (interval) context->notifyIntervalZoom(interval);
}
}
void
AnalysisSidebar::zoomIntervalSelected()
{
RideItem *rideItem = context->ride;
if (rideItem && rideItem->intervalsSelected().count()) {
// zoom the one interval that is selected via popup menu
context->notifyIntervalZoom(rideItem->intervalsSelected().first());
}
}
void
AnalysisSidebar::zoomOut()
{
context->notifyZoomOut(); // only really used by ride plot
}
void
AnalysisSidebar::zoomInterval()
{
// zoom into this interval on allPlot
context->notifyIntervalZoom(activeInterval);
}
void
AnalysisSidebar::createRouteIntervalSelected()
{
#if 0
// create a new route for this interval
context->athlete->routes->createRouteFromInterval(activeInterval);
#endif
}
void
AnalysisSidebar::frontInterval()
{
#if 0
int oindex = activeInterval->displaySequence;
for (int i=0; i<context->athlete->allIntervals->childCount(); i++) {
IntervalItem *it = (IntervalItem *)context->athlete->allIntervals->child(i);
int ds = it->displaySequence;
if (ds > oindex)
it->setDisplaySequence(ds-1);
}
activeInterval->setDisplaySequence(context->athlete->allIntervals->childCount());
// signal!
context->notifyIntervalsChanged();
#endif
}
void
AnalysisSidebar::backInterval()
{
#if 0
int oindex = activeInterval->displaySequence;
for (int i=0; i<context->athlete->allIntervals->childCount(); i++) {
IntervalItem *it = (IntervalItem *)context->athlete->allIntervals->child(i);
int ds = it->displaySequence;
if (ds < oindex)
it->setDisplaySequence(ds+1);
}
activeInterval->setDisplaySequence(1);
// signal!
context->notifyIntervalsChanged();
#endif
}