From b675d2672b98bd4090cf74560853a9b6d311f056 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 30 Apr 2013 20:17:42 +0000 Subject: [PATCH] Included UtiLite library directly in Rtabmap source (to simplify the installation) git-svn-id: http://rtabmap.googlecode.com/svn/trunk/rtabmap@856 f169173b-cf89-36c8-b27e-44dbe73f0c83 --- guilib/src/utilite/UImageView.h | 101 ++ guilib/src/utilite/UPlot.cpp | 2979 +++++++++++++++++++++++++++++++ guilib/src/utilite/UPlot.h | 624 +++++++ utilite/src/CMakeLists.txt | 42 + utilite/src/UConversion.cpp | 320 ++++ utilite/src/UDirectory.cpp | 375 ++++ utilite/src/UEventsHandler.cpp | 31 + utilite/src/UEventsManager.cpp | 235 +++ utilite/src/UFile.cpp | 95 + utilite/src/ULogger.cpp | 624 +++++++ utilite/src/UPlot.cpp | 2979 +++++++++++++++++++++++++++++++ utilite/src/UProcessInfo.cpp | 78 + utilite/src/UThread.cpp | 301 ++++ utilite/src/UTimer.cpp | 115 ++ 14 files changed, 8899 insertions(+) create mode 100644 guilib/src/utilite/UImageView.h create mode 100644 guilib/src/utilite/UPlot.cpp create mode 100644 guilib/src/utilite/UPlot.h create mode 100644 utilite/src/CMakeLists.txt create mode 100644 utilite/src/UConversion.cpp create mode 100644 utilite/src/UDirectory.cpp create mode 100644 utilite/src/UEventsHandler.cpp create mode 100644 utilite/src/UEventsManager.cpp create mode 100644 utilite/src/UFile.cpp create mode 100644 utilite/src/ULogger.cpp create mode 100644 utilite/src/UPlot.cpp create mode 100644 utilite/src/UProcessInfo.cpp create mode 100644 utilite/src/UThread.cpp create mode 100644 utilite/src/UTimer.cpp diff --git a/guilib/src/utilite/UImageView.h b/guilib/src/utilite/UImageView.h new file mode 100644 index 00000000..8a03c653 --- /dev/null +++ b/guilib/src/utilite/UImageView.h @@ -0,0 +1,101 @@ +/* + * ImageView.h + * + * Created on: 2012-06-20 + * Author: mathieu + */ + +#ifndef IMAGEVIEW_H_ +#define IMAGEVIEW_H_ + +#include +#include + +class UImageView : public QWidget +{ + Q_OBJECT; +public: + UImageView(QWidget * parent = 0) : QWidget(parent) {} + ~UImageView() {} + void setBackgroundBrush(const QBrush & brush) {brush_ = brush;} + +public slots: + void setImage(const QImage & image) + { + if(pixmap_.width() != image.width() || pixmap_.height() != image.height()) + { + this->setMinimumSize(image.width(), image.height()); + this->setGeometry(this->geometry().x(), this->geometry().y(), image.width(), image.height()); + } + pixmap_ = QPixmap::fromImage(image); + this->update(); + } + +private: + void computeScaleOffsets(float & scale, float & offsetX, float & offsetY) + { + scale = 1.0f; + offsetX = 0.0f; + offsetY = 0.0f; + + if(!pixmap_.isNull()) + { + float w = pixmap_.width(); + float h = pixmap_.height(); + float widthRatio = float(this->rect().width()) / w; + float heightRatio = float(this->rect().height()) / h; + + if(widthRatio < heightRatio) + { + scale = widthRatio; + } + else + { + scale = heightRatio; + } + + w *= scale; + h *= scale; + + if(w < this->rect().width()) + { + offsetX = (this->rect().width() - w)/2.0f; + } + if(h < this->rect().height()) + { + offsetY = (this->rect().height() - h)/2.0f; + } + } + } + +protected: + virtual void paintEvent(QPaintEvent *event) + { + QPainter painter(this); + + //Draw background + painter.save(); + painter.setBrush(brush_); + painter.drawRect(this->rect()); + painter.restore(); + + if(!pixmap_.isNull()) + { + painter.save(); + //Scale + float ratio, offsetX, offsetY; + this->computeScaleOffsets(ratio, offsetX, offsetY); + painter.translate(offsetX, offsetY); + painter.scale(ratio, ratio); + painter.drawPixmap(QPoint(0,0), pixmap_); + painter.restore(); + } + } + +private: + QPixmap pixmap_; + QBrush brush_; +}; + + +#endif /* IMAGEVIEW_H_ */ diff --git a/guilib/src/utilite/UPlot.cpp b/guilib/src/utilite/UPlot.cpp new file mode 100644 index 00000000..23400478 --- /dev/null +++ b/guilib/src/utilite/UPlot.cpp @@ -0,0 +1,2979 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "utilite/UPlot.h" +#include "rtabmap/utilite/ULogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_SVG_LIB +#include +#endif +#include + +#define PRINT_DEBUG 0 + +UPlotItem::UPlotItem(qreal dataX, qreal dataY, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0), + _text(0), + _textBackground(0) +{ + this->init(dataX, dataY); +} + +UPlotItem::UPlotItem(const QPointF & data, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0), + _text(0), + _textBackground(0) +{ + this->init(data.x(), data.y()); +} + +void UPlotItem::init(qreal dataX, qreal dataY) +{ + _data.setX(dataX); + _data.setY(dataY); + this->setAcceptsHoverEvents(true); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +UPlotItem::~UPlotItem() +{ + if(_previousItem && _nextItem) + { + _previousItem->setNextItem(_nextItem); + _nextItem->setPreviousItem(_previousItem); + } + else if(_previousItem) + { + _previousItem->setNextItem(0); + } + else if(_nextItem) + { + _nextItem->setPreviousItem(0); + } +} + +void UPlotItem::setData(const QPointF & data) +{ + _data = data; +} + +void UPlotItem::setNextItem(UPlotItem * nextItem) +{ + if(_nextItem != nextItem) + { + _nextItem = nextItem; + if(nextItem) + { + nextItem->setPreviousItem(this); + } + } +} + +void UPlotItem::setPreviousItem(UPlotItem * previousItem) +{ + if(_previousItem != previousItem) + { + _previousItem = previousItem; + if(previousItem) + { + previousItem->setNextItem(this); + } + } +} + +void UPlotItem::showDescription(bool shown) +{ + if(!_textBackground) + { + _textBackground = new QGraphicsRectItem(this); + _textBackground->setBrush(QBrush(QColor(255, 255, 255, 200))); + _textBackground->setPen(Qt::NoPen); + _textBackground->setZValue(this->zValue()+1); + _textBackground->setVisible(false); + + _text = new QGraphicsTextItem(_textBackground); + } + + if(this->parentItem() && this->parentItem() != _textBackground->parentItem()) + { + _textBackground->setParentItem(this->parentItem()); + _textBackground->setZValue(this->zValue()+1); + } + + if(this->scene() && shown) + { + _textBackground->setVisible(true); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + + this->setPen(QPen(this->pen().color(), 2)); + + QRectF rect = this->scene()->sceneRect(); + QPointF p = this->pos(); + QRectF br = _text->boundingRect(); + _textBackground->setRect(QRectF(0,0,br.width(), br.height())); + + // Make sure the text is always in the scene + if(p.x() - br.width() < 0) + { + p.setX(0); + } + else if(p.x() > rect.width()) + { + p.setX(rect.width() - br.width()); + } + else + { + p.setX(p.x() - br.width()); + } + + if(p.y() - br.height() < 0) + { + p.setY(0); + } + else + { + p.setY(p.y() - br.height()); + } + + _textBackground->setPos(p); + } + else + { + this->setPen(QPen(this->pen().color(), 1)); + _textBackground->setVisible(false); + } +} + +void UPlotItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void UPlotItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + if(!this->hasFocus()) + { + this->showDescription(false); + } + QGraphicsEllipseItem::hoverLeaveEvent(event); +} + +void UPlotItem::focusInEvent(QFocusEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::focusInEvent(event); +} + +void UPlotItem::focusOutEvent(QFocusEvent * event) +{ + this->showDescription(false); + QGraphicsEllipseItem::focusOutEvent(event); +} + +void UPlotItem::keyReleaseEvent(QKeyEvent * keyEvent) +{ + //Get the next/previous visible item + if(keyEvent->key() == Qt::Key_Right) + { + UPlotItem * next = _nextItem; + while(next && !next->isVisible()) + { + next = next->nextItem(); + } + if(next && next->isVisible()) + { + this->clearFocus(); + next->setFocus(); + } + } + else if(keyEvent->key() == Qt::Key_Left) + { + UPlotItem * previous = _previousItem; + while(previous && !previous->isVisible()) + { + previous = previous->previousItem(); + } + if(previous && previous->isVisible()) + { + this->clearFocus(); + previous->setFocus(); + } + } + QGraphicsEllipseItem::keyReleaseEvent(keyEvent); +} + + + + + +UPlotCurve::UPlotCurve(const QString & name, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); +} + +UPlotCurve::UPlotCurve(const QString & name, QVector data, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); + this->setData(data); +} + +UPlotCurve::UPlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); + this->setData(x, y); +} + +UPlotCurve::~UPlotCurve() +{ + if(_plot) + { + _plot->removeCurve(this); + } +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + this->clear(); + delete _rootItem; +} + +void UPlotCurve::attach(UPlot * plot) +{ + if(!plot || plot == _plot) + { + return; + } + if(_plot) + { + _plot->removeCurve(this); + } + _plot = plot; + _plot->addItem(_rootItem); +} + +void UPlotCurve::detach(UPlot * plot) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("curve=\"%s\" from plot=\"%s\"", this->objectName().toStdString().c_str(), plot?plot->objectName().toStdString().c_str():""); +#endif + if(plot && _plot == plot) + { + _plot = 0; + if(_rootItem->scene()) + { + _rootItem->scene()->removeItem(_rootItem); + } + } +} + +void UPlotCurve::updateMinMax() +{ + float x,y; + const UPlotItem * item; + if(!_items.size()) + { + _minMax = QVector(); + } + else + { + _minMax = QVector(4); + } + for(int i=0; i<_items.size(); ++i) + { + item = qgraphicsitem_cast(_items.at(i)); + if(item) + { + x = item->data().x(); + y = item->data().y(); + if(i==0) + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + else + { + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + } +} + +void UPlotCurve::_addValue(UPlotItem * data) +{ + // add item + if(data) + { + float x = data->data().x(); + float y = data->data().y(); + if(_minMax.size() != 4) + { + _minMax = QVector(4); + } + if(_items.size()) + { + data->setPreviousItem((UPlotItem *)_items.last()); + QGraphicsLineItem * line = new QGraphicsLineItem(_rootItem); + line->setPen(_pen); + line->setVisible(false); + _items.append(line); + //Update min/max + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + else + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + data->setParentItem(_rootItem); + data->setZValue(1); + _items.append(data); + data->setVisible(false); + QPen pen = data->pen(); + pen.setColor(_itemsColor); + data->setPen(pen); + } + else + { + ULOGGER_ERROR("Data is null ?!?"); + } +} + +void UPlotCurve::addValue(UPlotItem * data) +{ + // add item + if(data) + { + this->_addValue(data); + emit dataChanged(this); + } +} + +void UPlotCurve::addValue(float x, float y) +{ + float width = 2; // TODO warn : hard coded value! + this->addValue(new UPlotItem(x,y,width)); +} + +void UPlotCurve::addValue(float y) +{ + float x = 0; + if(_items.size()) + { + UPlotItem * lastItem = (UPlotItem *)_items.last(); + x = lastItem->data().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->addValue(x,y); +} + +void UPlotCurve::addValue(const QString & value) +{ + bool ok; + float v = value.toFloat(&ok); + if(ok) + { + this->addValue(v); + } + else + { + ULOGGER_ERROR("Value not valid, must be a number, received %s", value.toStdString().c_str()); + } +} + +void UPlotCurve::addValues(QVector & data) +{ + for(int i=0; i_addValue(data.at(i)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & xs, const QVector & ys) +{ + float width = 2; // TODO warn : hard coded value! + for(int i=0; i_addValue(new UPlotItem(xs.at(i),ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +int UPlotCurve::removeItem(int index) +{ + if(index >= 0 && index < _items.size()) + { + if(index!=0) + { + index-=1; + delete _items.takeAt(index); // the line + } + else if(_items.size()>1) + { + delete _items.takeAt(index+1); // the line + } + UPlotItem * item = (UPlotItem *)_items.takeAt(index); // the plot item + //Update min/max + if(_minMax.size() == 4) + { + if(item->data().x() == _minMax[0] || item->data().x() == _minMax[1] || + item->data().y() == _minMax[2] || item->data().y() == _minMax[3]) + { + if(_items.size()) + { + UPlotItem * tmp = (UPlotItem *)_items.at(0); + float x = tmp->data().x(); + float y = tmp->data().y(); + _minMax[0]=x; + _minMax[1]=x; + _minMax[2]=y; + _minMax[3]=y; + for(int i = 2; i<_items.size(); i+=2) + { + tmp = (UPlotItem*)_items.at(i); + x = tmp->data().x(); + y = tmp->data().y(); + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + else + { + _minMax = QVector(); + } + } + } + delete item; + } + + return index; +} + +void UPlotCurve::removeItem(UPlotItem * item) // ownership is transfered to the caller +{ + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i) == item) + { + if(i!=0) + { + i-=1; + delete _items[i]; + _items.removeAt(i); + } + else if(_items.size()>1) + { + delete _items[i+1]; + _items.removeAt(i+1); + } + item->scene()->removeItem(item); + _items.removeAt(i); + break; + } + } +} + +void UPlotCurve::clear() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + qDeleteAll(_rootItem->childItems()); + _items.clear(); +} + +void UPlotCurve::setPen(const QPen & pen) +{ + _pen = pen; + for(int i=1; i<_items.size(); i+=2) + { + ((QGraphicsLineItem*) _items.at(i))->setPen(_pen); + } +} + +void UPlotCurve::setBrush(const QBrush & brush) +{ + _brush = brush; + ULOGGER_WARN("Not used..."); +} + +void UPlotCurve::setItemsColor(const QColor & color) +{ + if(color.isValid()) + { + _itemsColor.setRgb(color.red(), color.green(), color.blue(), _itemsColor.alpha()); + for(int i=0; i<_items.size(); i+=2) + { + QPen pen = ((UPlotItem*) _items.at(i))->pen(); + pen.setColor(_itemsColor); + ((UPlotItem*) _items.at(i))->setPen(pen); + } + } +} + +void UPlotCurve::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept) +{ + //ULOGGER_DEBUG("scaleX=%f, scaleY=%f, offsetX=%f, offsetY=%f, xDir=%d, yDir=%d, _plot->scene()->width()=%f, _plot->scene()->height=%f", scaleX, scaleY, offsetX, offsetY, xDir, yDir,_plot->scene()->width(),_plot->scene()->height()); + //make sure direction values are 1 or -1 + xDir<0?xDir=-1:xDir=1; + yDir<0?yDir=-1:yDir=1; + + bool hide = false; + int j=0; + for(int i=_items.size()-1; i>=0; --i) + { + if(i%2 == 0) + { + UPlotItem * item = (UPlotItem *)_items.at(i); + if(hide) + { + if(maxItemsKept == 0 || j <= maxItemsKept) + { + // if not visible, stop looping... all other items are normally already hidden + if(!item->isVisible()) + { + break; + } + item->setVisible(false); + } + else + { + //remove the item with his line + i = this->removeItem(i); + } + } + else + { + QPointF newPos(((xDir*item->data().x()+offsetX)*scaleX-item->rect().width()/2.0f), + ((yDir*item->data().y()+offsetY)*scaleY-item->rect().width()/2.0f)); + if(!item->isVisible()) + { + item->setVisible(true); + } + item->setPos(newPos); + } + ++j; + } + else + { + if(hide) + { + _items.at(i)->setVisible(false); + } + else + { + UPlotItem * from = (UPlotItem *)_items.at(i-1); + UPlotItem * to = (UPlotItem *)_items.at(i+1); + QGraphicsLineItem * lineItem = (QGraphicsLineItem *)_items.at(i); + lineItem->setLine((xDir*from->data().x()+offsetX)*scaleX, + (yDir*from->data().y()+offsetY)*scaleY, + (xDir*to->data().x()+offsetX)*scaleX, + (yDir*to->data().y()+offsetY)*scaleY); + if(!lineItem->isVisible()) + { + lineItem->setVisible(true); + } + //Don't update not visible items + // (Detect also if the curve goes forward or backward) + QLineF line = lineItem->line(); + if((line.x1() <= line.x2() && line.x2() < 0-((line.x2() - line.x1()))) || + (line.x1() > line.x2() && line.x2() > lineItem->scene()->sceneRect().width() + ((line.x1() - line.x2())))) + { + hide = true; + } + + } + } + } + +} + +void UPlotCurve::draw(QPainter * painter, const QRect & limits) +{ + if(painter) + { + for(int i=_items.size()-1; i>=0 && _items.at(i)->isVisible(); i-=2) + { + //plotItem + const UPlotItem * item = (const UPlotItem *)_items.at(i); + int x = (int)item->x(); + if(x<0) + { + break; + } + + // draw line in first + if(i-1>=0) + { + //lineItem + const QGraphicsLineItem * lineItem = (const QGraphicsLineItem *)_items.at(i-1); + QLine line = lineItem->line().toLine(); + if(limits.contains(line.p1()) || limits.contains(line.p2())) + { + QPointF intersection; + QLineF::IntersectType type; + type = lineItem->line().intersect(QLineF(limits.topLeft(), limits.bottomLeft()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.topLeft(), limits.topRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.bottomLeft(), limits.bottomRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.topRight(), limits.bottomRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + } + } + } + painter->save(); + painter->setPen(this->pen()); + painter->setBrush(this->brush()); + painter->drawLine(line); + painter->restore(); + } + } + + if(limits.contains(item->pos().toPoint()) && limits.contains((item->pos() + QPointF(item->rect().width(), item->rect().height())).toPoint())) + { + painter->save(); + painter->setPen(QPen(_itemsColor)); + painter->drawEllipse(item->pos()+QPointF(item->rect().width()/2, item->rect().height()/2), (int)item->rect().width()/2, (int)item->rect().height()/2); + painter->restore(); + } + } + } +} + +int UPlotCurve::itemsSize() const +{ + return _items.size(); +} + +QPointF UPlotCurve::getItemData(int index) +{ + QPointF data; + //make sure the index point to a PlotItem {PlotItem, line, PlotItem, line...} + if(index>=0 && index < _items.size() && index % 2 == 0 ) + { + data = ((UPlotItem*)_items.at(index))->data(); + } + else + { + ULOGGER_ERROR("Wrong index, not pointing on a PlotItem"); + } + return data; +} + +void UPlotCurve::setVisible(bool visible) +{ + _visible = visible; + for(int i=0; i<_items.size(); ++i) + { + _items.at(i)->setVisible(visible); + } +} + +void UPlotCurve::setXIncrement(float increment) +{ + _xIncrement = increment; +} + +void UPlotCurve::setXStart(float val) +{ + _xStart = val; +} + +void UPlotCurve::setData(QVector & data) +{ + this->clear(); + for(int i = 0; iaddValue(data[i]); + } +} + +void UPlotCurve::setData(const QVector & x, const QVector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - x.size(); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + QVector::const_iterator i=x.begin(); + QVector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else if(y.size()>0 && x.size()==0) + { + this->setData(y); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const std::vector & x, const std::vector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(x.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + std::vector::const_iterator i=x.begin(); + std::vector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else if(y.size()>0 && x.size()==0) + { + this->setData(y); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const QVector & y) +{ + this->setData(y.toStdVector()); +} + +void UPlotCurve::setData(const std::vector & y) +{ + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(y.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + float x = 0; + std::vector::const_iterator j=y.begin(); + for(; j!=y.end(); ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(x++, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); +} + +void UPlotCurve::getData(QVector & x, QVector & y) const +{ + x.clear(); + y.clear(); + if(_items.size()) + { + x.resize((_items.size()-1)/2+1); + y.resize(x.size()); + int j=0; + for(int i=0; i<_items.size(); i+=2) + { + x[j] = ((UPlotItem*)_items.at(i))->data().x(); + y[j++] = ((UPlotItem*)_items.at(i))->data().y(); + } + } +} + + + + + +UPlotCurveThreshold::UPlotCurveThreshold(const QString & name, float thesholdValue, Qt::Orientation orientation, QObject * parent) : + UPlotCurve(name, parent), + _orientation(orientation) +{ + if(_orientation == Qt::Horizontal) + { + this->addValue(0, thesholdValue); + this->addValue(1, thesholdValue); + } + else + { + this->addValue(thesholdValue, 0); + this->addValue(thesholdValue, 1); + } +} + +UPlotCurveThreshold::~UPlotCurveThreshold() +{ + +} + +void UPlotCurveThreshold::setThreshold(float threshold) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%f", threshold); +#endif + if(_items.size() == 3) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), threshold)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), threshold)); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(threshold, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(threshold, item->data().y())); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } +} + +void UPlotCurveThreshold::setOrientation(Qt::Orientation orientation) +{ + if(_orientation != orientation) + { + _orientation = orientation; + if(_items.size() == 3) + { + UPlotItem * item = 0; + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().y(), item->data().x())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().y(), item->data().x())); + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + } +} + +void UPlotCurveThreshold::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept) +{ + if(_items.size() == 3) + { + if(_plot) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + //(xDir*item->data().x()+offsetX)*scaleX + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(-(offsetX-item->rect().width()/scaleX)/xDir, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF( (_plot->sceneRect().width()/scaleX-(offsetX+item->rect().width()/scaleX))/xDir, item->data().y())); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), -(offsetY-item->rect().height()/scaleY)/yDir)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), (_plot->sceneRect().height()/scaleY-(offsetY+item->rect().height()/scaleY))/yDir)); + } + this->updateMinMax(); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + UPlotCurve::update(scaleX, scaleY, offsetX, offsetY, xDir, yDir, maxItemsKept); +} + + + + + + + +UPlotAxis::UPlotAxis(Qt::Orientation orientation, float min, float max, QWidget * parent) : + QWidget(parent), + _orientation(orientation), + _reversed(false), + _gradMaxDigits(4), + _border(0) +{ + if(_orientation == Qt::Vertical) + { + _reversed = true; // default bottom->up + } +#ifdef WIN32 + this->setMinimumSize(15, 25); +#else + this->setMinimumSize(15, 25); +#endif + this->setAxis(min, max); // this initialize all attributes +} + +UPlotAxis::~UPlotAxis() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +// Vertical :bottom->up, horizontal :right->left +void UPlotAxis::setReversed(bool reversed) +{ + if(_reversed != reversed) + { + float min = _min; + _min = _max; + _max = min; + } + _reversed = reversed; +} + +void UPlotAxis::setAxis(float & min, float & max) +{ + int borderMin = 0; + int borderMax = 0; + if(_orientation == Qt::Vertical) + { + borderMin = borderMax = this->fontMetrics().height()/2; + } + else + { + borderMin = this->fontMetrics().width(QString::number(_min,'g',_gradMaxDigits))/2; + borderMax = this->fontMetrics().width(QString::number(_max,'g',_gradMaxDigits))/2; + } + int border = borderMin>borderMax?borderMin:borderMax; + int borderDelta; + int length; + if(_orientation == Qt::Vertical) + { + length = (this->height()-border*2); + } + else + { + length = (this->width()-border*2); + } + + if(length <= 70) + { + _count = 5; + } + else if(length <= 175) + { + _count = 10; + } + else if(length <= 350) + { + _count = 20; + } + else if(length <= 700) + { + _count = 40; + } + else if(length <= 1000) + { + _count = 60; + } + else if(length <= 1300) + { + _count = 80; + } + else + { + _count = 100; + } + + // Rounding min and max + if(min != max) + { + float mul = 1; + float rangef = max - min; + int countStep = _count/5; + float val; + for(int i=0; i<6; ++i) + { + val = (rangef/float(countStep)) * mul; + if( val >= 1.0f && val < 10.0f) + { + break; + } + else if(val<1) + { + mul *= 10.0f; + } + else + { + mul /= 10.0f; + } + } + //ULOGGER_DEBUG("min=%f, max=%f", min, max); + int minR = min*mul-0.9; + int maxR = max*mul+0.9; + min = float(minR)/mul; + max = float(maxR)/mul; + //ULOGGER_DEBUG("mul=%f, minR=%d, maxR=%d,countStep=%d", mul, minR, maxR, countStep); + } + + _min = min; + _max = max; + + if(_reversed) + { + _min = _max; + _max = min; + } + + if(_orientation == Qt::Vertical) + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + else + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + + if(borderDelta%2 != 0) + { + borderDelta+=1; + } + + _border = border + borderDelta/2; + + //Resize estimation + if(_orientation == Qt::Vertical) + { + int minWidth = 0; + for (int i = 0; i <= _count; i+=5) + { + QString n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + if(this->fontMetrics().width(n) > minWidth) + { + minWidth = this->fontMetrics().width(n); + } + } + this->setMinimumWidth(15+minWidth); + } +} + +void UPlotAxis::paintEvent(QPaintEvent * event) +{ + QPainter painter(this); + if(_orientation == Qt::Vertical) + { + painter.translate(0, _border); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(this->width(), 0, this->width()-10, 0); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(this->width()-(12+n.sizeHint().width()), n.sizeHint().height()/2-2, n.text()); + } + else + { + painter.drawLine(this->width(), 0, this->width()-5, 0); + } + painter.translate(0, _step); + } + } + else + { + painter.translate(_border, 0); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(0, 0, 0, 10); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(-(n.sizeHint().width()/2)+1, 22, n.text()); + } + else + { + painter.drawLine(0, 0, 0, 5); + } + painter.translate(_step, 0); + } + } +} + + + + +UPlotLegendItem::UPlotLegendItem(UPlotCurve * curve, QWidget * parent) : + QPushButton(parent), + _curve(curve) +{ + QString nameSpaced = curve->name(); + nameSpaced.replace('_', ' '); + this->setText(nameSpaced); + + this->setIcon(QIcon(this->createSymbol(curve->pen(), curve->brush()))); + this->setIconSize(QSize(25,20)); + + _aChangeText = new QAction(tr("Change text..."), this); + _aResetText = new QAction(tr("Reset text..."), this); + _aChangeColor = new QAction(tr("Change color..."), this); + _aCopyToClipboard = new QAction(tr("Copy curve data to the clipboard"), this); + _aMoveUp = new QAction(tr("Move up"), this); + _aMoveDown = new QAction(tr("Move down"), this); + _aRemoveCurve = new QAction(tr("Remove this curve"), this); + _menu = new QMenu(tr("Curve"), this); + _menu->addAction(_aChangeText); + _menu->addAction(_aResetText); + _menu->addAction(_aChangeColor); + _menu->addAction(_aCopyToClipboard); + _menu->addSeparator(); + _menu->addAction(_aMoveUp); + _menu->addAction(_aMoveDown); + _menu->addSeparator(); + _menu->addAction(_aRemoveCurve); +} + +UPlotLegendItem::~UPlotLegendItem() +{ + +} +void UPlotLegendItem::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aChangeText) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeText->text(), tr("Name :"), QLineEdit::Normal, this->text(), &ok); + if(ok && !text.isEmpty()) + { + this->setText(text); + } + } + else if(action == _aResetText) + { + if(_curve) + { + this->setText(_curve->name()); + } + } + else if(action == _aChangeColor) + { + if(_curve) + { + QPen pen = _curve->pen(); + QColor color = QColorDialog::getColor(pen.color(), this); + if(color.isValid()) + { + pen.setColor(color); + _curve->setPen(pen); + this->setIcon(QIcon(this->createSymbol(_curve->pen(), _curve->brush()))); + } + } + } + else if (action == _aCopyToClipboard) + { + if(_curve) + { + QVector x; + QVector y; + _curve->getData(x, y); + QString textX; + QString textY; + for(int i=0; isetText((textX+"\n")+textY); + } + } + else if(action == _aRemoveCurve) + { + emit legendItemRemoved(_curve); + } + else if(action == _aMoveUp) + { + emit moveUpRequest(this); + } + else if(action == _aMoveDown) + { + emit moveDownRequest(this); + } +} + +QPixmap UPlotLegendItem::createSymbol(const QPen & pen, const QBrush & brush) +{ + QPixmap pixmap(50, 50); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QPen p = pen; + p.setWidthF(4.0); + painter.setPen(p); + painter.drawLine(0.0, 25.0, 50.0, 25.0); + return pixmap; +} + + + + + + +UPlotLegend::UPlotLegend(QWidget * parent) : + QWidget(parent), + _flat(true) +{ + //menu + _aUseFlatButtons = new QAction(tr("Use flat buttons"), this); + _aUseFlatButtons->setCheckable(true); + _aUseFlatButtons->setChecked(_flat); + _menu = new QMenu(tr("Legend"), this); + _menu->addAction(_aUseFlatButtons); + + QVBoxLayout * vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins(0,0,0,0); + this->setLayout(vLayout); + vLayout->addStretch(0); + vLayout->setSpacing(0); +} + +UPlotLegend::~UPlotLegend() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +void UPlotLegend::setFlat(bool on) +{ + if(_flat != on) + { + _flat = on; + QList items = this->findChildren(); + for(int i=0; isetFlat(_flat); + items.at(i)->setChecked(!items.at(i)->isChecked()); + } + _aUseFlatButtons->setChecked(_flat); + } +} + +void UPlotLegend::addItem(UPlotCurve * curve) +{ + if(curve) + { + UPlotLegendItem * legendItem = new UPlotLegendItem(curve, this); + legendItem->setAutoDefault(false); + legendItem->setFlat(_flat); + legendItem->setCheckable(true); + legendItem->setChecked(false); + connect(legendItem, SIGNAL(toggled(bool)), this, SLOT(redirectToggled(bool))); + connect(legendItem, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeLegendItem(const UPlotCurve *))); + connect(legendItem, SIGNAL(moveUpRequest(UPlotLegendItem *)), this, SLOT(moveUp(UPlotLegendItem *))); + connect(legendItem, SIGNAL(moveDownRequest(UPlotLegendItem *)), this, SLOT(moveDown(UPlotLegendItem *))); + + // layout + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(legendItem); + hLayout->addStretch(0); + hLayout->setMargin(0); + + // add to the legend + ((QVBoxLayout*)this->layout())->insertLayout(this->layout()->count()-1, hLayout); + } +} + +bool UPlotLegend::remove(const UPlotCurve * curve) +{ + QList items = this->findChildren(); + for(int i=0; icurve() == curve) + { + delete items.at(i); + return true; + } + } + return false; +} + +void UPlotLegend::removeLegendItem(const UPlotCurve * curve) +{ + if(this->remove(curve)) + { + emit legendItemRemoved(curve); + } +} + +void UPlotLegend::moveUp(UPlotLegendItem * item) +{ + int index = -1; + QLayoutItem * layoutItem = 0; + for(int i=0; ilayout()->count(); ++i) + { + if(this->layout()->itemAt(i)->layout() && + this->layout()->itemAt(i)->layout()->indexOf(item) != -1) + { + layoutItem = this->layout()->itemAt(i); + index = i; + break; + } + } + if(index > 0 && layoutItem) + { + this->layout()->removeItem(layoutItem); + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(layoutItem->layout()->itemAt(0)->widget()); + hLayout->addStretch(0); + hLayout->setMargin(0); + ((QVBoxLayout*)this->layout())->insertLayout(index-1, hLayout); + delete layoutItem; + emit legendItemMoved(item->curve(), index-1); + } +} + +void UPlotLegend::moveDown(UPlotLegendItem * item) +{ + int index = -1; + QLayoutItem * layoutItem = 0; + for(int i=0; ilayout()->count(); ++i) + { + if(this->layout()->itemAt(i)->layout() && + this->layout()->itemAt(i)->layout()->indexOf(item) != -1) + { + layoutItem = this->layout()->itemAt(i); + index = i; + break; + } + } + if(index < this->layout()->count()-2 && layoutItem) + { + this->layout()->removeItem(layoutItem); + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(layoutItem->layout()->itemAt(0)->widget()); + hLayout->addStretch(0); + hLayout->setMargin(0); + ((QVBoxLayout*)this->layout())->insertLayout(index+1, hLayout); + delete layoutItem; + emit legendItemMoved(item->curve(), index+1); + } +} + +void UPlotLegend::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aUseFlatButtons) + { + this->setFlat(_aUseFlatButtons->isChecked()); + } +} + +void UPlotLegend::redirectToggled(bool toggled) +{ + if(sender()) + { + UPlotLegendItem * item = qobject_cast(sender()); + if(item) + { + emit legendItemToggled(item->curve(), _flat?!toggled:toggled); + } + } +} + + + + + + + +UOrientableLabel::UOrientableLabel(const QString & text, Qt::Orientation orientation, QWidget * parent) : + QLabel(text, parent), + _orientation(orientation) +{ +} + +UOrientableLabel::~UOrientableLabel() +{ +} + +QSize UOrientableLabel::sizeHint() const +{ + QSize size = QLabel::sizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; + +} + +QSize UOrientableLabel::minimumSizeHint() const +{ + QSize size = QLabel::minimumSizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; +} + +void UOrientableLabel::setOrientation(Qt::Orientation orientation) +{ + _orientation = orientation; + switch(orientation) + { + case Qt::Horizontal: + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + + case Qt::Vertical: + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + break; + } +} + +void UOrientableLabel::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + QRect r = rect(); + switch (_orientation) + { + case Qt::Horizontal: + break; + case Qt::Vertical: + p.rotate(-90); + p.translate(-height(), 0); + QSize size = r.size(); + size.transpose(); + r.setSize(size); + break; + } + p.drawText(r, this->alignment() | (this->wordWrap()?Qt::TextWordWrap:0), this->text()); +} + + + + + + + + + + + + + +UPlot::UPlot(QWidget *parent) : + QWidget(parent), + _maxVisibleItems(-1), + _autoScreenCaptureFormat("png"), + _bgColor(Qt::white) +{ + this->setupUi(); + this->createActions(); + this->createMenus(); + + // This will update actions + this->showLegend(true); + this->setGraphicsView(false); + this->setMaxVisibleItems(0); + this->showGrid(false); + this->showRefreshRate(false); + this->keepAllData(false); + + for(int i=0; i<4; ++i) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + if(i<2) + { + _fixedAxis[i] = false; + } + } + + _mouseCurrentPos = _mousePressedPos; // for zooming + + _refreshIntervalTime.start(); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + + _penStyleCount = rand() % 10 + 1; // rand 1->10 + _workingDirectory = QDir::homePath(); +} + +UPlot::~UPlot() +{ + _aAutoScreenCapture->setChecked(false); +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->title().toStdString().c_str()); +#endif + this->removeCurves(); +} + +void UPlot::setupUi() +{ + _legend = new UPlotLegend(this); + _view = new QGraphicsView(this); + _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setScene(new QGraphicsScene(0,0,0,0,this)); + _view->setStyleSheet( "QGraphicsView { border-style: none; }" ); + _sceneRoot = _view->scene()->addText(""); + _sceneRoot->translate(0,0); + _graphicsViewHolder = new QWidget(this); + _graphicsViewHolder->setMinimumSize(100,100); + _graphicsViewHolder->setMouseTracking(true); + _verticalAxis = new UPlotAxis(Qt::Vertical, 0, 1, this); + _horizontalAxis = new UPlotAxis(Qt::Horizontal, 0, 1, this); + _title = new QLabel(""); + _xLabel = new QLabel(""); + _refreshRate = new QLabel(""); + _yLabel = new UOrientableLabel(""); + _yLabel->setOrientation(Qt::Vertical); + _title->setAlignment(Qt::AlignCenter); + _xLabel->setAlignment(Qt::AlignCenter); + _yLabel->setAlignment(Qt::AlignCenter); + _refreshRate->setAlignment(Qt::AlignCenter); + _title->setWordWrap(true); + _xLabel->setWordWrap(true); + _yLabel->setWordWrap(true); + _title->setVisible(false); + _xLabel->setVisible(false); + _yLabel->setVisible(false); + _refreshRate->setVisible(false); + + //layouts + QVBoxLayout * vLayout = new QVBoxLayout(_graphicsViewHolder); + vLayout->setContentsMargins(0,0,0,0); + vLayout->addWidget(_view); + + QGridLayout * grid = new QGridLayout(this); + grid->setContentsMargins(0,0,0,0); + grid->addWidget(_title, 0, 2); + grid->addWidget(_yLabel, 1, 0); + grid->addWidget(_verticalAxis, 1, 1); + grid->addWidget(_refreshRate, 2, 1); + grid->addWidget(_graphicsViewHolder, 1, 2); + grid->setColumnStretch(2, 1); + grid->setRowStretch(1, 1); + grid->addWidget(_horizontalAxis, 2, 2); + grid->addWidget(_xLabel, 3, 2); + grid->addWidget(_legend, 1, 3); + + connect(_legend, SIGNAL(legendItemToggled(const UPlotCurve *, bool)), this, SLOT(showCurve(const UPlotCurve *, bool))); + connect(_legend, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeCurve(const UPlotCurve *))); + connect(_legend, SIGNAL(legendItemMoved(const UPlotCurve *, int)), this, SLOT(moveCurve(const UPlotCurve *, int))); +} + +void UPlot::createActions() +{ + _aShowLegend = new QAction(tr("Show legend"), this); + _aShowLegend->setCheckable(true); + _aShowGrid = new QAction(tr("Show grid"), this); + _aShowGrid->setCheckable(true); + _aShowRefreshRate = new QAction(tr("Show refresh rate"), this); + _aShowRefreshRate->setCheckable(true); + _aMouseTracking = new QAction(tr("Mouse tracking"), this); + _aMouseTracking->setCheckable(true); + _aGraphicsView = new QAction(tr("Graphics view"), this); + _aGraphicsView->setCheckable(true); + _aKeepAllData = new QAction(tr("Keep all data"), this); + _aKeepAllData->setCheckable(true); + _aLimit0 = new QAction(tr("No maximum items shown"), this); + _aLimit10 = new QAction(tr("10"), this); + _aLimit50 = new QAction(tr("50"), this); + _aLimit100 = new QAction(tr("100"), this); + _aLimit500 = new QAction(tr("500"), this); + _aLimit1000 = new QAction(tr("1000"), this); + _aLimitCustom = new QAction(tr(""), this); + _aLimit0->setCheckable(true); + _aLimit10->setCheckable(true); + _aLimit50->setCheckable(true); + _aLimit100->setCheckable(true); + _aLimit500->setCheckable(true); + _aLimit1000->setCheckable(true); + _aLimitCustom->setCheckable(true); + _aLimitCustom->setVisible(false); + _aAddVerticalLine = new QAction(tr("Vertical line..."), this); + _aAddHorizontalLine = new QAction(tr("Horizontal line..."), this); + _aChangeTitle = new QAction(tr("Change title"), this); + _aChangeXLabel = new QAction(tr("Change X label..."), this); + _aChangeYLabel = new QAction(tr("Change Y label..."), this); + _aChangeBackgroundColor = new QAction(tr("Change bg color..."), this); + _aYLabelVertical = new QAction(tr("Vertical orientation"), this); + _aYLabelVertical->setCheckable(true); + _aYLabelVertical->setChecked(true); + _aSaveFigure = new QAction(tr("Save figure..."), this); + _aAutoScreenCapture = new QAction(tr("Auto screen capture..."), this); + _aAutoScreenCapture->setCheckable(true); + _aClearData = new QAction(tr("Clear data"), this); + + QActionGroup * grpLimit = new QActionGroup(this); + grpLimit->addAction(_aLimit0); + grpLimit->addAction(_aLimit10); + grpLimit->addAction(_aLimit50); + grpLimit->addAction(_aLimit100); + grpLimit->addAction(_aLimit500); + grpLimit->addAction(_aLimit1000); + grpLimit->addAction(_aLimitCustom); + _aLimit0->setChecked(true); +} + +void UPlot::createMenus() +{ + _menu = new QMenu(tr("Plot"), this); + _menu->addAction(_aShowLegend); + _menu->addAction(_aShowGrid); + _menu->addAction(_aShowRefreshRate); + _menu->addAction(_aMouseTracking); + _menu->addAction(_aGraphicsView); + _menu->addAction(_aKeepAllData); + _menu->addSeparator()->setStatusTip(tr("Maximum items shown")); + _menu->addAction(_aLimit0); + _menu->addAction(_aLimit10); + _menu->addAction(_aLimit50); + _menu->addAction(_aLimit100); + _menu->addAction(_aLimit500); + _menu->addAction(_aLimit1000); + _menu->addAction(_aLimitCustom); + _menu->addSeparator(); + QMenu * addLineMenu = _menu->addMenu(tr("Add line")); + addLineMenu->addAction(_aAddHorizontalLine); + addLineMenu->addAction(_aAddVerticalLine); + _menu->addSeparator(); + _menu->addAction(_aChangeTitle); + _menu->addAction(_aChangeXLabel); + QMenu * yLabelMenu = _menu->addMenu(tr("Y label")); + yLabelMenu->addAction(_aChangeYLabel); + yLabelMenu->addAction(_aYLabelVertical); + _menu->addAction(_aChangeBackgroundColor); + _menu->addAction(_aSaveFigure); + _menu->addAction(_aAutoScreenCapture); + _menu->addSeparator(); + _menu->addAction(_aClearData); + +} + +UPlotCurve * UPlot::addCurve(const QString & curveName, const QColor & color) +{ + // add curve + UPlotCurve * curve = new UPlotCurve(curveName, this); + if(color.isValid()) + { + curve->setPen(color); + } + else + { + curve->setPen(this->getRandomPenColored()); + } + this->addCurve(curve); + return curve; +} + +bool UPlot::addCurve(UPlotCurve * curve, bool ownershipTransferred) +{ + if(curve) + { +#if PRINT_DEBUG + ULOGGER_DEBUG("Adding curve \"%s\" to plot \"%s\"...", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); +#endif + // only last curve can trigger an update, so disable previous connections + if(!qobject_cast(curve)) + { + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + disconnect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + // add curve + _curves.append(curve); + curve->attach(this); + curve->setItemsColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.red(), _bgColor.alpha())); + if(ownershipTransferred) + { + curve->setParent(this); + } + this->updateAxis(curve); + curve->setXStart(_axisMaximums[1]); + + connect(curve, SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + + _legend->addItem(curve); + +#if PRINT_DEBUG + ULOGGER_DEBUG("Curve \"%s\" added to plot \"%s\"", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); +#endif + + return true; + } + else + { + ULOGGER_ERROR("The curve is null!"); + } + return false; +} + +QStringList UPlot::curveNames() +{ + QStringList names; + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter) + { + names.append((*iter)->name()); + } + } + return names; +} + +bool UPlot::contains(const QString & curveName) +{ + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter && (*iter)->name().compare(curveName) == 0) + { + return true; + } + } + return false; +} + +QPen UPlot::getRandomPenColored() +{ + return QPen((Qt::GlobalColor)(_penStyleCount++ % 12 + 7 )); +} + +void UPlot::replot(QPainter * painter) +{ + if(_maxVisibleItems>0) + { + UPlotCurve * c = 0; + int maxItem = 0; + // find the curve with the most items + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible() && ((UPlotCurve *)(*i))->itemsSize() > maxItem) + { + c = *i; + maxItem = c->itemsSize(); + } + } + if(c && (maxItem-1)/2+1 > _maxVisibleItems && _axisMaximums[0] < c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x()) + { + _axisMaximums[0] = c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x(); + } + } + + float axis[4] = {0}; + for(int i=0; i<4; ++i) + { + axis[i] = _axisMaximums[i]; + } + + _verticalAxis->setAxis(axis[2], axis[3]); + _horizontalAxis->setAxis(axis[0], axis[1]); + if(_aGraphicsView->isChecked() && !painter) + { + _verticalAxis->update(); + _horizontalAxis->update(); + } + + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + + QRectF newRect(0,0, _graphicsViewHolder->size().width(), _graphicsViewHolder->size().height()); + _view->scene()->setSceneRect(newRect); + float borderHor = (float)_horizontalAxis->border(); + float borderVer = (float)_verticalAxis->border(); + + //grid + qDeleteAll(hGridLines); + hGridLines.clear(); + qDeleteAll(vGridLines); + vGridLines.clear(); + if(_aShowGrid->isChecked()) + { + // TODO make a PlotGrid class ? + float w = newRect.width()-(borderHor*2); + float h = newRect.height()-(borderVer*2); + float stepH = w / float(_horizontalAxis->count()); + float stepV = h / float(_verticalAxis->count()); + QPen dashPen(Qt::DashLine); + dashPen.setColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.blue(), 100)); + QPen pen(dashPen.color()); + for(float i=0.0f; i*stepV <= h+stepV; i+=5.0f) + { + //horizontal lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->save(); + painter->setPen(pen); + painter->drawLine(0, stepV*i+borderVer+0.5f, borderHor, stepV*i+borderVer+0.5f); + + painter->setPen(dashPen); + painter->drawLine(borderHor, stepV*i+borderVer+0.5f, w+borderHor, stepV*i+borderVer+0.5f); + + painter->setPen(pen); + painter->drawLine(w+borderHor, stepV*i+borderVer+0.5f, w+borderHor*2, stepV*i+borderVer+0.5f); + painter->restore(); + } + } + else + { + hGridLines.append(new QGraphicsLineItem(0, stepV*i+borderVer, borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(pen); + hGridLines.append(new QGraphicsLineItem(borderHor, stepV*i+borderVer, w+borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(dashPen); + hGridLines.append(new QGraphicsLineItem(w+borderHor, stepV*i+borderVer, w+borderHor*2, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(pen); + } + } + for(float i=0; i*stepH < w+stepH; i+=5.0f) + { + //vertical lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->save(); + painter->setPen(pen); + painter->drawLine(stepH*i+borderHor+0.5f, 0, stepH*i+borderHor+0.5f, borderVer); + + painter->setPen(dashPen); + painter->drawLine(stepH*i+borderHor+0.5f, borderVer, stepH*i+borderHor+0.5f, h+borderVer); + + painter->setPen(pen); + painter->drawLine(stepH*i+borderHor+0.5f, h+borderVer, stepH*i+borderHor+0.5f, h+borderVer*2); + painter->restore(); + } + } + else + { + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, 0, stepH*i+borderHor, borderVer, _sceneRoot)); + vGridLines.last()->setPen(pen); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, borderVer, stepH*i+borderHor, h+borderVer, _sceneRoot)); + vGridLines.last()->setPen(dashPen); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, h+borderVer, stepH*i+borderHor, h+borderVer*2, _sceneRoot)); + vGridLines.last()->setPen(pen); + } + } + } + + // curves + float scaleX = 1; + float scaleY = 1; + float den = 0; + den = axis[1] - axis[0]; + if(den != 0) + { + scaleX = (newRect.width()-(borderHor*2)) / den; + } + den = axis[3] - axis[2]; + if(den != 0) + { + scaleY = (newRect.height()-(borderVer*2)) / den; + } + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible()) + { + float xDir = 1.0f; + float yDir = -1.0f; + (*i)->update(scaleX, + scaleY, + xDir<0?axis[1]+borderHor/scaleX:-(axis[0]-borderHor/scaleX), + yDir<0?axis[3]+borderVer/scaleY:-(axis[2]-borderVer/scaleY), + xDir, + yDir, + _aKeepAllData->isChecked()?0:_maxVisibleItems); + if(painter) + { + (*i)->draw(painter, QRect(0,0,_graphicsViewHolder->rect().width(), _graphicsViewHolder->rect().height())); + } + } + } + + // Update refresh rate + if(_aShowRefreshRate->isChecked()) + { + int refreshRate = qRound(1000.0f/float(_refreshIntervalTime.restart())); + if(refreshRate > 0 && refreshRate < _lowestRefreshRate) + { + _lowestRefreshRate = refreshRate; + } + // Refresh the label only after each 1000 ms + if(_refreshStartTime.elapsed() > 1000) + { + _refreshRate->setText(QString::number(_lowestRefreshRate)); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + } + } +} + +void UPlot::setFixedXAxis(float x1, float x2) +{ + _fixedAxis[0] = true; + _axisMaximums[0] = x1; + _axisMaximums[1] = x2; +} + +void UPlot::setFixedYAxis(float y1, float y2) +{ + _fixedAxis[1] = true; + _axisMaximums[2] = y1; + _axisMaximums[3] = y2; +} + +void UPlot::updateAxis(const UPlotCurve * curve) +{ + if(curve && curve->isVisible() && curve->itemsSize() && curve->isMinMaxValid()) + { + const QVector & minMax = curve->getMinMax(); + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", minMax[0], minMax[1], minMax[2], minMax[3]); + if(minMax.size() != 4) + { + ULOGGER_ERROR("minMax size != 4 ?!?"); + return; + } + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } +} + +bool UPlot::updateAxis(float x1, float x2, float y1, float y2) +{ + bool modified = false; + modified = updateAxis(x1,y1); + if(!modified) + { + modified = updateAxis(x2,y2); + } + else + { + updateAxis(x2,y2); + } + return modified; +} + +bool UPlot::updateAxis(float x, float y) +{ + //ULOGGER_DEBUG("x=%f, y=%f", x,y); + bool modified = false; + if(!_fixedAxis[0] && (!_axisMaximumsSet[0] || x < _axisMaximums[0])) + { + _axisMaximums[0] = x; + _axisMaximumsSet[0] = true; + modified = true; + } + + if(!_fixedAxis[0] && (!_axisMaximumsSet[1] || x > _axisMaximums[1])) + { + _axisMaximums[1] = x; + _axisMaximumsSet[1] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[2] || y < _axisMaximums[2])) + { + _axisMaximums[2] = y; + _axisMaximumsSet[2] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[3] || y > _axisMaximums[3])) + { + _axisMaximums[3] = y; + _axisMaximumsSet[3] = true; + modified = true; + } + + return modified; +} + +void UPlot::updateAxis() +{ + //Reset the axis + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + } + } + + for(int i=0; i<_curves.size(); ++i) + { + if(_curves.at(i)->isVisible() && _curves.at(i)->isMinMaxValid()) + { + const QVector & minMax = _curves.at(i)->getMinMax(); + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + } + } + + _aGraphicsView->isChecked()?this->replot(0):this->update(); + + this->captureScreen(); +} + +void UPlot::paintEvent(QPaintEvent * event) +{ +#if PRINT_DEBUG + UDEBUG(""); +#endif + if(!_aGraphicsView->isChecked()) + { + QPainter painter(this); + painter.translate(_graphicsViewHolder->pos()); + painter.save(); + painter.setBrush(_bgColor); + painter.setPen(QPen(Qt::NoPen)); + painter.drawRect(_graphicsViewHolder->rect()); + painter.restore(); + + this->replot(&painter); + + if(_mouseCurrentPos != _mousePressedPos) + { + painter.save(); + int left, top, right, bottom; + left = _mousePressedPos.x() < _mouseCurrentPos.x() ? _mousePressedPos.x()-_graphicsViewHolder->x():_mouseCurrentPos.x()-_graphicsViewHolder->x(); + top = _mousePressedPos.y() < _mouseCurrentPos.y() ? _mousePressedPos.y()-1-_graphicsViewHolder->y():_mouseCurrentPos.y()-1-_graphicsViewHolder->y(); + right = _mousePressedPos.x() > _mouseCurrentPos.x() ? _mousePressedPos.x()-_graphicsViewHolder->x():_mouseCurrentPos.x()-_graphicsViewHolder->x(); + bottom = _mousePressedPos.y() > _mouseCurrentPos.y() ? _mousePressedPos.y()-_graphicsViewHolder->y():_mouseCurrentPos.y()-_graphicsViewHolder->y(); + if(left <= 0) + { + left = 1; + } + if(right >= _graphicsViewHolder->width()) + { + right = _graphicsViewHolder->width()-1; + } + if(top <= 0) + { + top = 1; + } + if(bottom >= _graphicsViewHolder->height()) + { + bottom = _graphicsViewHolder->height()-1; + } + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(QColor(255-_bgColor.red(),255-_bgColor.green(),255-_bgColor.blue(),100))); + painter.drawRect(0, 0, _graphicsViewHolder->width(), top); + painter.drawRect(0, top, left, bottom-top); + painter.drawRect(right, top, _graphicsViewHolder->width()-right, bottom-top); + painter.drawRect(0, bottom, _graphicsViewHolder->width(), _graphicsViewHolder->height()-bottom); + painter.restore(); + } + } + else + { + QWidget::paintEvent(event); + } +} + +void UPlot::resizeEvent(QResizeEvent * event) +{ + if(_aGraphicsView->isChecked()) + { + this->replot(0); + } + QWidget::resizeEvent(event); +} + +void UPlot::mousePressEvent(QMouseEvent * event) +{ + _mousePressedPos = event->pos(); + _mouseCurrentPos = _mousePressedPos; + QWidget::mousePressEvent(event); +} + +void UPlot::mouseMoveEvent(QMouseEvent * event) +{ + if(!_aGraphicsView->isChecked()) + { + if(!(QApplication::mouseButtons() & Qt::LeftButton)) + { + _mousePressedPos = _mouseCurrentPos; + } + + float x,y; + if(mousePosToValue(event->pos(), x ,y)) + { + if(QApplication::mouseButtons() & Qt::LeftButton) + { + _mouseCurrentPos = event->pos(); + this->update(); + } + + int xPos = event->pos().x() - _graphicsViewHolder->pos().x(); + int yPos = event->pos().y() - _graphicsViewHolder->pos().y(); + if((QApplication::mouseButtons() & Qt::LeftButton) || + (_aMouseTracking->isChecked() && xPos>=0 && yPos>=0 && xPos<_graphicsViewHolder->width() && yPos<_graphicsViewHolder->height())) + { + QToolTip::showText(event->globalPos(), QString("%1,%2").arg(x).arg(y)); + } + else + { + QToolTip::hideText(); + } + } + else + { + QToolTip::hideText(); + } + } + QWidget::mouseMoveEvent(event); +} + +void UPlot::mouseReleaseEvent(QMouseEvent * event) +{ + if(_mousePressedPos != _mouseCurrentPos) + { + int left,top,bottom,right; + + left = _mousePressedPos.x() < _mouseCurrentPos.x() ? _mousePressedPos.x():_mouseCurrentPos.x(); + top = _mousePressedPos.y() < _mouseCurrentPos.y() ? _mousePressedPos.y():_mouseCurrentPos.y(); + right = _mousePressedPos.x() > _mouseCurrentPos.x() ? _mousePressedPos.x():_mouseCurrentPos.x(); + bottom = _mousePressedPos.y() > _mouseCurrentPos.y() ? _mousePressedPos.y():_mouseCurrentPos.y(); + + if(right - left > 5 || bottom - top > 5) + { + float axis[4]; + if(mousePosToValue(QPoint(left, top), axis[0], axis[3]) && mousePosToValue(QPoint(right, bottom), axis[1], axis[2])) + { +#if PRINT_DEBUG + UDEBUG("resize! new axis = [%f, %f, %f, %f]", axis[0], axis[1], axis[2], axis[3]); +#endif + //update axis (only if not fixed) + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = axis[i]; + } + } + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } + } + _mousePressedPos = _mouseCurrentPos; + } + QWidget::mouseReleaseEvent(event); +} + +void UPlot::mouseDoubleClickEvent(QMouseEvent * event) +{ + this->updateAxis(); + QWidget::mouseDoubleClickEvent(event); +} + +bool UPlot::mousePosToValue(const QPoint & pos, float & x, float & y) +{ + int xPos = pos.x() - _graphicsViewHolder->pos().x() - _horizontalAxis->border(); + int yPos = pos.y() - _graphicsViewHolder->pos().y() - _verticalAxis->border(); + int maxX = _graphicsViewHolder->width() - _horizontalAxis->border()*2; + int maxY = _graphicsViewHolder->height() - _verticalAxis->border()*2; + if(maxX == 0 || maxY == 0) + { + return false; + } + + if(xPos < 0) + { + xPos = 0; + } + else if(xPos > maxX) + { + xPos = maxX; + } + + if(yPos < 0) + { + yPos = 0; + } + else if(yPos > maxY) + { + yPos = maxY; + } + + //UDEBUG("IN"); + //UDEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + //UDEBUG("border hor=%f ver=%f", (float)_horizontalAxis->border(), (float)_verticalAxis->border()); + //UDEBUG("rect = %d,%d %d,%d", _graphicsViewHolder->pos().x(), _graphicsViewHolder->pos().y(), _graphicsViewHolder->width(), _graphicsViewHolder->height()); + //UDEBUG("%d,%d", event->pos().x(), event->pos().y()); + //UDEBUG("x/y %d,%d", x, y); + //UDEBUG("max %d,%d", maxX, maxY); + + //UDEBUG("map %f,%f", x, y); + x = _axisMaximums[0] + float(xPos)*(_axisMaximums[1] - _axisMaximums[0]) / float(maxX); + y = _axisMaximums[2] + float(maxY - yPos)*(_axisMaximums[3] - _axisMaximums[2]) / float(maxY); + return true; +} + +void UPlot::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + + if(!action) + { + return; + } + else if(action == _aShowLegend) + { + this->showLegend(_aShowLegend->isChecked()); + } + else if(action == _aShowGrid) + { + this->showGrid(_aShowGrid->isChecked()); + } + else if(action == _aShowRefreshRate) + { + this->showRefreshRate(_aShowRefreshRate->isChecked()); + } + else if(action == _aMouseTracking) + { + this->trackMouse(_aMouseTracking->isChecked()); + } + else if(action == _aGraphicsView) + { + this->setGraphicsView(_aGraphicsView->isChecked()); + } + else if(action == _aKeepAllData) + { + this->keepAllData(_aKeepAllData->isChecked()); + } + else if(action == _aLimit0 || + action == _aLimit10 || + action == _aLimit50 || + action == _aLimit100 || + action == _aLimit500 || + action == _aLimit1000 || + action == _aLimitCustom) + { + this->setMaxVisibleItems(action->text().toInt()); + } + else if(action == _aAddVerticalLine || action == _aAddHorizontalLine) + { + bool ok; + QString text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + while(ok && text.isEmpty()) + { + QMessageBox::warning(this, action->text(), tr("The name is not valid or it is already used in this plot.")); + text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + } + if(ok) + { + double min = _axisMaximums[2]; + double max = _axisMaximums[3]; + QString axis = "Y"; + if(action == _aAddVerticalLine) + { + min = _axisMaximums[0]; + max = _axisMaximums[1]; + axis = "X"; + } + double value = QInputDialog::getDouble(this, + action->text(), + tr("%1 value (min=%2, max=%3):").arg(axis).arg(min).arg(max), + (min+max)/2, + -2147483647, + 2147483647, + 4, + &ok); + if(ok) + { + if(action == _aAddHorizontalLine) + { + this->addThreshold(text, value, Qt::Horizontal); + } + else + { + this->addThreshold(text, value, Qt::Vertical); + } + } + } + } + else if(action == _aChangeTitle) + { + bool ok; + QString text = _title->text(); + if(text.isEmpty()) + { + text = this->objectName(); + } + text = QInputDialog::getText(this, _aChangeTitle->text(), tr("Title :"), QLineEdit::Normal, text, &ok); + if(ok) + { + this->setTitle(text); + } + } + else if(action == _aChangeXLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeXLabel->text(), tr("X axis label :"), QLineEdit::Normal, _xLabel->text(), &ok); + if(ok) + { + this->setXLabel(text); + } + } + else if(action == _aChangeYLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeYLabel->text(), tr("Y axis label :"), QLineEdit::Normal, _yLabel->text(), &ok); + if(ok) + { + this->setYLabel(text, _yLabel->orientation()); + } + } + else if(action == _aYLabelVertical) + { + this->setYLabel(_yLabel->text(), _aYLabelVertical->isChecked()?Qt::Vertical:Qt::Horizontal); + } + else if(action == _aChangeBackgroundColor) + { + QColor color = QColorDialog::getColor(_bgColor, this); + if(color.isValid()) + { + this->setBackgroundColor(color); + } + } + else if(action == _aSaveFigure) + { + + QString text; +#ifdef QT_SVG_LIB + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf *.svg"); +#else + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf"); +#endif + if(!text.isEmpty()) + { + bool flatModified = false; + if(!_legend->isFlat()) + { + _legend->setFlat(true); + flatModified = true; + } + + QPalette p(palette()); + // Set background color to white + QColor c = p.color(QPalette::Background); + p.setColor(QPalette::Background, Qt::white); + setPalette(p); + +#ifdef QT_SVG_LIB + if(QFileInfo(text).suffix().compare("svg") == 0) + { + QSvgGenerator generator; + generator.setFileName(text); + generator.setSize(this->size()); + QPainter painter; + painter.begin(&generator); + this->render(&painter); + painter.end(); + } + else + { +#endif + if(QFileInfo(text).suffix().compare("pdf") == 0) + { + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(text); + this->render(&printer); + } + else + { + QPixmap figure = QPixmap::grabWidget(this); + figure.save(text); + } +#ifdef QT_SVG_LIB + } +#endif + // revert background color + p.setColor(QPalette::Background, c); + setPalette(p); + + if(flatModified) + { + _legend->setFlat(false); + } + } + } + else if(action == _aAutoScreenCapture) + { + if(_aAutoScreenCapture->isChecked()) + { + this->selectScreenCaptureFormat(); + } + } + else if(action == _aClearData) + { + this->clearData(); + } + else + { + ULOGGER_WARN("Unknown action"); + } +} + +void UPlot::setWorkingDirectory(const QString & workingDirectory) +{ + if(QDir(_workingDirectory).exists()) + { + _workingDirectory = workingDirectory; + } + else + { + ULOGGER_ERROR("The directory \"%s\" doesn't exist", workingDirectory.toStdString().c_str()); + } +} + +void UPlot::captureScreen() +{ + if(!_aAutoScreenCapture->isChecked()) + { + return; + } + QString targetDir = _workingDirectory + "/ScreensCaptured"; + QDir dir; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + targetDir += this->title().replace(" ", "_"); + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + QString name = (QDateTime::currentDateTime().toString("yyMMddhhmmsszzz") + ".") + _autoScreenCaptureFormat; + QPixmap figure = QPixmap::grabWidget(this); + figure.save(targetDir + name); +} + +void UPlot::selectScreenCaptureFormat() +{ + QStringList items; + items << QString("png") << QString("jpg"); + bool ok; + QString item = QInputDialog::getItem(this, tr("Select format"), tr("Format:"), items, 0, false, &ok); + if(ok && !item.isEmpty()) + { + _autoScreenCaptureFormat = item; + } + this->captureScreen(); +} + +void UPlot::clearData() +{ + for(int i=0; i<_curves.size(); ++i) + { + // Don't clear threshold curves + if(qobject_cast(_curves.at(i)) == 0) + { + _curves.at(i)->clear(); + } + } + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +// for convenience... +UPlotCurveThreshold * UPlot::addThreshold(const QString & name, float value, Qt::Orientation orientation) +{ + UPlotCurveThreshold * curve = new UPlotCurveThreshold(name, value, orientation, this); + QPen pen = curve->pen(); + pen.setStyle((Qt::PenStyle)(_penStyleCount++ % 4 + 2)); + curve->setPen(pen); + if(!this->addCurve(curve)) + { + if(curve) + { + delete curve; + } + } + else + { + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } + return curve; +} + +void UPlot::setTitle(const QString & text) +{ + _title->setText(text); + _title->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setXLabel(const QString & text) +{ + _xLabel->setText(text); + _xLabel->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setYLabel(const QString & text, Qt::Orientation orientation) +{ + _yLabel->setText(text); + _yLabel->setOrientation(orientation); + _yLabel->setVisible(!text.isEmpty()); + _aYLabelVertical->setChecked(orientation==Qt::Vertical); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setBackgroundColor(const QColor & color) +{ + if(color.isValid()) + { + _bgColor = color; + _view->scene()->setBackgroundBrush(QBrush(_bgColor)); + for(QList::iterator iter=_curves.begin(); iter!=_curves.end(); ++iter) + { + (*iter)->setItemsColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.blue(), _bgColor.alpha())); + } + } +} + +void UPlot::addItem(QGraphicsItem * item) +{ + item->setParentItem(_sceneRoot); + item->setZValue(1.0f); +} + +void UPlot::showLegend(bool shown) +{ + _legend->setVisible(shown); + _aShowLegend->setChecked(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::showGrid(bool shown) +{ + _aShowGrid->setChecked(shown); + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +void UPlot::showRefreshRate(bool shown) +{ + _aShowRefreshRate->setChecked(shown); + _refreshRate->setVisible(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::trackMouse(bool tracking) +{ + _aMouseTracking->setChecked(tracking); + this->setMouseTracking(tracking); +} + +void UPlot::setGraphicsView(bool on) +{ + _aGraphicsView->setChecked(on); + _view->setVisible(on); + _aGraphicsView->isChecked()?this->replot(0):this->update(); + _aMouseTracking->setEnabled(!on); +} + +void UPlot::keepAllData(bool kept) +{ + _aKeepAllData->setChecked(kept); +} + +void UPlot::setMaxVisibleItems(int maxVisibleItems) +{ + if(maxVisibleItems <= 0) + { + _aLimit0->setChecked(true); + } + else if(maxVisibleItems == 10) + { + _aLimit10->setChecked(true); + } + else if(maxVisibleItems == 50) + { + _aLimit50->setChecked(true); + } + else if(maxVisibleItems == 100) + { + _aLimit100->setChecked(true); + } + else if(maxVisibleItems == 500) + { + _aLimit500->setChecked(true); + } + else if(maxVisibleItems == 1000) + { + _aLimit1000->setChecked(true); + } + else + { + _aLimitCustom->setVisible(true); + _aLimitCustom->setChecked(true); + _aLimitCustom->setText(QString::number(maxVisibleItems)); + } + _maxVisibleItems = maxVisibleItems; + updateAxis(); +} + +QRectF UPlot::sceneRect() const +{ + return _view->sceneRect(); +} + +void UPlot::removeCurves() +{ + QList tmp = _curves; + for(QList::iterator iter=tmp.begin(); iter!=tmp.end(); ++iter) + { + this->removeCurve(*iter); + } + _curves.clear(); +} + +void UPlot::removeCurve(const UPlotCurve * curve) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); +#if PRINT_DEBUG + ULOGGER_DEBUG("Plot=\"%s\" removing curve=\"%s\"", this->objectName().toStdString().c_str(), curve?curve->name().toStdString().c_str():""); +#endif + if(iter!=_curves.end()) + { + UPlotCurve * c = *iter; + c->detach(this); + _curves.erase(iter); + _legend->remove(c); + if(!qobject_cast(c)) + { + // transfer update connection to next curve + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + connect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + if(c->parent() == this) + { + delete c; + } + // Update axis + updateAxis(); + } +} + +void UPlot::showCurve(const UPlotCurve * curve, bool shown) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); + if(iter!=_curves.end()) + { + UPlotCurve * value = *iter; + if(value->isVisible() != shown) + { + value->setVisible(shown); + this->updateAxis(); + } + } +} + +void UPlot::moveCurve(const UPlotCurve * curve, int index) +{ + // this will change the print order + int currentIndex = -1; + UPlotCurve * c = 0; + for(int i=0; i<_curves.size(); ++i) + { + if(_curves.at(i) == curve) + { + c = _curves.at(i); + currentIndex = i; + break; + } + } + + if(c && currentIndex != index) + { + _curves.removeAt(currentIndex); + QList children = _sceneRoot->childItems(); + _curves.insert(index, c); + if(currentIndex > index) + { + children[currentIndex]->stackBefore(children[index]); + } + else + { + if(currentIndexstackBefore(children[currentIndex]); + } + else + { + children[currentIndex]->stackBefore(children[index]); + } + } + if(currentIndex == children.size()-2 && currentIndex < index) + { + children[index]->stackBefore(children[currentIndex]); + } + } + this->update(); + } +} diff --git a/guilib/src/utilite/UPlot.h b/guilib/src/utilite/UPlot.h new file mode 100644 index 00000000..ac016c55 --- /dev/null +++ b/guilib/src/utilite/UPlot.h @@ -0,0 +1,624 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#ifndef UPLOT_H_ +#define UPLOT_H_ + +#include "rtabmap/utilite/UtiLiteExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QGraphicsView; +class QGraphicsScene; +class QGraphicsItem; +class QFormLayout; + +/** + * UPlotItem is a QGraphicsEllipseItem and can be inherited to do custom behaviors + * on an hoverEnterEvent() for example. + */ +class UTILITE_EXP UPlotItem : public QGraphicsEllipseItem +{ +public: + /** + * Constructor 1. + */ + UPlotItem(qreal dataX, qreal dataY, qreal width=2); + /** + * Constructor 2. + */ + UPlotItem(const QPointF & data, qreal width=2); + virtual ~UPlotItem(); + +public: + void setNextItem(UPlotItem * nextItem); + void setPreviousItem(UPlotItem * previousItem); + void setData(const QPointF & data); + + UPlotItem * nextItem() const {return _nextItem;} + UPlotItem * previousItem() const {return _previousItem;}; + const QPointF & data() const {return _data;} + +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent * event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent * event); + virtual void focusInEvent(QFocusEvent * event); + virtual void focusOutEvent(QFocusEvent * event); + virtual void keyReleaseEvent(QKeyEvent * keyEvent); + + virtual void showDescription(bool shown); +private: + void init(qreal dataX, qreal dataY); + +private: + QPointF _data; + UPlotItem * _previousItem; + UPlotItem * _nextItem; + QGraphicsTextItem * _text; + QGraphicsRectItem * _textBackground; +}; + +class UPlot; + +/** + * UPlotCurve is a curve used to hold data shown in a UPlot. + */ +class UTILITE_EXP UPlotCurve : public QObject +{ + Q_OBJECT + +public: + /** + * Constructor 1 + */ + UPlotCurve(const QString & name, QObject * parent = 0); + /** + * Constructor 2 + */ + UPlotCurve(const QString & name, const QVector data, QObject * parent = 0); + /** + * Constructor 3 + */ + UPlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent = 0); + virtual ~UPlotCurve(); + + /** + * Get pen. + */ + const QPen & pen() const {return _pen;} + /** + * Get brush. + */ + const QBrush & brush() const {return _brush;} + + /** + * Set pen. + */ + void setPen(const QPen & pen); + /** + * Set brush. + */ + void setBrush(const QBrush & brush); + + void setItemsColor(const QColor & color); + QColor itemsColor() const {return _itemsColor;} + + /** + * Get name. + */ + QString name() const {return _name;} + /** + * Get the number of items in the curve (dot + line items). + */ + int itemsSize() const; + QPointF getItemData(int index); + bool isVisible() const {return _visible;} + void setData(QVector & data); // take the ownership + void getData(QVector & x, QVector & y) const; // only call in Qt MainThread + void draw(QPainter * painter, const QRect & limits); + +public slots: + /** + * + * Clear curve's values. + */ + virtual void clear(); + /** + * + * Show or hide the curve. + */ + void setVisible(bool visible); + /** + * + * Set increment of the x values (when auto-increment is used). + */ + void setXIncrement(float increment); + /** + * + * Set starting x value (when auto-increment is used). + */ + void setXStart(float val); + /** + * + * Add a single value, using a custom UPlotItem. + */ + void addValue(UPlotItem * data); // take the ownership + /** + * + * Add a single value y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValue(float y); + /** + * + * Add a single value y at x. + */ + void addValue(float x, float y); + /** + * + * For convenience... + * Add a single value y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValue(const QString & y); + /** + * + * For convenience... + * Add multiple values, using custom UPlotItem. + */ + void addValues(QVector & data); // take the ownership + /** + * + * Add multiple values y at x. Vectors must have the same size. + */ + void addValues(const QVector & xs, const QVector & ys); + /** + * + * Add multiple values y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValues(const QVector & ys); + void addValues(const QVector & ys); // for convenience + /** + * + * Add multiple values y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValues(const std::vector & ys); // for convenience + void addValues(const std::vector & ys); // for convenience + + void setData(const QVector & x, const QVector & y); + void setData(const std::vector & x, const std::vector & y); + void setData(const QVector & y); + void setData(const std::vector & y); + +signals: + /** + * + * emitted when data is changed. + */ + void dataChanged(const UPlotCurve *); + +protected: + friend class UPlot; + void attach(UPlot * plot); + void detach(UPlot * plot); + void updateMinMax(); + const QVector & getMinMax() const {return _minMax;} + int removeItem(int index); + void _addValue(UPlotItem * data);; + virtual bool isMinMaxValid() const {return _minMax.size();} + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept); + QList _items; + UPlot * _plot; + +private: + void removeItem(UPlotItem * item); + +private: + QString _name; + QPen _pen; + QBrush _brush; + float _xIncrement; + float _xStart; + bool _visible; + bool _valuesShown; + QVector _minMax; // minX, maxX, minY, maxY + QGraphicsRectItem * _rootItem; + QColor _itemsColor; +}; + + +/** + * A special UPlotCurve that shows as a line at the specified value, spanning all the UPlot. + */ +class UTILITE_EXP UPlotCurveThreshold : public UPlotCurve +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotCurveThreshold(const QString & name, float thesholdValue, Qt::Orientation orientation = Qt::Horizontal, QObject * parent = 0); + virtual ~UPlotCurveThreshold(); + +public slots: + /** + * Set threshold value. + */ + void setThreshold(float threshold); + /** + * Set orientation (Qt::Horizontal or Qt::Vertical). + */ + void setOrientation(Qt::Orientation orientation); + +protected: + friend class UPlot; + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept); + virtual bool isMinMaxValid() const {return false;} + +private: + Qt::Orientation _orientation; +}; + +/** + * The UPlot axis object. + */ +class UTILITE_EXP UPlotAxis : public QWidget +{ +public: + /** + * Constructor. + */ + UPlotAxis(Qt::Orientation orientation = Qt::Horizontal, float min=0, float max=1, QWidget * parent = 0); + virtual ~UPlotAxis(); + +public: + /** + * Set axis minimum and maximum values, compute the resulting + * intervals depending on the size of the axis. + */ + void setAxis(float & min, float & max); + /** + * Size of the border between the first line and the beginning of the widget. + */ + int border() const {return _border;} + /** + * Interval step value. + */ + int step() const {return _step;} + /** + * Number of intervals. + */ + int count() const {return _count;} + /** + * Reverse the axis (for vertical :bottom->up, for horizontal :right->left) + */ + void setReversed(bool reversed); // Vertical :bottom->up, horizontal :right->left + +protected: + virtual void paintEvent(QPaintEvent * event); + +private: + Qt::Orientation _orientation; + float _min; + float _max; + int _count; + int _step; + bool _reversed; + int _gradMaxDigits; + int _border; +}; + + +/** + * The UPlot legend item. Used internally by UPlot. + */ +class UTILITE_EXP UPlotLegendItem : public QPushButton +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotLegendItem(UPlotCurve * curve, QWidget * parent = 0); + virtual ~UPlotLegendItem(); + const UPlotCurve * curve() const {return _curve;} + QPixmap createSymbol(const QPen & pen, const QBrush & brush); + +signals: + void legendItemRemoved(const UPlotCurve *); + void moveUpRequest(UPlotLegendItem *); + void moveDownRequest(UPlotLegendItem *); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private: + UPlotCurve * _curve; + QMenu * _menu; + QAction * _aChangeText; + QAction * _aResetText; + QAction * _aChangeColor; + QAction * _aCopyToClipboard; + QAction * _aRemoveCurve; + QAction * _aMoveUp; + QAction * _aMoveDown; +}; + +/** + * The UPlot legend. Used internally by UPlot. + */ +class UTILITE_EXP UPlotLegend : public QWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotLegend(QWidget * parent = 0); + virtual ~UPlotLegend(); + + void setFlat(bool on); + bool isFlat() const {return _flat;} + void addItem(UPlotCurve * curve); + bool remove(const UPlotCurve * curve); + +private slots: + void removeLegendItem(const UPlotCurve * curve); + void moveUp(UPlotLegendItem * item); + void moveDown(UPlotLegendItem * item); + +signals: + void legendItemRemoved(const UPlotCurve * curve); + void legendItemToggled(const UPlotCurve * curve, bool toggled); + void legendItemMoved(const UPlotCurve * curve, int); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private slots: + void redirectToggled(bool); + +private: + bool _flat; + QMenu * _menu; + QAction * _aUseFlatButtons; +}; + + +/** + * Orientable QLabel. Inherit QLabel and let you to specify the orientation. + */ +class UTILITE_EXP UOrientableLabel : public QLabel +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UOrientableLabel(const QString & text, Qt::Orientation orientation = Qt::Horizontal, QWidget * parent = 0); + virtual ~UOrientableLabel(); + /** + * Get orientation. + */ + Qt::Orientation orientation() const {return _orientation;} + /** + * Set orientation (Qt::Vertical or Qt::Horizontal). + */ + void setOrientation(Qt::Orientation orientation); + QSize sizeHint() const; + QSize minimumSizeHint() const; +protected: + virtual void paintEvent(QPaintEvent* event); +private: + Qt::Orientation _orientation; +}; + +/** + * UPlot is a QWidget to create a plot like MATLAB, and + * incrementally add new values like a scope using Qt signals/slots. + * Many customizations can be done at runtime with the right-click menu. + * @image html UPlot.gif + * @image html UPlotMenu.png + * + * Example: + * @code + * #include "utilite/UPlot.h" + * #include + * + * int main(int argc, char * argv[]) + * { + * QApplication app(argc, argv); + * UPlot plot; + * UPlotCurve * curve = plot.addCurve("My curve"); + * float y[10] = {0, 1, 2, 3, -3, -2, -1, 0, 1, 2}; + * curve->addValues(std::vector(y, y+10)); + * plot.showGrid(true); + * plot.setGraphicsView(true); + * plot.show(); + * app.exec(); + * return 0; + * } + * @endcode + * @image html SimplePlot.tiff + * + * + */ +class UTILITE_EXP UPlot : public QWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlot(QWidget * parent = 0); + virtual ~UPlot(); + + /** + * Add a curve. The returned curve doesn't need to be deallocated (UPlot keeps the ownership). + */ + UPlotCurve * addCurve(const QString & curveName, const QColor & color = QColor()); + /** + * Add a curve. Ownership is transferred to UPlot if ownershipTransferred=true. + */ + bool addCurve(UPlotCurve * curve, bool ownershipTransferred = true); + /** + * Get all curve names. + */ + QStringList curveNames(); + bool contains(const QString & curveName); + void removeCurves(); + /** + * Add a threshold to the plot. + */ + UPlotCurveThreshold * addThreshold(const QString & name, float value, Qt::Orientation orientation = Qt::Horizontal); + QString title() const {return this->objectName();} + QPen getRandomPenColored(); + void showLegend(bool shown); + void showGrid(bool shown); + void showRefreshRate(bool shown); + void trackMouse(bool tracking); + void keepAllData(bool kept); + void showXAxis(bool shown) {_horizontalAxis->setVisible(shown);} + void showYAxis(bool shown) {_verticalAxis->setVisible(shown);} + void setVariableXAxis() {_fixedAxis[0] = false;} + void setVariableYAxis() {_fixedAxis[1] = false;} + void setFixedXAxis(float x1, float x2); + void setFixedYAxis(float y1, float y2); + void setMaxVisibleItems(int maxVisibleItems); + void setTitle(const QString & text); + void setXLabel(const QString & text); + void setYLabel(const QString & text, Qt::Orientation orientation = Qt::Vertical); + void setWorkingDirectory(const QString & workingDirectory); + void setGraphicsView(bool on); + void setBackgroundColor(const QColor & color); + QRectF sceneRect() const; + +public slots: + /** + * + * Remove a curve. If UPlot is the parent of the curve, the curve is deleted. + */ + void removeCurve(const UPlotCurve * curve); + void showCurve(const UPlotCurve * curve, bool shown); + void updateAxis(); //reset axis and recompute it with all curves minMax + /** + * + * Clear all curves' data. + */ + void clearData(); + +private slots: + void captureScreen(); + void updateAxis(const UPlotCurve * curve); + void moveCurve(const UPlotCurve *, int index); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + virtual void paintEvent(QPaintEvent * event); + virtual void resizeEvent(QResizeEvent * event); + virtual void mousePressEvent(QMouseEvent * event); + virtual void mouseMoveEvent(QMouseEvent * event); + virtual void mouseReleaseEvent(QMouseEvent * event); + virtual void mouseDoubleClickEvent(QMouseEvent * event); + +private: + friend class UPlotCurve; + void addItem(QGraphicsItem * item); + +private: + void replot(QPainter * painter); + bool updateAxis(float x, float y); + bool updateAxis(float x1, float x2, float y1, float y2); + void setupUi(); + void createActions(); + void createMenus(); + void selectScreenCaptureFormat(); + bool mousePosToValue(const QPoint & pos, float & x, float & y); + +private: + UPlotLegend * _legend; + QGraphicsView * _view; + QGraphicsItem * _sceneRoot; + QWidget * _graphicsViewHolder; + float _axisMaximums[4]; // {x1->x2, y1->y2} + bool _axisMaximumsSet[4]; // {x1->x2, y1->y2} + bool _fixedAxis[2]; + UPlotAxis * _verticalAxis; + UPlotAxis * _horizontalAxis; + int _penStyleCount; + int _maxVisibleItems; + QList hGridLines; + QList vGridLines; + QList _curves; + QLabel * _title; + QLabel * _xLabel; + UOrientableLabel * _yLabel; + QLabel * _refreshRate; + QString _workingDirectory; + QTime _refreshIntervalTime; + int _lowestRefreshRate; + QTime _refreshStartTime; + QString _autoScreenCaptureFormat; + QPoint _mousePressedPos; + QPoint _mouseCurrentPos; + QColor _bgColor; + + QMenu * _menu; + QAction * _aShowLegend; + QAction * _aShowGrid; + QAction * _aKeepAllData; + QAction * _aLimit0; + QAction * _aLimit10; + QAction * _aLimit50; + QAction * _aLimit100; + QAction * _aLimit500; + QAction * _aLimit1000; + QAction * _aLimitCustom; + QAction * _aAddVerticalLine; + QAction * _aAddHorizontalLine; + QAction * _aChangeTitle; + QAction * _aChangeXLabel; + QAction * _aChangeYLabel; + QAction * _aChangeBackgroundColor; + QAction * _aYLabelVertical; + QAction * _aShowRefreshRate; + QAction * _aMouseTracking; + QAction * _aSaveFigure; + QAction * _aAutoScreenCapture; + QAction * _aClearData; + QAction * _aGraphicsView; +}; + +#endif /* UPLOT_H_ */ diff --git a/utilite/src/CMakeLists.txt b/utilite/src/CMakeLists.txt new file mode 100644 index 00000000..d0f5caa0 --- /dev/null +++ b/utilite/src/CMakeLists.txt @@ -0,0 +1,42 @@ + +SET(SRC_FILES + UEventsManager.cpp + UEventsHandler.cpp + UFile.cpp + UDirectory.cpp + UConversion.cpp + ULogger.cpp + UThread.cpp + UTimer.cpp + UProcessInfo.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${PTHREADS_INCLUDE_DIR} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +ADD_LIBRARY(rtabmap_utilite ${SRC_FILES}) +IF(WIN32) + TARGET_LINK_LIBRARIES(rtabmap_utilite ${PTHREADS_LIBRARY} ${LIBRARIES} "-lpsapi") +ELSE(WIN32) + TARGET_LINK_LIBRARIES(rtabmap_utilite ${PTHREADS_LIBRARY} ${LIBRARIES}) +ENDIF(WIN32) + +SET_TARGET_PROPERTIES( +rtabmap_utilite +PROPERTIES + OUTPUT_NAME ${PROJECT_PREFIX}_utilite + INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib +) + +INSTALL(TARGETS rtabmap_utilite + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/ DESTINATION include/ COMPONENT devel FILES_MATCHING PATTERN "*.h" PATTERN ".svn" EXCLUDE) + diff --git a/utilite/src/UConversion.cpp b/utilite/src/UConversion.cpp new file mode 100644 index 00000000..ba762572 --- /dev/null +++ b/utilite/src/UConversion.cpp @@ -0,0 +1,320 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UConversion.h" + +#include +#include +#include "rtabmap/utilite/ULogger.h" + +std::string uReplaceChar(const std::string & str, char before, char after) +{ + std::string result = str; + for(unsigned int i=0; i= 'a' && result[i]<='z') + { + result[i] = result[i] - 'a' + 'A'; + } + } + return result; +} + +std::string uToLowerCase(const std::string & str) +{ + std::string result = str; + for(unsigned int i=0; i= 'A' && result[i]<='Z') + { + result[i] = result[i] - 'A' + 'a'; + } + } + return result; +} + +std::string uNumber2Str(unsigned int number) +{ + std::stringstream s; + s << number; + return s.str(); +} + +std::string uNumber2Str(int number) +{ + std::stringstream s; + s << number; + return s.str(); +} + +std::string uNumber2Str(float number) +{ + std::stringstream s; + s << number; + return s.str(); +} + +std::string uNumber2Str(double number) +{ + std::stringstream s; + s << number; + return s.str(); +} + +std::string uBool2Str(bool boolean) +{ + std::string s; + if(boolean) + { + s = "true"; + } + else + { + s = "false"; + } + return s; +} + +bool uStr2Bool(const char * str) +{ + return !(str && (strcmp(str, "false") == 0 || strcmp(str, "FALSE") == 0 || strcmp(str, "0") == 0)); +} + +std::string uBytes2Hex(const char * bytes, unsigned int bytesLen) +{ + std::string hex; + if(!bytes || bytesLen == 0) + { + return hex; + } + const unsigned char * bytes_u = (const unsigned char*)(bytes); + + hex.resize(bytesLen*2); + char * pHex = &hex[0]; + const unsigned char * pEnd = (bytes_u + bytesLen); + for(const unsigned char * pChar = bytes_u; pChar != pEnd; ++pChar, pHex += 2) + { + pHex[0] = uHex2Ascii(*pChar, 0); + pHex[1] = uHex2Ascii(*pChar, 1); + } + return hex; +} + +std::vector uHex2Bytes(const std::string & hex) +{ + return uHex2Bytes(&hex[0], hex.length()); +} + +std::vector uHex2Bytes(const char * hex, int hexLen) +{ + std::vector bytes; + if(!hex || hexLen % 2 || hexLen == 0) + { + return bytes; // must be pair + } + + unsigned int bytesLen = hexLen / 2; + bytes.resize(bytesLen); + unsigned char * pBytes = (unsigned char *)&bytes[0]; + const unsigned char * pHex = (const unsigned char *)hex; + + unsigned char * pEnd = (pBytes + bytesLen); + for(unsigned char * pChar = pBytes; pChar != pEnd; pChar++, pHex += 2) + { + *pChar = (uAscii2Hex(pHex[0]) << 4) | uAscii2Hex(pHex[1]); + } + return bytes; +} + +// The hex str MUST not contains any null values (0x00) +std::string uHex2Str(const std::string & hex) +{ + std::vector bytes = uHex2Bytes(hex); + return std::string(&bytes[0], bytes.size()); +} + +static const char HEX2ASCII[256][2] = +{ + {'0','0'},{'0','1'},{'0','2'},{'0','3'},{'0','4'},{'0','5'},{'0','6'},{'0','7'},{'0','8'},{'0','9'},{'0','A'},{'0','B'},{'0','C'},{'0','D'},{'0','E'},{'0','F'}, + {'1','0'},{'1','1'},{'1','2'},{'1','3'},{'1','4'},{'1','5'},{'1','6'},{'1','7'},{'1','8'},{'1','9'},{'1','A'},{'1','B'},{'1','C'},{'1','D'},{'1','E'},{'1','F'}, + {'2','0'},{'2','1'},{'2','2'},{'2','3'},{'2','4'},{'2','5'},{'2','6'},{'2','7'},{'2','8'},{'2','9'},{'2','A'},{'2','B'},{'2','C'},{'2','D'},{'2','E'},{'2','F'}, + {'3','0'},{'3','1'},{'3','2'},{'3','3'},{'3','4'},{'3','5'},{'3','6'},{'3','7'},{'3','8'},{'3','9'},{'3','A'},{'3','B'},{'3','C'},{'3','D'},{'3','E'},{'3','F'}, + {'4','0'},{'4','1'},{'4','2'},{'4','3'},{'4','4'},{'4','5'},{'4','6'},{'4','7'},{'4','8'},{'4','9'},{'4','A'},{'4','B'},{'4','C'},{'4','D'},{'4','E'},{'4','F'}, + {'5','0'},{'5','1'},{'5','2'},{'5','3'},{'5','4'},{'5','5'},{'5','6'},{'5','7'},{'5','8'},{'5','9'},{'5','A'},{'5','B'},{'5','C'},{'5','D'},{'5','E'},{'5','F'}, + {'6','0'},{'6','1'},{'6','2'},{'6','3'},{'6','4'},{'6','5'},{'6','6'},{'6','7'},{'6','8'},{'6','9'},{'6','A'},{'6','B'},{'6','C'},{'6','D'},{'6','E'},{'6','F'}, + {'7','0'},{'7','1'},{'7','2'},{'7','3'},{'7','4'},{'7','5'},{'7','6'},{'7','7'},{'7','8'},{'7','9'},{'7','A'},{'7','B'},{'7','C'},{'7','D'},{'7','E'},{'7','F'}, + {'8','0'},{'8','1'},{'8','2'},{'8','3'},{'8','4'},{'8','5'},{'8','6'},{'8','7'},{'8','8'},{'8','9'},{'8','A'},{'8','B'},{'8','C'},{'8','D'},{'8','E'},{'8','F'}, + {'9','0'},{'9','1'},{'9','2'},{'9','3'},{'9','4'},{'9','5'},{'9','6'},{'9','7'},{'9','8'},{'9','9'},{'9','A'},{'9','B'},{'9','C'},{'9','D'},{'9','E'},{'9','F'}, + {'A','0'},{'A','1'},{'A','2'},{'A','3'},{'A','4'},{'A','5'},{'A','6'},{'A','7'},{'A','8'},{'A','9'},{'A','A'},{'A','B'},{'A','C'},{'A','D'},{'A','E'},{'A','F'}, + {'B','0'},{'B','1'},{'B','2'},{'B','3'},{'B','4'},{'B','5'},{'B','6'},{'B','7'},{'B','8'},{'B','9'},{'B','A'},{'B','B'},{'B','C'},{'B','D'},{'B','E'},{'B','F'}, + {'C','0'},{'C','1'},{'C','2'},{'C','3'},{'C','4'},{'C','5'},{'C','6'},{'C','7'},{'C','8'},{'C','9'},{'C','A'},{'C','B'},{'C','C'},{'C','D'},{'C','E'},{'C','F'}, + {'D','0'},{'D','1'},{'D','2'},{'D','3'},{'D','4'},{'D','5'},{'D','6'},{'D','7'},{'D','8'},{'D','9'},{'D','A'},{'D','B'},{'D','C'},{'D','D'},{'D','E'},{'D','F'}, + {'E','0'},{'E','1'},{'E','2'},{'E','3'},{'E','4'},{'E','5'},{'E','6'},{'E','7'},{'E','8'},{'E','9'},{'E','A'},{'E','B'},{'E','C'},{'E','D'},{'E','E'},{'E','F'}, + {'F','0'},{'F','1'},{'F','2'},{'F','3'},{'F','4'},{'F','5'},{'F','6'},{'F','7'},{'F','8'},{'F','9'},{'F','A'},{'F','B'},{'F','C'},{'F','D'},{'F','E'},{'F','F'} +}; + +unsigned char uHex2Ascii(const unsigned char & c, bool rightPart) +{ + if(rightPart) + { + return HEX2ASCII[c][1]; + } + else + { + return HEX2ASCII[c][0]; + } +} + +unsigned char uAscii2Hex(const unsigned char & c) +{ + switch(c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return c-'0'; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return c-'A'+10; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return c-'a'+10; + default: + return 0x00; + } +} + +std::string uFormatv (const char *fmt, va_list args) +{ + // Allocate a buffer on the stack that's big enough for us almost + // all the time. Be prepared to allocate dynamically if it doesn't fit. + size_t size = 1024; + std::vector dynamicbuf(size); + char *buf = &dynamicbuf[0]; + + va_list argsTmp; + + while (1) { + va_copy(argsTmp, args); + + // Try to vsnprintf into our buffer. + int needed; + if(argsTmp != 0) + { + needed = vsnprintf (buf, size, fmt, argsTmp); + } + else + { + needed = snprintf (buf, size, "%s", fmt); + } + va_end(argsTmp); + // NB. C99 (which modern Linux and OS X follow) says vsnprintf + // failure returns the length it would have needed. But older + // glibc and current Windows return -1 for failure, i.e., not + // telling us how much was needed. + if (needed < (int)size-1 && needed >= 0) { + // It fit fine so we're done. + return std::string (buf, (size_t) needed); + } + + // vsnprintf reported that it wanted to write more characters + // than we allotted. So try again using a dynamic buffer. This + // doesn't happen very often if we chose our initial size well. + size = needed>=0?needed+2:size*2; + dynamicbuf.resize (size); + buf = &dynamicbuf[0]; + } + return std::string(); // would not reach this, but for compiler complaints... +} + +std::string uFormat (const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + std::string buf = uFormatv(fmt, args); + va_end(args); + return buf; +} + +#ifdef WIN32 +// returned whar_t * must be deleted : delete [] wText; +wchar_t * createWCharFromChar(const char * text) +{ + DWORD length = MultiByteToWideChar (CP_ACP, 0, text, -1, NULL, 0); + wchar_t * wText = new wchar_t[length]; + MultiByteToWideChar (CP_ACP, 0, text, -1, wText, length ); + return wText; +} + +// returned char * must be deleted : delete [] text; +char * createCharFromWChar(const wchar_t * wText) +{ + DWORD length = WideCharToMultiByte (CP_ACP, 0, wText, -1, NULL, 0, NULL, NULL); + char * text = new char[length]; + WideCharToMultiByte (CP_ACP, 0, wText, -1, text, length, NULL, NULL); + return text; +} +#endif diff --git a/utilite/src/UDirectory.cpp b/utilite/src/UDirectory.cpp new file mode 100644 index 00000000..fbf7f651 --- /dev/null +++ b/utilite/src/UDirectory.cpp @@ -0,0 +1,375 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UDirectory.h" + +#ifdef WIN32 + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#include "rtabmap/utilite/UStl.h" +#include "rtabmap/utilite/UFile.h" +#include "rtabmap/utilite/UDirectory.h" +#include "rtabmap/utilite/UConversion.h" + +#include "rtabmap/utilite/ULogger.h" + +#ifdef WIN32 + +bool sortCallback(const std::string & a, const std::string & b) +{ + return uStrNumCmp(a,b) < 0; +} +#elif __APPLE__ +int sortCallback(const void * aa, const void * bb) +{ + const struct dirent ** a = (const struct dirent **)aa; + const struct dirent ** b = (const struct dirent **)bb; + + return uStrNumCmp((*a)->d_name, (*b)->d_name); +} +#else +int sortCallback( const dirent ** a, const dirent ** b) +{ + return uStrNumCmp((*a)->d_name, (*b)->d_name); +} +#endif + +UDirectory::UDirectory(const std::string & path, const std::string & extensions) +{ + extensions_ = uListToVector(uSplit(extensions, ' ')); + path_ = path; + iFileName_ = fileNames_.begin(); + this->update(); +} + +UDirectory::UDirectory(const UDirectory & dir) +{ + *this = dir; +} + +UDirectory & UDirectory::operator=(const UDirectory & dir) +{ + extensions_ = dir.extensions_; + path_ = dir.path_; + fileNames_ = dir.fileNames_; + for(iFileName_=fileNames_.begin(); iFileName_!=fileNames_.end(); ++iFileName_) + { + if(iFileName_->compare(*dir.iFileName_) == 0) + { + break; + } + } + return *this; +} + +UDirectory::~UDirectory() +{ +} + +void UDirectory::setPath(const std::string & path, const std::string & extensions) +{ + extensions_ = uListToVector(uSplit(extensions, ' ')); + path_ = path; + fileNames_.clear(); + iFileName_ = fileNames_.begin(); + this->update(); +} + +void UDirectory::update() +{ + if(exists(path_)) + { + std::string lastName; + bool endOfDir = false; + if(iFileName_ != fileNames_.end()) + { + //Record the last file name + lastName = *iFileName_; + } + else if(fileNames_.size()) + { + lastName = *fileNames_.rbegin(); + endOfDir = true; + } + fileNames_.clear(); +#ifdef WIN32 + WIN32_FIND_DATA fileInformation; + #ifdef UNICODE + wchar_t * pathAll = createWCharFromChar((path_+"\\*").c_str()); + HANDLE hFile = ::FindFirstFile(pathAll, &fileInformation); + delete [] pathAll; + #else + HANDLE hFile = ::FindFirstFile((path_+"\\*").c_str(), &fileInformation); + #endif + if(hFile != INVALID_HANDLE_VALUE) + { + do + { + #ifdef UNICODE + char * fileName = createCharFromWChar(fileInformation.cFileName); + fileNames_.push_back(fileName); + delete [] fileName; + #else + fileNames_.push_back(fileInformation.cFileName); + #endif + } while(::FindNextFile(hFile, &fileInformation) == TRUE); + ::FindClose(hFile); + std::vector vFileNames = uListToVector(fileNames_); + std::sort(vFileNames.begin(), vFileNames.end(), sortCallback); + fileNames_ = uVectorToList(vFileNames); + } +#else + int nameListSize; + struct dirent ** nameList = 0; + nameListSize = scandir(path_.c_str(), &nameList, 0, sortCallback); + if(nameList && nameListSize>0) + { + for (int i=0;id_name); + free(nameList[i]); + } + free(nameList); + } +#endif + + //filter extensions... + std::list::iterator iter = fileNames_.begin(); + bool valid; + while(iter!=fileNames_.end()) + { + valid = false; + if(extensions_.size() == 0 && + iter->compare(".") != 0 && + iter->compare("..") != 0) + { + valid = true; + } + for(unsigned int i=0; i::iterator iter=fileNames_.begin(); iter!=fileNames_.end(); ++iter) + { + if(lastName.compare(*iter) == 0) + { + found = true; + iFileName_ = iter; + break; + } + } + if(endOfDir && found) + { + ++iFileName_; + } + else if(endOfDir && fileNames_.size()) + { + iFileName_ = --fileNames_.end(); + } + } + } +} + +bool UDirectory::isValid() +{ + return exists(path_); +} + +std::string UDirectory::getNextFileName() +{ + std::string fileName; + if(iFileName_ != fileNames_.end()) + { + fileName = *iFileName_; + ++iFileName_; + } + return fileName; +} + +void UDirectory::rewind() +{ + iFileName_ = fileNames_.begin(); +} + + +bool UDirectory::exists(const std::string & dirPath) +{ + bool r = false; +#if WIN32 + #ifdef UNICODE + wchar_t * wDirPath = createWCharFromChar(dirPath.c_str()); + DWORD dwAttrib = GetFileAttributes(wDirPath); + delete [] wDirPath; + #else + DWORD dwAttrib = GetFileAttributes(dirPath.c_str()); + #endif + r = (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +#else + DIR *dp; + if((dp = opendir(dirPath.c_str())) != NULL) + { + r = true; + closedir(dp); + } +#endif + return r; +} + +// return the directory path of the file +std::string UDirectory::getDir(const std::string & filePath) +{ + std::string dir = filePath; + int i=dir.size()-1; + for(; i>=0; --i) + { + if(dir[i] == '/' || dir[i] == '\\') + { + //remove separators... + dir[i] = 0; + --i; + while(i>=0 && (dir[i] == '/' || dir[i] == '\\')) + { + dir[i] = 0; + --i; + } + break; + } + else + { + dir[i] = 0; + } + } + + if(i<0) + { + dir = "."; + } + else + { + dir.resize(i+1); + } + + return dir; +} + +std::string UDirectory::currentDir(bool trailingSeparator) +{ + std::string dir; + char * buffer; + +#ifdef WIN32 + buffer = _getcwd(NULL, 0); +#else + buffer = getcwd(NULL, MAXPATHLEN); +#endif + + if( buffer != NULL ) + { + dir = buffer; + free(buffer); + if(trailingSeparator) + { + dir += separator(); + } + } + + return dir; +} + +bool UDirectory::makeDir(const std::string & dirPath) +{ + int status; +#if WIN32 + status = _mkdir(dirPath.c_str()); +#else + status = mkdir(dirPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +#endif + return status==0; +} + +bool UDirectory::removeDir(const std::string & dirPath) +{ + int status; +#if WIN32 + status = _rmdir(dirPath.c_str()); +#else + status = rmdir(dirPath.c_str()); +#endif + return status==0; +} + +std::string UDirectory::homeDir() +{ + std::string path; +#if WIN32 + #ifdef UNICODE + wchar_t wProfilePath[250]; + ExpandEnvironmentStrings(L"%userprofile%",wProfilePath,250); + char * profilePath = createCharFromWChar(wProfilePath); + path = profilePath; + delete [] profilePath; + #else + char profilePath[250]; + ExpandEnvironmentStrings("%userprofile%",profilePath,250); + path = profilePath; + #endif +#else + path = getenv("HOME"); +#endif + return path; +} + +std::string UDirectory::separator() +{ +#ifdef WIN32 + return "\\"; +#else + return "/"; +#endif +} diff --git a/utilite/src/UEventsHandler.cpp b/utilite/src/UEventsHandler.cpp new file mode 100644 index 00000000..2ed2c2d9 --- /dev/null +++ b/utilite/src/UEventsHandler.cpp @@ -0,0 +1,31 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UEventsHandler.h" +#include "rtabmap/utilite/UEventsManager.h" + +UEventsHandler::~UEventsHandler() +{ + UEventsManager::removeHandler(this); +} + +void UEventsHandler::post(UEvent * event, bool async) +{ + UEventsManager::post(event, async); +} diff --git a/utilite/src/UEventsManager.cpp b/utilite/src/UEventsManager.cpp new file mode 100644 index 00000000..b09c120d --- /dev/null +++ b/utilite/src/UEventsManager.cpp @@ -0,0 +1,235 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UEventsManager.h" +#include "rtabmap/utilite/UEvent.h" +#include +#include "rtabmap/utilite/UStl.h" + +UEventsManager* UEventsManager::instance_ = 0; +UDestroyer UEventsManager::destroyer_; + +void UEventsManager::addHandler(UEventsHandler* handler) +{ + if(!handler) + { + UERROR("Handler is null!"); + return; + } + else + { + UEventsManager::getInstance()->_addHandler(handler); + } +} + +void UEventsManager::removeHandler(UEventsHandler* handler) +{ + if(!handler) + { + UERROR("Handler is null!"); + return; + } + else + { + UEventsManager::getInstance()->_removeHandler(handler); + } +} + +void UEventsManager::post(UEvent * event, bool async) +{ + if(!event) + { + UERROR("Event is null!"); + return; + } + else + { + UEventsManager::getInstance()->_postEvent(event, async); + } +} + +UEventsManager* UEventsManager::getInstance() +{ + if(!instance_) + { + instance_ = new UEventsManager(); + destroyer_.setDoomed(instance_); + instance_->start(); // Start the thread + } + return instance_; +} + +UEventsManager::UEventsManager() +{ +} + +UEventsManager::~UEventsManager() +{ + join(true); + + // Free memory + for(std::list::iterator it=events_.begin(); it!=events_.end(); ++it) + { + delete *it; + } + events_.clear(); + + handlers_.clear(); + + instance_ = 0; +} + +void UEventsManager::mainLoop() +{ + postEventSem_.acquire(); + if(!this->isKilled()) + { + dispatchEvents(); + } +} + +void UEventsManager::mainLoopKill() +{ + postEventSem_.release(); +} + +void UEventsManager::dispatchEvents() +{ + if(events_.size() == 0) + { + return; + } + + std::list::iterator it; + std::list eventsBuf; + + // Copy events in a buffer : + // Other threads can post events + // while events are handled. + eventsMutex_.lock(); + { + eventsBuf = events_; + events_.clear(); + } + eventsMutex_.unlock(); + + // Past events to handlers + for(it=eventsBuf.begin(); it!=eventsBuf.end(); ++it) + { + dispatchEvent(*it); + delete *it; + } + eventsBuf.clear(); +} + +void UEventsManager::dispatchEvent(UEvent * event) +{ + UEventsHandler * handler; + handlersMutex_.lock(); + std::list handlers = handlers_; + for(std::list::iterator it=handlers.begin(); it!=handlers.end(); ++it) + { + // Check if the handler is still in the + // handlers_ list (may be changed if addHandler() or + // removeHandler() is called in EventsHandler::handleEvent()) + if(std::find(handlers_.begin(), handlers_.end(), *it) != handlers_.end()) + { + handler = *it; + handlersMutex_.unlock(); + + // To be able to add/remove an handler in a handleEvent call (without a deadlock) + // @see _addHandler(), _removeHandler() + handler->handleEvent(event); + + handlersMutex_.lock(); + } + } + handlersMutex_.unlock(); + +} + +void UEventsManager::_addHandler(UEventsHandler* handler) +{ + if(!this->isKilled()) + { + handlersMutex_.lock(); + { + //make sure it is not already in the list + bool handlerFound = false; + for(std::list::iterator it=handlers_.begin(); it!=handlers_.end(); ++it) + { + if(*it == handler) + { + handlerFound = true; + } + } + if(!handlerFound) + { + handlers_.push_back(handler); + } + } + handlersMutex_.unlock(); + } +} + +void UEventsManager::_removeHandler(UEventsHandler* handler) +{ + if(!this->isKilled()) + { + handlersMutex_.lock(); + { + for (std::list::iterator it = handlers_.begin(); it!=handlers_.end(); ++it) + { + if(*it == handler) + { + handlers_.erase(it); + break; + } + } + } + handlersMutex_.unlock(); + } +} + +void UEventsManager::_postEvent(UEvent * event, bool async) +{ + if(!this->isKilled()) + { + if(async) + { + eventsMutex_.lock(); + { + events_.push_back(event); + } + eventsMutex_.unlock(); + + // Signal the EventsManager that an Event is added + postEventSem_.release(); + } + else + { + dispatchEvent(event); + delete event; + } + } + else + { + delete event; + } +} diff --git a/utilite/src/UFile.cpp b/utilite/src/UFile.cpp new file mode 100644 index 00000000..9b4bd38c --- /dev/null +++ b/utilite/src/UFile.cpp @@ -0,0 +1,95 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UFile.h" + +#include +#include "rtabmap/utilite/UStl.h" + +bool UFile::exists(const std::string &filePath) +{ + bool fileExists = false; + std::ifstream in(filePath.c_str(), std::ios::in); + if (in.is_open()) + { + fileExists = true; + in.close(); + } + return fileExists; +} + +long UFile::length(const std::string &filePath) +{ + long fileSize = 0; + FILE* fp = 0; +#ifdef _MSC_VER + fopen_s(&fp, filePath.c_str(), "rb"); +#else + fp = fopen(filePath.c_str(), "rb"); +#endif + if(fp == NULL) + { + return 0; + } + + fseek(fp , 0 , SEEK_END); + fileSize = ftell(fp); + fclose(fp); + + return fileSize; +} + +int UFile::erase(const std::string &filePath) +{ + return remove(filePath.c_str()); +} + +int UFile::rename(const std::string &oldFilePath, + const std::string &newFilePath) +{ + return rename(oldFilePath.c_str(), newFilePath.c_str()); +} + +std::string UFile::getName(const std::string & filePath) +{ + std::string fullPath = filePath; + std::string name; + for(int i=fullPath.size()-1; i>=0; --i) + { + if(fullPath[i] == '/' || fullPath[i] == '\\') + { + break; + } + else + { + name.insert(name.begin(), fullPath[i]); + } + } + return name; +} + +std::string UFile::getExtension(const std::string &filePath) +{ + std::list list = uSplit(filePath, '.'); + if(list.size()) + { + return list.back(); + } + return ""; +} diff --git a/utilite/src/ULogger.cpp b/utilite/src/ULogger.cpp new file mode 100644 index 00000000..d5c3f590 --- /dev/null +++ b/utilite/src/ULogger.cpp @@ -0,0 +1,624 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/ULogger.h" +#include "rtabmap/utilite/UConversion.h" +#include "rtabmap/utilite/UFile.h" +#include "rtabmap/utilite/UStl.h" +#include "rtabmap/utilite/UEventsManager.h" +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#ifdef WIN32 +#include +#define COLOR_NORMAL FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED +#define COLOR_RED FOREGROUND_RED | FOREGROUND_INTENSITY +#define COLOR_GREEN FOREGROUND_GREEN +#define COLOR_YELLOW FOREGROUND_GREEN | FOREGROUND_RED +#else +#define COLOR_NORMAL "\033[0m" +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_YELLOW "\033[33m" +#endif + +bool ULogger::append_ = true; +bool ULogger::printTime_ = true; +bool ULogger::printLevel_ = true; +bool ULogger::printEndline_ = true; +bool ULogger::printColored_ = true; +bool ULogger::printWhere_ = true; +bool ULogger::printWhereFullPath_ = false; +bool ULogger::limitWhereLength_ = false; +bool ULogger::buffered_ = false; +bool ULogger::exitingState_ = false; +ULogger::Level ULogger::level_ = kInfo; // By default, we show all info msgs + upper level (Warning, Error) +ULogger::Level ULogger::exitLevel_ = kFatal; +ULogger::Level ULogger::eventLevel_ = kFatal; +const char * ULogger::levelName_[5] = {"DEBUG", " INFO", " WARN", "ERROR", "FATAL"}; +ULogger* ULogger::instance_ = 0; +UDestroyer ULogger::destroyer_; +ULogger::Type ULogger::type_ = ULogger::kTypeNoLog; // Default nothing +UMutex ULogger::loggerMutex_; +const std::string ULogger::kDefaultLogFileName = "./ULog.txt"; +std::string ULogger::logFileName_; +std::string ULogger::bufferedMsgs_; + +/** + * This class is used to write logs in the console. This class cannot + * be directly used, use ULogger::setType() to console type to print in + * console and use macro UDEBUG(), UINFO()... to print messages. + * @see ULogger + */ +class UConsoleLogger : public ULogger +{ +public : + virtual ~UConsoleLogger() {this->_flush();} + +protected: + /** + * Only the Logger can create inherited + * loggers according to the Abstract factory patterns. + */ + friend class ULogger; + + UConsoleLogger() {} + +private: + virtual void _write(const char* msg, va_list arg) + { + if(arg != 0) + { + vprintf(msg, arg); + } + else + { + printf("%s", msg); + } + } +}; + +/** + * This class is used to write logs in a file. This class cannot + * be directly used, use ULogger::setType() to file type to print in + * a file and use macro UDEBUG(), UINFO()... to print messages. + * @see ULogger + */ +class UFileLogger : public ULogger +{ +public: + virtual ~UFileLogger() + { + this->_flush(); + if(fout_) + { + fclose(fout_); + } + } + +protected: + /** + * Only the Logger can create inherited + * loggers according to the Abstract factory patterns. + */ + friend class ULogger; + + /** + * The UFileLogger constructor. + * @param fileName the file name + * @param append if true append logs in the file, + * ortherwise it overrides the file. + * + */ + UFileLogger(const std::string &fileName, bool append) + { + fileName_ = fileName; + + if(!append) { + std::ofstream fileToClear(fileName_.c_str(), std::ios::out); + fileToClear.clear(); + fileToClear.close(); + } + +#ifdef _MSC_VER + fopen_s(&fout_, fileName_.c_str(), "a"); +#else + fout_ = fopen(fileName_.c_str(), "a"); +#endif + + if(!fout_) { + printf("FileLogger : Cannot open file : %s\n", fileName_.c_str()); // TODO send Event instead, or return error code + return; + } + } + +private: + virtual void _write(const char* msg, va_list arg) + { + if(fout_) + { + if(arg != 0) + { + vfprintf(fout_, msg, arg); + } + else + { + fprintf(fout_, "%s", msg); + } + } + } + +private: + std::string fileName_; ///< the file name + FILE* fout_; + std::string bufferedMsgs_; +}; + +void ULogger::setType(Type type, const std::string &fileName, bool append) +{ + ULogger::flush(); + loggerMutex_.lock(); + { + // instance not yet created + if(!instance_) + { + type_ = type; + logFileName_ = fileName; + append_ = append; + instance_ = createInstance(); + } + // type changed + else if(type_ != type || (type_ == kTypeFile && logFileName_.compare(fileName)!=0)) + { + destroyer_.setDoomed(0); + delete instance_; + instance_ = 0; + type_ = type; + logFileName_ = fileName; + append_ = append; + instance_ = createInstance(); + } + } + loggerMutex_.unlock(); +} + +void ULogger::reset() +{ + ULogger::setType(ULogger::kTypeNoLog); + append_ = true; + printTime_ = true; + printLevel_ = true; + printEndline_ = true; + printColored_ = true; + printWhere_ = true; + printWhereFullPath_ = false; + limitWhereLength_ = false; + level_ = kInfo; // By default, we show all info msgs + upper level (Warning, Error) + logFileName_ = ULogger::kDefaultLogFileName; +} + +void ULogger::setBuffered(bool buffered) +{ + if(!buffered) + { + ULogger::flush(); + } + buffered_ = buffered; +} + + +void ULogger::flush() +{ + loggerMutex_.lock(); + if(!instance_ || bufferedMsgs_.size()==0) + { + loggerMutex_.unlock(); + return; + } + + instance_->_flush(); + loggerMutex_.unlock(); +} + +void ULogger::_flush() +{ + ULogger::getInstance()->_write(bufferedMsgs_.c_str(), 0); + bufferedMsgs_.clear(); +} + +void ULogger::write(const char* msg, ...) +{ + loggerMutex_.lock(); + if(!instance_) + { + loggerMutex_.unlock(); + return; + } + + std::string endline = ""; + if(printEndline_) { + endline = "\r\n"; + } + + std::string time = ""; + if(printTime_) + { + getTime(time); + time.append(" - "); + } + + + if(printTime_) + { + if(buffered_) + { + bufferedMsgs_.append(time.c_str()); + } + else + { + ULogger::getInstance()->_write(time.c_str(), 0); + } + } + + va_list args; + va_start(args, msg); + if(buffered_) + { + bufferedMsgs_.append(uFormatv(msg, args)); + } + else + { + ULogger::getInstance()->_write(msg, args); + } + va_end(args); + if(printEndline_) + { + if(buffered_) + { + bufferedMsgs_.append(endline.c_str()); + } + else + { + ULogger::getInstance()->_write(endline.c_str(), 0); + } + } + loggerMutex_.unlock(); + +} + +void ULogger::write(ULogger::Level level, + const char * file, + int line, + const char * function, + const char* msg, + ...) +{ + if(exitingState_) + { + // Ignore messages after a fatal exit... + return; + } + loggerMutex_.lock(); + if(type_ == kTypeNoLog && level < kFatal) + { + loggerMutex_.unlock(); + return; + } + if(strlen(msg) == 0 && !printWhere_ && level < exitLevel_) + { + loggerMutex_.unlock(); + // No need to show an empty message if we don't print where. + return; + } + + if(level >= level_) + { +#ifdef WIN32 + int color = 0; +#else + const char* color = NULL; +#endif + switch(level) + { + case kDebug: + color = COLOR_GREEN; + break; + case kInfo: + color = COLOR_NORMAL; + break; + case kWarning: + color = COLOR_YELLOW; + break; + case kError: + case kFatal: + color = COLOR_RED; + break; + default: + break; + } + + std::string endline = ""; + if(printEndline_) { + endline = "\r\n"; + } + + std::string time = ""; + if(printTime_) + { + time.append("("); + getTime(time); + time.append(") "); + } + + std::string levelStr = ""; + if(printLevel_) + { + const int bufSize = 30; + char buf[bufSize] = {0}; + +#ifdef _MSC_VER + sprintf_s(buf, bufSize, "[%s]", levelName_[level]); +#else + snprintf(buf, bufSize, "[%s]", levelName_[level]); +#endif + levelStr = buf; + levelStr.append(" "); + } + + std::string whereStr = ""; + if(printWhere_) + { + whereStr.append(""); + //File + if(printWhereFullPath_) + { + whereStr.append(file); + } + else + { + std::string fileName = UFile::getName(file); + if(limitWhereLength_ && fileName.size() > 8) + { + fileName.erase(8); + fileName.append("~"); + } + whereStr.append(fileName); + } + + //Line + whereStr.append(":"); + std::string lineStr = uNumber2Str(line); + whereStr.append(lineStr); + + //Function + whereStr.append("::"); + std::string funcStr = function; + if(!printWhereFullPath_ && limitWhereLength_ && funcStr.size() > 8) + { + funcStr.erase(8); + funcStr.append("~"); + } + funcStr.append("()"); + whereStr.append(funcStr); + + whereStr.append(" "); + } + + va_list args; + + if(type_ != kTypeNoLog) + { + va_start(args, msg); +#ifdef WIN32 + HANDLE H = GetStdHandle(STD_OUTPUT_HANDLE); +#endif + if(type_ == ULogger::kTypeConsole && printColored_) + { +#ifdef WIN32 + SetConsoleTextAttribute(H,color); +#else + if(buffered_) + { + bufferedMsgs_.append(color); + } + else + { + ULogger::getInstance()->_write(color, 0); + } +#endif + } + + if(buffered_) + { + bufferedMsgs_.append(levelStr.c_str()); + bufferedMsgs_.append(time.c_str()); + bufferedMsgs_.append(whereStr.c_str()); + bufferedMsgs_.append(uFormatv(msg, args)); + } + else + { + ULogger::getInstance()->_write(levelStr.c_str(), 0); + ULogger::getInstance()->_write(time.c_str(), 0); + ULogger::getInstance()->_write(whereStr.c_str(), 0); + ULogger::getInstance()->_write(msg, args); + } + if(type_ == ULogger::kTypeConsole && printColored_) + { +#ifdef WIN32 + SetConsoleTextAttribute(H,COLOR_NORMAL); +#else + if(buffered_) + { + bufferedMsgs_.append(COLOR_NORMAL); + } + else + { + ULogger::getInstance()->_write(COLOR_NORMAL, 0); + } +#endif + } + if(buffered_) + { + bufferedMsgs_.append(endline.c_str()); + } + else + { + ULogger::getInstance()->_write(endline.c_str(), 0); + } + va_end (args); + } + + if(level >= eventLevel_) + { + std::string fullMsg = uFormat("%s%s%s", levelStr.c_str(), time.c_str(), whereStr.c_str()); + va_start(args, msg); + if(args != 0) + { + fullMsg.append(uFormatv(msg, args)); + } + else + { + fullMsg.append(msg); + } + va_end(args); + if(level >= exitLevel_) + { + // Send it synchronously, then receivers + // can do something before the code (exiting) below is executed. + exitingState_ = true; + UEventsManager::post(new ULogEvent(fullMsg, kFatal), false); + } + else + { + UEventsManager::post(new ULogEvent(fullMsg, level)); + } + } + + if(level >= exitLevel_) + { + printf("\n*******\n%s message occurred!\n", levelName_[level]); + printf(" %s%s%s", levelStr.c_str(), time.c_str(), whereStr.c_str()); + va_start(args, msg); + if(args != 0) + { + vprintf(msg, args); + } + else + { + printf("%s", msg); + } + va_end(args); + printf("\n*******\n"); + destroyer_.setDoomed(0); + delete instance_; // If a FileLogger is used, this will close the file. + instance_ = 0; + //======================================================================== + // EXIT APPLICATION + exit(1); + //======================================================================== + } + } + loggerMutex_.unlock(); +} + +int ULogger::getTime(std::string &timeStr) +{ + if(!printTime_) { + return 0; + } + struct tm timeinfo; + const int bufSize = 30; + char buf[bufSize] = {0}; + +#if _MSC_VER + time_t rawtime; + time(&rawtime); + localtime_s (&timeinfo, &rawtime ); + int result = sprintf_s(buf, bufSize, "%d-%s%d-%s%d %s%d:%s%d:%s%d", + timeinfo.tm_year+1900, + (timeinfo.tm_mon+1) < 10 ? "0":"", timeinfo.tm_mon+1, + (timeinfo.tm_mday) < 10 ? "0":"", timeinfo.tm_mday, + (timeinfo.tm_hour) < 10 ? "0":"", timeinfo.tm_hour, + (timeinfo.tm_min) < 10 ? "0":"", timeinfo.tm_min, + (timeinfo.tm_sec) < 10 ? "0":"", timeinfo.tm_sec); +#elif WIN32 + time_t rawtime; + time(&rawtime); + timeinfo = *localtime (&rawtime); + int result = snprintf(buf, bufSize, "%d-%s%d-%s%d %s%d:%s%d:%s%d", + timeinfo.tm_year+1900, + (timeinfo.tm_mon+1) < 10 ? "0":"", timeinfo.tm_mon+1, + (timeinfo.tm_mday) < 10 ? "0":"", timeinfo.tm_mday, + (timeinfo.tm_hour) < 10 ? "0":"", timeinfo.tm_hour, + (timeinfo.tm_min) < 10 ? "0":"", timeinfo.tm_min, + (timeinfo.tm_sec) < 10 ? "0":"", timeinfo.tm_sec); + #else + struct timeval rawtime; + gettimeofday(&rawtime, NULL); + localtime_r (&rawtime.tv_sec, &timeinfo); + int result = snprintf(buf, bufSize, "%d-%s%d-%s%d %s%d:%s%d:%s%d.%s%d", + timeinfo.tm_year+1900, + (timeinfo.tm_mon+1) < 10 ? "0":"", timeinfo.tm_mon+1, + (timeinfo.tm_mday) < 10 ? "0":"", timeinfo.tm_mday, + (timeinfo.tm_hour) < 10 ? "0":"", timeinfo.tm_hour, + (timeinfo.tm_min) < 10 ? "0":"", timeinfo.tm_min, + (timeinfo.tm_sec) < 10 ? "0":"", timeinfo.tm_sec, + (rawtime.tv_usec/1000) < 10 ? "00":(rawtime.tv_usec/1000) < 100?"0":"", int(rawtime.tv_usec/1000)); +#endif + if(result) + { + timeStr.append(buf); + } + return result; +} + +ULogger* ULogger::getInstance() +{ + if(!instance_) + { + instance_ = createInstance(); + } + return instance_; +} + +ULogger* ULogger::createInstance() +{ + ULogger* instance = 0; + if(type_ == ULogger::kTypeConsole) + { + instance = new UConsoleLogger(); + } + else if(type_ == ULogger::kTypeFile) + { + instance = new UFileLogger(logFileName_, append_); + } + destroyer_.setDoomed(instance); + return instance; +} + +ULogger::~ULogger() +{ + instance_ = 0; + //printf("Logger is destroyed...\n\r"); +} diff --git a/utilite/src/UPlot.cpp b/utilite/src/UPlot.cpp new file mode 100644 index 00000000..8273795f --- /dev/null +++ b/utilite/src/UPlot.cpp @@ -0,0 +1,2979 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UPlot.h" +#include "rtabmap/utilite/ULogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_SVG_LIB +#include +#endif +#include + +#define PRINT_DEBUG 0 + +UPlotItem::UPlotItem(qreal dataX, qreal dataY, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0), + _text(0), + _textBackground(0) +{ + this->init(dataX, dataY); +} + +UPlotItem::UPlotItem(const QPointF & data, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0), + _text(0), + _textBackground(0) +{ + this->init(data.x(), data.y()); +} + +void UPlotItem::init(qreal dataX, qreal dataY) +{ + _data.setX(dataX); + _data.setY(dataY); + this->setAcceptsHoverEvents(true); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +UPlotItem::~UPlotItem() +{ + if(_previousItem && _nextItem) + { + _previousItem->setNextItem(_nextItem); + _nextItem->setPreviousItem(_previousItem); + } + else if(_previousItem) + { + _previousItem->setNextItem(0); + } + else if(_nextItem) + { + _nextItem->setPreviousItem(0); + } +} + +void UPlotItem::setData(const QPointF & data) +{ + _data = data; +} + +void UPlotItem::setNextItem(UPlotItem * nextItem) +{ + if(_nextItem != nextItem) + { + _nextItem = nextItem; + if(nextItem) + { + nextItem->setPreviousItem(this); + } + } +} + +void UPlotItem::setPreviousItem(UPlotItem * previousItem) +{ + if(_previousItem != previousItem) + { + _previousItem = previousItem; + if(previousItem) + { + previousItem->setNextItem(this); + } + } +} + +void UPlotItem::showDescription(bool shown) +{ + if(!_textBackground) + { + _textBackground = new QGraphicsRectItem(this); + _textBackground->setBrush(QBrush(QColor(255, 255, 255, 200))); + _textBackground->setPen(Qt::NoPen); + _textBackground->setZValue(this->zValue()+1); + _textBackground->setVisible(false); + + _text = new QGraphicsTextItem(_textBackground); + } + + if(this->parentItem() && this->parentItem() != _textBackground->parentItem()) + { + _textBackground->setParentItem(this->parentItem()); + _textBackground->setZValue(this->zValue()+1); + } + + if(this->scene() && shown) + { + _textBackground->setVisible(true); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + + this->setPen(QPen(this->pen().color(), 2)); + + QRectF rect = this->scene()->sceneRect(); + QPointF p = this->pos(); + QRectF br = _text->boundingRect(); + _textBackground->setRect(QRectF(0,0,br.width(), br.height())); + + // Make sure the text is always in the scene + if(p.x() - br.width() < 0) + { + p.setX(0); + } + else if(p.x() > rect.width()) + { + p.setX(rect.width() - br.width()); + } + else + { + p.setX(p.x() - br.width()); + } + + if(p.y() - br.height() < 0) + { + p.setY(0); + } + else + { + p.setY(p.y() - br.height()); + } + + _textBackground->setPos(p); + } + else + { + this->setPen(QPen(this->pen().color(), 1)); + _textBackground->setVisible(false); + } +} + +void UPlotItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void UPlotItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + if(!this->hasFocus()) + { + this->showDescription(false); + } + QGraphicsEllipseItem::hoverLeaveEvent(event); +} + +void UPlotItem::focusInEvent(QFocusEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::focusInEvent(event); +} + +void UPlotItem::focusOutEvent(QFocusEvent * event) +{ + this->showDescription(false); + QGraphicsEllipseItem::focusOutEvent(event); +} + +void UPlotItem::keyReleaseEvent(QKeyEvent * keyEvent) +{ + //Get the next/previous visible item + if(keyEvent->key() == Qt::Key_Right) + { + UPlotItem * next = _nextItem; + while(next && !next->isVisible()) + { + next = next->nextItem(); + } + if(next && next->isVisible()) + { + this->clearFocus(); + next->setFocus(); + } + } + else if(keyEvent->key() == Qt::Key_Left) + { + UPlotItem * previous = _previousItem; + while(previous && !previous->isVisible()) + { + previous = previous->previousItem(); + } + if(previous && previous->isVisible()) + { + this->clearFocus(); + previous->setFocus(); + } + } + QGraphicsEllipseItem::keyReleaseEvent(keyEvent); +} + + + + + +UPlotCurve::UPlotCurve(const QString & name, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); +} + +UPlotCurve::UPlotCurve(const QString & name, QVector data, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); + this->setData(data); +} + +UPlotCurve::UPlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false), + _itemsColor(0,0,0,150) +{ + _rootItem = new QGraphicsRectItem(); + this->setData(x, y); +} + +UPlotCurve::~UPlotCurve() +{ + if(_plot) + { + _plot->removeCurve(this); + } +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + this->clear(); + delete _rootItem; +} + +void UPlotCurve::attach(UPlot * plot) +{ + if(!plot || plot == _plot) + { + return; + } + if(_plot) + { + _plot->removeCurve(this); + } + _plot = plot; + _plot->addItem(_rootItem); +} + +void UPlotCurve::detach(UPlot * plot) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("curve=\"%s\" from plot=\"%s\"", this->objectName().toStdString().c_str(), plot?plot->objectName().toStdString().c_str():""); +#endif + if(plot && _plot == plot) + { + _plot = 0; + if(_rootItem->scene()) + { + _rootItem->scene()->removeItem(_rootItem); + } + } +} + +void UPlotCurve::updateMinMax() +{ + float x,y; + const UPlotItem * item; + if(!_items.size()) + { + _minMax = QVector(); + } + else + { + _minMax = QVector(4); + } + for(int i=0; i<_items.size(); ++i) + { + item = qgraphicsitem_cast(_items.at(i)); + if(item) + { + x = item->data().x(); + y = item->data().y(); + if(i==0) + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + else + { + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + } +} + +void UPlotCurve::_addValue(UPlotItem * data) +{ + // add item + if(data) + { + float x = data->data().x(); + float y = data->data().y(); + if(_minMax.size() != 4) + { + _minMax = QVector(4); + } + if(_items.size()) + { + data->setPreviousItem((UPlotItem *)_items.last()); + QGraphicsLineItem * line = new QGraphicsLineItem(_rootItem); + line->setPen(_pen); + line->setVisible(false); + _items.append(line); + //Update min/max + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + else + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + data->setParentItem(_rootItem); + data->setZValue(1); + _items.append(data); + data->setVisible(false); + QPen pen = data->pen(); + pen.setColor(_itemsColor); + data->setPen(pen); + } + else + { + ULOGGER_ERROR("Data is null ?!?"); + } +} + +void UPlotCurve::addValue(UPlotItem * data) +{ + // add item + if(data) + { + this->_addValue(data); + emit dataChanged(this); + } +} + +void UPlotCurve::addValue(float x, float y) +{ + float width = 2; // TODO warn : hard coded value! + this->addValue(new UPlotItem(x,y,width)); +} + +void UPlotCurve::addValue(float y) +{ + float x = 0; + if(_items.size()) + { + UPlotItem * lastItem = (UPlotItem *)_items.last(); + x = lastItem->data().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->addValue(x,y); +} + +void UPlotCurve::addValue(const QString & value) +{ + bool ok; + float v = value.toFloat(&ok); + if(ok) + { + this->addValue(v); + } + else + { + ULOGGER_ERROR("Value not valid, must be a number, received %s", value.toStdString().c_str()); + } +} + +void UPlotCurve::addValues(QVector & data) +{ + for(int i=0; i_addValue(data.at(i)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & xs, const QVector & ys) +{ + float width = 2; // TODO warn : hard coded value! + for(int i=0; i_addValue(new UPlotItem(xs.at(i),ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +int UPlotCurve::removeItem(int index) +{ + if(index >= 0 && index < _items.size()) + { + if(index!=0) + { + index-=1; + delete _items.takeAt(index); // the line + } + else if(_items.size()>1) + { + delete _items.takeAt(index+1); // the line + } + UPlotItem * item = (UPlotItem *)_items.takeAt(index); // the plot item + //Update min/max + if(_minMax.size() == 4) + { + if(item->data().x() == _minMax[0] || item->data().x() == _minMax[1] || + item->data().y() == _minMax[2] || item->data().y() == _minMax[3]) + { + if(_items.size()) + { + UPlotItem * tmp = (UPlotItem *)_items.at(0); + float x = tmp->data().x(); + float y = tmp->data().y(); + _minMax[0]=x; + _minMax[1]=x; + _minMax[2]=y; + _minMax[3]=y; + for(int i = 2; i<_items.size(); i+=2) + { + tmp = (UPlotItem*)_items.at(i); + x = tmp->data().x(); + y = tmp->data().y(); + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + else + { + _minMax = QVector(); + } + } + } + delete item; + } + + return index; +} + +void UPlotCurve::removeItem(UPlotItem * item) // ownership is transfered to the caller +{ + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i) == item) + { + if(i!=0) + { + i-=1; + delete _items[i]; + _items.removeAt(i); + } + else if(_items.size()>1) + { + delete _items[i+1]; + _items.removeAt(i+1); + } + item->scene()->removeItem(item); + _items.removeAt(i); + break; + } + } +} + +void UPlotCurve::clear() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + qDeleteAll(_rootItem->childItems()); + _items.clear(); +} + +void UPlotCurve::setPen(const QPen & pen) +{ + _pen = pen; + for(int i=1; i<_items.size(); i+=2) + { + ((QGraphicsLineItem*) _items.at(i))->setPen(_pen); + } +} + +void UPlotCurve::setBrush(const QBrush & brush) +{ + _brush = brush; + ULOGGER_WARN("Not used..."); +} + +void UPlotCurve::setItemsColor(const QColor & color) +{ + if(color.isValid()) + { + _itemsColor.setRgb(color.red(), color.green(), color.blue(), _itemsColor.alpha()); + for(int i=0; i<_items.size(); i+=2) + { + QPen pen = ((UPlotItem*) _items.at(i))->pen(); + pen.setColor(_itemsColor); + ((UPlotItem*) _items.at(i))->setPen(pen); + } + } +} + +void UPlotCurve::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept) +{ + //ULOGGER_DEBUG("scaleX=%f, scaleY=%f, offsetX=%f, offsetY=%f, xDir=%d, yDir=%d, _plot->scene()->width()=%f, _plot->scene()->height=%f", scaleX, scaleY, offsetX, offsetY, xDir, yDir,_plot->scene()->width(),_plot->scene()->height()); + //make sure direction values are 1 or -1 + xDir<0?xDir=-1:xDir=1; + yDir<0?yDir=-1:yDir=1; + + bool hide = false; + int j=0; + for(int i=_items.size()-1; i>=0; --i) + { + if(i%2 == 0) + { + UPlotItem * item = (UPlotItem *)_items.at(i); + if(hide) + { + if(maxItemsKept == 0 || j <= maxItemsKept) + { + // if not visible, stop looping... all other items are normally already hidden + if(!item->isVisible()) + { + break; + } + item->setVisible(false); + } + else + { + //remove the item with his line + i = this->removeItem(i); + } + } + else + { + QPointF newPos(((xDir*item->data().x()+offsetX)*scaleX-item->rect().width()/2.0f), + ((yDir*item->data().y()+offsetY)*scaleY-item->rect().width()/2.0f)); + if(!item->isVisible()) + { + item->setVisible(true); + } + item->setPos(newPos); + } + ++j; + } + else + { + if(hide) + { + _items.at(i)->setVisible(false); + } + else + { + UPlotItem * from = (UPlotItem *)_items.at(i-1); + UPlotItem * to = (UPlotItem *)_items.at(i+1); + QGraphicsLineItem * lineItem = (QGraphicsLineItem *)_items.at(i); + lineItem->setLine((xDir*from->data().x()+offsetX)*scaleX, + (yDir*from->data().y()+offsetY)*scaleY, + (xDir*to->data().x()+offsetX)*scaleX, + (yDir*to->data().y()+offsetY)*scaleY); + if(!lineItem->isVisible()) + { + lineItem->setVisible(true); + } + //Don't update not visible items + // (Detect also if the curve goes forward or backward) + QLineF line = lineItem->line(); + if((line.x1() <= line.x2() && line.x2() < 0-((line.x2() - line.x1()))) || + (line.x1() > line.x2() && line.x2() > lineItem->scene()->sceneRect().width() + ((line.x1() - line.x2())))) + { + hide = true; + } + + } + } + } + +} + +void UPlotCurve::draw(QPainter * painter, const QRect & limits) +{ + if(painter) + { + for(int i=_items.size()-1; i>=0 && _items.at(i)->isVisible(); i-=2) + { + //plotItem + const UPlotItem * item = (const UPlotItem *)_items.at(i); + int x = (int)item->x(); + if(x<0) + { + break; + } + + // draw line in first + if(i-1>=0) + { + //lineItem + const QGraphicsLineItem * lineItem = (const QGraphicsLineItem *)_items.at(i-1); + QLine line = lineItem->line().toLine(); + if(limits.contains(line.p1()) || limits.contains(line.p2())) + { + QPointF intersection; + QLineF::IntersectType type; + type = lineItem->line().intersect(QLineF(limits.topLeft(), limits.bottomLeft()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.topLeft(), limits.topRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.bottomLeft(), limits.bottomRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + else + { + type = lineItem->line().intersect(QLineF(limits.topRight(), limits.bottomRight()), &intersection); + if(type == QLineF::BoundedIntersection) + { + !limits.contains(line.p1())?line.setP1(intersection.toPoint()):line.setP2(intersection.toPoint()); + } + } + } + } + painter->save(); + painter->setPen(this->pen()); + painter->setBrush(this->brush()); + painter->drawLine(line); + painter->restore(); + } + } + + if(limits.contains(item->pos().toPoint()) && limits.contains((item->pos() + QPointF(item->rect().width(), item->rect().height())).toPoint())) + { + painter->save(); + painter->setPen(QPen(_itemsColor)); + painter->drawEllipse(item->pos()+QPointF(item->rect().width()/2, item->rect().height()/2), (int)item->rect().width()/2, (int)item->rect().height()/2); + painter->restore(); + } + } + } +} + +int UPlotCurve::itemsSize() const +{ + return _items.size(); +} + +QPointF UPlotCurve::getItemData(int index) +{ + QPointF data; + //make sure the index point to a PlotItem {PlotItem, line, PlotItem, line...} + if(index>=0 && index < _items.size() && index % 2 == 0 ) + { + data = ((UPlotItem*)_items.at(index))->data(); + } + else + { + ULOGGER_ERROR("Wrong index, not pointing on a PlotItem"); + } + return data; +} + +void UPlotCurve::setVisible(bool visible) +{ + _visible = visible; + for(int i=0; i<_items.size(); ++i) + { + _items.at(i)->setVisible(visible); + } +} + +void UPlotCurve::setXIncrement(float increment) +{ + _xIncrement = increment; +} + +void UPlotCurve::setXStart(float val) +{ + _xStart = val; +} + +void UPlotCurve::setData(QVector & data) +{ + this->clear(); + for(int i = 0; iaddValue(data[i]); + } +} + +void UPlotCurve::setData(const QVector & x, const QVector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - x.size(); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + QVector::const_iterator i=x.begin(); + QVector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else if(y.size()>0 && x.size()==0) + { + this->setData(y); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const std::vector & x, const std::vector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(x.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + std::vector::const_iterator i=x.begin(); + std::vector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else if(y.size()>0 && x.size()==0) + { + this->setData(y); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const QVector & y) +{ + this->setData(y.toStdVector()); +} + +void UPlotCurve::setData(const std::vector & y) +{ + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(y.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + float x = 0; + std::vector::const_iterator j=y.begin(); + for(; j!=y.end(); ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(x++, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); +} + +void UPlotCurve::getData(QVector & x, QVector & y) const +{ + x.clear(); + y.clear(); + if(_items.size()) + { + x.resize((_items.size()-1)/2+1); + y.resize(x.size()); + int j=0; + for(int i=0; i<_items.size(); i+=2) + { + x[j] = ((UPlotItem*)_items.at(i))->data().x(); + y[j++] = ((UPlotItem*)_items.at(i))->data().y(); + } + } +} + + + + + +UPlotCurveThreshold::UPlotCurveThreshold(const QString & name, float thesholdValue, Qt::Orientation orientation, QObject * parent) : + UPlotCurve(name, parent), + _orientation(orientation) +{ + if(_orientation == Qt::Horizontal) + { + this->addValue(0, thesholdValue); + this->addValue(1, thesholdValue); + } + else + { + this->addValue(thesholdValue, 0); + this->addValue(thesholdValue, 1); + } +} + +UPlotCurveThreshold::~UPlotCurveThreshold() +{ + +} + +void UPlotCurveThreshold::setThreshold(float threshold) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%f", threshold); +#endif + if(_items.size() == 3) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), threshold)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), threshold)); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(threshold, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(threshold, item->data().y())); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } +} + +void UPlotCurveThreshold::setOrientation(Qt::Orientation orientation) +{ + if(_orientation != orientation) + { + _orientation = orientation; + if(_items.size() == 3) + { + UPlotItem * item = 0; + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().y(), item->data().x())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().y(), item->data().x())); + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + } +} + +void UPlotCurveThreshold::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, int maxItemsKept) +{ + if(_items.size() == 3) + { + if(_plot) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + //(xDir*item->data().x()+offsetX)*scaleX + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(-(offsetX-item->rect().width()/scaleX)/xDir, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF( (_plot->sceneRect().width()/scaleX-(offsetX+item->rect().width()/scaleX))/xDir, item->data().y())); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), -(offsetY-item->rect().height()/scaleY)/yDir)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), (_plot->sceneRect().height()/scaleY-(offsetY+item->rect().height()/scaleY))/yDir)); + } + this->updateMinMax(); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + UPlotCurve::update(scaleX, scaleY, offsetX, offsetY, xDir, yDir, maxItemsKept); +} + + + + + + + +UPlotAxis::UPlotAxis(Qt::Orientation orientation, float min, float max, QWidget * parent) : + QWidget(parent), + _orientation(orientation), + _reversed(false), + _gradMaxDigits(4), + _border(0) +{ + if(_orientation == Qt::Vertical) + { + _reversed = true; // default bottom->up + } +#ifdef WIN32 + this->setMinimumSize(15, 25); +#else + this->setMinimumSize(15, 25); +#endif + this->setAxis(min, max); // this initialize all attributes +} + +UPlotAxis::~UPlotAxis() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +// Vertical :bottom->up, horizontal :right->left +void UPlotAxis::setReversed(bool reversed) +{ + if(_reversed != reversed) + { + float min = _min; + _min = _max; + _max = min; + } + _reversed = reversed; +} + +void UPlotAxis::setAxis(float & min, float & max) +{ + int borderMin = 0; + int borderMax = 0; + if(_orientation == Qt::Vertical) + { + borderMin = borderMax = this->fontMetrics().height()/2; + } + else + { + borderMin = this->fontMetrics().width(QString::number(_min,'g',_gradMaxDigits))/2; + borderMax = this->fontMetrics().width(QString::number(_max,'g',_gradMaxDigits))/2; + } + int border = borderMin>borderMax?borderMin:borderMax; + int borderDelta; + int length; + if(_orientation == Qt::Vertical) + { + length = (this->height()-border*2); + } + else + { + length = (this->width()-border*2); + } + + if(length <= 70) + { + _count = 5; + } + else if(length <= 175) + { + _count = 10; + } + else if(length <= 350) + { + _count = 20; + } + else if(length <= 700) + { + _count = 40; + } + else if(length <= 1000) + { + _count = 60; + } + else if(length <= 1300) + { + _count = 80; + } + else + { + _count = 100; + } + + // Rounding min and max + if(min != max) + { + float mul = 1; + float rangef = max - min; + int countStep = _count/5; + float val; + for(int i=0; i<6; ++i) + { + val = (rangef/float(countStep)) * mul; + if( val >= 1.0f && val < 10.0f) + { + break; + } + else if(val<1) + { + mul *= 10.0f; + } + else + { + mul /= 10.0f; + } + } + //ULOGGER_DEBUG("min=%f, max=%f", min, max); + int minR = min*mul-0.9; + int maxR = max*mul+0.9; + min = float(minR)/mul; + max = float(maxR)/mul; + //ULOGGER_DEBUG("mul=%f, minR=%d, maxR=%d,countStep=%d", mul, minR, maxR, countStep); + } + + _min = min; + _max = max; + + if(_reversed) + { + _min = _max; + _max = min; + } + + if(_orientation == Qt::Vertical) + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + else + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + + if(borderDelta%2 != 0) + { + borderDelta+=1; + } + + _border = border + borderDelta/2; + + //Resize estimation + if(_orientation == Qt::Vertical) + { + int minWidth = 0; + for (int i = 0; i <= _count; i+=5) + { + QString n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + if(this->fontMetrics().width(n) > minWidth) + { + minWidth = this->fontMetrics().width(n); + } + } + this->setMinimumWidth(15+minWidth); + } +} + +void UPlotAxis::paintEvent(QPaintEvent * event) +{ + QPainter painter(this); + if(_orientation == Qt::Vertical) + { + painter.translate(0, _border); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(this->width(), 0, this->width()-10, 0); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(this->width()-(12+n.sizeHint().width()), n.sizeHint().height()/2-2, n.text()); + } + else + { + painter.drawLine(this->width(), 0, this->width()-5, 0); + } + painter.translate(0, _step); + } + } + else + { + painter.translate(_border, 0); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(0, 0, 0, 10); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(-(n.sizeHint().width()/2)+1, 22, n.text()); + } + else + { + painter.drawLine(0, 0, 0, 5); + } + painter.translate(_step, 0); + } + } +} + + + + +UPlotLegendItem::UPlotLegendItem(UPlotCurve * curve, QWidget * parent) : + QPushButton(parent), + _curve(curve) +{ + QString nameSpaced = curve->name(); + nameSpaced.replace('_', ' '); + this->setText(nameSpaced); + + this->setIcon(QIcon(this->createSymbol(curve->pen(), curve->brush()))); + this->setIconSize(QSize(25,20)); + + _aChangeText = new QAction(tr("Change text..."), this); + _aResetText = new QAction(tr("Reset text..."), this); + _aChangeColor = new QAction(tr("Change color..."), this); + _aCopyToClipboard = new QAction(tr("Copy curve data to the clipboard"), this); + _aMoveUp = new QAction(tr("Move up"), this); + _aMoveDown = new QAction(tr("Move down"), this); + _aRemoveCurve = new QAction(tr("Remove this curve"), this); + _menu = new QMenu(tr("Curve"), this); + _menu->addAction(_aChangeText); + _menu->addAction(_aResetText); + _menu->addAction(_aChangeColor); + _menu->addAction(_aCopyToClipboard); + _menu->addSeparator(); + _menu->addAction(_aMoveUp); + _menu->addAction(_aMoveDown); + _menu->addSeparator(); + _menu->addAction(_aRemoveCurve); +} + +UPlotLegendItem::~UPlotLegendItem() +{ + +} +void UPlotLegendItem::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aChangeText) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeText->text(), tr("Name :"), QLineEdit::Normal, this->text(), &ok); + if(ok && !text.isEmpty()) + { + this->setText(text); + } + } + else if(action == _aResetText) + { + if(_curve) + { + this->setText(_curve->name()); + } + } + else if(action == _aChangeColor) + { + if(_curve) + { + QPen pen = _curve->pen(); + QColor color = QColorDialog::getColor(pen.color(), this); + if(color.isValid()) + { + pen.setColor(color); + _curve->setPen(pen); + this->setIcon(QIcon(this->createSymbol(_curve->pen(), _curve->brush()))); + } + } + } + else if (action == _aCopyToClipboard) + { + if(_curve) + { + QVector x; + QVector y; + _curve->getData(x, y); + QString textX; + QString textY; + for(int i=0; isetText((textX+"\n")+textY); + } + } + else if(action == _aRemoveCurve) + { + emit legendItemRemoved(_curve); + } + else if(action == _aMoveUp) + { + emit moveUpRequest(this); + } + else if(action == _aMoveDown) + { + emit moveDownRequest(this); + } +} + +QPixmap UPlotLegendItem::createSymbol(const QPen & pen, const QBrush & brush) +{ + QPixmap pixmap(50, 50); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QPen p = pen; + p.setWidthF(4.0); + painter.setPen(p); + painter.drawLine(0.0, 25.0, 50.0, 25.0); + return pixmap; +} + + + + + + +UPlotLegend::UPlotLegend(QWidget * parent) : + QWidget(parent), + _flat(true) +{ + //menu + _aUseFlatButtons = new QAction(tr("Use flat buttons"), this); + _aUseFlatButtons->setCheckable(true); + _aUseFlatButtons->setChecked(_flat); + _menu = new QMenu(tr("Legend"), this); + _menu->addAction(_aUseFlatButtons); + + QVBoxLayout * vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins(0,0,0,0); + this->setLayout(vLayout); + vLayout->addStretch(0); + vLayout->setSpacing(0); +} + +UPlotLegend::~UPlotLegend() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +void UPlotLegend::setFlat(bool on) +{ + if(_flat != on) + { + _flat = on; + QList items = this->findChildren(); + for(int i=0; isetFlat(_flat); + items.at(i)->setChecked(!items.at(i)->isChecked()); + } + _aUseFlatButtons->setChecked(_flat); + } +} + +void UPlotLegend::addItem(UPlotCurve * curve) +{ + if(curve) + { + UPlotLegendItem * legendItem = new UPlotLegendItem(curve, this); + legendItem->setAutoDefault(false); + legendItem->setFlat(_flat); + legendItem->setCheckable(true); + legendItem->setChecked(false); + connect(legendItem, SIGNAL(toggled(bool)), this, SLOT(redirectToggled(bool))); + connect(legendItem, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeLegendItem(const UPlotCurve *))); + connect(legendItem, SIGNAL(moveUpRequest(UPlotLegendItem *)), this, SLOT(moveUp(UPlotLegendItem *))); + connect(legendItem, SIGNAL(moveDownRequest(UPlotLegendItem *)), this, SLOT(moveDown(UPlotLegendItem *))); + + // layout + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(legendItem); + hLayout->addStretch(0); + hLayout->setMargin(0); + + // add to the legend + ((QVBoxLayout*)this->layout())->insertLayout(this->layout()->count()-1, hLayout); + } +} + +bool UPlotLegend::remove(const UPlotCurve * curve) +{ + QList items = this->findChildren(); + for(int i=0; icurve() == curve) + { + delete items.at(i); + return true; + } + } + return false; +} + +void UPlotLegend::removeLegendItem(const UPlotCurve * curve) +{ + if(this->remove(curve)) + { + emit legendItemRemoved(curve); + } +} + +void UPlotLegend::moveUp(UPlotLegendItem * item) +{ + int index = -1; + QLayoutItem * layoutItem = 0; + for(int i=0; ilayout()->count(); ++i) + { + if(this->layout()->itemAt(i)->layout() && + this->layout()->itemAt(i)->layout()->indexOf(item) != -1) + { + layoutItem = this->layout()->itemAt(i); + index = i; + break; + } + } + if(index > 0 && layoutItem) + { + this->layout()->removeItem(layoutItem); + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(layoutItem->layout()->itemAt(0)->widget()); + hLayout->addStretch(0); + hLayout->setMargin(0); + ((QVBoxLayout*)this->layout())->insertLayout(index-1, hLayout); + delete layoutItem; + emit legendItemMoved(item->curve(), index-1); + } +} + +void UPlotLegend::moveDown(UPlotLegendItem * item) +{ + int index = -1; + QLayoutItem * layoutItem = 0; + for(int i=0; ilayout()->count(); ++i) + { + if(this->layout()->itemAt(i)->layout() && + this->layout()->itemAt(i)->layout()->indexOf(item) != -1) + { + layoutItem = this->layout()->itemAt(i); + index = i; + break; + } + } + if(index < this->layout()->count()-2 && layoutItem) + { + this->layout()->removeItem(layoutItem); + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(layoutItem->layout()->itemAt(0)->widget()); + hLayout->addStretch(0); + hLayout->setMargin(0); + ((QVBoxLayout*)this->layout())->insertLayout(index+1, hLayout); + delete layoutItem; + emit legendItemMoved(item->curve(), index+1); + } +} + +void UPlotLegend::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aUseFlatButtons) + { + this->setFlat(_aUseFlatButtons->isChecked()); + } +} + +void UPlotLegend::redirectToggled(bool toggled) +{ + if(sender()) + { + UPlotLegendItem * item = qobject_cast(sender()); + if(item) + { + emit legendItemToggled(item->curve(), _flat?!toggled:toggled); + } + } +} + + + + + + + +UOrientableLabel::UOrientableLabel(const QString & text, Qt::Orientation orientation, QWidget * parent) : + QLabel(text, parent), + _orientation(orientation) +{ +} + +UOrientableLabel::~UOrientableLabel() +{ +} + +QSize UOrientableLabel::sizeHint() const +{ + QSize size = QLabel::sizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; + +} + +QSize UOrientableLabel::minimumSizeHint() const +{ + QSize size = QLabel::minimumSizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; +} + +void UOrientableLabel::setOrientation(Qt::Orientation orientation) +{ + _orientation = orientation; + switch(orientation) + { + case Qt::Horizontal: + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + + case Qt::Vertical: + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + break; + } +} + +void UOrientableLabel::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + QRect r = rect(); + switch (_orientation) + { + case Qt::Horizontal: + break; + case Qt::Vertical: + p.rotate(-90); + p.translate(-height(), 0); + QSize size = r.size(); + size.transpose(); + r.setSize(size); + break; + } + p.drawText(r, this->alignment() | (this->wordWrap()?Qt::TextWordWrap:0), this->text()); +} + + + + + + + + + + + + + +UPlot::UPlot(QWidget *parent) : + QWidget(parent), + _maxVisibleItems(-1), + _autoScreenCaptureFormat("png"), + _bgColor(Qt::white) +{ + this->setupUi(); + this->createActions(); + this->createMenus(); + + // This will update actions + this->showLegend(true); + this->setGraphicsView(false); + this->setMaxVisibleItems(0); + this->showGrid(false); + this->showRefreshRate(false); + this->keepAllData(false); + + for(int i=0; i<4; ++i) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + if(i<2) + { + _fixedAxis[i] = false; + } + } + + _mouseCurrentPos = _mousePressedPos; // for zooming + + _refreshIntervalTime.start(); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + + _penStyleCount = rand() % 10 + 1; // rand 1->10 + _workingDirectory = QDir::homePath(); +} + +UPlot::~UPlot() +{ + _aAutoScreenCapture->setChecked(false); +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->title().toStdString().c_str()); +#endif + this->removeCurves(); +} + +void UPlot::setupUi() +{ + _legend = new UPlotLegend(this); + _view = new QGraphicsView(this); + _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setScene(new QGraphicsScene(0,0,0,0,this)); + _view->setStyleSheet( "QGraphicsView { border-style: none; }" ); + _sceneRoot = _view->scene()->addText(""); + _sceneRoot->translate(0,0); + _graphicsViewHolder = new QWidget(this); + _graphicsViewHolder->setMinimumSize(100,100); + _graphicsViewHolder->setMouseTracking(true); + _verticalAxis = new UPlotAxis(Qt::Vertical, 0, 1, this); + _horizontalAxis = new UPlotAxis(Qt::Horizontal, 0, 1, this); + _title = new QLabel(""); + _xLabel = new QLabel(""); + _refreshRate = new QLabel(""); + _yLabel = new UOrientableLabel(""); + _yLabel->setOrientation(Qt::Vertical); + _title->setAlignment(Qt::AlignCenter); + _xLabel->setAlignment(Qt::AlignCenter); + _yLabel->setAlignment(Qt::AlignCenter); + _refreshRate->setAlignment(Qt::AlignCenter); + _title->setWordWrap(true); + _xLabel->setWordWrap(true); + _yLabel->setWordWrap(true); + _title->setVisible(false); + _xLabel->setVisible(false); + _yLabel->setVisible(false); + _refreshRate->setVisible(false); + + //layouts + QVBoxLayout * vLayout = new QVBoxLayout(_graphicsViewHolder); + vLayout->setContentsMargins(0,0,0,0); + vLayout->addWidget(_view); + + QGridLayout * grid = new QGridLayout(this); + grid->setContentsMargins(0,0,0,0); + grid->addWidget(_title, 0, 2); + grid->addWidget(_yLabel, 1, 0); + grid->addWidget(_verticalAxis, 1, 1); + grid->addWidget(_refreshRate, 2, 1); + grid->addWidget(_graphicsViewHolder, 1, 2); + grid->setColumnStretch(2, 1); + grid->setRowStretch(1, 1); + grid->addWidget(_horizontalAxis, 2, 2); + grid->addWidget(_xLabel, 3, 2); + grid->addWidget(_legend, 1, 3); + + connect(_legend, SIGNAL(legendItemToggled(const UPlotCurve *, bool)), this, SLOT(showCurve(const UPlotCurve *, bool))); + connect(_legend, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeCurve(const UPlotCurve *))); + connect(_legend, SIGNAL(legendItemMoved(const UPlotCurve *, int)), this, SLOT(moveCurve(const UPlotCurve *, int))); +} + +void UPlot::createActions() +{ + _aShowLegend = new QAction(tr("Show legend"), this); + _aShowLegend->setCheckable(true); + _aShowGrid = new QAction(tr("Show grid"), this); + _aShowGrid->setCheckable(true); + _aShowRefreshRate = new QAction(tr("Show refresh rate"), this); + _aShowRefreshRate->setCheckable(true); + _aMouseTracking = new QAction(tr("Mouse tracking"), this); + _aMouseTracking->setCheckable(true); + _aGraphicsView = new QAction(tr("Graphics view"), this); + _aGraphicsView->setCheckable(true); + _aKeepAllData = new QAction(tr("Keep all data"), this); + _aKeepAllData->setCheckable(true); + _aLimit0 = new QAction(tr("No maximum items shown"), this); + _aLimit10 = new QAction(tr("10"), this); + _aLimit50 = new QAction(tr("50"), this); + _aLimit100 = new QAction(tr("100"), this); + _aLimit500 = new QAction(tr("500"), this); + _aLimit1000 = new QAction(tr("1000"), this); + _aLimitCustom = new QAction(tr(""), this); + _aLimit0->setCheckable(true); + _aLimit10->setCheckable(true); + _aLimit50->setCheckable(true); + _aLimit100->setCheckable(true); + _aLimit500->setCheckable(true); + _aLimit1000->setCheckable(true); + _aLimitCustom->setCheckable(true); + _aLimitCustom->setVisible(false); + _aAddVerticalLine = new QAction(tr("Vertical line..."), this); + _aAddHorizontalLine = new QAction(tr("Horizontal line..."), this); + _aChangeTitle = new QAction(tr("Change title"), this); + _aChangeXLabel = new QAction(tr("Change X label..."), this); + _aChangeYLabel = new QAction(tr("Change Y label..."), this); + _aChangeBackgroundColor = new QAction(tr("Change bg color..."), this); + _aYLabelVertical = new QAction(tr("Vertical orientation"), this); + _aYLabelVertical->setCheckable(true); + _aYLabelVertical->setChecked(true); + _aSaveFigure = new QAction(tr("Save figure..."), this); + _aAutoScreenCapture = new QAction(tr("Auto screen capture..."), this); + _aAutoScreenCapture->setCheckable(true); + _aClearData = new QAction(tr("Clear data"), this); + + QActionGroup * grpLimit = new QActionGroup(this); + grpLimit->addAction(_aLimit0); + grpLimit->addAction(_aLimit10); + grpLimit->addAction(_aLimit50); + grpLimit->addAction(_aLimit100); + grpLimit->addAction(_aLimit500); + grpLimit->addAction(_aLimit1000); + grpLimit->addAction(_aLimitCustom); + _aLimit0->setChecked(true); +} + +void UPlot::createMenus() +{ + _menu = new QMenu(tr("Plot"), this); + _menu->addAction(_aShowLegend); + _menu->addAction(_aShowGrid); + _menu->addAction(_aShowRefreshRate); + _menu->addAction(_aMouseTracking); + _menu->addAction(_aGraphicsView); + _menu->addAction(_aKeepAllData); + _menu->addSeparator()->setStatusTip(tr("Maximum items shown")); + _menu->addAction(_aLimit0); + _menu->addAction(_aLimit10); + _menu->addAction(_aLimit50); + _menu->addAction(_aLimit100); + _menu->addAction(_aLimit500); + _menu->addAction(_aLimit1000); + _menu->addAction(_aLimitCustom); + _menu->addSeparator(); + QMenu * addLineMenu = _menu->addMenu(tr("Add line")); + addLineMenu->addAction(_aAddHorizontalLine); + addLineMenu->addAction(_aAddVerticalLine); + _menu->addSeparator(); + _menu->addAction(_aChangeTitle); + _menu->addAction(_aChangeXLabel); + QMenu * yLabelMenu = _menu->addMenu(tr("Y label")); + yLabelMenu->addAction(_aChangeYLabel); + yLabelMenu->addAction(_aYLabelVertical); + _menu->addAction(_aChangeBackgroundColor); + _menu->addAction(_aSaveFigure); + _menu->addAction(_aAutoScreenCapture); + _menu->addSeparator(); + _menu->addAction(_aClearData); + +} + +UPlotCurve * UPlot::addCurve(const QString & curveName, const QColor & color) +{ + // add curve + UPlotCurve * curve = new UPlotCurve(curveName, this); + if(color.isValid()) + { + curve->setPen(color); + } + else + { + curve->setPen(this->getRandomPenColored()); + } + this->addCurve(curve); + return curve; +} + +bool UPlot::addCurve(UPlotCurve * curve, bool ownershipTransferred) +{ + if(curve) + { +#if PRINT_DEBUG + ULOGGER_DEBUG("Adding curve \"%s\" to plot \"%s\"...", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); +#endif + // only last curve can trigger an update, so disable previous connections + if(!qobject_cast(curve)) + { + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + disconnect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + // add curve + _curves.append(curve); + curve->attach(this); + curve->setItemsColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.red(), _bgColor.alpha())); + if(ownershipTransferred) + { + curve->setParent(this); + } + this->updateAxis(curve); + curve->setXStart(_axisMaximums[1]); + + connect(curve, SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + + _legend->addItem(curve); + +#if PRINT_DEBUG + ULOGGER_DEBUG("Curve \"%s\" added to plot \"%s\"", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); +#endif + + return true; + } + else + { + ULOGGER_ERROR("The curve is null!"); + } + return false; +} + +QStringList UPlot::curveNames() +{ + QStringList names; + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter) + { + names.append((*iter)->name()); + } + } + return names; +} + +bool UPlot::contains(const QString & curveName) +{ + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter && (*iter)->name().compare(curveName) == 0) + { + return true; + } + } + return false; +} + +QPen UPlot::getRandomPenColored() +{ + return QPen((Qt::GlobalColor)(_penStyleCount++ % 12 + 7 )); +} + +void UPlot::replot(QPainter * painter) +{ + if(_maxVisibleItems>0) + { + UPlotCurve * c = 0; + int maxItem = 0; + // find the curve with the most items + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible() && ((UPlotCurve *)(*i))->itemsSize() > maxItem) + { + c = *i; + maxItem = c->itemsSize(); + } + } + if(c && (maxItem-1)/2+1 > _maxVisibleItems && _axisMaximums[0] < c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x()) + { + _axisMaximums[0] = c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x(); + } + } + + float axis[4] = {0}; + for(int i=0; i<4; ++i) + { + axis[i] = _axisMaximums[i]; + } + + _verticalAxis->setAxis(axis[2], axis[3]); + _horizontalAxis->setAxis(axis[0], axis[1]); + if(_aGraphicsView->isChecked() && !painter) + { + _verticalAxis->update(); + _horizontalAxis->update(); + } + + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + + QRectF newRect(0,0, _graphicsViewHolder->size().width(), _graphicsViewHolder->size().height()); + _view->scene()->setSceneRect(newRect); + float borderHor = (float)_horizontalAxis->border(); + float borderVer = (float)_verticalAxis->border(); + + //grid + qDeleteAll(hGridLines); + hGridLines.clear(); + qDeleteAll(vGridLines); + vGridLines.clear(); + if(_aShowGrid->isChecked()) + { + // TODO make a PlotGrid class ? + float w = newRect.width()-(borderHor*2); + float h = newRect.height()-(borderVer*2); + float stepH = w / float(_horizontalAxis->count()); + float stepV = h / float(_verticalAxis->count()); + QPen dashPen(Qt::DashLine); + dashPen.setColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.blue(), 100)); + QPen pen(dashPen.color()); + for(float i=0.0f; i*stepV <= h+stepV; i+=5.0f) + { + //horizontal lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->save(); + painter->setPen(pen); + painter->drawLine(0, stepV*i+borderVer+0.5f, borderHor, stepV*i+borderVer+0.5f); + + painter->setPen(dashPen); + painter->drawLine(borderHor, stepV*i+borderVer+0.5f, w+borderHor, stepV*i+borderVer+0.5f); + + painter->setPen(pen); + painter->drawLine(w+borderHor, stepV*i+borderVer+0.5f, w+borderHor*2, stepV*i+borderVer+0.5f); + painter->restore(); + } + } + else + { + hGridLines.append(new QGraphicsLineItem(0, stepV*i+borderVer, borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(pen); + hGridLines.append(new QGraphicsLineItem(borderHor, stepV*i+borderVer, w+borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(dashPen); + hGridLines.append(new QGraphicsLineItem(w+borderHor, stepV*i+borderVer, w+borderHor*2, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(pen); + } + } + for(float i=0; i*stepH < w+stepH; i+=5.0f) + { + //vertical lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->save(); + painter->setPen(pen); + painter->drawLine(stepH*i+borderHor+0.5f, 0, stepH*i+borderHor+0.5f, borderVer); + + painter->setPen(dashPen); + painter->drawLine(stepH*i+borderHor+0.5f, borderVer, stepH*i+borderHor+0.5f, h+borderVer); + + painter->setPen(pen); + painter->drawLine(stepH*i+borderHor+0.5f, h+borderVer, stepH*i+borderHor+0.5f, h+borderVer*2); + painter->restore(); + } + } + else + { + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, 0, stepH*i+borderHor, borderVer, _sceneRoot)); + vGridLines.last()->setPen(pen); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, borderVer, stepH*i+borderHor, h+borderVer, _sceneRoot)); + vGridLines.last()->setPen(dashPen); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, h+borderVer, stepH*i+borderHor, h+borderVer*2, _sceneRoot)); + vGridLines.last()->setPen(pen); + } + } + } + + // curves + float scaleX = 1; + float scaleY = 1; + float den = 0; + den = axis[1] - axis[0]; + if(den != 0) + { + scaleX = (newRect.width()-(borderHor*2)) / den; + } + den = axis[3] - axis[2]; + if(den != 0) + { + scaleY = (newRect.height()-(borderVer*2)) / den; + } + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible()) + { + float xDir = 1.0f; + float yDir = -1.0f; + (*i)->update(scaleX, + scaleY, + xDir<0?axis[1]+borderHor/scaleX:-(axis[0]-borderHor/scaleX), + yDir<0?axis[3]+borderVer/scaleY:-(axis[2]-borderVer/scaleY), + xDir, + yDir, + _aKeepAllData->isChecked()?0:_maxVisibleItems); + if(painter) + { + (*i)->draw(painter, QRect(0,0,_graphicsViewHolder->rect().width(), _graphicsViewHolder->rect().height())); + } + } + } + + // Update refresh rate + if(_aShowRefreshRate->isChecked()) + { + int refreshRate = qRound(1000.0f/float(_refreshIntervalTime.restart())); + if(refreshRate > 0 && refreshRate < _lowestRefreshRate) + { + _lowestRefreshRate = refreshRate; + } + // Refresh the label only after each 1000 ms + if(_refreshStartTime.elapsed() > 1000) + { + _refreshRate->setText(QString::number(_lowestRefreshRate)); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + } + } +} + +void UPlot::setFixedXAxis(float x1, float x2) +{ + _fixedAxis[0] = true; + _axisMaximums[0] = x1; + _axisMaximums[1] = x2; +} + +void UPlot::setFixedYAxis(float y1, float y2) +{ + _fixedAxis[1] = true; + _axisMaximums[2] = y1; + _axisMaximums[3] = y2; +} + +void UPlot::updateAxis(const UPlotCurve * curve) +{ + if(curve && curve->isVisible() && curve->itemsSize() && curve->isMinMaxValid()) + { + const QVector & minMax = curve->getMinMax(); + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", minMax[0], minMax[1], minMax[2], minMax[3]); + if(minMax.size() != 4) + { + ULOGGER_ERROR("minMax size != 4 ?!?"); + return; + } + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } +} + +bool UPlot::updateAxis(float x1, float x2, float y1, float y2) +{ + bool modified = false; + modified = updateAxis(x1,y1); + if(!modified) + { + modified = updateAxis(x2,y2); + } + else + { + updateAxis(x2,y2); + } + return modified; +} + +bool UPlot::updateAxis(float x, float y) +{ + //ULOGGER_DEBUG("x=%f, y=%f", x,y); + bool modified = false; + if(!_fixedAxis[0] && (!_axisMaximumsSet[0] || x < _axisMaximums[0])) + { + _axisMaximums[0] = x; + _axisMaximumsSet[0] = true; + modified = true; + } + + if(!_fixedAxis[0] && (!_axisMaximumsSet[1] || x > _axisMaximums[1])) + { + _axisMaximums[1] = x; + _axisMaximumsSet[1] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[2] || y < _axisMaximums[2])) + { + _axisMaximums[2] = y; + _axisMaximumsSet[2] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[3] || y > _axisMaximums[3])) + { + _axisMaximums[3] = y; + _axisMaximumsSet[3] = true; + modified = true; + } + + return modified; +} + +void UPlot::updateAxis() +{ + //Reset the axis + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + } + } + + for(int i=0; i<_curves.size(); ++i) + { + if(_curves.at(i)->isVisible() && _curves.at(i)->isMinMaxValid()) + { + const QVector & minMax = _curves.at(i)->getMinMax(); + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + } + } + + _aGraphicsView->isChecked()?this->replot(0):this->update(); + + this->captureScreen(); +} + +void UPlot::paintEvent(QPaintEvent * event) +{ +#if PRINT_DEBUG + UDEBUG(""); +#endif + if(!_aGraphicsView->isChecked()) + { + QPainter painter(this); + painter.translate(_graphicsViewHolder->pos()); + painter.save(); + painter.setBrush(_bgColor); + painter.setPen(QPen(Qt::NoPen)); + painter.drawRect(_graphicsViewHolder->rect()); + painter.restore(); + + this->replot(&painter); + + if(_mouseCurrentPos != _mousePressedPos) + { + painter.save(); + int left, top, right, bottom; + left = _mousePressedPos.x() < _mouseCurrentPos.x() ? _mousePressedPos.x()-_graphicsViewHolder->x():_mouseCurrentPos.x()-_graphicsViewHolder->x(); + top = _mousePressedPos.y() < _mouseCurrentPos.y() ? _mousePressedPos.y()-1-_graphicsViewHolder->y():_mouseCurrentPos.y()-1-_graphicsViewHolder->y(); + right = _mousePressedPos.x() > _mouseCurrentPos.x() ? _mousePressedPos.x()-_graphicsViewHolder->x():_mouseCurrentPos.x()-_graphicsViewHolder->x(); + bottom = _mousePressedPos.y() > _mouseCurrentPos.y() ? _mousePressedPos.y()-_graphicsViewHolder->y():_mouseCurrentPos.y()-_graphicsViewHolder->y(); + if(left <= 0) + { + left = 1; + } + if(right >= _graphicsViewHolder->width()) + { + right = _graphicsViewHolder->width()-1; + } + if(top <= 0) + { + top = 1; + } + if(bottom >= _graphicsViewHolder->height()) + { + bottom = _graphicsViewHolder->height()-1; + } + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(QColor(255-_bgColor.red(),255-_bgColor.green(),255-_bgColor.blue(),100))); + painter.drawRect(0, 0, _graphicsViewHolder->width(), top); + painter.drawRect(0, top, left, bottom-top); + painter.drawRect(right, top, _graphicsViewHolder->width()-right, bottom-top); + painter.drawRect(0, bottom, _graphicsViewHolder->width(), _graphicsViewHolder->height()-bottom); + painter.restore(); + } + } + else + { + QWidget::paintEvent(event); + } +} + +void UPlot::resizeEvent(QResizeEvent * event) +{ + if(_aGraphicsView->isChecked()) + { + this->replot(0); + } + QWidget::resizeEvent(event); +} + +void UPlot::mousePressEvent(QMouseEvent * event) +{ + _mousePressedPos = event->pos(); + _mouseCurrentPos = _mousePressedPos; + QWidget::mousePressEvent(event); +} + +void UPlot::mouseMoveEvent(QMouseEvent * event) +{ + if(!_aGraphicsView->isChecked()) + { + if(!(QApplication::mouseButtons() & Qt::LeftButton)) + { + _mousePressedPos = _mouseCurrentPos; + } + + float x,y; + if(mousePosToValue(event->pos(), x ,y)) + { + if(QApplication::mouseButtons() & Qt::LeftButton) + { + _mouseCurrentPos = event->pos(); + this->update(); + } + + int xPos = event->pos().x() - _graphicsViewHolder->pos().x(); + int yPos = event->pos().y() - _graphicsViewHolder->pos().y(); + if((QApplication::mouseButtons() & Qt::LeftButton) || + (_aMouseTracking->isChecked() && xPos>=0 && yPos>=0 && xPos<_graphicsViewHolder->width() && yPos<_graphicsViewHolder->height())) + { + QToolTip::showText(event->globalPos(), QString("%1,%2").arg(x).arg(y)); + } + else + { + QToolTip::hideText(); + } + } + else + { + QToolTip::hideText(); + } + } + QWidget::mouseMoveEvent(event); +} + +void UPlot::mouseReleaseEvent(QMouseEvent * event) +{ + if(_mousePressedPos != _mouseCurrentPos) + { + int left,top,bottom,right; + + left = _mousePressedPos.x() < _mouseCurrentPos.x() ? _mousePressedPos.x():_mouseCurrentPos.x(); + top = _mousePressedPos.y() < _mouseCurrentPos.y() ? _mousePressedPos.y():_mouseCurrentPos.y(); + right = _mousePressedPos.x() > _mouseCurrentPos.x() ? _mousePressedPos.x():_mouseCurrentPos.x(); + bottom = _mousePressedPos.y() > _mouseCurrentPos.y() ? _mousePressedPos.y():_mouseCurrentPos.y(); + + if(right - left > 5 || bottom - top > 5) + { + float axis[4]; + if(mousePosToValue(QPoint(left, top), axis[0], axis[3]) && mousePosToValue(QPoint(right, bottom), axis[1], axis[2])) + { +#if PRINT_DEBUG + UDEBUG("resize! new axis = [%f, %f, %f, %f]", axis[0], axis[1], axis[2], axis[3]); +#endif + //update axis (only if not fixed) + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = axis[i]; + } + } + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } + } + _mousePressedPos = _mouseCurrentPos; + } + QWidget::mouseReleaseEvent(event); +} + +void UPlot::mouseDoubleClickEvent(QMouseEvent * event) +{ + this->updateAxis(); + QWidget::mouseDoubleClickEvent(event); +} + +bool UPlot::mousePosToValue(const QPoint & pos, float & x, float & y) +{ + int xPos = pos.x() - _graphicsViewHolder->pos().x() - _horizontalAxis->border(); + int yPos = pos.y() - _graphicsViewHolder->pos().y() - _verticalAxis->border(); + int maxX = _graphicsViewHolder->width() - _horizontalAxis->border()*2; + int maxY = _graphicsViewHolder->height() - _verticalAxis->border()*2; + if(maxX == 0 || maxY == 0) + { + return false; + } + + if(xPos < 0) + { + xPos = 0; + } + else if(xPos > maxX) + { + xPos = maxX; + } + + if(yPos < 0) + { + yPos = 0; + } + else if(yPos > maxY) + { + yPos = maxY; + } + + //UDEBUG("IN"); + //UDEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + //UDEBUG("border hor=%f ver=%f", (float)_horizontalAxis->border(), (float)_verticalAxis->border()); + //UDEBUG("rect = %d,%d %d,%d", _graphicsViewHolder->pos().x(), _graphicsViewHolder->pos().y(), _graphicsViewHolder->width(), _graphicsViewHolder->height()); + //UDEBUG("%d,%d", event->pos().x(), event->pos().y()); + //UDEBUG("x/y %d,%d", x, y); + //UDEBUG("max %d,%d", maxX, maxY); + + //UDEBUG("map %f,%f", x, y); + x = _axisMaximums[0] + float(xPos)*(_axisMaximums[1] - _axisMaximums[0]) / float(maxX); + y = _axisMaximums[2] + float(maxY - yPos)*(_axisMaximums[3] - _axisMaximums[2]) / float(maxY); + return true; +} + +void UPlot::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + + if(!action) + { + return; + } + else if(action == _aShowLegend) + { + this->showLegend(_aShowLegend->isChecked()); + } + else if(action == _aShowGrid) + { + this->showGrid(_aShowGrid->isChecked()); + } + else if(action == _aShowRefreshRate) + { + this->showRefreshRate(_aShowRefreshRate->isChecked()); + } + else if(action == _aMouseTracking) + { + this->trackMouse(_aMouseTracking->isChecked()); + } + else if(action == _aGraphicsView) + { + this->setGraphicsView(_aGraphicsView->isChecked()); + } + else if(action == _aKeepAllData) + { + this->keepAllData(_aKeepAllData->isChecked()); + } + else if(action == _aLimit0 || + action == _aLimit10 || + action == _aLimit50 || + action == _aLimit100 || + action == _aLimit500 || + action == _aLimit1000 || + action == _aLimitCustom) + { + this->setMaxVisibleItems(action->text().toInt()); + } + else if(action == _aAddVerticalLine || action == _aAddHorizontalLine) + { + bool ok; + QString text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + while(ok && text.isEmpty()) + { + QMessageBox::warning(this, action->text(), tr("The name is not valid or it is already used in this plot.")); + text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + } + if(ok) + { + double min = _axisMaximums[2]; + double max = _axisMaximums[3]; + QString axis = "Y"; + if(action == _aAddVerticalLine) + { + min = _axisMaximums[0]; + max = _axisMaximums[1]; + axis = "X"; + } + double value = QInputDialog::getDouble(this, + action->text(), + tr("%1 value (min=%2, max=%3):").arg(axis).arg(min).arg(max), + (min+max)/2, + -2147483647, + 2147483647, + 4, + &ok); + if(ok) + { + if(action == _aAddHorizontalLine) + { + this->addThreshold(text, value, Qt::Horizontal); + } + else + { + this->addThreshold(text, value, Qt::Vertical); + } + } + } + } + else if(action == _aChangeTitle) + { + bool ok; + QString text = _title->text(); + if(text.isEmpty()) + { + text = this->objectName(); + } + text = QInputDialog::getText(this, _aChangeTitle->text(), tr("Title :"), QLineEdit::Normal, text, &ok); + if(ok) + { + this->setTitle(text); + } + } + else if(action == _aChangeXLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeXLabel->text(), tr("X axis label :"), QLineEdit::Normal, _xLabel->text(), &ok); + if(ok) + { + this->setXLabel(text); + } + } + else if(action == _aChangeYLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeYLabel->text(), tr("Y axis label :"), QLineEdit::Normal, _yLabel->text(), &ok); + if(ok) + { + this->setYLabel(text, _yLabel->orientation()); + } + } + else if(action == _aYLabelVertical) + { + this->setYLabel(_yLabel->text(), _aYLabelVertical->isChecked()?Qt::Vertical:Qt::Horizontal); + } + else if(action == _aChangeBackgroundColor) + { + QColor color = QColorDialog::getColor(_bgColor, this); + if(color.isValid()) + { + this->setBackgroundColor(color); + } + } + else if(action == _aSaveFigure) + { + + QString text; +#ifdef QT_SVG_LIB + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf *.svg"); +#else + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf"); +#endif + if(!text.isEmpty()) + { + bool flatModified = false; + if(!_legend->isFlat()) + { + _legend->setFlat(true); + flatModified = true; + } + + QPalette p(palette()); + // Set background color to white + QColor c = p.color(QPalette::Background); + p.setColor(QPalette::Background, Qt::white); + setPalette(p); + +#ifdef QT_SVG_LIB + if(QFileInfo(text).suffix().compare("svg") == 0) + { + QSvgGenerator generator; + generator.setFileName(text); + generator.setSize(this->size()); + QPainter painter; + painter.begin(&generator); + this->render(&painter); + painter.end(); + } + else + { +#endif + if(QFileInfo(text).suffix().compare("pdf") == 0) + { + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(text); + this->render(&printer); + } + else + { + QPixmap figure = QPixmap::grabWidget(this); + figure.save(text); + } +#ifdef QT_SVG_LIB + } +#endif + // revert background color + p.setColor(QPalette::Background, c); + setPalette(p); + + if(flatModified) + { + _legend->setFlat(false); + } + } + } + else if(action == _aAutoScreenCapture) + { + if(_aAutoScreenCapture->isChecked()) + { + this->selectScreenCaptureFormat(); + } + } + else if(action == _aClearData) + { + this->clearData(); + } + else + { + ULOGGER_WARN("Unknown action"); + } +} + +void UPlot::setWorkingDirectory(const QString & workingDirectory) +{ + if(QDir(_workingDirectory).exists()) + { + _workingDirectory = workingDirectory; + } + else + { + ULOGGER_ERROR("The directory \"%s\" doesn't exist", workingDirectory.toStdString().c_str()); + } +} + +void UPlot::captureScreen() +{ + if(!_aAutoScreenCapture->isChecked()) + { + return; + } + QString targetDir = _workingDirectory + "/ScreensCaptured"; + QDir dir; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + targetDir += this->title().replace(" ", "_"); + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + QString name = (QDateTime::currentDateTime().toString("yyMMddhhmmsszzz") + ".") + _autoScreenCaptureFormat; + QPixmap figure = QPixmap::grabWidget(this); + figure.save(targetDir + name); +} + +void UPlot::selectScreenCaptureFormat() +{ + QStringList items; + items << QString("png") << QString("jpg"); + bool ok; + QString item = QInputDialog::getItem(this, tr("Select format"), tr("Format:"), items, 0, false, &ok); + if(ok && !item.isEmpty()) + { + _autoScreenCaptureFormat = item; + } + this->captureScreen(); +} + +void UPlot::clearData() +{ + for(int i=0; i<_curves.size(); ++i) + { + // Don't clear threshold curves + if(qobject_cast(_curves.at(i)) == 0) + { + _curves.at(i)->clear(); + } + } + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +// for convenience... +UPlotCurveThreshold * UPlot::addThreshold(const QString & name, float value, Qt::Orientation orientation) +{ + UPlotCurveThreshold * curve = new UPlotCurveThreshold(name, value, orientation, this); + QPen pen = curve->pen(); + pen.setStyle((Qt::PenStyle)(_penStyleCount++ % 4 + 2)); + curve->setPen(pen); + if(!this->addCurve(curve)) + { + if(curve) + { + delete curve; + } + } + else + { + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } + return curve; +} + +void UPlot::setTitle(const QString & text) +{ + _title->setText(text); + _title->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setXLabel(const QString & text) +{ + _xLabel->setText(text); + _xLabel->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setYLabel(const QString & text, Qt::Orientation orientation) +{ + _yLabel->setText(text); + _yLabel->setOrientation(orientation); + _yLabel->setVisible(!text.isEmpty()); + _aYLabelVertical->setChecked(orientation==Qt::Vertical); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setBackgroundColor(const QColor & color) +{ + if(color.isValid()) + { + _bgColor = color; + _view->scene()->setBackgroundBrush(QBrush(_bgColor)); + for(QList::iterator iter=_curves.begin(); iter!=_curves.end(); ++iter) + { + (*iter)->setItemsColor(QColor(255-_bgColor.red(), 255-_bgColor.green(), 255-_bgColor.blue(), _bgColor.alpha())); + } + } +} + +void UPlot::addItem(QGraphicsItem * item) +{ + item->setParentItem(_sceneRoot); + item->setZValue(1.0f); +} + +void UPlot::showLegend(bool shown) +{ + _legend->setVisible(shown); + _aShowLegend->setChecked(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::showGrid(bool shown) +{ + _aShowGrid->setChecked(shown); + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +void UPlot::showRefreshRate(bool shown) +{ + _aShowRefreshRate->setChecked(shown); + _refreshRate->setVisible(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::trackMouse(bool tracking) +{ + _aMouseTracking->setChecked(tracking); + this->setMouseTracking(tracking); +} + +void UPlot::setGraphicsView(bool on) +{ + _aGraphicsView->setChecked(on); + _view->setVisible(on); + _aGraphicsView->isChecked()?this->replot(0):this->update(); + _aMouseTracking->setEnabled(!on); +} + +void UPlot::keepAllData(bool kept) +{ + _aKeepAllData->setChecked(kept); +} + +void UPlot::setMaxVisibleItems(int maxVisibleItems) +{ + if(maxVisibleItems <= 0) + { + _aLimit0->setChecked(true); + } + else if(maxVisibleItems == 10) + { + _aLimit10->setChecked(true); + } + else if(maxVisibleItems == 50) + { + _aLimit50->setChecked(true); + } + else if(maxVisibleItems == 100) + { + _aLimit100->setChecked(true); + } + else if(maxVisibleItems == 500) + { + _aLimit500->setChecked(true); + } + else if(maxVisibleItems == 1000) + { + _aLimit1000->setChecked(true); + } + else + { + _aLimitCustom->setVisible(true); + _aLimitCustom->setChecked(true); + _aLimitCustom->setText(QString::number(maxVisibleItems)); + } + _maxVisibleItems = maxVisibleItems; + updateAxis(); +} + +QRectF UPlot::sceneRect() const +{ + return _view->sceneRect(); +} + +void UPlot::removeCurves() +{ + QList tmp = _curves; + for(QList::iterator iter=tmp.begin(); iter!=tmp.end(); ++iter) + { + this->removeCurve(*iter); + } + _curves.clear(); +} + +void UPlot::removeCurve(const UPlotCurve * curve) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); +#if PRINT_DEBUG + ULOGGER_DEBUG("Plot=\"%s\" removing curve=\"%s\"", this->objectName().toStdString().c_str(), curve?curve->name().toStdString().c_str():""); +#endif + if(iter!=_curves.end()) + { + UPlotCurve * c = *iter; + c->detach(this); + _curves.erase(iter); + _legend->remove(c); + if(!qobject_cast(c)) + { + // transfer update connection to next curve + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + connect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + if(c->parent() == this) + { + delete c; + } + // Update axis + updateAxis(); + } +} + +void UPlot::showCurve(const UPlotCurve * curve, bool shown) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); + if(iter!=_curves.end()) + { + UPlotCurve * value = *iter; + if(value->isVisible() != shown) + { + value->setVisible(shown); + this->updateAxis(); + } + } +} + +void UPlot::moveCurve(const UPlotCurve * curve, int index) +{ + // this will change the print order + int currentIndex = -1; + UPlotCurve * c = 0; + for(int i=0; i<_curves.size(); ++i) + { + if(_curves.at(i) == curve) + { + c = _curves.at(i); + currentIndex = i; + break; + } + } + + if(c && currentIndex != index) + { + _curves.removeAt(currentIndex); + QList children = _sceneRoot->childItems(); + _curves.insert(index, c); + if(currentIndex > index) + { + children[currentIndex]->stackBefore(children[index]); + } + else + { + if(currentIndexstackBefore(children[currentIndex]); + } + else + { + children[currentIndex]->stackBefore(children[index]); + } + } + if(currentIndex == children.size()-2 && currentIndex < index) + { + children[index]->stackBefore(children[currentIndex]); + } + } + this->update(); + } +} diff --git a/utilite/src/UProcessInfo.cpp b/utilite/src/UProcessInfo.cpp new file mode 100644 index 00000000..51f34034 --- /dev/null +++ b/utilite/src/UProcessInfo.cpp @@ -0,0 +1,78 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UProcessInfo.h" + +#ifdef WIN32 +#include "Windows.h" +#include "Psapi.h" +#elif __APPLE__ +#include +#else +#include +#include +#include "rtabmap/utilite/UStl.h" +#endif + +UProcessInfo::UProcessInfo() {} + +UProcessInfo::~UProcessInfo() {} + +// return in bytes +long int UProcessInfo::getMemoryUsage() +{ + long int memoryUsage = -1; + +#ifdef WIN32 + HANDLE hProc = GetCurrentProcess(); + PROCESS_MEMORY_COUNTERS info; + BOOL okay = GetProcessMemoryInfo(hProc, &info, sizeof(info)); + if(okay) + { + memoryUsage = info.WorkingSetSize; + } +#elif __APPLE__ + rusage u; + if(getrusage(RUSAGE_SELF, &u) == 0) + { + memoryUsage = u.ru_maxrss; + } +#else + std::fstream file("/proc/self/status", std::fstream::in); + if(file.is_open()) + { + std::string bytes; + while(std::getline(file, bytes)) + { + if(bytes.find("VmRSS") != bytes.npos) + { + std::list strs = uSplit(bytes, ' '); + if(strs.size()>1) + { + memoryUsage = atol(uValueAt(strs,1).c_str()) * 1024; + } + break; + } + } + file.close(); + } +#endif + + return memoryUsage; +} diff --git a/utilite/src/UThread.cpp b/utilite/src/UThread.cpp new file mode 100644 index 00000000..bc6845c0 --- /dev/null +++ b/utilite/src/UThread.cpp @@ -0,0 +1,301 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UThread.h" +#include "rtabmap/utilite/ULogger.h" +#ifdef __APPLE__ +#include +#include +#endif + +#define PRINT_DEBUG 0 + +//////////////////////////// +// public: +//////////////////////////// + +UThread::UThread(Priority priority) : + state_(kSIdle), + priority_(priority), + handle_(0), + threadId_(0), + cpuAffinity_(-1) +{} + +UThread::~UThread() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +void UThread::kill() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif + killSafelyMutex_.lock(); + { + if(this->isRunning()) + { + // Thread is creating + while(state_ == kSCreating) + { + uSleep(1); + } + + if(state_ == kSRunning) + { + state_ = kSKilled; + + // Call function to do something before wait + mainLoopKill(); + } + else + { + UERROR("thread (%d) is supposed to be running...", threadId_); + } + } + else + { +#if PRINT_DEBUG + UDEBUG("thread (%d) is not running...", threadId_); +#endif + } + } + killSafelyMutex_.unlock(); +} + +void UThread::join(bool killFirst) +{ + //make sure the thread is created + while(this->isCreating()) + { + uSleep(1); + } + +#if WIN32 +#if PRINT_DEBUG + UDEBUG("Thread %d joining %d", UThreadC::Self(), threadId_); +#endif + if(UThreadC::Self() == threadId_) +#else +#if PRINT_DEBUG + UDEBUG("Thread %d joining %d", UThreadC::Self(), handle_); +#endif + if(UThreadC::Self() == handle_) +#endif + { + UERROR("Thread cannot join itself"); + return; + } + + if(killFirst) + { + this->kill(); + } + + runningMutex_.lock(); + runningMutex_.unlock(); + +#if PRINT_DEBUG + UDEBUG("Join ended for %d", UThreadC::Self()); +#endif +} + +void UThread::start() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif + + if(state_ == kSIdle || state_ == kSKilled) + { + if(state_ == kSKilled) + { + // make sure it is finished + runningMutex_.lock(); + runningMutex_.unlock(); + } + + state_ = kSCreating; + int r = UThreadC::Create(threadId_, &handle_, true); // Create detached + if(r) + { + UERROR("Failed to create a thread! errno=%d", r); + threadId_=0; + handle_=0; + state_ = kSIdle; + } + else + { +#if PRINT_DEBUG + ULOGGER_DEBUG("StateThread::startThread() thread id=%d _handle=%d", threadId_, handle_); +#endif + } + } +} + +//TODO : Support pThread +void UThread::setPriority(Priority priority) +{ + priority_ = priority; +} + +//TODO : Support pThread +void UThread::applyPriority() +{ + if(handle_) + { +#ifdef WIN32 + int p = THREAD_PRIORITY_NORMAL; + switch(priority_) + { + case kPLow: + p = THREAD_PRIORITY_LOWEST; + break; + + case kPBelowNormal: + p = THREAD_PRIORITY_BELOW_NORMAL; + break; + + case kPNormal: + p = THREAD_PRIORITY_NORMAL; + break; + + case kPAboveNormal: + p = THREAD_PRIORITY_ABOVE_NORMAL; + break; + + case kPRealTime: + p = THREAD_PRIORITY_TIME_CRITICAL; + break; + + default: + break; + } + SetThreadPriority(handle_, p); +#endif + } +} + +void UThread::setAffinity(int cpu) +{ + cpuAffinity_ = cpu; + if(cpuAffinity_<0) + { + cpuAffinity_ = 0; + } +} + +//TODO : Support Windows and linux +void UThread::applyAffinity() +{ + if(cpuAffinity_>0) + { +#ifdef WIN32 +#elif __APPLE__ + thread_affinity_policy_data_t affPolicy; + affPolicy.affinity_tag = cpuAffinity_; + kern_return_t ret = thread_policy_set( + mach_thread_self(), + THREAD_AFFINITY_POLICY, + (integer_t*) &affPolicy, + THREAD_AFFINITY_POLICY_COUNT); + if(ret != KERN_SUCCESS) + { + UERROR("thread_policy_set returned %d", ret); + } +#else + /*unsigned long mask = cpuAffinity_; + + if (pthread_setaffinity_np( + pthread_self(), + sizeof(mask), + &mask) <0) + { + UERROR("pthread_setaffinity_np failed"); + } + }*/ +#endif + } +} + +bool UThread::isCreating() const +{ + return state_ == kSCreating; +} + +bool UThread::isRunning() const +{ + return state_ == kSRunning || state_ == kSCreating; +} + +bool UThread::isIdle() const +{ + return state_ == kSIdle; +} + +bool UThread::isKilled() const +{ + return state_ == kSKilled; +} + +//////////////////////////// +// private: +//////////////////////////// + +void UThread::ThreadMain() +{ + runningMutex_.lock(); + applyPriority(); + applyAffinity(); + +#if PRINT_DEBUG + ULOGGER_DEBUG("before mainLoopBegin()"); +#endif + + state_ = kSRunning; + mainLoopBegin(); + +#if PRINT_DEBUG + ULOGGER_DEBUG("before mainLoop()"); +#endif + + while(state_ == kSRunning) + { + mainLoop(); + } + +#if PRINT_DEBUG + ULOGGER_DEBUG("before mainLoopEnd()"); +#endif + + mainLoopEnd(); + + handle_ = 0; + threadId_ = 0; + state_ = kSIdle; + + runningMutex_.unlock(); +#if PRINT_DEBUG + ULOGGER_DEBUG("Exiting thread loop"); +#endif +} + diff --git a/utilite/src/UTimer.cpp b/utilite/src/UTimer.cpp new file mode 100644 index 00000000..5b2c1f23 --- /dev/null +++ b/utilite/src/UTimer.cpp @@ -0,0 +1,115 @@ +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "rtabmap/utilite/UTimer.h" +#include "rtabmap/utilite/ULogger.h" + +/////////////////////// +// public: +/////////////////////// +UTimer::UTimer() +{ +#ifdef WIN32 + QueryPerformanceFrequency(&frequency_); +#endif + start(); // This will initialize the private counters +} + +UTimer::~UTimer() {} + +#ifdef WIN32 +double UTimer::now() +{ + LARGE_INTEGER count, freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return double(count.QuadPart) / freq.QuadPart; +} + +void UTimer::start() +{ + QueryPerformanceCounter(&startTimeRecorded_); + stopTimeRecorded_ = startTimeRecorded_; +} +void UTimer::stop() +{ + QueryPerformanceCounter(&stopTimeRecorded_); + +} +double UTimer::getElapsedTime() +{ + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return double(now.QuadPart - startTimeRecorded_.QuadPart) / frequency_.QuadPart; +} +double UTimer::getInterval() +{ + if(stopTimeRecorded_.QuadPart == startTimeRecorded_.QuadPart) + { + return getElapsedTime(); + } + else + { + return double(stopTimeRecorded_.QuadPart - startTimeRecorded_.QuadPart) / frequency_.QuadPart; + } +} +#else +double UTimer::now() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return double(tv.tv_sec) + double(tv.tv_usec) / 1000000.0; +} + +void UTimer::start() +{ + gettimeofday(&startTimeRecorded_, NULL); + stopTimeRecorded_ = startTimeRecorded_; +} +void UTimer::stop() +{ + gettimeofday(&stopTimeRecorded_, NULL); + +} +double UTimer::getElapsedTime() +{ + return UTimer::now() - (double(startTimeRecorded_.tv_sec) + double(startTimeRecorded_.tv_usec) / 1000000.0); + +} +double UTimer::getInterval() +{ + if(startTimeRecorded_.tv_sec == stopTimeRecorded_.tv_sec && startTimeRecorded_.tv_usec == stopTimeRecorded_.tv_usec) + { + return getElapsedTime(); + } + else + { + double start = double(startTimeRecorded_.tv_sec) + double(startTimeRecorded_.tv_usec) / 1000000.0; + double stop = double(stopTimeRecorded_.tv_sec) + double(stopTimeRecorded_.tv_usec) / 1000000.0; + return stop - start; + } +} +#endif + +double UTimer::ticks() // Stop->start and return Interval +{ + double inter = elapsed(); + start(); + return inter; +}