diff --git a/Makefile_rhel7 b/Makefile_rhel7 new file mode 100644 index 0000000..0ec1bfc --- /dev/null +++ b/Makefile_rhel7 @@ -0,0 +1,386 @@ +############################################################################# +# Makefile for building: rhel7/SeaClient +# Generated by qmake (1.07a) (Qt 3.3.4) on: Thu Apr 6 11:05:39 2017 +# Project: SeaClient.pro +# Template: app +# Command: $(QMAKE) -o Makefile_rhel7 SeaClient.pro +############################################################################# + +####### Compiler, tools and options + +CC = gcc +CXX = g++ +LEX = flex +YACC = yacc +CFLAGS = -pipe -Wall -W -g -D_REENTRANT -DQT_THREAD_SUPPORT -DQT_SHARED +CXXFLAGS = -pipe -Wall -W -Wno-non-virtual-dtor -Wno-unused-parameter -Wno-write-strings -Wno-unused-but-set-variable -Wno-type-limits -g -D_REENTRANT -DQT_THREAD_SUPPORT -DQT_SHARED +LEXFLAGS = +YACCFLAGS= -d +INCPATH = -I/afs/psi.ch/user/z/zolliker/public/qt64/mkspecs/default -I. -I../qtx64/qt-x11-free-3.3.4/include -I../qtx64/qwt-4.2.0/include -I$(QTDIR)/include -I.ui/ -I.moc/ +LINK = g++ +LFLAGS = -Wl,-rpath,$(QTDIR)/lib +LIBS = $(SUBLIBS) -L$(QTDIR)/lib -L/usr/X11R6/lib -L/afs/psi.ch/user/z/zolliker/public/qwt64/lib -lqwt -lqt-mt -lXext -lX11 -lm -lpthread +AR = ar cqs +RANLIB = +MOC = $(QTDIR)/bin/moc +UIC = $(QTDIR)/bin/uic +QMAKE = qmake +TAR = tar -cf +GZIP = gzip -9f +COPY = cp -f +COPY_FILE= $(COPY) +COPY_DIR = $(COPY) -r +INSTALL_FILE= $(COPY_FILE) +INSTALL_DIR = $(COPY_DIR) +DEL_FILE = rm -f +SYMLINK = ln -sf +DEL_DIR = rmdir +MOVE = mv -f +CHK_DIR_EXISTS= test -d +MKDIR = mkdir -p + +####### Output directory + +OBJECTS_DIR = rhel7/ + +####### Files + +HEADERS = main.h \ + seaset.h \ + seaplot.h \ + sicsconn.h \ + tab.h \ + settings.h \ + command.h \ + graph.h \ + utils.h \ + export.h \ + report.h \ + device.h +SOURCES = main.cpp \ + seaset.cpp \ + seaplot.cpp \ + instr_hosts.cpp \ + sicsconn.cpp \ + tab.cpp \ + settings.cpp \ + command.cpp \ + graph.cpp \ + utils.cpp \ + export.cpp \ + report.cpp \ + device.cpp +OBJECTS = rhel7/main.o \ + rhel7/seaset.o \ + rhel7/seaplot.o \ + rhel7/instr_hosts.o \ + rhel7/sicsconn.o \ + rhel7/tab.o \ + rhel7/settings.o \ + rhel7/command.o \ + rhel7/graph.o \ + rhel7/utils.o \ + rhel7/export.o \ + rhel7/report.o \ + rhel7/device.o +FORMS = +UICDECLS = +UICIMPLS = +SRCMOC = .moc/moc_main.cpp \ + .moc/moc_seaset.cpp \ + .moc/moc_seaplot.cpp \ + .moc/moc_sicsconn.cpp \ + .moc/moc_tab.cpp \ + .moc/moc_settings.cpp \ + .moc/moc_command.cpp \ + .moc/moc_graph.cpp \ + .moc/moc_utils.cpp \ + .moc/moc_export.cpp \ + .moc/moc_report.cpp \ + .moc/moc_device.cpp +OBJMOC = rhel7/moc_main.o \ + rhel7/moc_seaset.o \ + rhel7/moc_seaplot.o \ + rhel7/moc_sicsconn.o \ + rhel7/moc_tab.o \ + rhel7/moc_settings.o \ + rhel7/moc_command.o \ + rhel7/moc_graph.o \ + rhel7/moc_utils.o \ + rhel7/moc_export.o \ + rhel7/moc_report.o \ + rhel7/moc_device.o +DIST = SeaClient.pro +QMAKE_TARGET = SeaClient +DESTDIR = rhel7/ +TARGET = rhel7/SeaClient + +first: all +####### Implicit rules + +.SUFFIXES: .c .o .cpp .cc .cxx .C + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cc.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cxx.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.C.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.c.o: + $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $< + +####### Build rules + +all: Makefile_rhel7 $(TARGET) + +$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC) + test -d rhel7/ || mkdir -p rhel7/ + $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(OBJCOMP) $(LIBS) + +mocables: $(SRCMOC) +uicables: $(UICDECLS) $(UICIMPLS) + +$(MOC): + ( cd $(QTDIR)/src/moc && $(MAKE) ) + +Makefile_rhel7: SeaClient.pro /afs/psi.ch/user/z/zolliker/public/qt64/mkspecs/default/qmake.conf ../qt64/lib/libqt-mt.prl + $(QMAKE) -o Makefile_rhel7 SeaClient.pro +qmake: + @$(QMAKE) -o Makefile_rhel7 SeaClient.pro + +dist: + @mkdir -p rhel7/SeaClient && $(COPY_FILE) --parents $(SOURCES) $(HEADERS) $(FORMS) $(DIST) rhel7/SeaClient/ && ( cd `dirname rhel7/SeaClient` && $(TAR) SeaClient.tar SeaClient && $(GZIP) SeaClient.tar ) && $(MOVE) `dirname rhel7/SeaClient`/SeaClient.tar.gz . && $(DEL_FILE) -r rhel7/SeaClient + +mocclean: + -$(DEL_FILE) $(OBJMOC) + -$(DEL_FILE) $(SRCMOC) + +uiclean: + +yaccclean: +lexclean: +clean: mocclean + -$(DEL_FILE) $(OBJECTS) + -$(DEL_FILE) *~ core *.core + + +####### Sub-libraries + +distclean: clean + -$(DEL_FILE) rhel7/$(TARGET) $(TARGET) + + +FORCE: + +####### Compile + +rhel7/main.o: main.cpp main.h \ + instr_hosts.h \ + tab.h \ + graph.h \ + settings.h \ + device.h \ + export.h \ + report.h \ + seaset.h \ + sicsconn.h \ + seaplot.h \ + command.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/main.o main.cpp + +rhel7/seaset.o: seaset.cpp seaset.h \ + utils.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/seaset.o seaset.cpp + +rhel7/seaplot.o: seaplot.cpp seaplot.h \ + utils.h \ + seaset.h \ + sicsconn.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/seaplot.o seaplot.cpp + +rhel7/instr_hosts.o: instr_hosts.cpp instr_hosts.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/instr_hosts.o instr_hosts.cpp + +rhel7/sicsconn.o: sicsconn.cpp sicsconn.h \ + instr_hosts.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/sicsconn.o sicsconn.cpp + +rhel7/tab.o: tab.cpp tab.h \ + graph.h \ + settings.h \ + device.h \ + export.h \ + report.h \ + seaset.h \ + sicsconn.h \ + seaplot.h \ + command.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/tab.o tab.cpp + +rhel7/settings.o: settings.cpp settings.h \ + utils.h \ + seaset.h \ + device.h \ + sicsconn.h \ + seaplot.h \ + command.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/settings.o settings.cpp + +rhel7/command.o: command.cpp command.h \ + utils.h \ + sicsconn.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/command.o command.cpp + +rhel7/graph.o: graph.cpp graph.h \ + seaset.h \ + utils.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/graph.o graph.cpp + +rhel7/utils.o: utils.cpp utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/utils.o utils.cpp + +rhel7/export.o: export.cpp export.h \ + seaset.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/export.o export.cpp + +rhel7/report.o: report.cpp report.h \ + sicsconn.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/report.o report.cpp + +rhel7/device.o: device.cpp device.h \ + utils.h \ + seaset.h \ + command.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/device.o device.cpp + +rhel7/moc_main.o: .moc/moc_main.cpp main.h tab.h \ + graph.h \ + settings.h \ + device.h \ + export.h \ + report.h \ + seaset.h \ + sicsconn.h \ + seaplot.h \ + command.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_main.o .moc/moc_main.cpp + +rhel7/moc_seaset.o: .moc/moc_seaset.cpp seaset.h sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_seaset.o .moc/moc_seaset.cpp + +rhel7/moc_seaplot.o: .moc/moc_seaplot.cpp seaplot.h seaset.h \ + sicsconn.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_seaplot.o .moc/moc_seaplot.cpp + +rhel7/moc_sicsconn.o: .moc/moc_sicsconn.cpp sicsconn.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_sicsconn.o .moc/moc_sicsconn.cpp + +rhel7/moc_tab.o: .moc/moc_tab.cpp tab.h graph.h \ + settings.h \ + device.h \ + export.h \ + report.h \ + seaset.h \ + sicsconn.h \ + seaplot.h \ + command.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_tab.o .moc/moc_tab.cpp + +rhel7/moc_settings.o: .moc/moc_settings.cpp settings.h seaset.h \ + device.h \ + sicsconn.h \ + seaplot.h \ + command.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_settings.o .moc/moc_settings.cpp + +rhel7/moc_command.o: .moc/moc_command.cpp command.h sicsconn.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_command.o .moc/moc_command.cpp + +rhel7/moc_graph.o: .moc/moc_graph.cpp graph.h seaset.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_graph.o .moc/moc_graph.cpp + +rhel7/moc_utils.o: .moc/moc_utils.cpp utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_utils.o .moc/moc_utils.cpp + +rhel7/moc_export.o: .moc/moc_export.cpp export.h seaset.h \ + sicsconn.h \ + seaplot.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_export.o .moc/moc_export.cpp + +rhel7/moc_report.o: .moc/moc_report.cpp report.h sicsconn.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_report.o .moc/moc_report.cpp + +rhel7/moc_device.o: .moc/moc_device.cpp device.h seaset.h \ + command.h \ + sicsconn.h \ + seaplot.h \ + utils.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o rhel7/moc_device.o .moc/moc_device.cpp + +.moc/moc_main.cpp: $(MOC) main.h + $(MOC) main.h -o .moc/moc_main.cpp + +.moc/moc_seaset.cpp: $(MOC) seaset.h + $(MOC) seaset.h -o .moc/moc_seaset.cpp + +.moc/moc_seaplot.cpp: $(MOC) seaplot.h + $(MOC) seaplot.h -o .moc/moc_seaplot.cpp + +.moc/moc_sicsconn.cpp: $(MOC) sicsconn.h + $(MOC) sicsconn.h -o .moc/moc_sicsconn.cpp + +.moc/moc_tab.cpp: $(MOC) tab.h + $(MOC) tab.h -o .moc/moc_tab.cpp + +.moc/moc_settings.cpp: $(MOC) settings.h + $(MOC) settings.h -o .moc/moc_settings.cpp + +.moc/moc_command.cpp: $(MOC) command.h + $(MOC) command.h -o .moc/moc_command.cpp + +.moc/moc_graph.cpp: $(MOC) graph.h + $(MOC) graph.h -o .moc/moc_graph.cpp + +.moc/moc_utils.cpp: $(MOC) utils.h + $(MOC) utils.h -o .moc/moc_utils.cpp + +.moc/moc_export.cpp: $(MOC) export.h + $(MOC) export.h -o .moc/moc_export.cpp + +.moc/moc_report.cpp: $(MOC) report.h + $(MOC) report.h -o .moc/moc_report.cpp + +.moc/moc_device.cpp: $(MOC) device.h + $(MOC) device.h -o .moc/moc_device.cpp + +####### Install + +install: + +uninstall: + diff --git a/SeaClient.pro b/SeaClient.pro new file mode 100644 index 0000000..24f417a --- /dev/null +++ b/SeaClient.pro @@ -0,0 +1,47 @@ +TEMPLATE = app +LANGUAGE = C++ + +CONFIG += qt-mt warn_on thread debug static + +QMAKE_CXXFLAGS_WARN_ON += -Wno-non-virtual-dtor -Wno-unused-parameter -Wno-write-strings \ + -Wno-unused-but-set-variable -Wno-type-limits + +LIBS += -L$$(QWTDIR)/lib -lqwt + +INCLUDEPATH += $$(QTDIR)/include +INCLUDEPATH += $$(QWTDIR)/include + +HEADERS += main.h \ + seaset.h \ + seaplot.h \ + sicsconn.h \ + tab.h \ + settings.h \ + command.h \ + graph.h \ + utils.h \ + export.h \ + report.h \ + device.h + +SOURCES += main.cpp \ + seaset.cpp \ + seaplot.cpp \ + instr_hosts.cpp \ + sicsconn.cpp \ + tab.cpp \ + settings.cpp \ + command.cpp \ + graph.cpp \ + utils.cpp \ + export.cpp \ + report.cpp \ + device.cpp + +unix { + UI_DIR = .ui + MOC_DIR = .moc + OBJECTS_DIR = $$(linuxsys) + DESTDIR = $$(linuxsys) +} + diff --git a/command.cpp b/command.cpp new file mode 100644 index 0000000..4f078fa --- /dev/null +++ b/command.cpp @@ -0,0 +1,351 @@ +#include "command.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +class CmdLine : public QLineEdit { +public: + CmdLine(QWidget *parent, const char *name) : QLineEdit(parent, name), history(""), edit("") { + p = history.end(); + atEnd = true; + } + void keyPressEvent(QKeyEvent *e) { + int key = e->key(); + if (key == Qt::Key_Up) { + if (p == history.end()) { + if (text().isEmpty()) { + if (!edit.isEmpty()) { + setText(edit); + return; + } + } else { + edit = text(); + } + } + if (p != history.begin()) { + --p; + setText(*p); + } + } else if (key == Qt::Key_Down) { + if (p != history.end()) { + p++; + } + if (p == history.end()) { + if (edit.compare(text()) == 0) { + setText(""); + } else { + setText(edit); + } + } else { + setText(*p); + } + } else if (key == Qt::Key_Return || key == Qt::Key_Enter) { + edit=""; + QLineEdit::keyPressEvent(e); + } else { + QLineEdit::keyPressEvent(e); + if (p == history.end()) { + edit = text(); + } + } + } + void addToHistory(const QString &txt) { + p = history.find(txt); + if (p != history.end()) { + history.remove(p); + } + history.append(txt); + p = history.end(); + while (history.count() > 256) { + history.remove(history.begin()); + } + } +private: + QStringList history; + QStringList::Iterator p; + QString edit; + bool atEnd; +}; + +Command::Command(QWidget * parent, const char *name, SicsConnection *initConn) + : QWidget(parent, name, 0) +{ + //QGridLayout *grid; + QVBoxLayout *vbox; + QFont monospace("Courier"); + + getHistory = true; + //grid = new QGridLayout(this, 2, 2, 4, 4, "grid"); + vbox = new QVBoxLayout(this, 4, 4, "cmdBox"); + + logStartupState = 1; + + log = new MyTextEdit(this, "log"); + log->setReadOnly(TRUE); + //add_to_log(" "); + log->setBuffered(true); + + vbox->addWidget(new QLabel("Input/Output History:", this)); + vbox->addWidget(log); + ownCommand = false; + quiet = false; + + vbox->addWidget(new QLabel("Command Input:", this)); + monospace.setStyleHint(QFont::TypeWriter); + cmd = new CmdLine(this, "cmd"); + cmd->setFont(monospace); + vbox->addWidget(cmd,3,0); + clearWState(WState_Polished); + conn=0; + cmd->setFocusPolicy(StrongFocus); + setConn(initConn); + scrollH = 0; + QTimer *timer = new QTimer(this, "repeat"); + connect(timer, SIGNAL(timeout()), this, SLOT(handler())); + timer->start(10); + checktmo = false; + + /* + dirtyLog = 0; + + QTimer *logTimer = new QTimer(this); + connect(logTimer, SIGNAL(timeout()), SLOT(showLog())); + logTimer->start(50); + + connect(log, SIGNAL(contentsMoving(int, int)), this, SLOT(scrolling(int, int))); + */ +} + +void Command::showIt(bool yes) { + setShown(yes); +} + +void Command::showText() { + printf("%s\n", log->text().latin1()); +} + +int str_beg(const char *text, const char *name) { + return (strncmp(text, name, strlen(name)) == 0); +} + +void Command::add_to_log(QString line, Style style) { +/* + QFont f = font(); + static bool x=true; + if (x) { + printf("family %s size %d %d s %d\n", f.family().latin1(), f.pointSize(), f.pixelSize(), f.styleStrategy()); + x = false; + } +*/ + if (ownCommand) { + log->appendHtml(""); + } + switch (style) { + case style_command: log->appendHtml(""); break; + case style_error: log->appendHtml(""); break; + case style_normal: break; + } + log->appendText(line); + if (style > 0) { + log->appendHtml(""); + } + if (ownCommand) { + log->appendHtml(""); + } + log->appendText("\n"); +} + +/* +void Command::showLog(void) { + if (dirtyLog) { + dirtyLog = 0; + log->setText(logText); + log->scrollToBottom(); + } +} + +void Command::scrolling(int x, int y) { + scrolled = true; +} +*/ + +void Command::handleSics(const char * text, bool *done) { + if (str_beg(text, "TRANSACTIONFINISHED")) { + if (logStartupState) { + log->setBuffered(false); + } + ownCommand = false; + if (done) *done = false; + return; + } + if (str_beg(text, "Deleting connection") || + str_beg(text, "Accepted connection") || + (str_beg(text, "User ") && strstr(text, " privilege") != 0) || + str_beg(text, "Change of Authorisation") || + str_beg(text, "fulltransact config ") || + str_beg(text, "UserRights =") || + str_beg(text, "fulltransact status") || + (str_beg(text, "OK") && strlen(text) <= 3) || + str_beg(text, "fulltransact commandlog tail") || + str_beg(text, "Login OK")) { + if (done) *done = false; + return; + } + + activate(); + if (str_beg(text, "fulltransact") || str_beg(text, "transact") || str_beg(text, "TRANSACTIONSTART")) { + if (str_beg(text, "TRANSACTIONSTART config listen")) { + if (done) *done = false; + return; + } + if (text[0] == 'f') { + text += 13; + } else if (text[0] == 'T') { + text += 17; + ownCommand = true; + } else { + text += 8; + } +// if (!blankLine) { +// add_to_log(" "); +// } + if (strcmp(text, " ") > 0) { + add_to_log(" "); + if (timeStamp != "") { + add_to_log(timeStamp); + timeStamp = ""; + } + add_to_log(text, style_command); +// printf("*** BB {%s}\n", text); + if (getHistory) { + ((CmdLine *)cmd)->addToHistory(text); + } + } + } else if (strstr(text, "transAct") == text || strstr(text, "fulltransAct") == text) { // hide these commands + // quiet = true; + return; + } else if (strstr(text, "ERROR:")) { + add_to_log(text, style_error); +// printf("*** RR %s\n", text); + } else if (quiet) { + quiet = false; /* swallow only one line. this is for nicos read -> frappy read -> sea spam*/ + } else { + if (strcmp(text, " ") > 0) { + if (str_beg(text, "===")) { + timeStamp = text; + } else { + if (timeStamp != "") { + add_to_log(timeStamp); + timeStamp = ""; + } + add_to_log(text); + } +// printf("*** N %s %s %s\n", text); + } + } + if (done) *done = true; + return; +} + +void Command::handler() { + int iret; + + iret = conn->handleBuffer(0); + if (iret == -2) { // connect_error + // printf("*** reconnect\n"); + conn->reconnect(); + if (getCommandLog == 0) { + getCommandLog = 1; /* force config listen to be done on the new connection */ + } + } else if (iret > 0) { // success + checktmo = false; + if (getCommandLog == 2) { + conn->sendCommand("commandlog tail 999"); + getCommandLog = 1; + } else if (getCommandLog == 1) { + conn->getResponse(); + getCommandLog = 0; + conn->sendCommand("config listen 1"); + // printf("*** config listen\n"); + } else if (!cmdbuffer.isEmpty()) { + if (sendCmd1(cmdbuffer.first().latin1())) { + tmot.start(); + checktmo = true; + cmdbuffer.pop_front(); + } + } + } else if (checktmo && tmot.elapsed() > 10000) { + tmot.start(); + // printf("*** timeout\n"); + handleSics("ERROR: timeout -> reconnect\n"); + conn->reconnect(); + getCommandLog = 1; /* force config listen to be done on the new connection */ + } + + if (height() < scrollH) { + printf("%d\n", scrollH); + log->scrollToBottom(); + } + scrollH = height(); + if (conn) { + conn->handleMessages(0); + } +} + +void Command::sendCmd(const char *command) { + QString qs(command); + cmdbuffer.append(qs); + ((CmdLine *)cmd)->addToHistory(command); +} + +int Command::sendCmd1(const char *command) { + int iret; + QString line; + QString cCmd; + + iret = conn->sendCommand(command); + if (iret < 0) { + handleSics("ERROR: connection failed\n"); + return 0; + } +// if (!blankLine) { +// add_to_log(" "); +// } +// add_to_log(command, style_command); + cmd->setText(""); + log->scrollToBottom(); + + while ((iret=conn->getLine(line)) > 0) { + handleSics(line.latin1()); + } +// add_to_log(" "); +// blankLine = true; + log->scrollToBottom(); + return 1; +} + +void Command::handleCmd() { + if (getHistory) { + getHistory = false; + } + sendCmd(cmd->text().latin1()); +} + +void Command::setConn(SicsConnection *conn) { + assert(conn); + if (this->conn != conn) { + this->conn = conn; + connect(conn, SIGNAL(handle(const char *, bool *)), this, SLOT(handleSics(const char *, bool *))); + connect(cmd, SIGNAL(returnPressed()), this, SLOT(handleCmd())); + getCommandLog = 2; + } + getHistory = true; +} diff --git a/command.h b/command.h new file mode 100644 index 0000000..28e2327 --- /dev/null +++ b/command.h @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include "sicsconn.h" +#include "utils.h" + +class QCheckBox; + +class Command : public QWidget +{ + Q_OBJECT +public: + Command(QWidget *parent, const char *name, SicsConnection *conn); + void setConn(SicsConnection *conn); + void showText(); + QLineEdit *cmd; + MyTextEdit *log; + enum Style {style_normal, style_command, style_error}; + +signals: + void activate(); + +public slots: + void handleSics(const char *text, bool *done = NULL); + void sendCmd(const char *cmd); + void handleCmd(); + void handler(); + void showIt(bool show); +// void showLog(); +// void scrolling(int x, int y); + +private: + void add_to_log(QString text, Style style = style_normal); + int sendCmd1(const char *cmd); + + SicsConnection *conn; + int scrollH; + bool getHistory; + bool ownCommand; + bool quiet; /* for suppressing 'spam' when reading values from nicos/frappy */ + bool blankLine; + QString timeStamp; + int getCommandLog; + QStringList cmdbuffer; + QTime tmot; + bool checktmo; + int logStartupState; +// bool dirtyLog; +}; diff --git a/device.cpp b/device.cpp new file mode 100644 index 0000000..33482e3 --- /dev/null +++ b/device.cpp @@ -0,0 +1,1458 @@ +#include "device.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +static const char *rPix[] = { + "10 10 2 1", + " c None", + "X c #0000CC", + "XX ", + "XXXX ", + "XXXXXX ", + "XXXXXXXX ", + "XXXXXXXXXX", + "XXXXXXXXXX", + "XXXXXXXX ", + "XXXXXX ", + "XXXX ", + "XX ", +}; + +static const char *dPix[] = { + "10 10 2 1", + " c None", + "X c #0000CC", + "XXXXXXXXXX", + "XXXXXXXXXX", + " XXXXXXXX ", + " XXXXXXXX ", + " XXXXXX ", + " XXXXXX ", + " XXXX ", + " XXXX ", + " XX ", + " XX ", +}; + +/* +static const char *grPix[] = { + "16 12 4 1", + " c None", + "/ c #CC0000", + "- c #0000CC", + "X c #000000", + "XXXXXXXXXXXXXXXX", + "X X", + "X X", + "X - X", + "X ---- X", + "X -- ----- X", + "X ---- ----X", + "X----- -X", + "X-- X", + "X X", + "X X", + "XXXXXXXXXXXXXXXX", +}; + +static const char *gnPix[] = { + "16 12 4 1", + " c None", + "/ c #CC0000", + "- c #0000CC", + "X c #000000", + "X//XXXXXXXXXX//X", + "X // // X", + "X // // X", + "X // - // X", + "X //--// X", + "X ////---- X", + "X ----// ----X", + "X-----//// -X", + "X-- // // X", + "X // // X", + "X // // X", + "XX//XXXXXXXX//XX", +}; +*/ + +static int lineEditFrameWidth = 0; + +LineInput::LineInput(QWidget *parent, QString &labelTxt, const char *var) : QHBox(parent, var), value("") { + QBoxLayout *l = dynamic_cast(layout()); + + l->setAutoAdd(false); + + label = new QLabel(labelTxt, this); + label->setAlignment(Qt::AlignRight); + l->addWidget(label); + + le = new QLineEdit(this, var); + connect(le, SIGNAL(returnPressed()), this, SLOT(handleReturn())); + connect(le, SIGNAL(lostFocus()), this, SLOT(focusLost())); + QFont monospace("Courier"); + monospace.setStyleHint(QFont::TypeWriter); + le->setFont(monospace); + l->addWidget(le); + + l->setSpacing(6); +} + +void LineInput::setLength(int l) { + QFontMetrics fm = le->fontMetrics(); + if (l==0) { + l = 9; + } + if (l <= 9) { + le->setAlignment(Qt::AlignRight); + } else { + le->setAlignment(Qt::AlignLeft); + } + lineEditFrameWidth = le->frameWidth(); + le->setFixedWidth(fm.width("0") * l + 2 * lineEditFrameWidth + 4); +} + +int LineInput::stdWidth() { + QBoxLayout *l = dynamic_cast(layout()); + setLength(0); + l->activate(); + return label->width() + l->spacing() + 2 * l->margin() + le->width(); +} + +void LineInput::handleReturn() { + QString c(name()); + if (le->text().isEmpty()) { + c.append(" \"\""); + } else { + c.append(" "); + c.append(le->text()); + } + sendCmd(c.latin1()); + value = le->text(); + changed(); +} + +void LineInput::focusLost() { + if (value.compare(le->text()) != 0) { + handleReturn(); + } +} + +void LineInput::setValue(const QString &value) { + if ((this->value.compare(le->text()) == 0) && this->value.compare(value) != 0) { + le->setText(value); + this->value = value; + } +} + +void LineInput::setEraseColor(const QColor & color) { + QHBox::setEraseColor(color); + label->setEraseColor(color); + label->setEraseColor(color); +} + +RdOnly::RdOnly(QWidget *parent, QString &labelTxt, QString &val, const char *var) : QHBox(parent, var) { + QBoxLayout *l = dynamic_cast(layout()); + + l->setAutoAdd(false); + label = new QLabel(labelTxt, this); + value = new QLabel(val, this); + QFont monospace("Courier"); + monospace.setStyleHint(QFont::TypeWriter); + value->setFont(monospace); + label->setAlignment(Qt::AlignRight); + value->setAlignment(Qt::AlignRight); + l->addWidget(label); + l->addWidget(value); + l->setSpacing(6); + len = 9; +} + +void RdOnly::setLength(int l) { + QFontMetrics fm = value->fontMetrics(); + if (l==0) { + l = 9; + } + len = l; + if (l <= 9) { + value->setAlignment(Qt::AlignRight); + } else { + value->setAlignment(Qt::AlignLeft); + } + value->setFixedWidth(fm.width("0") * l + 2 * lineEditFrameWidth + 4); +} + +void RdOnly::setValue(const QString &val) { + bool ok; + double f; + int p, i; + QString v, ve; + + if (val.length() <= len) { + // length is ok + value->setText(val); + return; + } + f = val.stripWhiteSpace().toDouble(&ok); + if (!ok) { + // it's not a number + value->setText(val); + return; + } + // try to reduce the length by reducing the precision + for (p = 6; p > 0; p--) { + v.setNum(f, 'g', p); + i = v.find('e'); + if (i >= 0) { + i++; + if (v[i] == '-') { + i++; + } else if (v[i] == '+') { + // skip + in exponent + v.remove(i,1); + } + if (v[i] == '0') { + // skip leading 0 in exponent + v.remove(i,1); + } + } + if (v.length() <= len) { + value->setText(v); + return; + } + } + value->setText(v); +} + +CheckBox::CheckBox(const QString &label, QWidget *parent, const char *var) + : QCheckBox(label, parent, var) { + connect(this, SIGNAL(clicked()), this, SLOT(handleReturn())); +} + +void CheckBox::handleReturn() { + QString c(name()); + if (isOn()) { + c.append(" 1"); + } else { + c.append(" 0"); + } + sendCmd(c.latin1()); + changed(); +} + +void CheckBox::setValue(const QString &value) { + if (value[0] == '0' || value[0] <= ' ') { + if (isOn()) { + setChecked(false); + } + } else { + if (!isOn()) { + setChecked(true); + } + } +} + +ClickButton::ClickButton(QWidget *parent, const char *var) + : QPushButton(parent, var) { + dx = 0; +} + +void ClickButton::handleReturn() { + setFocus(); + sendCmd(name()); + changed(); +} + +void ClickButton::setLabel(const QString &label) { + QFontMetrics fm = fontMetrics(); + if (dx == 0) { + setText("BUTTON"); + dx = (width() - fm.width("BUTTON")) / 3; + } + setText(label); + setMaximumWidth(fm.width(text()) + dx); +} + +RadioButton::RadioButton(const QString &label, QWidget *parent, + const QString &cmdArg, const char *value) : QRadioButton(label, parent, value) +{ + cmd = cmdArg; + connect(this, SIGNAL(clicked()), this , SLOT(handleClick())); +} + +void RadioButton::handleClick() { + QString c(cmd); + c.append(" "); + c.append(name()); + sendCmd(c.latin1()); + clearOthers(name()); + changed(); + setFocus(); +} + +void RadioButton::setValue(const QString &value) { + setChecked(value.compare(name()) == 0); +} + +void ColorMenu::init() { + const QPixmap *pixmap; + int i; + + setEditable(true); + listBox()->setRowMode(QListBox::FitToHeight); + insertItem(QString("auto"), 0); + for (i=1; i < 16; i++) { + pixmap = thisPixmap(i); + if (pixmap == NULL) { + i = -1; + break; + } + insertItem(*pixmap, i); + } + connect(this, SIGNAL(activated(int)), + this, SLOT(handleAct(int))); +} + +ColorMenu::ColorMenu(QString &color, QWidget *parent) + : QComboBox(parent, "settings") +{ + init(); + setCurrentItem(0); + setCurrentText(color); +} + +ColorMenu::ColorMenu(QWidget *parent, const char *var) + : QComboBox(parent, var) +{ + init(); + nochange = true; + setCurrentItem(0); + setCurrentText(""); + nochange = false; + setLength(0); + value = ""; + connect(this, SIGNAL(textChanged(const QString &)), + this, SLOT(handleChange(const QString &))); +} + +void ColorMenu::handleAct(int index) { + QString text; + +// printf("*** handleAct %d\n", index); + if (index > 0 && index < 16) { + Convert2ColorName(index, text); + nochange = true; + setCurrentItem(0); + nochange = false; + value = text; + setCurrentText(text); + } +// printf("*** handleAct end\n"); +} + +void ColorMenu::setValue(const QString &text) { + if (value.compare(currentText()) == 0 && value.compare(text) != 0) { +// printf("*** setValue %s -> %s\n", value.latin1(), text.latin1()); + nochange = true; + setCurrentItem(0); + value = text; + setCurrentText(text); + nochange = false; + } +} + +void ColorMenu::handleChange(const QString &text) { + QString c(name()); + int index; + +// printf("*** nochange %d empty %d\n", nochange, (text == "")); + if (nochange || text == "") return; + index = Convert2ColorIndex(text); + if (index < -1) return; // not a valid text + value = text; +// printf("*** handleChange %s\n", text.latin1()); + c.append(" "); + c.append(text); + sendCmd(c.latin1()); + changed(); +} + +int ColorMenu::getColorIndex() { + int index = Convert2ColorIndex(currentText()); + if (index < -1) { + index = -1; + } + return index; +} + +void ColorMenu::setLength(int min, int max) { + QFontMetrics fm = fontMetrics(); + if (min == 0) { + min = 10; + } + if (max < min) { + max = min; + } + setMinimumWidth(fm.width("0") * min + 8); + setMaximumWidth(fm.width("0") * max + 8); +} + +Menu::Menu(const QString &label, QWidget *parent, const char *var) + : QComboBox(parent, var) +{ + connect(this, SIGNAL(activated(const QString &)), + this, SLOT(handleAct(const QString &))); +} + +void Menu::handleAct(const QString &value) { + QString c(name()); + + c.append(" "); + c.append(value); + sendCmd(c.latin1()); + changed(); +} + +void Menu::setValue(const QString &value) { + setCurrentText(value); +} + +/* +GraphButton::GraphButton(QWidget *parent, const char *name) + : QPushButton(parent, name) +{ + setFlat(true); + setPixmap(QPixmap(grPix)); + connect(this, SIGNAL(clicked()), this, SLOT(handleClick())); + setFixedSize(24,20); + hide(); +} + +void GraphButton::handleClick() { + selectItem(name()); + changed(); +} +*/ + +bool PathStartsWith(QString &path, const char *name) { + if (path.startsWith(name)) { + int l = strlen(name); +// if (path.length(name) == l || path[l] == '/') return true; + if (path[l] == 0 || path[l] == '/') return true; + } + return false; +} + +void Group::init() { + indent = 0; + isSelectMenu = false; + colDiv = 1; +} + +Group::Group(Device *p, const char *name, bool selectMenu) + : QVBox(p->viewport(), name) + , items(101,false) +{ + //title = new QLabel("Title", this); + //boxLayout->addWidget(title); + parentGroup = 0; + device = p; + init(); + arrow = 0; + shown=true; + title = 0; + isSelectMenu = selectMenu; + autoclose = false; +} + +Group::Group(Group *p, const QString &tit, const char *name) + : QVBox(p->device->viewport(), name) + , items(101,false) +{ + QFont bold; + QBoxLayout *lay; + + parentGroup = p; + device = p->device; + init(); + isSelectMenu = p->isSelectMenu; + if (tit.isEmpty()) { + arrow = 0; + shown = true; + } else { + title = new QLabel(tit, this); + lay = dynamic_cast(layout()); + lay->setAutoAdd(false); + lay->add(title); + lay->addStretch(1); + setFrameStyle(QFrame::MenuBarPanel); + + indent = p->indent + 20; + arrow = new QPushButton(this, "arrow"); + arrow->setFlat(true); + SetEraseColor(arrow, topLevelWidget(), true); + arrow->setPixmap(QPixmap(rPix)); + arrow->setFixedSize(16, 16); + bold = font(); + bold.setWeight(75); + setFont(bold); + connect(arrow, SIGNAL(clicked()), this, SLOT(toggle())); + shown=false; + } +} + +void Group::showIt(bool yes) { + shown = yes; + if (arrow) { // && arrow->isShown()) { + if (shown) { + arrow->setPixmap(QPixmap(dPix)); + } else { + arrow->setPixmap(QPixmap(rPix)); + } + } +} + +void Group::newline() { + device->x = indent; + device->y = device->nextY; +} + +void Group::add(Item *item, QWidget *w) { + int frac, offs, hei, wid; + QFont font; + + if (w == 0) w = item->w; + if (wStyle != item->wStyle) { + switch (item->wStyle) { + case 0: + item->normalFont = w->font(); + case 'W': // remove warning style + case 'A': // remove warning style +// case 'G': // remove graph settings style + w->setEraseColor(eraseColor()); + break; + case 'H': // remove header style + w->setFont(item->normalFont); + } + switch (wStyle) { + case 'W': // warning: yellow background + w->setEraseColor(QColor(255,255,0)); + break; + case 'A': // warning: orange background + w->setEraseColor(QColor(255,127,0)); + break; +// case 'G': // graph settings: grey background +// w->setEraseColor(QColor(150,150,150)); +// break; + case 'H': // header style + font = item->normalFont; + font.setWeight(QFont::Normal); + if (font.pointSize() > 0) { + font.setPointSize(font.pointSize() * 6 / 4); + } else { + font.setPixelSize(font.pixelSize() * 6 / 4); + } + w->setFont(font); + } + item->wStyle = wStyle; + } + wStyle = 0; + //printf("%s: add %s/%s %d\n", name(), w->className(), w->name(), sameRow); + if (!sameRow) { + frac = (device->x - indent) % (device->columnWidth / colDiv); + device->x -= frac; + if (frac > device->xspace / 2) { + device->x += device->columnWidth / colDiv; + } + } + if (item->w->layout()) { + item->w->layout()->activate(); + } + w->resize(w->sizeHint()); + wid = w->width(); + hei = w->height(); + if (device->x + wid > device->visibleWidth()) { + newline(); + } + device->x += device->xspace; + if (hei >= device->lineHeight) { + offs = 0; + device->nextY = QMAX(device->nextY, device->y + hei + device->yspace); + } else { + offs = (device->lineHeight - hei) / 2; + device->nextY = QMAX(device->nextY, device->y + device->lineHeight + device->yspace); + } + device->addChild(item->w, device->x, device->y + offs); + device->x += wid; + device->maxWid = QMAX(device->maxWid, device->x); + item->w->show(); + item->used = true; + sameRow = false; + tight = false; + if (!tip.isEmpty()) { + QToolTip::add(item->w, tip); + tip=""; + } +} + +void Group::initItem(Item *item, QWidget *w, const char *line, bool activate) { + if (activate) { + connect(w, SIGNAL(sendCmd(const char *)), device->com, SLOT(sendCmd(const char *))); + connect(w, SIGNAL(changed()), device, SLOT(update())); + } + if (item->w) delete item->w; + item->w = w; + items.replace(line, item); +} + +void Group::toggle() { + if (shown) { + if (autoclose) { + autocloseAll(); // close containing groups + } else { + } + autoclose = false; + } + showIt(!shown); + device->update(false); +} + +Item *Group::findItem(const char *name) { + Item *item; + item = items.find(name); + if (!item) { + item = new Item(); + item->grp = this; + } + return item; +} + +void Group::hideContents() { + Item *item; + Group *g; + + for (QDictIterator it(items); (item = it.current()); ++it) { + if (item->used) { + g = dynamic_cast(item->w); + if (g) { + if (g->shown) { + g->hideContents(); + } + g->arrow->hide(); + } + item->w->hide(); + /* + if (item->graphButton) { + item->graphButton->hide(); + } + */ + } + } +} + +void Group::updateLayout(QStringList::Iterator &sit) { + QString lineQ; + int lwid=0; + LineInput *lineInp; + CheckBox *cb; + QLabel *label; + RdOnly *rdonly; + Menu *menu=0; + ColorMenu *colorMenu=0; + ClickButton *pb; + QString tit(""); + QPtrStack stack; + Group *grp; + QString value; + const char *line; + Item *item; + Item *rightItem = 0; + int labelNum=0; + char labelCode[8]; + const char *arg; + RadioButton *rb; + QString radioName(""); + QString radioLine(""); + int i; + int menuItem; + int yline; + bool autocloseOnRadio = false; + // obsolete graph: char groupName[256]; + + device->x = indent; + tip=""; + sameRow = false; + tight = false; + wStyle = 0; + + for (QDictIterator it(items); (item = it.current()); ++it) { + item->used = false; + } + for (; sit != device->code.end(); ++sit) { + lineQ = (*sit); + line = lineQ.latin1(); + if (line[0] != '-') { + if (line[0]) { + if (printit()) printf("%s\n", line); + } + continue; + } + arg = line+2; + //printf("L %s\n", line); + switch (line[1]) { + case '-': // no line break for next item + tight = true; + if (line[2] != '-') { + sameRow = true; + } + break; + case 'P': // line break + newline(); + break; + case 'W': // custom width + lwid = atoi(arg); + break; + case 'T': // group title + tit = arg; + break; + case 'H': // help (tool tip) + if (!tip.isEmpty()) { + tip.append("\n"); + } + tip.append(arg); + break; + case '>': // add last item to group title line (experimental) + rightItem = item; + break; + /* + case 's': // auto open (for stick group), will be obsolete + if (device->openGroups == "") { + device->openGroups = "sticks"; + device->update(); + } + break; + */ + case 'G': // group + item = findItem(line); + grp = dynamic_cast(item->w); + if (grp == 0) { + grp = new Group(this, tit, arg); + initItem(item, grp, line); + } else { + grp->title->setText(tit); + } + if (device->openGroup != "" && PathStartsWith(device->openGroup, arg)) { + grp->autoclose = false; + if (device->openGroup == arg) { + device->openGroup = ""; // opening finished + } + if (!grp->shown) grp->showIt(true); + device->update(false); + } + + if (arg[0] != '-' || grp->shown) { + newline(); + sameRow = true; + device->x += 14; + add(item, grp->title); + if (shown && grp->arrow) { + yline = device->y + 2; + device->addChild(grp->arrow, indent + 2, yline); + grp->arrow->show(); + } + /* + if (device->doJump && !grp->shown) { + // open automatically group tree in visibleGr (clicked on graph icon) + snprintf(groupName, sizeof groupName, " %s ", grp->name()); + if (device->visibleGr.find(groupName) >= 0) { + grp->showIt(true); + grp->closeLater = true; + //printf("open %s\n", groupName); + } + } + */ + + if (rightItem) { +// device->addChild(rightItem->w, device->x + device->xspace, device->y); + rightItem = 0; + } else { + newline(); + } + } + tit=""; + ++sit; + grp->updateLayout(sit); + newline(); + if (grp->shown) { + grp->setLineWidth(1); + grp->setFrameStyle(QFrame::Box | QFrame::Sunken); + grp->setFixedSize(device->width(), device->y - yline); + device->y += 4; + device->nextY += 4; + } else { + grp->setFrameStyle(QFrame::MenuBarPanel); + grp->setFixedSize(device->width(), grp->title->height()); + } + grp->update(); + if (sit == device->code.end()) --sit; + break; + case 'V': // value for next item + value = arg; + break; + case 'i': // readonly + item = findItem(line); + rdonly = dynamic_cast(item->w); + if (rdonly == 0) { + rdonly = new RdOnly(device->viewport(), tit, value, arg); + initItem(item, rdonly, line); + } else { + rdonly->label->setText(tit); + } + if (tight) { + rdonly->label->setMinimumWidth(0); + rdonly->value->setMinimumWidth(0); + } else { + rdonly->label->setMinimumWidth(device->labelWidth); + rdonly->value->setMinimumWidth(device->leWidth); + } + add(item); + lwid = 0; + rdonly->setValue(value); + value=""; + break; + case 'I': // text input + item = findItem(line); + lineInp = dynamic_cast(item->w); + if (lineInp == 0) { + lineInp = new LineInput(device->viewport(), tit, arg); + initItem(item, lineInp, line, true); + } else { + lineInp->label->setText(tit); + } + lineInp->setLength(lwid); + if (tight) { + lineInp->label->setMinimumWidth(0); + } else { + lineInp->label->setMinimumWidth(device->labelWidth); + } + add(item); + lwid = 0; + lineInp->setValue(value); + value=""; + break; + case 'S': // set style + wStyle = arg[0]; + break; + case 'D': // divide column (typically -D2: double number of columns) + colDiv = atoi(arg); + break; + case 'L': // label + snprintf(labelCode, sizeof labelCode, "%d", labelNum); + labelNum++; + item = findItem(labelCode); + label = dynamic_cast(item->w); + if (label == 0) { + label = new QLabel(arg, device->viewport()); + initItem(item, label, labelCode); + } else { + label->setText(arg); + } + add(item); + break; + case 'M': // menu (experimental ?) + if (arg[0] == 0) { // end of menu + if (menu) { + for (i=menu->count(); iremoveItem(i); + } + } + menu=0; + value = ""; + break; + } + item = findItem(line); + menu = dynamic_cast(item->w); + if (menu == 0) { + menu = new Menu(tit, device->viewport(), arg); + initItem(item, menu, line, true); + } + menuItem = 0; + add(item); + break; + case 'm': // menu item + if (menu) { + if (menuItem < menu->count()) { + if (menu->text(menuItem).compare(value) != 0) { + menu->changeItem(arg, menuItem); + } + } else { + menu->insertItem(arg, menuItem); + } + menuItem++; + } + break; + case 'c': // pen style menu + item = findItem(line); + colorMenu = dynamic_cast(item->w); + if (colorMenu == 0) { + colorMenu = new ColorMenu(device->viewport(), arg); + initItem(item, colorMenu, line, true); + } + colorMenu->setValue(value); + colorMenu->setLength(lwid); + add(item); + case 'a': // autoclose on radio button (experimental?) + //device->closeGroup = this; + autocloseOnRadio = true; + //closeLater = true; + break; + case 'R': // radio button group + radioName = arg; + device->selectCmd = radioName; + device->selectCmd.append(" "); + device->selectCmd.append(value); + break; + case 'r': // radio button + if (radioName.isEmpty()) break; + radioLine = radioName + line; + line = radioLine.latin1(); + item = findItem(line); + if (tit == "") { + tit = arg; + } + rb = dynamic_cast(item->w); + if (rb == 0) { + rb = new RadioButton(tit, device->viewport(), radioName, arg); + if (isSelectMenu) { + initItem(item, rb, line, false); + connect(rb, SIGNAL(sendCmd(const char *)), device, SLOT(setSelectCmd(const char *))); + connect(rb, SIGNAL(clearOthers(const QString &)), this, SIGNAL(clearRadioButtons(const QString &))); + connect(this, SIGNAL(clearRadioButtons(const QString &)), rb, SLOT(setValue(const QString &))); + } else { + initItem(item, rb, line, true); +// if (device->closeGroup) { +// connect(rb, SIGNAL(clicked()), device->closeGroup, SLOT(toggle())); +// } + if (autocloseOnRadio) { + connect(rb, SIGNAL(clicked()), device, SLOT(closeMarkedGroups())); + } + } + } else { + rb->setText(tit); + } + if (sameRow) { + rb->setMinimumWidth(0); + } else { + rb->setMinimumWidth(device->labelWidth / 2); + } + add(item); + rb->setChecked(value.compare(arg) == 0); + tit=""; + break; + case 'B': // push button + item = findItem(line); + pb = dynamic_cast(item->w); + if (pb == 0) { + pb = new ClickButton(device->viewport(), arg); + pb->setLabel(tit); + switch (arg[0]) { + case 'C': + initItem(item, pb, line, false); + connect(pb, SIGNAL(clicked()), device, SLOT(switchLayout())); + break; + case 'D': + initItem(item, pb, line, false); + connect(pb, SIGNAL(clicked()), device, SLOT(selectIt())); + pb->setDefault(true); + break; + case 'S': + initItem(item, pb, line, false); + connect(pb, SIGNAL(sendCmd(const char *)), device, SLOT(openGrps(const char *))); + connect(pb, SIGNAL(changed()), device, SLOT(update())); + connect(pb, SIGNAL(clicked()), pb, SLOT(handleReturn())); + break; + default: + initItem(item, pb, line, true); + connect(pb, SIGNAL(clicked()), pb, SLOT(handleReturn())); + break; + } + } + add(item); + pb->setLabel(tit); + tit=""; + break; + case 'C': // check box + item = findItem(line); + cb = dynamic_cast(item->w); + if (cb == 0) { + cb = new CheckBox(tit, device->viewport(), arg); + initItem(item, cb, line, true); + if (autocloseOnRadio) { + connect(cb, SIGNAL(clicked()), device, SLOT(closeMarkedGroups())); + } + } else { + cb->setText(tit); + } + add(item); + cb->setValue(value); + value=""; + tit=""; + break; + /* + case 'X': // make graphic button + if (item) { + if (item->graphButton == 0) { + item->graphButton = new GraphButton(this, arg); + connect(item->graphButton, SIGNAL(selectItem(const char *)), + device, SLOT(selectGraphItem(const char *))); + } + if (device->graphOn) { + device->addChild(item->graphButton, device->x, device->y); + device->x += device->graphButtonWidth; + item->graphButton->show(); + arg = strrchr(arg, ' '); + if (arg) { + tip = "go to graph settings for this item ("; + tip.append(arg + 3); + tip.append(")"); + QToolTip::add(item->graphButton, tip); + tip = ""; + } + } else { + item->graphButton->hide(); + } + } + break; + */ + case 'g': // graph descriptor + if (device->graphDesc == "") { + device->graphDesc = arg; + } else if (device->graphDesc != arg) { + device->graphDesc = arg; + device->rebuildTime = DoubleTime() + 1; +// printf("*** desc changed\n"); + // a graph rebuild will become necessary + } else if (device->rebuildTime != 0 + && DoubleTime() > device->rebuildTime) { + // but we do not rebuild before the descriptor was two times the + // same in succession and at least 1 second has passed + device->rebuildTime = 0; + device->updateInterval = 0.5; +// printf("*** rebuild\n"); + QTimer::singleShot(1, device, SLOT(rebuild())); + } + break; + /* + case 'J': // jump to me + if (device->doJump) { + device->doJump = false; + device->yJump = device->y; + } + break; + */ + case 'E': // end of nested group + goto END; + default: + if (printit()) printf("%s\n", line); + } + } +END: +// if (device->closeGroup == this) { +// device->closeGroup = 0; +// } + /* graph obsolete + if (device->closeGr && shown && closeLater) { + // check if group is to be closed + snprintf(groupName, sizeof groupName, " %s ", name()); + if (device->visibleGr.find(groupName) < 0) { + closeLater = false; + showIt(false); + for (QDictIterator it(items); (item = it.current()); ++it) { + item->used = false; + } + } + } + */ + for (QDictIterator it(items); (item = it.current()); ++it) { + if (!item->used) { + grp = dynamic_cast(item->w); + if (grp) { + grp->hideContents(); + grp->arrow->hide(); + } + item->w->hide(); + /* + if (item->graphButton) { + item->graphButton->hide(); + } + */ + } + } +} + +void Group::appendTo(QString &c) { + Group *g; + Item *item; + + c.append(" "); + c.append(name()); + for (QDictIterator it(items); (item = it.current()); ++it) { + g = dynamic_cast(item->w); + if (g) { + if (g->shown) { + g->appendTo(c); + } + } + } +} + +void Group::autocloseAll() { + Group *g; + Item *item; + + for (QDictIterator it(items); (item = it.current()); ++it) { + g = dynamic_cast(item->w); + if (g) { + g->autocloseAll(); + if (g->shown && g->autoclose) { + printf("autoclose %s\n", g->name()); + g->toggle(); + } + } + } +} + +bool Group::openGroup(QString &path) { + Group *g; + Item *item; + + for (QDictIterator it(items); (item = it.current()); ++it) { + g = dynamic_cast(item->w); + if (g && PathStartsWith(path, g->name())) { + if (!g->shown) { + g->toggle(); + // g->autoclose = true; + } + if (path == g->name()) { + return g->shown; + } + return g->openGroup(path); + } + } + return false; +} + +Group* Group::findGroup(QString &path) { + Group *g; + Item *item; + + for (QDictIterator it(items); (item = it.current()); ++it) { + g = dynamic_cast(item->w); + if (g) { + if (PathStartsWith(path, g->name())) { + if (path == g->name()) return g; + return g->findGroup(path); + } + } + } + return NULL; +} + +Device::Device(QWidget* parent) + : QScrollView(parent) +{ + LineInput *lineInp; + + SetEraseColor(viewport(), topLevelWidget()); + setFrameStyle(0); + xspace=6; + yspace=4; + QString tit("This is a very long label"); + lineInp = new LineInput(this, tit, "test"); + lineInp->setLength(0); + columnWidth = lineInp->stdWidth() + xspace; + leWidth = lineInp->le->width(); + labelWidth = lineInp->label->width(); + // graphButtonWidth = 24; + lineHeight = QMAX(lineInp->le->sizeHint().height(), lineInp->label->sizeHint().height()); + delete lineInp; + /* + gButton = new GraphButton(this, "g-0"); + graphOn = false; + visibleGr = ""; + doJump = false; + yJump = 0; + */ + updateMode = update_idle; + stopUpdate = false; + //selectState = select_idle; + rebuildTime = 0; + //enableClipper(true); +} + +void Device::init(Command *command, SeaSet *initSet) { + + set = initSet; + com = command; + device = new Group(this, "main"); + select = new Group(this, "select", true); + mainGroup = device; + addChild(mainGroup, 0, 0); + y = yspace; + update(false); + /* + QToolTip::add(gButton, "graph settings"); + connect(gButton, SIGNAL(clicked()), this, SLOT(graphToggle())); + */ + + updateInterval = 5.0; + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(timedUpdate())); + timer->start(10); +} + +void Device::rebuild() { + restart(graphDesc); +} + +/* +void Device::graphToggle() { + if (graphOn) { + QToolTip::add(gButton, "graph settings"); + columnWidth -= graphButtonWidth; + graphOn = false; + gButton->setPixmap(QPixmap(grPix)); + visibleGr = ""; + closeGr = true; + } else { + QToolTip::add(gButton, "close graph settings"); + columnWidth += graphButtonWidth; + graphOn = true; + gButton->setPixmap(QPixmap(gnPix)); + } + visibleGr = ""; + update(false); +} +*/ + +void Device::switchLayout() { + mainGroup->hideContents(); + if (mainGroup == select) { + mainGroup = device; + } else { + mainGroup = select; + } + update(false); +} + +void Device::selectIt() { + mainGroup->hideContents(); + com->sendCmd(selectCmd.latin1()); + mainGroup = device; + update(false); + QTimer::singleShot(100,this,SLOT(update())); +} + +void Device::setSelectCmd(const char *cmd) { + selectCmd = cmd; +} + +void Device::resizeEvent(QResizeEvent *e) { + if (e->size().width() != e->oldSize().width()) { + if (e->size().width() > 10 || e->oldSize().width() > 10) { + if (updateInterval > 0) { + updateInterval = 0; + QTimer::singleShot(100,this,SLOT(update())); + } + } + } + QScrollView::resizeEvent(e); +} + +/* +void Device::selectGraphItem(const char *id) { + //printf("SELECT %s\n", id); + visibleGr = ""; + if (id) { + visibleGr = " "; + visibleGr.append(id); + closeGr = true; + doJump = true; + } + update(false); +} +*/ + +void Device::timedUpdate() { + double now; + + if (updateMode == update_start) { +// printf("*** update start\n"); + updateStart(); + updateMode = update_complete; + tmot.start(); + } + if (updateMode == update_complete) { + if (tmot.elapsed() > 10000) { + set->sc->reconnect(); + tmot.start(); + printf("timeout -> reconnect\n"); + updateMode = update_idle; + } + if (set->sc->handleBuffer(0) <= 0) return; +// printf("*** update complete\n"); + if (set->sc->getResponse() < 0) { + set->sc->reconnect(); + printf("timeout -> reconnected\n"); + updateMode = update_idle; + return; + } + updateComplete(); + updateMode = update_idle; + return; + } + if (mainGroup == select && lastUpdate > 0) { + return; + } + now = DoubleTime(); + if (updateInterval == 0) { + updateInterval = 0.5; +// printf("*** immediate update\n"); + } else { + if (now < lastUpdate + updateInterval) { + return; + } + if (updateInterval < 0.99) { + updateInterval += 0.1; + } else { + updateInterval *= 2; + } + if (updateInterval >= 10) { + updateInterval = 10; + } +// printf("*** next update in %g sec\n", updateInterval); + } + if (isHidden() || height() < 3) { + lastUpdate = now; + return; + } + updateMode = update_start; +} + +void Device::activate() { + double now; + + if (mainGroup == select) return; + now = DoubleTime(); + if (now > lastUpdate) { + update(false); +// lastUpdate = now; + } +} + +void Device::closeMarkedGroups() { + mainGroup->autocloseAll(); + //selectState = select_idle; +} + +void Device::openGrps(const char *groups) { + Group *g; + bool openit; + + openGroup = groups + 1; // skip first letter S in button name + g = mainGroup->findGroup(openGroup); + openit = !g || !g->shown; + mainGroup->autocloseAll(); + if (openit) { + if (!mainGroup->openGroup(openGroup)) { + update(false); + return; + } + } + openGroup = ""; + update(false); +} + +void Device::update(bool refresh) { + if (refresh) { + updateInterval = 0.5; + stopUpdate = true; + } + lastUpdate = 0; +} + +void Device::updateStart() { + QString c("layout"); + + lastUpdate = DoubleTime(); + code.clear(); + mainGroup->appendTo(c); + /* + if (graphOn && !visibleGr.isEmpty()) { + if (doJump) { + c.append(visibleGr); + } else { + int i = visibleGr.findRev(' '); + if (i >= 0) { + c.append(visibleGr.mid(i)); + } + } + } + */ +// printf("*** SEND %s\n", c.latin1()); + set->sc->sendCommand(c.latin1()); + //printf("%s\n", c.latin1()); +} + +void Device::updateComplete() { + QString line; + + while (set->sc->getLine(line) > 0) { + code.append(line); + } + + if (stopUpdate) { + stopUpdate = false; + return; + } + + QStringList::Iterator it = code.begin(); + y = yspace; + nextY = yspace; + maxWid = 0; + +/* graph obsolete: + addChild(gButton, this->visibleWidth() - graphButtonWidth, 2); + gButton->show(); +*/ + // closeGroup = 0; + mainGroup->updateLayout(it); + resizeContents(maxWid, y); + +/* + if (yJump > 0) { + ensureVisible(0, yJump); + yJump = 0; + } +*/ +} diff --git a/device.h b/device.h new file mode 100644 index 0000000..dcb12e1 --- /dev/null +++ b/device.h @@ -0,0 +1,267 @@ +#include +#include +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "seaset.h" +#include "command.h" + +class Device; +class Group; + +class LineInput : public QHBox { + Q_OBJECT +public: + LineInput(QWidget *parent, QString &labelText, const char *var); + void setLength(int l); + int stdWidth(); + void setEraseColor(const QColor & color); +signals: + void sendCmd(const char *cmd); + void changed(); +public slots: + void handleReturn(); + void focusLost(); + void setValue(const QString &value); +public: + QLineEdit *le; + QLabel *label; +// bool graphOn; +private: + QString value; +}; + +class RdOnly : public QHBox { + Q_OBJECT +public: + RdOnly(QWidget *parent, QString &labelText, QString &val, const char *var); + void setLength(int l); +public slots: + void setValue(const QString &val); +public: + QLabel *label, *value; +private: + unsigned int len; +}; + +class CheckBox : public QCheckBox { + Q_OBJECT +public: + CheckBox(const QString &label, QWidget *parent, const char *var); +signals: + void sendCmd(const char *cmd); + void changed(); +public slots: + void handleReturn(); + void setValue(const QString &value); +}; + +class ClickButton : public QPushButton { + Q_OBJECT +public: + ClickButton(QWidget *parent, const char *var); + void setLabel(const QString &label); +signals: + void sendCmd(const char *cmd); + void changed(); +public slots: + void handleReturn(); +private: + int dx; +}; + +class RadioButton : public QRadioButton { + Q_OBJECT +public: + RadioButton(const QString &label, QWidget *parent, const QString &cmd, const char *value); +signals: + void sendCmd(const char *cmd); + void clearOthers(const QString &cmd); + void changed(); +public slots: + void handleClick(); + void setValue(const QString &value); +private: + QString cmd; +}; + +class Menu : public QComboBox { + Q_OBJECT +public: + Menu(const QString &label, QWidget *parent, const char *var); +signals: + void sendCmd(const char *cmd); + void changed(); +public slots: + void handleAct(const QString &value); + void setValue(const QString &value); +}; + +class ColorMenu : public QComboBox { + Q_OBJECT +public: + ColorMenu(QWidget *parent, const char *var); + ColorMenu(QString &color, QWidget *parent); + int getColorIndex(); + void setLength(int min, int max=0); +signals: + void sendCmd(const char *cmd); + void changed(); +public slots: + void handleAct(int index); + void handleChange(const QString &value); + void setValue(const QString &value); +private: + QString value; + void init(); + bool nochange; +}; + +class Item { +public: + QWidget *w; + bool used; + char wStyle; + QFont normalFont; + Group *grp; +// QPushButton *graphButton; + Item() { + this->used = true; + this->w = 0; + this->wStyle = 0; +// this->graphButton = 0; + } +}; + +/* +class GraphButton : public QPushButton { + Q_OBJECT +public: + GraphButton(QWidget *parent, const char *name); +signals: + void selectItem(const char *id); + void changed(); +public slots: + void handleClick(); +}; +*/ + +class Group : public QVBox { + Q_OBJECT +public: + Group(Device *p, const char *name, bool selectMenu=false); + Group(Group *p, const QString &tit, const char *name); + void updateLayout(QStringList::Iterator &it); + void appendTo(QString &c); + void hideContents(); + void autocloseAll(); + bool openGroup(QString &path); + Group* findGroup(QString &path); + bool shown; + bool autoclose; + +signals: + void changeHeight(); + void clearRadioButtons(const QString &except); + +public slots: + void toggle(); + +private: + void init(); + void add(Item *item, QWidget *w = 0); + void initItem(Item *item, QWidget *w, const char *line, bool active = false); + Item *findItem(const char *name); + void showIt(bool show); + void newline(); + + QPushButton *arrow; + QLabel *title; + Device *device; + Group *parentGroup; + QDict items; + int indent; // x indentation + bool sameRow; + bool tight; + QString tip; + char wStyle; + bool isSelectMenu; + int colDiv; + //bool closeLater; +}; + +typedef enum {select_idle, select_open} SelectState; + +class Device : public QScrollView +{ + Q_OBJECT + +friend class Group; + +public: + Device(QWidget* parent); + void init(Command *command, SeaSet *initSet); + +signals: + void restart(const char *vars); + +public slots: + void update(bool refresh = true); + void timedUpdate(); + void switchLayout(); + void selectIt(); + void setSelectCmd(const char *cmd); + // void graphToggle(); + // void selectGraphItem(const char *id); + void rebuild(); + void activate(); + void closeMarkedGroups(); + void openGrps(const char *groups); + +private: + void resizeEvent(QResizeEvent *e); + void updateStart(); + void updateComplete(); + + QString selectCmd; + SeaSet *set; + Group *mainGroup; + Group *device; + Group *select; + Command *com; + // GraphButton *gButton; + QStringList code; + int x, y; + int nextY; + int maxWid; + int xspace, yspace; + int labelWidth; + int leWidth; + int columnWidth; + // int graphButtonWidth; + int lineHeight; + QString graphDesc; + // bool graphOn; + // QString visibleGr; + // bool doJump; + QString openGroup; /* group path to open */ + SelectState selectState; + //int yJump; + bool stopUpdate; + double updateInterval; + double lastUpdate; + double rebuildTime; + // Group *closeGroup; + enum {update_idle, update_start, update_complete} updateMode; + QTime tmot; +}; + +#endif diff --git a/export.cpp b/export.cpp new file mode 100644 index 0000000..866de87 --- /dev/null +++ b/export.cpp @@ -0,0 +1,373 @@ +#include "export.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "seaset.h" +#include "sicsconn.h" +#include +#include + +void Export::addRow(QString label, QWidget *widget, QString tip) { + QLabel *labelW; + if (label != "") { + labelW = new QLabel(label, this); + lay->addWidget(labelW, row, 0); + if (tip != "") { + QToolTip::add(labelW, tip); + } + } + lay->addWidget(widget, row, 1); + if (tip != "") { + QToolTip::add(widget, tip); + } + row++; +} + +Export::Export(SeaSet *setArg, QWidget *parent, const char *instr) + : QWidget(parent) +{ + QPushButton *ok, *cancel; + char wd[PATH_MAX]; + + set = setArg; + row = 0; + instrument = instr; + + lay = new QGridLayout(this, 1, 3, 3, -1, "exportLayout"); + lay->setColStretch(2,1); + + addRow("export: ", new QLabel("all active curves", this), ""); + + lay->addWidget(new QLabel("step [sec]", this), 1, 0); + stepLE = new QLineEdit(this); + QValidator* validator = new QIntValidator( 1, 24*3600, this ); + stepLE->setValidator(validator); + stepLE->setText("auto"); + stepLE->setAlignment(Qt::AlignRight); + stepLE->setFixedWidth(100); + addRow("step [sec]", stepLE, + "time step [sec] (auto: 5 sec or more, max. 1000 points)" + ); + + scaleLE = new QLineEdit(this); + scaleLE->setText("1"); + scaleLE->setAlignment(Qt::AlignRight); + scaleLE->setFixedWidth(100); + addRow("x-scale [sec]", scaleLE, "enter 60 for minutes / 3600 for hours"); + + noneLE = new QLineEdit(this); + noneLE->setText("NaN"); + noneLE->setFixedWidth(100); + addRow("none value", noneLE, "value to be used for undefined numbers"); + + filePath = getcwd(wd, sizeof wd); + filePath.append("/seaexport.dat"); + + cancel = new QPushButton("Quit", this); + lay->addWidget(cancel, row, 0); + connect(cancel, SIGNAL(clicked()), this, SIGNAL(finish())); + + ok = new QPushButton("Export", this); + lay->addWidget(ok, 5, 1); + ok->setDefault(true); + ok->setFixedWidth(100); + addRow("", ok, ""); + connect(ok, SIGNAL(clicked()), this, SLOT(exportXY())); + + message = new QLabel("", this); + lay->addWidget(message, row, 1); + row++; + + lay->setRowStretch(row,1); +} + +void Export::showMe() { + show(); + updateCurveList(); +} + +void Export::updateCurveList() { + QString text("The following curves are active and to be exported:"); + int cnt=0; + SeaData *d; + + if (isHidden()) return; + for (d = set->dataList.first(); d != 0; d = set->dataList.next()) { + if (d->isActive()) { + text.append("\n "); + text.append(d->label); + cnt++; + } + } + if (cnt == 0) { + text = "no active curves"; + } + text.append("\n\ncurves are de-/activated by clicking on their legend"); + message->setText(text); + show(); +} + +void Export::progress(int bytes) { + QString text; + + if (progressBytes < 0) return; + if (isHidden()) return; + progressBytes += bytes; + if (progressBytes >= 0 && lastProgress.elapsed() > 250) { + lastProgress.start(); + text.sprintf("%.2f MBytes read", progressBytes * 0.000001); + message->setText(text); + show(); + procEvt(); + } +} + +void Export::exportXY() { + + QString cmd; + QString line; + int iret, cnt, icol; + bool ok; + time_t dt, t, from, to, step, lastRead, basis; + time_t i, lasti, len, siz, scale, mag, endi; + int prec; + SeaData *d; + QString text; + double val; + double *data; + double yy, yy0; + FILE *fil; + QString fileResult; + double eps; + static double undefpluseps = 0; + + fileResult = QFileDialog::getSaveFileName( + filePath, QString::null, this, + "save file dialog", + "select file to save exported data" + ); + if (fileResult == "") { + return; + } + filePath = fileResult; + + from = set->startRange; + to = set->endRange; + step = stepLE->text().toInt(); + if (step == 0) { /* auto */ + step = ((to - from) / 5000 + 1) * 5; + } + from = (from / step) * step; + scale = scaleLE->text().toInt(); + if (scale < 1) { + scale = 1; + } + len = (to - from - 1) / step; + to = from + len * step; + + if (step == 1) { + cmd.sprintf("graph %ld %ld text", from, to); + } else { + cmd.sprintf("graph %ld %ld np %ld", from, to, to - from + 2); + } + + cnt = 0; + for (d = set->dataList.first(); d != 0; d = set->dataList.next()) { + if (d->isActive()) { + cmd.append(" "); + cmd.append(d->name); + cnt++; + } + } + if (cnt == 0) { + message->setText("no curves active"); + show(); + return; + } + siz = len * cnt; + data = (double *)malloc(siz * sizeof(*data)); + if (data == NULL) { + message->setText("no memory"); + show(); + return; + } + + progressBytes = 0; + message->setText("waiting for server"); + procEvt(); + lastProgress.start(); + iret = set->ec->command(cmd); + progressBytes = -1; + + if (iret < 0) { + message->setText("can not connect to the GraphServer"); + goto err2; + } +//printf("%s\n", cmd.latin1()); + + text = "Curves:"; + message->setText(text); + procEvt(); + + iret = set->ec->getLine(line); + if (iret <= 0) { + message->setText("can not get response 1"); + goto err2; + } +//printf("> %s\n", line.latin1()); + + /* get returned absolute time */ + lastRead = line.toLong(); + //sscanf(line, "%ld", &lastRead); + + iret = set->ec->getLine(line); + if (iret <= 0) { + message->setText("can not get response 2"); + goto err2; + } + //printf("> %s\n", line.latin1()); + if (!line.startsWith("*")) { + message->setText("missing *"); + goto err2; + } + + icol = 0; + fil = fopen(filePath.latin1(), "w"); + fprintf(fil, "# SEA export from %s\n", instrument); + fprintf(fil, "# col 1: Time/%d sec since %s 0:00\n", (int)scale, set->dateLabel(0).latin1()); + + endi = 0; + for (d = set->dataList.first(); d != 0; set->dataList.findRef(d), d = set->dataList.next()) { + if (d->isActive()) { + yy0 = DATA_UNDEF; + yy = DATA_UNDEF; + lasti = -1; + //printf("- %s\n", d->label.latin1()); + fprintf(fil, "# col %d: %s (%s) %s\n", + icol + 2, d->name.latin1(), d->label.latin1(), + d->row->tagLabel->text().latin1()); + text.append("\n "); + text.append(d->label); + line = line.section(' ', 0, 0); + if (d->name == line.mid(1)) { + t = 0; + do { + iret = set->ec->getLine(line); + if (iret <= 0) { + message->setText("can not get line"); + goto err; + } +//printf("> %s\n", line.latin1()); + if (line.startsWith("*")) { + i = len; + } else { + /* decode line to x[n] / y[n] */ + dt = line.section(' ', 0, 0).toLong(&ok); + /* + iret = sscanf(line, "%ld %n", &dt, &pos); + if (iret < 1) { + */ + if (!ok) { + message->setText("bad timestep"); + goto err; + } + t += dt; + i = (t - from) / step; + yy0 = yy; + yy = line.section(' ', 1, 1).toDouble(&ok); + //iret = sscanf(line + pos, "%lf", &yy); + // printf("scan %8ld %9.4f\n", t - from, yy); + if (!ok) { // not a valid value + yy = DATA_UNDEF; + } else { + if (yy == DATA_UNDEF) { // by chance + if (undefpluseps == 0) { // create closest possible (but different) value to DATA_UNDEF + undefpluseps = yy; + eps = undefpluseps * 1e-9; + while (undefpluseps == DATA_UNDEF) { + undefpluseps += eps; + eps = eps * 2; + } + } + yy = undefpluseps; + } + } + } + /*---*/ + + if (lasti < i) { + if (lasti < 0) { + lasti = 0; + } + while (lasti < i - 1 && lasti < len) { + assert(lasti * cnt + icol < siz); + data[lasti*cnt + icol] = yy0; + lasti++; + } + if (lasti < len) { + data[lasti*cnt + icol] = yy; + // printf("%ld %9.4f\n", lasti, yy); + if (yy != DATA_UNDEF) { + if (lasti > endi && lasti != len) endi = lasti; + } + } + lasti++; + } + } while (!line.startsWith("*")); + } else { + for (i = 0; isetText(text); + procEvt(); + } + } + //fprintf(fil, "\n"); + basis = (from - set->base) / step; + //printf("endi %ld\n", endi); + for (i = 0; i < endi; i++) { + if (scale == 1) { + fprintf(fil, "%ld", (basis + i) * step); + } else { + mag = 10 * scale / step; + for (prec = 0; mag > 1; prec++,mag/=10); + fprintf(fil, "%.*f", prec, (double)(basis + i) * step / scale); + } + for (icol = 0; icol < cnt; icol++) { + val = data[i*cnt + icol]; + if (val == DATA_UNDEF) { + fprintf(fil, "\t%s", noneLE->text().latin1()); + } else { + // fprintf(fil, "\t%.7g", val); + // we might write here full precision, this will not watse space in case step is not 1 + fprintf(fil, "\t%.14g", val); + } + } + fprintf(fil,"\n"); + } + text.append("\n\nwritten to\n"); + text.append(filePath.latin1()); + message->setText(text); +err: + fclose(fil); +err2: + free(data); + show(); +} diff --git a/export.h b/export.h new file mode 100644 index 0000000..5948080 --- /dev/null +++ b/export.h @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "seaset.h" + +class Export : public QWidget +{ + Q_OBJECT + +public: + Export(SeaSet *setArg, QWidget *parent=0, const char *instr=0); + void showMe(); +private: + QLineEdit *stepLE; + QLineEdit *noneLE; + QLineEdit *scaleLE; + QLabel *message; + SeaSet *set; + QGridLayout *lay; + int row; + QString filePath; + const char *instrument; + int progressBytes; + QTime lastProgress; +signals: + void finish(); + void procEvt(); +public slots: + void updateCurveList(); + void exportXY(); + void addRow(QString label, QWidget *widget, QString tip); + void progress(int bytes); +}; diff --git a/graph.cpp b/graph.cpp new file mode 100644 index 0000000..0a86c36 --- /dev/null +++ b/graph.cpp @@ -0,0 +1,355 @@ +#include "graph.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "seaset.h" +#include "utils.h" + +static char *xZoomPix[] = { + "16 16 2 1", + " c None", + "X c #000000", + " ", + " ", + " X X ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " XX XX ", + " X X ", + " ", + " " +}; + +static char *leftPix[] = { + "16 16 2 1", + " c None", + "X c #000000", + " ", + " ", + " X ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " X ", + " ", + " " +}; + +static char *rightPix[] = { + "16 16 2 1", + " c None", + "X c #000000", + " ", + " ", + " X ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " XX ", + " X ", + " ", + " " +}; + +static char *undoPix[] = { + "16 16 2 1", + " c None", + "X c #000000", + " ", + " XXXXXXXXX ", + " XX ", + " XX ", + " X ", + " X ", + " X ", + " X X ", + " XX XX ", + " XX XX ", + " XXXXXXXXXXXX ", + " XX ", + " XX ", + " X ", + " ", + " " +}; + +Graph::Graph(const char* arg, QWidget *parent, long range) + : QWidget(parent, 0) +{ + QPixmap pmOut((const char **)xZoomPix); + QPixmap pmLeft((const char **)leftPix); + QPixmap pmRight((const char **)rightPix); + QPixmap pmUndo((const char **)undoPix); + QString str; + //QHBox *buttonBar; + QPushButton* buttonOk; + QPushButton* buttonAll; + QPushButton* buttonUndo; + QPushButton* buttonLeft; + QPushButton* buttonOut; + QPushButton* buttonRight; + QPushButton* buttonSettings; + QPushButton* buttonXY; + QSplitter *graphSplit; + QVBoxLayout* graphLayout; + QHBoxLayout* buttonLayout; + int w,h; + const char * vars; + + setName("Graph"); + + graphLayout = new QVBoxLayout(this, 0, 0, "graphLayout"); + + graphSplit = new QSplitter(Qt::Vertical, this, "splitter"); + graphLayout->add(graphSplit); + + buttonLayout = new QHBoxLayout(0, 6, 2, "buttonBar"); + graphLayout->addLayout(buttonLayout); + + buttonLayout->addWidget(new QLabel("Range", this)); + + lineEditRange = new QComboBox(true, this); + //SetLength(lineEditRange, 5, 6); + buttonLayout->addWidget(lineEditRange); + QToolTip::add(lineEditRange, "enter a number and m,h or d"); + + buttonOk = new QPushButton(this); + buttonOk->setDefault(true); + buttonOk->setText("OK"); + ShrinkButton(buttonOk); + buttonLayout->addWidget(buttonOk); + + buttonSettings = new QPushButton(this); + buttonSettings->setText("Settings"); + ShrinkButton(buttonSettings); + buttonLayout->addWidget(buttonSettings); + QToolTip::add(buttonSettings, "edit shown curves"); + + buttonAll = new QPushButton(this); + buttonAll->setText("Show All"); + ShrinkButton(buttonAll); + buttonLayout->addWidget(buttonAll); + QToolTip::add(buttonAll, "show all curves"); + + buttonXY = new QPushButton(this); + buttonXY->setText("export"); + ShrinkButton(buttonXY); + buttonLayout->addWidget(buttonXY); + QToolTip::add(buttonXY, "export data"); + + liveBox = new QCheckBox(this); + liveBox->setText("live mode"); + buttonLayout->addWidget(liveBox); + liveBox->setChecked(false); + + buttonLayout->addStretch(1); + + buttonUndo = new QPushButton(this); + buttonUndo->setPixmap(pmUndo); + w = QMAX(22, buttonUndo->sizeHint().width() - 6); + h = QMAX(22, buttonUndo->sizeHint().height() - 6); + if (w > h) { // Mac like + h += 6; + w -= 6; + } + buttonUndo->setFixedSize(w, h); + buttonLayout->addWidget(buttonUndo); + buttonUndo->setEnabled(false); + QToolTip::add(buttonUndo, "undo zoom or shift action"); + + buttonLeft = new QPushButton(this); + buttonLeft->setPixmap(pmLeft); + buttonLeft->setFixedSize(w, h); + buttonLayout->addWidget(buttonLeft); + QToolTip::add(buttonLeft, "scroll left"); + + buttonRight = new QPushButton(this); + buttonRight->setPixmap(pmRight); + buttonRight->setFixedSize(w, h); + buttonLayout->addWidget(buttonRight); + QToolTip::add(buttonRight, "scroll right"); + + buttonOut = new QPushButton(this); + buttonOut->setPixmap(pmOut); + buttonOut->setFixedSize(w, h); + buttonLayout->addWidget(buttonOut); + QToolTip::add(buttonOut, "zoom out horizontally"); + + str="Sea "; + str.append(arg); + topLevelWidget()->setCaption(str); + + clearWState(WState_Polished); + + unit='m'; + str="30m"; + + set = new SeaSet(graphSplit, range, "seaset"); + + const char *items[]={ + "1m","2m","5m","10m","15m","30m" + ,"1h","2h","4h","8h","16h" + ,"1d","2d","4d","8d",0}; + lineEditRange->insertStrList(items); + lineEditRange->setSizeLimit(15); + lineEditRange->setMaxCount(15); + lineEditRange->setEditText(str); + lineEditRange->lineEdit()->selectAll(); + + connect(lineEditRange, SIGNAL(activated(int)), + this, SLOT(activateRange())); + connect(buttonOk, SIGNAL(clicked()), + this, SLOT(activateRange())); + connect(buttonAll, SIGNAL(clicked()), + set, SLOT(showAll())); + connect(buttonSettings, SIGNAL(clicked()), + this, SIGNAL(gotoSettings())); + connect(buttonXY, SIGNAL(clicked()), + this, SIGNAL(openExport())); + connect(buttonUndo, SIGNAL(clicked()), + set, SLOT(undoZoom())); + connect(buttonLeft, SIGNAL(clicked()), + set, SLOT(shiftLeft())); + connect(buttonOut, SIGNAL(clicked()), + set, SLOT(zoomOutX())); + connect(buttonRight, SIGNAL(clicked()), + set, SLOT(shiftRight())); + connect(liveBox, SIGNAL(toggled(bool)), + set, SLOT(setLive(bool))); + connect(set, SIGNAL(undoEnabled(bool)), + buttonUndo, SLOT(setEnabled(bool))); + connect(lineEditRange->lineEdit(), SIGNAL(textChanged(const QString &)), + this, SLOT(autoReturn(const QString &))); + + set->setHost(arg); + + if (printit()) { + printf("reading curve list,\n"); + } + if (range < 365*24*3600) { + vars = "now"; + } else { + vars = NULL; + } + set->autoCurves(vars); + if (printit()) { + printf("opening window.\n"); + } + //lineEditRange->selectAll(); + + + QTimer *timer = new QTimer(this, "repeat"); + connect(timer, SIGNAL(timeout()), this, SLOT(liveUpdate())); + timer->start(2500); +} + +void Graph::liveUpdate() { + if (isShown() && width() > 3) { + set->liveUpdate(); + } +} + +void Graph::showIt(bool yes) { + if (yes) { + set->liveUpdate(); + show(); + } else { + hide(); + } +} + +void Graph::activateRange() { + long fact; + QString t; + double rng; + unsigned int j; + + //setFocus(); + t = lineEditRange->currentText(); + for (j = 0; j < t.length(); j++) { + if (t[j] > '9') { + unit = t.latin1()[j]; + t.truncate(j); + break; + } + } + switch (unit) { + case 'D': unit='d'; + case 'd': fact = 24*3600; break; + case 'H': unit='h'; + case 'h': fact = 3600; break; + case 'M': unit='m'; + case 'm': fact = 60; break; + case 'S': unit='s'; + case 's': fact = 1; break; + default: fact = 60; unit = 'm'; + } + rng = t.toDouble() * fact; + //printf("%s %lf %ld\n", t.latin1(), rng, fact); + if (rng > SEA_MAX_RANGE) { + unit = 'd'; + t.sprintf("%d%c", (SEA_MAX_RANGE + 36000) / 24 / 3600, unit); + lineEditRange->setEditText(t); + rng = SEA_MAX_RANGE; + } else if (rng < 59.5) { + rng = 60; + if (unit == 's') { + lineEditRange->setEditText("60s"); + } else { + unit = 'm'; + lineEditRange->setEditText("1m"); + } + } else { + t.append(unit); + lineEditRange->setEditText(t); + } + set->rescaleRange(long(rng)); + lineEditRange->lineEdit()->selectAll(); +} + +void Graph::autoReturn(const QString &text) { + if (0 == text.find(QRegExp("\\d{1,4}[DdHhMmSs]"))) { + activateRange(); + } else if (0 == text.find(QRegExp("[Qq]"))) { + exit(0); + } +} diff --git a/graph.h b/graph.h new file mode 100644 index 0000000..d21994b --- /dev/null +++ b/graph.h @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "seaset.h" + +class QVBoxLayout; +class QHBoxLayout; +class QSpacerItem; +class QPushButton; +class QCheckBox; + +class Graph : public QWidget +{ + Q_OBJECT + +public: + Graph(const char* host = 0, QWidget *parent=0, long range=1800); + + SeaSet *set; + QComboBox* lineEditRange; + QCheckBox* liveBox; + +public slots: + void activateRange(); + void autoReturn(const QString &); + void showIt(bool show); + void liveUpdate(); +signals: + void gotoSettings(); + void openExport(); + +private: + char unit; + void init(); +}; + + + diff --git a/instr_hosts.cpp b/instr_hosts.cpp new file mode 120000 index 0000000..fc5a977 --- /dev/null +++ b/instr_hosts.cpp @@ -0,0 +1 @@ +../six/instr_hosts.c \ No newline at end of file diff --git a/instr_hosts.h b/instr_hosts.h new file mode 120000 index 0000000..af7b719 --- /dev/null +++ b/instr_hosts.h @@ -0,0 +1 @@ +../six/instr_hosts.h \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8a52b6d --- /dev/null +++ b/main.cpp @@ -0,0 +1,521 @@ +#include "main.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "instr_hosts.h" + +QPoint firstPos; + +FILE *fil; +char preffile[PATH_MAX]=""; +int prefdirlen = 0; +char prefil[80]=""; +char homeinstr[64]=""; +const char *instr; +//char prefinstr[64]=""; +char frominstr[128]; + +char *strtoupper(const char *str) { + static char buf[128]; + int i; + for (i = 0; i < 127; i++) { + if (str[i] == 0) break; + buf[i] = toupper(str[i]); + } + buf[i] = 0; + return buf; +} + +const char *fullinstr(const char *host, const char *instr) { + static char buf[128]; + if (strncasecmp(host, instr, strlen(instr)) == 0) { + return host; + } + snprintf(buf, sizeof buf, "%s at %s", host, strtoupper(instr)); + return buf; +} + +SeaApp::SeaApp(int &argc, char **argv) : QApplication(argc, argv) { + char *arg; + char *style=0; + char instrList[1024]; + QString deviceList; + int i; + + int port; + char host[64]; + int query; + struct tm tm; + time_t startupTime, now, range; + QString tip; + SicsConnection *seaman; + char *remotehost; + char *remoteinstr; + char *hostname; + char warning[256]; + int x, y, w, h; + QFont afont = font(); + afont.setPointSize(12); + setFont(afont); + + time(&lastActive); + time(&lastTimer); + tab = NULL; + sleepTime = 3600; + sleeping = false; + frominstr[0] = 0; + + home = InstrHost("sea", "", homeinstr, sizeof homeinstr, host, sizeof host, &port); + if (port == 0) { + homeinstr[0]='\0'; + } + remoteinstr = InstrHostRemoteInstr(); + hostname = InstrHostName(); + remotehost = InstrHostRemoteName(); + instr = NULL; + query = -1; + range = 1800; + setprintit(0); + for (i=1; i=argc) { + QStringList keys=QStyleFactory::keys(); + printf("styles: %s\n", keys.join(", ").latin1()); + ciao(); + } + style = argv[i]; + } else if (strcasecmp(arg,"-h") == 0) { + /* ??? */ + home = 1; + } else if (strcasecmp(arg,"-p") == 0) { + i++; + /* ignore obsolete pid file option */ + } else if (strcasecmp(arg,"-c") == 0) { + i++; + /* ignore obsolete watch/restart option */ + } else if (strcasecmp(arg,"-t") == 0) { + /* set sleep time (1 h by default) */ + i++; + if (isetHost("seaman", "sea"); + seaman->setUser("seauser seaser\n"); + + seaman->command(seamancmd.latin1()); + if (!seaman->getLine(deviceList)) { + deviceList = ""; + } + QStringList devlist(QStringList::split(',', deviceList, true)); + + seaman->disconnect(); + + int row = 1, col=0, cols = 4; + + menu.setCaption("Sea"); +// grid.setMargin(5); + + QLabel *label = new QLabel("Select instrument", &menu); + QFont font = label->font(); + font.setPointSize(18); + label->setFont(font); + QFont tooltipfont = QToolTip::font(); + QToolTip::setFont(font); + grid.addMultiCellWidget(label, 0, 0, 0, cols-1); + + QStringList::Iterator dit = devlist.begin(); + for (QStringList::Iterator it = list.begin(); it != list.end(); ++it, ++dit) { + if ((*it).isEmpty()) { + if (col >= 0) { + col = 0; + row++; + } + } else { + rb = new QRadioButton(*it, &menu); + rb->setFont(font); + if (!(*dit).isEmpty()) { + QToolTip::add(rb, *dit); + } + grid.addWidget(rb, row, col); + QObject::connect(rb, SIGNAL(clicked()), &menu, SLOT(accept())); + bg.insert(rb); + col++; + if (col >= cols) { + col = 0; + row++; + } + if ((*it).compare(instr) == 0) { + rb->setChecked(true); + } + } + } + if (col > 0) { + row++; + col = 0; + } + + if (query == 1) { + /* + QString cbtext("default instrument when started from "); + cbtext += remotehost; + defi = new QCheckBox(cbtext, &menu); + if (prefinstr[0]) defi->setChecked(true); + grid.addMultiCellWidget(defi, row, row, 0, cols-1); + row++; + */ + } + + QPushButton ok("OK", &menu); + ok.setDefault(true); + ok.setFont(font); + connect(&ok, SIGNAL(clicked()), &menu, SLOT(accept())); + grid.addWidget(&ok, row, 0); + + QPushButton cancel("Quit", &menu); + cancel.setFont(font); + connect(&cancel, SIGNAL(clicked()), &menu, SLOT(reject())); + grid.addWidget(&cancel, row, cols-1); + + bg.hide(); + + if (printit()) { + printf("Instrument selection\n"); + } + if (menu.exec() == QDialog::Rejected) ciao(); + rb = dynamic_cast(bg.selected()); + if (!rb) ciao(); + instr = strdup(rb->text()); + /* + if (defi) { + if (defi->isChecked()) { + snprintf(prefinstr, sizeof prefinstr, "%s", instr); + } else { + prefinstr[0] = 0; + } + } + */ + QToolTip::setFont(tooltipfont); + } + + + if (frominstr[0]) { + snprintf(warning, sizeof warning, "WARNING: connected to %s,\n%s\n", strtoupper(instr), frominstr); + printf("\n%s\n", warning); + } else { + warning[0] = 0; + } + time(&startupTime); + if (printit()) { + printf("Sea %s starting,\n", instr); + } + + // read preferences + // include the screen size into the name: this helps if clients are used + // remotely from different screen sizes + + // new pref. file: include remote host name + snprintf(preffile, sizeof preffile, "%s/.seapref/%s_%s_%dx%d", + getenv("HOME"), remotehost, instr, desktop()->width(), desktop()->height()); + prefdirlen = strstr(preffile, "seapref/") + 7 - preffile; + fil = fopen(preffile, "r"); + if (fil) { /* new style pref file */ + char header[128]; + fgets(header, sizeof header, fil); // skip header + if (4 != fscanf(fil, "%d %d %d %d", &x, &y, &w, &h)) { + x = -1; + } + fclose(fil); + prefil[0] = 0; + } else { + // old style pref. file + snprintf(prefil, sizeof prefil, ".sea_%dx%d_%s", + desktop()->width(), desktop()->height(), instr); + fil = fopen(prefil, "r"); + if (fil) { + if (4 != fscanf(fil, "%d %d %d %d", &x, &y, &w, &h)) { + x = -1; + } + fclose(fil); + } else { + x = -1; + } + } + + + + tab = new Tab(strdup(instr), 0, range, warning); + if (tab->graph == NULL) ciao(); + connect(tab->device, SIGNAL(restart(const char*)), tab->graph->set, SLOT(restart(const char *))); + + if (x >= 0) { + tab->initSize(w, h); + tab->move(x, y); + } else { + tab->initSize(1000, 800); + } + tab->show(); + /* + QBoxLayout *lay = dynamic_cast(tab->layout()); + + QLayoutIterator it = lay->iterator(); + QLayoutItem *child; + while ( (child = it.current()) != 0 ) { + if (child->widget()) { + printf("- widget %s\n", child->widget()->name()); + } else if (child->spacerItem()) { + printf("- spacer\n"); + } else if (child->layout()) { + printf("- layout\n"); + } else { + printf("- null\n"); + } + ++it; + } + lay->insertWidget(3,tab->split); + */ + firstPos = tab->pos(); + tab->move(firstPos); + time(&now); + if (range > 365*24*3600) { + tip.sprintf("initially off"); + } else if ((int)(now - startupTime) < 7) { + tip.sprintf("initially on\n%d sec for startup:\nconnection seems fast enough", (int)(now - startupTime)); + tab->graph->liveBox->setChecked(true); + } else { + tip.sprintf("initially off\n%d sec for startup:\nconnection seems slow", (int)(now - startupTime)); + } + QToolTip::add(tab->graph->liveBox, tip); + + connect(this, SIGNAL(lastWindowClosed()), this, SLOT(quit())); +// connect(this, SIGNAL(lastWindowClosed()), this, SLOT(ciao())); + + connect(tab->exportXY, SIGNAL(procEvt()), this, SLOT(procEvt())); + connect(tab->settings->set, SIGNAL(gotoTime(time_t, time_t, time_t, bool)), + tab->report, SLOT(gotoTime(time_t, time_t, time_t, bool))); + connect(tab->report, SIGNAL(setMarker(time_t)), + tab->settings->set, SLOT(setMarker(time_t))); + connect(tab->settings->set->ec, SIGNAL(progress(int)), tab->exportXY, SLOT(progress(int))); + return; +} + +void Exit(int code) { + exit(code); +} + +void SeaApp::ciao() { + Exit(0); +} + +void SeaApp::fail() { + Exit(257); +} + +void SeaApp::procEvt() { + processEvents(100); +} + +QRect savedWinGeo; + +bool moveOrResize = true; + +bool SeaApp::notify(QObject *receiver, QEvent *e) { + time_t now; + bool result; + + QEvent::Type t = e->type(); + switch (t) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + case QEvent::KeyPress: + lastActive = time(NULL); + if (tab) { + if (sleeping) { + tab->wake(); + sleeping = FALSE; + } + tab->graph->set->runningMarkerOff(true); + result = QApplication::notify(receiver, e); + tab->graph->set->runningMarkerOff(false); + return result; + } + break; + case QEvent::Timer: + time(&now); + if (now > lastActive + sleepTime) { + if (frominstr[0]) + exit(); + if (lastTimer < now - 120) { + lastActive = now + 120 - sleepTime; + } +// if (home != 1 && now > lastActive + 24*3600) { +// exit(); // exit when not on instrument computer after one day +// } + if (now > lastActive + 24*3600) { + exit(); // exit anyway when not active for one day + } + if (!sleeping && tab != NULL) { + tab->sleep("sleeping ... click to wake up"); + sleeping = TRUE; + } + } + if (moveOrResize && now != lastTimer && tab != NULL && + tab->settings->set->sc->state != disconnected) { + moveOrResize = false; + QRect winGeo(tab->pos(), tab->size()); + if (winGeo != savedWinGeo) { + savedWinGeo = winGeo; + fil = fopen(preffile, "w"); + if (fil == NULL) { + preffile[prefdirlen] = 0; + mkdir(preffile, 0755); + chmod(preffile, 0755); + preffile[prefdirlen] = '/'; + fil = fopen(preffile, "w"); + } + if (fil) { + fprintf(fil, "#seaclient preferences V1.1\n%d %d %d %d\n", + winGeo.x(), winGeo.y(), + winGeo.width(), winGeo.height()); + fclose(fil); + } + } + } + lastTimer = now; + break; + case QEvent::Move: + case QEvent::Resize: + moveOrResize = true; + break; + case QEvent::WindowDeactivate: +// printf("deact\n"); + break; + default: + break; + } + return QApplication::notify(receiver, e); +} + +int main(int argc, char ** argv) { + SeaApp sea(argc, argv); +// printf("exit %d\n", sea.exec()); +// return 0; + return sea.exec(); +} diff --git a/main.h b/main.h new file mode 100644 index 0000000..f8ecb3f --- /dev/null +++ b/main.h @@ -0,0 +1,26 @@ +#include +#include +#include +#include "tab.h" + +class SeaApp : public QApplication { + Q_OBJECT + +public: + SeaApp(int &argc, char **argv); + + Tab *tab; + int home; + time_t sleepTime; + +public slots: + void ciao(); + void fail(); + void procEvt(); + +private: + bool notify(QObject *recevier, QEvent *e); + time_t lastActive; + time_t lastTimer; + bool sleeping; +}; diff --git a/report.cpp b/report.cpp new file mode 100644 index 0000000..d031855 --- /dev/null +++ b/report.cpp @@ -0,0 +1,182 @@ +#include "report.h" +#include +#include +#include +#include + +Report::Report(QWidget *parent, SicsConnection *conn) : QWidget(parent, "report") +{ + QVBoxLayout *vbox = new QVBoxLayout(this, 0, 0, "report_vbox"); + QHBoxLayout *hdr = new QHBoxLayout(0, 3, 3, "report_hdr"); + + this->conn = conn; + + editButton = new QPushButton("edit", this); + hdr->addWidget(editButton); + + varEdit = new QLineEdit("tt.main", this); + hdr->addWidget(varEdit); + + leaveButton = new QPushButton("close", this); + hdr->addWidget(leaveButton); + + vbox->addLayout(hdr); + + textedit = new MyTextEdit(this, "report_edit"); + textedit->setReadOnly(TRUE); + textedit->setSelectionAttributes(1, QColor(255,255,128), FALSE); + + vbox->addWidget(textedit, 1); + + timer = new QTimer(this, "reportTimer"); + + connect(leaveButton, SIGNAL(clicked()), this, SIGNAL(leave())); + connect(editButton, SIGNAL(clicked()), this, SLOT(editReport())); + connect(timer, SIGNAL(timeout()), this, SLOT(updatePos())); + +} + +void Report::gotoTime(time_t from, time_t at, time_t to, bool silent) { + char cmd[128]; + int iret; + QString line; + time_t lastRead, t, dt; + bool ok; + QString message; + QDateTime datim; + int l; + int p; + + if (isHidden()) { + if (silent) return; + openMe(); + } + if (variable != varEdit->text() || from != this->from || to != this->to) { + + this->to = to; + this->from = from; + times.clear(); + + variable = varEdit->text(); + snprintf(cmd, sizeof cmd, "graph %ld %ld text np %ld %s", + from, to, to - from, variable.latin1()); + + textedit->clear(); + conn->command(cmd); + + iret = conn->getLine(line); + if (iret <= 0) { + message = ("can not get response 1"); + goto err; + } + + /* get returned absolute time */ + lastRead = line.toLong(); + + iret = conn->getLine(line); + if (iret <= 0) { + message = "can not get response 2"; + goto err; + } + + if (!line.startsWith("*" + variable)) { + message = "response '" + line + "' must start with '*" + variable + "'"; + goto err; + } + + t = 0; + for (;;) { + iret = conn->getLine(line); + if (iret <= 0) { + message = "can not get line"; + goto err; + } + //printf("> %s\n", line.latin1()); + if (line.startsWith("*")) { + break; + } + dt = line.section(' ', 0, 0).toLong(&ok); + if (!ok) { + message = "bad timestep"; + goto err; + } + t += dt; + times.append(t); + datim.setTime_t(t); + textedit->appendHtml("" + datim.toString("hh:mm:ss ") + ""); + textedit->appendText( + line.section(' ', 1).replace("\\n","\n").replace("\\t","\t").replace("\\\\","\\") + +"\n" + ); + } + textedit->append("\n"); + pos = -1; + } + l = times.count(); + + p = l - 1; + for (int ipos = 0; ipos < l; ipos++) { + if (times[ipos] >= at) { + p = ipos - 1; + break; + } + } + if (p < 0) p = 0; + if (pos < 0) { + pos = l; + } + setCursorPos(p); + timer->start(200); + return; +err: + textedit->append("error: "+message); +} + +void Report::setCursorPos(int newpos) { + int index; + + textedit->setCursorPosition(newpos, 0); + if (newpos > pos) { + textedit->moveCursor(QTextEdit::MoveDown, false); + textedit->moveCursor(QTextEdit::MoveDown, false); + } else { + textedit->moveCursor(QTextEdit::MoveUp, false); + textedit->moveCursor(QTextEdit::MoveUp, false); + } + textedit->getCursorPosition(&posc, &index); + pos = newpos; + textedit->setSelection(pos, 0, pos+1, 0, 1); + textedit->setCursorPosition(posc, 0); + textedit->ensureCursorVisible(); +} + +void Report::updatePos() { + int para, index; + + textedit->getCursorPosition(¶, &index); + if (para == pos) return; + if (para == posc && index == 0) { + // cursor repositioning pending + posc = pos; + textedit->setCursorPosition(posc, 0); + textedit->ensureCursorVisible(); + } else { + setCursorPos(para); + } + if (pos < (int)times.count()) { + setMarker(times[pos]); + } +} + +void Report::editReport() { + QString text; + + if (textedit->isReadOnly()) { + textedit->setReadOnly(FALSE); + timer->stop(); + setMarker(0); + } else { + printf("%s\n", textedit->text().latin1()); + textedit->setText(textedit->text()); + } +} diff --git a/report.h b/report.h new file mode 100644 index 0000000..f13d752 --- /dev/null +++ b/report.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#include +#include "sicsconn.h" +#include "utils.h" + +class Report : public QWidget +{ + Q_OBJECT +public: + Report(QWidget *parent, SicsConnection *conn); +signals: + void leave(); + void openMe(); + void setMarker(time_t t); +public slots: + void gotoTime(time_t from, time_t at, time_t to, bool silent); + void updatePos(); + void editReport(); +private: + void setCursorPos(int newpos); + MyTextEdit *textedit; + QPushButton *editButton; + QPushButton *leaveButton; + QLineEdit *varEdit; + time_t atTime; + QString variable; + SicsConnection *conn; + int pos; + int posc; + time_t from, to; + QValueVector times; + QTimer *timer; +}; diff --git a/seaplot.cpp b/seaplot.cpp new file mode 100644 index 0000000..5056074 --- /dev/null +++ b/seaplot.cpp @@ -0,0 +1,722 @@ +#include "seaplot.h" +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define SEA_RESOLUTION 0.00005 /* chagned from 0.0005 for Amy, July 2020 */ + +LegendButton::LegendButton(QWidget *parent, SeaPlot *plot, SeaCurve *crv) : QwtLegendButton(parent) { + this->plot = plot; + this->crv = crv; + connect(this, SIGNAL(clicked()), this, SLOT(legendClicked())); + connect(&clickTimer, SIGNAL(timeout()), this, SLOT(handleClickTimer())); + label = new FlatButton(crv->title(), parent); + setFixedHeight(label->sizeHint().height()); + setFixedWidth(16); + connect(label, SIGNAL(clicked()), this, SLOT(legendClicked())); + connect(label, SIGNAL(mousePressed()), this, SLOT(mousePressed())); +}; + +void LegendButton::init(SeaPlot *plot, SeaCurve *crv) { + this->plot = plot; + this->crv = crv; +}; + +void LegendButton::mousePressed() { + clickDone = false; + clickTimer.stop(); + clickTimer.start(300); +} + +void LegendButton::handleClickTimer() { + if (!clickDone) { + clickTimer.stop(); + clickDone = true; + longClick(); + } +} + +void LegendButton::legendClicked() { + if (!clickDone) { + clickDone = true; + clickTimer.stop(); + plot->switchBold(crv); + } +}; + +void LegendButton::longClick() { + plot->setOneBold(crv); +}; + + +void LegendButton::mousePressEvent(QMouseEvent *e) { + mousePressed(); + QwtLegendButton::mousePressEvent(e); +} + +void LegendButton::setTitle(const QString &title) { + label->setText(title); +} + +class TimeScaleDraw: public QwtScaleDraw { +public: + TimeScaleDraw(SeaSet *set) : QwtScaleDraw() { + this->set = set; + } + virtual QString label(double v) const { + QTime time; + QwtScaleDiv div = scaleDiv(); + double step = div.majStep(); + int itic0 = lround(div.lBound() / step + 0.5001); + int itic1 = lround(div.hBound() / step - 0.5001); + int itic = lround(v / step);; + int m; + + if (step < 23*3600) { + m = 2; + if (step > 59 && step < 121) { + m = 5; + } else if (lround(step) == 600) { + m = 3; + } + } else { + m = (itic1 - itic0) / 5 + 1; + } + time.setHMS(0,0,0); + itic0 = lround(div.lBound() / step / m + 0.5001); + itic1 = lround(div.hBound() / step / m - 0.5001); + if (itic1 - itic0 > 1 && v > div.hBound() - step) { + return ""; //do not show last label + } else if (itic % m == 0) { + if (step < 3599) { + return time.addSecs(lround(v)).toString("hh:mm"); + } else if (lround(v/3600) % 24 == 0) { + return set->dateLabel(lround(v/(24*3600))); + } else { + return time.addSecs(lround(v)).toString("hh").append("h"); + } + } else { + return ""; + } + } + SeaSet *set; +}; + +class YScaleDraw: public QwtScaleDraw { +public: + YScaleDraw(SeaSet *set) : QwtScaleDraw() { + this->set = set; + } + virtual int maxLabelWidth(const QFontMetrics &fm) const { + return set->leftLabelWidth(QwtScaleDraw::maxLabelWidth(fm)); + } + SeaSet *set; +}; + +class SeaZoomer: public QwtPlotZoomer { +friend class SeaPlot; +public: + SeaZoomer(SeaPlot *p) : + QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft, + QwtPicker::DragSelection, QwtPicker::AlwaysOff, + p->canvas()) { + yAuto = true; + displayMarker = false; + pl = p; + doZoom = false; + }; + SeaPlot *pl; + bool yAuto; + bool setMark; + bool displayMarker; + bool track; + bool doZoom; + int xp, yp; + QTime t; + +protected: + QwtDoubleSize minZoomSize() const { + const QwtScaleDiv *scy = pl->axisScale(QwtPlot::yLeft); + double hei = fabs(scy->hBound() + scy->lBound()) * SEA_RESOLUTION; + return QwtDoubleSize(60., hei); + }; + + void rescale() { + QwtDoubleRect r = zoomRect(); + if (doZoom && r != scaleRect()) { + pl->zoomTo(scaleRect(), r); + } + } + + void enableMarker() { + if (!displayMarker) { + QWidget *parent = parentWidget(); + track = parent->hasMouseTracking(); + parent->setMouseTracking(true); + displayMarker = true; + } + } + + void disableMarker() { + if (displayMarker) { + QWidget *parent = parentWidget(); + displayMarker = false; + parent->setMouseTracking(track); + } + } + + void widgetMousePressEvent(QMouseEvent *e) { + //printf("*** mousePress %d %d\n", e->x(), e->y()); + xp = e->x(); + yp = e->y(); + if (pl->set->markerFix) { + setMark = true; + } else { + setMark = !pl->set->markerWasOn; + } + pl->set->markerFix = false; + t.start(); + QwtPlotZoomer::widgetMousePressEvent(e); + } + + void widgetMouseReleaseEvent(QMouseEvent *e) { + //printf("*** mouseRelease %d %d %d\n", e->x(), e->y(), t.elapsed()); + // mechanism for avoiding unintended zooms: + // do not allow if the rectangle is too small + // or if the mouse was pressed for less than 200 ms + doZoom = abs(e->x() - xp) + abs(e->y() - yp) > 2 && t.elapsed() > 200; + QwtPlotZoomer::widgetMouseReleaseEvent(e); + if (!doZoom && !displayMarker && setMark) { + QwtDoublePoint p = invTransform(e->pos()); + pl->set->setMarker(p.x()); + } + } + + void widgetMouseDoubleClickEvent(QMouseEvent *e) { + //printf("*** mouseDoubleClick %d %d %d\n", e->x(), e->y(), t.elapsed()); + setMark = true; + pl->set->markerFix = true; + QwtDoublePoint at =invTransform(e->pos()); + //pl->set->gotoTime(at.x(), FALSE); + } + + void widgetMouseMoveEvent(QMouseEvent *e) { + if (displayMarker) { + QwtDoublePoint p = invTransform(e->pos()); + if (!pl->set->markerFix) { + pl->set->setMarker(p.x()); + pl->set->gotoTime(p.x(), TRUE); + } + } else { + QwtPlotZoomer::widgetMouseMoveEvent(e); + } + } +}; + +SeaCurve::SeaCurve(QwtPlot *parent, const QString &title) : QwtPlotCurve(parent, title) { + leg = NULL; + iColor = -1; +}; + +void SeaCurve::draw(QPainter *p, const QwtDiMap &xMap, const QwtDiMap &yMap, + int from, int to) { + int i, start; + double ylowlim; + + if (leg->plot->logarithmic) { + ylowlim = 1e-30; + } else { + ylowlim = DATA_UNDEF; + } + start = -1; + if (to < 0) { + to = dataSize() - 1; + if (to < 0) return; + } + for (i=from; i<=to; i++) { + if (i > 0 && y(i) <= ylowlim) { /* undefined value */ + if (start >= 0) { + QwtPlotCurve::draw(p, xMap, yMap, start, i-1); + start = -1; + } + } else { + if (start < 0) start = i; + } + } + if (start >= 0) { + QwtPlotCurve::draw(p, xMap, yMap, start, to); + } +}; + +void SeaCurve::setPen(const QPen &p) { + QwtCurve::setPen(p); + if (leg) leg->setCurvePen(p); +}; + +void SeaCurve::setTitle(const QString &t) { + QwtCurve::setTitle(t); + if (leg) leg->setTitle(t); +}; + +void SeaCurve::yBounds(double from, double to, double &ymin, double &ymax) { + int i, n = dataSize(); + double y1, y2; + double ya, yb, yc, yd, yi; + int k, n1, n2, nn; + bool done; + double ylowlim; + +// implementing a mechanism to ignore spikes: +// after the first pass, the whole range is divided into 3 ranges. +// in the next pass, only the points in the middle range are taken into account +// for minimum and maximum, the others are counted only. When the number of points +// in the upper or in the lower part are less than 2 %, the range is reduced by +// the upper and/or the lower part and a next pass is done + + if (leg->plot->logarithmic) { + ylowlim = 1e-30; + } else { + ylowlim = DATA_UNDEF; + } + y1 = 1e30; + y2 = -1e30; + ya = -1e30; + yb = -1e30; + yc = 1e30; + yd = 1e30; + k = 0; + yi = 1e30; + while (1) { + n1 = 0; + n2 = 0; + nn = 0; + for (i=0; i= from) { + break; + } + } + for (; i to) { + break; + } + yi = y(i); + if (yi > ylowlim) { // and therefore yi != DATA_UNDEF + if (yi < yb) { + n1++; + } else if (yi > yc) { + n2++; + } else { + nn++; + if (yi > y2) { + y2 = yi; + } + if (yi < y1) { + y1 = yi; + } + } + } + } + if (i == n && yi > ylowlim) { + if (yi < y1) y1 = yi; + if (yi > y2) y2 = yi; + } + if (k == 0) { + ya = y1; + yd = y2; + } else { + done = true; + if (n1 * 49 < nn + n2 && y1 >= yb) { + ya = y1; + done = false; + } + if (n2 * 49 < nn + n1 && y2 <= yc) { + yd = y2; + done = false; + } + if (done || k > 20 || ya == yd) { + break; + } + //printf("%s %d %d %d %d %f %f\n", data->label.latin1(), k, n1, n2, nn, ya, yd); + } + k++; + yb = ya + (yd - ya) / 3; + yc = yd - (yd - ya) / 3; + y1 = yc; + y2 = yb; + } + if (yi < 0.9e30) { + ymin = QMIN(ymin, ya); + ymax = QMAX(ymax, yd); + } + return; +} + +void SeaCurve::changeBold() { + int i; + if (iColor == -1) { + i = Convert2ColorIndex("black"); + } else { + i = iColor; + } + leg->plot->set->saveBoldState(data); + if (data->bold) { + setPen(thisPen(i, 0)); + QToolTip::add(leg, "hide curve (long click: show this curve only)"); + QToolTip::add(leg->label, "hide curve (long click: show this curve only)"); + } else { + setPen(thisPen(i, 1)); + QToolTip::add(leg, "show curve (long click: this curve only)"); + QToolTip::add(leg->label, "show curve (long click: this curve only)"); + } +} + +SeaPlot::SeaPlot(QWidget *p, SeaSet *set, const QString &name) : QwtPlot(p, name) { + QTime now(QTime::currentTime()); + QTime midnight(0,0,0); + double dnow; + + //setAutoLegend( TRUE ); + //setLegendPosition( QwtPlot::Left , 0.5); + this->set = set; + + dnow = midnight.secsTo(now); + setAxisScale(QwtPlot::xBottom, dnow-1800, dnow+600, 300.0); + setAxisScale(QwtPlot::yLeft, 0.0, 0.9999); + setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw(set)); + setAxisScaleDraw(QwtPlot::yLeft, new YScaleDraw(set)); + setAxisLabelFormat(QwtPlot::yLeft, 'g', 5); + zoom = new SeaZoomer(this); + zoom->setRubberBand(QwtPlotZoomer::RectRubberBand); + zoom->setRubberBandPen(QPen(Qt::red)); + plotLayout()->setCanvasMargin(0, SeaPlot::yLeft); + plotLayout()->setCanvasMargin(0, SeaPlot::yRight); + canvas()->setPaletteBackgroundColor(QColor(238,238,238)); + setGridPen(QPen(QColor(221, 221, 221), 1)); + logarithmic = false; + removeAll(); +}; + +void SeaPlot::removeAll() { + tag = ""; + removeCurves(); + marker = insertCurve("Marker"); +// setCurvePen(marker, QPen(Qt::white, 3)); + setCurvePen(marker, QPen(Qt::black, 1)); +} + +void SeaPlot::setMarker(double x) { + const QwtScaleDiv *s = axisScale(yLeft); + double xval[2], yval[2], h; + SeaCurve *crv; + QwtPlotCurveIterator itc = curveIterator(); + + if (x == DATA_UNDEF) { + zoom->disableMarker(); + xval[0] = DATA_UNDEF; + xval[1] = DATA_UNDEF; + } else { + zoom->enableMarker(); + xval[0] = x; + xval[1] = x; + } + h = s->hBound() - s->lBound(); + if (logarithmic) { + yval[0] = s->lBound() * 0.5; + } else { + yval[0] = s->lBound() - h; + } + yval[1] = s->hBound() + h; + setCurveData(marker, xval, yval, 2); + + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { + crv->data->showValueAt(x); + } + } +} + +void SeaPlot::refresh() { + const QwtScaleDiv *s = axisScale(xBottom); + replotRange(s->lBound(), s->hBound(), s->majStep()); +}; + +void SeaPlot::autoColors() { + SeaCurve *crv; + QwtPlotCurveIterator itc = curveIterator(); + int i; + + usedColors.fill(false, N_SEACOLORS); + usedColors.setBit(0); // do not use white + + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { + if (crv->iColor >= 0 && crv->iColor < N_SEACOLORS) { + usedColors.setBit(crv->iColor); + } + } + } + + i=0; + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { + if (crv->iColor < 0) { + while (i < N_SEACOLORS && usedColors.at(i)) { + i++; + } + if (i >= N_SEACOLORS) { + crv->iColor = Convert2ColorIndex("black"); + } else { + crv->iColor = i; + usedColors.setBit(i); + crv->changeBold(); + } + } + } + } +} + +LegendButton *SeaPlot::putCurve(SeaData *d, QWidget *parent, LegendButton *legButton) { + SeaCurve *crv = new SeaCurve(this, d->label); + + d->plot = this; + d->key = insertCurve(crv); + + if (legButton) { + legButton->init(this, crv); + } else { + legButton = new LegendButton(parent, this, crv); + } + legButton->setTitle(d->label); + + //QFont myfont(legButton->font()); + //myfont.setPointSize(12); + //legButton->setFont(myfont); + //legButton->setFixedHeight(legButton->fontMetrics().height()+2); + + crv->leg = legButton; + //crv->leg->setTitle(crv->title()); + //crv->leg->setTitle(""); + //if (d->style >= 0 && d->style <= N_SEACOLORS) { + if (d->style >= 0) { + crv->iColor = d->style; + crv->autoColor = false; + } else { + crv->iColor = -1; + crv->autoColor = true; + } + crv->data = d; + crv->changeBold(); + crv->setStyle(QwtCurve::Lines); + d->modified = true; + d->update(); + return legButton; +} + +void SeaPlot::showAgain() { + SeaCurve *crv; + QwtPlotCurveIterator itc = curveIterator(); + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { + crv->data->bold = true; + crv->changeBold(); + } + } + autoColors(); + //refresh(); + set->replotAnyway(); +} + +bool SeaPlot::toggleLinLog() { + logarithmic = ! logarithmic; + if (logarithmic) { + setAxisOptions(QwtPlot::yLeft, QwtAutoScale::Logarithmic); + } else { + setAxisOptions(QwtPlot::yLeft, QwtAutoScale::None); + } + refresh(); + return logarithmic; +} + +void SeaPlot::zoomMax() { + autoZoom(true); + refresh(); +} + +void SeaPlot::autoZoom(bool on) { + zoom->yAuto = on; + setAutoOff(!on); +} + +void SeaPlot::undoZoom(QwtDoubleRect &r) { + setAxisScale(QwtPlot::yLeft, r.y1(), r.y2()); + zoom->setZoomBase(); +} + +void SeaPlot::zoomTo(QwtDoubleRect old, QwtDoubleRect &r) { + set->saveRange(this, old, zoom->yAuto); + autoZoom(false); + setAxisScale(QwtPlot::yLeft, r.y1(), r.y2()); + set->rescale(r.x1(), r.x2()); + zoom->setZoomBase(); +} + +void SeaPlot::zoomOut() { + QwtDoubleRect r = zoom->zoomRect(); + QwtDoubleRect old= zoom->scaleRect(); + + set->saveRange(this, old, zoom->yAuto); + if (logarithmic) { + double h = log(r.y2()) - log(r.y1()); + r.setY2(exp(log(r.y2()) + h / 2)); + r.setY1(exp(log(r.y1()) - h / 2)); + } else { + double h = r.y2() - r.y1(); + r.setY2(r.y2() + h / 2); + r.setY1(r.y1() - h / 2); + } + zoom->setZoomBase(r); + zoom->zoom(0); + autoZoom(false); + refresh(); +} + +void SeaPlot::shiftUp() { + QwtDoubleRect r = zoom->zoomRect(); + double h = (r.y2() - r.y1()) * 0.5; + QwtDoubleRect old= zoom->scaleRect(); + + set->saveRange(this, old, zoom->yAuto); + r.setY2(r.y2() + h); + r.setY1(r.y1() + h); + zoom->setZoomBase(r); + zoom->zoom(r); + zoom->setZoomBase(); + zoom->zoom(0); + autoZoom(false); + refresh(); +} + +void SeaPlot::shiftDown() { + QwtDoubleRect r = zoom->zoomRect(); + double h = (r.y2() - r.y1()) * 0.5; + QwtDoubleRect old= zoom->scaleRect(); + + set->saveRange(this, old, zoom->yAuto); + r.setY2(r.y2() - h); + r.setY1(r.y1() - h); + zoom->setZoomBase(r); + zoom->zoom(r); + zoom->setZoomBase(); + zoom->zoom(0); + autoZoom(false); + refresh(); +} + +int SeaPlot::replotRange(double from, double to, double tick) { + SeaCurve *crv; + QwtDoubleRect r; + double ymin, ymax, yminA, ymaxA, y0, res; + int result; + bool nobold; + + result = 1; + ymin = 1e30; + ymax = -1e30; + yminA = ymin; + ymaxA = ymax; + nobold = true; + QwtPlotCurveIterator itc = curveIterator(); + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { + crv->yBounds(from, to, yminA, ymaxA); + if (crv->data->bold) { + crv->yBounds(from, to, ymin, ymax); + } + } + } + if (ymin > ymax) { + ymin = yminA; + ymax = ymaxA; + if (ymin > ymax) { + ymin = 0; + ymax = 1; + result = 0; + } + } + r = zoom->zoomRect(); + if (zoom->yAuto) { + res = 0.1; + } else { + ymin = r.y1(); + ymax = r.y2(); + res = SEA_RESOLUTION; + } + if (logarithmic) { + if (ymax <= 0) { + ymax = ymaxA; + ymin = yminA; + } else if (ymin <= 0) { + ymin = QMIN(ymax, yminA); + } + y0 = (log(ymin) + log(ymax)) / 2; + if ((log(ymax) - log(ymin)) < res) { + ymin = exp(y0 - res * 0.4999); + ymax = exp(y0 + res * 0.5); + } + } else { + y0 = (ymin + ymax) / 2; + if ((ymax - ymin) < y0 * res) { + ymin = y0 * (1 - res * 0.4999); + ymax = y0 * (1 + res * 0.5); + } + } + setAxisMaxMinor(QwtPlot::xBottom, 0); + setAxisScale(QwtPlot::xBottom, from, to, tick); + setAxisScale(QwtPlot::yLeft, ymin, ymax); + replot(); + if (zoom->zoomRectIndex() == 0) { + zoom->setZoomBase(); + } + return result; +}; + +int SeaPlot::replotRange() { + const QwtScaleDiv *scx = axisScale(QwtPlot::xBottom); + return replotRange(scx->lBound(), scx->hBound(), scx->majStep()); +}; + +void SeaPlot::switchBold(SeaCurve *crv) { + crv->data->bold = !crv->data->bold; + crv->changeBold(); + crv->leg->update(); + set->replotAnyway(); + set->updateCurveList(); +} + +void SeaPlot::setOneBold(SeaCurve *selectedCrv) { + QwtPlotCurveIterator itc = curveIterator(); + SeaCurve *crv; + + for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) { + crv = dynamic_cast(c); + if (crv) { // omit Marker + crv->data->bold = crv == selectedCrv; + crv->changeBold(); + crv->leg->update(); + } + } + autoZoom(true); + set->replotAnyway(); + set->updateCurveList(); +} + diff --git a/seaplot.h b/seaplot.h new file mode 100644 index 0000000..6a9f4b0 --- /dev/null +++ b/seaplot.h @@ -0,0 +1,97 @@ +#ifndef SEAPLOT_H +#define SEAPLOT_H + +#include +#include +#include +#include +#include "seaset.h" + +class SeaSet; +class SeaData; +class SeaZoomer; +class SeaCurve; +class LegendButton; +class QGrid; +class QLineEdit; + +class SeaPlot : public QwtPlot +{ + Q_OBJECT + +public: + SeaPlot(QWidget *p, SeaSet *set, const QString &name); + void removeAll(); + LegendButton *putCurve(SeaData *d, QWidget *parent, LegendButton *legButton = NULL); + int replotRange(double from, double to, double tick); + int replotRange(); + void refresh(); + void switchBold(SeaCurve *crv); + void setOneBold(SeaCurve *crv); + void showAgain(); + SeaSet *set; + QString tag; + void autoZoom(bool on); + QwtDoubleRect undoRect; + void zoomTo(QwtDoubleRect old, QwtDoubleRect &r); + void undoZoom(QwtDoubleRect &r); + bool toggleLinLog(); + void autoColors(); + long marker; + void setMarker(double x); + bool logarithmic; + QBitArray usedColors; +signals: + void setAutoOff(bool off); +private slots: + void zoomOut(); + void zoomMax(); + void shiftUp(); + void shiftDown(); +private: + SeaZoomer *zoom; + double y1, y2; +}; + +class FlatButton; + +class LegendButton : public QwtLegendButton { + Q_OBJECT +public: + LegendButton(QWidget *parent, SeaPlot *plot, SeaCurve *crv); + void init(SeaPlot *plot, SeaCurve *crv); + void setTitle(const QString &title); + FlatButton *label; + QLabel *valueLabel; + SeaPlot *plot; + QTimer clickTimer; +protected: + void mousePressEvent(QMouseEvent *e); +public slots: + void legendClicked(); + void longClick(); + void handleClickTimer(); + void mousePressed(); +private: + SeaCurve *crv; + bool clickDone; +}; + + +class SeaCurve : public QwtPlotCurve { +public: + SeaCurve(QwtPlot *parent, const QString &title); + void draw(QPainter *p, const QwtDiMap &xMap, const QwtDiMap &yMap, int from, int to); + void setPen(const QPen &p); + void setTitle(const QString &t); + void yBounds(double from, double to, double &ymin, double &ymax); + void changeBold(); + + SeaData *data; + bool bold; + int iColor; + bool autoColor; + LegendButton *leg; +}; + +#endif diff --git a/seaset.cpp b/seaset.cpp new file mode 100644 index 0000000..e1c6231 --- /dev/null +++ b/seaset.cpp @@ -0,0 +1,1901 @@ +#include "seaset.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(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(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 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 seaStart[64]; + 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(seaStart, sizeof seaStart, "ssh %s@%s sea start", instr, host); + } else { + snprintf(seaStart, sizeof seaStart, "ssh l_samenv@%s sea start %s", host, instr); + } + sc->startServer = seaStart; + } + */ +} + +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(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(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(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(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(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 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); +} diff --git a/seaset.h b/seaset.h new file mode 100644 index 0000000..6c8a244 --- /dev/null +++ b/seaset.h @@ -0,0 +1,228 @@ +#ifndef SEASET_H +#define SEASET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sicsconn.h" +#include "seaplot.h" + +#define SEA_MAX_RANGE (240*3600-60) + +// values lower or equal than this value are considered as undefined +#define DATA_UNDEF (-1e33) + +class SeaPlot; +class SeaSet; +class SeaRow; +class QGrid; +class LegendButton; + +class SeaData { +public: + SeaData(const QString &name, const QString &label, const QString &plotName, int color=0); + ~SeaData(); + int addValue(double xx, QString value); + bool update(); + void init(time_t step, int siz); + void showValueAt(double at); + bool isActive(); + + QString name; + QString label; + QString plotName; + int size; + double *x; // x[0..size_m-1] + double *y; // y[0..size_m-1] + double step_m; + double lastx; + int size_m; + bool clr; + bool dirty; + bool modified; + bool bold; + bool exact; + double period; // update period + long key; + SeaPlot *plot; + SeaRow *row; + QLabel *shownValue; + int decipos; + long style; +}; + +typedef enum {hiddenRow, shownRow, newRow} RowState; + +class SeaRow: public QScrollView { + Q_OBJECT +public: + SeaRow(QWidget *parent); + QHBox *hBox; +// QScrollView *left; + SeaPlot *plot; + QGrid *leg; + QScrollView *legScroll; + QVBox *right; + SeaSet *set; + QPtrList legendList; + uint usedLegend; + QHBox *bBox; + RowState state; + QPushButton *lBut; + QPixmap pmLin, pmLog; + QLabel *tagLabel; + QLabel *timeLabel; + void setInnerHeights(bool isfirstrow, int rowH); + void setTag(QString tag); + + virtual void resizeEvent(QResizeEvent *e); + virtual QSize minimumSizeHint() const; +public slots: + void hideRow(); + void linLog(); +}; + +typedef enum {liveOff, liveAuto, liveOn} LiveState; + +typedef struct GetArg { + bool all, append; + time_t from, to; +} GetArg; + +typedef struct AutoArg { + QString vars; +} AutoArg; + +class SeaSet : public QObject { + Q_OBJECT +friend class SeaPlot; +friend class SeaRow; +public: + SeaSet( QSplitter *parent, long range, const char *name); + SeaRow *newPlot(const char *name = ""); + int leftLabelWidth(int in); + void insertData(const QString &cName, const QString &label, const QString &pName, const int color); + void clrDataList(); + void finishDataList(); + QString dateLabel(long dayOffset); + QPtrList dataList; + void readNewReplot(); + void rescaleRange(time_t range); + void rescale(time_t from, time_t to); + void rescale(double from, double to); + void calcBase(time_t from); + void saveRange(); + void saveRange(SeaPlot *plot, QwtDoubleRect &old, bool yAuto); + void hideRow(); + void adjustSizes(); + int sicsCommand(QString &cmd, int graph=0); // blocking + int sendSicsCommand(QString &cmd, int graph=0); // non-blocking + void setMarker(double x); + void setTimeLabel(double x); + void runningMarkerOff(bool beforeEvent); + void saveRowHeight(SeaRow *row, int height); + int getRowHeight(SeaRow *row); + void saveBoldState(SeaData *data); + void getBoldState(SeaData *data); + void setFirstRow(); + void gotoTime(double at, bool silent); + + SicsConnection *sc, *uc, *gc, *ec; + int legendWidth, actLegendWidth; + QString hostport; + time_t startRange, endRange; + time_t base; + double markerPos; + bool markerWasOn; + bool markerFix; + SeaRow *firstRow; + double lastTime; + +public slots: + void autoCurves(const char *vars = 0); + void setHost(const QString &hostport); + void setTimeRange(time_t start, long seconds); + void showAll(); + void liveUpdate(); + //void handleSics(const char *line); + void shiftLeft(); + void zoomOutX(); + void shiftRight(); + void undoZoom(); + void setLive(bool on); + void restart(const char *vars); + void replot(); + void replotAnyway(); + void asyncHandler(); + void setMarker(time_t t); + +signals: + void putCurve(const QString &name, const QString &plot); + void undoEnabled(bool on); + void updateCurveList(); + void gotoTime(time_t from, time_t at, time_t to, bool silent); + +private: + void setBase(); + void setDate(); + void getCurves(bool all, time_t from, time_t to); + SeaData *findData(const QString &name); + void calcLeftLabelWidth(); + + bool autoCurvesP1(); + void autoCurvesP2(); + int getCurvesP1(); + void getCurvesP2(); + + QSplitter *split; + QLayout *layout; + time_t lastRead; // time of last read on server + bool compressed; + QPtrList rows; + int nplots; + QwtPlotZoomer *zoom; + int labelWidth; + int labelMode; + bool reread; + int refresh; + LiveState live; + time_t liveTime; + SeaPlot *undoPlot; + QwtDoubleRect undoRect; + time_t undoStart, undoEnd; + bool undoAuto; + bool initSizes; + int xSize; + time_t curveStep; + // asynchronous stuff: + enum {async_idle, async_auto, async_get} async_mode; + AutoArg autoArg, autoArgDo; + GetArg getArg, getArgDo; + bool autoDo, getDo, getNewData; + QTimer *asyncTimer; + QTime meas; + QTime tmot; + bool showPlot; + QDict rowHeightDict; + QDict boldDict; +}; + +void setprintit(int p); +int printit(void); + +#endif diff --git a/settings.cpp b/settings.cpp new file mode 100644 index 0000000..4f05e2d --- /dev/null +++ b/settings.cpp @@ -0,0 +1,339 @@ +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +void SetLength(QLineEdit *lineEdit, int min, int max) { + lineEdit->setMaxLength(max); + int w0 = lineEdit->fontMetrics().width('0'); + int w = lineEdit->frameWidth() * 2 + 6; + lineEdit->setMinimumWidth(w + w0 * min); + lineEdit->setMaximumWidth(w + w0 * max); +} + +Settings::Settings(QWidget* parent) + : QWidget(parent) +{ + QHBoxLayout *buttonLayout; + QVBoxLayout *vbox; + QString str; + time_t now; + struct tm date; + + vbox = new QVBoxLayout(this, 0, 0, "vBox"); + + setName("Settings"); + + table = new QScrollView(this, "table"); + form = new QWidget(table->viewport(), "form"); + formVL = new QVBoxLayout(form, 0, 0, "formVL"); + formLayout = new QGridLayout(0, 3, 3, 6, 3); + formVL->addLayout(formLayout); + formVL->addStretch(1); + /* + formBox = new QHBox(table->viewport(), "formBox"); + form = new QGrid(4, formBox, "form"); + */ + table->addChild(form); + table->setHScrollBarMode(QScrollView::AlwaysOff); + + buttonLayout = new QHBoxLayout(0, 3, 3, "buttonLayout"); + + newButton = new QPushButton(this, "newButton"); + newButton->setText("New Curve"); + buttonLayout->addWidget(newButton); + + /* + autoButton = new QPushButton(this, "autoButton"); + autoButton->setText("Auto"); + buttonLayout->addWidget(autoButton); + */ + + cancelButton = new QPushButton(this, "cancelButton"); + cancelButton->setText("Cancel"); + buttonLayout->addWidget(cancelButton); + + okButton = new QPushButton(this, "okButton"); + okButton->setText("OK"); + buttonLayout->addWidget(okButton); + okButton->setDefault(true); + + buttonLayout->addStretch(1); + time(&now); + date = *localtime(&now); + + buttonLayout->addWidget(new QLabel("# Days", this)); + rLE = new QLineEdit(this); + SetLength(rLE, 2, 2); + rLE->setText(str); + rLE->setValidator(new QIntValidator(0, 99, this)); + buttonLayout->addWidget(rLE); + + buttonLayout->addWidget(new QLabel("starting from", this)); + dLE = new QLineEdit(this); + SetLength(dLE, 2, 2); + str.sprintf("%d", date.tm_mday); + dLE->setText(str); + dLE->setValidator(new QIntValidator(0, 31, this)); + buttonLayout->addWidget(dLE); + + buttonLayout->addWidget(new QLabel(".", this)); + mLE = new QLineEdit(this); + SetLength(mLE, 2, 2); + str.sprintf("%d", date.tm_mon+1); + mLE->setText(str); + mLE->setValidator(new QIntValidator(1, 12, this)); + buttonLayout->addWidget(mLE); + + buttonLayout->addWidget(new QLabel(".", this)); + yLE = new QLineEdit(this); + SetLength(yLE, 4, 4); + str.sprintf("%d", date.tm_year+1900); + yLE->setText(str); + yLE->setValidator(new QIntValidator(2001, date.tm_year+1900, this)); + buttonLayout->addWidget(yLE); + + hrLE = new QLineEdit(this); + SetLength(hrLE, 5, 5); + hrLE->setText("00:00"); + buttonLayout->addWidget(hrLE); + + vbox->addLayout(buttonLayout); + vbox->addWidget(table, 1); + //vbox->addStretch(1); + + formLayout->addWidget(new QLabel("Curve", form), 0, 0); + formLayout->addWidget(new QLabel("Label", form), 0, 1); + formLayout->addWidget(new QLabel("Unit", form), 0, 2); + formLayout->addWidget(new QLabel("Color", form), 0, 3); + + /* + new QLabel("Curve", form); + new QLabel("Label", form); + new QLabel("Plot", form); + new QLabel("Color", form); + */ + + row = 1; + + connect(newButton, SIGNAL(clicked()), + this, SLOT(addEmpty())); + connect(okButton, SIGNAL(clicked()), + this, SLOT(ok())); + /* + connect(autoButton, SIGNAL(clicked()), + this, SLOT(autoCurves())); + */ + connect(cancelButton, SIGNAL(clicked()), + this, SLOT(cancel())); + + connect(rLE, SIGNAL(textChanged(const QString &)), + this, SLOT(checkRange(const QString &))); + /* + connect(dLE, SIGNAL(textChanged(const QString &)), + this, SLOT(calcDate(const QString &))); + connect(mLE, SIGNAL(textChanged(const QString &)), + this, SLOT(calcDate(const QString &))); + connect(yLE, SIGNAL(textChanged(const QString &)), + this, SLOT(calcDate(const QString &))); + */ + clearWState(WState_Polished); +} + + +void Settings::updateHeight() { + form->setMinimumHeight(table->visibleHeight()); +} + +void Settings::resizeEvent(QResizeEvent *e) { + updateHeight(); + form->setFixedWidth(table->visibleWidth()); + QWidget::resizeEvent(e); +} + +void Settings::showIt(bool yes) { + if (yes) { + clear(); + putCurves(&set->dataList); + show(); + } else { + hide(); + } +} + +#define YEAR (24*3600*360) + +void Settings::checkRange(const QString &str0) { + if (str0.compare("") == 0) { + dLE->setEnabled(false); + mLE->setEnabled(false); + yLE->setEnabled(false); + hrLE->setEnabled(false); + } else { + dLE->setEnabled(true); + mLE->setEnabled(true); + yLE->setEnabled(true); + hrLE->setEnabled(true); + } +} + +void Settings::putCurve(const QString &name, const QString &label, + const QString &plotName, long colorIndex) { + QLineEdit *n, *p; + ColorMenu *cm; + unsigned int idx; + QString colortext; + + idx = row - 1; + + if (idx < names.count()) { + n = names.at(idx); + } else { + n = new QLineEdit(form); + SetLength(n, 8, 64); + formLayout->addWidget(n, row, 0); + names.append(n); + } + n->setText(name); + n->show(); + + if (idx < labels.count()) { + p = labels.at(idx); + } else { + p = new QLineEdit(form); + SetLength(p, 8, 64); + formLayout->addWidget(p, row, 1); + labels.append(p); + } + p->setText(label); + p->show(); + + if (idx < plots.count()) { + p = plots.at(idx); + } else { + p = new QLineEdit(form); + SetLength(p, 4, 8); + formLayout->addWidget(p, row, 2); + plots.append(p); + } + p->setText(plotName); + p->show(); + + Convert2ColorName(colorIndex, colortext); + if (idx < colors.count()) { + cm = colors.at(idx); + cm->setCurrentItem(0); + cm->setCurrentText(colortext); + } else { + cm = new ColorMenu(colortext, form); + cm->setLength(12, 32); + formLayout->addWidget(cm, row, 3); + colors.append(cm); + } + cm->show(); + + row++; +} + + +void Settings::putCurves(QPtrList *dataList) { + SeaData *c; + for (c = dataList->first(); c != 0; c = dataList->next()) { + putCurve(c->name, c->label, c->plotName, c->style); + } + updateHeight(); +} + +void Settings::addEmpty() { + putCurve("","","",-1); + updateHeight(); +/* + names.last()->show(); + labels.last()->show(); + plots.last()->show(); + colors.last()->show(); +*/ +} + +void Settings::apply() { + int i, n; + long days; + time_t t; + struct tm date; + QStringList hr_min; + + if (rLE->text().compare("") != 0) { + days = rLE->text().toLong(); + + time(&t); + date = *localtime(&t); + date.tm_mday = dLE->text().toInt(); + date.tm_mon = mLE->text().toInt() - 1; + date.tm_year = yLE->text().toInt() - 1900; + hr_min = QStringList::split(":", hrLE->text()); + date.tm_hour = (int)hr_min[0].toFloat(); + if (hr_min.size() > 1) { + date.tm_min = (int)hr_min[1].toFloat(); + } else { + date.tm_min = 0; + } + date.tm_sec = 0; + date.tm_isdst = -1; + + set->setTimeRange(mktime(&date), 24*3600*QMAX(1, days)); + set->autoCurves(); + } else { + set->clrDataList(); + n = names.count(); + for (i = 0; i < n; i++) { + //printf("%s\n", names.at(i)->text().latin1()); + set->insertData(names.at(i)->text(), labels.at(i)->text(), plots.at(i)->text(), colors.at(i)->getColorIndex()); + } + set->finishDataList(); + } +} + +void Settings::ok() { + apply(); + leaveSettings(); +} + +void Settings::cancel() { + leaveSettings(); +} + +void Settings::clear() { + int i, n; + n = names.count(); + for (i = 0; i < n; i++) { + names.at(i)->setText(""); + plots.at(i)->setText(""); + labels.at(i)->setText(""); + colors.at(i)->setValue(0); + } + row = 1; + rLE->setText(""); +} + +void Settings::autoCurves() { + clear(); + set->autoCurves(); + putCurves(&set->dataList); +} + + diff --git a/settings.h b/settings.h new file mode 100644 index 0000000..eee4fb5 --- /dev/null +++ b/settings.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include "seaset.h" +// for ColorMenu: +#include "device.h" + +class Settings : public QWidget +{ + Q_OBJECT + +public: + Settings(QWidget* parent = 0); + //~Settings(); + + void clear(); + void putCurves(QPtrList *curves); + void apply(); + SeaSet *set; + QPushButton *okButton, *cancelButton, *newButton; + /* QPushButton *autoButton; */ + QLineEdit *rLE, *dLE, *mLE, *yLE, *hrLE; +signals: + void leaveSettings(); +public slots: + void addEmpty(); + void ok(); + void cancel(); + void autoCurves(); + void checkRange(const QString &str); + void showIt(bool show); + void resizeEvent(QResizeEvent *e); +protected: + void putCurve(const QString &name, const QString &label, const QString &plotName, long colorIndex); + void updateHeight(); + QScrollView *table; + QWidget *form; + QVBoxLayout *formVL; + QGridLayout *formLayout; + QPtrList names; + QPtrList labels; + QPtrList plots; + QPtrList colors; + int row; + +}; + + diff --git a/sicsconn.cpp b/sicsconn.cpp new file mode 100644 index 0000000..43b5f0c --- /dev/null +++ b/sicsconn.cpp @@ -0,0 +1,601 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sicsconn.h" +#include "instr_hosts.h" +#include +#include +#include +#include +#include + + +int printit(void); + +int uselect(int nfds, + fd_set * readfds, fd_set * writefds, fd_set * exceptfds, + struct timeval *timeout) +{ + + sigset_t sigmask; + struct timespec tmo, *tmoPtr; + int result; + + sigfillset(&sigmask); + if (timeout) { + tmo.tv_sec = timeout->tv_sec; + tmo.tv_nsec = timeout->tv_usec * 1000; + tmoPtr = &tmo; + } else { + tmoPtr = NULL; + } + result = pselect(nfds, readfds, writefds, exceptfds, tmoPtr, &sigmask); + return result; +} + +static int sigpipe_ignored = 0; + +int SicsSockAdr(struct sockaddr_in *sockaddr, const char *hostport, + int defaultPort, const char *server) { + // create sockadr from hostport (hostname and port number separated with colon) + // hostport may also be an instrument name, in this case server has to be defined + + struct hostent *hostent; /* Host database entry */ + struct in_addr addr; /* For 64/32 bit madness */ + char *p; + char host[128], instr[32]; + int port; + int l; + + (void) memset((char *) sockaddr, '\0', sizeof(struct sockaddr_in)); + if (hostport == NULL) hostport=""; + p = strchr((char *)hostport, ':'); + if (p != NULL) { + l = p - hostport; + port = atoi(p+1); + } else { + InstrHost((char *)server, (char *)hostport, instr, sizeof instr, + host, sizeof host, &port); + if (port == 0) { + port = atoi(hostport); + if (port != 0) { + l = 0; + } else { + l = strlen(hostport); + } + } else { + l = -1; + } + } + if (l>= 0) { + if (l >= (int)sizeof host) return -1; + strncpy(host, hostport, l); + host[l]='\0'; + } + if (port == 0) port = defaultPort; + if (port == 0) return -1; + + sockaddr->sin_family = AF_INET; + sockaddr->sin_port = htons((unsigned short) (port & 0xFFFF)); + + if (host[0]=='\0') { + addr.s_addr = INADDR_ANY; + } else { + hostent = gethostbyname(host); + if (hostent != NULL) { + memcpy((char *) &addr, + (char *) hostent->h_addr_list[0], (size_t) hostent->h_length); + } else { + addr.s_addr = inet_addr(host); + if (addr.s_addr == (unsigned long)-1) { + return -1; /* error */ + } + } + } + /* + * There is a rumor that this assignment may require care on + * some 64 bit machines. + */ + sockaddr->sin_addr.s_addr = addr.s_addr; + return 0; +} + +int SicsConnect(const char *hostport, const char *server, bool async) { + struct sockaddr_in sadr; + int iret, fd; + int oldopts; + + iret = SicsSockAdr(&sadr, hostport, 8641, server); + if (iret < 0) return -1; + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) return -1; + if (async) { + oldopts = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, oldopts | O_NONBLOCK); + } + iret = connect(fd, (struct sockaddr *)&sadr, sizeof(sadr)); + if (iret < 0) { + switch (errno) { + case EINPROGRESS: + case EALREADY: + case EISCONN: + return fd; + } + return -1; + } + return fd; +} + +int SicsConnectSuccess(int fd) +{ + // returns 1 on success, 0 when pending, -1 on error + fd_set wmask, rmask; + struct timeval tmo = { 0, 0 }; + int oldopts; + int ret; + + oldopts = fcntl(fd, F_GETFL, 0); + assert(oldopts | O_NONBLOCK); /* fd must be in non-blocking mode */ + + FD_ZERO(&wmask); + FD_ZERO(&rmask); + FD_SET(fd, &wmask); + FD_SET(fd, &rmask); + ret = uselect(fd + 1, &rmask, &wmask, NULL, &tmo); + if (ret > 0) { + if (FD_ISSET(fd, &rmask)) { /* there may already be data for read */ + if (recv(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */ + ret = -1; /* first recv failed */ + } + } else { + if (FD_ISSET(fd,&wmask)) { + if (send(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */ + ret = -2; /* first send failed */ + } + } + } + } + fcntl(fd, F_SETFL, oldopts & ~O_NONBLOCK); /* reset to blocking mode */ + return ret; +} + +SicsConnection::SicsConnection(QObject *parent) : QObject(parent) { + hostport = NULL; + userpass = NULL; + server = NULL; + state = disconnected; + bufline = ""; + bufstate = buf_got_line; + connect_state = connect_start; + command2send = ""; + tmo = 0; + if (!sigpipe_ignored) { + signal (SIGPIPE, SIG_IGN); + sigpipe_ignored = 1; + } + rdbuffer = ""; + progressPos = 0; + rdpos = 0; + debug = 0; +} + +//SicsConnection::SicsConnection(SicsConnection *copy) { +// hostport = strdup(copy->hostport); +// userpass = NULL; +// state = disconnected; +//} + +SicsConnection::~SicsConnection() { + if (state != disconnected) { + close(fd); + fd = -1; + } + if (hostport) free(hostport); + if (userpass) free(userpass); + if (server) free(server); +} + +void SicsConnection::setHost(const char *hostport, const char *server) { + if (this->hostport != NULL) free(this->hostport); + if (hostport != NULL) { + this->hostport = strdup(hostport); + } else { + this->hostport = NULL; + } + if (this->server != NULL) free(this->server); + this->server = strdup(server); +} + +void SicsConnection::setUser(const char *userpass) { + if (this->userpass != NULL) free(this->userpass); + if (userpass != NULL) { + this->userpass = strdup(userpass); + } else { + this->userpass = NULL; + } +} + +int SicsConnection::handleBuffer(void) { + return handleBuffer(0); +} + +int SicsConnection::handleBuffer(int tmo) { + fd_set mask; + struct timeval tmov; + int iret = 1; + int pos; + char ch; + char line[128]; + + switch (connect_state) { + case connect_start: + fd = SicsConnect(hostport, server, true); + if (fd < 0) { + connect_state = connect_error; // what else? + return -1; + } + connect_state = connect_wait; + return 0; + case connect_wait: + iret = SicsConnectSuccess(fd); + if (iret <= 0) { + if (startServer) { + iret = system(startServer); + if (iret) { + printf("can not connect to sea and failed to start the sea server\n"); + printf("\n"); + pos = strcspn(hostport, ":"); + printf("please execute 'sea start' from the instrument account of %.*s\n", pos, hostport); + printf("\n"); + exit(1); + } + } else { + iret = 1; + } + startServer = NULL; + return iret; + } + connect_state = connect_login; + return 0; + case connect_login: + if (userpass == NULL) { + snprintf(line, sizeof line, "Spy 007\n"); + } else { + snprintf(line, sizeof line, "%s", userpass); + } + bufstate = buf_sent_line; + send(fd, line, strlen(line), 0); + connect_state = connect_waitlogin; +// if (strcmp(server, "sea") == 0 && userpass != NULL) { +// printf("*** send login (%s %s)\n", server, line); +// } + return 0; + case connect_error: + return -2; + default: break; + } + + FD_ZERO(&mask); + while (bufstate > buf_got_finished) { + do { + FD_SET(fd, &mask); + tmov.tv_sec = tmo; + tmov.tv_usec = 0; + iret = select(fd+1, &mask, NULL, NULL, &tmov); + if (iret < 0) { + perror("select"); + close(fd); + fd = -1; + connect_state = connect_error; + state = disconnected; + return -2; + } else if (iret == 0) { + return -1; /* no more data */ + } + iret = recv(fd, &ch, 1, 0); + if (iret <= 0) { + close(fd); + fd = -1; + connect_state = connect_error; + state = disconnected; + if (printit()) printf("zero on receive -> disconnected\n"); +// printf("*** disconnected (zero on receive)\n"); + return -2; + } + bufline.append(ch); + } while (ch != '\n'); + iret = 0; + rdbuffer += bufline; + if (debug) { + // printf("%s bufline{%s}\n", debug, bufline.latin1()); + } + if ((int)rdbuffer.length() >= progressPos + 1000) { + progress(rdbuffer.length() - progressPos); + progressPos = rdbuffer.length(); + } +// if (strcmp(server, "sea") == 0 && userpass != NULL) { +// printf("*** bufline %s", bufline.latin1()); +// } + if (bufstate == buf_got_start && bufline.startsWith("TRANSACTIONFINISHED")) { + bufstate = buf_got_finished; + iret = 1; + } + if (bufstate == buf_sent_transact && bufline.startsWith("TRANSACTIONSTART")) { + bufstate = buf_got_start; + } + if (bufstate == buf_sent_line) { + bufstate = buf_got_line; + if (connect_state == connect_waitlogin) { + if (debug) { + printf("connectlogin %s\n", bufline.latin1()); + } + if (bufline.startsWith("Login OK\n")) { + connect_state = connect_done; + state = connected; + if (command2send != "") { + sendThis(command2send.latin1()); + command2send = ""; + if (state == disconnected) { + return -1; + } + bufstate = buf_sent_transact; + } + } else { + bufstate = buf_sent_line; + } + } else { + iret = 1; + } + } + bufline = ""; + } + + return iret; +} + +int SicsConnection::getLine(QString &line) { + QString qline; + int pos, len; + + if (state == disconnected) { + return -1; + } + handleBuffer(0); + if (rdpos >= (int)rdbuffer.length() && tmo > 0) { + if (handleBuffer(tmo) < 0) { + printf("timeout %d\n", tmo); + } + } + if (rdpos < (long)rdbuffer.length()) { + pos = rdbuffer.find('\n', rdpos); + if (pos < rdpos) { // lf not found, return up to end of buffer (is this correct?) + return 0; /***/ + len = rdbuffer.length() - rdpos; + line = rdbuffer.mid(rdpos); + progressPos -= rdbuffer.length(); + rdbuffer=""; + rdpos = 0; + } else { + len = pos - rdpos; + line = rdbuffer.mid(rdpos,len); + rdpos = pos + 1; + //rdbuffer.remove(0, pos + 1); + } + if (debug) { + //printf("%s getLine{%s}{%s}\n", debug, line.latin1(), rdbuffer.mid(rdpos,9).latin1()); + printf("%s getLine{%s}\n", debug, line.latin1()); + } + if (line.startsWith("TRANSACTIONFINISHED")) { + if (state == replying) { + state = connected; + return 0; + } + } + return len; + } else { + if (debug) { + // printf("%s error\n", debug); + } + return -1; + } + return 0; +} + +void SicsConnection::swallow() { + QString line; + bool done; + + if (debug) { + printf("%s swallow start\n", debug); + } + while (state == replying) { + if (getLine(line) > 0) { + done = false; + handle(line, &done); + if (!done) { + //printf("*** swallow {%s}\n", line); + } + } + } + if (debug) { + printf("%s swallow end\n", debug); + } +} + +int SicsConnection::sendThis(const char *str) { + int l, iret; + if (state != disconnected) { + l = strlen(str); +// if (strcmp(server, "sea") == 0 && userpass != NULL) { +// printf("*** send {%s}\n", str); +// } + iret = send(fd, str, l, 0); + if (iret != l) { + printf("sent %d of %d, disconnect\n", iret, l); + close(fd); + fd = -1; + state = disconnected; + return -1; + } + return 1; + } + return -1; +} + +void SicsConnection::reconnect(void) { + if (state != disconnected) { + close(fd); + fd = -1; + state = disconnected; + } + connect_state = connect_start; + command2send.sprintf("fulltransact config listen 1\n"); +} + +void SicsConnection::disconnect(void) { + close(fd); + fd = -1; + state = disconnected; +} + +int SicsConnection::sendCommand(const char *cmd) { + char *nl; + + if (debug) { + printf("%s swallow before %s\n", debug, cmd); + } + swallow(); + if (debug) { + printf("%s sendCommand %s\n", debug, cmd); + } + nl = strrchr((char *)cmd, '\n'); /* check if we have to add a final line break */ + if (nl == NULL || nl[1] != '\0') { + command2send.sprintf("fulltransact %s\n", cmd); + } else { + command2send.sprintf("fulltransact %s", cmd); + } + + if (state == disconnected) { +// printf("*** reconnect!\n"); + connect_state = connect_start; + } else { + sendThis(command2send.latin1()); + bufstate = buf_sent_transact; + command2send = ""; + if (state == disconnected) { + return -1; + } + } + return 1; +} + +int SicsConnection::getResponse() { // get BEGINNING of response + int iret; + QString line; + + state = connected; + tmo = 10; + while (1) { + iret = getLine(line); + if (iret <= 0) { + if (iret == -2) { + if (printit()) printf("timeout on sea command\n"); + } + //printf("error in getLine %d\n", iret); + if (debug) { + printf("%s error in getResponse {%s}\n", debug, rdbuffer.latin1() + rdpos); + } + return iret; + } + if (line.startsWith("TRANSACTIONSTART")) break; + if (debug) { + printf("%s getResponse handle %s\n", debug, line.latin1()); + } + handle(line.latin1()); + }; + state = replying; + if (debug) { + printf("%s gotResponse\n", debug); + } + return 1; +} + +int SicsConnection::command(const char *cmd) { + int iret; + int cnt, tries; + + tries = 3; + do { + cnt = 5; + while (cnt > 0 && (connect_state != connect_done || bufstate > buf_got_finished)) { + handleBuffer(1); + cnt--; + } + iret = sendCommand(cmd); + handleBuffer(5); + iret = getResponse(); + tries--; + } while (iret < 0 && tries > 0); + return iret; +} + +int SicsConnection::handleMessages(int tmoArg) { + int iret; + QString line; + bool bs=false; + + tmo = tmoArg; + if (bufstate <= buf_got_finished) { + bufstate = buf_sent_line; + bs = true; + } + iret = getLine(line); + tmo = 0; + while (iret >= 0) { + if (iret > 0) { + handle(line.latin1()); + } + iret = getLine(line); + } + if (bs && bufstate == buf_sent_line) { + bufstate = buf_got_line; + } + return iret; +} + +#ifdef TESTSC + +#include + +int main(void) { + SicsConnection *sc; + char line[256]; + char *lin; + int iret; + + sc = SicsNewConnection("lnsl15", 13006); + if (sc == NULL) return -1; + while (1) { + printf("> "); + lin = fgets(line, sizeof line, stdin); + if (lin == NULL) return 1; + iret = SicsCommand(sc, line); + if (iret < 0) return 2; + while (1) { + iret = getLine(line); + if (iret == 0) break; + if (iret < 0) return 3; + if (iret > 0) { + printf("(%s)\n", line.latin1()); + } + } + } +} + +#endif diff --git a/sicsconn.h b/sicsconn.h new file mode 100644 index 0000000..e7fca9d --- /dev/null +++ b/sicsconn.h @@ -0,0 +1,72 @@ +#ifndef SICSCONN_H +#define SICSCONN_H + +#include +#include + +typedef enum { disconnected, connected, replying } ConnState; + +class SicsConnection : public QObject { + Q_OBJECT +public: + SicsConnection(QObject *parent); + ~SicsConnection(); + void setHost(const char *hostport, const char *server); + void setUser(const char *userpass); + int command(const char *cmd); + /* send a command (and connect before, if necessary) + return values: 1 on success + -1 on error (no connection) + 0 on empty response (should not happen) + */ + + int sendCommand(const char *cmd); + // send, but do not wait + + int getResponse(); + // get beginning of response + + int getLine(QString &line); + /* get answer from command + the return value is: + - the number of characters read (excluding line break, but at least 1) + - 0 when the response is at its end + - negative on error + */ + int handleMessages(int tmo); + /* handle pending messages */ + + int handleBuffer(int tmo); + void swallow(); + char *debug; + ConnState state; + char *startServer = NULL; + +signals: + void handle(const char *line, bool *done = NULL); + void reconnected(void); + void progress(int bytes); + +public slots: + void reconnect(void); + void disconnect(void); + int handleBuffer(void); + +private: + int sendThis(const char *cmd); + int fd; + char *hostport; + char *userpass; + char *server; + int tmo; + enum {buf_got_line, buf_got_finished, buf_sent_line, buf_got_start, buf_sent_transact} bufstate; + enum {connect_done, connect_start, connect_wait, connect_login, connect_waitlogin, connect_error} connect_state; + QString command2send; + QString bufline; + QString rdbuffer; + int rdpos; + int progressPos; + long tim; +}; + +#endif diff --git a/tab.cpp b/tab.cpp new file mode 100644 index 0000000..415fc25 --- /dev/null +++ b/tab.cpp @@ -0,0 +1,267 @@ +#include "tab.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum {GRAPH, SETTINGS, DEVICE, COMMAND}; + +void Tab::checkSplit(SplitNo splitNo) { + assert(widgetno == splitNo); + widgetno++; +} + +//Tab::Tab(const char* instr, QWidget *parent, long range, const char *warning) +// : QDialog(parent, "tab", false, Qt::WType_TopLevel | Qt::WStyle_Minimize) +Tab::Tab(const char* instr, QWidget *parent, long range, const char *warning) + : QMainWindow(parent, "tab", Qt::WType_TopLevel | Qt::WStyle_Minimize) +{ + //QBoxLayout *vbox; + QApplication::style().polish(this); + + actFocus = -1; + + //vbox = new QVBoxLayout(this, 0, 0, "vbx"); + split = new QSplitter(Qt::Horizontal, this); + setCentralWidget(split); + //vbox->addWidget(split); + + widgetno = 0; + leftWidgetNo = graph_split; + rightWidgetNo = right_split; + + graph = new Graph(instr, split, range); + if (graph->set == NULL) { + graph = NULL; + return; + } + checkSplit(graph_split); + + graph->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, 1, 1)); + connect(graph, SIGNAL(gotoSettings()), this, SLOT(gotoSettings())); + settings = new Settings(split); + settings->set = graph->set; + connect(settings, SIGNAL(leaveSettings()), + this, SLOT(leaveSettings())); + settings->hide(); + checkSplit(settings_split); + + rightSplit = new QSplitter(Qt::Vertical, split, "rightSplit"); + rightSplit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, 1, 1)); + checkSplit(right_split); + + if (warning[0]) { + foreignwarn = new QVBox(rightSplit); + warnlabel = new QLabel(warning, foreignwarn); + showparbutton = new QPushButton("show parameters", foreignwarn); + QSize siz = foreignwarn->minimumSizeHint(); + foreignwarn->setFixedHeight(siz.height()); + warnlabel->setEraseColor(QColor(255,127,0)); + } + + device = new Device(rightSplit); + if (warning[0]) { + device->hide(); + connect(showparbutton, SIGNAL(clicked()), this, SLOT(showPars())); + } + + command = new Command(rightSplit, "Command", graph->set->uc); + connect(command->cmd, SIGNAL(returnPressed()), device, SLOT(update())); + connect(command, SIGNAL(activate()), device, SLOT(activate())); + device->init(command, graph->set); + + QTimer *timer = new QTimer(this, "repeat"); + connect(timer, SIGNAL(timeout()), this, SLOT(handler())); + timer->start(100); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + + exportXY = new Export(graph->set, split, instr); + exportXY->hide(); + exportXY->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, 1, 1)); + connect(exportXY, SIGNAL(finish()), this, SLOT(finishExport())); + connect(graph, SIGNAL(openExport()), this, SLOT(openExport())); + connect(graph->set, SIGNAL(updateCurveList()), exportXY, SLOT(updateCurveList())); + checkSplit(export_split); + + report = new Report(split, graph->set->ec); + report->hide(); + checkSplit(report_split); + connect(report, SIGNAL(leave()), this, SLOT(leaveReport())); + connect(report, SIGNAL(openMe()), this, SLOT(openReport())); + + message = new QLabel(split); + message->hide(); + + for (int i = 0; i < 2 * 3; i++) { + oldSizes[i] = 0; + } +} + +bool Tab::shown(QWidget *w) { + return w->isShown() && w->width() > 3 && w->height() > 3; +} + +void Tab::showPars() { + QValueList splitsizes; + int h; + + showparbutton->hide(); + QSize siz = warnlabel->minimumSizeHint(); + foreignwarn->setFixedHeight(siz.height()); + device->show(); + splitsizes = rightSplit->sizes(); + h = (splitsizes[1] + splitsizes[2]) / 2; + splitsizes[1] = h; + splitsizes[2] = h; + rightSplit->setSizes(splitsizes); + device->update(); +} + +bool Tab::splitChange(SplitNo splitNo) { + int lw = 0, rw = 0; + int i; + int tot; + float s; + + sizes = split->sizes(); + lw = sizes[leftWidgetNo]; + rw = sizes[rightWidgetNo]; + i = leftWidgetNo + 2 * (rightWidgetNo - right_split); + if (splitNo < right_split) { + if (splitNo == leftWidgetNo) return FALSE; + leftWidgetNo = splitNo; + } else { + if (splitNo == rightWidgetNo) return FALSE; + rightWidgetNo = splitNo; + } + tot = lw + rw; + if (rw > 1 && lw > 1) { + oldSizes[i] = lw / float(tot); + } + s = oldSizes[leftWidgetNo + 2 * (rightWidgetNo - right_split)]; + if (s != 0) { + lw = s * tot + 0.5; + rw = tot - lw; + } + for (i = 0; i < n_split; i++) { + sizes[i] = 0; + } + sizes[leftWidgetNo] = lw; + sizes[rightWidgetNo] = rw; + return TRUE; +} + +void Tab::gotoSettings() { + if (splitChange(settings_split)) { + graph->showIt(false); + settings->showIt(true); + split->setSizes(sizes); + } +} + +void Tab::leaveSettings() { + if (splitChange(graph_split)) { + settings->showIt(false); + graph->showIt(true); + } + split->setSizes(sizes); +} + +void Tab::openExport() { + if (splitChange(export_split)) { + rightSplit->hide(); + exportXY->showMe(); + split->setSizes(sizes); + } +} + +void Tab::finishExport() { + if (splitChange(right_split)) { + exportXY->hide(); + rightSplit->show(); + split->setSizes(sizes); + } +} + +void Tab::openReport() { + if (splitChange(report_split)) { + rightSplit->hide(); + report->show(); + split->setSizes(sizes); + } +} + +void Tab::leaveReport() { + if (splitChange(right_split)) { + report->hide(); + rightSplit->show(); + split->setSizes(sizes); + } +} + +void Tab::sleep(const char *text) { + graph->hide(); + rightSplit->hide(); + message->setText(text); + message->show(); +} + +void Tab::wake() { + graph->show(); + rightSplit->show(); + exportXY->hide(); + report->hide(); + message->hide(); + graph->set->sc->disconnect(); + graph->set->uc->disconnect(); + graph->set->gc->disconnect(); + graph->set->ec->disconnect(); + graph->set->sc->reconnect(); + /* uc is connected automatically with sc */ + graph->set->gc->reconnect(); + graph->set->ec->reconnect(); +} + +void Tab::initSize(int w, int h) { + QSize qs = QSize(w, h).expandedTo(split->minimumSizeHint()); + resize(qs); + sizes = split->sizes(); + sizes[0] = w/2; + sizes[2] = w/2; + split->setSizes(sizes); +} + +void Tab::handler() { + command->handler(); + if (shown(command) && command->cmd->hasFocus()) { + actFocus = COMMAND; + } + if (shown(graph) && graph->lineEditRange->hasFocus()) { + actFocus = GRAPH; + } + if (!shown(settings) && !shown(device)) { + if (actFocus == COMMAND) { + command->cmd->setFocus(); + } else { + if (!graph->lineEditRange->hasFocus()) { + graph->lineEditRange->setFocus(); + graph->lineEditRange->lineEdit()->selectAll(); + } + } + } +} + +void Tab::resizeEvent(QResizeEvent *e) { + //QDialog::resizeEvent(e); + QMainWindow::resizeEvent(e); +} diff --git a/tab.h b/tab.h new file mode 100644 index 0000000..a14c802 --- /dev/null +++ b/tab.h @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include "graph.h" +#include "settings.h" +#include "device.h" +#include "export.h" +#include "report.h" + +//class Tab : public QDialog +class Tab : public QMainWindow +{ + Q_OBJECT +public: + Tab(const char* instr, QWidget *parent, long range, const char *fromInstr); + void initSize(int w, int h); + void sleep(const char *text); + void wake(); + + Graph *graph; + Settings *settings; + Command *command; + Device *device; + Export *exportXY; + Report *report; + QSplitter *split; + QSplitter *rightSplit; + QLabel *message; + QVBox *foreignwarn; + QPushButton *showparbutton; + QLabel *warnlabel; + +public slots: + void leaveSettings(); + void gotoSettings(); + void handler(); + void openExport(); + void finishExport(); + void openReport(); + void leaveReport(); + void showPars(); + +signals: + void restart(); + +private: + int actFocus; + int watchSocket; + void resizeEvent(QResizeEvent *e); + bool shown(QWidget *w); + + enum SplitNo {graph_split, settings_split, right_split, export_split, report_split, n_split}; + + void checkSplit(SplitNo splitNo); + bool splitChange(SplitNo splitNo); + + int widgetno; + float oldSizes[2*3]; + SplitNo leftWidgetNo, rightWidgetNo; + QValueList sizes; +}; diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..a329255 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,462 @@ +#include "utils.h" +#include +#include +#include +#include + +void SetEraseColor(QWidget *widget, QWidget *from, bool inclBgr) { + const QPixmap *p; + p = from->erasePixmap(); + if (p) { + widget->setErasePixmap(*p); + if (inclBgr) { + widget->setPaletteBackgroundPixmap(*p); + } + } else { + widget->setEraseColor(from->eraseColor()); + if (inclBgr) { + widget->setPaletteBackgroundColor(from->eraseColor()); + } + } +} + +FlatButton::FlatButton(const QString &label, QWidget *parent, const char *name) + : QLabel(label, parent, name) { + x = -1; +} + +void FlatButton::mousePressEvent(QMouseEvent *e) { + x = e->x(); + y = e->y(); + mousePressed(); +} + +void FlatButton::mouseReleaseEvent(QMouseEvent *e) { + if (x >= 0 && abs(e->x() - x) + abs(e->y() - y) < 5) { + clicked(); + } + x = -1; +} + +void ShrinkButton(QPushButton *b) { + int margin; + QString text(b->text()); + b->setText("This is a long text"); + margin = b->sizeHint().width() - b->fontMetrics().width(b->text()); + b->setText(text); + b->setFixedWidth(b->fontMetrics().width(text) + margin); +} + +double DoubleTime(void) +{ + struct timeval now; + /* the resolution of this function is usec, if the machine supports this + and the mantissa of a double is 51 bits or more (31 for sec and 20 for milliseconds) + */ + gettimeofday(&now, NULL); + return now.tv_sec + now.tv_usec / 1e6; +} + +static char *coltab[N_SEACOLORS*2] = { + "X c #FFFFFF", "white", + "X c #FF0000", "red", + "X c #00FF00", "lime", // was green + "X c #0000FF", "blue", + "X c #FF00FF", "magenta", // fuchsia + "X c #FFFF00", "yellow", + "X c #00FFFF", "cyan", // aqua + "X c #000000", "black", + "X c #FFA500", "orange", + "X c #006400", "dark_green", + "X c #9400D3", "dark_violet", + "X c #A52A2A", "brown", + "X c #87CEEB", "sky_blue", + "X c #808080", "gray", + "X c #FF69B4", "hot_pink", + "X c #FFFFE0", "light_yellow", + "X c #00FF7F", "spring_green", + "X c #000080", "navy", + "X c #1E90FF", "dodger_blue", + "X c #9ACD32", "yellow_green", + "X c #008B8B", "dark_cyan", + "X c #808000", "olive", + "X c #DEB887", "burly_wood", + "X c #7B68EE", "medium_slate_blue", + "X c #483D8B", "dark_slate_blue", + "X c #98FB98", "pale_green", + "X c #FF1493", "deep_pink", + "X c #FF6347", "tomato", + "X c #32CD32", "lime_green", + "X c #DDA0DD", "plum", + "X c #7FFF00", "chartreuse", + "X c #800080", "purple", + "X c #00CED1", "dark_turquoise", + "X c #8FBC8F", "dark_sea_green", + "X c #4682B4", "steel_blue", + "X c #800000", "maroon", + "X c #3CB371", "medium_sea_green", + "X c #FF4500", "orange_red", + "X c #BA55D3", "medium_orchid", + "X c #2F4F4F", "dark_slate_gray", + "X c #CD853F", "peru", + "X c #228B22", "forest_green", + "X c #48D1CC", "medium_turquoise", + "X c #DC143C", "crimson", + "X c #D3D3D3", "light_gray", + "X c #ADFF2F", "green_yellow", + "X c #7FFFD4", "aquamarine", + "X c #BC8F8F", "rosy_brown", + "X c #20B2AA", "light_sea_green", + "X c #C71585", "medium_violet_red", + "X c #F0E68C", "khaki", + "X c #6495ED", "cornflower_blue", + "X c #556B2F", "dark_olive_green", + "X c #CD5C5C", "indian_red", + "X c #2E8B57", "sea_green", + "X c #F08080", "light_coral", + "X c #8A2BE2", "blue_violet", + "X c #AFEEEE", "pale_turquoise", + "X c #4169E1", "royal_blue", + "X c #0000CD", "medium_blue", + "X c #B8860B", "dark_goldenrod", + "X c #00BFFF", "deep_sky_blue", + "X c #FFC0CB", "pink", + "X c #4B0082", "indigo", + "X c #A0522D", "sienna", + "X c #FFD700", "gold", + "X c #F4A460", "sandy_brown", + "X c #DAA520", "goldenrod", + "X c #DA70D6", "orchid", + "X c #E6E6FA", "lavender", + "X c #5F9EA0", "cadet_blue", + "X c #D2691E", "chocolate", + "X c #66CDAA", "medium_aquamarine", + "X c #6B8E23", "olive_drab", + "X c #A9A9A9", "dark_gray", + "X c #BDB76B", "dark_khaki", + "X c #696969", "dim_gray", + "X c #B0C4DE", "light_steel_blue", + "X c #191970", "midnight_blue", + "X c #FFE4C4", "bisque", + "X c #6A5ACD", "slate_blue", + "X c #EE82EE", "violet", + "X c #8B4513", "saddle_brown", + "X c #FF7F50", "coral", + "X c #008000", "mid_green", + "X c #DB7093", "pale_violet_red", + "X c #C0C0C0", "silver", + "X c #E0FFFF", "light_cyan", + "X c #9370DB", "medium_purple", + "X c #FF8C00", "dark_orange", + "X c #00FA9A", "medium_spring_green", + "X c #E9967A", "dark_salmon", + "X c #778899", "light_slate_gray", + "X c #9932CC", "dark_orchid", + "X c #EEE8AA", "pale_goldenrod", + "X c #F8F8FF", "ghost_white", + "X c #FFA07A", "light_salmon", + "X c #ADD8E6", "light_blue", + "X c #D8BFD8", "thistle", + "X c #FFE4E1", "misty_rose", + "X c #FFDEAD", "navajo_white", + "X c #40E0D0", "turquoise", + "X c #90EE90", "light_green", + "X c #B22222", "fire_brick", + "X c #008080", "teal", + "X c #F0FFF0", "honeydew", + "X c #FFFACD", "lemon_chiffon", + "X c #FFF5EE", "seashell", + "X c #F5F5DC", "beige", + "X c #DCDCDC", "gainsboro", + "X c #FA8072", "salmon", + "X c #8B008B", "dark_magenta", + "X c #FFB6C1", "light_pink", + "X c #708090", "slate_gray", + "X c #87CEFA", "light_sky_blue", + "X c #FFEFD5", "papaya_whip", + "X c #D2B48C", "tan", + "X c #FFFFF0", "ivory", + "X c #F0FFFF", "azure", + "X c #F5DEB3", "wheat", + "X c #00008B", "dark_blue", + "X c #FFDAB9", "peach_puff", + "X c #8B0000", "dark_red", + "X c #FAF0E6", "linen", + "X c #B0E0E6", "powder_blue", + "X c #FFE4B5", "moccasin", + "X c #F5F5F5", "white_smoke", + "X c #FFF8DC", "cornsilk", + "X c #FFFAFA", "snow", + "X c #FFF0F5", "lavender_blush", + "X c #FFEBCD", "blanched_almond", + "X c #F0F8FF", "alice_blue", + "X c #FAEBD7", "antique_white", + "X c #FDF5E6", "old_lace", + "X c #FAFAD2", "light_goldenrod_yellow", + "X c #F5FFFA", "mint_cream", + "X c #FFFAF0", "floral_white", + "X c #7CFC00", "lawn_green" +}; + +static long seaColors[N_SEACOLORS]={0}; + +static void getRGB(long i, long &r, long &g, long &b) { + long code; + char *k; + + if (i >= 0x1000000 && i < 0x2000000) { + code = i - 0x1000000; + } else { + if (i < 0 || i >= N_SEACOLORS) { + r = 0; + g = 0; + b = 0; + return; + } + if (seaColors[i] == 0) { + k = strchr(coltab[i*2], '#'); + if (k) { + seaColors[i] = strtol(k+1, NULL, 16); + } + } + code = seaColors[i]; + } + b = code % 256; code = code / 256; + g = code % 256; code = code / 256; + r = code % 256; +} + +QPen thisPen(long i, int w) { + long r,g,b; + + getRGB(i, r, g, b); + if (w == 0) { + w = (3 * r + 5 * g + b) / 460; /* make pen larger for bright colors, 0 <= w <= 4 */ + if (w < 2) { + w = 2; + } + } + return QPen(QColor(r,g,b), w, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin); +}; + +static const char *miniPix[] = { + "1 1 1 1", + " c None", + " " +}; + + +// the following routine is for a color widget +const QPixmap *thisPixmap(long i, int w) { + char *penpix[3+11]; + long l, j, k; + + if (i < 0) { + return new QPixmap(miniPix); + } + if (i >= N_SEACOLORS) { + return NULL; + } + l = thisPen(i, w).width(); + if (l > 11) l = 11; + penpix[0] = "20 11 2 1"; + penpix[1] = coltab[i*2]; + penpix[2] = " c None"; + k = (11 - l) / 2; + for (j = 0; j < k; j++) { + penpix[j+3] = " "; + } + for (j = k; j < k + l; j++) { + penpix[j+3] = "XXXXXXXXXXXXXXXXXXXX"; + } + for (j = k + l; j < 11; j++) { + penpix[j+3] = " "; + } + return new QPixmap((const char **)penpix); +}; + +long Convert2ColorIndex(const QString &color) { + QString s=color.stripWhiteSpace(); + long rgb; + bool ok; + long r,g,b,rr,gg,bb,d; + long dist; + long index; + long i; + const char *ccol; + + if (s[0] == '#') { // hexadecimal color + s.remove(0,1); + rgb = s.toLong(&ok, 16); + if (!ok) { + return -2; + } + if (s.length() == 3) { // short form: find closest index + bb = rgb % 16 * 17; rgb = rgb / 16; + gg = rgb % 16 * 17; rgb = rgb / 16; + rr = rgb % 16 * 17; + dist = 9999999; + index = -2; + for (i = 0; i < N_SEACOLORS; i++) { + getRGB(i, r, g, b); + r -= rr; + g -= gg; + b -= bb; + d = 5*r*r + 8*g*g + 5*b*b; + if (d < dist) { + dist = d; + index = i; + } + } + } else if (s.length() == 6) { // long form: keep rgb code + index = 0x1000000 + rgb; + } else { + return -2; + } + return index; + } + index = s.toLong(&ok); + if (ok) { // color index + return index; + } + i = s.find("grey"); + if (i >= 0) { + s.replace(i+2, 1, "a"); + } + ccol = s.latin1(); + if (strcasecmp(ccol, "auto") == 0) { + return -1; + } + for (i = 0; i < N_SEACOLORS; i++) { + if (strcasecmp(coltab[i*2+1], ccol) == 0) { + return i; + } + } + if (strcasecmp(ccol, "green") == 0) { + return 2; + } + if (strcasecmp(ccol, "fuchsia") == 0) { + return 4; + } + if (strcasecmp(ccol, "aqua") == 0) { + return 6; + } + return -2; +} + +bool Convert2ColorName(long i, QString &result) { + if (i >= 0x1000000 && i < 0x2000000) { + result.sprintf("#%6.6lX", i - 0x1000000); + return true; + } + if (i < 0 || i >= N_SEACOLORS) { + result = "auto"; + return false; + } else { + result = coltab[i*2+1]; + return true; + } +} + +QString toRichText(QString &txt, bool *finished) { + QString result(""); + QChar ch; + int i, l; + bool wasspace; + + if (txt == "" || txt == " ") { + return " "; + } + l = txt.length(); + wasspace = true; + for (i = 0; i < l; i++) { + ch = txt[i]; + if (ch == ' ') { + if (wasspace) { + result.append(" "); + } else { + result.append(' '); + wasspace = true; + } + } else { + wasspace = false; + if (ch == '&') { + result.append("&"); + } else if (ch == '<') { + result.append("<"); + } else { + result.append(ch); + } + } + } + return result; +} + +MyTextEdit::MyTextEdit(QWidget * parent, const char *name) : QTextEdit(parent, name) +{ + QFont monospace("Courier"); + + setTextFormat(Qt::RichText); + setWordWrap(QTextEdit::WidgetWidth); + setWrapPolicy(QTextEdit::Anywhere); + monospace.setStyleHint(QFont::TypeWriter); + setFont(monospace); + linebuffer = ""; + buffered = false; + buffer = QStringList(); +} + +void MyTextEdit::appendHtml(QString text) { + linebuffer.append(text); +} + +void MyTextEdit::setBuffered(bool arg) { + if (buffered) { + hide(); + for (QStringList::Iterator it = buffer.begin(); it != buffer.end(); ++it ) { + append(*it); + } + show(); + buffer.clear(); + } + buffered = arg; +} + +void MyTextEdit::appendText(QString text) { + QChar ch; + int i, l; + bool wasspace; + + l = text.length(); + wasspace = true; + for (i = 0; i < l; i++) { + ch = text[i]; + if (ch == ' ') { + if (wasspace) { + linebuffer.append(" "); + } else { + linebuffer.append(' '); + wasspace = true; + } + } else { + wasspace = false; + if (ch == '\n') { + if (linebuffer == "" || linebuffer == " ") { + linebuffer="
"; + } + if (buffered) { + buffer.append(linebuffer); + } else { + append(linebuffer); + } + linebuffer = ""; + } else if (ch == '&') { + linebuffer.append("&"); + } else if (ch == '<') { + linebuffer.append("<"); + } else { + linebuffer.append(ch); + } + } + } +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..c6444db --- /dev/null +++ b/utils.h @@ -0,0 +1,59 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define N_SEACOLORS 138 + +class FlatButton: public QLabel { + Q_OBJECT +public: + FlatButton(const QString &label, QWidget *parent, const char *name=0); +signals: + void clicked(); + void mousePressed(); +protected: + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); +private: + int x, y; +}; + +void ShrinkButton(QPushButton *b); +void SetEraseColor(QWidget *widget, QWidget *from, bool inclBgr = false); + +double DoubleTime(void); + +QPen thisPen(long i, int w = 0); + +const QPixmap *thisPixmap(long i, int w = 0); + +long Convert2ColorIndex(const QString &color); + +bool Convert2ColorName(long i, QString &result); + +// my own textedit, making paragraphs for newlines, but with single line spacing +class MyTextEdit: public QTextEdit { + Q_OBJECT +public: + MyTextEdit(QWidget * parent, const char *name=0); + void appendHtml(QString text); + void appendText(QString text); + void setBuffered(bool arg); +private: + bool buffered; + QString linebuffer; + QStringList buffer; +}; + + +#endif