libpappsomspp
Library for mass spectrometry
Loading...
Searching...
No Matches
baseplotwidget.cpp
Go to the documentation of this file.
1/* This code comes right from the msXpertSuite software project.
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * END software license
23 */
24
25
26/////////////////////// StdLib includes
27#include <vector>
28
29
30/////////////////////// Qt includes
31#include <QVector>
32
33
34/////////////////////// Local includes
35#include "../../core/types.h"
37#include "baseplotwidget.h"
40
41
43 qRegisterMetaType<pappso::BasePlotContext>("pappso::BasePlotContext");
45 qRegisterMetaType<pappso::BasePlotContext *>("pappso::BasePlotContext *");
46
47namespace pappso
48{
49BasePlotWidget::BasePlotWidget(QWidget *parent): QCustomPlot(parent)
50{
51 if(parent == nullptr)
52 qFatal("Programming error.");
53
54 // Default settings for the pen used to graph the data.
55 m_pen.setStyle(Qt::SolidLine);
56 m_pen.setBrush(Qt::black);
57 m_pen.setWidth(1);
58
59 // qDebug() << "Created new BasePlotWidget with" << layerCount()
60 //<< "layers before setting up widget.";
61 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
62
63 // As of today 20210313, the QCustomPlot is created with the following 6
64 // layers:
65 //
66 // All layers' name:
67 //
68 // Layer index 0 name: background
69 // Layer index 1 name: grid
70 // Layer index 2 name: main
71 // Layer index 3 name: axes
72 // Layer index 4 name: legend
73 // Layer index 5 name: overlay
74
75 if(!setupWidget())
76 qFatal("Programming error.");
77
78 // Do not call createAllAncillaryItems() in this base class because all the
79 // items will have been created *before* the addition of plots and then the
80 // rendering order will hide them to the viewer, since the rendering order is
81 // according to the order in which the items have been created.
82 //
83 // The fact that the ancillary items are created before trace plots is not a
84 // problem because the trace plots are sparse and do not effectively hide the
85 // data.
86 //
87 // But, in the color map plot widgets, we cannot afford to create the
88 // ancillary items *before* the plot itself because then, the rendering of the
89 // plot (created after) would screen off the ancillary items (created before).
90 //
91 // So, the createAllAncillaryItems() function needs to be called in the
92 // derived classes at the most appropriate moment in the setting up of the
93 // widget.
94 //
95 // All this is only a workaround of a bug in QCustomPlot. See
96 // https://www.qcustomplot.com/index.php/support/forum/2283.
97 //
98 // I initially wanted to have a plots layer on top of the default background
99 // layer and a items layer on top of it. But that setting prevented the
100 // selection of graphs.
101
102 // qDebug() << "Created new BasePlotWidget with" << layerCount()
103 //<< "layers after setting up widget.";
104 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
105
106 show();
107}
108
110 const QString &x_axis_label,
111 const QString &y_axis_label)
112 : QCustomPlot(parent), m_axisLabelX(x_axis_label), m_axisLabelY(y_axis_label)
113{
114 // qDebug();
115
116 if(parent == nullptr)
117 qFatal("Programming error.");
118
119 // Default settings for the pen used to graph the data.
120 m_pen.setStyle(Qt::SolidLine);
121 m_pen.setBrush(Qt::black);
122 m_pen.setWidth(1);
123
124 xAxis->setLabel(x_axis_label);
125 yAxis->setLabel(y_axis_label);
126
127 // qDebug() << "Created new BasePlotWidget with" << layerCount()
128 //<< "layers before setting up widget.";
129 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
130
131 // As of today 20210313, the QCustomPlot is created with the following 6
132 // layers:
133 //
134 // All layers' name:
135 //
136 // Layer index 0 name: background
137 // Layer index 1 name: grid
138 // Layer index 2 name: main
139 // Layer index 3 name: axes
140 // Layer index 4 name: legend
141 // Layer index 5 name: overlay
142
143 if(!setupWidget())
144 qFatal("Programming error.");
145
146 // qDebug() << "Created new BasePlotWidget with" << layerCount()
147 //<< "layers after setting up widget.";
148 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
149
150 show();
151}
152
153//! Destruct \c this BasePlotWidget instance.
154/*!
155
156 The destruction involves clearing the history, deleting all the axis range
157 history items for x and y axes.
158
159*/
161{
162 // qDebug() << "In the destructor of plot widget:" << this;
163
164 m_xAxisRangeHistory.clear();
165 m_yAxisRangeHistory.clear();
166
167 // Note that the QCustomPlot xxxItem objects are allocated with (this) which
168 // means their destruction is automatically handled upon *this' destruction.
169}
170
171QString
173{
174
175 QString text;
176
177 for(int iter = 0; iter < layerCount(); ++iter)
178 {
179 text +=
180 QString("Layer index %1: %2\n").arg(iter).arg(layer(iter)->name());
181 }
182
183 return text;
184}
185
186QString
187BasePlotWidget::layerableLayerName(QCPLayerable *layerable_p) const
188{
189 if(layerable_p == nullptr)
190 qFatal("Programming error.");
191
192 QCPLayer *layer_p = layerable_p->layer();
193
194 return layer_p->name();
195}
196
197int
198BasePlotWidget::layerableLayerIndex(QCPLayerable *layerable_p) const
199{
200 if(layerable_p == nullptr)
201 qFatal("Programming error.");
202
203 QCPLayer *layer_p = layerable_p->layer();
204
205 for(int iter = 0; iter < layerCount(); ++iter)
206 {
207 if(layer(iter) == layer_p)
208 return iter;
209 }
210
211 return -1;
212}
213
214void
216{
217 // Make a copy of the pen to just change its color and set that color to
218 // the tracer line.
219 QPen pen = m_pen;
220
221 // Create the lines that will act as tracers for position and selection of
222 // regions.
223 //
224 // We have the cross hair that serves as the cursor. That crosshair cursor is
225 // made of a vertical line (green, because when click-dragging the mouse it
226 // becomes the tracer that is being anchored at the region start. The second
227 // line i horizontal and is always black.
228
229 pen.setColor(QColor("steelblue"));
230
231 // The set of tracers (horizontal and vertical) that track the position of the
232 // mouse cursor.
233
234 mp_vPosTracerItem = new QCPItemLine(this);
235 mp_vPosTracerItem->setLayer("plotsLayer");
236 mp_vPosTracerItem->setPen(pen);
237 mp_vPosTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
238 mp_vPosTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
239 mp_vPosTracerItem->start->setCoords(0, 0);
240 mp_vPosTracerItem->end->setCoords(0, 0);
241
242 mp_hPosTracerItem = new QCPItemLine(this);
243 mp_hPosTracerItem->setLayer("plotsLayer");
244 mp_hPosTracerItem->setPen(pen);
245 mp_hPosTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
246 mp_hPosTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
247 mp_hPosTracerItem->start->setCoords(0, 0);
248 mp_hPosTracerItem->end->setCoords(0, 0);
249
250 // The set of tracers (horizontal only) that track the region
251 // spanning/selection regions.
252 //
253 // The start vertical tracer is colored in greeen.
254 pen.setColor(QColor("green"));
255
256 mp_vStartTracerItem = new QCPItemLine(this);
257 mp_vStartTracerItem->setLayer("plotsLayer");
258 mp_vStartTracerItem->setPen(pen);
259 mp_vStartTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
260 mp_vStartTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
261 mp_vStartTracerItem->start->setCoords(0, 0);
262 mp_vStartTracerItem->end->setCoords(0, 0);
263
264 // The end vertical tracer is colored in red.
265 pen.setColor(QColor("red"));
266
267 mp_vEndTracerItem = new QCPItemLine(this);
268 mp_vEndTracerItem->setLayer("plotsLayer");
269 mp_vEndTracerItem->setPen(pen);
270 mp_vEndTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
271 mp_vEndTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
272 mp_vEndTracerItem->start->setCoords(0, 0);
273 mp_vEndTracerItem->end->setCoords(0, 0);
274
275 // When the user click-drags the mouse, the X distance between the drag start
276 // point and the drag end point (current point) is the xDelta.
277 mp_xDeltaTextItem = new QCPItemText(this);
278 mp_xDeltaTextItem->setLayer("plotsLayer");
279 mp_xDeltaTextItem->setColor(QColor("steelblue"));
280 mp_xDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
281 mp_xDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
282 mp_xDeltaTextItem->setVisible(false);
283
284 // Same for the y delta
285 mp_yDeltaTextItem = new QCPItemText(this);
286 mp_yDeltaTextItem->setLayer("plotsLayer");
287 mp_yDeltaTextItem->setColor(QColor("steelblue"));
288 mp_yDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
289 mp_yDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
290 mp_yDeltaTextItem->setVisible(false);
291
292 // Make sure we prepare the four lines that will be needed to
293 // draw the selection rectangle.
294 pen = m_pen;
295
296 pen.setColor("steelblue");
297
298 mp_selectionRectangeLine1 = new QCPItemLine(this);
299 mp_selectionRectangeLine1->setLayer("plotsLayer");
300 mp_selectionRectangeLine1->setPen(pen);
301 mp_selectionRectangeLine1->start->setType(QCPItemPosition::ptPlotCoords);
302 mp_selectionRectangeLine1->end->setType(QCPItemPosition::ptPlotCoords);
303 mp_selectionRectangeLine1->start->setCoords(0, 0);
304 mp_selectionRectangeLine1->end->setCoords(0, 0);
305 mp_selectionRectangeLine1->setVisible(false);
306
307 mp_selectionRectangeLine2 = new QCPItemLine(this);
308 mp_selectionRectangeLine2->setLayer("plotsLayer");
309 mp_selectionRectangeLine2->setPen(pen);
310 mp_selectionRectangeLine2->start->setType(QCPItemPosition::ptPlotCoords);
311 mp_selectionRectangeLine2->end->setType(QCPItemPosition::ptPlotCoords);
312 mp_selectionRectangeLine2->start->setCoords(0, 0);
313 mp_selectionRectangeLine2->end->setCoords(0, 0);
314 mp_selectionRectangeLine2->setVisible(false);
315
316 mp_selectionRectangeLine3 = new QCPItemLine(this);
317 mp_selectionRectangeLine3->setLayer("plotsLayer");
318 mp_selectionRectangeLine3->setPen(pen);
319 mp_selectionRectangeLine3->start->setType(QCPItemPosition::ptPlotCoords);
320 mp_selectionRectangeLine3->end->setType(QCPItemPosition::ptPlotCoords);
321 mp_selectionRectangeLine3->start->setCoords(0, 0);
322 mp_selectionRectangeLine3->end->setCoords(0, 0);
323 mp_selectionRectangeLine3->setVisible(false);
324
325 mp_selectionRectangeLine4 = new QCPItemLine(this);
326 mp_selectionRectangeLine4->setLayer("plotsLayer");
327 mp_selectionRectangeLine4->setPen(pen);
328 mp_selectionRectangeLine4->start->setType(QCPItemPosition::ptPlotCoords);
329 mp_selectionRectangeLine4->end->setType(QCPItemPosition::ptPlotCoords);
330 mp_selectionRectangeLine4->start->setCoords(0, 0);
331 mp_selectionRectangeLine4->end->setCoords(0, 0);
332 mp_selectionRectangeLine4->setVisible(false);
333}
334
335bool
337{
338 // qDebug();
339
340 // By default the widget comes with a graph. Remove it.
341
342 if(graphCount())
343 {
344 // QCPLayer *layer_p = graph(0)->layer();
345 // qDebug() << "The graph was on layer:" << layer_p->name();
346
347 // As of today 20210313, the graph is created on the currentLayer(), that
348 // is "main".
349
350 removeGraph(0);
351 }
352
353 // The general idea is that we do want custom layers for the trace|colormap
354 // plots.
355
356 // qDebug().noquote() << "Right before creating the new layer, layers:\n"
357 //<< allLayerNamesToString();
358
359 // Add the layer that will store all the plots and all the ancillary items.
360 addLayer(
361 "plotsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
362 // Add the layer that will store the labels.
363 addLayer("labelsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
364 // qDebug().noquote() << "Added new plotsLayer, layers:\n"
365 //<< allLayerNamesToString();
366
367 // This is required so that we get the keyboard events.
368 setFocusPolicy(Qt::StrongFocus);
369 setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables | QCP::iMultiSelect);
370
371 // We want to capture the signals emitted by the QCustomPlot base class.
372 connect(
373 this, &QCustomPlot::mouseMove, this, &BasePlotWidget::mouseMoveHandler);
374
375 connect(
376 this, &QCustomPlot::mousePress, this, &BasePlotWidget::mousePressHandler);
377
378 connect(this,
379 &QCustomPlot::mouseRelease,
380 this,
382
383 connect(
384 this, &QCustomPlot::mouseWheel, this, &BasePlotWidget::mouseWheelHandler);
385
386 connect(this,
387 &QCustomPlot::axisDoubleClick,
388 this,
390
391 connect(this, &QCustomPlot::beforeReplot, this, [&]() { emit beforeReplotSignal(); });
392 connect(this, &QCustomPlot::afterLayout, this, [&]() { emit afterLayoutSignal(); });
393 connect(this, &QCustomPlot::afterReplot, this, [&]() { emit afterReplotSignal(); });
394
395 return true;
396}
397
398void
400{
401 m_pen = pen;
402}
403
404const QPen &
406{
407 return m_pen;
408}
409
410void
411BasePlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p,
412 const QColor &new_color)
413{
414 if(plottable_p == nullptr)
415 qFatal("Pointer cannot be nullptr.");
416
417 // First this single-graph widget
418 QPen pen;
419
420 pen = plottable_p->pen();
421 pen.setColor(new_color);
422 plottable_p->setPen(pen);
423
424 replot();
425}
426
427void
428BasePlotWidget::setPlottingColor(int index, const QColor &new_color)
429{
430 if(!new_color.isValid())
431 return;
432
433 QCPGraph *graph_p = graph(index);
434
435 if(graph_p == nullptr)
436 qFatal("Programming error.");
437
438 return setPlottingColor(graph_p, new_color);
439}
440
441QColor
442BasePlotWidget::getPlottingColor(QCPAbstractPlottable *plottable_p) const
443{
444 if(plottable_p == nullptr)
445 qFatal("Programming error.");
446
447 return plottable_p->pen().color();
448}
449
450QColor
452{
453 QCPGraph *graph_p = graph(index);
454
455 if(graph_p == nullptr)
456 qFatal("Programming error.");
457
458 return getPlottingColor(graph_p);
459}
460
461void
462BasePlotWidget::setAxisLabelX(const QString &label)
463{
464 xAxis->setLabel(label);
465}
466
467void
468BasePlotWidget::setAxisLabelY(const QString &label)
469{
470 yAxis->setLabel(label);
471}
472
473// AXES RANGE HISTORY-related functions
474void
476{
477 m_xAxisRangeHistory.clear();
478 m_yAxisRangeHistory.clear();
479
480 m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
481 m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
482
483 // qDebug() << "size of history:" << m_xAxisRangeHistory.size()
484 //<< "setting index to 0";
485
486 // qDebug() << "resetting axes history to values:" << xAxis->range().lower
487 //<< "--" << xAxis->range().upper << "and" << yAxis->range().lower
488 //<< "--" << yAxis->range().upper;
489
491}
492
493//! Create new axis range history items and append them to the history.
494/*!
495
496 The plot widget is queried to get the current x/y-axis ranges and the
497 current ranges are appended to the history for x-axis and for y-axis.
498
499*/
500void
502{
503 m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
504 m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
505
507
508 // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
509 //<< "current index:" << m_lastAxisRangeHistoryIndex
510 //<< xAxis->range().lower << "--" << xAxis->range().upper << "and"
511 //<< yAxis->range().lower << "--" << yAxis->range().upper;
512}
513
514//! Go up one history element in the axis history.
515/*!
516
517 If possible, back up one history item in the axis histories and update the
518 plot's x/y-axis ranges to match that history item.
519
520*/
521void
523{
524 // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
525 //<< "current index:" << m_lastAxisRangeHistoryIndex;
526
528 {
529 // qDebug() << "current index is 0 returning doing nothing";
530
531 return;
532 }
533
534 // qDebug() << "Setting index to:" << m_lastAxisRangeHistoryIndex - 1
535 //<< "and restoring axes history to that index";
536
538}
539
540//! Get the axis histories at index \p index and update the plot ranges.
541/*!
542
543 \param index index at which to select the axis history item.
544
545 \sa updateAxesRangeHistory().
546
547*/
548void
550{
551 // qDebug() << "Axes history size:" << m_xAxisRangeHistory.size()
552 //<< "current index:" << m_lastAxisRangeHistoryIndex
553 //<< "asking to restore index:" << index;
554
555 if(index >= m_xAxisRangeHistory.size())
556 {
557 // qDebug() << "index >= history size. Returning.";
558 return;
559 }
560
561 // We want to go back to the range history item at index, which means we want
562 // to pop back all the items between index+1 and size-1.
563
564 while(m_xAxisRangeHistory.size() > index + 1)
565 m_xAxisRangeHistory.pop_back();
566
567 if(m_xAxisRangeHistory.size() - 1 != index)
568 qFatal("Programming error.");
569
570 xAxis->setRange(*(m_xAxisRangeHistory.at(index)));
571 yAxis->setRange(*(m_yAxisRangeHistory.at(index)));
572
574
575 mp_vPosTracerItem->setVisible(false);
576 mp_hPosTracerItem->setVisible(false);
577
578 mp_vStartTracerItem->setVisible(false);
579 mp_vEndTracerItem->setVisible(false);
580
581
582 // The start tracer will keep beeing represented at the last position and last
583 // size even if we call this function repetitively. So actually do not show,
584 // it will reappare as soon as the mouse is moved.
585 // if(m_shouldTracersBeVisible)
586 //{
587 // mp_vStartTracerItem->setVisible(true);
588 //}
589
590 replot();
591
593
594 // qDebug() << "restored axes history to index:" << index
595 //<< "with values:" << xAxis->range().lower << "--"
596 //<< xAxis->range().upper << "and" << yAxis->range().lower << "--"
597 //<< yAxis->range().upper;
598
599 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
600}
601
602// AXES RANGE HISTORY-related functions
603
604
605/// KEYBOARD-related EVENTS
606void
608{
609 // qDebug() << "ENTER";
610
611 // We need this because some keys modify our behaviour.
612 m_context.m_pressedKeyCode = event->key();
613
614 m_context.m_pressedKeyCodes.insert(event->key());
615 m_context.m_releasedKeyCodes.remove(event->key());
616
617 m_context.m_pressedKeyText = event->text();
618
619 // qDebug() << "Pressed key code:" << m_context.m_pressedKeyCode;
620 // qDebug() << "Pressed key codes:" << m_context.m_pressedKeyCodes;
621
622 // qDebug() << "Pressed key text:" << m_context.m_pressedKeyText;
623
624 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
625
626 if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
627 event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
628 {
629 return directionKeyPressEvent(event);
630 }
631 else if(event->key() == m_leftMousePseudoButtonKey ||
632 event->key() == m_rightMousePseudoButtonKey)
633 {
634 return mousePseudoButtonKeyPressEvent(event);
635 }
636
637 // Do not do anything here, because this function is used by derived classes
638 // that will emit the signal below. Otherwise there are going to be multiple
639 // signals sent.
640 // qDebug() << "Going to emit keyPressEventSignal(m_context);";
641 // emit keyPressEventSignal(m_context);
642}
643
644//! Handle specific key codes and trigger respective actions.
645void
647{
648 m_context.m_releasedKeyCode = event->key();
649
650 m_context.m_releasedKeyCodes.insert(event->key());
651 m_context.m_pressedKeyCodes.remove(event->key());
652
653 m_context.m_releasedKeyText = event->text();
654
655 // qDebug() << "Released code:" << m_context.m_releasedKeyCode;
656 // qDebug() << "Released codes:" << m_context.m_releasedKeyCodes;
657 // qDebug() << "Released key:" << m_context.m_releasedKeyText;
658
659 // The keyboard key is being released, set the key code to 0.
660 m_context.m_pressedKeyCode = 0;
661 m_context.m_pressedKeyText = "";
662
663 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
664
665 // Now test if the key that was released is one of the housekeeping keys.
666 if(event->key() == Qt::Key_Backspace)
667 {
668 // qDebug();
669
670 // The user wants to iterate back in the x/y axis range history.
672
673 event->accept();
674 }
675 else if(event->key() == Qt::Key_Space)
676 {
677 return spaceKeyReleaseEvent(event);
678 }
679 else if(event->key() == Qt::Key_Delete)
680 {
681 // The user wants to delete a graph. What graph is to be determined
682 // programmatically:
683
684 // If there is a single graph, then that is the graph to be removed.
685 // If there are more than one graph, then only the ones that are selected
686 // are to be removed.
687
688 // Note that the user of this widget might want to provide the user with
689 // the ability to specify if all the children graph needs to be removed
690 // also. This can be coded in key modifiers. So provide the context.
691
692 int graph_count = plottableCount();
693
694 if(!graph_count)
695 {
696 // qDebug() << "Not a single graph in the plot widget. Doing
697 // nothing.";
698
699 event->accept();
700 return;
701 }
702
703 if(graph_count == 1)
704 {
705 // qDebug() << "A single graph is in the plot widget. Emitting a graph
706 // " "destruction requested signal for it:"
707 //<< graph();
708
710 }
711 else
712 {
713 // At this point we know there are more than one graph in the plot
714 // widget. We need to get the selected one (if any).
715 QList<QCPGraph *> selected_graph_list;
716
717 selected_graph_list = selectedGraphs();
718
719 if(!selected_graph_list.size())
720 {
721 event->accept();
722 return;
723 }
724
725 // qDebug() << "Number of selected graphs to be destrobyed:"
726 //<< selected_graph_list.size();
727
728 for(int iter = 0; iter < selected_graph_list.size(); ++iter)
729 {
730 // qDebug()
731 //<< "Emitting a graph destruction requested signal for graph:"
732 //<< selected_graph_list.at(iter);
733
735 this, selected_graph_list.at(iter), m_context);
736
737 // We do not do this, because we want the slot called by the
738 // signal above to handle that removal. Remember that it is not
739 // possible to delete graphs manually.
740 //
741 // removeGraph(selected_graph_list.at(iter));
742 }
743 event->accept();
744 }
745 }
746 // End of
747 // else if(event->key() == Qt::Key_Delete)
748 else if(event->key() == Qt::Key_T)
749 {
750 // The user wants to toggle the visibiity of the tracers.
752
754 hideTracers();
755 else
756 showTracers();
757
758 event->accept();
759 }
760 else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
761 event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
762 {
763 return directionKeyReleaseEvent(event);
764 }
765 else if(event->key() == m_leftMousePseudoButtonKey ||
766 event->key() == m_rightMousePseudoButtonKey)
767 {
769 }
770 else if(event->key() == Qt::Key_S)
771 {
772 // The user is defining the size of the rhomboid fixed side. That could be
773 // either a vertical side (less intuitive) or a horizontal size (more
774 // intuitive, first exclusive implementation). But, in order to be able to
775 // perform identical integrations starting from non-transposed color maps
776 // and transposed color maps, the ability to define a vertical fixed size
777 // side of the rhomboid integration scope has become necessary.
778
779 // Check if the vertical displacement is significant (>= 10% of the color
780 // map height.
781
783 {
784 // The user is dragging the cursor vertically in a sufficient delta to
785 // consider that they are willing to define a vertical fixed size
786 // of the rhomboid integration scope.
787
788 m_context.m_integrationScopeRhombWidth = 0;
789 m_context.m_integrationScopeRhombHeight = abs(
790 m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y());
791
792 // qDebug() << "Set m_context.m_integrationScopePolyHeight to"
793 // << m_context.m_integrationScopeRhombHeight
794 // << "upon release of S key";
795 }
796 else
797 {
798 // The user is dragging the cursor horiontally to define a horizontal
799 // fixed size of the rhomboid integration scope.
800
801 m_context.m_integrationScopeRhombWidth = abs(
802 m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x());
803 m_context.m_integrationScopeRhombHeight = 0;
804
805 // qDebug() << "Set m_context.m_integrationScopePolyWidth to"
806 // << m_context.m_integrationScopeRhombWidth
807 // << "upon release of S key";
808 }
809 }
810 // At this point emit the signal, since we did not treat it. Maybe the
811 // consumer widget wants to know that the keyboard key was released.
812
813 emit keyReleaseEventSignal(event, m_context);
814}
815
816void
817BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
818{
819 // qDebug();
820}
821
822void
824{
825 // qDebug() << "event key:" << event->key();
826
827 // The user is trying to move the positional cursor/markers. There are
828 // multiple way they can do that:
829 //
830 // 1.a. Hitting the arrow left/right keys alone will search for next pixel.
831 // 1.b. Hitting the arrow left/right keys with Alt modifier will search for
832 // a multiple of pixels that might be equivalent to one 20th of the pixel
833 // width of the plot widget. 1.c Hitting the left/right keys with Alt and
834 // Shift modifiers will search for a multiple of pixels that might be the
835 // equivalent to half of the pixel width.
836 //
837 // 2. Hitting the Control modifier will move the cursor to the next data
838 // point of the graph.
839
840 int pixel_increment = 0;
841
842 if(m_context.m_keyboardModifiers == Qt::NoModifier)
843 pixel_increment = 1;
844 else if(m_context.m_keyboardModifiers == Qt::AltModifier)
845 pixel_increment = 50;
846
847 // The user is moving the positional markers. This is equivalent to a
848 // non-dragging cursor movement to the next pixel. Note that the origin is
849 // located at the top left, so key down increments and key up decrements.
850
851 if(event->key() == Qt::Key_Left)
852 horizontalMoveMouseCursorCountPixels(-pixel_increment);
853 else if(event->key() == Qt::Key_Right)
855 else if(event->key() == Qt::Key_Up)
856 verticalMoveMouseCursorCountPixels(-pixel_increment);
857 else if(event->key() == Qt::Key_Down)
858 verticalMoveMouseCursorCountPixels(pixel_increment);
859
860 event->accept();
861}
862
863void
865{
866 // qDebug() << "event key:" << event->key();
867 event->accept();
868}
869
870void
872 [[maybe_unused]] QKeyEvent *event)
873{
874 // qDebug();
875}
876
877void
879{
880
881 QPointF pixel_coordinates(
882 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
883 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
884
885 Qt::MouseButton button = Qt::NoButton;
886 QEvent::Type q_event_type = QEvent::MouseButtonPress;
887
888 if(event->key() == m_leftMousePseudoButtonKey)
889 {
890 // Toggles the left mouse button on/off
891
892 button = Qt::LeftButton;
893
894 m_context.m_isLeftPseudoButtonKeyPressed =
895 !m_context.m_isLeftPseudoButtonKeyPressed;
896
897 if(m_context.m_isLeftPseudoButtonKeyPressed)
898 q_event_type = QEvent::MouseButtonPress;
899 else
900 q_event_type = QEvent::MouseButtonRelease;
901 }
902 else if(event->key() == m_rightMousePseudoButtonKey)
903 {
904 // Toggles the right mouse button.
905
906 button = Qt::RightButton;
907
908 m_context.m_isRightPseudoButtonKeyPressed =
909 !m_context.m_isRightPseudoButtonKeyPressed;
910
911 if(m_context.m_isRightPseudoButtonKeyPressed)
912 q_event_type = QEvent::MouseButtonPress;
913 else
914 q_event_type = QEvent::MouseButtonRelease;
915 }
916
917 // qDebug() << "pressed/released pseudo button:" << button
918 //<< "q_event_type:" << q_event_type;
919
920 // Synthesize a QMouseEvent and use it.
921
922 QMouseEvent *mouse_event_p =
923 new QMouseEvent(q_event_type,
924 pixel_coordinates,
925 mapToGlobal(pixel_coordinates.toPoint()),
926 mapToGlobal(pixel_coordinates.toPoint()),
927 button,
928 button,
929 m_context.m_keyboardModifiers,
930 Qt::MouseEventSynthesizedByApplication);
931
932 if(q_event_type == QEvent::MouseButtonPress)
933 mousePressHandler(mouse_event_p);
934 else
935 mouseReleaseHandler(mouse_event_p);
936
937 delete mouse_event_p;
938 // event->accept();
939}
940
941/// KEYBOARD-related EVENTS
942
943
944/// MOUSE-related EVENTS
945
946void
948{
949
950 // If we have no focus, then get it. See setFocus() to understand why asking
951 // for focus is cosly and thus why we want to make this decision first.
952 if(!hasFocus())
953 setFocus();
954
955 // qDebug() << (graph() != nullptr);
956 // if(graph(0) != nullptr)
957 // { // check if the widget contains some graphs
958
959 // The event->button() must be by Qt instructions considered to be 0.
960
961 // Whatever happens, we want to store the plot coordinates of the current
962 // mouse cursor position (will be useful later for countless needs).
963
964 QPointF mousePoint = event->position();
965
966 // qDebug() << "local mousePoint position in pixels:" << mousePoint;
967
968 m_context.m_lastCursorHoveredPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
969 m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
970
971 // qDebug() << "lastCursorHoveredPoint coord:"
972 //<< m_context.m_lastCursorHoveredPoint;
973
974 // Now, depending on the button(s) (if any) that are pressed or not, we
975 // have a different processing.
976
977 // qDebug();
978
979 if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
980 m_context.m_pressedMouseButtons & Qt::RightButton)
981 {
983 // qDebug() << "Emitting mouseMoveDraggingCursorSignal";
985 }
986 else
988 // }
989 // qDebug();
990 event->accept();
991}
992
993void
995{
996 Q_UNUSED(event)
997
998 // qDebug();
999 m_context.m_isMouseDragging = false;
1000
1001 // qDebug();
1002 // We are not dragging the mouse (no button pressed), simply let this
1003 // widget's consumer know the position of the cursor and update the markers.
1004 // The consumer of this widget will update mouse cursor position at
1005 // m_context.m_lastCursorHoveredPoint if so needed.
1006
1007 emit lastCursorHoveredPointSignal(m_context.m_lastCursorHoveredPoint);
1008
1009 // qDebug();
1010
1011 // We are not dragging, so we do not show the region end tracer we only
1012 // show the anchoring start trace that might be of use if the user starts
1013 // using the arrow keys to move the cursor.
1014 if(mp_vEndTracerItem != nullptr)
1015 mp_vEndTracerItem->setVisible(false);
1016
1017 // qDebug();
1018 // Only bother with the tracers if the user wants them to be visible.
1019 // Their crossing point must be exactly at the last cursor-hovered point.
1020
1022 {
1023 // We are not dragging, so only show the position markers (v and h);
1024
1025 // qDebug();
1026 if(mp_hPosTracerItem != nullptr)
1027 {
1028 // Horizontal position tracer.
1029 mp_hPosTracerItem->setVisible(true);
1030 mp_hPosTracerItem->start->setCoords(
1031 xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
1032 mp_hPosTracerItem->end->setCoords(
1033 xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
1034 }
1035
1036 // qDebug();
1037 // Vertical position tracer.
1038 if(mp_vPosTracerItem != nullptr)
1039 {
1040 mp_vPosTracerItem->setVisible(true);
1041
1042 mp_vPosTracerItem->setVisible(true);
1043 mp_vPosTracerItem->start->setCoords(
1044 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1045 mp_vPosTracerItem->end->setCoords(
1046 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1047 }
1048
1049 // qDebug();
1050 replot();
1051 }
1052
1053
1054 return;
1055}
1056
1057void
1059{
1060 // qDebug();
1061
1062 m_context.m_isMouseDragging = true;
1063
1064 // Now store the mouse position data into the the current drag point
1065 // member datum, that will be used in countless occasions later.
1066 m_context.m_currentDragPoint = m_context.m_lastCursorHoveredPoint;
1067 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1068
1069 // When we drag (either keyboard or mouse), we hide the position markers
1070 // (black) and we show the start and end vertical markers for the region.
1071 // Then, we draw the horizontal region range marker that delimits
1072 // horizontally the dragged-over region.
1073
1074 if(mp_hPosTracerItem != nullptr)
1075 mp_hPosTracerItem->setVisible(false);
1076 if(mp_vPosTracerItem != nullptr)
1077 mp_vPosTracerItem->setVisible(false);
1078
1079 // Only bother with the tracers if the user wants them to be visible.
1081 {
1082
1083 // The vertical end tracer position must be refreshed.
1084 mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
1085 yAxis->range().upper);
1086
1087 mp_vEndTracerItem->end->setCoords(m_context.m_currentDragPoint.x(),
1088 yAxis->range().lower);
1089
1090 mp_vEndTracerItem->setVisible(true);
1091 }
1092
1093 // Whatever the button, when we are dealing with the axes, we do not
1094 // want to show any of the tracers.
1095
1096 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1097 {
1098 if(mp_hPosTracerItem != nullptr)
1099 mp_hPosTracerItem->setVisible(false);
1100 if(mp_vPosTracerItem != nullptr)
1101 mp_vPosTracerItem->setVisible(false);
1102
1103 if(mp_vStartTracerItem != nullptr)
1104 mp_vStartTracerItem->setVisible(false);
1105 if(mp_vEndTracerItem != nullptr)
1106 mp_vEndTracerItem->setVisible(false);
1107 }
1108 else
1109 {
1110 // qDebug() << "Not moving the mouse cursor over any of the axes.";
1111
1112 // Since we are not dragging the mouse cursor over the axes, make sure
1113 // we store the drag directions in the context, as this might be
1114 // useful for later operations.
1115 // qDebug() << "Recording the drag direction(s).";
1116
1117 m_context.recordDragDirections();
1118
1119 // qDebug() << "Drag direction(s): " <<
1120 // m_context.dragDirectionsToString();
1121 }
1122
1123 // Because when we drag the mouse button (whatever the button) we need to
1124 // know what is the drag delta (distance between start point and current
1125 // point of the drag operation) on both axes, ask that these x|y deltas be
1126 // computed.
1128
1129 // Now deal with the BUTTON-SPECIFIC CODE.
1130
1131 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
1132 {
1134 }
1135 else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
1136 {
1138 }
1139}
1140
1141void
1143{
1144 Q_UNUSED(event)
1145
1146 // qDebug() << "The left button is dragging.";
1147
1148 // Set the context.m_isMeasuringDistance to false, which later might be set
1149 // to true if effectively we are measuring a distance. This is required
1150 // because the derived widget classes might want to know if they have to
1151 // perform some action on the basis that context is measuring a distance,
1152 // for example the mass spectrum-specific widget might want to compute
1153 // deconvolutions.
1154
1155 m_context.m_isMeasuringDistance = false;
1156
1157 // Let's first check if the mouse drag operation originated on either
1158 // axis. In that case, the user is performing axis reframing or rescaling.
1159
1160 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1161 {
1162 // qDebug() << "Click was on one of the axes.";
1163
1164 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1165 {
1166 // The user is asking a rescale of the plot.
1167
1168 // We know that we do not want the tracers when we perform axis
1169 // rescaling operations.
1170
1171 if(mp_hPosTracerItem != nullptr)
1172 mp_hPosTracerItem->setVisible(false);
1173 if(mp_vPosTracerItem != nullptr)
1174 mp_vPosTracerItem->setVisible(false);
1175
1176 if(mp_vStartTracerItem != nullptr)
1177 mp_vStartTracerItem->setVisible(false);
1178 if(mp_vEndTracerItem != nullptr)
1179 mp_vEndTracerItem->setVisible(false);
1180
1181 // This operation is particularly intensive, thus we want to
1182 // reduce the number of calculations by skipping this calculation
1183 // a number of times. The user can ask for this feature by
1184 // clicking the 'Q' letter.
1185
1186 if(m_context.m_pressedKeyCode == Qt::Key_Q)
1187 {
1189 {
1191 return;
1192 }
1193 else
1194 {
1196 }
1197 }
1198
1199 // qDebug() << "Asking that the axes be rescaled.";
1200
1201 axisRescale();
1202 }
1203 else
1204 {
1205 // The user was simply dragging the axis. Just pan, that is slide
1206 // the plot in the same direction as the mouse movement and with the
1207 // same amplitude.
1208
1209 // qDebug() << "Asking that the axes be panned.";
1210
1211 axisPan();
1212 }
1213
1214 return;
1215 }
1216
1217 // At this point we understand that the user was not performing any
1218 // panning/rescaling operation by clicking on any one of the axes.. Go on
1219 // with other possibilities.
1220
1221 // Let's check if the user is actually drawing a rectangle (covering a
1222 // real area) or is drawing a line.
1223
1224 // qDebug() << "The mouse dragging did not originate on an axis.";
1225
1227 {
1228 // qDebug() << "Apparently the selection is two-dimensional.";
1229
1230 // When we draw a two-dimensional integration scope, the tracers are of no
1231 // use.
1232
1233 if(mp_hPosTracerItem != nullptr)
1234 mp_hPosTracerItem->setVisible(false);
1235 if(mp_vPosTracerItem != nullptr)
1236 mp_vPosTracerItem->setVisible(false);
1237
1238 if(mp_vStartTracerItem != nullptr)
1239 mp_vStartTracerItem->setVisible(false);
1240 if(mp_vEndTracerItem != nullptr)
1241 mp_vEndTracerItem->setVisible(false);
1242
1243 // Draw the rectangle, false, not as line segment and
1244 // false, not for integration
1245 drawSelectionRectangleAndPrepareZoom(false /*as_line_segment*/,
1246 false /* for_integration*/);
1247
1248 // Draw the selection width/height text
1251 }
1252 else
1253 {
1254 // qDebug() << "Apparently we are measuring a delta.";
1255
1256 // Draw the rectangle, true, as line segment and
1257 // false, not for integration
1259
1260 // The pure position tracers should be hidden.
1261 if(mp_hPosTracerItem != nullptr)
1262 mp_hPosTracerItem->setVisible(true);
1263 if(mp_vPosTracerItem != nullptr)
1264 mp_vPosTracerItem->setVisible(true);
1265
1266 // Then, make sure the region range vertical tracers are visible.
1267 if(mp_vStartTracerItem != nullptr)
1268 mp_vStartTracerItem->setVisible(true);
1269 if(mp_vEndTracerItem != nullptr)
1270 mp_vEndTracerItem->setVisible(true);
1271
1272 // Draw the selection width text
1274 }
1275}
1276
1277void
1279{
1280 Q_UNUSED(event)
1281 // qDebug() << "The right button is dragging.";
1282
1283 // Set the context.m_isMeasuringDistance to false, which later might be set
1284 // to true if effectively we are measuring a distance. This is required
1285 // because the derived widgets might want to know if they have to perform
1286 // some action on the basis that context is measuring a distance, for
1287 // example the mass spectrum-specific widget might want to compute
1288 // deconvolutions.
1289
1290 m_context.m_isMeasuringDistance = false;
1291
1293 {
1294 // qDebug() << "Apparently the selection has height.";
1295
1296 // When we draw a rectangle the tracers are of no use.
1297
1298 if(mp_hPosTracerItem != nullptr)
1299 mp_hPosTracerItem->setVisible(false);
1300 if(mp_vPosTracerItem != nullptr)
1301 mp_vPosTracerItem->setVisible(false);
1302
1303 if(mp_vStartTracerItem != nullptr)
1304 mp_vStartTracerItem->setVisible(false);
1305 if(mp_vEndTracerItem != nullptr)
1306 mp_vEndTracerItem->setVisible(false);
1307
1308 // Draw the rectangle, false for as_line_segment and true for
1309 // integration.
1311
1312 // Draw the selection width/height text
1315 }
1316 else
1317 {
1318 // qDebug() << "Apparently the selection is a not a rectangle.";
1319
1320 // Draw the rectangle, true as line segment and
1321 // true for integration
1323
1324 // Draw the selection width text
1326 }
1327}
1328
1329void
1331{
1332 // qDebug() << "Entering";
1333
1334 // When the user clicks this widget it has to take focus.
1335 setFocus();
1336
1337 QPointF mousePoint = event->position();
1338
1339 m_context.m_lastPressedMouseButton = event->button();
1340 m_context.m_mouseButtonsAtMousePress = event->buttons();
1341
1342 // The pressedMouseButtons must continually inform on the status of
1343 // pressed buttons so add the pressed button.
1344 m_context.m_pressedMouseButtons |= event->button();
1345
1346 // qDebug().noquote() << m_context.toString();
1347
1348 // In all the processing of the events, we need to know if the user is
1349 // clicking somewhere with the intent to change the plot ranges (reframing
1350 // or rescaling the plot).
1351 //
1352 // Reframing the plot means that the new x and y axes ranges are modified
1353 // so that they match the region that the user has encompassed by left
1354 // clicking the mouse and dragging it over the plot. That is we reframe
1355 // the plot so that it contains only the "selected" region.
1356 //
1357 // Rescaling the plot means the the new x|y axis range is modified such
1358 // that the lower axis range is constant and the upper axis range is moved
1359 // either left or right by the same amont as the x|y delta encompassed by
1360 // the user moving the mouse. The axis is thus either compressed (mouse
1361 // movement is leftwards) or un-compressed (mouse movement is rightwards).
1362
1363 // There are two ways to perform axis range modifications:
1364 //
1365 // 1. By clicking on any of the axes
1366 // 2. By clicking on the plot region but using keyboard key modifiers,
1367 // like Alt and Ctrl.
1368 //
1369 // We need to know both cases separately which is why we need to perform a
1370 // number of tests below.
1371
1372 // Let's check if the click is on the axes, either X or Y, because that
1373 // will allow us to take proper actions.
1374
1375 if(isClickOntoXAxis(mousePoint))
1376 {
1377 // The X axis was clicked upon, we need to document that:
1378 // qDebug() << __FILE__ << __LINE__
1379 //<< "Layout element is axisRect and actually on an X axis part.";
1380
1381 m_context.m_wasClickOnXAxis = true;
1382
1383 // int currentInteractions = interactions();
1384 // currentInteractions |= QCP::iRangeDrag;
1385 // setInteractions((QCP::Interaction)currentInteractions);
1386 // axisRect()->setRangeDrag(xAxis->orientation());
1387 }
1388 else
1389 m_context.m_wasClickOnXAxis = false;
1390
1391 if(isClickOntoYAxis(mousePoint))
1392 {
1393 // The Y axis was clicked upon, we need to document that:
1394 // qDebug() << __FILE__ << __LINE__
1395 //<< "Layout element is axisRect and actually on an Y axis part.";
1396
1397 m_context.m_wasClickOnYAxis = true;
1398
1399 // int currentInteractions = interactions();
1400 // currentInteractions |= QCP::iRangeDrag;
1401 // setInteractions((QCP::Interaction)currentInteractions);
1402 // axisRect()->setRangeDrag(yAxis->orientation());
1403 }
1404 else
1405 m_context.m_wasClickOnYAxis = false;
1406
1407 // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1408
1409 if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
1410 {
1411 // qDebug() << __FILE__ << __LINE__
1412 // << "Click outside of axes.";
1413
1414 // int currentInteractions = interactions();
1415 // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1416 // setInteractions((QCP::Interaction)currentInteractions);
1417 }
1418
1419 m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1420 m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1421
1422 // Now install the vertical start tracer at the last cursor hovered
1423 // position.
1424 if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
1425 mp_vStartTracerItem->setVisible(true);
1426
1427 if(mp_vStartTracerItem != nullptr)
1428 {
1429 mp_vStartTracerItem->start->setCoords(
1430 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1431 mp_vStartTracerItem->end->setCoords(
1432 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1433 }
1434
1435 replot();
1436
1437 emit mousePressEventSignal(event, m_context);
1438
1439 // qDebug() << "Exiting after having emitted mousePressEventSignal with base
1440 // context:"
1441 // << m_context.toString();
1442}
1443
1444void
1446{
1447 // qDebug() << "Entering";
1448
1449 // Now the real code of this function.
1450
1451 m_context.m_lastReleasedMouseButton = event->button();
1452
1453 // The event->buttons() is the description of the buttons that are pressed
1454 // at the moment the handler is invoked, that is now. If left and right were
1455 // pressed, and left was released, event->buttons() would be right.
1456 m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1457
1458 // The pressedMouseButtons must continually inform on the status of pressed
1459 // buttons so remove the released button.
1460 m_context.m_pressedMouseButtons ^= event->button();
1461
1462 // qDebug().noquote() << m_context.toString();
1463
1464 // We'll need to know if modifiers were pressed a the moment the user
1465 // released the mouse button.
1466 m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1467
1468 if(!m_context.m_isMouseDragging)
1469 {
1470 // Let the user know that the mouse was *not* being dragged.
1471 m_context.m_wasMouseDragging = false;
1472
1473 event->accept();
1474
1475 return;
1476 }
1477
1478 // Let the user know that the mouse was being dragged.
1479 m_context.m_wasMouseDragging = true;
1480
1481 // We cannot hide all items in one go because we rely on their visibility
1482 // to know what kind of dragging operation we need to perform (line-only
1483 // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1484 // only thing we know is that we can make the text invisible.
1485
1486 // Same for the x delta text item
1487 mp_xDeltaTextItem->setVisible(false);
1488 mp_yDeltaTextItem->setVisible(false);
1489
1490 // We do not show the end vertical region range marker.
1491 mp_vEndTracerItem->setVisible(false);
1492
1493 // Horizontal position tracer.
1494 mp_hPosTracerItem->setVisible(true);
1495 mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1496 m_context.m_lastCursorHoveredPoint.y());
1497 mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1498 m_context.m_lastCursorHoveredPoint.y());
1499
1500 // Vertical position tracer.
1501 mp_vPosTracerItem->setVisible(true);
1502
1503 mp_vPosTracerItem->setVisible(true);
1504 mp_vPosTracerItem->start->setCoords(m_context.m_lastCursorHoveredPoint.x(),
1505 yAxis->range().upper);
1506 mp_vPosTracerItem->end->setCoords(m_context.m_lastCursorHoveredPoint.x(),
1507 yAxis->range().lower);
1508
1509 // Force replot now because later that call might not be performed.
1510 replot();
1511
1512 // If we were using the "quantum" display for the rescale of the axes
1513 // using the Ctrl-modified left button click drag in the axes, then reset
1514 // the count to 0.
1516
1517 // By definition we are stopping the drag operation by releasing the mouse
1518 // button. Whatever that mouse button was pressed before and if there was
1519 // one pressed before. We cannot set that boolean value to false before
1520 // this place, because we call a number of routines above that need to know
1521 // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1522 // example.
1523
1524 m_context.m_isMouseDragging = false;
1525
1526 // Now that we have computed the useful ranges, we need to check what to do
1527 // depending on the button that was pressed.
1528
1529 if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1530 {
1531 return mouseReleaseHandlerLeftButton(event);
1532 }
1533 else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1534 {
1535 return mouseReleaseHandlerRightButton(event);
1536 }
1537
1538 // FIXME: should we really accept ? No, since we pass the event on.
1539 // event->accept();
1540
1541 // Before returning, emit the signal for the user of
1542 // this class consumption.
1543 // qDebug() << "Emitting mouseReleaseEventSignal.";
1545
1546 // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with base
1547 // context:"
1548 // << m_context.toString();
1549
1550 return;
1551}
1552
1553void
1555{
1556 Q_UNUSED(event)
1557 // qDebug();
1558
1559 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1560 {
1561
1562 // When the mouse move handler pans the plot, we cannot store each axes
1563 // range history element that would mean store a huge amount of such
1564 // elements, as many element as there are mouse move event handled by
1565 // the Qt event queue. But we can store an axis range history element
1566 // for the last situation of the mouse move: when the button is
1567 // released:
1568
1570
1571 // qDebug() << "emit plotRangesChangedSignal(m_context);"
1572
1573 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
1574
1575 replot();
1576
1577 // Nothing else to do.
1578 return;
1579 }
1580
1581 // There are two possibilities:
1582 //
1583 // 1. The full integration scope (four lines) were currently drawn, which
1584 // means the user was willing to perform a zoom operation.
1585 //
1586 // 2. Only the first top line was drawn, which means the user was dragging
1587 // the cursor horizontally. That might have two ends, as shown below.
1588
1589 // So, first check what is drawn of the selection polygon.
1590
1591 SelectionDrawingLines selection_drawing_lines =
1593
1594 // Now that we know what was currently drawn of the selection polygon, we
1595 // can remove it. true to reset the values to 0.
1597
1598 // Force replot now because later that call might not be performed.
1599 replot();
1600
1601 if(selection_drawing_lines == SelectionDrawingLines::FULL_POLYGON)
1602 {
1603 // qDebug() << "Yes, the full polygon was visible";
1604
1605 // If we were dragging with the left button pressed and could draw a
1606 // rectangle, then we were preparing a zoom operation. Let's bring that
1607 // operation to its accomplishment.
1608
1609 axisZoom();
1610
1611 return;
1612 }
1613 else if(selection_drawing_lines == SelectionDrawingLines::TOP_LINE)
1614 {
1615 // qDebug() << "No, only the top line of the full polygon was visible";
1616
1617 // The user was dragging the left mouse cursor and that may mean they
1618 // were measuring a distance or willing to perform a special zoom
1619 // operation if the Ctrl key was down.
1620
1621 // If the user started by clicking in the plot region, dragged the mouse
1622 // cursor with the left button and pressed the Ctrl modifier, then that
1623 // means that they wanted to do a rescale over the x-axis in the form of
1624 // a reframing.
1625
1626 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1627 {
1628 return axisReframe();
1629 }
1630 }
1631 // else
1632 // qDebug() << "Another possibility.";
1633}
1634
1635void
1637{
1638 Q_UNUSED(event)
1639 // qDebug();
1640 // The right button is used for the integrations. Not for axis range
1641 // operations. So all we have to do is remove the various graphics items and
1642 // send a signal with the context that contains all the data required by the
1643 // user to perform the integrations over the right plot regions.
1644
1645 // Whatever we were doing we need to make the selection line invisible:
1646
1647 if(mp_xDeltaTextItem->visible())
1648 mp_xDeltaTextItem->setVisible(false);
1649 if(mp_yDeltaTextItem->visible())
1650 mp_yDeltaTextItem->setVisible(false);
1651
1652 // Also make the vertical end tracer invisible.
1653 mp_vEndTracerItem->setVisible(false);
1654
1655 // Once the integration is asked for, then the selection rectangle if of no
1656 // more use.
1658
1659 // Force replot now because later that call might not be performed.
1660 replot();
1661
1662 // Note that we only request an integration if the x-axis delta is enough.
1663
1664 double x_delta_pixel =
1665 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1666 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1667
1668 if(x_delta_pixel > 3)
1669 {
1670 // qDebug() << "Emitting integrationRequestedSignal(m_context)";
1672 }
1673 // else
1674 // qDebug() << "Not asking for integration.";
1675}
1676
1677void
1678BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
1679{
1680 // We should record the new range values each time the wheel is used to
1681 // zoom/unzoom.
1682
1683 m_context.m_xRange = QCPRange(xAxis->range());
1684 m_context.m_yRange = QCPRange(yAxis->range());
1685
1686 // qDebug() << "New x range: " << m_context.m_xRange;
1687 // qDebug() << "New y range: " << m_context.m_yRange;
1688
1690
1692 emit mouseWheelEventSignal(event, m_context);
1693
1694 event->accept();
1695}
1696
1697void
1699 QCPAxis *axis,
1700 [[maybe_unused]] QCPAxis::SelectablePart part,
1701 QMouseEvent *event)
1702{
1703 // qDebug();
1704
1705 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1706
1707 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1708 {
1709 // qDebug();
1710
1711 // If the Ctrl modifiers is active, then both axes are to be reset. Also
1712 // the histories are reset also.
1713
1714 rescaleAxes();
1716 }
1717 else
1718 {
1719 // qDebug();
1720
1721 // Only the axis passed as parameter is to be rescaled.
1722 // Reset the range of that axis to the max view possible.
1723
1724 axis->rescale();
1725
1727
1728 event->accept();
1729 }
1730
1731 // The double-click event does not cancel the mouse press event. That is, if
1732 // left-double-clicking, at the end of the operation the button still
1733 // "pressed". We need to remove manually the button from the pressed buttons
1734 // context member.
1735
1736 m_context.m_pressedMouseButtons ^= event->button();
1737
1739
1741
1742 replot();
1743}
1744
1745bool
1746BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1747{
1748 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1749
1750 if(layoutElement &&
1751 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1752 {
1753 // The graph is *inside* the axisRect that is the outermost envelope of
1754 // the graph. Thus, if we want to know if the click was indeed on an
1755 // axis, we need to check what selectable part of the the axisRect we
1756 // were clicking:
1757 QCPAxis::SelectablePart selectablePart;
1758
1759 selectablePart = xAxis->getPartAt(mousePoint);
1760
1761 if(selectablePart == QCPAxis::spAxisLabel ||
1762 selectablePart == QCPAxis::spAxis ||
1763 selectablePart == QCPAxis::spTickLabels)
1764 return true;
1765 }
1766
1767 return false;
1768}
1769
1770bool
1771BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1772{
1773 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1774
1775 if(layoutElement &&
1776 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1777 {
1778 // The graph is *inside* the axisRect that is the outermost envelope of
1779 // the graph. Thus, if we want to know if the click was indeed on an
1780 // axis, we need to check what selectable part of the the axisRect we
1781 // were clicking:
1782 QCPAxis::SelectablePart selectablePart;
1783
1784 selectablePart = yAxis->getPartAt(mousePoint);
1785
1786 if(selectablePart == QCPAxis::spAxisLabel ||
1787 selectablePart == QCPAxis::spAxis ||
1788 selectablePart == QCPAxis::spTickLabels)
1789 return true;
1790 }
1791
1792 return false;
1793}
1794
1795/// MOUSE-related EVENTS
1796
1797
1798/// MOUSE MOVEMENTS mouse/keyboard-triggered
1799
1800int
1802{
1803 // The user is dragging the mouse, probably to rescale the axes, but we need
1804 // to sort out in which direction the drag is happening.
1805
1806 // This function should be called after calculateDragDeltas, so that
1807 // m_context has the proper x/y delta values that we'll compare.
1808
1809 // Note that we cannot compare simply x or y deltas because the y axis might
1810 // have a different scale that the x axis. So we first need to convert the
1811 // positions to pixels.
1812
1813 double x_delta_pixel =
1814 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1815 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1816
1817 double y_delta_pixel =
1818 fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1819 yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1820
1821 if(x_delta_pixel > y_delta_pixel)
1822 return Qt::Horizontal;
1823
1824 return Qt::Vertical;
1825}
1826
1827void
1829{
1830 // First convert the graph coordinates to pixel coordinates.
1831
1832 QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1833 yAxis->coordToPixel(graph_coordinates.y()));
1834
1835 moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1836}
1837
1838void
1840{
1841 // qDebug() << "Calling set pos with new cursor position.";
1842 QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1843}
1844
1845void
1847{
1848 QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1849
1850 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1851 yAxis->coordToPixel(graph_coord.y()));
1852
1853 // Now we need ton convert the new coordinates to the global position system
1854 // and to move the cursor to that new position. That will create an event to
1855 // move the mouse cursor.
1856
1857 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1858}
1859
1860QPointF
1862{
1863 QPointF pixel_coordinates(
1864 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1865 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1866
1867 // Now convert back to local coordinates.
1868
1869 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1870 yAxis->pixelToCoord(pixel_coordinates.y()));
1871
1872 return graph_coordinates;
1873}
1874
1875void
1877{
1878
1879 QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1880
1881 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1882 yAxis->coordToPixel(graph_coord.y()));
1883
1884 // Now we need ton convert the new coordinates to the global position system
1885 // and to move the cursor to that new position. That will create an event to
1886 // move the mouse cursor.
1887
1888 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1889}
1890
1891QPointF
1893{
1894 QPointF pixel_coordinates(
1895 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1896 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1897
1898 // Now convert back to local coordinates.
1899
1900 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1901 yAxis->pixelToCoord(pixel_coordinates.y()));
1902
1903 return graph_coordinates;
1904}
1905
1906/// MOUSE MOVEMENTS mouse/keyboard-triggered
1907
1908
1909/// RANGE-related functions
1910
1911QCPRange
1912BasePlotWidget::getRangeX(bool &found_range, int index, RangeType range_type) const
1913{
1914 QCPGraph *graph_p = graph(index);
1915
1916 if(graph_p == nullptr)
1917 qFatal("Programming error.");
1918
1919 if(range_type == RangeType::OUTERMOST)
1920 return graph_p->getKeyRange(found_range);
1921 else if(range_type == RangeType::INNERMOST)
1922 return graph_p->keyAxis()->range();
1923 else
1924 qFatal("Programming error.");
1925
1926 return QCPRange();
1927}
1928
1929void
1931 bool &found_range, int index, RangeType range_type, double &start, double &stop) const
1932{
1933 QCPRange range = getRangeX(found_range, index, range_type);
1934
1935 start = range.lower;
1936 stop = range.upper;
1937}
1938
1939void
1941 int index,
1942 RangeType range_type,
1943 std::pair<double, double> &start_stop_pair) const
1944{
1945 QCPRange range = getRangeX(found_range, index, range_type);
1946
1947 start_stop_pair.first = range.lower;
1948 start_stop_pair.second = range.upper;
1949}
1950
1951QCPRange
1952BasePlotWidget::getRangeY(bool &found_range, int index, RangeType range_type) const
1953{
1954 QCPGraph *graph_p = graph(index);
1955
1956 if(graph_p == nullptr)
1957 qFatal("Programming error.");
1958
1959 if(range_type == RangeType::OUTERMOST)
1960 return graph_p->getValueRange(found_range);
1961 else if(range_type == RangeType::INNERMOST)
1962 return graph_p->valueAxis()->range();
1963 else
1964 qFatal("Programming error.");
1965
1966 return QCPRange();
1967}
1968
1969void
1971 bool &found_range, int index, RangeType range_type, double &start, double &stop) const
1972{
1973 QCPRange range = getRangeY(found_range, index, range_type);
1974
1975 start = range.lower;
1976 stop = range.upper;
1977}
1978
1979void
1981 int index,
1982 RangeType range_type,
1983 std::pair<double, double> &start_stop_pair) const
1984{
1985 QCPRange range = getRangeY(found_range, index, range_type);
1986
1987 start_stop_pair.first = range.lower;
1988 start_stop_pair.second = range.upper;
1989}
1990
1991QCPRange
1993 RangeType range_type,
1994 bool &found_range) const
1995{
1996 // Iterate in all the graphs in this widget and return a QCPRange that has
1997 // its lower member as the greatest lower value of all
1998 // its upper member as the smallest upper value of all
1999
2000 if(!graphCount())
2001 {
2002 found_range = false;
2003
2004 return QCPRange(0, 1);
2005 }
2006
2007 if(graphCount() == 1)
2008 return graph()->getKeyRange(found_range);
2009
2010 bool found_at_least_one_range = false;
2011
2012 // Create an invalid range.
2013 QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
2014
2015 for(int iter = 0; iter < graphCount(); ++iter)
2016 {
2017 QCPRange temp_range;
2018
2019 bool found_range_for_iter = false;
2020
2021 QCPGraph *graph_p = graph(iter);
2022
2023 // Depending on the axis param, select the key or value range.
2024
2025 if(axis == Enums::Axis::x)
2026 temp_range = graph_p->getKeyRange(found_range_for_iter);
2027 else if(axis == Enums::Axis::y)
2028 temp_range = graph_p->getValueRange(found_range_for_iter);
2029 else
2030 qFatal("Cannot reach this point. Programming error.");
2031
2032 // Was a range found for the iterated graph ? If not skip this
2033 // iteration.
2034
2035 if(!found_range_for_iter)
2036 continue;
2037
2038 // While the innermost_range is invalid, we need to seed it with a good
2039 // one. So check this.
2040
2041 if(!QCPRange::validRange(result_range))
2042 qFatal("The obtained range is invalid !");
2043
2044 // At this point we know the obtained range is OK.
2045 result_range = temp_range;
2046
2047 // We found at least one valid range!
2048 found_at_least_one_range = true;
2049
2050 // At this point we have two valid ranges to compare. Depending on
2051 // range_type, we need to perform distinct comparisons.
2052
2053 if(range_type == RangeType::INNERMOST)
2054 {
2055 if(temp_range.lower > result_range.lower)
2056 result_range.lower = temp_range.lower;
2057 if(temp_range.upper < result_range.upper)
2058 result_range.upper = temp_range.upper;
2059 }
2060 else if(range_type == RangeType::OUTERMOST)
2061 {
2062 if(temp_range.lower < result_range.lower)
2063 result_range.lower = temp_range.lower;
2064 if(temp_range.upper > result_range.upper)
2065 result_range.upper = temp_range.upper;
2066 }
2067 else
2068 qFatal("Cannot reach this point. Programming error.");
2069
2070 // Continue to next graph, if any.
2071 }
2072 // End of
2073 // for(int iter = 0; iter < graphCount(); ++iter)
2074
2075 // Let the caller know if we found at least one range.
2076 found_range = found_at_least_one_range;
2077
2078 return result_range;
2079}
2080
2081QCPRange
2083{
2084
2085 return getRange(Enums::Axis::x, RangeType::INNERMOST, found_range);
2086}
2087
2088QCPRange
2090{
2091 return getRange(Enums::Axis::x, RangeType::OUTERMOST, found_range);
2092}
2093
2094QCPRange
2096{
2097
2098 return getRange(Enums::Axis::y, RangeType::INNERMOST, found_range);
2099}
2100
2101QCPRange
2103{
2104 return getRange(Enums::Axis::y, RangeType::OUTERMOST, found_range);
2105}
2106
2107/// RANGE-related functions
2108
2109
2110/// PLOTTING / REPLOTTING functions
2111
2112void
2114{
2115 // Get the current x lower/upper range, that is, leftmost/rightmost x
2116 // coordinate.
2117 double xLower = xAxis->range().lower;
2118 double xUpper = xAxis->range().upper;
2119
2120 // Get the current y lower/upper range, that is, bottommost/topmost y
2121 // coordinate.
2122 double yLower = yAxis->range().lower;
2123 double yUpper = yAxis->range().upper;
2124
2125 // This function is called only when the user has clicked on the x/y axis or
2126 // when the user has dragged the left mouse button with the Ctrl key
2127 // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
2128 // move handler. So we need to test which axis was clicked-on.
2129
2130 if(m_context.m_wasClickOnXAxis)
2131 {
2132 // We are changing the range of the X axis.
2133
2134 // If xDelta is < 0, then we were dragging from right to left, we are
2135 // compressing the view on the x axis, by adding new data to the right
2136 // hand size of the graph. So we add xDelta to the upper bound of the
2137 // range. Otherwise we are uncompressing the view on the x axis and
2138 // remove the xDelta from the upper bound of the range. This is why we
2139 // have the
2140 // '-'
2141 // and not '+' below;
2142
2143 xAxis->setRange(xLower, xUpper - m_context.m_xDelta);
2144 }
2145 // End of
2146 // if(m_context.m_wasClickOnXAxis)
2147 else // that is, if(m_context.m_wasClickOnYAxis)
2148 {
2149 // We are changing the range of the Y axis.
2150
2151 // See above for an explanation of the computation (the - sign below).
2152
2153 yAxis->setRange(yLower, yUpper - m_context.m_yDelta);
2154 }
2155 // End of
2156 // else // that is, if(m_context.m_wasClickOnYAxis)
2157
2158 // Update the context with the current axes ranges
2159
2161
2162 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
2163
2164 replot();
2165}
2166
2167void
2169{
2170
2171 // double sorted_start_drag_point_x =
2172 // std::min(m_context.m_startDragPoint.x(),
2173 // m_context.m_currentDragPoint.x());
2174
2175 // xAxis->setRange(sorted_start_drag_point_x,
2176 // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2177
2178 xAxis->setRange(QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
2179
2180 // Note that the y axis should be rescaled from current lower value to new
2181 // upper value matching the y-axis position of the cursor when the mouse
2182 // button was released.
2183
2184 yAxis->setRange(xAxis->range().lower,
2185 std::max<double>(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));
2186
2187 // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2188 // xAxis->range().upper
2189 //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2190
2192
2194 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
2195
2196 replot();
2197}
2198
2199void
2201{
2202
2203 // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2204 // values before using them, because now we want to really have the lower x
2205 // value. Simply craft a QCPRange that will swap the values if lower is not
2206 // < than upper QCustomPlot calls this normalization).
2207
2208 xAxis->setRange(QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
2209
2210 yAxis->setRange(QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));
2211
2213
2215 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
2216
2217 replot();
2218}
2219
2220void
2222{
2223 // Sanity check
2224 if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
2225 qFatal(
2226 "This function can only be called if the mouse click was on one of the "
2227 "axes");
2228
2229 if(m_context.m_wasClickOnXAxis)
2230 {
2231 xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2232 m_context.m_xRange.upper - m_context.m_xDelta);
2233 }
2234
2235 if(m_context.m_wasClickOnYAxis)
2236 {
2237 yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2238 m_context.m_yRange.upper - m_context.m_yDelta);
2239 }
2240
2242
2243 // qDebug() << "The updated context:" << m_context.toString();
2244
2245 // We cannot store the new ranges in the history, because the pan operation
2246 // involved a huge quantity of micro-movements elicited upon each mouse move
2247 // cursor event so we would have a huge history.
2248 // updateAxesRangeHistory();
2249
2250 // Now that the context has the right range values, we can emit the
2251 // signal that will be used by this plot widget users, typically to
2252 // abide by the x/y range lock required by the user.
2253
2254 emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
2255
2256 replot();
2257}
2258
2259void
2261 QCPRange yAxisRange,
2262 Enums::Axis axis)
2263{
2264 // qDebug() << "With axis:" << (int)axis;
2265
2266 if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::x))
2267 {
2268 xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2269 }
2270
2271 if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::y))
2272 {
2273 yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2274 }
2275
2276 // We do not want to update the history, because there would be way too
2277 // much history items, since this function is called upon mouse moving
2278 // handling and not only during mouse release events.
2279 // updateAxesRangeHistory();
2280
2281 replot();
2282}
2283
2284void
2285BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2286{
2287 // qDebug();
2288
2289 xAxis->setRange(lower, upper);
2290
2291 replot();
2292}
2293
2294void
2295BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2296{
2297 // qDebug();
2298
2299 yAxis->setRange(lower, upper);
2300
2301 replot();
2302}
2303
2304/// PLOTTING / REPLOTTING functions
2305
2306
2307/// PLOT ITEMS : TRACER TEXT ITEMS...
2308
2309//! Hide the selection line, the xDelta text and the zoom rectangle items.
2310void
2312{
2313 mp_xDeltaTextItem->setVisible(false);
2314 mp_yDeltaTextItem->setVisible(false);
2315
2316 // mp_zoomRectItem->setVisible(false);
2318
2319 // Force a replot to make sure the action is immediately visible by the
2320 // user, even without moving the mouse.
2321 replot();
2322}
2323
2324//! Show the traces (vertical and horizontal).
2325void
2327{
2329
2330 mp_vPosTracerItem->setVisible(true);
2331 mp_hPosTracerItem->setVisible(true);
2332
2333 mp_vStartTracerItem->setVisible(true);
2334 mp_vEndTracerItem->setVisible(true);
2335
2336 // Force a replot to make sure the action is immediately visible by the
2337 // user, even without moving the mouse.
2338 replot();
2339}
2340
2341//! Hide the traces (vertical and horizontal).
2342void
2344{
2346 mp_hPosTracerItem->setVisible(false);
2347 mp_vPosTracerItem->setVisible(false);
2348
2349 mp_vStartTracerItem->setVisible(false);
2350 mp_vEndTracerItem->setVisible(false);
2351
2352 // Force a replot to make sure the action is immediately visible by the
2353 // user, even without moving the mouse.
2354 replot();
2355}
2356
2357void
2359 bool for_integration)
2360{
2361 // The user has dragged the mouse left button on the graph, which means he
2362 // is willing to draw a selection rectangle, either for zooming-in or for
2363 // integration.
2364
2365 if(mp_xDeltaTextItem != nullptr)
2366 mp_xDeltaTextItem->setVisible(false);
2367 if(mp_yDeltaTextItem != nullptr)
2368 mp_yDeltaTextItem->setVisible(false);
2369
2370 // Ensure the right selection rectangle is drawn.
2371
2372 updateIntegrationScopeDrawing(as_line_segment, for_integration);
2373
2374 // Note that if we draw a zoom rectangle, then we are certainly not
2375 // measuring anything. So set the boolean value to false so that the user of
2376 // this widget or derived classes know that there is nothing to perform upon
2377 // (like deconvolution, for example).
2378
2379 m_context.m_isMeasuringDistance = false;
2380
2381 // Also remove the delta value from the pipeline by sending a simple
2382 // distance without measurement signal.
2383
2384 emit xAxisMeasurementSignal(m_context, false);
2385
2386 replot();
2387}
2388
2389void
2391{
2392 // Depending on the kind of integration scope, we will have to display
2393 // differently calculated values. We want to provide the user with
2394 // the horizontal span of the integration scope. There are different
2395 // situations.
2396
2397 // 1. The scope is mono-dimensional across the x axis: the span
2398 // is thus simply the width.
2399
2400 // 2. The scope is bi-dimensional and is a rectangle: the span is
2401 // thus simply the width.
2402
2403 // 3. The socpe is bi-dimensional and is a rhomboid: the span is
2404 // the width.
2405
2406 // In the first and second cases above, the width is equal to the
2407 // m_context.m_xDelta.
2408
2409 // In the case of the rhomboid, the span is not m_context.m_xDelta,
2410 // it is more than that if the rhomboid is horizontal because it is
2411 // the m_context.m_xDelta plus the rhomboid's horizontal size.
2412
2413 // FIXME: is this still true?
2414 //
2415 // We do not want to show the position markers because the only horiontal
2416 // line to be visible must be contained between the start and end vertical
2417 // tracer items.
2418 mp_hPosTracerItem->setVisible(false);
2419 mp_vPosTracerItem->setVisible(false);
2420
2421 // We want to draw the text in the middle position of the leftmost-rightmost
2422 // point, even with rhomboid scopes.
2423
2424 QPointF leftmost_point;
2425 if(!m_context.mpa_integrationScope->getLeftMostPoint(leftmost_point))
2426 qFatal("Could not get the left-most point.");
2427
2428 double width;
2429 if(!m_context.mpa_integrationScope->getWidth(width))
2430 qFatal("Could not get width.");
2431 // qDebug() << "width:" << width;
2432
2433 double x_axis_center_position = leftmost_point.x() + width / 2;
2434
2435 // We want the text to print inside the rectangle, always at the current
2436 // drag point so the eye can follow the delta value while looking where to
2437 // drag the mouse. To position the text inside the rectangle, we need to
2438 // know what is the drag direction.
2439
2440 // What is the distance between the rectangle line at current drag point and
2441 // the text itself. Think of this as a margin distance between the
2442 // point of interest and the actual position of the text.
2443 int pixels_away_from_line = 15;
2444
2445 QPointF reference_point_for_y_axis_label_position;
2446
2447 // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2448 // order with respect to the y axis values !!! That is, pixel(0,0) is top
2449 // left of the graph.
2450 if(static_cast<int>(m_context.m_dragDirections) &
2451 static_cast<int>(DragDirections::BOTTOM_TO_TOP))
2452 {
2453 // We need to print outside the rectangle, that is pixels_away_from_line
2454 // pixels to the top, so with pixel y value decremented of that
2455 // pixels_above_line value (one would have expected to increment that
2456 // value, along the y axis, but the coordinates in pixel go in reverse
2457 // order).
2458
2459 pixels_away_from_line *= -1;
2460
2461 if(!m_context.mpa_integrationScope->getTopMostPoint(
2462 reference_point_for_y_axis_label_position))
2463 qFatal("Failed to get top most point.");
2464 }
2465 else
2466 {
2467 if(!m_context.mpa_integrationScope->getBottomMostPoint(
2468 reference_point_for_y_axis_label_position))
2469 qFatal("Failed to get bottom most point.");
2470 }
2471
2472 // double y_axis_pixel_coordinate =
2473 // yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2474 double y_axis_pixel_coordinate =
2475 yAxis->coordToPixel(reference_point_for_y_axis_label_position.y());
2476
2477 // Now that we have the coordinate in pixel units, we can correct
2478 // it by the value of the margin we want to give.
2479 double y_axis_modified_pixel_coordinate =
2480 y_axis_pixel_coordinate + pixels_away_from_line;
2481
2482 // Set aside a point instance to store the pixel coordinates of the text.
2483 QPointF pixel_coordinates;
2484
2485 pixel_coordinates.setX(x_axis_center_position);
2486 pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2487
2488 // Now convert back to graph coordinates.
2489 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2490 yAxis->pixelToCoord(pixel_coordinates.y()));
2491
2492 // qDebug() << "Should print the label at point:" << graph_coordinates;
2493
2494 if(mp_xDeltaTextItem != nullptr)
2495 {
2496 mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2497 graph_coordinates.y());
2498
2499 // Dynamically set the number of decimals to ensure we can read
2500 // a meaning full delta value even if it is very very very small.
2501 // That is, allow one to read 0.00333, 0.000333, 1.333 and so on.
2502
2503 // The computation below only works properly when the passed
2504 // value is fabs() (not negative !!!).
2505
2506 int decimals = Utils::zeroDecimalsInValue(width) + 3;
2507
2508 QString label_text = QString("full x span %1 -- x drag delta %2")
2509 .arg(width, 0, 'f', decimals)
2510 .arg(fabs(m_context.m_xDelta), 0, 'f', decimals);
2511
2512 mp_xDeltaTextItem->setText(label_text);
2513
2514 mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2515 mp_xDeltaTextItem->setVisible(true);
2516 }
2517
2518 // Set the boolean to true so that derived widgets know that something is
2519 // being measured, and they can act accordingly, for example by computing
2520 // deconvolutions in a mass spectrum.
2521 m_context.m_isMeasuringDistance = true;
2522
2523 replot();
2524
2525 // Let the caller know that we were measuring something.
2527
2528 return;
2529}
2530
2531void
2533{
2534 // See drawXScopeSpanFeatures() for explanations.
2535
2536 // Check right away if there is height!
2537 double height;
2538 if(!m_context.mpa_integrationScope->getHeight(height))
2539 qFatal("Could not get height.");
2540
2541 // If there is no height, we have nothing to do here.
2542 if(!height)
2543 return;
2544 // qDebug() << "height:" << height;
2545
2546 // FIXME: is this still true?
2547 //
2548 // We do not want to show the position markers because the only horiontal
2549 // line to be visible must be contained between the start and end vertical
2550 // tracer items.
2551 mp_hPosTracerItem->setVisible(false);
2552 mp_vPosTracerItem->setVisible(false);
2553
2554 // First the easy part: the vertical position: centered on the
2555 // scope Y span.
2556 QPointF bottom_most_point;
2557 if(!m_context.mpa_integrationScope->getBottomMostPoint(bottom_most_point))
2558 qFatal("Could not get the bottom-most bottom point.");
2559
2560 double y_axis_center_position = bottom_most_point.y() + height / 2;
2561
2562 // We want to draw the text outside the rectangle (if normal rectangle)
2563 // at a small distance from the vertical limit of the scope at the
2564 // position of the current drag point. We need to check the horizontal
2565 // drag direction to put the text at the right place (left of
2566 // current drag point if dragging right to left, for example).
2567
2568 // What is the distance between the rectangle line at current drag point and
2569 // the text itself.
2570 int pixels_away_from_line = 15;
2571 double x_axis_coordinate;
2572 double x_axis_pixel_coordinate;
2573
2574 if(static_cast<int>(m_context.m_dragDirections) &
2575 static_cast<int>(DragDirections::RIGHT_TO_LEFT))
2576 {
2577 QPointF left_most_point;
2578
2579 if(!m_context.mpa_integrationScope->getLeftMostPoint(left_most_point))
2580 qFatal("Failed to get left most point.");
2581
2582 x_axis_coordinate = left_most_point.x();
2583
2584 pixels_away_from_line *= -1;
2585 }
2586 else
2587 {
2588 QPointF right_most_point;
2589
2590 if(!m_context.mpa_integrationScope->getRightMostPoint(right_most_point))
2591 qFatal("Failed to get right most point.");
2592
2593 x_axis_coordinate = right_most_point.x();
2594 }
2595 x_axis_pixel_coordinate = xAxis->coordToPixel(x_axis_coordinate);
2596
2597 double x_axis_modified_pixel_coordinate =
2598 x_axis_pixel_coordinate + pixels_away_from_line;
2599
2600 // Set aside a point instance to store the pixel coordinates of the text.
2601 QPointF pixel_coordinates;
2602
2603 pixel_coordinates.setX(x_axis_modified_pixel_coordinate);
2604 pixel_coordinates.setY(y_axis_center_position);
2605
2606 // Now convert back to graph coordinates.
2607
2608 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2609 yAxis->pixelToCoord(pixel_coordinates.y()));
2610
2611 mp_yDeltaTextItem->position->setCoords(graph_coordinates.x(),
2612 y_axis_center_position);
2613
2614 int decimals = Utils::zeroDecimalsInValue(height) + 3;
2615
2616 QString label_text = QString("full y span %1 -- y drag delta %2")
2617 .arg(height, 0, 'f', decimals)
2618 .arg(fabs(m_context.m_yDelta), 0, 'f', decimals);
2619
2620 mp_yDeltaTextItem->setText(label_text);
2621 mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2622 mp_yDeltaTextItem->setVisible(true);
2623 mp_yDeltaTextItem->setRotation(90);
2624
2625 // Set the boolean to true so that derived widgets know that something is
2626 // being measured, and they can act accordingly, for example by computing
2627 // deconvolutions in a mass spectrum.
2628 m_context.m_isMeasuringDistance = true;
2629
2630 replot();
2631
2632 // Let the caller know that we were measuring something.
2634}
2635
2636void
2638{
2639
2640 // We compute signed differentials. If the user does not want the sign,
2641 // fabs(double) is their friend.
2642
2643 // Compute the xAxis differential:
2644
2645 m_context.m_xDelta =
2646 m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();
2647
2648 // Same with the Y-axis range:
2649
2650 m_context.m_yDelta =
2651 m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();
2652
2653 return;
2654}
2655
2656bool
2658{
2659 // First get the height of the plot.
2660 double plotHeight = yAxis->range().upper - yAxis->range().lower;
2661
2662 double heightDiff =
2663 fabs(m_context.m_startDragPoint.y() - m_context.m_currentDragPoint.y());
2664
2665 double heightDiffRatio = (heightDiff / plotHeight) * 100;
2666
2667 if(heightDiffRatio > 10)
2668 {
2669 return true;
2670 }
2671
2672 return false;
2673}
2674
2675void
2677{
2678
2679 // if(for_integration)
2680 // qDebug() << "for_integration:" << for_integration;
2681
2682 // By essence, the one-dimension IntegrationScope is characterized
2683 // by the left-most point and the width. Using these two data bits
2684 // it is possible to compute the x value of the right-most point.
2685
2686 double x_range_start =
2687 std::min(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
2688 double x_range_end =
2689 std::max(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
2690
2691 // qDebug() << "x_range_start:" << x_range_start << "-" << "x_range_end:" <<
2692 // x_range_end;
2693
2694 double y_position = m_context.m_startDragPoint.y();
2695
2696 m_context.updateIntegrationScope();
2697
2698 // Top line
2699 mp_selectionRectangeLine1->start->setCoords(
2700 QPointF(x_range_start, y_position));
2701 mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2702
2703 // Only if we are drawing a selection rectangle for integration, do we set
2704 // arrow heads to the line.
2705 if(for_integration)
2706 {
2707 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2708 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2709 }
2710 else
2711 {
2712 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2713 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2714 }
2715 mp_selectionRectangeLine1->setVisible(true);
2716
2717 // Right line: does not exist, start and end are the same end point of the
2718 // top line.
2719 mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2720 mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2721 mp_selectionRectangeLine2->setVisible(false);
2722
2723 // Bottom line: identical to the top line, but invisible
2724 mp_selectionRectangeLine3->start->setCoords(
2725 QPointF(x_range_start, y_position));
2726 mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2727 mp_selectionRectangeLine3->setVisible(false);
2728
2729 // Left line: does not exist: start and end are the same end point of the
2730 // top line.
2731 mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2732 mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2733 mp_selectionRectangeLine4->setVisible(false);
2734}
2735
2736void
2738{
2739 // qDebug();
2740
2741 // if(for_integration)
2742 // qDebug() << "for_integration:" << for_integration;
2743
2744 // We are handling a conventional rectangle. Just create four points
2745 // from top left to bottom right. But we want the top left point to be
2746 // effectively the top left point and the bottom point to be the bottom
2747 // point. So we need to try all four direction combinations, left to right
2748 // or converse versus top to bottom or converse.
2749
2750 m_context.updateIntegrationScopeRect();
2751
2752 // Now that the integration scope has been updated as a rectangle,
2753 // use these newly set data to actually draw the integration
2754 // scope lines.
2755
2756 QPointF bottom_left_point;
2757 if(!m_context.mpa_integrationScope->getPoint(bottom_left_point))
2758 qFatal("Failed to get point.");
2759 // qDebug() << "Starting point is left bottom point:" << bottom_left_point;
2760
2761 double width;
2762 if(!m_context.mpa_integrationScope->getWidth(width))
2763 qFatal("Failed to get width.");
2764 // qDebug() << "Width:" << width;
2765
2766 double height;
2767 if(!m_context.mpa_integrationScope->getHeight(height))
2768 qFatal("Failed to get height.");
2769 // qDebug() << "Height:" << height;
2770
2771 QPointF bottom_right_point(bottom_left_point.x() + width,
2772 bottom_left_point.y());
2773 // qDebug() << "bottom_right_point:" << bottom_right_point;
2774
2775 QPointF top_right_point(bottom_left_point.x() + width,
2776 bottom_left_point.y() + height);
2777 // qDebug() << "top_right_point:" << top_right_point;
2778
2779 QPointF top_left_point(bottom_left_point.x(), bottom_left_point.y() + height);
2780
2781 // qDebug() << "top_left_point:" << top_left_point;
2782
2783 // Start by drawing the bottom line because the IntegrationScopeRect has the
2784 // left bottom point and the width and the height to fully characterize it.
2785
2786 // Bottom line (left to right)
2787 mp_selectionRectangeLine3->start->setCoords(bottom_left_point);
2788 mp_selectionRectangeLine3->end->setCoords(bottom_right_point);
2789 mp_selectionRectangeLine3->setVisible(true);
2790
2791 // Right line (bottom to top)
2792 mp_selectionRectangeLine2->start->setCoords(bottom_right_point);
2793 mp_selectionRectangeLine2->end->setCoords(top_right_point);
2794 mp_selectionRectangeLine2->setVisible(true);
2795
2796 // Top line (right to left)
2797 mp_selectionRectangeLine1->start->setCoords(top_right_point);
2798 mp_selectionRectangeLine1->end->setCoords(top_left_point);
2799 mp_selectionRectangeLine1->setVisible(true);
2800
2801 // Left line (top to bottom)
2802 mp_selectionRectangeLine4->start->setCoords(top_left_point);
2803 mp_selectionRectangeLine4->end->setCoords(bottom_left_point);
2804 mp_selectionRectangeLine4->setVisible(true);
2805
2806 // Only if we are drawing a selection rectangle for integration, do we
2807 // set arrow heads to the line.
2808 if(for_integration)
2809 {
2810 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2811 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2812 }
2813 else
2814 {
2815 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2816 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2817 }
2818}
2819
2820void
2822{
2823 // We are handling a rhomboid scope, that is, a rectangle that
2824 // is tilted either to the left or to the right.
2825
2826 // There are two kinds of rhomboid integration scopes: horizontal and
2827 // vertical.
2828
2829 /*
2830 * +----------+
2831 * | |
2832 * | |
2833 * | |
2834 * | |
2835 * | |
2836 * | |
2837 * | |
2838 * +----------+
2839 * ----width---
2840 */
2841
2842 // As visible here, the fixed size of the rhomboid (using the S key in the
2843 // plot widget) is the *horizontal* side (this is the plot context's
2844 // m_integrationScopeRhombWidth).
2845
2846 IntegrationScopeFeatures scope_features;
2847
2848 // Top horizontal line
2849 QPointF point_1;
2850 scope_features = m_context.mpa_integrationScope->getLeftMostTopPoint(point_1);
2851
2852 // When the user rotates the horizontal rhomboid, at some point, if the
2853 // current drag point has the same y axis value as the start drag point, then
2854 // we say that the rhomboid is flattened on the x axis. In this case, we do
2855 // not draw anything as this is a purely unusable situation.
2856
2857 if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
2858 {
2859 // qDebug() << "The horizontal rhomboid is flattened on the x axis.";
2860
2861 mp_selectionRectangeLine1->setVisible(false);
2862 mp_selectionRectangeLine2->setVisible(false);
2863 mp_selectionRectangeLine3->setVisible(false);
2864 mp_selectionRectangeLine4->setVisible(false);
2865
2866 return;
2867 }
2868
2870 qFatal("The rhomboid should be horizontal!");
2871
2872 // At this point we can draw the rhomboid fine.
2873
2874 if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
2875 qFatal("Failed to getLeftMostTopPoint.");
2876 QPointF point_2;
2877 if(!m_context.mpa_integrationScope->getRightMostTopPoint(point_2))
2878 qFatal("Failed to getRightMostTopPoint.");
2879
2880 // qDebug() << "For top line, two points:" << point_1 << "--" << point_2;
2881
2882 mp_selectionRectangeLine1->start->setCoords(point_1);
2883 mp_selectionRectangeLine1->end->setCoords(point_2);
2884
2885 // Only if we are drawing a selection rectangle for integration, do we set
2886 // arrow heads to the line.
2887 if(for_integration)
2888 {
2889 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2890 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2891 }
2892 else
2893 {
2894 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2895 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2896 }
2897
2898 mp_selectionRectangeLine1->setVisible(true);
2899
2900 // Right line
2901 if(!m_context.mpa_integrationScope->getRightMostBottomPoint(point_1))
2902 qFatal("Failed to getRightMostBottomPoint.");
2903 mp_selectionRectangeLine2->start->setCoords(point_2);
2904 mp_selectionRectangeLine2->end->setCoords(point_1);
2905 mp_selectionRectangeLine2->setVisible(true);
2906
2907 // qDebug() << "For right line, two points:" << point_2 << "--" << point_1;
2908
2909 // Bottom horizontal line
2910 if(!m_context.mpa_integrationScope->getLeftMostBottomPoint(point_2))
2911 qFatal("Failed to getLeftMostBottomPoint.");
2912 mp_selectionRectangeLine3->start->setCoords(point_1);
2913 mp_selectionRectangeLine3->end->setCoords(point_2);
2914 mp_selectionRectangeLine3->setVisible(true);
2915
2916 // qDebug() << "For bottom line, two points:" << point_1 << "--" << point_2;
2917
2918 // Left line
2919 if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
2920 qFatal("Failed to getLeftMostTopPoint.");
2921 mp_selectionRectangeLine4->end->setCoords(point_2);
2922 mp_selectionRectangeLine4->start->setCoords(point_1);
2923 mp_selectionRectangeLine4->setVisible(true);
2924
2925 // qDebug() << "For left line, two points:" << point_2 << "--" << point_1;
2926}
2927
2928void
2930{
2931 // We are handling a rhomboid scope, that is, a rectangle that
2932 // is tilted either to the left or to the right.
2933
2934 // There are two kinds of rhomboid integration scopes: horizontal and
2935 // vertical.
2936
2937 /*
2938 * +3
2939 * . |
2940 * . |
2941 * . |
2942 * . +2
2943 * . .
2944 * . .
2945 * . .
2946 * 4+ .
2947 * | | .
2948 * height | | .
2949 * | | .
2950 * 1+
2951 *
2952 */
2953
2954 // As visible here, the fixed size of the rhomboid (using the S key in the
2955 // plot widget) is the *vertical* side (this is the plot context's
2956 // m_integrationScopeRhombHeight).
2957
2958 IntegrationScopeFeatures scope_features;
2959
2960 // Left vertical line
2961 QPointF point_1;
2962 scope_features = m_context.mpa_integrationScope->getLeftMostTopPoint(point_1);
2963
2964 // When the user rotates the vertical rhomboid, at some point, if the current
2965 // drag point is on the same x axis value as the start drag point, then we say
2966 // that the rhomboid is flattened on the y axis. In this case, we do not draw
2967 // anything as this is a purely unusable situation.
2968
2969 if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
2970 {
2971 // qDebug() << "The vertical rhomboid is flattened on the y axis.";
2972
2973 mp_selectionRectangeLine1->setVisible(false);
2974 mp_selectionRectangeLine2->setVisible(false);
2975 mp_selectionRectangeLine3->setVisible(false);
2976 mp_selectionRectangeLine4->setVisible(false);
2977
2978 return;
2979 }
2980
2982 qFatal("The rhomboid should be vertical!");
2983
2984 // At this point we can draw the rhomboid fine.
2985
2986 QPointF point_2;
2987 if(!m_context.mpa_integrationScope->getLeftMostBottomPoint(point_2))
2988 qFatal("Failed to getLeftMostBottomPoint.");
2989
2990 // qDebug() << "For left vertical line, two points:" << point_1 << "--"
2991 // << point_2;
2992
2993 mp_selectionRectangeLine1->start->setCoords(point_1);
2994 mp_selectionRectangeLine1->end->setCoords(point_2);
2995
2996 // Only if we are drawing a selection rectangle for integration, do we set
2997 // arrow heads to the line.
2998 if(for_integration)
2999 {
3000 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
3001 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
3002 }
3003 else
3004 {
3005 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
3006 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
3007 }
3008
3009 mp_selectionRectangeLine1->setVisible(true);
3010
3011 // Lower oblique line
3012 if(!m_context.mpa_integrationScope->getRightMostBottomPoint(point_1))
3013 qFatal("Failed to getRightMostBottomPoint.");
3014 mp_selectionRectangeLine2->start->setCoords(point_2);
3015 mp_selectionRectangeLine2->end->setCoords(point_1);
3016 mp_selectionRectangeLine2->setVisible(true);
3017
3018 // qDebug() << "For lower oblique line, two points:" << point_2 << "--"
3019 // << point_1;
3020
3021 // Right vertical line
3022 if(!m_context.mpa_integrationScope->getRightMostTopPoint(point_2))
3023 qFatal("Failed to getRightMostTopPoint.");
3024 mp_selectionRectangeLine3->start->setCoords(point_1);
3025 mp_selectionRectangeLine3->end->setCoords(point_2);
3026 mp_selectionRectangeLine3->setVisible(true);
3027
3028 // qDebug() << "For right vertical line, two points:" << point_1 << "--"
3029 // << point_2;
3030
3031 // Upper oblique line
3032 if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
3033 qFatal("Failed to get the LeftMostTopPoint.");
3034 mp_selectionRectangeLine4->end->setCoords(point_2);
3035 mp_selectionRectangeLine4->start->setCoords(point_1);
3036 mp_selectionRectangeLine4->setVisible(true);
3037
3038 // qDebug() << "For upper oblique line, two points:" << point_2 << "--"
3039 // << point_1;
3040}
3041
3042void
3044{
3045 // qDebug();
3046
3047 // if(for_integration)
3048 // qDebug() << "for_integration:" << for_integration;
3049
3050 // We are handling a skewed rectangle (rhomboid), that is a rectangle that
3051 // is tilted either to the left or to the right.
3052
3053 // There are two kinds of rhomboid integration scopes:
3054
3055 /*
3056 4+----------+3
3057 | |
3058 | |
3059 | |
3060 | |
3061 | |
3062 | |
3063 | |
3064 1+----------+2
3065 ----width---
3066 */
3067
3068 // As visible here, the fixed size of the rhomboid (using the S key in the
3069 // plot widget) is the *horizontal* side (this is the plot context's
3070 // m_integrationScopeRhombWidth).
3071
3072 // and
3073
3074
3075 /*
3076 * +3
3077 * . |
3078 * . |
3079 * . |
3080 * . +2
3081 * . .
3082 * . .
3083 * . .
3084 * 4+ .
3085 * | | .
3086 * height | | .
3087 * | | .
3088 * 1+
3089 *
3090 */
3091
3092 // As visible here, the fixed size of the rhomboid (using the S key in the
3093 // plot widget) is the *vertical* side (this is the plot context's
3094 // m_integrationScopeRhombHeight).
3095
3096 // qDebug() << "Before calling updateIntegrationScopeRhomb(), "
3097 // "m_integrationScopeRhombWidth:"
3098 // << m_context.m_integrationScopeRhombWidth
3099 // << "and m_integrationScopeRhombHeight:"
3100 // << m_context.m_integrationScopeRhombHeight;
3101
3102 m_context.updateIntegrationScopeRhomb();
3103
3104 // qDebug() << "After, m_integrationScopeRhombWidth:"
3105 // << m_context.m_integrationScopeRhombWidth
3106 // << "and m_integrationScopeRhombHeight:"
3107 // << m_context.m_integrationScopeRhombHeight;
3108
3109 // Now that the integration scope has been updated as a rhomboid,
3110 // use these newly set data to actually draw the integration
3111 // scope lines.
3112
3113 // We thus need to first establish if we have a horiontal or a vertical
3114 // rhomboid scope. This information is located in
3115 // m_context.m_integrationScopeRhombWidth and
3116 // m_context.m_integrationScopeRhombHeight. If width > 0, height *has to be
3117 // 0*, which indicates a horizontal rhomb.Conversely, if height is > 0, then
3118 // the rhomb is vertical.
3119
3120 if(m_context.m_integrationScopeRhombWidth > 0)
3121 // We are dealing with a horizontal scope.
3123 else if(m_context.m_integrationScopeRhombHeight > 0)
3124 // We are dealing with a vertical scope.
3125 updateIntegrationScopeVerticalRhomb(for_integration);
3126 else
3127 qFatal("Cannot be both the width or height of rhomboid scope be 0.");
3128}
3129
3130void
3132 bool for_integration)
3133{
3134 // qDebug() << "as_line_segment:" << as_line_segment;
3135 // qDebug() << "for_integration:" << for_integration;
3136
3137 // We now need to construct the selection rectangle, either for zoom or for
3138 // integration.
3139
3140 // There are two situations :
3141 //
3142 // 1. if the rectangle should look like a line segment
3143 //
3144 // 2. if the rectangle should actually look like a rectangle. In this case,
3145 // there are two sub-situations:
3146 //
3147 // a. if the Alt modifier key is down, then the rectangle is rhomboid.
3148 //
3149 // b. otherwise the rectangle is conventional.
3150
3151 if(as_line_segment)
3152 {
3153 // qDebug() << "Updating the integration scope to an IntegrationScope.";
3154 updateIntegrationScope(for_integration);
3155 }
3156 else
3157 {
3158 if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
3159 {
3160 // qDebug()
3161 // << "Updating the integration scope to an IntegrationScopeRect.";
3162 updateIntegrationScopeRect(for_integration);
3163 }
3164 else if(m_context.m_keyboardModifiers & Qt::AltModifier)
3165 {
3166 // The user might use the Alt modifier, but if no rhomboid side has
3167 // been defined using the S key, then we do not do any rhomboid
3168 // selection because we do not know the side size of that rhomboid.
3169
3170 if(!m_context.m_integrationScopeRhombHeight &&
3171 !m_context.m_integrationScopeRhombWidth)
3172 updateIntegrationScopeRect(for_integration);
3173 else
3174 // qDebug()
3175 // << "Updating the integration scope to an
3176 // IntegrationScopeRhomb.";
3177 updateIntegrationScopeRhomb(for_integration);
3178 }
3179 }
3180
3181 // Depending on the kind of IntegrationScope, (normal, rect or rhomb)
3182 // we have to measure things in different ways. We now set in the context
3183 // a number of parameters that will be used by its user.
3184
3185 QPointF point;
3186 double height;
3187 std::vector<QPointF> points;
3188
3189 // Integration scope values are sorted:
3190 // Line scope: point is left and width is right.x - left.x
3191 // Rect scope: point is bottom left.
3192 // Rhomb scope: points 1->4 are bottom left->bottom right->top right->top left
3193 // width is 2.x - 1.x.
3194
3195 if(m_context.mpa_integrationScope->getPoints(points))
3196 {
3197 // We have defined a IntegrationScopeRhomb.
3198
3199 if(!m_context.mpa_integrationScope->getLeftMostPoint(point))
3200 qFatal("Failed to get LeftMost point.");
3201 m_context.m_xRegionRangeStart = point.x();
3202
3203 if(!m_context.mpa_integrationScope->getRightMostPoint(point))
3204 qFatal("Failed to get RightMost point.");
3205 m_context.m_xRegionRangeStop = point.x();
3206 }
3207 else if(m_context.mpa_integrationScope->getHeight(height))
3208 {
3209 // We have defined a IntegrationScopeRect.
3210
3211 if(!m_context.mpa_integrationScope->getPoint(point))
3212 qFatal("Failed to get point.");
3213 m_context.m_xRegionRangeStart = point.x();
3214
3215 double width;
3216
3217 if(!m_context.mpa_integrationScope->getWidth(width))
3218 qFatal("Failed to get width.");
3219
3220 m_context.m_xRegionRangeStop = m_context.m_xRegionRangeStart + width;
3221
3222 m_context.m_yRegionRangeStart = point.y();
3223
3224 m_context.m_yRegionRangeStop = point.y() + height;
3225 }
3226 else
3227 {
3228 // We have defined a IntegrationScope.
3229
3230 if(!m_context.mpa_integrationScope->getPoint(point))
3231 qFatal("Failed to get point.");
3232 m_context.m_xRegionRangeStart = point.x();
3233
3234 double width;
3235
3236 if(!m_context.mpa_integrationScope->getWidth(width))
3237 qFatal("Failed to get width.");
3238 m_context.m_xRegionRangeStop = m_context.m_xRegionRangeStart + width;
3239 }
3240
3241 // At this point, draw the text describing the widths.
3242
3243 // We want the x-delta on the bottom of the rectangle, inside it
3244 // and the y-delta on the vertical side of the rectangle, inside it.
3245
3246 // Draw the selection width text
3248}
3249
3250void
3252{
3253 mp_selectionRectangeLine1->setVisible(false);
3254 mp_selectionRectangeLine2->setVisible(false);
3255 mp_selectionRectangeLine3->setVisible(false);
3256 mp_selectionRectangeLine4->setVisible(false);
3257
3258 if(reset_values)
3259 {
3261 }
3262}
3263
3264void
3266{
3267 static_cast<IntegrationScopeBase *>(m_context.mpa_integrationScope)->reset();
3268}
3269
3272{
3273 // There are four lines that make the selection polygon. We want to know
3274 // which lines are visible.
3275
3276 int current_selection_polygon =
3277 static_cast<int>(SelectionDrawingLines::NOT_SET);
3278
3279 if(mp_selectionRectangeLine1->visible())
3280 {
3281 current_selection_polygon |=
3282 static_cast<int>(SelectionDrawingLines::TOP_LINE);
3283 // qDebug() << "current_selection_polygon:" <<
3284 // current_selection_polygon;
3285 }
3286 if(mp_selectionRectangeLine2->visible())
3287 {
3288 current_selection_polygon |=
3289 static_cast<int>(SelectionDrawingLines::RIGHT_LINE);
3290 // qDebug() << "current_selection_polygon:" <<
3291 // current_selection_polygon;
3292 }
3293 if(mp_selectionRectangeLine3->visible())
3294 {
3295 current_selection_polygon |=
3296 static_cast<int>(SelectionDrawingLines::BOTTOM_LINE);
3297 // qDebug() << "current_selection_polygon:" <<
3298 // current_selection_polygon;
3299 }
3300 if(mp_selectionRectangeLine4->visible())
3301 {
3302 current_selection_polygon |=
3303 static_cast<int>(SelectionDrawingLines::LEFT_LINE);
3304 // qDebug() << "current_selection_polygon:" <<
3305 // current_selection_polygon;
3306 }
3307
3308 // qDebug() << "returning visibility:" << current_selection_polygon;
3309
3310 return static_cast<SelectionDrawingLines>(current_selection_polygon);
3311}
3312
3313bool
3315{
3316 // Sanity check
3317 int check = 0;
3318
3319 check += mp_selectionRectangeLine1->visible();
3320 check += mp_selectionRectangeLine2->visible();
3321 check += mp_selectionRectangeLine3->visible();
3322 check += mp_selectionRectangeLine4->visible();
3323
3324 if(check > 0)
3325 return true;
3326
3327 return false;
3328}
3329
3330void
3332{
3333 // qDebug() << "Setting focus to the QCustomPlot:" << this;
3334
3335 QCustomPlot::setFocus();
3336
3337 // qDebug() << "Emitting setFocusSignal().";
3338
3339 emit setFocusSignal();
3340}
3341
3342//! Redraw the background of the \p focusedPlotWidget plot widget.
3343void
3344BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3345{
3346 if(focusedPlotWidget == nullptr)
3348 "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3349 "-- "
3350 "ERROR focusedPlotWidget cannot be nullptr.");
3351
3352 if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3353 {
3354 // The focused widget is not *this widget. We should make sure that
3355 // we were not the one that had the focus, because in this case we
3356 // need to redraw an unfocused background.
3357
3358 axisRect()->setBackground(m_unfocusedBrush);
3359 }
3360 else
3361 {
3362 axisRect()->setBackground(m_focusedBrush);
3363 }
3364
3365 replot();
3366}
3367
3368void
3370{
3371 m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3372 m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3373
3374 // qDebug() << "The new updated context: " << m_context.toString();
3375}
3376
3377const BasePlotContext &
3379{
3380 return m_context;
3381}
3382
3383
3384int
3385BasePlotWidget::getPlotIndex(QCPAbstractPlottable *plottable_p)
3386{
3387 int plottable_count = plottableCount();
3388
3389 for(int iter = 0; iter < plottable_count; ++iter)
3390 if(plottable(iter) == plottable_p)
3391 return iter;
3392
3393 return -1;
3394}
3395
3396} // namespace pappso
int basePlotContextPtrMetaTypeId
int basePlotContextMetaTypeId
virtual void updateIntegrationScopeRect(bool for_integration=false)
int m_mouseMoveHandlerSkipAmount
How many mouse move events must be skipped *‍/.
std::size_t m_lastAxisRangeHistoryIndex
Index of the last axis range history item.
virtual void replotWithAxesRanges(QCPRange xAxisRange, QCPRange yAxisRange, Enums::Axis axis)
virtual void updateAxesRangeHistory()
Create new axis range history items and append them to the history.
virtual void mouseWheelHandler(QWheelEvent *event)
void plottableDestructionRequestedSignal(BasePlotWidget *base_plot_widget_p, QCPAbstractPlottable *plottable_p, const pappso::BasePlotContext &context)
bool m_shouldTracersBeVisible
Tells if the tracers should be visible.
virtual void hideSelectionRectangle(bool reset_values=false)
virtual void updateIntegrationScopeDrawing(bool as_line_segment=false, bool for_integration=false)
virtual void directionKeyReleaseEvent(QKeyEvent *event)
QCPItemText * mp_yDeltaTextItem
void mousePressEventSignal(QMouseEvent *event, const pappso::BasePlotContext &context)
QCPItemLine * mp_selectionRectangeLine1
Rectangle defining the borders of zoomed-in/out data.
virtual QCPRange getOutermostRangeX(bool &found_range) const
void lastCursorHoveredPointSignal(const QPointF &pointf)
virtual const BasePlotContext & getContext() const
virtual void drawSelectionRectangleAndPrepareZoom(bool as_line_segment=false, bool for_integration=false)
virtual QCPRange getRangeY(bool &found_range, int index, RangeType range_type) const
virtual void keyPressEvent(QKeyEvent *event)
KEYBOARD-related EVENTS.
virtual ~BasePlotWidget()
Destruct this BasePlotWidget instance.
void xAxisMeasurementSignal(const pappso::BasePlotContext &context, bool with_delta)
virtual QCPRange getRangeX(bool &found_range, int index, RangeType range_type) const
MOUSE MOVEMENTS mouse/keyboard-triggered.
virtual void updateIntegrationScope(bool for_integration=false)
virtual void mouseMoveHandlerLeftButtonDraggingCursor(QMouseEvent *event)
QCPItemLine * mp_selectionRectangeLine2
void plotRangesChangedSignal(QMouseEvent *event, const pappso::BasePlotContext &context)
QCPItemText * mp_xDeltaTextItem
Text describing the x-axis delta value during a drag operation.
virtual void setAxisLabelX(const QString &label)
int m_mouseMoveHandlerSkipCount
Counter to handle the "fat data" mouse move event handling.
virtual QCPRange getOutermostRangeY(bool &found_range) const
virtual void mouseMoveHandlerRightButtonDraggingCursor(QMouseEvent *event)
int dragDirection()
MOUSE-related EVENTS.
bool isClickOntoYAxis(const QPointF &mousePoint)
virtual void moveMouseCursorPixelCoordToGlobal(QPointF local_coordinates)
QCPItemLine * mp_hPosTracerItem
Horizontal position tracer.
QCPItemLine * mp_vPosTracerItem
Vertical position tracer.
virtual void setPen(const QPen &pen)
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event)
virtual QCPRange getInnermostRangeX(bool &found_range) const
virtual void redrawPlotBackground(QWidget *focusedPlotWidget)
Redraw the background of the focusedPlotWidget plot widget.
virtual void mouseReleaseHandlerLeftButton(QMouseEvent *event)
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
bool isClickOntoXAxis(const QPointF &mousePoint)
virtual void setAxisLabelY(const QString &label)
virtual void restoreAxesRangeHistory(std::size_t index)
Get the axis histories at index index and update the plot ranges.
virtual void drawXScopeSpanFeatures()
virtual void spaceKeyReleaseEvent(QKeyEvent *event)
virtual void replotWithAxisRangeX(double lower, double upper)
virtual void createAllAncillaryItems()
virtual QColor getPlottingColor(QCPAbstractPlottable *plottable_p) const
QBrush m_focusedBrush
Color used for the background of focused plot.
QPen m_pen
Pen used to draw the graph and textual elements in the plot widget.
virtual bool isSelectionRectangleVisible()
virtual bool isVerticalDisplacementAboveThreshold()
virtual void mousePressHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void verticalMoveMouseCursorCountPixels(int pixel_count)
virtual void updateIntegrationScopeRhomb(bool for_integration=false)
void mouseWheelEventSignal(QWheelEvent *event, const pappso::BasePlotContext &context)
virtual void resetAxesRangeHistory()
virtual SelectionDrawingLines whatIsVisibleOfTheSelectionRectangle()
virtual void showTracers()
Show the traces (vertical and horizontal).
virtual QPointF horizontalGetGraphCoordNewPointCountPixels(int pixel_count)
QCPItemLine * mp_selectionRectangeLine4
virtual void horizontalMoveMouseCursorCountPixels(int pixel_count)
virtual int getPlotIndex(QCPAbstractPlottable *plottable_p)
BasePlotWidget(QWidget *parent)
std::vector< QCPRange * > m_yAxisRangeHistory
List of y axis ranges occurring during the panning zooming actions.
virtual QCPRange getInnermostRangeY(bool &found_range) const
virtual void setFocus()
PLOT ITEMS : TRACER TEXT ITEMS...
virtual void drawYScopeSpanFeatures()
virtual const QPen & getPen() const
virtual void updateContextXandYAxisRanges()
virtual void updateIntegrationScopeHorizontalRhomb(bool for_integration=false)
virtual void mousePseudoButtonKeyPressEvent(QKeyEvent *event)
virtual void setPlottingColor(QCPAbstractPlottable *plottable_p, const QColor &new_color)
virtual void calculateDragDeltas()
QCPRange getRange(Enums::Axis axis, RangeType range_type, bool &found_range) const
virtual QPointF verticalGetGraphCoordNewPointCountPixels(int pixel_count)
QCPItemLine * mp_vStartTracerItem
Vertical selection start tracer (typically in green).
virtual void mouseReleaseHandler(QMouseEvent *event)
QBrush m_unfocusedBrush
Color used for the background of unfocused plot.
virtual void axisRescale()
RANGE-related functions.
void mouseMoveDraggingCursorSignal(QMouseEvent *event, const pappso::BasePlotContext &context)
virtual void moveMouseCursorGraphCoordToGlobal(QPointF plot_coordinates)
virtual QString allLayerNamesToString() const
QCPItemLine * mp_selectionRectangeLine3
void mouseReleaseEventSignal(QMouseEvent *event, const pappso::BasePlotContext &context)
virtual void axisDoubleClickHandler(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
QCPItemLine * mp_vEndTracerItem
Vertical selection end tracer (typically in red).
void plotRangesChangedWheelEventSignal(QWheelEvent *event, const pappso::BasePlotContext &context)
virtual void mouseMoveHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void directionKeyPressEvent(QKeyEvent *event)
virtual QString layerableLayerName(QCPLayerable *layerable_p) const
virtual void keyReleaseEvent(QKeyEvent *event)
Handle specific key codes and trigger respective actions.
virtual void resetSelectionRectangle()
virtual void restorePreviousAxesRangeHistory()
Go up one history element in the axis history.
virtual int layerableLayerIndex(QCPLayerable *layerable_p) const
void integrationRequestedSignal(const BasePlotContext &context)
void keyReleaseEventSignal(QKeyEvent *event, const pappso::BasePlotContext &context)
virtual void replotWithAxisRangeY(double lower, double upper)
virtual void hideTracers()
Hide the traces (vertical and horizontal).
virtual void mouseReleaseHandlerRightButton(QMouseEvent *event)
virtual void updateIntegrationScopeVerticalRhomb(bool for_integration=false)
virtual void mousePseudoButtonKeyReleaseEvent(QKeyEvent *event)
virtual void hideAllPlotItems()
PLOTTING / REPLOTTING functions.
std::vector< QCPRange * > m_xAxisRangeHistory
List of x axis ranges occurring during the panning zooming actions.
BasePlotContext m_context
static int zeroDecimalsInValue(pappso_double value)
Determine the number of zero decimals between the decimal point and the first non-zero decimal.
Definition utils.cpp:103
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition aa.cpp:39
SelectionDrawingLines