Files
GoldenCheetah/src/Charts/RChart.cpp
Mark Liversedge 457b3e70d0 R compare date range signals
.. refresh when compare state changes
2016-05-02 11:23:23 +01:00

409 lines
12 KiB
C++

/*
* Copyright (c) 2016 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 "R.h"
#include "RTool.h"
#include "RChart.h"
#include "RSyntax.h"
#include "Colors.h"
#include "TabView.h"
// unique identifier for each chart
static int id=0;
RConsole::RConsole(Context *context, RChart *parent)
: QTextEdit(parent)
, context(context), localEchoEnabled(true), parent(parent)
{
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
setFrameStyle(QFrame::NoFrame);
document()->setMaximumBlockCount(10240);
putData(GColor(CPLOTMARKER), QString(tr("R Console (%1)").arg(rtool->version)));
putData(GCColor::invertColor(GColor(CPLOTBACKGROUND)), "\n> ");
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
connect(context, SIGNAL(rMessage(QString)), this, SLOT(rMessage(QString)));
// history position
hpos=0;
// set unique runtimeid name
chartid = QString("gc%1").arg(id++);
configChanged(0);
}
void
RConsole::configChanged(qint32)
{
QFont courier("Courier", QFont().pointSize());
setFont(courier);
QPalette p = palette();
p.setColor(QPalette::Base, GColor(CPLOTBACKGROUND));
p.setColor(QPalette::Text, GCColor::invertColor(GColor(CPLOTBACKGROUND)));
setPalette(p);
setStyleSheet(TabView::ourStyleSheet());
// set default colors
if (rtool) rtool->configChanged();
}
void
RConsole::rMessage(QString x)
{
putData(GColor(CPLOTMARKER), x);
}
void RConsole::putData(QColor color, QString string)
{
// change color...
setTextColor(color);
putData(string);
}
void RConsole::putData(QString data)
{
insertPlainText(QString(data));
QScrollBar *bar = verticalScrollBar();
bar->setValue(bar->maximum());
}
void RConsole::setLocalEchoEnabled(bool set)
{
localEchoEnabled = set;
}
void RConsole::keyPressEvent(QKeyEvent *e)
{
switch (e->key()) {
case Qt::Key_Up:
if (hpos) {
hpos--;
setCurrentLine(history[hpos]);
}
break;
case Qt::Key_Down:
if (hpos < history.count()-1) {
hpos++;
setCurrentLine(history[hpos]);
}
break;
// you can always go to the right
case Qt::Key_Right:
QTextEdit::keyPressEvent(e);
break;
// you can only delete or move left from past first character
case Qt::Key_Left:
case Qt::Key_Backspace:
if (textCursor().position() - textCursor().block().position() > 2) QTextEdit::keyPressEvent(e);
break;
case Qt::Key_C:
{
Qt::KeyboardModifiers kmod = static_cast<QInputEvent*>(e)->modifiers();
bool ctrl = (kmod & Qt::ControlModifier) != 0;
if (ctrl) {
// ^C needs to clear program and go to next line
rtool->R->program.clear();
QTextCursor move = textCursor();
move.movePosition(QTextCursor::End);
setTextCursor(move);
// new prompt
putData("\n");
putData(GCColor::invertColor(GColor(CPLOTBACKGROUND)), "> ");
} else {
// normal C just do the usual
if (localEchoEnabled) QTextEdit::keyPressEvent(e);
}
}
break;
case Qt::Key_Enter:
case Qt::Key_Return:
{
QTextCursor move = textCursor();
move.movePosition(QTextCursor::End);
setTextCursor(move);
QString line = currentLine();
if (line.length() > 1) line = line.mid(2, line.length()-2);
else line = "";
putData("\n");
if (line != "") {
history << line;
hpos = history.count();
// lets run it
//qDebug()<<"RUN:" << line;
// set the context for the call - used by all the
// R functions to access the athlete data/model etc
rtool->context = context;
rtool->canvas = parent->canvas;
try {
// replace $$ with chart identifier (to avoid shared data)
line = line.replace("$$", chartid);
// we always get an array of strings
// so only print it out if its actually been set
SEXP ret = NULL;
int rc = rtool->R->parseEval(line, ret);
// if this isn't an assignment then print the result
// bit hacky, there must be a better way!
if(rc == 0 && ret != NULL && !Rf_isNull(ret) && !line.contains("<-") && !line.contains("print"))
Rf_PrintValue(ret);
putData(GColor(CPLOTMARKER), rtool->messages.join(""));
rtool->messages.clear();
} catch(std::exception& ex) {
putData(QColor(Qt::red), QString("%1\n").arg(QString(ex.what())));
putData(QColor(Qt::red), rtool->messages.join(""));
rtool->messages.clear();
} catch(...) {
putData(QColor(Qt::red), "error: general exception.\n");
putData(QColor(Qt::red), rtool->messages.join(""));
rtool->messages.clear();
}
// clear context
rtool->context = NULL;
rtool->canvas = NULL;
}
// prompt ">" for new command and ">>" for a continuation line
if (rtool->R->program.count()==0)
putData(GCColor::invertColor(GColor(CPLOTBACKGROUND)), "> ");
else
putData(GCColor::invertColor(GColor(CPLOTBACKGROUND)), ">>");
}
break;
default:
if (localEchoEnabled) QTextEdit::keyPressEvent(e);
emit getData(e->text().toLocal8Bit());
}
// if we edit or anything reset the history position
if (e->key()!= Qt::Key_Up && e->key()!=Qt::Key_Down) hpos = history.count();
}
void
RConsole::setCurrentLine(QString p)
{
QTextCursor select = textCursor();
select.select(QTextCursor::LineUnderCursor);
select.removeSelectedText();
putData(GCColor::invertColor(GColor(CPLOTBACKGROUND)), "> ");
putData(p);
}
QString
RConsole::currentLine()
{
return textCursor().block().text().trimmed();
}
void RConsole::mousePressEvent(QMouseEvent *e)
{
Q_UNUSED(e)
setFocus();
}
void RConsole::mouseDoubleClickEvent(QMouseEvent *e)
{
Q_UNUSED(e)
}
void RConsole::contextMenuEvent(QContextMenuEvent *e)
{
Q_UNUSED(e)
}
RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), context(context), ridesummary(ridesummary)
{
setControls(NULL);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(2,0,2,2);
setChartLayout(mainLayout);
// if we failed to startup embedded R properly
// then disable the RConsole altogether.
if (rtool) {
leftsplitter = new QSplitter(Qt::Vertical, this);
leftsplitter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
leftsplitter->setHandleWidth(1);
// LHS
script = new QTextEdit(this);
script->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
script->setFrameStyle(QFrame::NoFrame);
QFont courier("Courier", QFont().pointSize());
script->setFont(courier);
QPalette p = palette();
p.setColor(QPalette::Base, GColor(CPLOTBACKGROUND));
p.setColor(QPalette::Text, GCColor::invertColor(GColor(CPLOTBACKGROUND)));
script->setPalette(p);
script->setStyleSheet(TabView::ourStyleSheet());
// syntax highlighter
setScript("## R script will run on selection.\n"
"##\n"
"## GC.activity(compare=FALSE)\n"
"## GC.metrics(all=FALSE,\n"
"## compare=FALSE)\n"
"##\n"
"## Get the current ride or metrics\n"
"##\n");
leftsplitter->addWidget(script);
console = new RConsole(context, this);
leftsplitter->addWidget(console);
splitter = new QSplitter(Qt::Horizontal, this);
splitter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->setHandleWidth(1);
mainLayout->addWidget(splitter);
splitter->addWidget(leftsplitter);
canvas = new RCanvas(context, this);
canvas->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->addWidget(canvas);
if (ridesummary) {
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(runScript()));
// refresh when comparing
connect(context, SIGNAL(compareIntervalsStateChanged(bool)), this, SLOT(runScript()));
connect(context, SIGNAL(compareIntervalsChanged()), this, SLOT(runScript()));
} else {
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(runScript()));
// refresh when comparing
connect(context, SIGNAL(compareDateRangesStateChanged(bool)), this, SLOT(runScript()));
connect(context, SIGNAL(compareDateRangesChanged()), this, SLOT(runScript()));
}
} else {
// not starting
script = NULL;
console = NULL;
canvas = NULL;
}
}
QString
RChart::getScript() const
{
if (rtool && script) return script->toPlainText();
else return text;
}
void
RChart::setScript(QString string)
{
if (rtool && script) {
script->setText(string);
new RSyntax(script->document());
}
text = string;
}
void
RChart::runScript()
{
if (script->toPlainText() != "") {
// run it !!
rtool->context = context;
rtool->canvas = canvas;
QString line = script->toPlainText();
try {
// replace $$ with chart identifier (to avoid shared data)
line = line.replace("$$", console->chartid);
// run it
rtool->R->parseEval(line);
// output on console
if (rtool->messages.count()) {
console->putData("\n");
console->putData(GColor(CPLOTMARKER), rtool->messages.join(""));
rtool->messages.clear();
}
} catch(std::exception& ex) {
console->putData(QColor(Qt::red), QString("\n%1\n").arg(QString(ex.what())));
console->putData(QColor(Qt::red), rtool->messages.join(""));
rtool->messages.clear();
// clear
canvas->newPage();
} catch(...) {
console->putData(QColor(Qt::red), "\nerror: general exception.\n");
console->putData(QColor(Qt::red), rtool->messages.join(""));
rtool->messages.clear();
// clear
canvas->newPage();
}
// if the program expects more we clear it, otherwise
// weird things can happen!
rtool->R->program.clear();
// clear context
rtool->context = NULL;
rtool->canvas = NULL;
}
}