#include "graphic.h" #include "gif.h" #include "qad_types.h" #include "uglwidget.h" #include "ui_graphic.h" #include "ui_graphic_conf.h" #include #include #include #include #include #include #include #include #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) # include #endif #ifdef Q_OS_ANDROID # define NO_BUTTONS #else # ifndef FORCE_NO_GL # define HAS_GL # endif #endif #ifdef HAS_GL # ifndef GL_MULTISAMPLE # define GL_MULTISAMPLE 0x809D # endif #endif enum DateComponent { DateMSecs, DateSecs, DateMinutes, DateHours, DateDays, DateMonths, DateYears, DateComponentCount }; const char _button_prop_name_[] = "_button_"; const double rad2deg_qpie = 45. / atan(1.); __GraphicRegistrator__ __graphic_registrator__; class LegendScrollArea: public QScrollArea { public: LegendScrollArea(QWidget * w): QScrollArea() { minimum_hei = 0; setFrameShape(QFrame::NoFrame); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setWidgetResizable(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setWidget(w); viewport()->setAutoFillBackground(false); w->setAutoFillBackground(false); } int minimum_hei; protected: virtual QSize sizeHint() const { QSize ret; if (!widget()) return ret; ret = widget()->sizeHint(); return ret; } virtual QSize minimumSizeHint() const { return QSize(1, minimum_hei); } }; Graphic::Graphic(QWidget * parent): QFrame(parent), canvas(0), line_x_min(this), line_x_max(this), line_y_min(this), line_y_max(this) { canvas_gl = 0; timer_pause = timer_record = 0; gesture_angle = 45.; leg_update = true; visible_update = fullscr = need_mouse_pan = m_fakeGL = false; gestures = #ifdef Q_OS_ANDROID true; #else false; #endif func_gridMarkX = func_gridMarkY = nullptr; ui = new Ui::Graphic(); ui->setupUi(this); ui->status->hide(); ui->scrollLegend->layout()->addWidget(new LegendScrollArea(ui->widgetLegend)); ui->scrollLegend->hide(); fillDateFormats(); #ifdef NO_BUTTONS ui->widgetLeft->hide(); ui->widgetRight->hide(); QList btnlist = {ui->graphic_buttonAutofit, ui->graphic_checkGuides, ui->graphic_buttonFullscreen, ui->graphic_checkBorderInputs, ui->graphic_checkLegend, ui->graphic_checkPause, ui->graphic_buttonConfigure, ui->graphic_buttonRecord, ui->graphic_buttonClear, ui->graphic_buttonClose}; buttons_menu = new QMenu(this); for (auto * b: btnlist) { auto * a = new QAction(this); connect(a, SIGNAL(triggered()), b, SLOT(click())); connect(a, SIGNAL(triggered(bool)), b, SLOT(setChecked(bool))); a->setCheckable(b->isCheckable()); a->setProperty(_button_prop_name_, (quintptr)b); buttons_menu->addAction(a); } #endif QActionGroup * agroup = new QActionGroup(this); agroup->addAction(ui->graphic_actionGuidesFree); agroup->addAction(ui->graphic_actionGuidesTrace); ui->graphic_actionGuidesFree->setProperty("_value", (int)Free); ui->graphic_actionGuidesTrace->setProperty("_value", (int)Trace); connect(agroup, &QActionGroup::triggered, this, &Graphic::actionGuidesTriggered); ui->graphic_checkGuides->addAction(ui->graphic_actionGuidesFree); ui->graphic_checkGuides->addAction(ui->graphic_actionGuidesTrace); ui->graphic_buttonAutofit->addAction(ui->graphic_actionExpandX); ui->graphic_buttonAutofit->addAction(ui->graphic_actionExpandY); ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionSaveImage); ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionExportCSV); ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionExportCSV_View); line_x_min.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); line_x_max.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); ((QBoxLayout *)ui->widgetLY->layout())->insertWidget(0, &line_y_min); ((QBoxLayout *)ui->widgetLY->layout())->addWidget(&line_y_max); ((QBoxLayout *)ui->widgetLX->layout())->insertWidget(0, &line_x_min); ((QBoxLayout *)ui->widgetLX->layout())->addWidget(&line_x_max); tm.restart(); grid_numbers_x = grid_numbers_y = 1; LN10 = qLn(10.); line_x_min.setClearButtonVisible(true); line_x_max.setClearButtonVisible(true); line_y_min.setClearButtonVisible(true); line_y_max.setClearButtonVisible(true); connect(&line_x_min, &EvalSpinBox::valueChanged, this, &Graphic::lineXMinChanged); connect(&line_x_max, &EvalSpinBox::valueChanged, this, &Graphic::lineXMaxChanged); connect(&line_y_min, &EvalSpinBox::valueChanged, this, &Graphic::lineYMinChanged); connect(&line_y_max, &EvalSpinBox::valueChanged, this, &Graphic::lineYMaxChanged); connect(ui->actionLeaveFullscreen, &QAction::triggered, this, &Graphic::leaveFullscreen); connect(ui->canvas_raster, &UWidget::paintEvent, this, &Graphic::canvasPaintEvent); prepareCanvas(ui->canvas_raster); icon_exp_x = QIcon(":/icons/expand_x.png"); icon_exp_y = QIcon(":/icons/expand_y.png"); icon_exp_sx = QIcon(":/icons/expand_s_x.png"); icon_exp_sy = QIcon(":/icons/expand_s_y.png"); icon_pause_b = QImage(":/icons/pause-back.png"); icon_pause_f = QImage(":/icons/pause-front.png"); aupdate = grid = isFit = navigation = true; aalias = bufferActive = isOGL = cancel = guides = hasLblX = hasLblY = isHover = false; pause_ = only_expand_x = only_expand_y = m_LODOptimization = was_trace = false; limit_.setCoords(-std::numeric_limits::max(), -std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); eminx = eminy = std::numeric_limits::max(); emaxx = emaxy = -std::numeric_limits::max(); grad_x = grad_y = Auto; axis_type_x = Numeric; floating_axis_type = Trace; min_repaint_int = 25; // 40 Hz repaint interval lastw = lasth = 0; inc_x = 1.; // buffer = 0; gridx = gridy = 1.; history = 5.; visible_time = -1.; thick = lineThickness(this); #if QT_VERSION_MAJOR >= 5 thick *= devicePixelRatio(); #endif pause_phase = 0.; def_rect.setRect(0., 0., 1., 1.); selrect = def_rect; margins_.setRect(4, 4, 4, 4); curaction = gaMove; selbrush.setStyle(Qt::SolidPattern); selbrush.setColor(QColor(60, 175, 255, 100)); text_color = palette().color(QPalette::WindowText); grid_pen = QPen(palette().color(QPalette::Disabled, QPalette::WindowText), 0., Qt::DotLine); graphics.append(GraphicType()); curGraphic = 0; selpen = palette().color(QPalette::WindowText); selpen.setStyle(Qt::DashLine); back_color = palette().color(QPalette::Base); back_color.setAlpha(255); buttons_ = AllButtons; setOpenGL(false); setButtonsPosition(Graphic::Left); setAntialiasing(false); setCaption(""); setBorderInputsVisible(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->layoutButtons->update(); ui->widgetTop->addAction(ui->actionLeaveFullscreen); updateLegend(); setRectToLines(); conf = new GraphicConf(graphics, this); connect(conf, &GraphicConf::exportClicked, this, &Graphic::on_graphic_actionExportCSV_triggered); } Graphic::~Graphic() { for (auto & g: graphics) g.destroy(); #ifdef NO_BUTTONS delete buttons_menu; #endif delete conf; delete ui; } void Graphic::changeEvent(QEvent * e) { QFrame::changeEvent(e); if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); fillDateFormats(); return; } } void Graphic::resizeEvent(QResizeEvent *) { if (leg_update) #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(0, this, [this]() { updateLegend(false); }); #else updateLegend(false); #endif } void Graphic::showEvent(QShowEvent *) { if (need_createGL && !canvas_gl) { // qDebug() << "create GL on show"; canvas_gl = new UGLWidget(); ui->layoutCanvas->addWidget(canvas_gl); connect(canvas_gl, &UGLWidget::paintSignal, this, &Graphic::canvasPaintEvent); prepareCanvas(canvas_gl); ui->canvas_raster->hide(); canvas_gl->show(); canvas = canvas_gl; } need_createGL = false; } void Graphic::timerEvent(QTimerEvent * e) { if (e->timerId() == timer_pause) { pause_phase += 0.02; if (pause_phase > 1.) pause_phase -= 1.; repaintCanvas(); } if (e->timerId() == timer_record) { QPixmap im(canvas->size()); canvas->render(&im); record_imgs << im.toImage().convertToFormat(QImage::Format_Indexed8); } } bool Graphic::eventFilter(QObject * o, QEvent * e) { if (o == canvas) { switch (e->type()) { case QEvent::Gesture: if (!navigation || !gestures) break; foreach(QGesture * g, ((QGestureEvent *)e)->gestures()) procGesture(g); break; case QEvent::KeyPress: { int k = ((QKeyEvent *)e)->key(); if ((k == Qt::Key_Back || k == Qt::Key_Escape) && fullscr) { leaveFullscreen(); return true; } } break; case QEvent::TouchBegin: if (!navigation || !gestures) break; need_mouse_pan = true; break; case QEvent::TouchUpdate: { if (!navigation || !gestures) break; QList tpl = #if QT_VERSION_MAJOR <= 5 ((QTouchEvent *)e)->touchPoints(); #else ((QTouchEvent *)e)->points(); #endif if (tpl.size() == 2) { need_mouse_pan = false; QPointF dp = #if QT_VERSION_MAJOR <= 5 tpl[0].scenePos() - tpl[1].scenePos(); #else tpl[0].scenePosition() - tpl[1].scenePosition(); #endif gesture_angle = rad2deg_qpie * qAtan2(qAbs(dp.y()), qAbs(dp.x())); } } break; default: break; } } if (e->type() == QEvent::ContextMenu) { action_source = qobject_cast(o); } return QFrame::eventFilter(o, e); } void Graphic::prepareCanvas(QWidget * w) { connect(w, SIGNAL(mouseMoveEvent(QMouseEvent *)), this, SLOT(canvasMouseMoveEvent(QMouseEvent *))); connect(w, SIGNAL(mousePressEvent(QMouseEvent *)), this, SLOT(canvasMousePressEvent(QMouseEvent *))); connect(w, SIGNAL(mouseReleaseEvent(QMouseEvent *)), this, SLOT(canvasMouseReleaseEvent(QMouseEvent *))); connect(w, SIGNAL(mouseDoubleClickEvent(QMouseEvent *)), this, SLOT(canvasMouseDoubleClickEvent(QMouseEvent *))); connect(w, SIGNAL(wheelEvent(QWheelEvent *)), this, SLOT(canvasWheelEvent(QWheelEvent *))); connect(w, SIGNAL(leaveEvent(QEvent *)), this, SLOT(canvasLeaveEvent(QEvent *))); w->grabGesture(Qt::TapAndHoldGesture); w->grabGesture(Qt::PanGesture); w->grabGesture(Qt::PinchGesture); w->setMouseTracking(true); w->installEventFilter(this); } void Graphic::procGesture(QGesture * g) { if (!g) return; switch (g->gestureType()) { case Qt::PanGesture: { if (need_mouse_pan) break; QPanGesture * pg = (QPanGesture *)g; QPointF dp = -pg->delta(); dp.rx() /= getScaleX(); dp.ry() /= getScaleY(); dp.ry() = -dp.y(); selrect.translate(dp); totalUpdate(); } break; case Qt::PinchGesture: { QPinchGesture * pg = (QPinchGesture *)g; Qt::KeyboardModifiers km = Qt::NoModifier; if (gesture_angle <= 20.) km = Qt::ControlModifier; if (gesture_angle >= 70.) km = Qt::ShiftModifier; QPoint cp = pg->centerPoint().toPoint(); if (!fullscr) cp = mapFromGlobal(cp); procZoom(cp, (pg->scaleFactor() - 1.) * 500., km); totalUpdate(); } break; case Qt::TapAndHoldGesture: { QTapAndHoldGesture * pg = (QTapAndHoldGesture *)g; if (pg->state() == Qt::GestureStarted) QMetaObject::invokeMethod(this, [this]() { showMenu(); }, Qt::QueuedConnection); } break; default: break; } } void Graphic::procZoom(QPointF view_center, double dzoom, Qt::KeyboardModifiers km) { double scl, wid = canvas->width() - gridborder.x() - margins_.width() - margins_.left(), hei = canvas->height() - gridborder.y() - margins_.height() - margins_.top(); double px = view_center.x() - gridborder.x() - margins_.left(), py = hei - view_center.y() + margins_.height(); px = px / wid * selrect.width() + selrect.x(); py = py / hei * selrect.height() + selrect.y(); scl = 1. - dzoom / 500.; if (km == Qt::NoModifier) selrect.setRect(px - (px - selrect.x()) * scl, py - (py - selrect.y()) * scl, selrect.width() * scl, selrect.height() * scl); else { if (km == Qt::ControlModifier) selrect.setRect(px - (px - selrect.x()) * scl, selrect.y(), selrect.width() * scl, selrect.height()); if (km == Qt::ShiftModifier) selrect.setRect(selrect.x(), py - (py - selrect.y()) * scl, selrect.width(), selrect.height() * scl); if (km == Qt::AltModifier) selrect.translate((dzoom > 0. ? 1. : -1.) * selrect.width() / 2., 0.); } } void Graphic::totalUpdate() { isFit = false; emit visualRectChanged(); repaintCanvas(true); setRectToLines(); } void Graphic::canvasPaintEvent() { if (is_lines_update) return; int wid = canvas->width(), hei = canvas->height(); if (canvas->isHidden() || wid <= 1 || hei <= 1) return; if (!buffer.isNull()) { if (lastw != wid || lasth != hei) buffer = QPixmap(); } if (buffer.isNull()) { #if QT_VERSION_MAJOR >= 5 const qreal dpr = canvas->devicePixelRatio(); buffer = QPixmap(canvas->size() * dpr); buffer.setDevicePixelRatio(dpr); #else buffer = QPixmap(canvas->size()); #endif } lastw = wid; lasth = hei; font_sz = fontMetrics().size(0, "0"); font_sz.setHeight(font_sz.height() * 1.); #ifdef Q_OS_ANDROID font_sz.setWidth(font_sz.width() * 6); #else font_sz.setWidth(font_sz.width() * 8); #endif thick = lineThickness(this); #if QT_VERSION_MAJOR >= 5 thick *= canvas->devicePixelRatio(); #endif if (bufferActive) { QPainter p(canvas); p.drawPixmap(QPoint(), buffer); painter = &p; fp_size.clear(); if (curpos != startpos) drawAction(); drawGuides(); return; } QPainter p; #ifdef HAS_GL if (isOGL && !m_fakeGL) { p.fillRect(canvas->rect(), Qt::black); glClearColor(0.f, 0.f, 0.f, 0.f); p.begin(canvas); } else #endif p.begin(&buffer); p.fillRect(canvas->rect(), back_color); painter = &p; p.setFont(font()); gridborder = QPoint(5, 5); if (grid) { gridborder += QPoint(font_sz.width(), font_sz.height()); if (hasLblY) gridborder += QPoint(font_sz.height(), 0); if (hasLblX) gridborder += QPoint(0, font_sz.height()); if (axis_type_x == DateTime) gridborder += QPoint(0, font_sz.height() * 2); } painter->setClipping(true); painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y())); emit beforeGraphicPaintEvent(painter); painter->setClipping(false); if (grid) drawGrid(); p.setRenderHint(QPainter::Antialiasing, aalias); #ifdef HAS_GL if (isOGL && !m_fakeGL) { if (aalias) glEnable(GL_MULTISAMPLE); else glDisable(GL_MULTISAMPLE); } #endif fp_size.clear(); if (!aalias) p.translate(-0.5, -0.5); drawGraphics(); drawGuides(); if (pause_) drawPause(); emit graphicPaintEvent(painter); p.end(); if (isOGL && !m_fakeGL) return; p.begin(canvas); p.drawPixmap(QPoint(), buffer); p.end(); } void Graphic::canvasMouseMoveEvent(QMouseEvent * e) { isHover = true; curpos = e->pos(); curpos_r = canvas2real(curpos); QPointF dp; emit graphicMouseMoveEvent(curpos_r, e->buttons()); if (e->buttons() == Qt::NoButton) { if (ui->status->isVisible()) ui->status->setText(tr("Cursor: ") + pointCoords(curpos_r)); if (guides) repaintCanvas(); return; } if (!navigation) return; if (gestures) { if (!need_mouse_pan) return; curaction = gaMove; } else if (curaction != gaMove && (e->buttons() & Qt::RightButton) == Qt::RightButton) return; switch (curaction) { case gaZoomInRect: if (ui->status->isVisible()) { ui->status->setText(tr("Selection") + ": " + pointCoords(startpos_r) + " -> " + pointCoords(curpos_r) + ", " + tr("Size") + ": " + pointCoords(absPoint(curpos_r - startpos_r))); } repaintCanvas(true); break; case gaZoomRangeX: if (ui->status->isVisible()) { ui->status->setText(tr("Range") + ": " + QString::number(startpos_r.x(), 'f', 3) + " -> " + QString::number(curpos_r.x(), 'f', 3) + ", " + tr("Length") + ": " + QString::number(qAbs(curpos_r.x() - startpos_r.x()), 'f', 3)); } repaintCanvas(true); break; case gaZoomRangeY: if (ui->status->isVisible()) { ui->status->setText(tr("Range") + ": " + QString::number(startpos_r.y(), 'f', 3) + " -> " + QString::number(curpos_r.y(), 'f', 3) + ", " + tr("Length") + ": " + QString::number(qAbs(curpos_r.y() - startpos_r.y()), 'f', 3)); } repaintCanvas(true); break; case gaMove: dp = e->pos() - prevpos; dp.rx() *= selrect.width() / double(gridborder.x() + 5 - lastw); dp.ry() *= selrect.height() / double(lasth - gridborder.y() - 5); if (e->modifiers() == Qt::ControlModifier) dp.setY(0.); if (e->modifiers() == Qt::ShiftModifier) dp.setX(0.); selrect.translate(dp); totalUpdate(); break; default: break; } prevpos = e->pos(); } void Graphic::canvasMousePressEvent(QMouseEvent * e) { emit graphicMousePressEvent(canvas2real(QPointF(e->pos())), e->button()); if (!navigation) return; if (gestures && !need_mouse_pan) return; setGuidesCursor(); prevpos = e->pos(); startpos = prevpos; startpos_r = canvas2real(startpos); if (cancel || gestures) return; if (e->button() == QT_MID_BUTTON) curaction = gaMove; if (e->button() == Qt::RightButton) { if (bufferActive) { curpos = startpos; curpos_r = canvas2real(curpos); repaintCanvas(true); swapToNormal(); cancel = true; return; } else { prevaction = curaction; setCurrentAction(gaMove); return; } } if (e->button() == Qt::LeftButton) { if (e->modifiers() == Qt::ControlModifier) curaction = gaZoomRangeX; else if (e->modifiers() == Qt::ShiftModifier) curaction = gaZoomRangeY; else curaction = gaZoomInRect; switch (curaction) { case gaZoomInRect: case gaZoomRangeX: case gaZoomRangeY: swapToBuffer(); break; default: break; } } setCurrentAction(curaction); } void Graphic::canvasMouseReleaseEvent(QMouseEvent * e) { emit graphicMouseReleaseEvent(canvas2real(QPointF(e->pos())), e->button()); if (gestures) return; need_mouse_pan = false; if (!navigation) return; setGuidesCursor(); QPointF tlp, brp; QRect sr; sr = QRect(startpos, curpos).normalized(); if (cancel) { if (e->buttons() == Qt::NoButton) cancel = false; return; } if (e->button() == Qt::RightButton && curaction == gaMove) { curaction = prevaction; return; } if (e->button() == Qt::LeftButton && (e->buttons() & Qt::RightButton) != Qt::RightButton) { if (curpos != startpos) { tlp = canvas2real(sr.topLeft()); brp = canvas2real(sr.bottomRight()); isFit = false; switch (curaction) { case gaZoomInRect: if (sr.width() <= 1 || sr.height() <= 1) break; selrect.setCoords(tlp.x(), brp.y(), brp.x(), tlp.y()); setRectToLines(); break; case gaZoomRangeX: if (sr.width() <= 1) break; findGraphicsRect(tlp.x(), brp.x()); break; case gaZoomRangeY: if (sr.height() <= 1) break; findGraphicsRect(0., 0., brp.y(), tlp.y()); break; default: return; } } swapToNormal(); repaintCanvas(true); } if (ui->status->isVisible()) { ui->status->setText(tr("Cursor") + ": " + pointCoords(canvas2real(QPointF(e->pos())))); } emit visualRectChanged(); } void Graphic::canvasMouseDoubleClickEvent(QMouseEvent *) { if (!navigation) return; autofit(); } void Graphic::canvasWheelEvent(QWheelEvent * e) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) emit graphicWheelEvent(canvas2real(e->position()), e->angleDelta().y()); #else emit graphicWheelEvent(canvas2real(QPointF(e->pos())), e->delta()); #endif if (gestures) return; if (!navigation) return; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) procZoom(e->position(), e->modifiers() == Qt::AltModifier ? e->angleDelta().x() : e->angleDelta().y(), e->modifiers()); #else procZoom(e->pos(), e->delta(), e->modifiers()); #endif totalUpdate(); } void Graphic::zoom(float factor) { double wid = canvas->width() - gridborder.x() - margins_.width() - margins_.left(), hei = canvas->height() - gridborder.y() - margins_.height() - margins_.top(); double px = wid / 2, py = hei / 2; px = px / wid * selrect.width() + selrect.x(); py = py / hei * selrect.height() + selrect.y(); selrect.setRect(px - (px - selrect.x()) * factor, py - (py - selrect.y()) * factor, selrect.width() * factor, selrect.height() * factor); isFit = false; repaintCanvas(true); setRectToLines(); } void Graphic::fullscreen() { if (fullscr) leaveFullscreen(); else enterFullscreen(); } void Graphic::canvasLeaveEvent(QEvent *) { isHover = false; if (guides) repaintCanvas(true); if (ui->status->isVisible()) ui->status->setText(tr("Cursor") + ": ( ; )"); } void Graphic::clear() { for (int i = 0; i < graphics.size(); ++i) { graphics[i].polyline.clear(); graphics[i].polyline_pause.clear(); graphics[i]._lod.clear(); graphics[i]._lod_pause.clear(); graphics[i].max_x = 0.; graphics[i].cvrect = QRectF(); } if (isFit) autofit(); } void Graphic::setAntialiasing(bool enabled) { if (aalias == enabled) return; aalias = enabled; repaintCanvas(); } void Graphic::setAutoUpdate(bool enabled) { aupdate = enabled; } void Graphic::setPaused(bool yes) { pause_ = yes; ui->graphic_checkPause->blockSignals(true); ui->graphic_checkPause->setChecked(yes); ui->graphic_checkPause->blockSignals(false); for (int i = 0; i < graphics.size(); ++i) graphics[i].cvrect = QRectF(); if (!pause_) { killTimer(timer_pause); timer_pause = 0; repaintCanvas(true); return; } for (int i = 0; i < graphics.size(); ++i) { graphics[i].polyline_pause = graphics[i].polyline; graphics[i].polyline_pause.detach(); graphics[i]._lod_pause = graphics[i]._lod; graphics[i]._lod_pause.detach(); graphics[i].max_x_pause = graphics[i].max_x; } timer_pause = startTimer(40); } void Graphic::setHistorySize(double val) { history = val; double x; for (int i = 0; i < graphics.size(); ++i) { QPolygonF & pol(graphics[i].polyline); if (pol.isEmpty() || history <= 0.) continue; graphics[i].cvrect = QRectF(); x = pol.back().x() - history; for (int j = pol.size() - 2; j >= 0; --j) if (pol[j].x() < x) { pol.erase(pol.begin(), pol.begin() + j); break; } } } void Graphic::setMaxVisibleTime(double val) { visible_time = val; if (isFit) autofit(); } void Graphic::setAutoXIncrement(double val) { inc_x = val; } void Graphic::setLimit(const QRectF & val) { limit_ = val; } void Graphic::setMargins(const QRect & val) { margins_ = val; repaintCanvas(); } void Graphic::setMargins(int left_, int right_, int top_, int bottom_) { setMargins(QRect(left_, bottom_, right_, top_)); } void Graphic::setLeftMargin(int value) { margins_.moveLeft(value); setMargins(margins_); } void Graphic::setRightMargin(int value) { margins_.setWidth(value); setMargins(margins_); } void Graphic::setTopMargin(int value) { margins_.setHeight(value); setMargins(margins_); } void Graphic::setBottomMargin(int value) { margins_.moveTop(value); setMargins(margins_); } void Graphic::setMinimumRepaintInterval(const int & val) { min_repaint_int = val; } void Graphic::setOnlyExpandY(bool yes) { ui->graphic_actionExpandY->setChecked(yes); } void Graphic::setOnlyExpandX(bool yes) { ui->graphic_actionExpandX->setChecked(yes); } void Graphic::setGesturesNavigation(bool yes) { gestures = yes; } Graphic::GraphicsData Graphic::graphicsData() const { GraphicsData ret; ret.resize(graphics.size()); for (int i = 0; i < graphics.size(); ++i) ret[i] = graphics[i].polyline; return ret; } QByteArray Graphic::graphicsDataRaw() const { QByteArray ret; QDataStream s(&ret, QIODevice::WriteOnly); s << graphicsData(); return ret; } void Graphic::setGraphicsData(const Graphic::GraphicsData & gd) { setGraphicsCount(gd.size()); for (int i = 0; i < gd.size(); ++i) { setGraphicData(gd[i], i, false); } updateGraphics(); } void Graphic::setGraphicsDataRaw(const QByteArray & ba) { if (ba.isEmpty()) { clear(); return; } Graphic::GraphicsData gd; QDataStream s(ba); s >> gd; setGraphicsData(gd); } void Graphic::setGridNumbersMultiplierX(double value) { grid_numbers_x = value; updateGraphics(); } void Graphic::setGridNumbersMultiplierY(double value) { grid_numbers_y = value; updateGraphics(); } void Graphic::setGraduationX(Graduation value) { grad_x = value; updateGraphics(); } void Graphic::setGraduationY(Graduation value) { grad_y = value; updateGraphics(); } void Graphic::setGraduationStepX(double sx) { gridx = sx; updateGraphics(); } void Graphic::setGraduationStepY(double sy) { gridy = sy; if (aupdate) repaintCanvas(); } void Graphic::setGraduationSteps(double sx, double sy) { gridx = sx; gridy = sy; if (aupdate) repaintCanvas(); } void Graphic::setAxisType(AxisType t) { axis_type_x = t; if (aupdate) repaintCanvas(); } void Graphic::setFloatingAxisType(FloatingAxisType t) { floating_axis_type = t; setGuidesCursor(); if (aupdate) repaintCanvas(); } void Graphic::setFloatingAxisEnabled(bool on) { ui->graphic_checkGuides->setChecked(on); } void Graphic::setButtons(Graphic::Buttons b) { buttons_ = b; ui->graphic_buttonAutofit->setVisible(b.testFlag(Autofit)); ui->graphic_checkGuides->setVisible(b.testFlag(CursorAxis)); ui->graphic_buttonFullscreen->setVisible(b.testFlag(Fullscreen)); ui->graphic_checkBorderInputs->setVisible(b.testFlag(BorderInputs)); ui->graphic_checkLegend->setVisible(b.testFlag(Legend)); ui->graphic_buttonClear->setVisible(b.testFlag(Clear)); ui->graphic_buttonConfigure->setVisible(b.testFlag(Configure)); ui->graphic_buttonSaveMenu->setVisible(b.testFlag(Save) || b.testFlag(Export)); ui->graphic_actionExportCSV->setVisible(b.testFlag(Export)); ui->graphic_actionExportCSV_View->setVisible(b.testFlag(Export)); ui->graphic_actionSaveImage->setVisible(b.testFlag(Save)); ui->graphic_buttonClose->setVisible(b.testFlag(Close)); ui->graphic_checkPause->setVisible(b.testFlag(Pause)); ui->graphic_buttonRecord->setVisible(b.testFlag(Record)); if (ui->graphic_buttonAutofit->isVisible() || ui->graphic_checkGuides->isVisible() || ui->graphic_buttonConfigure->isVisible() || ui->graphic_checkPause->isVisible()) { ui->verticalSpacer->changeSize(0, 30, QSizePolicy::Preferred, QSizePolicy::Preferred); } else { ui->verticalSpacer->changeSize(0, 0, QSizePolicy::Preferred, QSizePolicy::Preferred); } ui->layoutButtons->update(); } void Graphic::setButtonsPosition(Graphic::Alignment a) { align = a; ui->widgetLeft->hide(); ui->widgetRight->hide(); #ifdef NO_BUTTONS return; #endif switch (a) { case Graphic::Left: ui->widgetLeft->setLayout(ui->layoutButtons); ui->widgetLeft->show(); break; case Graphic::Right: ui->widgetRight->setLayout(ui->layoutButtons); ui->widgetRight->show(); break; } } void Graphic::addPoint(const QPointF & p, int graphic, bool update_) { if (!checkGraphicIndex(graphic)) return; GraphicType & t(graphics[graphic]); if (!t.cvrect.isNull() && !pause_) { if (t.cvrect.top() < p.y()) t.cvrect.setTop(p.y()); if (t.cvrect.bottom() > p.y()) t.cvrect.setBottom(p.y()); if (t.cvrect.right() < p.x()) t.cvrect.setRight(p.x()); if (t.cvrect.left() > p.x()) t.cvrect.setLeft(p.x()); } if (t.polyline.size() == 0) t.max_x = p.x(); if (t.max_x < p.x()) t.max_x = p.x(); t.polyline << p; tick(graphic, true, update_); } void Graphic::addPoint(const QPointF & p, bool update) { addPoint(p, curGraphic, update); } void Graphic::addPoint(double x, double y, int graphic, bool update) { addPoint(QPointF(x, y), graphic, update); } void Graphic::addPoint(double x, double y, bool update) { addPoint(QPointF(x, y), update); } void Graphic::addPoint(double y, int graphic, bool update) { if (!checkGraphicIndex(graphic)) return; if (graphics[graphic].polyline.isEmpty()) { addPoint(QPointF(0.0, y), graphic, update); } else { addPoint(QPointF(graphics[graphic].max_x + inc_x, y), graphic, update); } } void Graphic::addPoint(double y, bool update) { addPoint(y, curGraphic, update); } void Graphic::addPoints(const QPolygonF & pts, int graphic, bool update_) { if (!checkGraphicIndex(graphic)) return; if (pts.isEmpty()) return; GraphicType & t(graphics[graphic]); if (!t.cvrect.isNull() && !pause_) { for (const QPointF & p: pts) { if (t.cvrect.top() < p.y()) t.cvrect.setTop(p.y()); if (t.cvrect.bottom() > p.y()) t.cvrect.setBottom(p.y()); if (t.cvrect.right() < p.x()) t.cvrect.setRight(p.x()); if (t.cvrect.left() > p.x()) t.cvrect.setLeft(p.x()); } } if (t.polyline.size() == 0) t.max_x = pts.at(0).x(); for (const QPointF & p: pts) { if (t.max_x < p.x()) t.max_x = p.x(); } t.polyline << pts; tick(graphic, true, update_); } void Graphic::addPoints(const QPolygonF & pts, bool update) { addPoints(pts, curGraphic, update); } void Graphic::addPoints(const QVector & pts, int graphic, bool update_) { if (!checkGraphicIndex(graphic)) return; QPolygonF ps; ps.reserve(pts.size()); double stx = 0; if (!graphics[graphic].polyline.isEmpty()) stx = graphics[graphic].max_x; for (int i = 0; i < pts.size(); ++i) { ps << QPointF(stx + i * inc_x, pts[i]); } addPoints(ps, graphic, update_); } void Graphic::addPoints(const QVector & pts, bool update) { addPoints(pts, curGraphic, update); } void Graphic::setGraphicData(const QVector & g, int graphic, bool update_) { if (!checkGraphicIndex(graphic)) return; GraphicType & t(graphics[graphic]); t.cvrect = QRectF(); t.polyline.clear(); t.polyline = g; if (t.polyline.size() == 0) { t.max_x = 0.; tick(graphic, false, update_); return; } t.max_x = t.polyline[0].x(); for (const auto & p: t.polyline) { if (t.max_x < p.x()) t.max_x = p.x(); } tick(graphic, false, update_); } void Graphic::setGraphicData(const QVector & g) { setGraphicData(g, curGraphic); } void Graphic::setGraphicProperties(const QString & name, const QColor & color, Qt::PenStyle style, double width, bool visible) { setGraphicProperties(curGraphic, name, color, style, width, visible); } void Graphic::setGraphicProperties(int graphic, const QString & name, const QColor & color, Qt::PenStyle style, double width, bool visible) { if (!checkGraphicIndex(graphic)) return; graphics[graphic].name = name; graphics[graphic].pen.setColor(color); graphics[graphic].pen.setStyle(style); graphics[graphic].pen.setWidth(width); graphics[graphic].visible = visible; updateLegend(); } void Graphic::addGraphic(const QString & name, const QColor & color, Qt::PenStyle style, double width, bool visible) { addGraphic(GraphicType(name, color, style, width, visible)); } void Graphic::addGraphic(const GraphicType & gd) { graphics << gd; updateLegend(); repaintCanvas(); } void Graphic::setVisualRect(const QRectF & rect) { selrect = rect; isFit = false; repaintCanvas(); } void Graphic::setDefaultRect(const QRectF & rect) { def_rect = rect; if (isFit) autofit(); } void Graphic::autofit() { isFit = true; bool isEmpty = true; for (const GraphicType & t: graphics) { const QPolygonF & pol(pause_ ? t.polyline_pause : t.polyline); if (!pol.isEmpty()) { isEmpty = false; break; } } if (isEmpty) grect = def_rect; selrect = grect; findGraphicsRect(); repaintCanvas(); } void Graphic::saveImage(QString filename) { ppath = filename; QPixmap im; QString custom_title; if (func_image_title) custom_title = func_image_title(); if (custom_title.isEmpty()) { im = QPixmap(canvas->size()); canvas->render(&im); } else { QSize sz = canvas->size(); QRectF tbr = fontMetrics().boundingRect(QRect(0, 0, sz.width(), 1), Qt::TextWordWrap, custom_title); sz.setHeight(sz.height() + tbr.height()); im = QPixmap(canvas->size()); im.fill(back_color); QPainter p(&im); p.setPen(text_color); p.drawText(tbr, Qt::TextWordWrap, custom_title); p.end(); canvas->render(&im, QPoint(0, tbr.height())); } im.save(ppath); } void Graphic::exportGraphics(QString filename, QChar decimal_point, bool view_only) { ppath = filename; QFile f(filename); if (!f.open(QIODevice::ReadWrite)) { QMessageBox::critical(this, tr("Export graphics"), tr("Can`t open file \"%1\"!").arg(filename)); return; } QApplication::setOverrideCursor(Qt::WaitCursor); f.resize(0); QTextStream ts(&f); ts << "#"; for (int i = 0; i < graphics.size(); ++i) { GraphicType & g(graphics[i]); if (!g.visible) continue; ts << ";" << (g.name + " " + (label_x.isEmpty() ? "X" : label_x)) << ";" << (g.name + " " + (label_y.isEmpty() ? "Y" : label_y)); } ts << "\n"; QVector view_gr; auto * source = &graphics; if (view_only) { for (const auto & g: graphics) { GraphicType gt; gt.visible = g.visible; gt.polyline.reserve(g.polyline.size() / 8); for (const auto & i: g.polyline) { if (selrect.contains(i)) gt.polyline << i; } view_gr << gt; } source = &view_gr; } bool has_data = true; int ind = 0; QString line; while (has_data) { has_data = false; line.clear(); line += QString::number(ind + 1); for (int i = 0; i < source->size(); ++i) { const GraphicType & g(source->at(i)); if (!g.visible) continue; if (ind >= g.polyline.size()) { line += ";;"; continue; } has_data = true; line += ";"; if (func_gridMarkX) { line += func_gridMarkX(g.polyline[ind].x()).replace('.', decimal_point); } else { line += QString::number(g.polyline[ind].x(), 'g', 15).replace('.', decimal_point); } line += ";"; if (func_gridMarkY) { line += func_gridMarkY(g.polyline[ind].y()).replace('.', decimal_point); } else { line += QString::number(g.polyline[ind].y(), 'g', 12).replace('.', decimal_point); } } ++ind; line += "\n"; if (has_data) ts << line; } QApplication::restoreOverrideCursor(); } void Graphic::setOpenGL(bool on) { #ifdef HAS_GL isOGL = on; need_createGL = false; if (on && !m_fakeGL) { if (!canvas_gl) { if (!isVisible()) need_createGL = true; else { // qDebug() << "create GL on setter"; canvas_gl = new UGLWidget(); ui->layoutCanvas->addWidget(canvas_gl); connect(canvas_gl, &UGLWidget::paintSignal, this, &Graphic::canvasPaintEvent); prepareCanvas(canvas_gl); } } if (canvas_gl) { ui->canvas_raster->hide(); canvas_gl->show(); canvas = canvas_gl; } } else { if (canvas_gl) canvas_gl->hide(); ui->canvas_raster->show(); canvas = ui->canvas_raster; } #else isOGL = false; ui->canvas_raster->show(); canvas = ui->canvas_raster; #endif repaintCanvas(); } void Graphic::update() { repaintCanvas(); } void Graphic::updateGraphics() { findGraphicsRect(); repaintCanvas(true); } void Graphic::setCurrentGraphic(int arg) { if (arg < 0 || arg >= graphics.size()) return; curGraphic = arg; } #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) # define _G_QRAND_ QRandomGenerator::global()->generate() #else # define _G_QRAND_ qrand() #endif void Graphic::setGraphicsCount(int count, bool update) { if (count < 0) return; bool changed = false; while (graphics.size() < count) { GraphicType gt(tr("y(x)")); if (loaded_configs.size() > graphics.size()) gt = loaded_configs[graphics.size()]; else gt.pen.setColor(QColor::fromHsv((graphics.size() * 55) % 360, 255, 255 - _G_QRAND_ % 115)); graphics.append(gt); changed = true; } while (graphics.size() > count) { if (action_source == graphics.back().pb) action_source = nullptr; graphics.back().destroy(); graphics.pop_back(); changed = true; } if (update && changed) updateLegend(); } #undef _G_QRAND_ void Graphic::removeGraphic(int arg, bool update) { if (arg < 0 || arg >= graphics.size()) return; graphics[arg].destroy(); graphics.remove(arg); if (update) updateLegend(); } void Graphic::setCustomGridMarkFuncs(std::function fx, std::function fy) { func_gridMarkX = fx; func_gridMarkY = fy; } void Graphic::setCustomSaveImageTitleFunc(std::function func) { func_image_title = func; } void Graphic::findGraphicsRect(double start_x, double end_x, double start_y, double end_y) { double cx, cy, maxX, minX, maxY, minY, vx; bool isRangeX = (start_x != end_x), isRangeY = (start_y != end_y); bool can_fast = (start_x == 0 && end_x == 0 && start_y == 0 && end_y == 0); bool anyVisible = false, isTimeLimit = (visible_time > 0.) && !(isRangeX || isRangeY); vx = -std::numeric_limits::max(); minY = minX = std::numeric_limits::max(); maxY = maxX = -std::numeric_limits::max(); foreach(const GraphicType & t, graphics) { if (!t.visible) continue; if (vx < (pause_ ? t.max_x_pause : t.max_x)) vx = (pause_ ? t.max_x_pause : t.max_x); } vx -= visible_time; for (int g = 0; g < graphics.size(); g++) { GraphicType & t(graphics[g]); if (!t.visible) continue; const QPolygonF & pol(pause_ ? t.polyline_pause : t.polyline); if (pol.isEmpty()) continue; bool f = true; if (t.cvrect.isNull() || !can_fast) { for (int i = 0; i < pol.size(); i++) { cx = pol[i].x(); cy = pol[i].y(); if ((start_x > cx || end_x < cx) && isRangeX) continue; if ((start_y > cy || end_y < cy) && isRangeY) continue; if ((cx < vx) && isTimeLimit) continue; if (f) { t.cvrect.setRect(cx, cy, 0, 0); f = false; } else { if (t.cvrect.top() < cy) t.cvrect.setTop(cy); if (t.cvrect.bottom() > cy) t.cvrect.setBottom(cy); if (t.cvrect.right() < cx) t.cvrect.setRight(cx); if (t.cvrect.left() > cx) t.cvrect.setLeft(cx); } } if (f) continue; } anyVisible = true; if (maxY < t.cvrect.top()) maxY = t.cvrect.top(); if (minY > t.cvrect.bottom()) minY = t.cvrect.bottom(); if (maxX < t.cvrect.right()) maxX = t.cvrect.right(); if (minX > t.cvrect.left()) minX = t.cvrect.left(); if (!can_fast) t.cvrect = QRectF(); } if (!anyVisible) { grect = def_rect; setRectToLines(); return; } if (maxX > limit_.right()) maxX = limit_.right(); if (minX > limit_.right()) minX = limit_.right(); if (minX < limit_.left()) minX = limit_.left(); if (maxX < limit_.left()) maxX = limit_.left(); if (maxY > limit_.bottom()) maxY = limit_.bottom(); if (minY > limit_.bottom()) minY = limit_.bottom(); if (minY < limit_.top()) minY = limit_.top(); if (maxY < limit_.top()) maxY = limit_.top(); if (minX > maxX) qSwap(minX, maxX); if (minY > maxY) qSwap(minY, maxY); if (qAbs(minX - maxX) < 1E-60) { minX -= defaultRect().width() / 2; maxX += defaultRect().width() / 2; } if (qAbs(minY - maxY) < 1E-60) { minY -= defaultRect().height() / 2; maxY += defaultRect().height() / 2; } if (only_expand_x) { if (minX > eminx) minX = eminx; if (maxX < emaxx) maxX = emaxx; } if (only_expand_y) { if (minY > eminy) minY = eminy; if (maxY < emaxy) maxY = emaxy; } eminx = minX; emaxx = maxX; eminy = minY; emaxy = maxY; if (isRangeX) selrect.setRect(start_x, minY, end_x - start_x, maxY - minY); else if (isRangeY) selrect.setRect(minX, start_y, maxX - minX, end_y - start_y); else grect.setRect(minX, minY, maxX - minX, maxY - minY); grect = grect.normalized(); if (isFit) { if (visible_time > 0.) { if (grect.width() > visible_time) grect.setLeft(grect.right() - visible_time); } selrect = grect; } setRectToLines(); } void Graphic::drawAction() { int wid = canvas->width(), hei = canvas->height() - gridborder.y(), sx = startpos.x(), sy = startpos.y(), cx = curpos.x(), cy = curpos.y(); painter->setPen(selpen); painter->setBrush(selbrush); switch (curaction) { case gaZoomInRect: { QSizeF rsz = QRectF(startpos_r, curpos_r).normalized().size(); painter->drawRect(QRect(startpos, curpos)); fp_size = " x " + pointCoords(QPointF(rsz.width(), rsz.height())); } break; case gaZoomRangeX: painter->drawLine(sx, hei, sx, 0); painter->drawLine(cx, hei, cx, 0); painter->fillRect(sx, 0, cx - sx, hei, selbrush); fp_size = " x " + pointCoords(QPointF(qAbs(startpos_r.x() - curpos_r.x()), 0.), true, false); break; case gaZoomRangeY: painter->drawLine(gridborder.x(), sy, wid, sy); painter->drawLine(gridborder.x(), cy, wid, cy); painter->fillRect(gridborder.x(), sy, wid - gridborder.x(), cy - sy, selbrush); fp_size = " x " + pointCoords(QPointF(0., qAbs(startpos_r.y() - curpos_r.y())), false, true); break; default: break; } } void Graphic::drawGrid() { int gbx = gridborder.x(), gby = gridborder.y(), cwid = canvas->width(), chei = canvas->height(); double px, py, range, step, start; int wid = cwid - gbx - 5, hei = chei - gby - 5, cx, cy, cnt, right = cwid + gbx; QRect rect; QPair str; range = selrect.bottom() - selrect.top(); if (grad_y == Graphic::Auto) step = splitRange(range, hei / gridy / font_sz.height() / 1.4); else step = gridy; start = roundTo(canvas2realY(-hei), step) - step; py = start + step; cy = 0; cx = gbx - 5; grid_pen.setWidth(qMax(qMax(qRound(thick / 1.4), 1), grid_pen.width())); #if QT_VERSION_MAJOR >= 5 grid_pen.setCosmetic(true); #endif QFont sf = font(); QFont nf = sf; sf.setPointSizeF(qMax(sf.pointSizeF() / 1.6, 7.)); QFontMetrics fm(nf), sfm(sf); if (step > 0.) { cnt = 1000; while (cnt-- > 0) { py -= step; if (fabs(py) < step * .5) py = 0.; cy = real2canvasY(py); if (cy < 0) continue; if (cy > hei + 5) break; painter->setPen(grid_pen); painter->drawLine(gbx, cy, cwid, cy); painter->setPen(text_color); cy += font_sz.height() / 4.; int dx = font_sz.height() / 8.; if (func_gridMarkY) { str.first = func_gridMarkY(py * grid_numbers_y); str.second.clear(); } else { str = gridMark(py * grid_numbers_y); if (!str.second.isEmpty()) { rect = sfm.boundingRect(str.second); painter->setFont(sf); painter->drawText(cx - rect.width() - dx, cy - font_sz.height() / 2.5, str.second); dx += rect.width() + font_sz.height() / 6.; } } rect = fm.boundingRect(str.first); painter->setFont(nf); painter->drawText(cx - rect.width() - dx, cy, str.first); } } cy = real2canvasY(0.); if (cy >= 0 && cy <= (hei + 5)) { QPen _p(grid_pen); _p.setStyle(Qt::SolidLine); painter->setPen(_p); painter->drawLine(gbx, cy, cwid, cy); } if (hasLblY) { painter->setPen(text_color); painter->save(); painter->translate(5, hei); painter->rotate(-90.); painter->drawText(0, 0, hei, font_sz.height(), Qt::AlignCenter, label_y); painter->restore(); } cy = chei - font_sz.height() / 4; if (hasLblX) cy -= font_sz.height(); range = selrect.right() - selrect.left(); QString df; if (axis_type_x == Graphic::Numeric) { if (grad_x == Graphic::Auto) step = splitRange(range, wid / gridx / font_sz.width() * 1.4); else step = gridx; start = roundTo(canvas2realX(right), step); px = start + step; if (step > 0.) { cnt = 1000; while (cnt-- > 0) { px -= step; if (fabs(px) < step * .5) px = 0.; cx = real2canvasX(px); if (cx > right) continue; if (cx < gbx) break; painter->setPen(grid_pen); painter->drawLine(cx, hei + 5, cx, 0); painter->setPen(text_color); int dx = -font_sz.height() / 4.; painter->setFont(nf); if (func_gridMarkX) { str.first = func_gridMarkX(px * grid_numbers_x); str.second.clear(); } else { str = gridMark(px * grid_numbers_x); } rect = fm.boundingRect(str.first); painter->drawText(cx + dx, cy, str.first); if (!str.second.isEmpty()) { dx += rect.width() + font_sz.height() / 6.; rect = sfm.boundingRect(str.second); painter->setFont(sf); painter->drawText(cx + dx, cy - font_sz.height() / 4., str.second); } } } cx = real2canvasX(0.); if (cx <= right && cx >= gbx) { QPen _p(grid_pen); _p.setStyle(Qt::SolidLine); painter->setPen(_p); painter->drawLine(cx, hei + 5, cx, 0); } } else { int dt_add[DateComponentCount]; int dt_add_lo[DateComponentCount]; for (int i = 0; i < DateComponentCount; ++i) dt_add_lo[i] = dt_add[i] = 0; DateFormats formats; step = splitRangeDate(range, wid / gridx / font_sz.width() / 1.5, formats, dt_add); for (int i = 0; i < DateComponentCount - 1; ++i) dt_add_lo[i + 1] = dt_add[i]; bool is_years = formats.center.isEmpty(); if (step > 0.) { int up_y = cy - font_sz.height() * 2; int ce_y = cy - font_sz.height(); int lo_y = cy; int ddx = 0, pcx = 0; QDateTime cd = QDateTime::fromMSecsSinceEpoch(canvas2realX(gbx) * grid_numbers_x), cdp, cdc, cdl, cdlp; QString ds; // qDebug() << step << range << int(wid / gridx / font_sz.width() * 1.4) << cd; if (!is_years) { roundDateTime(cd, dt_add); cdp = cdl = cdlp = cd; roundDateTime(cdl, dt_add_lo); roundDateTime(cdlp, dt_add_lo); } else { cd.setTime(QTime(0, 0, 0)); cd.setDate(QDate(roundTo(cd.date().year(), dt_add[6]) - dt_add[6], 1, 1)); } // qDebug() << cd << cur_scl[0] << cur_scl[1] << cur_scl[2] << cur_scl[3] << cur_scl[4] << cur_scl[5] << cur_scl[6]; struct Anchor { int x_start, x_end; QDateTime date; }; QVector areas_ce, areas_lo; cnt = 1000; int area_start = gbx, area_start_lo = gbx; pcx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x); while (cnt-- > 0) { addDateTime(cd, dt_add); bool need_text = true; if (!is_years) { cdc = cd; roundDateTime(cdc, dt_add); cx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x); if (cx < gbx) continue; if (cdp != cdc) { int cxc = real2canvasX(cdc.toMSecsSinceEpoch() / grid_numbers_x); // qDebug() << cx << cxc << ddx << pcx; if ((qAbs(cxc - cx) < ddx) && (cx != cxc)) need_text = false; areas_ce << Anchor{area_start, qMin(cxc, right), cdp}; area_start = areas_ce.back().x_end; cd = cdl = cdp = cdc; cx = cxc; roundDateTime(cdl, dt_add_lo); if (cdlp != cdl) { areas_lo << Anchor{area_start_lo, qMin(cxc, right), cdlp}; cdlp = cdl; area_start_lo = areas_lo.back().x_end; } } else { ddx = (cx - pcx) * 0.95; } pcx = cx; } else { cx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x); if (cx < gbx) continue; } if (cx > right) break; painter->setPen(grid_pen); painter->drawLine(cx, hei + 5, cx, 0); if (need_text) { painter->setPen(text_color); int dx = -font_sz.height() / 4.; painter->setFont(nf); painter->drawText(cx + dx, up_y, cd.toString(formats.upper)); } } if (!is_years) { if (area_start < right) areas_ce << Anchor{area_start, right, cdc}; if (area_start_lo < right) areas_lo << Anchor{area_start_lo, right, cdl}; // qDebug() << areas_lo.size() << formats.upper << areas_lo[0].date; painter->setPen(grid_pen); for (const auto & a: areas_ce) { painter->drawLine(a.x_start, hei + 5, a.x_start, ce_y); } for (const auto & a: areas_lo) { painter->drawLine(a.x_start, ce_y, a.x_start, lo_y); } painter->setPen(text_color); painter->setFont(nf); auto pfm = painter->fontMetrics(); for (const auto & a: areas_ce) { ds = a.date.toString(formats.center); auto str_rect = pfm.boundingRect(ds); painter->drawText(a.x_start + (a.x_end - a.x_start - str_rect.width()) / 2, ce_y, ds); } for (const auto & a: areas_lo) { ds = a.date.toString(formats.lower); auto str_rect = pfm.boundingRect(ds); painter->drawText(a.x_start + (a.x_end - a.x_start - str_rect.width()) / 2, lo_y, ds); } } } } painter->setPen(text_color); painter->setFont(nf); if (hasLblX) { painter->setPen(text_color); painter->drawText(gbx, chei - font_sz.height(), wid, font_sz.height(), Qt::AlignCenter, label_x); } QPen outer_pen(grid_pen.color(), qMax(thick, grid_pen.width())); #if QT_VERSION_MAJOR >= 5 outer_pen.setCosmetic(true); #endif painter->setPen(outer_pen); painter->drawRect(gbx, -1, wid + 6, hei + 6); } QPair Graphic::gridMark(double v) const { QPair ret; if ((qAbs(v) >= 1E+4 || qAbs(v) <= 1E-4) && v != 0.) { int p = qFloor(qLn(qAbs(v)) / LN10); v /= qPow(10., p); if (v == 10.) { v = 1.; p += 1; } ret.first = QString::fromUtf8("%1ยท10").arg(v); ret.second = QString::number(p); } else ret.first = QString::number(v, 'g', 8); return ret; } void Graphic::fillDateFormats() { date_formats.clear(); QString tr_ms = tr("ms"), tr_s = tr("s"), tr_m = tr("m"), tr_h = tr("h"); auto trFunc = [&](const DateFormats & src) -> DateFormats { DateFormats ret; ret.upper = QString(src.upper).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h); ret.center = QString(src.center).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h); ret.lower = QString(src.lower).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h); return ret; }; date_formats << trFunc(DateFormats{"zzz '%1'", "h '%4' mm '%3' ss '%2'", "yyyy MMM dd(ddd)"}); date_formats << trFunc(DateFormats{"ss '%2'", "h '%4' mm '%3'", "yyyy MMM dd(ddd)"}); date_formats << trFunc(DateFormats{"mm '%3'", "h '%4'", "yyyy MMM dd(ddd)"}); date_formats << trFunc(DateFormats{"h '%4'", "dd(ddd)", "yyyy MMM"}); date_formats << trFunc(DateFormats{"dd(ddd)", "MMM", "yyyy"}); date_formats << trFunc(DateFormats{"MMM", "yyyy", ""}); date_formats << trFunc(DateFormats{"yyyy", "", ""}); } void Graphic::askForExport(bool view_only) { QString f = QFileDialog::getSaveFileName(this, tr("Export graphics"), ppath, "CSV(*.csv)"); if (f.isEmpty()) return; QStringList items; items << "." << ","; bool ok; QString item = QInputDialog::getItem(this, tr("Select decimal point"), tr("Decimal point:"), items, 0, false, &ok); if (ok && !item.isEmpty()) exportGraphics(f, item.front(), view_only); } bool Graphic::checkGraphicIndex(int index) const { if (index < 0 || index >= graphics.size()) return false; return true; } void Graphic::graphicVisibleChanged() { if (isFit) autofit(); else { repaintCanvas(); } emit graphicSettingsChanged(); } void Graphic::drawGraphics() { if (isHover && ui->status->isVisible()) { ui->status->setText(tr("Cursor: ") + pointCoords(canvas2real(QPointF(curpos)))); } QPointF srp = -selrect.topLeft(); double sclx, scly, wid = canvas->width(), hei = canvas->height(); int cwid = (wid - gridborder.x() - margins_.left() - margins_.width()); sclx = cwid / selrect.width(); scly = (hei - gridborder.y() - margins_.top() - margins_.height()) / selrect.height(); painter->setClipping(true); painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y())); painter->translate(gridborder.x() + margins_.left(), hei - gridborder.y() - margins_.top()); painter->scale(sclx, -scly); painter->translate(srp); QTransform mat = painter->transform(); painter->resetTransform(); painter->setWorldMatrixEnabled(false); QPen pen; for (int i = 0; i < graphics.size(); ++i) { GraphicType & t(graphics[i]); QPolygonF & src_pol(pause_ ? t.polyline_pause : t.polyline); if (!t.visible || src_pol.isEmpty()) continue; QVector & src_lod(pause_ ? t._lod_pause : t._lod); int lod = 0; if (m_LODOptimization) { int gpcnt = src_pol.size(); qreal range = src_pol.back().x() - src_pol.front().x(); qreal ppp = (gpcnt * selrect.width() / qMax(range, 1.E-9) / cwid); lod = qBound(0, qFloor(log2(ppp) - 1), src_lod.size()); // qDebug() << "draw lod" << lod << src_lod[lod - 1].size(); } t.last_lod = lod; QPolygonF & rpol(lod == 0 ? src_pol : src_lod[lod - 1]); int ind_start = -1, ind_end = -1; if (m_LODOptimization) { qreal xs = selrect.left(), xe = selrect.right(), _offset = 2. / cwid * selrect.width(); xs -= _offset; xe += _offset; for (int i = 0; i < rpol.size(); ++i) { qreal px = rpol[i].x(); if (px < xs) continue; if (ind_start < 0) ind_start = qMax(0, i - 1); if (px > xe && ind_end < 0) { ind_end = qMin(rpol.size(), i + 1); break; } } if (ind_start < 0) ind_start = 0; if (ind_end < 0) ind_end = rpol.size(); // qDebug() << "bound" << ind_start << ind_end << rpol.size(); } else { ind_start = 0; ind_end = rpol.size(); } int polsize = ind_end - ind_start; if (polsize > 0) { QPolygonF cpol; if (m_LODOptimization && polsize < rpol.size()) { cpol.resize(polsize); memcpy(cpol.data(), &(rpol[ind_start]), polsize * sizeof(QPointF)); // qDebug() << "copy" << polsize; } else { cpol = rpol; } pen = t.pen; if (qRound(pen.widthF()) == pen.widthF()) pen.setWidth(pen.width() * thick); else pen.setWidthF(pen.widthF() * thick); pen.setCosmetic(true); if (t.lines) { painter->setPen(pen); if (t.fill) { painter->setBrush(t.fill_color); painter->drawPolygon(mat.map(cpol)); } else painter->drawPolyline(mat.map(cpol)); } if (t.points) { if (qRound(t.pointWidth) == t.pointWidth) pen.setWidth(qRound(t.pointWidth * thick)); else pen.setWidthF(t.pointWidth * thick); painter->setPen(pen); painter->drawPoints(mat.map(cpol)); } } } painter->setWorldMatrixEnabled(true); } QString Graphic::pointCoords(QPointF point, bool x, bool y) { QString ret = "("; if (x) { if (axis_type_x == Numeric) ret += QString::number(point.x(), 'f', 3); else ret += #if QT_VERSION_MAJOR <= 5 QDateTime::fromMSecsSinceEpoch(point.x()).toString(Qt::SystemLocaleShortDate); #else locale().toString(QDateTime::fromMSecsSinceEpoch(point.x()), QLocale::ShortFormat); #endif } if (y) { if (ret.size() > 1) ret += " ; "; ret += QString::number(point.y(), 'f', 3); } ret += ")"; return ret; } void Graphic::drawGuides() { if (!guides || !isHover) return; int wid = canvas->width(), hei = canvas->height(); painter->setRenderHint(QPainter::Antialiasing, false); QPen gpen(grid_pen.color(), qMax(qRound(thick / 1.4), 1)); #if QT_VERSION_MAJOR >= 5 gpen.setCosmetic(true); #endif painter->setPen(gpen); painter->resetTransform(); painter->setClipping(true); painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y())); QPoint apos = curpos; QPointF rpos = canvas2real(apos); QString str; str = pointCoords(rpos) + fp_size; bool trace_found = false; auto trace_free_func = [&](QPointF cursor) { double min_dist = -1; int gr = -1, mag_dist = fontHeight(this) * 2; QPointF point, scale = getScale(), dp; for (int g = 0; g < graphics.size(); ++g) { auto & t(graphics[g]); if (t.visible) { QPolygonF & src_pol(pause_ ? t.polyline_pause : t.polyline); QVector & src_lod(pause_ ? t._lod_pause : t._lod); QPolygonF & pol(t.last_lod == 0 ? src_pol : src_lod[t.last_lod - 1]); double dist = 0.; for (int i = 0; i < pol.size(); ++i) { point = pol[i]; if (!selrect.contains(point)) continue; dp = point - cursor; dp = QPointF(dp.x() * scale.x(), dp.y() * scale.y()); if (dp.manhattanLength() <= mag_dist) { dist = QVector2D(dp).lengthSquared(); if (min_dist > dist || min_dist < 0) { min_dist = dist; gr = g; rpos = point; } } } } } if (gr >= 0) { apos = real2canvas(rpos).toPoint(); str = " " + graphics[gr].name + ": " + pointCoords(rpos) + fp_size; trace_found = true; emit graphicTraceEvent(gr, rpos); } }; if (floating_axis_type == Trace) { trace_free_func(rpos); } if (was_trace && !trace_found) emit graphicTraceEvent(-1, QPointF()); was_trace = trace_found; painter->drawLine(0, apos.y(), wid, apos.y()); painter->drawLine(apos.x(), 0, apos.x(), hei); QPoint p = apos + QPoint(font_sz.height() / 4., -font_sz.height() / 4.); QFontMetrics fm(font()); QRect r = fm.boundingRect(str); if (r.width() + apos.x() > wid - font_sz.height() / 2.) p.setX(apos.x() - r.width() - font_sz.height() / 4.); if (apos.y() - r.height() < font_sz.height() / 8.) p.setY(apos.y() + r.height() - font_sz.height() / 8.); painter->setPen(text_color); QRect trect = painter->fontMetrics().boundingRect(str); QColor tcol = back_color; tcol.setAlphaF(0.65); painter->fillRect(trect.translated(p), tcol); painter->drawText(p, str); } void Graphic::drawPause() { painter->setClipping(false); painter->save(); painter->resetTransform(); painter->translate(canvas->width() - icon_pause_b.width() - 6, 6); double o = (0.5 - pause_phase) * 2; painter->setOpacity(o * o); painter->drawImage(0, 0, icon_pause_b); painter->setOpacity(1.); painter->drawImage(0, 0, icon_pause_f); painter->restore(); painter->setClipping(true); } double roundToSteps(double value, const QVector & steps) { double ret = value, min_err = -1.; for (double v: steps) { double sv = qRound64(value / v) * v; double err = qAbs(value - sv); if (min_err < 0 || min_err > err) { min_err = err; ret = sv; } } if (ret < steps[0]) ret = steps[0]; return ret; } double roundToNearest(double value, const QVector & values) { double ret = value, min_err = -1.; for (double v: values) { double err = qAbs(value - v); if (min_err < 0 || min_err > err) { min_err = err; ret = v; } } if (ret < values[0]) ret = values[0]; return ret; } double Graphic::splitRange(double range, int count) { double digits, step, tln; range = qAbs(range); tln = qFloor(qLn(range) / LN10); for (int i = 0; i <= 5; ++i) { digits = qPow(10., tln - i); step = qRound(range / count / digits); if (step > 0.) { digits = qPow(10., tln - i - 1); step = qRound(range / count / digits); break; } } step = roundToSteps(step, {5., 10.}) * digits; return step; } double Graphic::splitRangeDate(double range, int count, DateFormats & formats, int step[7]) { static const qint64 to_sec = 1000LL, to_min = 1000LL * 60, to_hour = 1000LL * 60 * 60, to_day = 1000LL * 60 * 60 * 24, to_month = 1000LL * 60 * 60 * 24 * 30, to_year = 1000LL * 60 * 60 * 24 * 30 * 12; double ret = splitRange(range, count); int format_index = DateYears; if (ret < to_sec / 1) { format_index = DateMSecs; step[DateMSecs] = qRound64(ret); } else if (ret < to_min / 2) { format_index = DateSecs; step[DateSecs] = roundToNearest(ret / to_sec, {1, 2, 5, 10, 15, 20, 30}); } else if (ret < to_hour) { format_index = DateMinutes; step[DateMinutes] = roundToNearest(ret / to_min, {1, 2, 5, 10, 15, 20, 30}); } else if (ret < to_day) { format_index = DateHours; step[DateHours] = roundToNearest(ret / to_hour, {1, 2, 3, 4, 6, 8, 12}); } else if (ret < to_month / 1.6) { format_index = DateDays; step[DateDays] = roundToNearest(ret / to_day, {1, 2, 5, 10}); } else if (ret < to_year) { format_index = DateMonths; step[DateMonths] = roundToNearest(ret / to_month, {1, 2, 3, 4, 6}); } else { format_index = DateYears; step[DateYears] = qRound64(ret / to_year); } formats = date_formats[format_index]; return ret; } double Graphic::roundTo(double value, double round_to) { if (round_to == 0.) return value; return qRound64(value / round_to) * round_to; } void Graphic::roundDateTime(QDateTime & dt, int * c) { QDate d(dt.date()); QTime t(dt.time()); if (c[DateMSecs] != 0) t.setHMS(t.hour(), t.minute(), t.second()); if (c[DateSecs] != 0) t.setHMS(t.hour(), t.minute(), 0); if (c[DateMinutes] != 0) t.setHMS(t.hour(), 0, 0); if (c[DateHours] != 0) { t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), d.day()); } if (c[DateDays] != 0) { t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), 1); } if (c[DateMonths] != 0 || c[DateYears] != 0) { t.setHMS(0, 0, 0); d.setDate(d.year(), 1, 1); } dt = QDateTime(d, t); } void Graphic::addDateTime(QDateTime & dt, int * c, qint64 mul) { if (c[DateMSecs] != 0) dt = dt.addMSecs(mul * c[DateMSecs]); if (c[DateSecs] != 0) dt = dt.addSecs(mul * c[DateSecs]); if (c[DateMinutes] != 0) dt = dt.addSecs(mul * c[DateMinutes] * 60); if (c[DateHours] != 0) dt = dt.addSecs(mul * c[DateHours] * 60 * 60); if (c[DateDays] != 0) dt = dt.addDays(mul * c[DateDays]); if (c[DateMonths] != 0) dt = dt.addMonths(mul * c[DateMonths]); if (c[DateYears] != 0) dt = dt.addYears(mul * c[DateYears]); } double Graphic::canvas2realX(double px) const { int gbx = gridborder.x() + margins_.left(); int wid = lastw - gbx - margins_.width(); double cx = px - gbx; double sclx = selrect.width() / (double)wid; return cx * sclx + selrect.x(); } double Graphic::canvas2realY(double py) const { int gby = gridborder.y() + margins_.top(); int hei = lasth - gby - margins_.height(); double cy = lasth - py - gby; double scly = selrect.height() / (double)hei; return cy * scly + selrect.y(); } double Graphic::real2canvasX(double px) const { int gbx = gridborder.x() + margins_.left(); int wid = lastw - gbx - margins_.width(); double sclx = selrect.width() / (double)wid; return (px - selrect.x()) / sclx + gbx; } double Graphic::real2canvasY(double py) const { int gby = gridborder.y() + margins_.top(); int hei = lasth - gby - margins_.height(); double scly = selrect.height() / (double)hei; return lasth - gby - (py - selrect.y()) / scly; } QPolygonF Graphic::real2canvas(const QPolygonF & real_polygon) const { QPolygonF ret; for (const auto & p: real_polygon) { ret << real2canvas(p); } return ret; } QPolygonF Graphic::canvas2real(const QPolygonF & canvas_polygon) const { QPolygonF ret; for (const auto & p: canvas_polygon) { ret << canvas2real(p); } return ret; } double Graphic::getScaleX() const { int gbx = gridborder.x() + margins_.left(); int wid = lastw - gbx - margins_.width(); return (double)wid / selrect.width(); } double Graphic::getScaleY() const { int gby = gridborder.y() + margins_.top(); int hei = lasth - gby - margins_.height(); return (double)hei / selrect.height(); } void Graphic::setCurrentAction(GraphicAction action) { curaction = action; switch (action) { case gaNone: setGuidesCursor(); break; case gaZoomInRect: setCanvasCursor(Qt::CrossCursor); break; case gaZoomRangeX: setCanvasCursor(Qt::SplitHCursor); break; case gaZoomRangeY: setCanvasCursor(Qt::SplitVCursor); break; case gaMove: setCanvasCursor(Qt::SizeAllCursor); break; } } void Graphic::setCanvasCursor(QCursor cursor) { ui->canvas_raster->setCursor(cursor); #ifdef HAS_GL if (canvas_gl) canvas_gl->setCursor(cursor); #endif } void Graphic::setGuidesCursor() { if (guides) { setCanvasCursor(floating_axis_type == Free ? Qt::BlankCursor : Qt::CrossCursor); } else setCanvasCursor(Qt::ArrowCursor); } void Graphic::swapToBuffer() { QImage timg; #ifdef HAS_GL if (isOGL && canvas_gl) { timg = canvas_gl->grabFrameBuffer(); QPainter p(&buffer); p.drawImage(0, 0, timg); p.end(); } #endif bufferActive = true; } void Graphic::setRectToLines() { is_lines_update = true; if (line_x_min.isVisible() && line_x_max.isVisible() && line_y_min.isVisible() && line_y_max.isVisible()) { line_x_min.blockSignals(true); line_x_max.blockSignals(true); line_y_min.blockSignals(true); line_y_max.blockSignals(true); if (!line_x_min.hasFocus()) { if (isFit) line_x_min.setValue(grect.left()); else line_x_min.setValue(selrect.left()); } if (!line_x_max.hasFocus()) { if (isFit) line_x_max.setValue(grect.right()); else line_x_max.setValue(selrect.right()); } if (!line_y_min.hasFocus()) { if (isFit) line_y_min.setValue(grect.bottom()); else line_y_min.setValue(selrect.bottom()); } if (!line_y_max.hasFocus()) { if (isFit) line_y_max.setValue(grect.top()); else line_y_max.setValue(selrect.top()); } // line_x_min.setDefaultText(QString::number(grect.left()).toUpper()); // line_x_max.setDefaultText(QString::number(grect.right()).toUpper()); // line_y_min.setDefaultText(QString::number(grect.bottom()).toUpper()); // line_y_max.setDefaultText(QString::number(grect.top()).toUpper()); line_x_min.blockSignals(false); line_x_max.blockSignals(false); line_y_min.blockSignals(false); line_y_max.blockSignals(false); } is_lines_update = false; } void Graphic::checkLines() { isFit = (line_x_min.isCleared() && line_x_max.isCleared() && line_y_min.isCleared() && line_y_max.isCleared()); repaintCanvas(true); } void Graphic::tick(int index, bool slide, bool update_) { if (slide) { GraphicType & t(graphics[index]); if (history > 0.) while (t.polyline.size() > 1) { if (fabs(t.polyline.back().x() - t.polyline.front().x()) <= history) break; /// TODO: [Graphic] fast autofit while addPoint(double y, ...) if (!t.cvrect.isNull()) { QPointF fp(t.polyline.first()); if (qFuzzyCompare(t.cvrect.left(), fp.x()) || qFuzzyCompare(t.cvrect.right(), fp.x()) || qFuzzyCompare(t.cvrect.top(), fp.y()) || qFuzzyCompare(t.cvrect.bottom(), fp.y())) { t.cvrect = QRectF(); } } t.polyline.pop_front(); } } else { calcLOD(index); } if (isFit) findGraphicsRect(); if (aupdate && update_) repaintCanvas(); } void Graphic::calcLOD(int index) { if (!m_LODOptimization) return; GraphicType & t(graphics[index]); t._lod.clear(); t._lod.reserve(32); int pcnt = t.polyline.size(); // qDebug() << "calcLOD" << index; while (pcnt >= 10) { QPolygonF & pl(t._lod.isEmpty() ? t.polyline : t._lod.back()); t._lod.append(QPolygonF()); QPolygonF & cl(t._lod.back()); if (pl.size() > 4) { cl << pl.front(); int qcnt = (pl.size() + 1) / 4; pcnt = qcnt * 2 + 2; int pc = 4; qreal mx[2] = {0., 0.}, my[2] = {0., 0.}, my_x[2] = {0., 0.}, px, py; for (int i = 0; i < qcnt; ++i) { int j = i * 4 + 1; if (i == qcnt - 1) pc = pl.size() - j - 1; mx[0] = mx[1] = my_x[0] = my_x[1] = pl[j].x(); my[0] = my[1] = pl[j].y(); for (int k = 1; k < pc; ++k) { px = pl[j + k].x(); py = pl[j + k].y(); mx[0] = qMin(mx[0], px); mx[1] = qMax(mx[1], px); if (my[0] > py) { my[0] = py; my_x[0] = px; } if (my[1] < py) { my[1] = py; my_x[1] = px; } } qreal dx = (mx[1] - mx[0]) / 4., cx = (mx[1] + mx[0]) / 2.; if (my_x[1] >= my_x[0]) cl << QPointF(cx - dx, my[0]) << QPointF(cx + dx, my[1]); else cl << QPointF(cx - dx, my[1]) << QPointF(cx + dx, my[0]); } cl << pl.back(); } else cl = pl; // qDebug() << "lod" << t._lod.size() << "->" << cl.size(); } } void Graphic::repaintCanvas(bool force) { if (tm.elapsed() < min_repaint_int && !force) return; tm.restart(); canvas->update(); } void Graphic::on_graphic_buttonAutofit_clicked() { autofit(); } void Graphic::on_graphic_buttonConfigure_clicked() { conf->graphicItems.clear(); for (int i = 0; i < graphics.size(); i++) { GraphicConf::GraphicItem item; item.icon = graphics[i].icon; item.name = graphics[i].name; conf->graphicItems.append(item); } conf->ui->colorGrid->setColor(grid_pen.color()); conf->ui->comboStyleGrid->setCurrentIndex((int)grid_pen.style()); conf->ui->spinWidthGrid->setValue(grid_pen.widthF()); #ifdef HAS_GL conf->ui->checkOGL->setChecked(isOGL); #else conf->ui->checkOGL->setEnabled(false); conf->ui->checkOGL->setChecked(false); #endif conf->ui->checkAAlias->setChecked(aalias); conf->ui->checkInputs->setChecked(borderInputsVisible()); conf->ui->checkStatus->setChecked(statusVisible()); conf->ui->checkLegend->setChecked(legendVisible()); conf->ui->groupGrid->setChecked(grid); conf->ui->checkGridAutoX->setChecked(grad_x == Auto); conf->ui->checkGridAutoY->setChecked(grad_y == Auto); conf->ui->colorBackground->setColor(back_color); conf->ui->colorText->setColor(text_color); conf->ui->spinGridStepX->setValue(gridx); conf->ui->spinGridStepY->setValue(gridy); conf->ui->spinMarginL->setValue(margins_.left()); conf->ui->spinMarginT->setValue(margins_.height()); conf->ui->spinMarginR->setValue(margins_.width()); conf->ui->spinMarginB->setValue(margins_.top()); conf->readParams(); if (conf->exec() == QDialog::Rejected) return; grid_pen = QPen(conf->ui->colorGrid->color(), conf->ui->spinWidthGrid->value(), (Qt::PenStyle)conf->ui->comboStyleGrid->currentIndex()); back_color = conf->ui->colorBackground->color(); text_color = conf->ui->colorText->color(); grad_x = conf->ui->checkGridAutoX->isChecked() ? Auto : Fixed; grad_y = conf->ui->checkGridAutoY->isChecked() ? Auto : Fixed; gridx = conf->ui->spinGridStepX->value(); gridy = conf->ui->spinGridStepY->value(); setOpenGL(conf->ui->checkOGL->isChecked()); setAntialiasing(conf->ui->checkAAlias->isChecked()); setBorderInputsVisible(conf->ui->checkInputs->isChecked()); setStatusVisible(conf->ui->checkStatus->isChecked()); setLegendVisible(conf->ui->checkLegend->isChecked()); setMargins(conf->ui->spinMarginL->value(), conf->ui->spinMarginR->value(), conf->ui->spinMarginT->value(), conf->ui->spinMarginB->value()); setGridEnabled(conf->ui->groupGrid->isChecked()); updateLegend(); repaintCanvas(); for (int i = 0; i < qMin(graphics.size(), loaded_configs.size()); ++i) { loaded_configs[i] = graphics[i]; loaded_configs[i].removeData(); } } void Graphic::on_graphic_buttonFullscreen_clicked() { fullscreen(); } void Graphic::on_graphic_actionSaveImage_triggered() { QString f = QFileDialog::getSaveFileName(this, tr("Save Image"), ppath, "PNG(*.png);;JPEG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tiff *.tif);;PPM(*.ppm)"); if (f.isEmpty()) return; saveImage(f); } void Graphic::on_graphic_actionExportCSV_triggered() { askForExport(false); } void Graphic::on_graphic_actionExportCSV_View_triggered() { askForExport(true); } void Graphic::on_graphic_buttonRecord_clicked(bool checked) { if (checked) { record_imgs.clear(); timer_record = startTimer(100); } else { killTimer(timer_record); timer_record = 0; if (record_imgs.isEmpty()) return; QString f = QFileDialog::getSaveFileName(this, tr("Save GIF"), ppath, "GIF(*.gif)"); if (!f.isEmpty()) { qApp->setOverrideCursor(Qt::BusyCursor); GifWriter gif_writer; int frame_delay = 10; if (GifBegin(&gif_writer, f.toUtf8(), static_cast(record_imgs.first().width()), static_cast(record_imgs.first().height()), static_cast(frame_delay))) { for (const QImage & im: record_imgs) { if (!GifWriteFrame(&gif_writer, im.convertToFormat(QImage::Format_RGBA8888).constBits(), static_cast(im.width()), static_cast(im.height()), static_cast(frame_delay))) { GifEnd(&gif_writer); qDebug() << "GifWriteFrame ERROR"; } } GifEnd(&gif_writer); } qApp->restoreOverrideCursor(); } record_imgs.clear(); } } void Graphic::on_graphic_checkGuides_toggled(bool checked) { guides = checked; setGuidesCursor(); repaintCanvas(); } void Graphic::updateLegend(bool es) { QPixmap pix(60, 22); for (int i = 0; i < graphics.size(); i++) { pix.fill(back_color); QPainter p(&pix); QPen pen = graphics[i].pen; if (qRound(pen.widthF()) == pen.widthF()) pen.setWidth(pen.width() * thick); else pen.setWidthF(pen.widthF() * thick); p.setPen(pen); p.drawLine(0, pix.height() / 2, pix.width(), pix.height() / 2); p.end(); graphics[i].icon = QIcon(pix); } if (!ui->scrollLegend->isVisibleTo(this)) { if (es) emit graphicSettingsChanged(); return; } leg_update = false; int ps = 100; for (int r = 0; r < ui->layoutLegend->rowCount(); ++r) for (int c = 0; c < ui->layoutLegend->columnCount(); ++c) { QLayoutItem * li = ui->layoutLegend->itemAtPosition(r, c); if (!li) continue; if (!li->widget()) continue; ui->layoutLegend->removeWidget(li->widget()); } ui->layoutLegend->invalidate(); for (int i = 0; i < graphics.size(); i++) { if (graphics[i].init()) { graphics[i].pb->addActions( {ui->actionCheck_all, ui->actionUncheck_all, ui->actionInvert_selection, ui->actionSelect_only_this}); graphics[i].pb->installEventFilter(this); connect(graphics[i].pb, SIGNAL(toggled(bool)), this, SLOT(graphicVisibleChange(bool))); graphics[i].pb->setContextMenuPolicy(Qt::ActionsContextMenu); } graphics[i].pb->setIconSize(pix.size()); graphics[i].pb->setIcon(graphics[i].icon); graphics[i].pb->setChecked(graphics[i].visible); graphics[i].pb->setText(graphics[i].name); graphics[i].pb->setProperty("graphic_num", i); int cps = graphics[i].pb->sizeHint().width() + 4; if (cps > ps) ps = cps; } LegendScrollArea * leg_sa = (LegendScrollArea *)ui->scrollLegend->layout()->itemAt(0)->widget(); int maxcol = qMax(leg_sa->width() / ps - 1, 1); int row = 0, col = 0; bool lv = ui->scrollLegend->isVisibleTo(this); ui->scrollLegend->hide(); for (int i = 0; i < graphics.size(); i++) { ui->layoutLegend->addWidget(graphics[i].pb, row, col); graphics[i].pb->show(); if (leg_sa->minimum_hei == 0) { leg_sa->minimum_hei = ui->widgetLegend->sizeHint().height(); } col++; if (col > maxcol) { col = 0; row++; } } ui->gridLayout->invalidate(); ui->scrollLegend->setVisible(lv); leg_sa->updateGeometry(); leg_update = true; if (es) emit graphicSettingsChanged(); } void Graphic::updateLegendChecks() { for (int i = 0; i < graphics.size(); i++) { if (!graphics[i].pb) continue; bool pbs = graphics[i].pb->blockSignals(true); graphics[i].pb->setChecked(graphics[i].visible); graphics[i].pb->blockSignals(pbs); } emit graphicSettingsChanged(); } void Graphic::graphicVisibleChange(bool checked) { if (visible_update) return; QCheckBox * cb = qobject_cast(sender()); int i = cb->property("graphic_num").toInt(); graphics[i].visible = checked; graphicVisibleChanged(); } void Graphic::lineXMinChanged(double value) { selrect.setLeft(value); checkLines(); } void Graphic::lineXMaxChanged(double value) { selrect.setRight(value); checkLines(); } void Graphic::lineYMinChanged(double value) { selrect.setBottom(value); checkLines(); } void Graphic::lineYMaxChanged(double value) { selrect.setTop(value); checkLines(); } void Graphic::on_actionCheck_all_triggered() { visible_update = true; for (auto & g: graphics) { g.visible = true; g.pb->setChecked(true); } visible_update = false; graphicVisibleChanged(); } void Graphic::on_actionUncheck_all_triggered() { visible_update = true; for (auto & g: graphics) { g.visible = false; g.pb->setChecked(false); } visible_update = false; graphicVisibleChanged(); } void Graphic::on_actionInvert_selection_triggered() { visible_update = true; for (auto & g: graphics) { g.visible = !g.visible; g.pb->setChecked(g.visible); } visible_update = false; graphicVisibleChanged(); } void Graphic::on_actionSelect_only_this_triggered() { visible_update = true; for (auto & g: graphics) { g.visible = (g.pb == action_source); g.pb->setChecked(g.visible); } visible_update = false; graphicVisibleChanged(); action_source = nullptr; } void Graphic::on_graphic_buttonClose_clicked() { emit closeRequest(this); } void Graphic::on_graphic_buttonClear_clicked() { clear(); emit cleared(); } void Graphic::enterFullscreen() { if (fullscr) return; fullscr = true; canvas->hide(); #ifdef Q_OS_ANDROID tm_fscr.restart(); QDialog dlg; dlg.setLayout(new QBoxLayout(QBoxLayout::TopToBottom)); dlg.layout()->setContentsMargins(0, 0, 0, 0); dlg.layout()->addWidget(canvas); QPushButton * btn = new QPushButton("Leave fullscreen"); dlg.layout()->addWidget(btn); connect(btn, SIGNAL(clicked(bool)), this, SLOT(leaveFullscreen())); canvas->show(); dlg.showFullScreen(); dlg.exec(); dlg.layout()->removeWidget(canvas); leaveFullscreen(); return; #else layout()->removeWidget(ui->widgetTop); ui->widgetTop->setParent(0); ui->widgetTop->showFullScreen(); ui->widgetTop->setFocus(); ui->widgetTop->raise(); canvas->show(); #endif } void Graphic::leaveFullscreen() { #ifdef Q_OS_ANDROID if (tm_fscr.elapsed() < 100) return; #endif if (!fullscr) return; fullscr = false; #ifdef Q_OS_ANDROID canvas->showNormal(); canvas->hide(); ui->layoutCanvas->addWidget(canvas); canvas->show(); #else layout()->addWidget(ui->widgetTop); canvas->show(); #endif } void Graphic::showMenu() { #ifdef NO_BUTTONS for (auto * a: buttons_menu->actions()) { QToolButton * b = (QToolButton *)(a->property(_button_prop_name_).toULongLong()); if (!b) { a->setVisible(false); continue; } a->blockSignals(true); a->setVisible(!b->isHidden()); a->setText(b->toolTip()); a->setIcon(b->icon()); a->setChecked(b->isChecked()); a->blockSignals(false); } buttons_menu->popup(QCursor::pos()); #endif } QString Graphic::caption() const { return ui->labelCaption->text(); } QString Graphic::graphicName(int index) const { if (!checkGraphicIndex(index)) return {}; return graphics[index].name; } QColor Graphic::graphicColor(int index) const { if (!checkGraphicIndex(index)) return Qt::black; return graphics[index].pen.color(); } Qt::PenStyle Graphic::graphicStyle(int index) const { if (!checkGraphicIndex(index)) return Qt::NoPen; return graphics[index].pen.style(); } double Graphic::graphicLineWidth(int index) const { if (!checkGraphicIndex(index)) return 0.; return graphics[index].pen.widthF(); } double Graphic::graphicPointWidth(int index) const { if (!checkGraphicIndex(index)) return 0.; return graphics[index].pointWidth; } QColor Graphic::graphicFillColor(int index) const { if (!checkGraphicIndex(index)) return Qt::black; return graphics[index].fill_color; } bool Graphic::graphicVisible(int index) const { if (!checkGraphicIndex(index)) return false; return graphics[index].visible; } bool Graphic::graphicLinesEnabled(int index) const { if (!checkGraphicIndex(index)) return false; return graphics[index].lines; } bool Graphic::graphicPointsEnabled(int index) const { if (!checkGraphicIndex(index)) return false; return graphics[index].points; } bool Graphic::graphicFillEnabled(int index) const { if (!checkGraphicIndex(index)) return false; return graphics[index].fill; } QPen Graphic::graphicPen(int index) const { if (!checkGraphicIndex(index)) return {}; return graphics[index].pen; } bool Graphic::borderInputsVisible() const { return ui->widgetLX->isVisible(); } bool Graphic::statusVisible() const { return ui->status->isVisible(); } bool Graphic::legendVisible() const { return ui->scrollLegend->isVisible(); } QVector Graphic::graphicData(int index) const { if (!checkGraphicIndex(index)) return {}; return graphics[index].polyline; } QByteArray Graphic::save() { // QByteArray ba; // QDataStream s(&ba, QIODevice::ReadWrite); // s << openGL() << antialiasing() << borderInputsVisible() << statusVisible() << legendVisible(); // s << graphics; // return ba; // version '2': ChunkStream cs; cs.add(1, antialiasing()).add(2, openGL()).add(3, borderInputsVisible()).add(4, statusVisible()).add(5, legendVisible()); cs.add(6, backgroundColor()).add(7, textColor()).add(8, margins()); cs.add(9, gridPen()).add(10, graduationX()).add(11, graduationY()).add(12, graduationStepX()).add(13, graduationStepY()); cs.add(14, graphics); cs.add(15, isFit).add(16, visualRect()); if (backgroundColor() == palette().color(QPalette::Base) && textColor() == palette().color(QPalette::WindowText) && gridColor() == palette().color(QPalette::Disabled, QPalette::WindowText)) cs.add(17, true); cs.add(18, gridEnabled()); return cs.data().prepend('2'); } void Graphic::load(QByteArray ba) { if (ba.isEmpty()) return; char ver = ba[0]; bool has_opengl = isOGL; switch (ver) { case '2': { // version '2': ba.remove(0, 1); QRectF vrect; ChunkStream cs(ba); bool def_colors = false; while (!cs.atEnd()) { switch (cs.read()) { case 1: aalias = cs.getData(); break; case 2: has_opengl = cs.getData(); break; case 3: setBorderInputsVisible(cs.getData()); break; case 4: setStatusVisible(cs.getData()); break; case 5: setLegendVisible(cs.getData()); break; case 6: if (!def_colors) back_color = cs.getData(); break; case 7: if (!def_colors) text_color = cs.getData(); break; case 8: margins_ = cs.getData(); break; case 9: if (!def_colors) grid_pen = cs.getData(); break; case 10: grad_x = cs.getData(); break; case 11: grad_y = cs.getData(); break; case 12: gridx = cs.getData(); break; case 13: gridy = cs.getData(); break; case 14: graphics = cs.getData>(); break; case 15: isFit = cs.getData(); break; case 16: vrect = cs.getData(); break; case 17: if (cs.getData()) { text_color = palette().color(QPalette::WindowText); grid_pen = QPen(palette().color(QPalette::Disabled, QPalette::WindowText), 0., Qt::DotLine); back_color = palette().color(QPalette::Base); def_colors = true; } break; case 18: grid = cs.getData(); break; default: break; } } if (!isFit) setVisualRect(vrect); } break; default: { // old version 0: QDataStream s(ba); bool a; s >> has_opengl; s >> aalias; s >> a; setBorderInputsVisible(a); s >> a; setStatusVisible(a); s >> a; s >> graphics; setLegendVisible(a); } break; } loaded_configs = graphics; for (auto & i: loaded_configs) i.removeData(); if (has_opengl != isOGL) setOpenGL(has_opengl); updateLegend(); repaintCanvas(true); } GraphicType Graphic::graphic(int arg) { if (!checkGraphicIndex(arg)) return {}; return graphics[arg]; } void Graphic::setAllGraphics(const QVector & g, bool update) { graphics = g; if (update) { updateLegend(); repaintCanvas(); } } void Graphic::setCaption(const QString & str) { ui->labelCaption->setText(str); ui->labelCaption->setVisible(str.length() > 0); if (aupdate) repaintCanvas(); } void Graphic::setLabelX(const QString & str) { label_x = str; hasLblX = (str.length() > 0); if (aupdate) repaintCanvas(); } void Graphic::setLabelY(const QString & str) { label_y = str; hasLblY = (str.length() > 0); if (aupdate) repaintCanvas(); } void Graphic::setGraphicName(const QString & str, int index) { if (!checkGraphicIndex(index)) return; graphics[index].name = str; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicName(const QString & str) { setGraphicName(str, curGraphic); } void Graphic::setBackgroundColor(const QColor & color) { back_color = color; if (aupdate) repaintCanvas(); updateLegend(); } void Graphic::setTextColor(const QColor & color) { text_color = color; if (aupdate) repaintCanvas(); } void Graphic::setGraphicColor(const QColor & color, int index) { if (!checkGraphicIndex(index)) return; graphics[index].pen.setColor(color); updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicColor(const QColor & color) { setGraphicColor(color, curGraphic); } void Graphic::setGridColor(const QColor & color) { grid_pen.setColor(color); if (aupdate) repaintCanvas(); } void Graphic::setSelectionColor(const QColor & color) { selpen.setColor(color); } void Graphic::setGraphicStyle(const Qt::PenStyle & style, int index) { if (!checkGraphicIndex(index)) return; graphics[index].pen.setStyle(style); updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicStyle(const Qt::PenStyle & style) { setGraphicStyle(style, curGraphic); } void Graphic::setGridStyle(const Qt::PenStyle & style) { grid_pen.setStyle(style); if (aupdate) repaintCanvas(); } void Graphic::setSelectionStyle(const Qt::PenStyle & style) { selpen.setStyle(style); } void Graphic::setGraphicVisible(bool visible, int index) { if (!checkGraphicIndex(index)) return; graphics[index].visible = visible; updateLegendChecks(); if (isFit) { autofit(); } else if (aupdate) { repaintCanvas(); } } void Graphic::setGraphicVisible(bool visible) { setGraphicVisible(visible, curGraphic); } void Graphic::setGraphicLineWidth(double w, int index) { if (!checkGraphicIndex(index)) return; if (qRound(w) == w) graphics[index].pen.setWidth(qRound(w)); else graphics[index].pen.setWidthF(w); updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicLineWidth(double w) { setGraphicLineWidth(w, curGraphic); } void Graphic::setGraphicPointWidth(double w, int index) { if (!checkGraphicIndex(index)) return; graphics[index].pointWidth = w; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicPointWidth(double w) { setGraphicPointWidth(w, curGraphic); } void Graphic::setGraphicFillColor(const QColor & w, int index) { if (!checkGraphicIndex(index)) return; graphics[index].fill_color = w; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicFillColor(const QColor & w) { setGraphicFillColor(w, curGraphic); } void Graphic::setGraphicLinesEnabled(bool w, int index) { if (!checkGraphicIndex(index)) return; graphics[index].lines = w; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicLinesEnabled(bool w) { setGraphicLinesEnabled(w, curGraphic); } void Graphic::setGraphicPointsEnabled(bool w, int index) { if (!checkGraphicIndex(index)) return; graphics[index].points = w; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicPointsEnabled(bool w) { setGraphicPointsEnabled(w, curGraphic); } void Graphic::setGraphicFillEnabled(bool w, int index) { if (!checkGraphicIndex(index)) return; graphics[index].fill = w; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicFillEnabled(bool w) { setGraphicFillEnabled(w, curGraphic); } void Graphic::setGraphicPen(const QPen & pen, int index) { if (!checkGraphicIndex(index)) return; graphics[index].pen = pen; updateLegend(); if (aupdate) repaintCanvas(); } void Graphic::setGraphicPen(const QPen & pen) { setGraphicPen(pen, curGraphic); } void Graphic::setGridPen(const QPen & pen) { grid_pen = pen; if (aupdate) repaintCanvas(); } void Graphic::setSelectionPen(const QPen & pen) { selpen = pen; } void Graphic::setSelectionBrush(const QBrush & brush) { selbrush = brush; } void Graphic::setNavigationEnabled(bool on) { navigation = on; } void Graphic::setLODOptimization(bool yes) { m_LODOptimization = yes; } void Graphic::setGridEnabled(bool enabled) { grid = enabled; repaintCanvas(); } void Graphic::setBorderInputsVisible(bool visible) { ui->widgetLX->setVisible(visible); ui->widgetLY->setVisible(visible); ui->graphic_checkBorderInputs->setChecked(visible); if (visible) setRectToLines(); } void Graphic::setStatusVisible(bool visible) { ui->status->setVisible(visible); } void Graphic::setLegendVisible(bool visible) { ui->scrollLegend->setVisible(visible); ui->graphic_checkLegend->setChecked(visible); #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(0, this, [this]() { updateLegend(); }); #else updateLegend(); #endif } void Graphic::on_graphic_actionExpandX_triggered(bool checked) { only_expand_x = checked; ui->graphic_actionExpandX->setIcon(checked ? icon_exp_x : icon_exp_sx); } void Graphic::on_graphic_actionExpandY_triggered(bool checked) { only_expand_y = checked; ui->graphic_actionExpandY->setIcon(checked ? icon_exp_y : icon_exp_sy); } void Graphic::on_graphic_checkBorderInputs_toggled(bool checked) { setBorderInputsVisible(checked); } void Graphic::on_graphic_checkLegend_toggled(bool checked) { setLegendVisible(checked); } void Graphic::on_graphic_checkPause_toggled(bool checked) { setPaused(checked); } void Graphic::actionGuidesTriggered(QAction * a) { ui->graphic_checkGuides->setChecked(true); setFloatingAxisType((FloatingAxisType)a->property("_value").toInt()); }