Files
seaclient/seaset.cpp
Markus Zolliker bc33026b2c start sea server even remotely over ssh
this works on neutron instruments (account equal host)
and on linse-c
2025-06-10 16:39:49 +02:00

1915 lines
44 KiB
C++

#include "seaset.h"
#include <time.h>
#include <qstringlist.h>
#include <qvaluelist.h>
#include <qmap.h>
#include <qptrlist.h>
#include <qpixmap.h>
#include <qlabel.h>
#include <qtooltip.h>
#include <qsize.h>
#include <qwt_scale.h>
#include <qwt_plot_layout.h>
#include <qwt_dyngrid_layout.h>
#include <qvbox.h>
#include <qhbox.h>
#include <qgrid.h>
#include <qlayout.h>
#include <qscrollview.h>
#include <qevent.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include "instr_hosts.h"
#include "utils.h"
#define MAX_LABELS 20
#define MAX_FUTURE 600
#define ONE_YEAR 365*24*3600
static int prt = 2;
void setprintit(int p) {
prt = p;
}
int printit(void) {
return prt>0;
}
SeaRow::SeaRow(QWidget *parent) : QScrollView(parent) {
this->plot = 0;
state = newRow;
setHScrollBarMode(QScrollView::AlwaysOff);
setVScrollBarMode(QScrollView::AlwaysOff);
usedLegend = 0;
timeLabel = 0;
};
void SeaRow::resizeEvent(QResizeEvent *e) {
int w, rightW, legW, rowW, rowH;
SeaRow *row;
bool isfirstrow;
if (e->size() == e->oldSize()) {
return;
}
if (isHidden()) {
if (set->showPlot) {
set->saveRowHeight(this, 0);
}
return;
}
rowH = e->size().height(); // row height
if (rowH == 1) {
rowH = 0;
}
if (set->showPlot) {
set->saveRowHeight(this, rowH);
}
if (right->isHidden() && rowH > 0) {
state = shownRow;
if (set->showPlot) {
set->reread = true;
right->show();
hBox->show();
}
}
rightW = bBox->sizeHint().width();
for (row = set->rows.first(); row != 0; set->rows.findRef(row), row = set->rows.next()) {
if (row->state != hiddenRow) {
legW = row->leg->sizeHint().width();
legW += row->legScroll->width() - row->legScroll->visibleWidth();
if (legW > rightW) {
rightW = legW;
}
}
}
rowW = e->size().width();
isfirstrow = true;
for (row = set->rows.first(); row != 0; set->rows.findRef(row), row = set->rows.next()) {
if (row->state != hiddenRow) {
if (row->right->width() != rightW) {
row->right->setFixedWidth(rightW);
}
w = e->size().width() - rightW - hBox->layout()->spacing();
if (row->plot->width() != w && w > 1) {
row->plot->setFixedWidth(w);
}
if (row == this) {
if (plot->height() != rowH) {
setInnerHeights(isfirstrow, rowH);
}
if (rowW != hBox->width()) {
hBox->setFixedWidth(rowW);
}
}
isfirstrow = false;
}
}
set->setFirstRow();
}
void SeaRow::setInnerHeights(bool isfirstrow, int rowH) {
int legH, butH, axisH;
axisH = 0;
butH = bBox->height();
legH = QMAX(rowH - butH, butH);
if (isfirstrow) {
// space for time label
legH -= timeLabel->height();
axisH = 0;
} else {
// plot height has to be increased by this value to hide the time axis
axisH = plot->axis(SeaPlot::xBottom)->height();
}
plot->setFixedHeight(rowH + axisH);
hBox->setFixedHeight(rowH + axisH);
if (legH > butH) {
legScroll->setFixedHeight(legH);
}
}
void SeaRow::hideRow() {
state = hiddenRow;
set->saveRowHeight(this, 0);
right->hide();
hBox->hide();
set->hideRow();
}
QSize SeaRow::minimumSizeHint() const {
QSize size = QScrollView::minimumSizeHint();
size.setHeight(30);
return size;
}
SeaData::SeaData(const QString &name, const QString &label, const QString &plotName, int color) {
this->name = name;
this->label = label;
this->plotName = plotName;
step_m = 1;
lastx = 0;
size_m = 0;
clr = false;
dirty = true;
key = 0;
plot = 0;
row = 0;
bold = true;
decipos = 3;
size = 0;
style = color;
//printf("curve %s %s\n", name.latin1(), plotName.latin1());
}
SeaData::~SeaData() {
}
bool SeaData::isActive() {
return bold && row != 0 && row->state != hiddenRow;
}
void SeaData::init(time_t step, int siz) {
step_m = step;
size_m = 0;
lastx = 1e30;
modified = true;
if (siz > size) {
if (size > 0) {
free(x);
free(y);
}
size = siz;
x = (double *)calloc(size, sizeof(*x));
y = (double *)calloc(size, sizeof(*y));
}
}
int SeaData::addValue(double xx, QString value) {
double yy;
bool ok;
if (size_m >= size) return -1;
while (size_m > 0 && xx <= x[size_m-1] + 0.5) {
size_m--;
}
if (period >= 0) {
if (size_m > 0 && xx > lastx + 2 * period) { /* gap, additional point needed */
x[size_m] = xx - period;
y[size_m] = y[size_m-1];
lastx = 1e30;
size_m++;
if (size_m >= size) return -1;
}
}
yy = value.toDouble(&ok);
//iret = sscanf(value, "%lf", &yy);
if (ok) { // valid value
/*
if (size_m >= 2 && yy == y[size_m-1] && yy == y[size_m-2]) {
size_m--; // do not draw the middle of 3 points on a horizontal line
}
*/
x[size_m] = xx;
y[size_m] = yy;
lastx = xx;
size_m++;
modified = true;
} else if (size_m > 0) { /* invalid value -> an undefined value is marked with DATA_UNDEF */
if (size_m > 0 && y[size_m-1] == DATA_UNDEF) {
return size_m;
}
x[size_m] = x[size_m-1];
y[size_m] = DATA_UNDEF;
lastx = 1e30;
size_m++;
modified = true;
}
return size_m;
}
bool SeaData::update() {
if (plot) {
if (modified) {
modified = false;
plot->setCurveRawData(key, x, y, size_m);
return true;
}
}
return false;
}
void SeaData::showValueAt(double at) {
QString text("");
int pos, epos;
int i, l;
double value = DATA_UNDEF;
if (size_m > 0) {
if (at == DATA_UNDEF) {
at = x[size_m-1];
}
}
for (i = size_m-1; i >= 0; i--) {
if (x[i] <= at + 0.5) {
value = y[i];
// if (i > 0 && x[i-1] >= x[i]-0.5 && value == DATA_UNDEF) {
// value = y[i-1];
// }
break;
}
}
if (value != DATA_UNDEF) {
text.sprintf(" %9.5g", value);
pos = text.find('.');
epos = text.find('e');
if (epos >= 0) {
epos ++;
if (text[epos] == '+') {
text.remove(epos, 1);
} else if (text[epos] == '-') {
epos ++;
}
if (text[epos] == '0') {
text.remove(epos, 1);
}
pos = text.length();
if (pos > 10) { // remove last digit before 'e' when number too long
epos = text.find('e');
text.remove(epos - 1, 1);
}
} else {
l = text.length();
if (pos < 0) pos = l;
while (pos < decipos && pos < 2 && l < 10) {
text.prepend(' ');
pos++;
l++;
}
}
while (pos > decipos && text[1] == ' ') {
text.remove(0, 1);
text.append(' ');
pos--;
}
text.truncate(10);
}
shownValue->setText(text);
}
void SeaSet::clrDataList() {
SeaData *d;
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.first()) {
dataList.remove();
delete d;
}
}
SeaData *SeaSet::findData(const QString &name) {
SeaData *d;
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.next()) {
if (d->name.compare(name) == 0) {
return d;
}
}
return NULL;
}
void SeaSet::insertData(const QString &name, const QString &label, const QString &plotName, const int color) {
SeaData *d;
QString pName;
if (plotName == 0) {
pName = "";
} else {
pName = plotName;
}
if (name.compare("!") < 0) return; // do not make curves with blank name
d = findData(name);
if (d) {
d->clr = false;
d->label = label;
d->plotName = pName;
//d->bold = true;
d->style = color;
return;
}
dataList.append(new SeaData(name, label, pName, color));
}
void SeaSet::finishDataList() {
SeaData *d;
SeaPlot *plot;
bool dirty;
SeaRow *row, *unused;
LegendButton *legButton;
QLabel *valueLabel;
QFont monospace("Courier");
monospace.setStyleHint(QFont::TypeWriter);
QFontMetrics fm(monospace);
QGridLayout *gridLayout;
uint idx;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
row->plot->removeAll();
row->usedLegend = 0;
}
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.next()) {
if (! d->clr) {
// find plot with that name
unused = 0;
plot=0;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
plot = row->plot;
if (plot->tag.compare(d->plotName) == 0) break;
if (!unused && plot->tag.compare("") == 0) {
unused = row;
}
}
if (!row) {
if (unused) {
row = unused;
plot = row->plot;
plot->tag = d->plotName;
row->setTag(d->plotName);
} else {
row = newPlot(d->plotName);
plot = row->plot;
}
//plot->setAxisTitle(SeaPlot::yLeft, d->plotName);
}
if (row->legendList.count() > row->usedLegend) {
legButton = row->legendList.at(row->usedLegend);
valueLabel = legButton->valueLabel;
legButton = plot->putCurve(d, row->leg, legButton);
d->row = row;
gridLayout = dynamic_cast<QGridLayout *>(row->leg->layout());
legButton->label->setText(d->label);
legButton->show();
legButton->label->show();
valueLabel->show();
// printf("recycle %s %s\n", plot->tag.latin1(), d->label.latin1());
} else {
valueLabel = new QLabel(row->leg);
valueLabel->setFont(monospace);
valueLabel->setFixedWidth(fm.width(" -0.0000e-0") + 2 * valueLabel->frameWidth());
legButton = plot->putCurve(d, row->leg);
d->row = row;
legButton->valueLabel = valueLabel;
row->legendList.append(legButton);
gridLayout = dynamic_cast<QGridLayout *>(row->leg->layout());
assert(gridLayout);
gridLayout->addWidget(legButton, d->key, 0, Qt::AlignLeft + Qt::AlignBottom);
gridLayout->addWidget(legButton->label, d->key, 1, Qt::AlignLeft + Qt::AlignBottom);
gridLayout->addWidget(valueLabel, d->key, 2, Qt::AlignRight + Qt::AlignBottom);
legButton->show();
legButton->label->show();
valueLabel->show();
// printf("new %s %s\n", plot->tag.latin1(), d->label.latin1());
}
row->usedLegend++;
d->shownValue = valueLabel;
gridLayout->activate();
if (showPlot) {
row->hBox->show();
row->right->show();
} else {
row->hBox->hide();
row->right->hide();
}
}
}
dirty = true;
while (dirty) {
dirty= false;
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.next()) {
if (d->clr) {
dataList.remove(d);
delete d;
dirty = true;
}
}
}
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
for (idx = row->usedLegend; idx < row->legendList.count(); idx++) {
legButton = row->legendList.at(idx);
legButton->setCurvePen(QPen(Qt::NoPen));
legButton->valueLabel->setText("");
legButton->label->setText("");
}
if (row->usedLegend == 0) {
row->hide();
row->state = hiddenRow;
} else {
row->show();
if (row->state == hiddenRow) row->state = shownRow;
row->plot->autoColors();
}
}
adjustSizes();
readNewReplot();
}
void SeaSet::saveRowHeight(SeaRow *row, int height) {
int *rowheight;
// printf("*** save %s %d\n", row->plot->tag.latin1(), height);
rowheight = rowHeightDict.find(row->plot->tag);
if (rowheight) {
*rowheight = height;
} else {
rowHeightDict.insert(row->plot->tag, new int(height));
}
}
void SeaSet::saveBoldState(SeaData *data) {
bool *boldstate;
boldstate = boldDict.find(data->name);
if (boldstate) {
*boldstate = data->bold;
} else if (!data->bold) {
boldDict.insert(data->name, new bool(false));
}
}
int SeaSet::getRowHeight(SeaRow *row) {
int *rowheight;
rowheight = rowHeightDict.find(row->plot->tag);
if (rowheight) {
// printf("** getRow %s %d\n", row->plot->tag.latin1(), *rowheight);
return *rowheight;
} else {
// printf("** getRow %s %d\n", row->plot->tag.latin1(), -1);
return -1;
}
}
void SeaSet::getBoldState(SeaData *data) {
bool *boldstate;
boldstate = boldDict.find(data->name);
if (boldstate) {
data->bold = *boldstate;
} else {
data->bold = true;
}
}
void SeaSet::adjustSizes() {
QValueList<int> sizes;
int i, cnt, rowN, h, tot;
int rowheight;
SeaRow *row;
bool isfirstrow;
/*
if (initSizes) {
initSizes = false;
sizes = split->sizes();
h = 400;
for (i = 0; i<(int)sizes.count(); i++) {
sizes[i] = h;
h = 200;
}
split->setSizes(sizes);
goto quit;
}
*/
tot = 0; /* sum of known sizes */
cnt = 0; /* count of known sizes (first row is counted twice) */
sizes = split->sizes();
rowN = 0;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
rowheight = getRowHeight(row);
if (rowheight == 0 && row->state != newRow) {
row->state = hiddenRow;
sizes[rowN] = 0;
} else if (rowheight <= 0) {
row->state = shownRow;
sizes[rowN] = -1;
} else {
row->state = shownRow;
cnt++;
tot += rowheight;
if (rowN == 0) {
cnt++;
}
sizes[rowN] = rowheight;
}
rowN++;
}
assert(rowN == (int)sizes.count());
if (cnt == 0) {
h = 200;
} else {
h = tot / cnt;
}
for (i = 0; i < rowN; i++) {
if (sizes[i] < 0) {
if (i == 0) {
sizes[i] = h*2;
} else {
sizes[i] = h;
}
}
}
split->setSizes(sizes);
//quit:
isfirstrow = true;
for (row = rows.first(), i = 0; row != 0; i++, rows.findRef(row), row = rows.next()) {
if (row->state != hiddenRow) {
rowheight = row->height();
if (rowheight > row->bBox->height()) {
row->setInnerHeights(isfirstrow, rowheight);
saveRowHeight(row, rowheight);
}
isfirstrow = false;
} else {
saveRowHeight(row, 0);
}
}
setFirstRow();
return;
}
void SeaSet::setFirstRow() {
SeaRow *row;
for (row = rows.first(); row != 0; row = rows.next()) {
if (row->state == shownRow) {
// this is the first row
if (row != firstRow) {
if (firstRow) {
firstRow->setInnerHeights(false, firstRow->height());
firstRow->timeLabel->setText("");
}
firstRow = row;
firstRow->setInnerHeights(true, firstRow->height());
}
return;
}
}
}
SeaSet::SeaSet(QSplitter *parent, long range, const char *name)
: QObject(parent, name), rowHeightDict(31, false), boldDict(127, false)
{
hostport = "";
sc = new SicsConnection(this);
uc = new SicsConnection(this);
uc->setUser("seauser seaser\n");
gc = new SicsConnection(this);
// gc->debug = "G";
ec = new SicsConnection(this);
connect(sc, SIGNAL(reconnected()), uc, SLOT(reconnect()));
firstRow = 0;
split = parent;
labelWidth = 30;
actLegendWidth = 10;
legendWidth = 10;
if (range > ONE_YEAR) { // range is definitly not a relative date
startRange = range - 1800;
endRange = range + 1800;
} else {
startRange = - range;
endRange = QMIN(MAX_FUTURE, range / 2);
}
live = liveOff;
lastRead = time(NULL);
reread = true;
undoPlot = 0;
undoStart = 0;
labelMode = 0;
initSizes = true;
refresh = 0;
xSize = 1000;
autoDo = false;
getDo = false;
getNewData = false;
autoArg.vars = "";
base = 0;
async_mode = async_idle;
asyncTimer = new QTimer(this);
connect(asyncTimer, SIGNAL(timeout()), SLOT(asyncHandler()));
asyncTimer->start(1);
meas.start();
showPlot = false;
markerPos = DATA_UNDEF;
markerWasOn = false;
}
void SeaSet::setHost(const QString &hostport) {
static char cmd[128], psw[32];
static char *user;
char host[128], instr[32];
char *hp = (char *)hostport.latin1();
char *p;
int port;
this->hostport = hostport;
sc->setHost(hostport.latin1(), "sea");
uc->setHost(hostport.latin1(), "sea");
gc->setHost(hostport.latin1(), "graph");
ec->setHost(hostport.latin1(), "graph");
p = strchr(hp, ':');
if (p == NULL) {
InstrHost("sea", hp, instr, sizeof instr, host, sizeof host, &port);
if (strcmp(host, instr) == 0) {
snprintf(psw, sizeof psw, "SSHPASS=%sLNS", strtoupper(instr));
user = instr;
} else if (strcmp(host, "linse-c") == 0) {
snprintf(psw, sizeof psw, "SSHPASS=1%dlns1", 7);
user = "l_samenv";
} else {
return;
}
putenv(psw);
snprintf(cmd, sizeof cmd, "sshpass -e ssh -Y %s@%s sea start %s",
user, host, instr);
sc->startServer = cmd;
for (int i=0; i<30; i++) {
sc->handleBuffer(1);
if (sc->connect_state == SicsConnection::connect_waitlogin) {
break;
}
usleep(250000);
}
}
}
void SeaSet::setLive(bool on) {
time_t range, now;
if (lastRead >= 0) {
now = lastRead;
} else {
now = time(NULL);
}
if (on) {
if (live == liveOff) {
range = QMAX(MAX_FUTURE, endRange - startRange);
if (endRange < now - range && endRange > MAX_FUTURE*2) {
live = liveAuto;
} else {
live = liveOn;
if (startRange <= 0) {
rescale(startRange, range / 2);
} else {
rescale(startRange, now + range / 2);
}
}
}
} else if (live != liveOff) {
live = liveOff;
if (endRange > now) {
endRange = now;
}
rescale(startRange, endRange);
}
}
void SeaSet::restart(const char *vars) {
if (autoArg.vars == "" || autoArg.vars.compare(vars) != 0) {
autoArgDo.vars = "0 ";
autoArgDo.vars.append(vars);
autoDo = true;
}
}
void SeaSet::setTimeRange(time_t startrange, long seconds) {
undoStart = 0;
undoPlot = 0;
undoEnabled(false);
showPlot = false;
rescale(startrange, startrange + QMIN(SEA_MAX_RANGE, QMAX(60, seconds)));
}
QString SeaSet::dateLabel(long dayOffset) {
QString str;
static char *wday[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" };
static char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
time_t t = base+12*3600+dayOffset*(24*3600);
struct tm basetm = *localtime(&t);
str.sprintf("%s.%d.%s.%2.2d", wday[basetm.tm_wday], basetm.tm_mday, month[basetm.tm_mon], basetm.tm_year % 100);
return str;
}
int SeaSet::sicsCommand(QString &cmd, int graph) {
int iret, cnt;
const char *hp;
SicsConnection *conn;
if (graph) {
conn = gc;
} else {
conn = sc;
}
iret = conn->command(cmd.latin1());
cnt = 2;
while (iret <= 0) {
if (iret == 0) {
if (printit()) printf("empty response\n");
} else {
hp = hostport.latin1();
if (hp == NULL || *hp == '\0') hp = "localhost";
if (printit()) printf("connection to %s failed\n", hp);
}
cnt --;
if (cnt <= 0) return -1;
if (printit()) printf("retry\n");
iret = conn->command(cmd.latin1());
}
return iret;
}
int SeaSet::sendSicsCommand(QString &cmd, int graph) {
int iret, cnt;
const char *hp;
SicsConnection *conn;
if (graph) {
conn = gc;
} else {
conn = sc;
}
iret = conn->sendCommand(cmd.latin1());
cnt = 2;
while (iret <= 0) {
if (iret == 0) {
if (printit()) printf("empty response\n");
} else {
hp = hostport.latin1();
if (hp == NULL || *hp == '\0') hp = "localhost";
if (printit()) printf("connection to %s failed\n", hp);
}
cnt --;
if (cnt <= 0) return -1;
if (printit()) printf("retry\n");
iret = conn->sendCommand(cmd.latin1());
}
return iret;
}
static const char *yZoomPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" XX ",
" XXXX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" ",
" ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XXXX ",
" XX ",
" "
};
static const char *yDownPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" ",
" ",
" ",
" ",
" ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XXXX ",
" XX ",
" ",
" ",
" ",
" ",
};
static const char *yUpPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" ",
" ",
" ",
" XX ",
" XXXX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" ",
" ",
" ",
" ",
" ",
" ",
};
static const char *yMaxPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
"XXXXX XX XXXXX",
" XXXX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" ",
" ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XXXX ",
"XXXXX XX XXXXX",
" "
};
static const char *closePix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XXXX ",
" XX ",
" XXXX ",
" XX XX ",
" XX XX ",
" XX XX ",
" XX XX ",
" ",
" ",
" "
};
static const char *logPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" ",
" XX ",
" XX XXX XXX ",
" XX XX XX XX XX ",
" XX XX XX XXXX ",
" XX XXX XX ",
" XXX ",
" X X ",
" X XXXX ",
" X X X X ",
" X X X X ",
" X X X X ",
" ",
" ",
" "
};
static const char *linPix[] = {
"16 16 2 1",
" c None",
"X c #000000",
" ",
" ",
" X ",
" X XX XX ",
" X X X X X ",
" X X X XXX ",
" X XX X ",
" XX ",
" XX XX ",
" XX XXXXX ",
" XX XX XX XX ",
" XX XX XX XX ",
" XX XX XX XX ",
" ",
" ",
" "
};
void SeaRow::linLog() {
if (plot->toggleLinLog()) {
lBut->setPixmap(pmLog);
} else {
lBut->setPixmap(pmLin);
}
}
void SeaRow::setTag(QString tag) {
int pos;
pos = tag.findRev('_');
if (pos > 0) {
tag.truncate(pos);
}
tag.prepend(" [");
tag.append("]");
tagLabel->setText(tag);
}
SeaRow *SeaSet::newPlot(const char *name) {
QPushButton *zBut, *oBut, *uBut, *dBut, *cBut;
SeaPlot *plot;
QString txt;
int rowN = rows.count();
QPixmap pmOut(yZoomPix);
QPixmap pmMax(yMaxPix);
QPixmap pmUp(yUpPix);
QPixmap pmDown(yDownPix);
QPixmap pmClose(closePix);
SeaRow *row;
QVBox *vbox;
QBoxLayout *rowLayout, *bLayout, *vLayout;
QScrollView *scroll;
QSizePolicy narrow(QSizePolicy::Maximum, QSizePolicy::Maximum, 1, 0);
QSizePolicy wide(QSizePolicy::Expanding, QSizePolicy::Expanding, 5, 0);
//QSizePolicy high(QSizePolicy::Expanding, QSizePolicy::Expanding, 5, 10);
int w, h;
row = new SeaRow(split);
row->setFrameStyle(0);
row->hBox = new QHBox(row, "rowHbox");
rowLayout = dynamic_cast<QBoxLayout *>(row->hBox->layout());
assert(rowLayout);
row->hBox->setSpacing(6);
rowLayout->setAutoAdd(false);
row->set = this;
row->pmLog = (const char **)logPix;
row->pmLin = linPix;
split->setResizeMode(row, QSplitter::Stretch);
txt.sprintf("plot%d", rowN);
plot = new SeaPlot(row->hBox, this, txt);
plot->tag = name;
plot->setSizePolicy(wide);
row->plot = plot;
rowLayout->addWidget(plot);
vbox = new QVBox(row->hBox, "vbox");
vLayout = dynamic_cast<QBoxLayout *>(vbox->layout());
vLayout->setAutoAdd(false);
vbox->setSizePolicy(narrow);
vLayout->setAlignment(Qt::AlignTop);
rowLayout->addWidget(vbox, 0, Qt::AlignTop);
row->right = vbox;
row->bBox = new QHBox(vbox, "bBox");
bLayout = dynamic_cast<QBoxLayout *>(row->bBox->layout());
assert(bLayout);
bLayout->setAutoAdd(false);
vLayout->addWidget(row->bBox);
scroll = new QScrollView(vbox, "scroll", Qt::WNoAutoErase);
scroll->setHScrollBarMode(QScrollView::AlwaysOff);
scroll->setFrameStyle(0);
scroll->setSizePolicy(wide);
row->legScroll = scroll;
vLayout->addWidget(scroll);
row->timeLabel = new QLabel(row->right);
row->timeLabel->setText("");
vLayout->addWidget(row->timeLabel);
row->timeLabel->setFont(row->plot->axisFont(QwtPlot::xBottom));
vLayout->addStretch(1);
row->leg = new QGrid(3,scroll->viewport(),"legendGrid1");
SetEraseColor(scroll->viewport(), scroll->topLevelWidget());
row->leg->layout()->setAutoAdd(false);
scroll->addChild(row->leg);
uBut = new QPushButton(row->bBox);
uBut->setPixmap(pmUp);
bLayout->addWidget(uBut);
dBut = new QPushButton(row->bBox);
dBut->setPixmap(pmDown);
bLayout->addWidget(dBut);
oBut = new QPushButton(row->bBox);
oBut->setPixmap(pmOut);
bLayout->addWidget(oBut);
zBut = new QPushButton(row->bBox);
zBut->setPixmap(pmMax);
bLayout->addWidget(zBut);
row->lBut = new QPushButton(row->bBox);
row->lBut->setPixmap(row->pmLin);
bLayout->addWidget(row->lBut);
row->tagLabel = new QLabel(row->bBox);
row->setTag(name);
bLayout->addWidget(row->tagLabel);
bLayout->addStretch(1);
cBut = new QPushButton(row->bBox);
cBut->setPixmap(pmClose);
bLayout->addWidget(cBut);
w = QMAX(22, uBut->sizeHint().width() - 6);
h = QMAX(22, uBut->sizeHint().height() - 6);
if (w > h) { // Mac like
h += 6;
w -= 6;
}
uBut->setFixedSize(w, h);
QToolTip::add(uBut, "scroll up");
oBut->setFixedSize(w, h);
QToolTip::add(oBut, "zoom out vertically");
dBut->setFixedSize(w, h);
QToolTip::add(dBut, "scroll down");
zBut->setFixedSize(w, h);
QToolTip::add(zBut, "auto y-range");
cBut->setFixedSize(w, h);
QToolTip::add(cBut, "hide this row");
row->lBut->setFixedSize(w, h);
QToolTip::add(row->lBut, "log / lin scale");
zBut->setEnabled(false);
plot->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
rows.append(row);
connect(oBut, SIGNAL(clicked()),
plot, SLOT(zoomOut()));
connect(uBut, SIGNAL(clicked()),
plot, SLOT(shiftUp()));
connect(dBut, SIGNAL(clicked()),
plot, SLOT(shiftDown()));
connect(plot, SIGNAL(setAutoOff(bool)),
zBut, SLOT(setEnabled(bool)));
connect(zBut, SIGNAL(clicked()),
plot, SLOT(zoomMax()));
connect(cBut, SIGNAL(clicked()),
row, SLOT(hideRow()));
connect(row->lBut, SIGNAL(clicked()),
row, SLOT(linLog()));
row->leg->show();
return row;
}
void SeaSet::autoCurves(const char *vars) {
if (vars == NULL) {
autoArgDo.vars = "";
} else {
autoArgDo.vars = vars;
}
autoDo = true;
// printf("*** autoCurves\n");
}
bool SeaSet::autoCurvesP1() {
QString cmd;
int iret;
if (autoArg.vars != "" && autoArg.vars != "now") {
return false;
}
if (autoArg.vars == "now") {
cmd.sprintf("graph 0 0 text vars");
autoArg.vars = "";
} else {
cmd.sprintf("graph %ld %ld text vars", startRange, endRange);
//printf("DEBUG graph $ld %ld test vars\n", startRange, endRange);
}
iret = gc->sendCommand(cmd);
if (iret < 0) {
if (printit()) {
printf("failed autoCurves\n");
exit(0);
}
}
// printf("*** end autoCurvesP1\n");
return true;
}
void SeaSet::autoCurvesP2() {
QString line;
int iret;
time_t now;
QString item, pName, label;
QStringList list, itemS;
QStringList::iterator its;
SeaData *d;
long style;
if (autoArg.vars == "") {
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
/* get returned absolute time */
now = line.toLong();
//sscanf(line, "%ld", &now);
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
if (!line.startsWith("*vars")) {
if (printit()) printf("missing *vars\n");
goto badresponse;
}
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
iret = line.find(' ');
if (iret >= 0) {
autoArg.vars = line.mid(iret).stripWhiteSpace();
}
base = 0; // force calcBase
} else {
time(&now);
line = autoArg.vars;
}
if (base == 0) {
if (startRange < 0) {
startRange += now;
}
calcBase(startRange);
if (endRange <= ONE_YEAR) {
endRange += now;
}
}
clrDataList();
while (!line.startsWith("*")) {
iret = line.find(' ');
if (iret >= 0) {
list = QStringList::split(" ", line.mid(iret));
for (its = list.end(); its != list.begin(); ) {
its--;
item = *its;
if (item.contains('|')) {
itemS = QStringList::split("|", item, TRUE);
} else {
itemS = QStringList::split("/", item, TRUE);
}
style = -1;
if (itemS.count() > 1) {
pName = itemS[1];
item = itemS[0];
if (itemS.count() > 2) {
if (itemS.count() > 3) {
style = Convert2ColorIndex(itemS[3]);
if (style < -1) {
style = -1; // set bad colors to auto
}
}
label = itemS[2];
} else {
label = item;
}
} else {
pName = "";
label = item;
}
if (pName == 0) {
pName = "";
}
d = findData(item);
if (d) {
dataList.remove(d);
d->plotName = pName;
d->clr = false;
//d->bold = true;
d->label = label;
d->style = style;
} else {
d = new SeaData(item, label, pName, style);
getBoldState(d);
}
dataList.prepend(d);
}
}
if (autoArg.vars != "") break;
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
};
//printf("DEBUG end graph\n");
if (prt) {
printf("reading curves,\n");
}
// printf("*** start finishDataList\n");
finishDataList();
// printf("*** end autoCurvesP2\n");
return; // 0
badresponse:
if (printit()) {
if (iret > 0) {
printf("syntax error in response %s\n", line.latin1());
} else if (iret == 0) {
printf("early end of response\n");
} else if (iret == -2) {
printf("timeout on sics connection\n");
} else {
printf("error on response\n");
}
}
return; // -1
}
void SeaSet::getCurves(bool all, time_t from, time_t to) {
getArgDo.all = all;
getArgDo.append = false;
getArgDo.from = from;
getArgDo.to = to;
getDo = true;
}
int SeaSet::getCurvesP1() {
QString cmd;
int iret, cnt;
SeaData *d;
// printf("*** do getCurvesP1\n");
if (getArg.from == getArg.to) {
getArg.from = startRange;
getArg.to = endRange;
}
cnt = 0;
if (getArg.append) {
curveStep = 1;
} else {
curveStep = long(getArg.to - getArg.from) / xSize + 1;
}
cmd.sprintf("graph %ld %ld np %d", getArg.from, getArg.to, xSize);
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.next()) {
if ((d->dirty || getArg.all) && d->row->state != hiddenRow) {
cmd.append(" ");
cmd.append(d->name);
cnt++;
}
}
if (cnt == 0) {
// printf("*** no data\n");
return 0;
}
//printf("*** %s\n", cmd.latin1());
iret = gc->sendCommand(cmd);
return 1;
}
void SeaSet::getCurvesP2() {
QString line;
int iret, siz;
time_t t, dt, tmax;
SeaData *d;
int iperiod;
bool ok;
// printf("*** do getCurvesP2\n");
assert(async_mode == async_get);
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
//printf("> %s\n", line);
/* get returned absolute time */
lastRead = line.toLong();
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
//printf("> %s\n", line.latin1());
if (!line.startsWith("*")) goto badresponse;
tmax = 0;
for (d = dataList.first(); d != 0; dataList.findRef(d), d = dataList.next()) {
if ((d->dirty || getArg.all) && d->row->state != hiddenRow) {
d->dirty = false;
d->period = 1;
iret = line.find(' ');
if (iret >= 0) {
iperiod = line.find("period", iret);
if (iperiod >= iret) {
d->period = line.mid(iperiod + 7).toLong();
}
//if (period) {
// sscanf(period, "period %lf", &d->period);
//}
line = line.left(iret);
}
if (d->name == line.mid(1)) {
t = 0;
if (!getArg.append) {
d->init(curveStep, xSize*2);
}
siz = 0;
while (1) {
iret = gc->getLine(line);
if (iret <= 0) goto badresponse;
//printf("> %s\n", line);
if (line.startsWith("*")) break;
/* decode line to x[n] / y[n] */
dt = line.section(' ', 0, 0).toLong(&ok);
//cnt = sscanf(line, "%ld %n", &dt, &pos);
//if (cnt < 1) {
if (!ok) {
if (printit()) printf("bad timestep %s\n", line.latin1());
goto badresponse;
}
t += dt;
siz = d->addValue(double(t - base), line.section(' ', 1));
}
d->showValueAt(markerPos);
if (siz < 0) {
if (printit()) printf("overflow\n");
if (t < endRange) endRange = t;
// } else if (siz > d->size - 5) {
// if (t < endRange) endRange = t;
}
tmax = QMAX(tmax, t);
if (d->update() && getArg.append) {
refresh = 1; //ref
}
}
//printf("DEBUG end line %s\n", line.latin1());
}
}
lastTime = tmax - base;
if (markerPos == DATA_UNDEF) {
setTimeLabel(lastTime);
}
if (!getArg.append) {
compressed = line.mid(1,1) != '0';
//printf("*** compressed %d\n", compressed);
}
iret = gc->getLine(line);
//printf("> %s\n", line.latin1());
if (iret != 0) {
if (printit()) printf("missing end\n");
}
// printf("*** end getCurves\n");
if (!showPlot) {
showAll();
showPlot = true;
}
QTimer::singleShot(0, this, SLOT(replot()));
return; // 1
badresponse:
if (printit()) {
if (iret > 0) {
printf("syntax error in response %s\n", line.latin1());
} else if (iret == 0) {
printf("early end of response\n");
} else if (iret == -2) {
printf("timeout on sics connection\n");
} else {
printf("error on response\n");
}
}
return; // -1
}
void SeaSet::asyncHandler() {
int msec;
if ((msec=meas.restart()) > 100) {
// printf("%d msec\n", msec);
}
if (async_mode == async_idle) {
if (autoDo) {
autoArg = autoArgDo;
autoDo = false;
if (autoCurvesP1()) {
async_mode = async_auto;
tmot.start();
} else {
autoCurvesP2();
}
} else if (getDo) {
getArg = getArgDo;
if (getCurvesP1() == 0) {
async_mode = async_idle;
} else {
async_mode = async_get;
tmot.start();
}
getDo = false;
if (getArg.all) {
reread = false;
}
} else if (getNewData) {
getArg.append = true;
getArg.all = true;
getArg.from = lastRead;
getArg.to = 0;
getNewData = false;
// printf("*** getCurvesP1\n");
if (getCurvesP1() == 0) {
async_mode = async_idle;
} else {
async_mode = async_get;
tmot.start();
}
} else if (reread) {
getCurves(true, startRange, endRange);
reread = false;
}
}
if (async_mode == async_auto) {
if (tmot.elapsed() > 10000) {
if (printit()) printf("autoCurves timeout\n");
gc->reconnect();
tmot.start();
async_mode = async_idle;
return;
}
if (gc->handleBuffer(0) <= 0) return;
if (gc->getResponse() < 0) {
if (printit()) printf("autoCurves2 timeout\n");
gc->reconnect();
async_mode = async_idle;
return;
}
// printf("*** start autoCurvesP2\n");
autoCurvesP2();
async_mode = async_idle;
}
if (async_mode == async_get) {
if (tmot.elapsed() > 10000) {
if (printit()) printf("getCurves timeout\n");
tmot.start();
gc->reconnect();
async_mode = async_idle;
return;
}
if (gc->handleBuffer(0) <= 0) return;
if (gc->getResponse() < 0) {
// printf("*** again getCurvesP1\n");
if (printit()) printf("getCurves2 timeout\n");
gc->reconnect();
async_mode = async_idle;
return;
}
// printf("*** start getCurvesP2\n");
getCurvesP2();
async_mode = async_idle;
}
}
void SeaSet::calcLeftLabelWidth() {
SeaRow *row;
labelMode = 2; // set to max mode
labelWidth = 0;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
if (row->state != hiddenRow) {
// force recursive calls for all labels on all plots
row->plot->axis(QwtPlot::yLeft)->minimumSizeHint();
}
}
if (labelWidth == 0) {
labelWidth = 30;
}
labelMode = 1; // set to cached mode
}
int SeaSet::leftLabelWidth(int in) {
if (prt == 1) {
setprintit(0);
}
if (labelMode == 2) { // max (calc) mode
if (in > labelWidth) labelWidth = in;
// printf("*** in %d\n", in);
return in;
}
if (labelMode == 1) { // cached mode
// printf("*** lwc %d\n", labelWidth + 4);
return labelWidth + 4;
}
calcLeftLabelWidth();
labelMode = 1; // set to cached mode
// printf("*** lw %d\n", labelWidth);
return labelWidth;
}
void SeaSet::showAll() {
SeaRow *row;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
if (getRowHeight(row) == 0) {
saveRowHeight(row, -1);
}
row->state = shownRow;
row->hBox->show();
row->right->show();
if (showPlot) row->plot->showAgain();
}
adjustSizes();
readNewReplot();
}
void SeaSet::shiftLeft() {
long w;
saveRange();
w = (endRange - startRange) * 3 / 4;
rescale(startRange - w, endRange - w);
}
void SeaSet::shiftRight() {
long w;
saveRange();
w = (endRange - startRange) * 3 / 4;
rescale(startRange + w, endRange + w);
}
void SeaSet::zoomOutX() {
long w;
time_t s, t;
saveRange();
w = endRange - startRange;
if (w > SEA_MAX_RANGE/2) w = SEA_MAX_RANGE/2;
if (startRange <= 0) {
rescaleRange(w * 2);
} else {
if (liveTime < base) {
t = time(NULL);
} else {
t = liveTime;
}
if (endRange + w/2 > t + MAX_FUTURE) {
s = t + MAX_FUTURE - w * 2;
} else {
s = startRange - w / 2;
}
rescale(s, s + w * 2);
}
}
void SeaSet::saveRange() {
undoEnabled(true);
undoPlot = 0;
undoStart = startRange;
undoEnd = endRange;
}
void SeaSet::saveRange(SeaPlot *plot, QwtDoubleRect &old, bool yAuto) {
saveRange();
undoPlot = plot;
undoAuto = yAuto;
undoRect = old;
}
void SeaSet::undoZoom() {
if (undoPlot) {
undoPlot->undoZoom(undoRect);
undoPlot->autoZoom(undoAuto);
}
if (undoStart != 0) {
rescale(undoStart, undoEnd);
}
undoStart = 0;
undoPlot = 0;
undoEnabled(false);
}
void SeaSet::liveUpdate() {
if (live == liveOn) {
if (endRange < ONE_YEAR) {
endRange += lastRead;
}
if (startRange < ONE_YEAR) {
startRange += lastRead;
}
if (lastRead >= endRange) {
endRange += QMIN(MAX_FUTURE, (endRange - startRange)/2);
getCurves(true, startRange, endRange);
} else {
getNewData = true;
}
}
}
void SeaSet::replot() {
if (refresh <= 0) return;
replotAnyway();
refresh--;
if (refresh > 0) {
QTimer::singleShot(0, this, SLOT(replot()));
}
}
void SeaSet::replotAnyway() {
float xrange;
float tick;
SeaPlot *plot;
SeaRow *row;
static long steps[]={30,60,
2*60,5*60,10*60,30*60,
3600,2*3600,3*3600,6*3600,24*3600,
0};
int i;
double x1, x2;
SeaData *d;
int lw;
int maxpos;
double y;
char txt[16], *pos;
SeaCurve *crv;
x1 = startRange - base;
x2 = endRange - base;
xrange = x2 - x1;
for (i=0; steps[i] > 0; i++) {
tick = steps[i];
if (xrange < tick*MAX_LABELS) break;
}
legendWidth = 50;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
plot = row->plot;
i = plot->replotRange(x1, x2, tick);
QwtPlotCurveIterator itc = plot->curveIterator();
maxpos = 0;
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = (dynamic_cast<SeaCurve *>(c));
if (crv) {
d = crv->data;
if (d->size_m > 0) {
y = d->y[d->size_m-1];
snprintf(txt, sizeof(txt), "%1.5g", y);
pos = strchr(txt, '.');
if (pos == 0) {
pos = txt + strlen(txt);
}
maxpos = QMAX(maxpos, (pos - txt));
}
}
}
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = (dynamic_cast<SeaCurve *>(c));
if (crv) {
d = crv->data;
d->decipos = maxpos + 1;
}
}
}
lw = labelWidth;
calcLeftLabelWidth();
if (labelWidth != lw) {
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
if (row->state != hiddenRow) {
((QWidget *)row->plot->axis(QwtPlot::yLeft))->updateGeometry();
}
}
}
//layout->activate();
// printf("*** end replot\n");
if (prt) prt = 1;
}
void SeaSet::hideRow() {
SeaRow *row;
int rowN = 0, firstRow = -1, tot = 0;
QValueList<int> sizes;
sizes = split->sizes();
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
if (row->hBox->isHidden()) {
tot += sizes[rowN];
sizes[rowN] = 0;
} else if (firstRow < 0) {
firstRow = rowN;
}
rowN ++;
}
if (firstRow < 0) {
firstRow = 0;
}
sizes[firstRow] += tot;
split->setSizes(sizes);
}
void SeaSet::calcBase(time_t from) {
struct tm basetm;
// calculate new base
// printf("*** calc base %ld\n", from);
if (from <= 0) {
from += time(NULL);
}
basetm = *localtime(&from);
basetm.tm_hour = 0;
basetm.tm_min = 0;
basetm.tm_sec = 0;
base = mktime(&basetm);
}
void SeaSet::rescale(time_t from, time_t to) {
if (to < lastRead && live == liveOn) {
live = liveAuto;
}
if (from < startRange || to > endRange
|| (compressed && (from != startRange || to != endRange))) {
reread = true;
}
startRange = from;
endRange = to;
if ((to > lastRead || (to >= 0 && to < SEA_MAX_RANGE)) && live == liveAuto) {
live = liveOn;
}
replotAnyway();
if (from < base || from > base + 30*3600) {
calcBase(from);
reread = true;
}
if (reread) {
getCurves(true, from, to);
} else {
getCurves(false, startRange, endRange);
}
refresh = 2;
reread = false;
}
void SeaSet::rescaleRange(time_t range) {
time_t t1, t2, now;
undoStart = 0;
undoPlot = 0;
undoEnabled(false);
now = time(NULL);
t1 = - QMIN(SEA_MAX_RANGE, QMAX(60, range));
if (live != liveOff) {
t2 = QMIN(MAX_FUTURE, range / 2);
} else {
t2 = 1;
}
startRange = t1 + now;
endRange = t2 + now;
replotAnyway();
calcBase(now - range - 4000);
getCurves(true, t1, t2);
startRange = t1 + lastRead;
endRange = t2 + lastRead;
if (live == liveAuto) live = liveOn;
refresh = 2;
}
// called on zoom:
void SeaSet::rescale(double x1, double x2) {
rescale(base + (long)(x1), base + (long)(x2));
}
void SeaSet::readNewReplot() {
// if (live == liveAuto) live = liveOn;
getCurves(false, startRange, endRange);
refresh = 2;
}
void SeaSet::runningMarkerOff(bool beforeEvent) {
// before and after a MousePress, MouseDblClick or KeyPress event
// the marker is set off
// markerWasOn is set accordingly
// if clicked inside a plot, SeaPlotZommer will then set the marker with a MouseReleaseEvent
if (markerPos == DATA_UNDEF) {
markerWasOn = false;
} else {
markerWasOn = beforeEvent;
if (!markerFix) {
setMarker(DATA_UNDEF);
}
}
}
void SeaSet::setTimeLabel(double x) {
QString tstr;
QTime time;
if (firstRow != 0) {
if (x == DATA_UNDEF) {
firstRow->timeLabel->setText("");
} else {
time.setHMS(0,0,0);
tstr = dateLabel(floor(x/(24*3600)));
tstr += " " + time.addSecs(floor(x)).toString("hh:mm");
firstRow->timeLabel->setText(tstr);
}
}
}
void SeaSet::setMarker(double x) {
SeaRow *row;
SeaPlot *plot;
if (x != DATA_UNDEF) {
if (x < startRange - base) {
x = startRange - base;
} else if (x > endRange - base) {
x = endRange - base;
}
}
markerPos = x;
for (row = rows.first(); row != 0; rows.findRef(row), row = rows.next()) {
plot = row->plot;
plot->setMarker(x);
plot->replot();
}
if (x == DATA_UNDEF) {
setTimeLabel(lastTime);
} else {
setTimeLabel(x);
}
}
void SeaSet::setMarker(time_t t) {
if (t == 0) {
setMarker(DATA_UNDEF);
} else {
setMarker((double)(t - base));
}
}
void SeaSet::gotoTime(double at, bool silent) {
gotoTime(startRange, base + (long)at, endRange, silent);
}