File indexing completed on 2024-04-28 03:51:21

0001 /*.
0002     SPDX-FileCopyrightText: 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
0003     SPDX-FileCopyrightText: 2014 Inge Wallin <inge@lysator.liu.se>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "toolgraphics.h"
0009 
0010 #include "ui_configure_graph.h"
0011 #include "ui_configure_meter.h"
0012 #include "ui_configure_controller.h"
0013 
0014 #include <stepcore/tool.h>
0015 #include <stepcore/particle.h>
0016 #include <stepcore/rigidbody.h>
0017 #include <stepcore/solver.h>
0018 #include <stepcore/collisionsolver.h>
0019 
0020 #include "worldmodel.h"
0021 #include "worldscene.h"
0022 #include "worldfactory.h"
0023 #include "latexformula.h"
0024 
0025 #include <QAbstractButton>
0026 #include <QAbstractTextDocumentLayout>
0027 #include <QAction>
0028 #include <QApplication>
0029 #include <QColorDialog>
0030 #include <QDialog>
0031 #include <QDialogButtonBox>
0032 #include <QEvent>
0033 #include <QFileDialog>
0034 #include <QFocusEvent>
0035 #include <QGraphicsScene>
0036 #include <QGraphicsSceneMouseEvent>
0037 #include <QGraphicsView>
0038 #include <QGridLayout>
0039 #include <QInputDialog>
0040 #include <QItemSelectionModel>
0041 #include <QLCDNumber>
0042 #include <QLabel>
0043 #include <QPainter>
0044 #include <QPushButton>
0045 #include <QStyleOptionGraphicsItem>
0046 #include <QTemporaryFile>
0047 #include <QTextDocument>
0048 #include <QUrl>
0049 #include <QVBoxLayout>
0050 
0051 #include <KFontAction>
0052 #include <KFontSizeAction>
0053 #include <KIO/CopyJob>
0054 #include <KIO/Job>
0055 #include <KIO/FileCopyJob>
0056 #include <KJobWidgets>
0057 #include <KLocalizedString>
0058 #include <KMessageBox>
0059 #include <KPlotAxis>
0060 #include <KPlotObject>
0061 #include <KPlotPoint>
0062 #include <KPlotWidget>
0063 #include <KToggleAction>
0064 #include <KToolBar>
0065 
0066 #include <float.h>
0067 
0068 
0069 StepCore::Vector2d WidgetVertexHandlerGraphicsItem::value()
0070 {
0071     double s = currentViewScale();
0072     StepCore::Vector2d size = _item->metaObject()->property(QStringLiteral("size"))->
0073                             readVariant(_item).value<StepCore::Vector2d>()/s;
0074     return (size.array()* corners[_vertexNum].array()).matrix();
0075 }
0076 
0077 void WidgetVertexHandlerGraphicsItem::setValue(const StepCore::Vector2d& value)
0078 {
0079     double s = currentViewScale();
0080 
0081     QGraphicsView* activeView = scene()->views().first();
0082     QTransform viewportTransform = activeView->viewportTransform();
0083 
0084     StepCore::Vector2d size = _item->metaObject()->property(QStringLiteral("size"))->
0085                         readVariant(_item).value<StepCore::Vector2d>()/s;
0086     StepCore::Vector2d position = _item->metaObject()->property(QStringLiteral("position"))->
0087                             readVariant(_item).value<StepCore::Vector2d>();
0088 
0089     StepCore::Vector2d oCorner = position - (size.array()*((corners[_vertexNum]).array())).matrix();
0090 
0091     oCorner = pointToVector( viewportTransform.inverted().map(
0092                 QPointF(viewportTransform.map(vectorToPoint(oCorner)).toPoint()) ));
0093 
0094     StepCore::Vector2d delta = (value + position - oCorner)/2.0;
0095     StepCore::Vector2d newPos = oCorner + delta;
0096     newPos = pointToVector( viewportTransform.inverted().map(
0097                 QPointF(viewportTransform.map(vectorToPoint(newPos)).toPoint()) ));
0098     StepCore::Vector2d newSize = (newPos - oCorner)*2.0;
0099 
0100     StepCore::Vector2d sign = (delta.array()*(corners[_vertexNum].array())).matrix();
0101     double d = -0.1/s;
0102     if(sign[0] < d || sign[1] < d) {
0103         if(sign[0] < d) {
0104             newPos[0] = oCorner[0]; newSize[0] = 0;
0105             _vertexNum ^= 1;
0106         }
0107         if(sign[1] < d) {
0108             newPos[1] = oCorner[1]; newSize[1] = 0;
0109             _vertexNum ^= 2;
0110         }
0111         _worldModel->setProperty(_item, QStringLiteral("position"), QVariant::fromValue(newPos));
0112         _worldModel->setProperty(_item, QStringLiteral("size"), QVariant::fromValue((newSize*s).eval()));
0113         setValue(value);
0114         return;
0115     }
0116 
0117     _worldModel->setProperty(_item, QStringLiteral("position"), QVariant::fromValue(newPos));
0118     _worldModel->setProperty(_item, QStringLiteral("size"), QVariant::fromValue((newSize*s).eval()));
0119 }
0120 
0121 WidgetGraphicsItem::WidgetGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
0122     : StepGraphicsItem(item, worldModel), _centralWidget(nullptr)
0123 {
0124     setFlag(QGraphicsItem::ItemIsSelectable);
0125     setFlag(QGraphicsItem::ItemIsMovable);
0126     setAcceptHoverEvents(true);
0127 
0128     _backgroundBrush = Qt::NoBrush;
0129 
0130     _boundingRect = QRectF(0, 0, 0, 0);
0131 }
0132 
0133 WidgetGraphicsItem::~WidgetGraphicsItem()
0134 {
0135     if(_centralWidget) {
0136         _centralWidget->hide();
0137         _centralWidget->deleteLater();
0138     }
0139 }
0140 
0141 OnHoverHandlerGraphicsItem* WidgetGraphicsItem::createOnHoverHandler(const QPointF& pos)
0142 {
0143     double s = currentViewScale();
0144     StepCore::Vector2d size = _item->metaObject()->property(QStringLiteral("size"))->
0145                             readVariant(_item).value<StepCore::Vector2d>()/s;
0146     StepCore::Vector2d position = _item->metaObject()->property(QStringLiteral("position"))->
0147                             readVariant(_item).value<StepCore::Vector2d>();
0148     StepCore::Vector2d l = pointToVector(pos) - position;
0149 
0150     int num = -1; double minDist2 = HANDLER_SNAP_SIZE*HANDLER_SNAP_SIZE/s/s;
0151     for(unsigned int i=0; i<4; ++i) {
0152         double dist2 = (l - (size.array()*(WidgetVertexHandlerGraphicsItem::corners[i]).array()).matrix()).squaredNorm();
0153         if(dist2 < minDist2) { num = i; minDist2 = dist2; }
0154     }
0155 
0156     if(_onHoverHandler && _onHoverHandler->vertexNum() == num)
0157         return _onHoverHandler;
0158 
0159     if(num >= 0)
0160         return new WidgetVertexHandlerGraphicsItem(_item, _worldModel, this, num);
0161 
0162     return nullptr;
0163 }
0164 
0165 // XXX: ???
0166 void WidgetGraphicsItem::mouseSetPos(const QPointF& pos, const QPointF&, MovingState)
0167 {
0168     QGraphicsView* activeView = scene()->views().first();
0169     QTransform itemTransform = activeView->transform() * deviceTransform(activeView->viewportTransform());
0170     StepCore::Vector2d newPos = pointToVector( itemTransform.inverted().map(
0171                 QPointF(itemTransform.map(pos/*/50.0*/).toPoint()) ))/**50.0*/;
0172     _worldModel->setProperty(_item, QStringLiteral("position"), QVariant::fromValue(newPos));
0173 }
0174 
0175 void WidgetGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
0176 {
0177     painter->setRenderHint(QPainter::Antialiasing, true);
0178     if(_isSelected) painter->setPen(QPen(SELECTION_COLOR, 0, Qt::DashLine));
0179     else painter->setPen(QPen(Qt::NoPen));
0180     painter->setBrush(_backgroundBrush);
0181     painter->drawRect(_boundingRect);
0182 }
0183 
0184 void WidgetGraphicsItem::setCenteralWidget(QWidget* widget)
0185 {
0186     if(_centralWidget) {
0187         _centralWidget->hide();
0188         _centralWidget->deleteLater();
0189     }
0190     _centralWidget = widget;
0191     viewScaleChanged();
0192 }
0193 
0194 void WidgetGraphicsItem::viewScaleChanged()
0195 {
0196     if(!scene() || scene()->views().isEmpty()) return;
0197     QGraphicsView* activeView = scene()->views().first();
0198 
0199     QPointF position = vectorToPoint(_item->metaObject()->property(QStringLiteral("position"))->
0200                     readVariant(_item).value<StepCore::Vector2d>());
0201     
0202     // Move item to the closest pixel position
0203     QPoint viewPosition = activeView->mapFromScene(position);
0204     setPos(activeView->mapToScene(viewPosition));
0205 
0206     StepCore::Vector2d size = _item->metaObject()->property(QStringLiteral("size"))->
0207                     readVariant(_item).value<StepCore::Vector2d>();
0208 
0209     QSize viewSize(qRound(size[0]), qRound(size[1]));
0210     QPoint viewTopLeft =
0211         viewPosition - QPoint(viewSize.width() / 2, viewSize.height() / 2);
0212     QRect viewRect(viewTopLeft, viewSize);
0213     
0214     QRectF sceneRect =
0215         activeView->mapToScene(viewRect.adjusted(0, 0, 1, 1)).boundingRect();
0216     QRectF boundingRect = mapRectFromScene(sceneRect);
0217     double s = currentViewScale();
0218     boundingRect.adjust(-SELECTION_MARGIN/s, -SELECTION_MARGIN/s,
0219                         SELECTION_MARGIN/s, SELECTION_MARGIN/s);
0220     
0221     if(boundingRect != _boundingRect) {
0222         prepareGeometryChange();
0223         _boundingRect = boundingRect;
0224         update();
0225     }
0226     
0227     // Reparent the widget if necessary.
0228     if(_centralWidget->parentWidget() != activeView->viewport()) {
0229        _centralWidget->setParent(activeView->viewport());
0230        _centralWidget->show();
0231     }
0232 
0233     _centralWidget->setGeometry(viewRect.adjusted(0, 0, 1, 1));
0234 }
0235 
0236 void WidgetGraphicsItem::stateChanged()
0237 {
0238     update();
0239 }
0240 
0241 void WidgetGraphicsItem::worldDataChanged(bool dynamicOnly)
0242 {
0243     if(!dynamicOnly) {
0244         /*QPointF position = vectorToPoint(_item->metaObject()->property("position")->
0245                     readVariant(_item).value<StepCore::Vector2d>());
0246         setPos(position);*/
0247         viewScaleChanged();
0248         update();
0249     }
0250 }
0251 
0252 /////////////////////////////////////////////////////////////////////////////////////////
0253 
0254 QString NoteTextEdit::emptyNotice() const
0255 {
0256     return i18n("Click to enter text");
0257 }
0258 
0259 /*
0260 void NoteTextEdit::focusInEvent(QFocusEvent *event)
0261 {
0262     if(_noteItem->note()->text().isEmpty()) {
0263         ++_noteItem->_updating;
0264         setPlainText("");
0265         _noteItem->worldDataChanged(false);
0266         --_noteItem->_updating;
0267     }
0268     _noteItem->_hasFocus = true;
0269     _noteItem->_toolBar->show();
0270     _noteItem->setSelected(true);
0271     _noteItem->viewScaleChanged();
0272     KTextEdit::focusInEvent(event);
0273 }
0274 
0275 void NoteTextEdit::focusOutEvent(QFocusEvent *event)
0276 {
0277     qDebug() << event->reason() << endl;
0278     qDebug() << QApplication::focusWidget()->metaObject()->className() << endl;
0279 
0280     QObject* f = QApplication::focusWidget();
0281     if(f == this) f = NULL;
0282     while(f && f != _noteItem->_widget) f = f->parent();
0283 
0284     if(!f && event->reason() != Qt::PopupFocusReason) {
0285         if(_noteItem->note()->text().isEmpty()) {
0286             ++_noteItem->_updating;
0287             setPlainText(emptyNotice());
0288             _noteItem->worldDataChanged(false);
0289             --_noteItem->_updating;
0290         }
0291         _noteItem->_hasFocus = false;
0292         _noteItem->_toolBar->hide();
0293         _noteItem->viewScaleChanged();
0294     }
0295     KTextEdit::focusOutEvent(event);
0296 }
0297 */
0298 
0299 StepCore::NoteFormula* NoteTextEdit::formulaAt(const QPoint& pos)
0300 {
0301     int p = document()->documentLayout()->hitTest(pos, Qt::ExactHit);
0302     if(p < 0) return nullptr;
0303 
0304     QTextCursor cursor(document());
0305     cursor.setPosition(p);
0306     if(cursor.atEnd()) return nullptr;
0307     cursor.setPosition(p+1);
0308 
0309     QTextFormat format = cursor.charFormat();
0310     if(!format.isImageFormat()) return nullptr;
0311     QString image = format.toImageFormat().name();
0312 
0313     StepCore::Item* item = _noteItem->note()->childItem(image);
0314     if(!item) {
0315         foreach(StepCore::Item* it, _noteItem->_newItems)
0316             if(it->name() == image) { item = it; break; }
0317     }
0318 
0319     if(!item || !item->metaObject()->inherits<StepCore::NoteFormula>())
0320         return nullptr;
0321 
0322     return static_cast<StepCore::NoteFormula*>(item);
0323 }
0324 
0325 void NoteTextEdit::mousePressEvent(QMouseEvent *e)
0326 {
0327     if(e->button() == Qt::LeftButton) _mousePressPoint = e->pos();
0328     KTextEdit::mousePressEvent(e);
0329 }
0330 
0331 void NoteTextEdit::mouseMoveEvent(QMouseEvent *e)
0332 {
0333     if(formulaAt(e->pos()) != nullptr) {
0334         viewport()->setCursor(Qt::PointingHandCursor);
0335     } else {
0336         viewport()->setCursor(Qt::IBeamCursor);
0337     }
0338     _mousePressPoint.setX(-1);
0339     KTextEdit::mouseMoveEvent(e);
0340 }
0341 
0342 void NoteTextEdit::mouseReleaseEvent(QMouseEvent *e)
0343 {
0344     if(e->button() == Qt::LeftButton && e->pos() == _mousePressPoint) {
0345         StepCore::NoteFormula* formula = formulaAt(e->pos());
0346         if(formula) {
0347             e->accept();
0348             _noteItem->editFormula(formula);
0349             setDocument(document());
0350             return;
0351         }
0352     }
0353     KTextEdit::mouseReleaseEvent(e);
0354 }
0355 
0356 NoteGraphicsItem::NoteGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
0357     : WidgetGraphicsItem(item, worldModel)
0358 {
0359     Q_ASSERT(dynamic_cast<StepCore::Note*>(_item) != nullptr);
0360     setFlag(QGraphicsItem::ItemIsSelectable);
0361     setFlag(QGraphicsItem::ItemIsMovable);
0362     setAcceptHoverEvents(true);
0363 
0364     _widget = new QWidget();
0365     _widget->setPalette(QPalette(Qt::lightGray));
0366 
0367     _textEdit = new NoteTextEdit(this, _widget);
0368     _textEdit->setFrameShape(QFrame::NoFrame);
0369     QPalette p = _textEdit->palette();
0370     p.setColor(QPalette::Base, Qt::transparent);
0371     _textEdit->setPalette(p);
0372     //_textEdit->setStyleSheet(".NoteTextEdit {background-color: rgba(0,0,0,0%);}");
0373 
0374     _toolBar = new KToolBar(_widget);
0375     _toolBar->setIconDimensions(16);
0376     _toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0377     _toolBar->setContentsMargins(0,0,0,0);
0378     if(_toolBar->layout()) _toolBar->layout()->setSpacing(0);
0379     //_toolBar->setStyleSheet(".KToolBar {margin: 0px; border-width: 0px; padding: 0px; }");
0380 
0381     _actionColor = new QAction(QIcon(), i18n("&Color"), _toolBar);
0382 
0383     _actionBold = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-bold")), i18n("&Bold"), _toolBar);
0384     _actionBold->setShortcut(Qt::CTRL | Qt::Key_B);
0385     _actionItalic = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-italic")), i18n("&Italic"), _toolBar);
0386     _actionItalic->setShortcut(Qt::CTRL | Qt::Key_I);
0387     _actionUnderline = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-underline")), i18n("&Underline"), _toolBar);
0388     _actionUnderline->setShortcut(Qt::CTRL | Qt::Key_U);
0389 
0390     _actionAlignLeft = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-left")), i18n("Align &Left"), _toolBar);
0391     _actionAlignLeft->setShortcut(Qt::CTRL | Qt::Key_L);
0392     _actionAlignCenter = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-center")), i18n("Align C&enter"), _toolBar);
0393     _actionAlignCenter->setShortcut(Qt::CTRL | Qt::Key_E);
0394     _actionAlignRight = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-right")), i18n("Align &Right"), _toolBar);
0395     _actionAlignRight->setShortcut(Qt::CTRL | Qt::Key_R);
0396     _actionAlignJustify = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-fill")), i18n("Align &Justify"), _toolBar);
0397     _actionAlignJustify->setShortcut(Qt::CTRL | Qt::Key_J);
0398 
0399     _actionAlign = new KSelectAction(i18n("&Align"), _toolBar);
0400     _actionAlign->setToolBarMode(KSelectAction::MenuMode);
0401     _actionAlign->setToolButtonPopupMode(QToolButton::InstantPopup);
0402     _actionAlign->addAction(_actionAlignLeft);
0403     _actionAlign->addAction(_actionAlignCenter);
0404     _actionAlign->addAction(_actionAlignRight);
0405     _actionAlign->addAction(_actionAlignJustify);
0406     _actionAlignLeft->setChecked(true);
0407 
0408     _actionFont = new KFontAction(i18n("&Font"), _toolBar);
0409     _actionFontSize = new KFontSizeAction(i18n("Font &Size"), _toolBar);
0410 
0411     _actionInsertImage = new QAction(QIcon::fromTheme(QStringLiteral("insert-image")), i18n("Insert &Image..."), _toolBar);
0412 #ifdef __GNUC__
0413 #warning Select right icon here
0414 #endif
0415     _actionInsertFormula = new QAction(QIcon::fromTheme(QStringLiteral("application-vnd.oasis.opendocument.formula")),
0416                                     i18n("Insert &Formula..."), _toolBar);
0417 
0418     connect(_actionColor, &QAction::triggered, this, &NoteGraphicsItem::formatColor);
0419     connect(_actionBold, &QAction::triggered, this, &NoteGraphicsItem::formatBold);
0420     connect(_actionItalic, &QAction::triggered, _textEdit, &QTextEdit::setFontItalic);
0421     connect(_actionUnderline, &QAction::triggered, _textEdit, &QTextEdit::setFontUnderline);
0422     connect(_actionAlign, SIGNAL(triggered(QAction*)), this, SLOT(formatAlign(QAction*)));
0423     connect(_actionFont, SIGNAL(triggered(QString)), this, SLOT(formatFontFamily(QString)));
0424     connect(_actionFontSize, &KFontSizeAction::fontSizeChanged, this, &NoteGraphicsItem::formatFontSize);
0425     connect(_actionInsertImage, &QAction::triggered, this, &NoteGraphicsItem::insertImage);
0426     connect(_actionInsertFormula, &QAction::triggered, this, &NoteGraphicsItem::insertFormula);
0427     
0428     connect(_textEdit, &QTextEdit::currentCharFormatChanged,
0429                             this, &NoteGraphicsItem::currentCharFormatChanged);
0430     connect(_textEdit, &QTextEdit::cursorPositionChanged, this, &NoteGraphicsItem::cursorPositionChanged);
0431 
0432 
0433     connect(_toolBar, SIGNAL(actionTriggered(QAction*)), _textEdit, SLOT(setFocus()));
0434 
0435     _toolBar->addAction(_actionAlign);
0436     _toolBar->addAction(_actionBold);
0437     _toolBar->addAction(_actionItalic);
0438     _toolBar->addAction(_actionUnderline);
0439     _toolBar->addAction(_actionColor);
0440     //_toolBar->addSeparator();
0441 
0442     //_toolBar->addSeparator();
0443 
0444     _toolBar->addAction(_actionInsertImage);
0445     _toolBar->addAction(_actionInsertFormula);
0446 
0447     _toolBar->addAction(_actionFontSize);
0448     _toolBar->addAction(_actionFont);
0449 
0450     QVBoxLayout* layout = new QVBoxLayout(_widget);
0451     layout->setContentsMargins(0,0,0,0);
0452     layout->setSpacing(0);
0453     layout->addWidget(_textEdit);
0454     layout->addWidget(_toolBar);
0455 
0456     // without it focus is passed to QGrahicsView
0457     _toolBar->setFocusPolicy(Qt::ClickFocus);
0458     _toolBar->setFocusProxy(_textEdit);
0459     _widget->setFocusProxy(_textEdit);
0460 
0461     _hasFocus = false;
0462     _toolBar->hide();
0463 
0464     _textEdit->installEventFilter(this);
0465 
0466     QComboBox* font = qobject_cast<QComboBox*>(_toolBar->widgetForAction(_actionFont));
0467     if(font) {
0468         font->setMinimumContentsLength(5);
0469         font->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0470         font->installEventFilter(this);
0471         font->setToolTip(_actionFont->toolTip());
0472     }
0473 
0474     QComboBox* fontSize = qobject_cast<QComboBox*>(_toolBar->widgetForAction(_actionFontSize));
0475     if(fontSize) {
0476         fontSize->setMinimumContentsLength(2);
0477         fontSize->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0478         fontSize->installEventFilter(this);
0479         fontSize->setToolTip(_actionFontSize->toolTip());
0480     }
0481 
0482     setCenteralWidget(_widget);
0483     setOnHoverHandlerEnabled(true);
0484     _widget->setMouseTracking(true);
0485     _textEdit->setMouseTracking(true);
0486     _toolBar->setMouseTracking(true);
0487 }
0488 
0489 inline StepCore::Note* NoteGraphicsItem::note() const
0490 {
0491     return static_cast<StepCore::Note*>(_item);
0492 }
0493 
0494 bool NoteGraphicsItem::eventFilter(QObject* obj, QEvent* event)
0495 {
0496     if(event->type() == QEvent::FocusIn) {
0497         if(!_hasFocus) {
0498             _hasFocus = true;
0499             if(note()->text().isEmpty()) {
0500                 //++_updating;
0501                 _textEdit->setPlainText(QLatin1String(""));
0502                 worldDataChanged(false);
0503                 //--_updating;
0504             }
0505             _toolBar->show();
0506             bool multiSelect = (QApplication::keyboardModifiers() & Qt::ControlModifier) != 0;
0507             if(!multiSelect/* && !isSelected()*/) {
0508                 if(scene()) scene()->clearSelection();
0509                 _worldModel->selectionModel()->clearSelection();
0510             }
0511             setSelected(true);
0512             viewScaleChanged();
0513         }
0514     } else if(event->type() == QEvent::FocusOut &&
0515             static_cast<QFocusEvent*>(event)->reason() != Qt::PopupFocusReason) {
0516 
0517         QObject* f = QApplication::focusWidget();
0518         if(f == obj) f = nullptr;
0519         while(f && f != _widget) f = f->parent();
0520 
0521         if(!f) {
0522             _worldModel->simulationPause();
0523 
0524             QString newText = _textEdit->toHtml();
0525             if(newText != note()->text()) {
0526                 //++_updating;
0527                 _worldModel->beginMacro(i18n("Edit %1", _item->name()));
0528 
0529                 foreach(StepCore::Item* item, note()->items())
0530                     if(!newText.contains(item->name())) _worldModel->deleteItem(item);
0531 
0532                 foreach(StepCore::Item* item, _newItems)
0533                     if(!newText.contains(item->name())) _newItems.removeAll(item);
0534 
0535                 foreach(StepCore::Item* item, _newItems)
0536                     _worldModel->addItem(item, note());
0537 
0538                 _newItems.clear();
0539 
0540                 _worldModel->setProperty(_item, QStringLiteral("text"), newText);
0541 
0542                 _worldModel->endMacro();
0543                 //--_updating;
0544             }
0545 
0546             _hasFocus = false;
0547             _toolBar->hide();
0548             _textEdit->clear();
0549 
0550             viewScaleChanged();
0551             worldDataChanged(false);
0552         }
0553     }
0554     return QObject::eventFilter(obj, event);
0555 }
0556 
0557 void NoteGraphicsItem::formatColor()
0558 {
0559     QColor color = _textEdit->textColor();
0560     color = QColorDialog::getColor(color, _widget);
0561     if(QColorDialog::Accepted && color.isValid()) {
0562         _textEdit->setTextColor(color);
0563     }
0564 }
0565 
0566 void NoteGraphicsItem::formatBold(bool checked)
0567 {
0568     _textEdit->setFontWeight(checked ? QFont::Bold : QFont::Normal);
0569 }
0570 
0571 void NoteGraphicsItem::formatAlign(QAction* action)
0572 {
0573     if(action == _actionAlignLeft)
0574         _textEdit->setAlignment(Qt::AlignLeft);
0575     else if(action == _actionAlignCenter)
0576         _textEdit->setAlignment(Qt::AlignHCenter);
0577     else if(action == _actionAlignRight)
0578         _textEdit->setAlignment(Qt::AlignRight);
0579     else if(action == _actionAlignJustify)
0580         _textEdit->setAlignment(Qt::AlignJustify);
0581 
0582     _actionAlign->setIcon(action->icon());
0583 }
0584 
0585 void NoteGraphicsItem::formatFontFamily(const QString& family)
0586 {
0587     _textEdit->setFontFamily(family);
0588     _textEdit->setFocus();
0589 }
0590 
0591 void NoteGraphicsItem::formatFontSize(int size)
0592 {
0593     if(size > 0) _textEdit->setFontPointSize(size);
0594     else currentCharFormatChanged(_textEdit->currentCharFormat());
0595     _textEdit->setFocus();
0596 }
0597 
0598 void NoteGraphicsItem::currentCharFormatChanged(const QTextCharFormat& f)
0599 {
0600     _actionBold->setChecked(f.fontWeight() >= QFont::Bold);
0601     _actionItalic->setChecked(f.fontItalic());
0602     _actionUnderline->setChecked(f.fontUnderline());
0603 
0604     QPixmap pix(16,16);
0605     pix.fill(_textEdit->textColor());
0606     _actionColor->setIcon(pix);
0607 
0608     QFontInfo ff(f.font());
0609 #ifdef __GNUC__
0610 #warning Strange, the following line does nothing !
0611 #endif
0612     _actionFont->setFont(ff.family());
0613     _actionFontSize->setFontSize(ff.pointSize());
0614 }
0615 
0616 void NoteGraphicsItem::cursorPositionChanged()
0617 {
0618     if(_textEdit->alignment() & Qt::AlignLeft)
0619         _actionAlignLeft->setChecked(true);
0620     else if(_textEdit->alignment() & Qt::AlignHCenter)
0621         _actionAlignCenter->setChecked(true);
0622     else if(_textEdit->alignment() & Qt::AlignRight)
0623         _actionAlignRight->setChecked(true);
0624     else if(_textEdit->alignment() & Qt::AlignJustify)
0625         _actionAlignJustify->setChecked(true);
0626 
0627     _actionAlign->setIcon(_actionAlign->currentAction()->icon());
0628 }
0629 
0630 void NoteGraphicsItem::insertImage()
0631 {
0632     QUrl url = QFileDialog::getOpenFileUrl(_widget, i18nc("@title:window", "Open Image File"), QUrl(), i18n("Images (*.png *.jpg *.jpeg)"));
0633     if(url.isEmpty()) return;
0634 
0635     QTemporaryFile tempFile;
0636     tempFile.open();
0637     KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tempFile.fileName()), -1, KIO::Overwrite);
0638     KJobWidgets::setWindow(job, _widget);
0639     job->exec();
0640     if (job->error()) {
0641         KMessageBox::error(_widget, job->errorString());
0642         return;
0643     }
0644 
0645     QByteArray data = tempFile.readAll();
0646     tempFile.close();
0647 
0648     QPixmap pixmap;
0649     if(!pixmap.loadFromData(data)) {
0650         KMessageBox::error(_widget, i18n("Cannot parse file '%1'", tempFile.fileName()));
0651         return;
0652     }
0653 
0654     QString imgName;
0655     for(int n=0;; ++n) {
0656         imgName = QStringLiteral("img:%1").arg(n);
0657         if(note()->childItem(imgName) != nullptr) continue;
0658         bool found = false;
0659         foreach(StepCore::Item* item, _newItems)
0660             if(item->name() == imgName) { found = true; break; }
0661         if(!found) break;
0662     }
0663     
0664     _newItems << new StepCore::NoteImage(imgName, data);
0665     //_textEdit->document()->addResource(QTextDocument::ImageResource, imgName, pixmap);
0666     _textEdit->insertHtml(QStringLiteral("<img src=\"%1\" />").arg(imgName));
0667 }
0668 
0669 void NoteGraphicsItem::insertFormula()
0670 {
0671     QString imgName;
0672     for(int n=0;; ++n) {
0673         imgName = QStringLiteral("fml:%1").arg(n);
0674         if(note()->childItem(imgName) != nullptr) continue;
0675         bool found = false;
0676         foreach(StepCore::Item* item, _newItems)
0677             if(item->name() == imgName) { found = true; break; }
0678         if(!found) break;
0679     }
0680 
0681     StepCore::NoteFormula* formula = new StepCore::NoteFormula(imgName);
0682     if(!editFormula(formula)) {
0683         delete formula;
0684         return;
0685     }
0686 
0687     _newItems << formula;
0688     _textEdit->insertHtml(QStringLiteral("<img src=\"%1\" />").arg(imgName));
0689 }
0690 
0691 bool NoteGraphicsItem::editFormula(StepCore::NoteFormula* formula)
0692 {
0693     if(!LatexFormula::isLatexInstalled()) {
0694         KMessageBox::error(_widget, i18n("Cannot find latex installation. "
0695                     "You need 'latex', 'dvips' and 'gs' executables installed and accessible from $PATH"));
0696         return false;
0697     }
0698 
0699     bool ok;
0700     QString code = QInputDialog::getMultiLineText(_widget, i18nc("@title:window", "LaTex Formula - Step"),
0701                 i18n("Enter LaTeX formula string:"), QString(formula->code()), &ok);
0702     if(!ok) return false;
0703 
0704     QByteArray image;
0705     QString error;
0706 
0707     bool result = LatexFormula::compileFormula(code, &image, &error);
0708 
0709     if(!result) {
0710         KMessageBox::error(_widget, i18n("Cannot compile LaTeX formula: %1", error));
0711         return false;
0712     }
0713 
0714     QPixmap pixmap;
0715     if(!pixmap.loadFromData(image)) {
0716         KMessageBox::error(_widget, i18n("Cannot parse result image"));
0717         return false;
0718     }
0719 
0720     formula->setCode(code);
0721     formula->setImage(image);
0722 
0723     _textEdit->document()->addResource(QTextDocument::ImageResource,
0724                                             QUrl(formula->name()), pixmap);
0725     return true;
0726 }
0727 
0728 void NoteGraphicsItem::worldDataChanged(bool dynamicOnly)
0729 {
0730     if(!dynamicOnly) {
0731         if(!_hasFocus && _textEdit->toHtml() != note()->text()) {
0732             //++_updating;
0733 
0734             const StepCore::ItemList::const_iterator end = note()->items().end();
0735             for(StepCore::ItemList::const_iterator it = note()->items().begin(); it != end; ++it) {
0736                 if((*it)->metaObject()->inherits<StepCore::NoteImage>()) {
0737                     QPixmap pix;
0738                     pix.loadFromData(static_cast<StepCore::NoteImage*>(*it)->image());
0739                     _textEdit->document()->addResource(QTextDocument::ImageResource, QUrl((*it)->name()), pix);
0740                 }
0741             }
0742 
0743             /*
0744             const StepCore::NoteDataMap::const_iterator end = note()->dataMap().constEnd();
0745             for(StepCore::NoteDataMap::const_iterator it = note()->dataMap().constBegin();
0746                                                                             it != end; ++it) {
0747                 QPixmap pix;
0748                 pix.loadFromData(it.value());
0749                 _textEdit->document()->addResource(QTextDocument::ImageResource, it.key(), pix);
0750             }
0751             */
0752 
0753             if(!_textEdit->hasFocus() && note()->text().isEmpty()) {
0754                 _textEdit->setPlainText(_textEdit->emptyNotice());
0755             } else {
0756                 _textEdit->setHtml(note()->text());
0757             }
0758 
0759             currentCharFormatChanged(_textEdit->currentCharFormat());
0760             cursorPositionChanged();
0761             //--_updating;
0762         }
0763         WidgetGraphicsItem::worldDataChanged(dynamicOnly);
0764     }
0765 }
0766 
0767 ////////////////////////////////////////////////////
0768 DataSourceWidget::DataSourceWidget(QWidget* parent)
0769     : QWidget(parent), _worldModel(nullptr)
0770 {
0771     _skipReadOnly = false;
0772     
0773     QHBoxLayout *layout = new QHBoxLayout(this);
0774     layout->setContentsMargins(0,0,0,0);
0775 
0776     _object = new KComboBox(this);
0777     _object->setToolTip(i18n("Object name"));
0778     _object->setMinimumContentsLength(10);
0779     layout->addWidget(_object, 1);
0780 
0781     _property = new KComboBox(this);
0782     _property->setToolTip(i18n("Property name"));
0783     _property->setEnabled(false);
0784     _property->setMinimumContentsLength(10);
0785     layout->addWidget(_property, 1);
0786 
0787     _index = new KComboBox(this);
0788     _index->setToolTip(i18n("Vector index"));
0789     _index->setMinimumContentsLength(1);
0790     _index->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0791     layout->addWidget(_index, 0);
0792 
0793     connect(_object, SIGNAL(activated(int)),
0794             this, SLOT(objectSelected(int)));
0795     connect(_property, SIGNAL(activated(int)),
0796             this, SLOT(propertySelected(int)));
0797 
0798     connect(_object, SIGNAL(activated(int)),
0799             this, SIGNAL(dataSourceChanged()));
0800     connect(_property, SIGNAL(activated(int)),
0801             this, SIGNAL(dataSourceChanged()));
0802     connect(_index, SIGNAL(activated(int)),
0803             this, SIGNAL(dataSourceChanged()));
0804 }
0805 
0806 void DataSourceWidget::addObjects(const QModelIndex& parent, const QString& indent)
0807 {
0808     for(int i=0; i<_worldModel->rowCount(parent); ++i) {
0809         QModelIndex index = _worldModel->index(i, 0, parent);
0810         QString name = index.data(WorldModel::FormattedNameRole).toString();
0811         _object->addItem(indent + name, QVariant::fromValue(_worldModel->object(index)));
0812         addObjects(index, indent + ' ');
0813     }
0814 }
0815 
0816 StepCore::Object* DataSourceWidget::dataObject() const
0817 {
0818     if(_object->currentIndex() < 0) return nullptr;
0819     return _object->itemData(_object->currentIndex()).value<StepCore::Object*>();
0820 }
0821 
0822 void DataSourceWidget::setDataSource(WorldModel* worldModel,
0823             StepCore::Object* object, const QString& property, int index)
0824 {
0825     _worldModel = worldModel;
0826     if(!_worldModel) return;
0827 
0828     _object->clear();
0829 
0830     addObjects(QModelIndex(), QLatin1String(""));
0831 
0832     int objIndex = _object->findData(QVariant::fromValue(object));
0833     _object->setCurrentIndex( objIndex );
0834     objectSelected(objIndex);
0835 
0836     int propIndex = _property->findData(property);
0837     _property->setCurrentIndex( propIndex );
0838     propertySelected(propIndex);
0839 
0840     _index->setCurrentIndex( index );
0841 }
0842 
0843 void DataSourceWidget::objectSelected(int index)
0844 {
0845     Q_ASSERT(_worldModel);
0846 
0847     _property->clear();
0848 
0849     const StepCore::Object* obj = _object->itemData(index).value<StepCore::Object*>();
0850     if(obj != nullptr) {
0851         _property->setEnabled(true);
0852         for(int i=0; i<obj->metaObject()->propertyCount(); ++i) {
0853             const StepCore::MetaProperty* pr = obj->metaObject()->property(i);
0854             if(_skipReadOnly && !pr->isWritable()) continue;
0855             if(pr->userTypeId() == qMetaTypeId<double>() ||
0856                         pr->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
0857                 _property->addItem(pr->nameTr(), pr->name());
0858             }
0859         }
0860         propertySelected(_property->currentIndex());
0861     } else {
0862         _property->setEnabled(false);
0863     }
0864 }
0865 
0866 void DataSourceWidget::propertySelected(int index)
0867 {
0868     Q_ASSERT(_worldModel);
0869 
0870     QString text = _property->itemData(index).toString();
0871     const StepCore::Object* obj = _object->itemData(_object->currentIndex())
0872                                                         .value<StepCore::Object*>();
0873     const StepCore::MetaProperty* pr = obj ? obj->metaObject()->property(text) : nullptr;
0874 
0875     _index->clear();
0876     if(pr != nullptr && pr->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
0877         _index->setEnabled(true);
0878         _index->addItem(QStringLiteral("0"));
0879         _index->addItem(QStringLiteral("1"));
0880     } else {
0881         _index->setEnabled(false);
0882     }
0883 }
0884 
0885 ////////////////////////////////////////////////////
0886 GraphGraphicsItem::GraphGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
0887     : WidgetGraphicsItem(item, worldModel)
0888 {
0889     Q_ASSERT(dynamic_cast<StepCore::Graph*>(_item) != nullptr);
0890     setFlag(QGraphicsItem::ItemIsSelectable);
0891     setFlag(QGraphicsItem::ItemIsMovable);
0892     setAcceptHoverEvents(true);
0893 
0894     _plotWidget = new KPlotWidget();
0895     _plotWidget->setPalette(QPalette(Qt::lightGray));
0896     _plotWidget->setBackgroundColor(Qt::white);
0897     _plotWidget->setForegroundColor(Qt::black);
0898     //_plotWidget->setLeftPadding(0);
0899     //_plotWidget->setTopPadding(2);
0900     //_plotWidget->setRightPadding(3);
0901 
0902     _plotObject = new KPlotObject(Qt::black);
0903     _plotObject->setShowPoints(false);
0904     _plotObject->setShowLines(true);
0905     _plotObject->setPointStyle(KPlotObject::Square);
0906 
0907     _plotObject1 = new KPlotObject(Qt::red);
0908     _plotObject1->setShowPoints(true);
0909     _plotObject1->setShowLines(false);
0910     _plotObject1->setPointStyle(KPlotObject::Square);
0911 
0912     QList<KPlotObject*> plotObjects;
0913     plotObjects << _plotObject;
0914     plotObjects << _plotObject1;
0915 
0916     //_plotWidget->setAntialiasing(true);
0917     _plotWidget->addPlotObjects(plotObjects);
0918 
0919     _lastColor = 0xff000000;
0920     _lastPointTime = -HUGE_VAL;
0921 
0922     setCenteralWidget(_plotWidget);
0923 
0924     setOnHoverHandlerEnabled(true);
0925     _plotWidget->setMouseTracking(true);
0926 }
0927 
0928 inline StepCore::Graph* GraphGraphicsItem::graph() const
0929 {
0930     return static_cast<StepCore::Graph*>(_item);
0931 }
0932 
0933 void GraphGraphicsItem::adjustLimits()
0934 {
0935     double minX =  HUGE_VAL, minY =  HUGE_VAL;
0936     double maxX = -HUGE_VAL, maxY = -HUGE_VAL;
0937 
0938     if(graph()->autoLimitsX() || graph()->autoLimitsY()) {
0939         for(int i=0; i<(int) graph()->points().size(); ++i) {
0940             StepCore::Vector2d p = graph()->points()[i];
0941             if(p[0] < minX) minX = p[0];
0942             if(p[0] > maxX) maxX = p[0];
0943             if(p[1] < minY) minY = p[1];
0944             if(p[1] > maxY) maxY = p[1];
0945         }
0946     }
0947 
0948     if(!graph()->autoLimitsX() || graph()->points().empty()) {
0949         minX = graph()->limitsX()[0];
0950         maxX = graph()->limitsX()[1];
0951     } else {
0952         double range = maxX - minX;
0953         if(range != 0) { minX -= 0.1*range; maxX += 0.1*range; }
0954         else { minX -= 0.5; maxX += 0.5; }
0955     }
0956 
0957     if(!graph()->autoLimitsY() || graph()->points().empty()) {
0958         minY = graph()->limitsY()[0];
0959         maxY = graph()->limitsY()[1];
0960     } else {
0961         double range = maxY - minY;
0962         if(range != 0) { minY -= 0.1*range; maxY += 0.1*range; }
0963         else { minY -= 0.5; maxY += 0.5; }
0964     }
0965 
0966     _plotWidget->setLimits(minX, maxX, minY, maxY);
0967 }
0968 
0969 void GraphGraphicsItem::worldDataChanged(bool dynamicOnly)
0970 {
0971     if(!dynamicOnly) {
0972         viewScaleChanged();
0973 
0974         // Labels
0975         QString labelX, labelY;
0976         if(graph()->isValidX()) {
0977             labelX = i18n("%1.%2", _worldModel->formatName(graph()->objectX()), graph()->propertyX());
0978             if(graph()->indexX() >= 0) labelX.append(i18n("[%1]", graph()->indexX()));
0979             QString units = graph()->unitsX();
0980             if(!units.isEmpty()) labelX.append(" [").append(units).append("]");
0981         } else {
0982             labelX = i18n("[not configured]");
0983         }
0984         if(graph()->isValidY()) {
0985             labelY = i18n("%1.%2", _worldModel->formatName(graph()->objectY()), graph()->propertyY());
0986             if(graph()->indexY() >= 0) labelY.append(i18n("[%1]", graph()->indexY()));
0987             QString units = graph()->unitsY();
0988             if(!units.isEmpty()) labelY.append(" [").append(units).append("]");
0989         } else {
0990             labelY = i18n("[not configured]");
0991         }
0992         _plotWidget->axis( KPlotWidget::BottomAxis )->setLabel(labelX);
0993         _plotWidget->axis( KPlotWidget::LeftAxis )->setLabel(labelY);
0994 
0995         if(!graph()->autoLimitsX() && !graph()->autoLimitsY()) adjustLimits();
0996 
0997         _plotObject->setShowPoints(graph()->showPoints());
0998         _plotObject->setShowLines(graph()->showLines());
0999 
1000         if(_lastColor != graph()->color()) {
1001             _lastColor = graph()->color();
1002             _plotObject->setLinePen(QPen(QColor::fromRgba(_lastColor),0));
1003         }
1004 
1005         /*
1006         // Points
1007         _plotObject->clearPoints();
1008         for(int i=0; i<(int) graph()->points().size(); ++i) {
1009             StepCore::Vector2d p = graph()->points()[i];
1010             _plotObject->addPoint(p[0], p[1]);
1011         }
1012 
1013         adjustLimits();
1014         _plotWidget->update();
1015         */
1016 
1017         WidgetGraphicsItem::worldDataChanged(dynamicOnly);
1018     }
1019     
1020     if(_worldModel->isSimulationActive()) {
1021         if(_worldModel->world()->time() > _lastPointTime
1022                     + 1.0/_worldModel->simulationFps() - 1e-2/_worldModel->simulationFps()) {
1023             graph()->recordPoint();
1024             _lastPointTime = _worldModel->world()->time();
1025         }
1026     }
1027 
1028     int po_count, p_count;
1029     do {
1030         const QList<KPlotPoint*> points = _plotObject->points();
1031         po_count = points.count(); p_count = graph()->points().size();
1032         int count = qMin(po_count, p_count);
1033         for(int p=0; p < count; ++p)
1034             points[p]->setPosition(vectorToPoint(graph()->points()[p]));
1035     } while(0);
1036 
1037     if(po_count < p_count) {
1038         for(; po_count < p_count; ++po_count)
1039             _plotObject->addPoint(vectorToPoint(graph()->points()[po_count]));
1040     } else {
1041         for(--po_count; po_count >= p_count; --po_count)
1042             _plotObject->removePoint(po_count);
1043     }
1044 
1045     if(p_count > 0) {
1046         if(_plotObject1->points().isEmpty()) {
1047             _plotObject1->addPoint(0,0);
1048         }
1049         _plotObject1->points()[0]->setPosition(vectorToPoint(graph()->points()[p_count-1]));
1050     } else {
1051         _plotObject1->clearPoints();
1052     }
1053 
1054     if(graph()->autoLimitsX() || graph()->autoLimitsY()) adjustLimits();
1055     _plotWidget->update();
1056 
1057 #if 0
1058 //#error Do setProperty here and remove DynamicOnly from points
1059         if(ok) {
1060             _plotObject->addPoint(point[0], point[1]);
1061             if(graph()->autoLimitsX() || graph()->autoLimitsY()) 
1062                 adjustLimits();
1063             _plotWidget->update();
1064         }
1065         _lastPointTime = _worldModel->world()->time();
1066         worldDataChanged(false);
1067         //_worldModel->setProperty(graph(), "name", QString("test"));
1068     }
1069 #endif
1070 }
1071 
1072 void GraphMenuHandler::populateMenu(QMenu* menu, KActionCollection* actions)
1073 {
1074     _confUi = nullptr;
1075     _confDialog = nullptr;
1076     _confChanged = false;
1077 
1078     menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Graph"), this, &GraphMenuHandler::clearGraph);
1079     menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Graph..."), this, &GraphMenuHandler::configureGraph);
1080     menu->addSeparator();
1081     ItemMenuHandler::populateMenu(menu, actions);
1082 }
1083 
1084 inline StepCore::Graph* GraphMenuHandler::graph() const
1085 {
1086     return static_cast<StepCore::Graph*>(_object);
1087 }
1088 
1089 void GraphMenuHandler::configureGraph()
1090 {
1091     if(_worldModel->isSimulationActive())
1092         _worldModel->simulationStop();
1093 
1094     _confChanged = false;
1095     _confDialog = new QDialog(); // XXX: parent?
1096     
1097     _confDialog->setWindowTitle(i18nc("@title:window", "Configure Graph"));
1098     _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
1099                       | QDialogButtonBox::Cancel
1100                       | QDialogButtonBox::Apply);
1101     QWidget *mainWidget = new QWidget();
1102     QVBoxLayout *mainLayout = new QVBoxLayout;
1103     _confDialog->setLayout(mainLayout);
1104     mainLayout->addWidget(mainWidget);
1105 
1106     QPushButton *okButton = _buttonBox->button(QDialogButtonBox::Ok);
1107     okButton->setDefault(true);
1108     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
1109     _confDialog->connect(_buttonBox, &QDialogButtonBox::accepted, _confDialog, &QDialog::accept);
1110     _confDialog->connect(_buttonBox, &QDialogButtonBox::rejected, _confDialog, &QDialog::reject);
1111     mainLayout->addWidget(_buttonBox);
1112 
1113     _confUi = new Ui::WidgetConfigureGraph;
1114     _confUi->setupUi(mainWidget);
1115 
1116     _confUi->dataSourceX->setDataSource(_worldModel,
1117                     graph()->objectX(), graph()->propertyX(), graph()->indexX());
1118     _confUi->dataSourceY->setDataSource(_worldModel,
1119                     graph()->objectY(), graph()->propertyY(), graph()->indexY());
1120 
1121     _confUi->checkBoxAutoX->setChecked(graph()->autoLimitsX());
1122     _confUi->checkBoxAutoY->setChecked(graph()->autoLimitsY());
1123 
1124     _confUi->lineEditMinX->setValidator(
1125                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMinX));
1126     _confUi->lineEditMaxX->setValidator(
1127                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMaxX));
1128     _confUi->lineEditMinY->setValidator(
1129                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMinY));
1130     _confUi->lineEditMaxY->setValidator(
1131                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMaxY));
1132 
1133     _confUi->lineEditMinX->setText(QString::number(graph()->limitsX()[0]));
1134     _confUi->lineEditMaxX->setText(QString::number(graph()->limitsX()[1]));
1135     _confUi->lineEditMinY->setText(QString::number(graph()->limitsY()[0]));
1136     _confUi->lineEditMaxY->setText(QString::number(graph()->limitsY()[1]));
1137 
1138     _confUi->checkBoxShowLines->setChecked(graph()->showLines());
1139     _confUi->checkBoxShowPoints->setChecked(graph()->showPoints());
1140 
1141     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
1142 
1143     connect(_buttonBox, &QDialogButtonBox::clicked, this, &GraphMenuHandler::confApply);
1144 
1145     connect(_confUi->dataSourceX, &DataSourceWidget::dataSourceChanged, this, &GraphMenuHandler::confChanged);
1146     connect(_confUi->dataSourceY, &DataSourceWidget::dataSourceChanged, this, &GraphMenuHandler::confChanged);
1147     connect(_confUi->checkBoxAutoX, &QCheckBox::stateChanged, this, &GraphMenuHandler::confChanged);
1148     connect(_confUi->checkBoxAutoY, &QCheckBox::stateChanged, this, &GraphMenuHandler::confChanged);
1149     connect(_confUi->lineEditMinX, &QLineEdit::textEdited, this, &GraphMenuHandler::confChanged);
1150     connect(_confUi->lineEditMaxX, &QLineEdit::textEdited, this, &GraphMenuHandler::confChanged);
1151     connect(_confUi->lineEditMinY, &QLineEdit::textEdited, this, &GraphMenuHandler::confChanged);
1152     connect(_confUi->lineEditMaxY, &QLineEdit::textEdited, this, &GraphMenuHandler::confChanged);
1153     connect(_confUi->checkBoxShowLines, &QCheckBox::stateChanged, this, &GraphMenuHandler::confChanged);
1154     connect(_confUi->checkBoxShowPoints, &QCheckBox::stateChanged, this, &GraphMenuHandler::confChanged);
1155 
1156     _confDialog->exec();
1157 
1158     delete _confDialog; _confDialog = nullptr;
1159     delete _confUi; _confUi = nullptr;
1160 }
1161 
1162 void GraphMenuHandler::confApply(QAbstractButton *button)
1163 {
1164     if (_buttonBox->button(QDialogButtonBox::Apply) != button
1165     && _buttonBox->button(QDialogButtonBox::Ok) != button) {
1166     return;
1167     }
1168 
1169     Q_ASSERT(_confUi && _confDialog);
1170 
1171     // XXX: check for actual change ?
1172     if(!_confChanged) return;
1173     _worldModel->beginMacro(i18n("Edit properties of %1", graph()->name()));
1174 
1175     QVariant objX = QVariant::fromValue(_confUi->dataSourceX->dataObject());
1176 
1177     QVariant objY = QVariant::fromValue(_confUi->dataSourceY->dataObject());
1178 
1179     _worldModel->setProperty(graph(), QStringLiteral("objectX"), objX);
1180     _worldModel->setProperty(graph(), QStringLiteral("propertyX"),
1181                         _confUi->dataSourceX->dataProperty());
1182     _worldModel->setProperty(graph(), QStringLiteral("indexX"),
1183                         _confUi->dataSourceX->dataIndex());
1184 
1185     _worldModel->setProperty(graph(), QStringLiteral("objectY"), objY);
1186     _worldModel->setProperty(graph(), QStringLiteral("propertyY"),
1187                         _confUi->dataSourceY->dataProperty());
1188     _worldModel->setProperty(graph(), QStringLiteral("indexY"),
1189                         _confUi->dataSourceY->dataIndex());
1190 
1191     _worldModel->setProperty(graph(), QStringLiteral("autoLimitsX"),
1192                         _confUi->checkBoxAutoX->isChecked());
1193     _worldModel->setProperty(graph(), QStringLiteral("autoLimitsY"),
1194                         _confUi->checkBoxAutoY->isChecked());
1195 
1196     StepCore::Vector2d limitsX(_confUi->lineEditMinX->text().toDouble(),
1197                                _confUi->lineEditMaxX->text().toDouble());
1198     StepCore::Vector2d limitsY(_confUi->lineEditMinY->text().toDouble(),
1199                                _confUi->lineEditMaxY->text().toDouble());
1200 
1201     _worldModel->setProperty(graph(), QStringLiteral("limitsX"),
1202                         QVariant::fromValue(limitsX));
1203     _worldModel->setProperty(graph(), QStringLiteral("limitsY"),
1204                         QVariant::fromValue(limitsY));
1205 
1206     _worldModel->setProperty(graph(), QStringLiteral("showLines"),
1207                         _confUi->checkBoxShowLines->isChecked());
1208     _worldModel->setProperty(graph(), QStringLiteral("showPoints"),
1209                         _confUi->checkBoxShowPoints->isChecked());
1210 
1211     _worldModel->endMacro();
1212 }
1213 
1214 void GraphMenuHandler::confChanged()
1215 {
1216     Q_ASSERT(_confUi && _confDialog);
1217     _confChanged = true;
1218     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
1219 }
1220 
1221 void GraphMenuHandler::clearGraph()
1222 {
1223     _worldModel->simulationPause();
1224     //_lastPointTime = -HUGE_VAL; // XXX
1225     _worldModel->beginMacro(i18n("Clear graph %1", _object->name()));
1226     _worldModel->setProperty(graph(), QStringLiteral("points"),
1227                    QVariant::fromValue(StepCore::Vector2dList()) );
1228     _worldModel->endMacro();
1229 }
1230 
1231 ////////////////////////////////////////////////////
1232 MeterGraphicsItem::MeterGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
1233     : WidgetGraphicsItem(item, worldModel)
1234 {
1235     Q_ASSERT(dynamic_cast<StepCore::Meter*>(_item) != nullptr);
1236     setFlag(QGraphicsItem::ItemIsSelectable);
1237     setFlag(QGraphicsItem::ItemIsMovable);
1238     setAcceptHoverEvents(true);
1239     setBackgroundBrush(QBrush(Qt::white));
1240 
1241     _widget = new QFrame();
1242     _widget->setFrameShape(QFrame::Box);
1243     _widget->setPalette(QPalette(Qt::lightGray));
1244 
1245     QGridLayout* layout = new QGridLayout(_widget);
1246     layout->setContentsMargins(0,0,2,0);
1247     layout->setSpacing(0);
1248 
1249     _lcdNumber = new QLCDNumber(_widget);
1250     _lcdNumber->setFrameShape(QFrame::NoFrame);
1251     _lcdNumber->setSegmentStyle(QLCDNumber::Flat);
1252     _lcdNumber->display(0);
1253 
1254     _labelUnits = new QLabel(_widget);
1255     _labelUnits->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
1256 
1257     layout->addWidget(_lcdNumber, 0, 0, 1, 1);
1258     layout->addWidget(_labelUnits, 0, 1, 1, 1);
1259 
1260     setCenteralWidget(_widget);
1261     setOnHoverHandlerEnabled(true);
1262     _widget->setMouseTracking(true);
1263     _lcdNumber->setMouseTracking(true);
1264     _labelUnits->setMouseTracking(true);
1265 }
1266 
1267 inline StepCore::Meter* MeterGraphicsItem::meter() const
1268 {
1269     return static_cast<StepCore::Meter*>(_item);
1270 }
1271 
1272 void MeterGraphicsItem::worldDataChanged(bool dynamicOnly)
1273 {
1274     if(!dynamicOnly) {
1275         viewScaleChanged();
1276 
1277         if(meter()->digits() != _lcdNumber->digitCount())
1278             _lcdNumber->setDigitCount(meter()->digits());
1279 
1280         QString units = meter()->units();
1281         if(units != _labelUnits->text()) {
1282             QFont font(_labelUnits->font());
1283             int pixelSize = int(meter()->size()[1]/2);
1284             for(; pixelSize > 0; --pixelSize) {
1285                 font.setPixelSize(pixelSize);
1286                 QFontMetrics fm(font);
1287                 if(fm.boundingRect(units).width() < int(meter()->size()[0]/3)) break;
1288             }
1289             _labelUnits->setFont(font);
1290             _labelUnits->setText(units);
1291         }
1292 
1293         WidgetGraphicsItem::worldDataChanged(dynamicOnly);
1294     }
1295 
1296     double value = meter()->value();
1297     _lcdNumber->display(value);
1298 }
1299 
1300 void MeterMenuHandler::populateMenu(QMenu* menu, KActionCollection* actions)
1301 {
1302     _confUi = nullptr;
1303     _confDialog = nullptr;
1304     _confChanged = false;
1305 
1306     menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Meter..."), this, &MeterMenuHandler::configureMeter);
1307     menu->addSeparator();
1308     ItemMenuHandler::populateMenu(menu, actions);
1309 }
1310 
1311 inline StepCore::Meter* MeterMenuHandler::meter() const
1312 {
1313     return static_cast<StepCore::Meter*>(_object);
1314 }
1315 
1316 void MeterMenuHandler::configureMeter()
1317 {
1318     if(_worldModel->isSimulationActive())
1319         _worldModel->simulationStop();
1320 
1321     _confChanged = false;
1322 
1323     _confDialog = new QDialog(); // XXX
1324     _confDialog->setWindowTitle(i18nc("@title:window", "Configure Meter"));
1325     _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
1326                       | QDialogButtonBox::Cancel
1327                       | QDialogButtonBox::Apply);
1328     QWidget *mainWidget = new QWidget();
1329     QVBoxLayout *mainLayout = new QVBoxLayout;
1330     _confDialog->setLayout(mainLayout);
1331     mainLayout->addWidget(mainWidget);
1332 
1333     QPushButton *okButton = _buttonBox->button(QDialogButtonBox::Ok);
1334     okButton->setDefault(true);
1335     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
1336     _confDialog->connect(_buttonBox, &QDialogButtonBox::accepted, _confDialog, &QDialog::accept);
1337     _confDialog->connect(_buttonBox, &QDialogButtonBox::rejected, _confDialog, &QDialog::reject);
1338     mainLayout->addWidget(_buttonBox);
1339 
1340     _confUi = new Ui::WidgetConfigureMeter;
1341     _confUi->setupUi(mainWidget);
1342 
1343     _confUi->dataSource->setDataSource(_worldModel,
1344                 meter()->object(), meter()->property(), meter()->index());
1345 
1346     _confUi->lineEditDigits->setValidator(
1347                 new QIntValidator(0, 100, _confUi->lineEditDigits));
1348     _confUi->lineEditDigits->setText(QString::number(meter()->digits()));
1349 
1350     connect(_buttonBox, &QDialogButtonBox::clicked, this, &MeterMenuHandler::confApply);
1351 
1352     connect(_confUi->dataSource, &DataSourceWidget::dataSourceChanged, this, &MeterMenuHandler::confChanged);
1353     connect(_confUi->lineEditDigits, &QLineEdit::textEdited, this, &MeterMenuHandler::confChanged);
1354 
1355     _confDialog->exec();
1356 
1357     delete _confDialog; _confDialog = nullptr;
1358     delete _confUi; _confUi = nullptr;
1359 }
1360 
1361 void MeterMenuHandler::confApply(QAbstractButton *button)
1362 {
1363     if (_buttonBox->button(QDialogButtonBox::Apply) != button
1364     && _buttonBox->button(QDialogButtonBox::Ok) != button) {
1365     return;
1366     }
1367 
1368     Q_ASSERT(_confUi && _confDialog);
1369 
1370     // XXX: check for actual change ?
1371     if(!_confChanged) return;
1372     _worldModel->beginMacro(i18n("Edit properties of %1", meter()->name()));
1373 
1374     _worldModel->setProperty(meter(), QStringLiteral("object"),
1375                         QVariant::fromValue(_confUi->dataSource->dataObject()));
1376     _worldModel->setProperty(meter(), QStringLiteral("property"),
1377                         _confUi->dataSource->dataProperty());
1378     _worldModel->setProperty(meter(), QStringLiteral("index"),
1379                         _confUi->dataSource->dataIndex());
1380 
1381     _worldModel->setProperty(meter(), QStringLiteral("digits"),
1382                         _confUi->lineEditDigits->text().toInt());
1383 
1384     _worldModel->endMacro();
1385 }
1386 
1387 void MeterMenuHandler::confChanged()
1388 {
1389     Q_ASSERT(_confUi && _confDialog);
1390     _confChanged = true;
1391     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
1392 }
1393 
1394 ////////////////////////////////////////////////////
1395 ControllerGraphicsItem::ControllerGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
1396     : WidgetGraphicsItem(item, worldModel)
1397 {
1398     Q_ASSERT(dynamic_cast<StepCore::Controller*>(_item) != nullptr);
1399     setFlag(QGraphicsItem::ItemIsSelectable);
1400     setFlag(QGraphicsItem::ItemIsMovable);
1401     setAcceptHoverEvents(true);
1402     setBackgroundBrush(QBrush(Qt::white));
1403 
1404     _widget = new QWidget();
1405     _widget->setPalette(QPalette(Qt::lightGray));
1406     QGridLayout* layout = new QGridLayout(_widget);
1407 
1408     _labelMin = new QLabel(_widget); _labelMin->setAlignment(Qt::AlignRight);
1409     _labelMax = new QLabel(_widget); _labelMax->setAlignment(Qt::AlignLeft);
1410     _labelSource = new QLabel(_widget); _labelSource->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
1411     _slider = new QSlider(Qt::Horizontal, _widget);
1412     _slider->setRange(SLIDER_MIN, SLIDER_MAX);
1413     connect(_slider, &QAbstractSlider::sliderMoved, this, &ControllerGraphicsItem::sliderChanged);
1414     connect(_slider, &QAbstractSlider::sliderReleased, this, &ControllerGraphicsItem::sliderReleased);
1415 
1416     layout->addWidget(_labelMin, 0, 0, 1, 1);
1417     layout->addWidget(_slider, 0, 1, 1, 1);
1418     layout->addWidget(_labelMax, 0, 2, 1, 1);
1419     layout->addWidget(_labelSource, 1, 0, 1, 3);
1420 
1421     _incAction = new QAction(i18n("Increase Value"), _widget);
1422     _decAction = new QAction(i18n("Decrease Value"), _widget);
1423 
1424     connect(_incAction, &QAction::triggered, this, &ControllerGraphicsItem::incTriggered);
1425     connect(_decAction, &QAction::triggered, this, &ControllerGraphicsItem::decTriggered);
1426 
1427     _widget->addAction(_incAction);
1428     _widget->addAction(_decAction);
1429     //_widget->addAction(_configureAction);
1430     //_widget->setContextMenuPolicy(Qt::ActionsContextMenu);
1431 
1432     _lastValue = 1;
1433     _changed = false;
1434 
1435     setCenteralWidget(_widget);
1436     setOnHoverHandlerEnabled(true);
1437     _widget->setMouseTracking(true);
1438     _labelMin->setMouseTracking(true);
1439     _labelMax->setMouseTracking(true);
1440     _labelSource->setMouseTracking(true);
1441     _slider->setMouseTracking(true);
1442 }
1443 
1444 inline StepCore::Controller* ControllerGraphicsItem::controller() const
1445 {
1446     return static_cast<StepCore::Controller*>(_item);
1447 }
1448 
1449 void ControllerGraphicsItem::decTriggered()
1450 {
1451     _worldModel->simulationPause();
1452     _worldModel->beginMacro(i18n("Decrease controller %1", _item->name()));
1453     _worldModel->setProperty(controller(), QStringLiteral("value"),
1454                     controller()->value() - controller()->increment());
1455     _worldModel->endMacro();
1456 }
1457 
1458 void ControllerGraphicsItem::incTriggered()
1459 {
1460     _worldModel->simulationPause();
1461     _worldModel->beginMacro(i18n("Increase controller %1", _item->name()));
1462     _worldModel->setProperty(controller(), QStringLiteral("value"),
1463                     controller()->value() + controller()->increment());
1464     _worldModel->endMacro();
1465 }
1466 
1467 void ControllerGraphicsItem::worldDataChanged(bool dynamicOnly)
1468 {
1469     if(!dynamicOnly) {
1470         viewScaleChanged();
1471 
1472         // Labels
1473         _labelMin->setText(QString::number(controller()->limits()[0]));
1474         _labelMax->setText(QString::number(controller()->limits()[1]));
1475 
1476         QString source;
1477         if(controller()->isValid()) {
1478             source = i18n("%1.%2", _worldModel->formatName(controller()->object()), controller()->property());
1479             if(controller()->index() >= 0) source.append(i18n("[%1]", controller()->index()));
1480             QString units = controller()->units();
1481             if(!units.isEmpty()) source.append(" [").append(units).append("]");
1482         } else {
1483             source = i18n("[not configured]");
1484         }
1485         _labelSource->setText(source);
1486 
1487         if(_incAction->isEnabled() != controller()->isValid()) {
1488             _incAction->setEnabled(controller()->isValid());
1489             _decAction->setEnabled(controller()->isValid());
1490         }
1491 
1492         if(_incShortcut != controller()->increaseShortcut()) {
1493             _incShortcut = controller()->increaseShortcut();
1494             _incAction->setShortcut(QKeySequence(_incShortcut));
1495         }
1496 
1497         if(_decShortcut != controller()->decreaseShortcut()) {
1498             _decShortcut = controller()->decreaseShortcut();
1499             _decAction->setShortcut(QKeySequence(_decShortcut));
1500         }
1501 
1502         //if(!graph()->autoLimitsX() && !graph()->autoLimitsY()) adjustLimits();
1503 
1504         /*
1505         // Points
1506         _plotObject->clearPoints();
1507         for(int i=0; i<(int) graph()->points().size(); ++i) {
1508             StepCore::Vector2d p = graph()->points()[i];
1509             _plotObject->addPoint(p[0], p[1]);
1510         }
1511 
1512         adjustLimits();
1513         _plotWidget->update();
1514         */
1515 
1516         WidgetGraphicsItem::worldDataChanged(dynamicOnly);
1517     }
1518 
1519     double value = round((controller()->value() - controller()->limits()[0]) *
1520             (SLIDER_MAX - SLIDER_MIN) / (controller()->limits()[1] - controller()->limits()[0]) + SLIDER_MIN);
1521 
1522     if(value <= SLIDER_MIN && _lastValue > SLIDER_MIN) {
1523         QPalette palette; palette.setColor(_labelMin->foregroundRole(), Qt::red);
1524         _labelMin->setPalette(palette);
1525     } else if(value > SLIDER_MIN && _lastValue <= SLIDER_MIN) {
1526         QPalette palette; _labelMin->setPalette(palette);
1527     }
1528 
1529     if(value >= SLIDER_MAX-1 && _lastValue < SLIDER_MAX-1) {
1530         QPalette palette; palette.setColor(_labelMax->foregroundRole(), Qt::red);
1531         _labelMax->setPalette(palette);
1532     } else if(value < SLIDER_MAX-1 && _lastValue >= SLIDER_MAX-1) {
1533         QPalette palette; _labelMax->setPalette(palette);
1534     }
1535 
1536     _lastValue = value;
1537 
1538     if(value < SLIDER_MIN) value = SLIDER_MIN;
1539     else if(value > SLIDER_MAX-1) value = SLIDER_MAX-1;
1540 
1541     _slider->setValue(int(value));
1542 
1543 #if 0
1544     
1545     if(_worldModel->isSimulationActive()) {
1546         if(_worldModel->world()->time() > _lastPointTime
1547                     + 1.0/_worldModel->simulationFps() - 1e-2/_worldModel->simulationFps()) {
1548             StepCore::Vector2d point = graph()->recordPoint();
1549             _lastPointTime = _worldModel->world()->time();
1550         }
1551     }
1552 
1553     int po_count, p_count;
1554     do {
1555         const QList<KPlotPoint*> points = _plotObject->points();
1556         po_count = points.count(); p_count = graph()->points().size();
1557         int count = qMin(po_count, p_count);
1558         for(int p=0; p < count; ++p)
1559             points[p]->setPosition(vectorToPoint(graph()->points()[p]));
1560     } while(0);
1561 
1562     if(po_count < p_count) {
1563         for(; po_count < p_count; ++po_count)
1564             _plotObject->addPoint(vectorToPoint(graph()->points()[po_count]));
1565     } else {
1566         for(--po_count; po_count >= p_count; --po_count)
1567             _plotObject->removePoint(po_count);
1568     }
1569 
1570     if(graph()->autoLimitsX() || graph()->autoLimitsY()) adjustLimits();
1571     _plotWidget->update();
1572 #endif
1573 #if 0
1574 //#error Do setProperty here and remove DynamicOnly from points
1575         if(ok) {
1576             _plotObject->addPoint(point[0], point[1]);
1577             if(graph()->autoLimitsX() || graph()->autoLimitsY()) 
1578                 adjustLimits();
1579             _plotWidget->update();
1580         }
1581         _lastPointTime = _worldModel->world()->time();
1582         worldDataChanged(false);
1583         //_worldModel->setProperty(graph(), "name", QString("test"));
1584     }
1585 #endif
1586 }
1587 
1588 void ControllerGraphicsItem::sliderChanged(int value)
1589 {
1590     Q_ASSERT(value == _slider->sliderPosition());
1591     if(!controller()->isValid()) return;
1592     //if(!_worldModel->isSimulationActive()) {
1593         _worldModel->simulationPause();
1594         if(!_changed) {
1595             _worldModel->beginMacro(i18n("Change controller %1", controller()->name()));
1596             _changed = true;
1597         }
1598         double v = controller()->limits()[0] + (value - SLIDER_MIN) *
1599                 (controller()->limits()[1] - controller()->limits()[0]) / (SLIDER_MAX - SLIDER_MIN);
1600         _worldModel->setProperty(controller(), QStringLiteral("value"), v);
1601     //}
1602 }
1603 
1604 void ControllerGraphicsItem::sliderReleased()
1605 {
1606     if(_changed) {
1607         _worldModel->endMacro();
1608         _changed = false;
1609     }
1610 }
1611 
1612 void ControllerMenuHandler::populateMenu(QMenu* menu, KActionCollection* actions)
1613 {
1614     _confUi = nullptr;
1615     _confDialog = nullptr;
1616     _confChanged = false;
1617 
1618     menu->addAction(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Increase Value"), this, &ControllerMenuHandler::incTriggered);
1619     menu->addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Decrease Value"), this, &ControllerMenuHandler::decTriggered);
1620     menu->addSeparator();
1621     menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Controller..."), this, &ControllerMenuHandler::configureController);
1622     menu->addSeparator();
1623     ItemMenuHandler::populateMenu(menu, actions);
1624 }
1625 
1626 inline StepCore::Controller* ControllerMenuHandler::controller() const
1627 {
1628     return static_cast<StepCore::Controller*>(_object);
1629 }
1630 
1631 void ControllerMenuHandler::configureController()
1632 {
1633     if(_worldModel->isSimulationActive())
1634         _worldModel->simulationStop();
1635 
1636     _confChanged = false;
1637     _confDialog = new QDialog(); // XXX
1638     
1639     _confDialog->setWindowTitle(i18nc("@title:window", "Configure Controller"));
1640     _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
1641                       | QDialogButtonBox::Cancel
1642                       | QDialogButtonBox::Apply);
1643     QWidget *mainWidget = new QWidget();
1644     QVBoxLayout *mainLayout = new QVBoxLayout;
1645     _confDialog->setLayout(mainLayout);
1646     mainLayout->addWidget(mainWidget);
1647 
1648     QPushButton *okButton = _buttonBox->button(QDialogButtonBox::Ok);
1649     okButton->setDefault(true);
1650     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
1651     _confDialog->connect(_buttonBox, &QDialogButtonBox::accepted, _confDialog, &QDialog::accept);
1652     _confDialog->connect(_buttonBox, &QDialogButtonBox::rejected, _confDialog, &QDialog::reject);
1653     mainLayout->addWidget(_buttonBox);
1654 
1655     _confUi = new Ui::WidgetConfigureController;
1656     _confUi->setupUi(mainWidget);
1657 
1658     _confUi->dataSource->setSkipReadOnly(true);
1659     _confUi->dataSource->setDataSource(_worldModel, controller()->object(),
1660                                     controller()->property(), controller()->index());
1661 
1662     _confUi->lineEditMin->setValidator(
1663                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMin));
1664     _confUi->lineEditMax->setValidator(
1665                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditMax));
1666 
1667     _confUi->lineEditMin->setText(QString::number(controller()->limits()[0]));
1668     _confUi->lineEditMax->setText(QString::number(controller()->limits()[1]));
1669 
1670     _confUi->keyIncrease->setModifierlessAllowed(true);
1671     _confUi->keyDecrease->setModifierlessAllowed(true);
1672 
1673     //_confUi->keyIncrease->setKeySequence(_incAction->shortcut().primary());
1674     //_confUi->keyDecrease->setKeySequence(_decAction->shortcut().primary());
1675     _confUi->keyIncrease->setKeySequence(QKeySequence(controller()->increaseShortcut()));
1676     _confUi->keyDecrease->setKeySequence(QKeySequence(controller()->decreaseShortcut()));
1677 
1678     _confUi->lineEditIncrement->setValidator(
1679                 new QDoubleValidator(-HUGE_VAL, HUGE_VAL, DBL_DIG, _confUi->lineEditIncrement));
1680     _confUi->lineEditIncrement->setText(QString::number(controller()->increment()));
1681 
1682     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
1683 
1684     connect(_buttonBox, &QDialogButtonBox::clicked, this, &ControllerMenuHandler::confApply);
1685 
1686     connect(_confUi->dataSource, &DataSourceWidget::dataSourceChanged, this, &ControllerMenuHandler::confChanged);
1687     connect(_confUi->lineEditMin, &QLineEdit::textEdited, this, &ControllerMenuHandler::confChanged);
1688     connect(_confUi->lineEditMax, &QLineEdit::textEdited, this, &ControllerMenuHandler::confChanged);
1689     connect(_confUi->keyIncrease, &KKeySequenceWidget::keySequenceChanged, this, &ControllerMenuHandler::confChanged);
1690     connect(_confUi->keyDecrease, &KKeySequenceWidget::keySequenceChanged, this, &ControllerMenuHandler::confChanged);
1691     connect(_confUi->lineEditIncrement, &QLineEdit::textEdited, this, &ControllerMenuHandler::confChanged);
1692 
1693     _confDialog->exec();
1694 
1695     delete _confDialog; _confDialog = nullptr;
1696     delete _confUi; _confUi = nullptr;
1697 }
1698 
1699 void ControllerMenuHandler::confApply(QAbstractButton *button)
1700 {
1701     if (_buttonBox->button(QDialogButtonBox::Apply) != button
1702     && _buttonBox->button(QDialogButtonBox::Ok) != button) {
1703     return;
1704     }
1705 
1706     Q_ASSERT(_confUi && _confDialog);
1707 
1708     // XXX: check for actual change ?
1709     if(!_confChanged) return;
1710     _worldModel->beginMacro(i18n("Edit properties of %1", controller()->name()));
1711 
1712     _worldModel->setProperty(controller(), QStringLiteral("object"),
1713                     QVariant::fromValue(_confUi->dataSource->dataObject()));
1714     _worldModel->setProperty(controller(), QStringLiteral("property"),
1715                     _confUi->dataSource->dataProperty());
1716     _worldModel->setProperty(controller(), QStringLiteral("index"),
1717                     _confUi->dataSource->dataIndex());
1718 
1719     StepCore::Vector2d limits(_confUi->lineEditMin->text().toDouble(),
1720                               _confUi->lineEditMax->text().toDouble());
1721 
1722     _worldModel->setProperty(controller(), QStringLiteral("limits"),
1723                             QVariant::fromValue(limits));
1724 
1725     _worldModel->setProperty(controller(), QStringLiteral("increaseShortcut"),
1726                             QVariant::fromValue(_confUi->keyIncrease->keySequence().toString()));
1727 
1728     _worldModel->setProperty(controller(), QStringLiteral("decreaseShortcut"),
1729                             QVariant::fromValue(_confUi->keyDecrease->keySequence().toString()));
1730 
1731     _worldModel->setProperty(controller(), QStringLiteral("increment"),
1732                             QVariant::fromValue(_confUi->lineEditIncrement->text().toDouble()));
1733 
1734     _worldModel->endMacro();
1735 }
1736 
1737 void ControllerMenuHandler::confChanged()
1738 {
1739     Q_ASSERT(_confUi && _confDialog);
1740     _confChanged = true;
1741     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
1742 }
1743 
1744 void ControllerMenuHandler::decTriggered()
1745 {
1746     _worldModel->simulationPause();
1747     _worldModel->beginMacro(i18n("Decrease controller %1", _object->name()));
1748     _worldModel->setProperty(controller(), QStringLiteral("value"),
1749                     controller()->value() - controller()->increment());
1750     _worldModel->endMacro();
1751 }
1752 
1753 void ControllerMenuHandler::incTriggered()
1754 {
1755     _worldModel->simulationPause();
1756     _worldModel->beginMacro(i18n("Increase controller %1", _object->name()));
1757     _worldModel->setProperty(controller(), QStringLiteral("value"),
1758                     controller()->value() + controller()->increment());
1759     _worldModel->endMacro();
1760 }
1761 
1762 ////////////////////////////////////////////////////
1763 
1764 TracerGraphicsItem::TracerGraphicsItem(StepCore::Item* item, WorldModel* worldModel)
1765     : StepGraphicsItem(item, worldModel), _moving(false), _movingDelta(0,0)
1766 {
1767     Q_ASSERT(dynamic_cast<StepCore::Tracer*>(_item) != nullptr);
1768     setFlag(QGraphicsItem::ItemIsSelectable);
1769     setFlag(QGraphicsItem::ItemIsMovable);
1770     setZValue(HANDLER_ZVALUE);
1771 
1772     /*
1773     _lastArrowRadius = -1;
1774     _velocityHandler = new ArrowHandlerGraphicsItem(item, worldModel, this,
1775                    _item->metaObject()->property("velocity"));
1776     _velocityHandler->setVisible(false);*/
1777     //scene()->addItem(_velocityHandler);
1778 
1779     _lastPos = QPointF(0,0);
1780     _lastPointTime = -HUGE_VAL;
1781 }
1782 
1783 inline StepCore::Tracer* TracerGraphicsItem::tracer() const
1784 {
1785     return static_cast<StepCore::Tracer*>(_item);
1786 }
1787 
1788 
1789 QPainterPath TracerGraphicsItem::shape() const
1790 {
1791     QPainterPath path;
1792     double w = (HANDLER_SIZE+1)/currentViewScale();
1793     // XXX: add _points here!
1794     path.addEllipse(QRectF(_lastPos.x()-w,  _lastPos.y()-w,w*2,w*2));
1795     return path;
1796 }
1797 
1798 void TracerGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
1799 {
1800     double s = currentViewScale();
1801     double w = HANDLER_SIZE/s;
1802 
1803     painter->setRenderHint(QPainter::Antialiasing, true);
1804     painter->setPen(QPen(QColor::fromRgba(tracer()->color()), 0));
1805     //painter->setBrush(QBrush(Qt::black));
1806     painter->drawPolyline(_points);
1807     painter->drawEllipse(QRectF(_lastPos.x()-w,  _lastPos.y()-w, w*2,w*2));
1808     painter->drawPoint(_lastPos);
1809 
1810     if(_isSelected) {
1811         painter->setPen(QPen(SELECTION_COLOR, 0, Qt::DashLine));
1812         //painter->setBrush(QBrush());
1813         //painter->setBrush(QBrush(QColor(0, 0x99, 0xff)));
1814         w = (HANDLER_SIZE + SELECTION_MARGIN)/s;
1815         painter->drawEllipse(QRectF(_lastPos.x()-w, _lastPos.y()-w, w*2, w*2));
1816     }
1817 
1818 }
1819 
1820 void TracerGraphicsItem::viewScaleChanged()
1821 {
1822     prepareGeometryChange();
1823 
1824     double s = currentViewScale();
1825     double w = (HANDLER_SIZE+SELECTION_MARGIN)/s;
1826     QPointF p = vectorToPoint(tracer()->position());
1827 
1828     _boundingRect = _points.boundingRect() | QRectF(p.x()-w, p.y()-w,2*w,2*w);
1829     update();
1830 }
1831 
1832 void TracerGraphicsItem::worldDataChanged(bool)
1833 {
1834     /*
1835     if(_isMouseOverItem || _isSelected) {
1836         double vnorm = particle()->velocity().norm();
1837         double anorm = particle()->force().norm() / particle()->mass();
1838         double arrowRadius = qMax(vnorm, anorm) + ARROW_STROKE/currentViewScale();
1839         if(arrowRadius > _lastArrowRadius || arrowRadius < _lastArrowRadius/2) {
1840             _lastArrowRadius = arrowRadius;
1841             viewScaleChanged();
1842         }
1843         update();
1844     }
1845     */
1846 
1847     //setPos(vectorToPoint(tracer()->position()));
1848 
1849     if(_worldModel->isSimulationActive()) {
1850         if(_worldModel->world()->time() > _lastPointTime
1851                     + 1.0/_worldModel->simulationFps() - 1e-2/_worldModel->simulationFps()) {
1852             tracer()->recordPoint();
1853             _lastPointTime = _worldModel->world()->time();
1854         }
1855     }
1856 
1857     bool geometryChange = false;
1858     int po_count, p_count;
1859     do {
1860         po_count = _points.size(); p_count = tracer()->points().size();
1861         int count = qMin(po_count, p_count);
1862         for(int p=0; p < count; ++p) {
1863             QPointF point = vectorToPoint(tracer()->points()[p]);
1864             if(point != _points[p]) {
1865                 geometryChange = true;
1866                 _points[p] = point;
1867             }
1868         }
1869     } while(0);
1870 
1871     if(po_count < p_count) {
1872         geometryChange = true;
1873         for(; po_count < p_count; ++po_count)
1874             _points << vectorToPoint(tracer()->points()[po_count]);
1875     } else {
1876         geometryChange = true;
1877         _points.resize(p_count);
1878     }
1879 
1880     QPointF point = vectorToPoint(tracer()->position());
1881     if(point != _lastPos) {
1882         geometryChange = true;
1883         _lastPos = point;
1884     }
1885 
1886     if(geometryChange) {
1887         viewScaleChanged();
1888     }
1889 }
1890 
1891 void TracerGraphicsItem::mouseSetPos(const QPointF&, const QPointF& diff, MovingState movingState)
1892 {
1893     static_cast<WorldScene*>(scene())->snapItem(vectorToPoint(tracer()->position()) + diff,
1894                 WorldScene::SnapRigidBody | WorldScene::SnapParticle |
1895                 WorldScene::SnapSetLocalPosition, nullptr, movingState, _item);
1896 }
1897 
1898 void TracerMenuHandler::populateMenu(QMenu* menu, KActionCollection* actions)
1899 {
1900     menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Trace"), this, &TracerMenuHandler::clearTracer);
1901     menu->addSeparator();
1902     ItemMenuHandler::populateMenu(menu, actions);
1903 }
1904 
1905 void TracerMenuHandler::clearTracer()
1906 {
1907     _worldModel->simulationPause();
1908     //_lastPointTime = -HUGE_VAL; // XX
1909     _worldModel->beginMacro(i18n("Clear tracer %1", _object->name()));
1910     _worldModel->setProperty(_object, QStringLiteral("points"),
1911                    QVariant::fromValue(StepCore::Vector2dList()) );
1912     _worldModel->endMacro();
1913 }
1914 
1915 #include "moc_toolgraphics.cpp"