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
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 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
614
615 if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
616 event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
617 {
618 return directionKeyPressEvent(event);
619 }
620 else if(event->key() == m_leftMousePseudoButtonKey ||
621 event->key() == m_rightMousePseudoButtonKey)
622 {
623 return mousePseudoButtonKeyPressEvent(event);
624 }
625
626 // Do not do anything here, because this function is used by derived classes
627 // that will emit the signal below. Otherwise there are going to be multiple
628 // signals sent.
629 // qDebug() << "Going to emit keyPressEventSignal(m_context);";
630 // emit keyPressEventSignal(m_context);
631}
632
633//! Handle specific key codes and trigger respective actions.
634void
636{
637 m_context.m_releasedKeyCode = event->key();
638
639 // The keyboard key is being released, set the key code to 0.
640 m_context.m_pressedKeyCode = 0;
641
642 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
643
644 // Now test if the key that was released is one of the housekeeping keys.
645 if(event->key() == Qt::Key_Backspace)
646 {
647 // qDebug();
648
649 // The user wants to iterate back in the x/y axis range history.
651
652 event->accept();
653 }
654 else if(event->key() == Qt::Key_Space)
655 {
656 return spaceKeyReleaseEvent(event);
657 }
658 else if(event->key() == Qt::Key_Delete)
659 {
660 // The user wants to delete a graph. What graph is to be determined
661 // programmatically:
662
663 // If there is a single graph, then that is the graph to be removed.
664 // If there are more than one graph, then only the ones that are selected
665 // are to be removed.
666
667 // Note that the user of this widget might want to provide the user with
668 // the ability to specify if all the children graph needs to be removed
669 // also. This can be coded in key modifiers. So provide the context.
670
671 int graph_count = plottableCount();
672
673 if(!graph_count)
674 {
675 // qDebug() << "Not a single graph in the plot widget. Doing
676 // nothing.";
677
678 event->accept();
679 return;
680 }
681
682 if(graph_count == 1)
683 {
684 // qDebug() << "A single graph is in the plot widget. Emitting a graph
685 // " "destruction requested signal for it:"
686 //<< graph();
687
689 }
690 else
691 {
692 // At this point we know there are more than one graph in the plot
693 // widget. We need to get the selected one (if any).
694 QList<QCPGraph *> selected_graph_list;
695
696 selected_graph_list = selectedGraphs();
697
698 if(!selected_graph_list.size())
699 {
700 event->accept();
701 return;
702 }
703
704 // qDebug() << "Number of selected graphs to be destrobyed:"
705 //<< selected_graph_list.size();
706
707 for(int iter = 0; iter < selected_graph_list.size(); ++iter)
708 {
709 // qDebug()
710 //<< "Emitting a graph destruction requested signal for graph:"
711 //<< selected_graph_list.at(iter);
712
714 this, selected_graph_list.at(iter), m_context);
715
716 // We do not do this, because we want the slot called by the
717 // signal above to handle that removal. Remember that it is not
718 // possible to delete graphs manually.
719 //
720 // removeGraph(selected_graph_list.at(iter));
721 }
722 event->accept();
723 }
724 }
725 // End of
726 // else if(event->key() == Qt::Key_Delete)
727 else if(event->key() == Qt::Key_T)
728 {
729 // The user wants to toggle the visibiity of the tracers.
731
733 hideTracers();
734 else
735 showTracers();
736
737 event->accept();
738 }
739 else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
740 event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
741 {
742 return directionKeyReleaseEvent(event);
743 }
744 else if(event->key() == m_leftMousePseudoButtonKey ||
745 event->key() == m_rightMousePseudoButtonKey)
746 {
748 }
749 else if(event->key() == Qt::Key_S)
750 {
751 // The user is defining the size of the rhomboid fixed side. That could be
752 // either a vertical side (less intuitive) or a horizontal size (more
753 // intuitive, first exclusive implementation). But, in order to be able to
754 // perform identical integrations starting from non-transposed color maps
755 // and transposed color maps, the ability to define a vertical fixed size
756 // side of the rhomboid integration scope has become necessary.
757
758 // Check if the vertical displacement is significant (>= 10% of the color
759 // map height.
760
762 {
763 // The user is dragging the cursor vertically in a sufficient delta to
764 // consider that they are willing to define a vertical fixed size
765 // of the rhomboid integration scope.
766
767 m_context.m_integrationScopeRhombWidth = 0;
768 m_context.m_integrationScopeRhombHeight = abs(
769 m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y());
770
771 // qDebug() << "Set m_context.m_integrationScopePolyHeight to"
772 // << m_context.m_integrationScopeRhombHeight
773 // << "upon release of S key";
774 }
775 else
776 {
777 // The user is dragging the cursor horiontally to define a horizontal
778 // fixed size of the rhomboid integration scope.
779
780 m_context.m_integrationScopeRhombWidth = abs(
781 m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x());
782 m_context.m_integrationScopeRhombHeight = 0;
783
784 // qDebug() << "Set m_context.m_integrationScopePolyWidth to"
785 // << m_context.m_integrationScopeRhombWidth
786 // << "upon release of S key";
787 }
788 }
789 // At this point emit the signal, since we did not treat it. Maybe the
790 // consumer widget wants to know that the keyboard key was released.
791
793}
794
795void
796BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
797{
798 // qDebug();
799}
800
801void
803{
804 // qDebug() << "event key:" << event->key();
805
806 // The user is trying to move the positional cursor/markers. There are
807 // multiple way they can do that:
808 //
809 // 1.a. Hitting the arrow left/right keys alone will search for next pixel.
810 // 1.b. Hitting the arrow left/right keys with Alt modifier will search for
811 // a multiple of pixels that might be equivalent to one 20th of the pixel
812 // width of the plot widget. 1.c Hitting the left/right keys with Alt and
813 // Shift modifiers will search for a multiple of pixels that might be the
814 // equivalent to half of the pixel width.
815 //
816 // 2. Hitting the Control modifier will move the cursor to the next data
817 // point of the graph.
818
819 int pixel_increment = 0;
820
821 if(m_context.m_keyboardModifiers == Qt::NoModifier)
822 pixel_increment = 1;
823 else if(m_context.m_keyboardModifiers == Qt::AltModifier)
824 pixel_increment = 50;
825
826 // The user is moving the positional markers. This is equivalent to a
827 // non-dragging cursor movement to the next pixel. Note that the origin is
828 // located at the top left, so key down increments and key up decrements.
829
830 if(event->key() == Qt::Key_Left)
831 horizontalMoveMouseCursorCountPixels(-pixel_increment);
832 else if(event->key() == Qt::Key_Right)
834 else if(event->key() == Qt::Key_Up)
835 verticalMoveMouseCursorCountPixels(-pixel_increment);
836 else if(event->key() == Qt::Key_Down)
837 verticalMoveMouseCursorCountPixels(pixel_increment);
838
839 event->accept();
840}
841
842void
844{
845 // qDebug() << "event key:" << event->key();
846 event->accept();
847}
848
849void
851 [[maybe_unused]] QKeyEvent *event)
852{
853 // qDebug();
854}
855
856void
858{
859
860 QPointF pixel_coordinates(
861 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
862 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
863
864 Qt::MouseButton button = Qt::NoButton;
865 QEvent::Type q_event_type = QEvent::MouseButtonPress;
866
867 if(event->key() == m_leftMousePseudoButtonKey)
868 {
869 // Toggles the left mouse button on/off
870
871 button = Qt::LeftButton;
872
873 m_context.m_isLeftPseudoButtonKeyPressed =
874 !m_context.m_isLeftPseudoButtonKeyPressed;
875
876 if(m_context.m_isLeftPseudoButtonKeyPressed)
877 q_event_type = QEvent::MouseButtonPress;
878 else
879 q_event_type = QEvent::MouseButtonRelease;
880 }
881 else if(event->key() == m_rightMousePseudoButtonKey)
882 {
883 // Toggles the right mouse button.
884
885 button = Qt::RightButton;
886
887 m_context.m_isRightPseudoButtonKeyPressed =
888 !m_context.m_isRightPseudoButtonKeyPressed;
889
890 if(m_context.m_isRightPseudoButtonKeyPressed)
891 q_event_type = QEvent::MouseButtonPress;
892 else
893 q_event_type = QEvent::MouseButtonRelease;
894 }
895
896 // qDebug() << "pressed/released pseudo button:" << button
897 //<< "q_event_type:" << q_event_type;
898
899 // Synthesize a QMouseEvent and use it.
900
901 QMouseEvent *mouse_event_p =
902 new QMouseEvent(q_event_type,
903 pixel_coordinates,
904 mapToGlobal(pixel_coordinates.toPoint()),
905 mapToGlobal(pixel_coordinates.toPoint()),
906 button,
907 button,
908 m_context.m_keyboardModifiers,
909 Qt::MouseEventSynthesizedByApplication);
910
911 if(q_event_type == QEvent::MouseButtonPress)
912 mousePressHandler(mouse_event_p);
913 else
914 mouseReleaseHandler(mouse_event_p);
915
916 delete mouse_event_p;
917 // event->accept();
918}
919
920/// KEYBOARD-related EVENTS
921
922
923/// MOUSE-related EVENTS
924
925void
927{
928
929 // If we have no focus, then get it. See setFocus() to understand why asking
930 // for focus is cosly and thus why we want to make this decision first.
931 if(!hasFocus())
932 setFocus();
933
934 // qDebug() << (graph() != nullptr);
935 // if(graph(0) != nullptr)
936 // { // check if the widget contains some graphs
937
938 // The event->button() must be by Qt instructions considered to be 0.
939
940 // Whatever happens, we want to store the plot coordinates of the current
941 // mouse cursor position (will be useful later for countless needs).
942
943 QPointF mousePoint = event->position();
944
945 // qDebug() << "local mousePoint position in pixels:" << mousePoint;
946
947 m_context.m_lastCursorHoveredPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
948 m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
949
950 // qDebug() << "lastCursorHoveredPoint coord:"
951 //<< m_context.m_lastCursorHoveredPoint;
952
953 // Now, depending on the button(s) (if any) that are pressed or not, we
954 // have a different processing.
955
956 // qDebug();
957
958 if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
959 m_context.m_pressedMouseButtons & Qt::RightButton)
961 else
963 // }
964 // qDebug();
965 event->accept();
966}
967
968void
970{
971
972 // qDebug();
973 m_context.m_isMouseDragging = false;
974
975 // qDebug();
976 // We are not dragging the mouse (no button pressed), simply let this
977 // widget's consumer know the position of the cursor and update the markers.
978 // The consumer of this widget will update mouse cursor position at
979 // m_context.m_lastCursorHoveredPoint if so needed.
980
981 emit lastCursorHoveredPointSignal(m_context.m_lastCursorHoveredPoint);
982
983 // qDebug();
984
985 // We are not dragging, so we do not show the region end tracer we only
986 // show the anchoring start trace that might be of use if the user starts
987 // using the arrow keys to move the cursor.
988 if(mp_vEndTracerItem != nullptr)
989 mp_vEndTracerItem->setVisible(false);
990
991 // qDebug();
992 // Only bother with the tracers if the user wants them to be visible.
993 // Their crossing point must be exactly at the last cursor-hovered point.
994
996 {
997 // We are not dragging, so only show the position markers (v and h);
998
999 // qDebug();
1000 if(mp_hPosTracerItem != nullptr)
1001 {
1002 // Horizontal position tracer.
1003 mp_hPosTracerItem->setVisible(true);
1004 mp_hPosTracerItem->start->setCoords(
1005 xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
1006 mp_hPosTracerItem->end->setCoords(
1007 xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
1008 }
1009
1010 // qDebug();
1011 // Vertical position tracer.
1012 if(mp_vPosTracerItem != nullptr)
1013 {
1014 mp_vPosTracerItem->setVisible(true);
1015
1016 mp_vPosTracerItem->setVisible(true);
1017 mp_vPosTracerItem->start->setCoords(
1018 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1019 mp_vPosTracerItem->end->setCoords(
1020 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1021 }
1022
1023 // qDebug();
1024 replot();
1025 }
1026
1027
1028 return;
1029}
1030
1031void
1033{
1034 // qDebug();
1035
1036 m_context.m_isMouseDragging = true;
1037
1038 // Now store the mouse position data into the the current drag point
1039 // member datum, that will be used in countless occasions later.
1040 m_context.m_currentDragPoint = m_context.m_lastCursorHoveredPoint;
1041 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1042
1043 // When we drag (either keyboard or mouse), we hide the position markers
1044 // (black) and we show the start and end vertical markers for the region.
1045 // Then, we draw the horizontal region range marker that delimits
1046 // horizontally the dragged-over region.
1047
1048 if(mp_hPosTracerItem != nullptr)
1049 mp_hPosTracerItem->setVisible(false);
1050 if(mp_vPosTracerItem != nullptr)
1051 mp_vPosTracerItem->setVisible(false);
1052
1053 // Only bother with the tracers if the user wants them to be visible.
1055 {
1056
1057 // The vertical end tracer position must be refreshed.
1058 mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
1059 yAxis->range().upper);
1060
1061 mp_vEndTracerItem->end->setCoords(m_context.m_currentDragPoint.x(),
1062 yAxis->range().lower);
1063
1064 mp_vEndTracerItem->setVisible(true);
1065 }
1066
1067 // Whatever the button, when we are dealing with the axes, we do not
1068 // want to show any of the tracers.
1069
1070 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1071 {
1072 if(mp_hPosTracerItem != nullptr)
1073 mp_hPosTracerItem->setVisible(false);
1074 if(mp_vPosTracerItem != nullptr)
1075 mp_vPosTracerItem->setVisible(false);
1076
1077 if(mp_vStartTracerItem != nullptr)
1078 mp_vStartTracerItem->setVisible(false);
1079 if(mp_vEndTracerItem != nullptr)
1080 mp_vEndTracerItem->setVisible(false);
1081 }
1082 else
1083 {
1084 // qDebug() << "Not moving the mouse cursor over any of the axes.";
1085
1086 // Since we are not dragging the mouse cursor over the axes, make sure
1087 // we store the drag directions in the context, as this might be
1088 // useful for later operations.
1089 // qDebug() << "Recording the drag direction(s).";
1090
1091 m_context.recordDragDirections();
1092
1093 // qDebug() << "Drag direction(s): " <<
1094 // m_context.dragDirectionsToString();
1095 }
1096
1097 // Because when we drag the mouse button (whatever the button) we need to
1098 // know what is the drag delta (distance between start point and current
1099 // point of the drag operation) on both axes, ask that these x|y deltas be
1100 // computed.
1102
1103 // Now deal with the BUTTON-SPECIFIC CODE.
1104
1105 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
1106 {
1108 }
1109 else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
1110 {
1112 }
1113}
1114
1115void
1117{
1118 // qDebug() << "The left button is dragging.";
1119
1120 // Set the context.m_isMeasuringDistance to false, which later might be set
1121 // to true if effectively we are measuring a distance. This is required
1122 // because the derived widget classes might want to know if they have to
1123 // perform some action on the basis that context is measuring a distance,
1124 // for example the mass spectrum-specific widget might want to compute
1125 // deconvolutions.
1126
1127 m_context.m_isMeasuringDistance = false;
1128
1129 // Let's first check if the mouse drag operation originated on either
1130 // axis. In that case, the user is performing axis reframing or rescaling.
1131
1132 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1133 {
1134 // qDebug() << "Click was on one of the axes.";
1135
1136 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1137 {
1138 // The user is asking a rescale of the plot.
1139
1140 // We know that we do not want the tracers when we perform axis
1141 // rescaling operations.
1142
1143 if(mp_hPosTracerItem != nullptr)
1144 mp_hPosTracerItem->setVisible(false);
1145 if(mp_vPosTracerItem != nullptr)
1146 mp_vPosTracerItem->setVisible(false);
1147
1148 if(mp_vStartTracerItem != nullptr)
1149 mp_vStartTracerItem->setVisible(false);
1150 if(mp_vEndTracerItem != nullptr)
1151 mp_vEndTracerItem->setVisible(false);
1152
1153 // This operation is particularly intensive, thus we want to
1154 // reduce the number of calculations by skipping this calculation
1155 // a number of times. The user can ask for this feature by
1156 // clicking the 'Q' letter.
1157
1158 if(m_context.m_pressedKeyCode == Qt::Key_Q)
1159 {
1161 {
1163 return;
1164 }
1165 else
1166 {
1168 }
1169 }
1170
1171 // qDebug() << "Asking that the axes be rescaled.";
1172
1173 axisRescale();
1174 }
1175 else
1176 {
1177 // The user was simply dragging the axis. Just pan, that is slide
1178 // the plot in the same direction as the mouse movement and with the
1179 // same amplitude.
1180
1181 // qDebug() << "Asking that the axes be panned.";
1182
1183 axisPan();
1184 }
1185
1186 return;
1187 }
1188
1189 // At this point we understand that the user was not performing any
1190 // panning/rescaling operation by clicking on any one of the axes.. Go on
1191 // with other possibilities.
1192
1193 // Let's check if the user is actually drawing a rectangle (covering a
1194 // real area) or is drawing a line.
1195
1196 // qDebug() << "The mouse dragging did not originate on an axis.";
1197
1199 {
1200 // qDebug() << "Apparently the selection is two-dimensional.";
1201
1202 // When we draw a two-dimensional integration scope, the tracers are of no
1203 // use.
1204
1205 if(mp_hPosTracerItem != nullptr)
1206 mp_hPosTracerItem->setVisible(false);
1207 if(mp_vPosTracerItem != nullptr)
1208 mp_vPosTracerItem->setVisible(false);
1209
1210 if(mp_vStartTracerItem != nullptr)
1211 mp_vStartTracerItem->setVisible(false);
1212 if(mp_vEndTracerItem != nullptr)
1213 mp_vEndTracerItem->setVisible(false);
1214
1215 // Draw the rectangle, false, not as line segment and
1216 // false, not for integration
1217 drawSelectionRectangleAndPrepareZoom(false /*as_line_segment*/,
1218 false /* for_integration*/);
1219
1220 // Draw the selection width/height text
1223 }
1224 else
1225 {
1226 // qDebug() << "Apparently we are measuring a delta.";
1227
1228 // Draw the rectangle, true, as line segment and
1229 // false, not for integration
1231
1232 // The pure position tracers should be hidden.
1233 if(mp_hPosTracerItem != nullptr)
1234 mp_hPosTracerItem->setVisible(true);
1235 if(mp_vPosTracerItem != nullptr)
1236 mp_vPosTracerItem->setVisible(true);
1237
1238 // Then, make sure the region range vertical tracers are visible.
1239 if(mp_vStartTracerItem != nullptr)
1240 mp_vStartTracerItem->setVisible(true);
1241 if(mp_vEndTracerItem != nullptr)
1242 mp_vEndTracerItem->setVisible(true);
1243
1244 // Draw the selection width text
1246 }
1247}
1248
1249void
1251{
1252 // qDebug() << "The right button is dragging.";
1253
1254 // Set the context.m_isMeasuringDistance to false, which later might be set
1255 // to true if effectively we are measuring a distance. This is required
1256 // because the derived widgets might want to know if they have to perform
1257 // some action on the basis that context is measuring a distance, for
1258 // example the mass spectrum-specific widget might want to compute
1259 // deconvolutions.
1260
1261 m_context.m_isMeasuringDistance = false;
1262
1264 {
1265 // qDebug() << "Apparently the selection has height.";
1266
1267 // When we draw a rectangle the tracers are of no use.
1268
1269 if(mp_hPosTracerItem != nullptr)
1270 mp_hPosTracerItem->setVisible(false);
1271 if(mp_vPosTracerItem != nullptr)
1272 mp_vPosTracerItem->setVisible(false);
1273
1274 if(mp_vStartTracerItem != nullptr)
1275 mp_vStartTracerItem->setVisible(false);
1276 if(mp_vEndTracerItem != nullptr)
1277 mp_vEndTracerItem->setVisible(false);
1278
1279 // Draw the rectangle, false for as_line_segment and true for
1280 // integration.
1282
1283 // Draw the selection width/height text
1286 }
1287 else
1288 {
1289 // qDebug() << "Apparently the selection is a not a rectangle.";
1290
1291 // Draw the rectangle, true as line segment and
1292 // true for integration
1294
1295 // Draw the selection width text
1297 }
1298}
1299
1300void
1302{
1303 // qDebug() << "Entering";
1304
1305 // When the user clicks this widget it has to take focus.
1306 setFocus();
1307
1308 QPointF mousePoint = event->position();
1309
1310 m_context.m_lastPressedMouseButton = event->button();
1311 m_context.m_mouseButtonsAtMousePress = event->buttons();
1312
1313 // The pressedMouseButtons must continually inform on the status of
1314 // pressed buttons so add the pressed button.
1315 m_context.m_pressedMouseButtons |= event->button();
1316
1317 // qDebug().noquote() << m_context.toString();
1318
1319 // In all the processing of the events, we need to know if the user is
1320 // clicking somewhere with the intent to change the plot ranges (reframing
1321 // or rescaling the plot).
1322 //
1323 // Reframing the plot means that the new x and y axes ranges are modified
1324 // so that they match the region that the user has encompassed by left
1325 // clicking the mouse and dragging it over the plot. That is we reframe
1326 // the plot so that it contains only the "selected" region.
1327 //
1328 // Rescaling the plot means the the new x|y axis range is modified such
1329 // that the lower axis range is constant and the upper axis range is moved
1330 // either left or right by the same amont as the x|y delta encompassed by
1331 // the user moving the mouse. The axis is thus either compressed (mouse
1332 // movement is leftwards) or un-compressed (mouse movement is rightwards).
1333
1334 // There are two ways to perform axis range modifications:
1335 //
1336 // 1. By clicking on any of the axes
1337 // 2. By clicking on the plot region but using keyboard key modifiers,
1338 // like Alt and Ctrl.
1339 //
1340 // We need to know both cases separately which is why we need to perform a
1341 // number of tests below.
1342
1343 // Let's check if the click is on the axes, either X or Y, because that
1344 // will allow us to take proper actions.
1345
1346 if(isClickOntoXAxis(mousePoint))
1347 {
1348 // The X axis was clicked upon, we need to document that:
1349 // qDebug() << __FILE__ << __LINE__
1350 //<< "Layout element is axisRect and actually on an X axis part.";
1351
1352 m_context.m_wasClickOnXAxis = true;
1353
1354 // int currentInteractions = interactions();
1355 // currentInteractions |= QCP::iRangeDrag;
1356 // setInteractions((QCP::Interaction)currentInteractions);
1357 // axisRect()->setRangeDrag(xAxis->orientation());
1358 }
1359 else
1360 m_context.m_wasClickOnXAxis = false;
1361
1362 if(isClickOntoYAxis(mousePoint))
1363 {
1364 // The Y axis was clicked upon, we need to document that:
1365 // qDebug() << __FILE__ << __LINE__
1366 //<< "Layout element is axisRect and actually on an Y axis part.";
1367
1368 m_context.m_wasClickOnYAxis = true;
1369
1370 // int currentInteractions = interactions();
1371 // currentInteractions |= QCP::iRangeDrag;
1372 // setInteractions((QCP::Interaction)currentInteractions);
1373 // axisRect()->setRangeDrag(yAxis->orientation());
1374 }
1375 else
1376 m_context.m_wasClickOnYAxis = false;
1377
1378 // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1379
1380 if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
1381 {
1382 // qDebug() << __FILE__ << __LINE__
1383 // << "Click outside of axes.";
1384
1385 // int currentInteractions = interactions();
1386 // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1387 // setInteractions((QCP::Interaction)currentInteractions);
1388 }
1389
1390 m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1391 m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1392
1393 // Now install the vertical start tracer at the last cursor hovered
1394 // position.
1395 if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
1396 mp_vStartTracerItem->setVisible(true);
1397
1398 if(mp_vStartTracerItem != nullptr)
1399 {
1400 mp_vStartTracerItem->start->setCoords(
1401 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1402 mp_vStartTracerItem->end->setCoords(
1403 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1404 }
1405
1406 replot();
1407
1409
1410 // qDebug() << "Exiting after having emitted mousePressEventSignal with base
1411 // context:"
1412 // << m_context.toString();
1413}
1414
1415void
1417{
1418 // qDebug() << "Entering";
1419
1420 // Now the real code of this function.
1421
1422 m_context.m_lastReleasedMouseButton = event->button();
1423
1424 // The event->buttons() is the description of the buttons that are pressed
1425 // at the moment the handler is invoked, that is now. If left and right were
1426 // pressed, and left was released, event->buttons() would be right.
1427 m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1428
1429 // The pressedMouseButtons must continually inform on the status of pressed
1430 // buttons so remove the released button.
1431 m_context.m_pressedMouseButtons ^= event->button();
1432
1433 // qDebug().noquote() << m_context.toString();
1434
1435 // We'll need to know if modifiers were pressed a the moment the user
1436 // released the mouse button.
1437 m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1438
1439 if(!m_context.m_isMouseDragging)
1440 {
1441 // Let the user know that the mouse was *not* being dragged.
1442 m_context.m_wasMouseDragging = false;
1443
1444 event->accept();
1445
1446 return;
1447 }
1448
1449 // Let the user know that the mouse was being dragged.
1450 m_context.m_wasMouseDragging = true;
1451
1452 // We cannot hide all items in one go because we rely on their visibility
1453 // to know what kind of dragging operation we need to perform (line-only
1454 // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1455 // only thing we know is that we can make the text invisible.
1456
1457 // Same for the x delta text item
1458 mp_xDeltaTextItem->setVisible(false);
1459 mp_yDeltaTextItem->setVisible(false);
1460
1461 // We do not show the end vertical region range marker.
1462 mp_vEndTracerItem->setVisible(false);
1463
1464 // Horizontal position tracer.
1465 mp_hPosTracerItem->setVisible(true);
1466 mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1467 m_context.m_lastCursorHoveredPoint.y());
1468 mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1469 m_context.m_lastCursorHoveredPoint.y());
1470
1471 // Vertical position tracer.
1472 mp_vPosTracerItem->setVisible(true);
1473
1474 mp_vPosTracerItem->setVisible(true);
1475 mp_vPosTracerItem->start->setCoords(m_context.m_lastCursorHoveredPoint.x(),
1476 yAxis->range().upper);
1477 mp_vPosTracerItem->end->setCoords(m_context.m_lastCursorHoveredPoint.x(),
1478 yAxis->range().lower);
1479
1480 // Force replot now because later that call might not be performed.
1481 replot();
1482
1483 // If we were using the "quantum" display for the rescale of the axes
1484 // using the Ctrl-modified left button click drag in the axes, then reset
1485 // the count to 0.
1487
1488 // By definition we are stopping the drag operation by releasing the mouse
1489 // button. Whatever that mouse button was pressed before and if there was
1490 // one pressed before. We cannot set that boolean value to false before
1491 // this place, because we call a number of routines above that need to know
1492 // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1493 // example.
1494
1495 m_context.m_isMouseDragging = false;
1496
1497 // Now that we have computed the useful ranges, we need to check what to do
1498 // depending on the button that was pressed.
1499
1500 if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1501 {
1503 }
1504 else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1505 {
1507 }
1508
1509 event->accept();
1510
1511 // Before returning, emit the signal for the user of
1512 // this class consumption.
1513 // qDebug() << "Emitting mouseReleaseEventSignal.";
1515
1516 // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with base
1517 // context:"
1518 // << m_context.toString();
1519
1520 return;
1521}
1522
1523void
1525{
1526 // qDebug();
1527
1528 if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
1529 {
1530
1531 // When the mouse move handler pans the plot, we cannot store each axes
1532 // range history element that would mean store a huge amount of such
1533 // elements, as many element as there are mouse move event handled by
1534 // the Qt event queue. But we can store an axis range history element
1535 // for the last situation of the mouse move: when the button is
1536 // released:
1537
1539
1540 // qDebug() << "emit plotRangesChangedSignal(m_context);"
1541
1543
1544 replot();
1545
1546 // Nothing else to do.
1547 return;
1548 }
1549
1550 // There are two possibilities:
1551 //
1552 // 1. The full integration scope (four lines) were currently drawn, which
1553 // means the user was willing to perform a zoom operation.
1554 //
1555 // 2. Only the first top line was drawn, which means the user was dragging
1556 // the cursor horizontally. That might have two ends, as shown below.
1557
1558 // So, first check what is drawn of the selection polygon.
1559
1560 SelectionDrawingLines selection_drawing_lines =
1562
1563 // Now that we know what was currently drawn of the selection polygon, we
1564 // can remove it. true to reset the values to 0.
1566
1567 // Force replot now because later that call might not be performed.
1568 replot();
1569
1570 if(selection_drawing_lines == SelectionDrawingLines::FULL_POLYGON)
1571 {
1572 // qDebug() << "Yes, the full polygon was visible";
1573
1574 // If we were dragging with the left button pressed and could draw a
1575 // rectangle, then we were preparing a zoom operation. Let's bring that
1576 // operation to its accomplishment.
1577
1578 axisZoom();
1579
1580 return;
1581 }
1582 else if(selection_drawing_lines == SelectionDrawingLines::TOP_LINE)
1583 {
1584 // qDebug() << "No, only the top line of the full polygon was visible";
1585
1586 // The user was dragging the left mouse cursor and that may mean they
1587 // were measuring a distance or willing to perform a special zoom
1588 // operation if the Ctrl key was down.
1589
1590 // If the user started by clicking in the plot region, dragged the mouse
1591 // cursor with the left button and pressed the Ctrl modifier, then that
1592 // means that they wanted to do a rescale over the x-axis in the form of
1593 // a reframing.
1594
1595 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1596 {
1597 return axisReframe();
1598 }
1599 }
1600 // else
1601 // qDebug() << "Another possibility.";
1602}
1603
1604void
1606{
1607 // qDebug();
1608 // The right button is used for the integrations. Not for axis range
1609 // operations. So all we have to do is remove the various graphics items and
1610 // send a signal with the context that contains all the data required by the
1611 // user to perform the integrations over the right plot regions.
1612
1613 // Whatever we were doing we need to make the selection line invisible:
1614
1615 if(mp_xDeltaTextItem->visible())
1616 mp_xDeltaTextItem->setVisible(false);
1617 if(mp_yDeltaTextItem->visible())
1618 mp_yDeltaTextItem->setVisible(false);
1619
1620 // Also make the vertical end tracer invisible.
1621 mp_vEndTracerItem->setVisible(false);
1622
1623 // Once the integration is asked for, then the selection rectangle if of no
1624 // more use.
1626
1627 // Force replot now because later that call might not be performed.
1628 replot();
1629
1630 // Note that we only request an integration if the x-axis delta is enough.
1631
1632 double x_delta_pixel =
1633 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1634 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1635
1636 if(x_delta_pixel > 3)
1637 {
1638 // qDebug() << "Emitting integrationRequestedSignal(m_context)";
1640 }
1641 // else
1642 // qDebug() << "Not asking for integration.";
1643}
1644
1645void
1646BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
1647{
1648 // We should record the new range values each time the wheel is used to
1649 // zoom/unzoom.
1650
1651 m_context.m_xRange = QCPRange(xAxis->range());
1652 m_context.m_yRange = QCPRange(yAxis->range());
1653
1654 // qDebug() << "New x range: " << m_context.m_xRange;
1655 // qDebug() << "New y range: " << m_context.m_yRange;
1656
1658
1661
1662 event->accept();
1663}
1664
1665void
1667 QCPAxis *axis,
1668 [[maybe_unused]] QCPAxis::SelectablePart part,
1669 QMouseEvent *event)
1670{
1671 // qDebug();
1672
1673 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1674
1675 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1676 {
1677 // qDebug();
1678
1679 // If the Ctrl modifiers is active, then both axes are to be reset. Also
1680 // the histories are reset also.
1681
1682 rescaleAxes();
1684 }
1685 else
1686 {
1687 // qDebug();
1688
1689 // Only the axis passed as parameter is to be rescaled.
1690 // Reset the range of that axis to the max view possible.
1691
1692 axis->rescale();
1693
1695
1696 event->accept();
1697 }
1698
1699 // The double-click event does not cancel the mouse press event. That is, if
1700 // left-double-clicking, at the end of the operation the button still
1701 // "pressed". We need to remove manually the button from the pressed buttons
1702 // context member.
1703
1704 m_context.m_pressedMouseButtons ^= event->button();
1705
1707
1709
1710 replot();
1711}
1712
1713bool
1714BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1715{
1716 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1717
1718 if(layoutElement &&
1719 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1720 {
1721 // The graph is *inside* the axisRect that is the outermost envelope of
1722 // the graph. Thus, if we want to know if the click was indeed on an
1723 // axis, we need to check what selectable part of the the axisRect we
1724 // were clicking:
1725 QCPAxis::SelectablePart selectablePart;
1726
1727 selectablePart = xAxis->getPartAt(mousePoint);
1728
1729 if(selectablePart == QCPAxis::spAxisLabel ||
1730 selectablePart == QCPAxis::spAxis ||
1731 selectablePart == QCPAxis::spTickLabels)
1732 return true;
1733 }
1734
1735 return false;
1736}
1737
1738bool
1739BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1740{
1741 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1742
1743 if(layoutElement &&
1744 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1745 {
1746 // The graph is *inside* the axisRect that is the outermost envelope of
1747 // the graph. Thus, if we want to know if the click was indeed on an
1748 // axis, we need to check what selectable part of the the axisRect we
1749 // were clicking:
1750 QCPAxis::SelectablePart selectablePart;
1751
1752 selectablePart = yAxis->getPartAt(mousePoint);
1753
1754 if(selectablePart == QCPAxis::spAxisLabel ||
1755 selectablePart == QCPAxis::spAxis ||
1756 selectablePart == QCPAxis::spTickLabels)
1757 return true;
1758 }
1759
1760 return false;
1761}
1762
1763/// MOUSE-related EVENTS
1764
1765
1766/// MOUSE MOVEMENTS mouse/keyboard-triggered
1767
1768int
1770{
1771 // The user is dragging the mouse, probably to rescale the axes, but we need
1772 // to sort out in which direction the drag is happening.
1773
1774 // This function should be called after calculateDragDeltas, so that
1775 // m_context has the proper x/y delta values that we'll compare.
1776
1777 // Note that we cannot compare simply x or y deltas because the y axis might
1778 // have a different scale that the x axis. So we first need to convert the
1779 // positions to pixels.
1780
1781 double x_delta_pixel =
1782 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1783 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1784
1785 double y_delta_pixel =
1786 fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1787 yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1788
1789 if(x_delta_pixel > y_delta_pixel)
1790 return Qt::Horizontal;
1791
1792 return Qt::Vertical;
1793}
1794
1795void
1797{
1798 // First convert the graph coordinates to pixel coordinates.
1799
1800 QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1801 yAxis->coordToPixel(graph_coordinates.y()));
1802
1803 moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1804}
1805
1806void
1808{
1809 // qDebug() << "Calling set pos with new cursor position.";
1810 QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1811}
1812
1813void
1815{
1816 QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1817
1818 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1819 yAxis->coordToPixel(graph_coord.y()));
1820
1821 // Now we need ton convert the new coordinates to the global position system
1822 // and to move the cursor to that new position. That will create an event to
1823 // move the mouse cursor.
1824
1825 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1826}
1827
1828QPointF
1830{
1831 QPointF pixel_coordinates(
1832 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1833 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1834
1835 // Now convert back to local coordinates.
1836
1837 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1838 yAxis->pixelToCoord(pixel_coordinates.y()));
1839
1840 return graph_coordinates;
1841}
1842
1843void
1845{
1846
1847 QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1848
1849 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1850 yAxis->coordToPixel(graph_coord.y()));
1851
1852 // Now we need ton convert the new coordinates to the global position system
1853 // and to move the cursor to that new position. That will create an event to
1854 // move the mouse cursor.
1855
1856 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1857}
1858
1859QPointF
1861{
1862 QPointF pixel_coordinates(
1863 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1864 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1865
1866 // Now convert back to local coordinates.
1867
1868 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1869 yAxis->pixelToCoord(pixel_coordinates.y()));
1870
1871 return graph_coordinates;
1872}
1873
1874/// MOUSE MOVEMENTS mouse/keyboard-triggered
1875
1876
1877/// RANGE-related functions
1878
1879QCPRange
1880BasePlotWidget::getRangeX(bool &found_range, int index) const
1881{
1882 QCPGraph *graph_p = graph(index);
1883
1884 if(graph_p == nullptr)
1885 qFatal("Programming error.");
1886
1887 return graph_p->getKeyRange(found_range);
1888}
1889
1890QCPRange
1891BasePlotWidget::getRangeY(bool &found_range, int index) const
1892{
1893 QCPGraph *graph_p = graph(index);
1894
1895 if(graph_p == nullptr)
1896 qFatal("Programming error.");
1897
1898 return graph_p->getValueRange(found_range);
1899}
1900
1901QCPRange
1903 RangeType range_type,
1904 bool &found_range) const
1905{
1906
1907 // Iterate in all the graphs in this widget and return a QCPRange that has
1908 // its lower member as the greatest lower value of all
1909 // its upper member as the smallest upper value of all
1910
1911 if(!graphCount())
1912 {
1913 found_range = false;
1914
1915 return QCPRange(0, 1);
1916 }
1917
1918 if(graphCount() == 1)
1919 return graph()->getKeyRange(found_range);
1920
1921 bool found_at_least_one_range = false;
1922
1923 // Create an invalid range.
1924 QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
1925
1926 for(int iter = 0; iter < graphCount(); ++iter)
1927 {
1928 QCPRange temp_range;
1929
1930 bool found_range_for_iter = false;
1931
1932 QCPGraph *graph_p = graph(iter);
1933
1934 // Depending on the axis param, select the key or value range.
1935
1936 if(axis == Enums::Axis::x)
1937 temp_range = graph_p->getKeyRange(found_range_for_iter);
1938 else if(axis == Enums::Axis::y)
1939 temp_range = graph_p->getValueRange(found_range_for_iter);
1940 else
1941 qFatal("Cannot reach this point. Programming error.");
1942
1943 // Was a range found for the iterated graph ? If not skip this
1944 // iteration.
1945
1946 if(!found_range_for_iter)
1947 continue;
1948
1949 // While the innermost_range is invalid, we need to seed it with a good
1950 // one. So check this.
1951
1952 if(!QCPRange::validRange(result_range))
1953 qFatal("The obtained range is invalid !");
1954
1955 // At this point we know the obtained range is OK.
1956 result_range = temp_range;
1957
1958 // We found at least one valid range!
1959 found_at_least_one_range = true;
1960
1961 // At this point we have two valid ranges to compare. Depending on
1962 // range_type, we need to perform distinct comparisons.
1963
1964 if(range_type == RangeType::innermost)
1965 {
1966 if(temp_range.lower > result_range.lower)
1967 result_range.lower = temp_range.lower;
1968 if(temp_range.upper < result_range.upper)
1969 result_range.upper = temp_range.upper;
1970 }
1971 else if(range_type == RangeType::outermost)
1972 {
1973 if(temp_range.lower < result_range.lower)
1974 result_range.lower = temp_range.lower;
1975 if(temp_range.upper > result_range.upper)
1976 result_range.upper = temp_range.upper;
1977 }
1978 else
1979 qFatal("Cannot reach this point. Programming error.");
1980
1981 // Continue to next graph, if any.
1982 }
1983 // End of
1984 // for(int iter = 0; iter < graphCount(); ++iter)
1985
1986 // Let the caller know if we found at least one range.
1987 found_range = found_at_least_one_range;
1988
1989 return result_range;
1990}
1991
1992QCPRange
1994{
1995
1996 return getRange(Enums::Axis::x, RangeType::innermost, found_range);
1997}
1998
1999QCPRange
2001{
2002 return getRange(Enums::Axis::x, RangeType::outermost, found_range);
2003}
2004
2005QCPRange
2007{
2008
2009 return getRange(Enums::Axis::y, RangeType::innermost, found_range);
2010}
2011
2012QCPRange
2014{
2015 return getRange(Enums::Axis::y, RangeType::outermost, found_range);
2016}
2017
2018/// RANGE-related functions
2019
2020
2021/// PLOTTING / REPLOTTING functions
2022
2023void
2025{
2026 // Get the current x lower/upper range, that is, leftmost/rightmost x
2027 // coordinate.
2028 double xLower = xAxis->range().lower;
2029 double xUpper = xAxis->range().upper;
2030
2031 // Get the current y lower/upper range, that is, bottommost/topmost y
2032 // coordinate.
2033 double yLower = yAxis->range().lower;
2034 double yUpper = yAxis->range().upper;
2035
2036 // This function is called only when the user has clicked on the x/y axis or
2037 // when the user has dragged the left mouse button with the Ctrl key
2038 // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
2039 // move handler. So we need to test which axis was clicked-on.
2040
2041 if(m_context.m_wasClickOnXAxis)
2042 {
2043 // We are changing the range of the X axis.
2044
2045 // If xDelta is < 0, then we were dragging from right to left, we are
2046 // compressing the view on the x axis, by adding new data to the right
2047 // hand size of the graph. So we add xDelta to the upper bound of the
2048 // range. Otherwise we are uncompressing the view on the x axis and
2049 // remove the xDelta from the upper bound of the range. This is why we
2050 // have the
2051 // '-'
2052 // and not '+' below;
2053
2054 xAxis->setRange(xLower, xUpper - m_context.m_xDelta);
2055 }
2056 // End of
2057 // if(m_context.m_wasClickOnXAxis)
2058 else // that is, if(m_context.m_wasClickOnYAxis)
2059 {
2060 // We are changing the range of the Y axis.
2061
2062 // See above for an explanation of the computation (the - sign below).
2063
2064 yAxis->setRange(yLower, yUpper - m_context.m_yDelta);
2065 }
2066 // End of
2067 // else // that is, if(m_context.m_wasClickOnYAxis)
2068
2069 // Update the context with the current axes ranges
2070
2072
2074
2075 replot();
2076}
2077
2078void
2080{
2081
2082 // double sorted_start_drag_point_x =
2083 // std::min(m_context.m_startDragPoint.x(),
2084 // m_context.m_currentDragPoint.x());
2085
2086 // xAxis->setRange(sorted_start_drag_point_x,
2087 // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2088
2089 xAxis->setRange(
2090 QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeEnd));
2091
2092 // Note that the y axis should be rescaled from current lower value to new
2093 // upper value matching the y-axis position of the cursor when the mouse
2094 // button was released.
2095
2096 yAxis->setRange(xAxis->range().lower,
2097 std::max<double>(m_context.m_yRegionRangeStart,
2098 m_context.m_yRegionRangeEnd));
2099
2100 // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2101 // xAxis->range().upper
2102 //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2103
2105
2108
2109 replot();
2110}
2111
2112void
2114{
2115
2116 // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2117 // values before using them, because now we want to really have the lower x
2118 // value. Simply craft a QCPRange that will swap the values if lower is not
2119 // < than upper QCustomPlot calls this normalization).
2120
2121 xAxis->setRange(
2122 QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeEnd));
2123
2124 yAxis->setRange(
2125 QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeEnd));
2126
2128
2131
2132 replot();
2133}
2134
2135void
2137{
2138 // Sanity check
2139 if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
2140 qFatal(
2141 "This function can only be called if the mouse click was on one of the "
2142 "axes");
2143
2144 if(m_context.m_wasClickOnXAxis)
2145 {
2146 xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2147 m_context.m_xRange.upper - m_context.m_xDelta);
2148 }
2149
2150 if(m_context.m_wasClickOnYAxis)
2151 {
2152 yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2153 m_context.m_yRange.upper - m_context.m_yDelta);
2154 }
2155
2157
2158 // qDebug() << "The updated context:" << m_context.toString();
2159
2160 // We cannot store the new ranges in the history, because the pan operation
2161 // involved a huge quantity of micro-movements elicited upon each mouse move
2162 // cursor event so we would have a huge history.
2163 // updateAxesRangeHistory();
2164
2165 // Now that the context has the right range values, we can emit the
2166 // signal that will be used by this plot widget users, typically to
2167 // abide by the x/y range lock required by the user.
2168
2170
2171 replot();
2172}
2173
2174void
2176 QCPRange yAxisRange,
2177 Enums::Axis axis)
2178{
2179 // qDebug() << "With axis:" << (int)axis;
2180
2181 if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::x))
2182 {
2183 xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2184 }
2185
2186 if(static_cast<int>(axis) & static_cast<int>(Enums::Axis::y))
2187 {
2188 yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2189 }
2190
2191 // We do not want to update the history, because there would be way too
2192 // much history items, since this function is called upon mouse moving
2193 // handling and not only during mouse release events.
2194 // updateAxesRangeHistory();
2195
2196 replot();
2197}
2198
2199void
2200BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2201{
2202 // qDebug();
2203
2204 xAxis->setRange(lower, upper);
2205
2206 replot();
2207}
2208
2209void
2210BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2211{
2212 // qDebug();
2213
2214 yAxis->setRange(lower, upper);
2215
2216 replot();
2217}
2218
2219/// PLOTTING / REPLOTTING functions
2220
2221
2222/// PLOT ITEMS : TRACER TEXT ITEMS...
2223
2224//! Hide the selection line, the xDelta text and the zoom rectangle items.
2225void
2227{
2228 mp_xDeltaTextItem->setVisible(false);
2229 mp_yDeltaTextItem->setVisible(false);
2230
2231 // mp_zoomRectItem->setVisible(false);
2233
2234 // Force a replot to make sure the action is immediately visible by the
2235 // user, even without moving the mouse.
2236 replot();
2237}
2238
2239//! Show the traces (vertical and horizontal).
2240void
2242{
2244
2245 mp_vPosTracerItem->setVisible(true);
2246 mp_hPosTracerItem->setVisible(true);
2247
2248 mp_vStartTracerItem->setVisible(true);
2249 mp_vEndTracerItem->setVisible(true);
2250
2251 // Force a replot to make sure the action is immediately visible by the
2252 // user, even without moving the mouse.
2253 replot();
2254}
2255
2256//! Hide the traces (vertical and horizontal).
2257void
2259{
2261 mp_hPosTracerItem->setVisible(false);
2262 mp_vPosTracerItem->setVisible(false);
2263
2264 mp_vStartTracerItem->setVisible(false);
2265 mp_vEndTracerItem->setVisible(false);
2266
2267 // Force a replot to make sure the action is immediately visible by the
2268 // user, even without moving the mouse.
2269 replot();
2270}
2271
2272void
2274 bool for_integration)
2275{
2276 // The user has dragged the mouse left button on the graph, which means he
2277 // is willing to draw a selection rectangle, either for zooming-in or for
2278 // integration.
2279
2280 if(mp_xDeltaTextItem != nullptr)
2281 mp_xDeltaTextItem->setVisible(false);
2282 if(mp_yDeltaTextItem != nullptr)
2283 mp_yDeltaTextItem->setVisible(false);
2284
2285 // Ensure the right selection rectangle is drawn.
2286
2287 updateIntegrationScopeDrawing(as_line_segment, for_integration);
2288
2289 // Note that if we draw a zoom rectangle, then we are certainly not
2290 // measuring anything. So set the boolean value to false so that the user of
2291 // this widget or derived classes know that there is nothing to perform upon
2292 // (like deconvolution, for example).
2293
2294 m_context.m_isMeasuringDistance = false;
2295
2296 // Also remove the delta value from the pipeline by sending a simple
2297 // distance without measurement signal.
2298
2299 emit xAxisMeasurementSignal(m_context, false);
2300
2301 replot();
2302}
2303
2304void
2306{
2307 // Depending on the kind of integration scope, we will have to display
2308 // differently calculated values. We want to provide the user with
2309 // the horizontal span of the integration scope. There are different
2310 // situations.
2311
2312 // 1. The scope is mono-dimensional across the x axis: the span
2313 // is thus simply the width.
2314
2315 // 2. The scope is bi-dimensional and is a rectangle: the span is
2316 // thus simply the width.
2317
2318 // 3. The socpe is bi-dimensional and is a rhomboid: the span is
2319 // the width.
2320
2321 // In the first and second cases above, the width is equal to the
2322 // m_context.m_xDelta.
2323
2324 // In the case of the rhomboid, the span is not m_context.m_xDelta,
2325 // it is more than that if the rhomboid is horizontal because it is
2326 // the m_context.m_xDelta plus the rhomboid's horizontal size.
2327
2328 // FIXME: is this still true?
2329 //
2330 // We do not want to show the position markers because the only horiontal
2331 // line to be visible must be contained between the start and end vertical
2332 // tracer items.
2333 mp_hPosTracerItem->setVisible(false);
2334 mp_vPosTracerItem->setVisible(false);
2335
2336 // We want to draw the text in the middle position of the leftmost-rightmost
2337 // point, even with rhomboid scopes.
2338
2339 QPointF leftmost_point;
2340 if(!m_context.msp_integrationScope->getLeftMostPoint(leftmost_point))
2341 qFatal("Could not get the left-most point.");
2342
2343 double width;
2344 if(!m_context.msp_integrationScope->getWidth(width))
2345 qFatal("Could not get width.");
2346 // qDebug() << "width:" << width;
2347
2348 double x_axis_center_position = leftmost_point.x() + width / 2;
2349
2350 // We want the text to print inside the rectangle, always at the current
2351 // drag point so the eye can follow the delta value while looking where to
2352 // drag the mouse. To position the text inside the rectangle, we need to
2353 // know what is the drag direction.
2354
2355 // What is the distance between the rectangle line at current drag point and
2356 // the text itself. Think of this as a margin distance between the
2357 // point of interest and the actual position of the text.
2358 int pixels_away_from_line = 15;
2359
2360 QPointF reference_point_for_y_axis_label_position;
2361
2362 // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2363 // order with respect to the y axis values !!! That is, pixel(0,0) is top
2364 // left of the graph.
2365 if(static_cast<int>(m_context.m_dragDirections) &
2366 static_cast<int>(DragDirections::BOTTOM_TO_TOP))
2367 {
2368 // We need to print outside the rectangle, that is pixels_away_from_line
2369 // pixels to the top, so with pixel y value decremented of that
2370 // pixels_above_line value (one would have expected to increment that
2371 // value, along the y axis, but the coordinates in pixel go in reverse
2372 // order).
2373
2374 pixels_away_from_line *= -1;
2375
2376 if(!m_context.msp_integrationScope->getTopMostPoint(
2377 reference_point_for_y_axis_label_position))
2378 qFatal("Failed to get top most point.");
2379 }
2380 else
2381 {
2382 if(!m_context.msp_integrationScope->getBottomMostPoint(
2383 reference_point_for_y_axis_label_position))
2384 qFatal("Failed to get bottom most point.");
2385 }
2386
2387 // double y_axis_pixel_coordinate =
2388 // yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2389 double y_axis_pixel_coordinate =
2390 yAxis->coordToPixel(reference_point_for_y_axis_label_position.y());
2391
2392 // Now that we have the coordinate in pixel units, we can correct
2393 // it by the value of the margin we want to give.
2394 double y_axis_modified_pixel_coordinate =
2395 y_axis_pixel_coordinate + pixels_away_from_line;
2396
2397 // Set aside a point instance to store the pixel coordinates of the text.
2398 QPointF pixel_coordinates;
2399
2400 pixel_coordinates.setX(x_axis_center_position);
2401 pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2402
2403 // Now convert back to graph coordinates.
2404 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2405 yAxis->pixelToCoord(pixel_coordinates.y()));
2406
2407 // qDebug() << "Should print the label at point:" << graph_coordinates;
2408
2409 if(mp_xDeltaTextItem != nullptr)
2410 {
2411 mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2412 graph_coordinates.y());
2413
2414 // Dynamically set the number of decimals to ensure we can read
2415 // a meaning full delta value even if it is very very very small.
2416 // That is, allow one to read 0.00333, 0.000333, 1.333 and so on.
2417
2418 // The computation below only works properly when the passed
2419 // value is fabs() (not negative !!!).
2420
2421 int decimals = Utils::zeroDecimalsInValue(width) + 3;
2422
2423 QString label_text = QString("full x span %1 -- x drag delta %2")
2424 .arg(width, 0, 'f', decimals)
2425 .arg(fabs(m_context.m_xDelta), 0, 'f', decimals);
2426
2427 mp_xDeltaTextItem->setText(label_text);
2428
2429 mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2430 mp_xDeltaTextItem->setVisible(true);
2431 }
2432
2433 // Set the boolean to true so that derived widgets know that something is
2434 // being measured, and they can act accordingly, for example by computing
2435 // deconvolutions in a mass spectrum.
2436 m_context.m_isMeasuringDistance = true;
2437
2438 replot();
2439
2440 // Let the caller know that we were measuring something.
2442
2443 return;
2444}
2445
2446void
2448{
2449 // See drawXScopeSpanFeatures() for explanations.
2450
2451 // Check right away if there is height!
2452 double height;
2453 if(!m_context.msp_integrationScope->getHeight(height))
2454 qFatal("Could not get height.");
2455
2456 // If there is no height, we have nothing to do here.
2457 if(!height)
2458 return;
2459 // qDebug() << "height:" << height;
2460
2461 // FIXME: is this still true?
2462 //
2463 // We do not want to show the position markers because the only horiontal
2464 // line to be visible must be contained between the start and end vertical
2465 // tracer items.
2466 mp_hPosTracerItem->setVisible(false);
2467 mp_vPosTracerItem->setVisible(false);
2468
2469 // First the easy part: the vertical position: centered on the
2470 // scope Y span.
2471 QPointF bottom_most_point;
2472 if(!m_context.msp_integrationScope->getBottomMostPoint(bottom_most_point))
2473 qFatal("Could not get the bottom-most bottom point.");
2474
2475 double y_axis_center_position = bottom_most_point.y() + height / 2;
2476
2477 // We want to draw the text outside the rectangle (if normal rectangle)
2478 // at a small distance from the vertical limit of the scope at the
2479 // position of the current drag point. We need to check the horizontal
2480 // drag direction to put the text at the right place (left of
2481 // current drag point if dragging right to left, for example).
2482
2483 // What is the distance between the rectangle line at current drag point and
2484 // the text itself.
2485 int pixels_away_from_line = 15;
2486 double x_axis_coordinate;
2487 double x_axis_pixel_coordinate;
2488
2489 if(static_cast<int>(m_context.m_dragDirections) &
2490 static_cast<int>(DragDirections::RIGHT_TO_LEFT))
2491 {
2492 QPointF left_most_point;
2493
2494 if(!m_context.msp_integrationScope->getLeftMostPoint(left_most_point))
2495 qFatal("Failed to get left most point.");
2496
2497 x_axis_coordinate = left_most_point.x();
2498
2499 pixels_away_from_line *= -1;
2500 }
2501 else
2502 {
2503 QPointF right_most_point;
2504
2505 if(!m_context.msp_integrationScope->getRightMostPoint(right_most_point))
2506 qFatal("Failed to get right most point.");
2507
2508 x_axis_coordinate = right_most_point.x();
2509 }
2510 x_axis_pixel_coordinate = xAxis->coordToPixel(x_axis_coordinate);
2511
2512 double x_axis_modified_pixel_coordinate =
2513 x_axis_pixel_coordinate + pixels_away_from_line;
2514
2515 // Set aside a point instance to store the pixel coordinates of the text.
2516 QPointF pixel_coordinates;
2517
2518 pixel_coordinates.setX(x_axis_modified_pixel_coordinate);
2519 pixel_coordinates.setY(y_axis_center_position);
2520
2521 // Now convert back to graph coordinates.
2522
2523 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2524 yAxis->pixelToCoord(pixel_coordinates.y()));
2525
2526 mp_yDeltaTextItem->position->setCoords(graph_coordinates.x(),
2527 y_axis_center_position);
2528
2529 int decimals = Utils::zeroDecimalsInValue(height) + 3;
2530
2531 QString label_text = QString("full y span %1 -- y drag delta %2")
2532 .arg(height, 0, 'f', decimals)
2533 .arg(fabs(m_context.m_yDelta), 0, 'f', decimals);
2534
2535 mp_yDeltaTextItem->setText(label_text);
2536 mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2537 mp_yDeltaTextItem->setVisible(true);
2538 mp_yDeltaTextItem->setRotation(90);
2539
2540 // Set the boolean to true so that derived widgets know that something is
2541 // being measured, and they can act accordingly, for example by computing
2542 // deconvolutions in a mass spectrum.
2543 m_context.m_isMeasuringDistance = true;
2544
2545 replot();
2546
2547 // Let the caller know that we were measuring something.
2549}
2550
2551void
2553{
2554
2555 // We compute signed differentials. If the user does not want the sign,
2556 // fabs(double) is their friend.
2557
2558 // Compute the xAxis differential:
2559
2560 m_context.m_xDelta =
2561 m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();
2562
2563 // Same with the Y-axis range:
2564
2565 m_context.m_yDelta =
2566 m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();
2567
2568 return;
2569}
2570
2571bool
2573{
2574 // First get the height of the plot.
2575 double plotHeight = yAxis->range().upper - yAxis->range().lower;
2576
2577 double heightDiff =
2578 fabs(m_context.m_startDragPoint.y() - m_context.m_currentDragPoint.y());
2579
2580 double heightDiffRatio = (heightDiff / plotHeight) * 100;
2581
2582 if(heightDiffRatio > 10)
2583 {
2584 return true;
2585 }
2586
2587 return false;
2588}
2589
2590void
2592{
2593
2594 // if(for_integration)
2595 // qDebug() << "for_integration:" << for_integration;
2596
2597 // By essence, the one-dimension IntegrationScope is characterized
2598 // by the left-most point and the width. Using these two data bits
2599 // it is possible to compute the x value of the right-most point.
2600
2601 double x_range_start =
2602 std::min(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
2603 double x_range_end =
2604 std::max(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
2605
2606 // qDebug() << "x_range_start:" << x_range_start << "-" << "x_range_end:" <<
2607 // x_range_end;
2608
2609 double y_position = m_context.m_startDragPoint.y();
2610
2611 m_context.updateIntegrationScope();
2612
2613 // Top line
2614 mp_selectionRectangeLine1->start->setCoords(
2615 QPointF(x_range_start, y_position));
2616 mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2617
2618 // Only if we are drawing a selection rectangle for integration, do we set
2619 // arrow heads to the line.
2620 if(for_integration)
2621 {
2622 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2623 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2624 }
2625 else
2626 {
2627 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2628 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2629 }
2630 mp_selectionRectangeLine1->setVisible(true);
2631
2632 // Right line: does not exist, start and end are the same end point of the
2633 // top line.
2634 mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2635 mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2636 mp_selectionRectangeLine2->setVisible(false);
2637
2638 // Bottom line: identical to the top line, but invisible
2639 mp_selectionRectangeLine3->start->setCoords(
2640 QPointF(x_range_start, y_position));
2641 mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2642 mp_selectionRectangeLine3->setVisible(false);
2643
2644 // Left line: does not exist: start and end are the same end point of the
2645 // top line.
2646 mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2647 mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2648 mp_selectionRectangeLine4->setVisible(false);
2649}
2650
2651void
2653{
2654 // qDebug();
2655
2656 // if(for_integration)
2657 // qDebug() << "for_integration:" << for_integration;
2658
2659 // We are handling a conventional rectangle. Just create four points
2660 // from top left to bottom right. But we want the top left point to be
2661 // effectively the top left point and the bottom point to be the bottom
2662 // point. So we need to try all four direction combinations, left to right
2663 // or converse versus top to bottom or converse.
2664
2665 m_context.updateIntegrationScopeRect();
2666
2667 // Now that the integration scope has been updated as a rectangle,
2668 // use these newly set data to actually draw the integration
2669 // scope lines.
2670
2671 QPointF bottom_left_point;
2672 if(!m_context.msp_integrationScope->getPoint(bottom_left_point))
2673 qFatal("Failed to get point.");
2674 // qDebug() << "Starting point is left bottom point:" << bottom_left_point;
2675
2676 double width;
2677 if(!m_context.msp_integrationScope->getWidth(width))
2678 qFatal("Failed to get width.");
2679 // qDebug() << "Width:" << width;
2680
2681 double height;
2682 if(!m_context.msp_integrationScope->getHeight(height))
2683 qFatal("Failed to get height.");
2684 // qDebug() << "Height:" << height;
2685
2686 QPointF bottom_right_point(bottom_left_point.x() + width,
2687 bottom_left_point.y());
2688 // qDebug() << "bottom_right_point:" << bottom_right_point;
2689
2690 QPointF top_right_point(bottom_left_point.x() + width,
2691 bottom_left_point.y() + height);
2692 // qDebug() << "top_right_point:" << top_right_point;
2693
2694 QPointF top_left_point(bottom_left_point.x(), bottom_left_point.y() + height);
2695
2696 // qDebug() << "top_left_point:" << top_left_point;
2697
2698 // Start by drawing the bottom line because the IntegrationScopeRect has the
2699 // left bottom point and the width and the height to fully characterize it.
2700
2701 // Bottom line (left to right)
2702 mp_selectionRectangeLine3->start->setCoords(bottom_left_point);
2703 mp_selectionRectangeLine3->end->setCoords(bottom_right_point);
2704 mp_selectionRectangeLine3->setVisible(true);
2705
2706 // Right line (bottom to top)
2707 mp_selectionRectangeLine2->start->setCoords(bottom_right_point);
2708 mp_selectionRectangeLine2->end->setCoords(top_right_point);
2709 mp_selectionRectangeLine2->setVisible(true);
2710
2711 // Top line (right to left)
2712 mp_selectionRectangeLine1->start->setCoords(top_right_point);
2713 mp_selectionRectangeLine1->end->setCoords(top_left_point);
2714 mp_selectionRectangeLine1->setVisible(true);
2715
2716 // Left line (top to bottom)
2717 mp_selectionRectangeLine4->start->setCoords(top_left_point);
2718 mp_selectionRectangeLine4->end->setCoords(bottom_left_point);
2719 mp_selectionRectangeLine4->setVisible(true);
2720
2721 // Only if we are drawing a selection rectangle for integration, do we
2722 // set arrow heads to the line.
2723 if(for_integration)
2724 {
2725 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2726 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2727 }
2728 else
2729 {
2730 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2731 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2732 }
2733}
2734
2735void
2737{
2738 // We are handling a rhomboid scope, that is, a rectangle that
2739 // is tilted either to the left or to the right.
2740
2741 // There are two kinds of rhomboid integration scopes: horizontal and
2742 // vertical.
2743
2744 /*
2745 * +----------+
2746 * | |
2747 * | |
2748 * | |
2749 * | |
2750 * | |
2751 * | |
2752 * | |
2753 * +----------+
2754 * ----width---
2755 */
2756
2757 // As visible here, the fixed size of the rhomboid (using the S key in the
2758 // plot widget) is the *horizontal* side (this is the plot context's
2759 // m_integrationScopeRhombWidth).
2760
2761 IntegrationScopeFeatures scope_features;
2762
2763 // Top horizontal line
2764 QPointF point_1;
2765 scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);
2766
2767 // When the user rotates the horizontal rhomboid, at some point, if the
2768 // current drag point has the same y axis value as the start drag point, then
2769 // we say that the rhomboid is flattened on the x axis. In this case, we do
2770 // not draw anything as this is a purely unusable situation.
2771
2772 if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
2773 {
2774 // qDebug() << "The horizontal rhomboid is flattened on the x axis.";
2775
2776 mp_selectionRectangeLine1->setVisible(false);
2777 mp_selectionRectangeLine2->setVisible(false);
2778 mp_selectionRectangeLine3->setVisible(false);
2779 mp_selectionRectangeLine4->setVisible(false);
2780
2781 return;
2782 }
2783
2785 qFatal("The rhomboid should be horizontal!");
2786
2787 // At this point we can draw the rhomboid fine.
2788
2789 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
2790 qFatal("Failed to getLeftMostTopPoint.");
2791 QPointF point_2;
2792 if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
2793 qFatal("Failed to getRightMostTopPoint.");
2794
2795 // qDebug() << "For top line, two points:" << point_1 << "--" << point_2;
2796
2797 mp_selectionRectangeLine1->start->setCoords(point_1);
2798 mp_selectionRectangeLine1->end->setCoords(point_2);
2799
2800 // Only if we are drawing a selection rectangle for integration, do we set
2801 // arrow heads to the line.
2802 if(for_integration)
2803 {
2804 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2805 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2806 }
2807 else
2808 {
2809 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2810 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2811 }
2812
2813 mp_selectionRectangeLine1->setVisible(true);
2814
2815 // Right line
2816 if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
2817 qFatal("Failed to getRightMostBottomPoint.");
2818 mp_selectionRectangeLine2->start->setCoords(point_2);
2819 mp_selectionRectangeLine2->end->setCoords(point_1);
2820 mp_selectionRectangeLine2->setVisible(true);
2821
2822 // qDebug() << "For right line, two points:" << point_2 << "--" << point_1;
2823
2824 // Bottom horizontal line
2825 if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
2826 qFatal("Failed to getLeftMostBottomPoint.");
2827 mp_selectionRectangeLine3->start->setCoords(point_1);
2828 mp_selectionRectangeLine3->end->setCoords(point_2);
2829 mp_selectionRectangeLine3->setVisible(true);
2830
2831 // qDebug() << "For bottom line, two points:" << point_1 << "--" << point_2;
2832
2833 // Left line
2834 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
2835 qFatal("Failed to getLeftMostTopPoint.");
2836 mp_selectionRectangeLine4->end->setCoords(point_2);
2837 mp_selectionRectangeLine4->start->setCoords(point_1);
2838 mp_selectionRectangeLine4->setVisible(true);
2839
2840 // qDebug() << "For left line, two points:" << point_2 << "--" << point_1;
2841}
2842
2843void
2845{
2846 // We are handling a rhomboid scope, that is, a rectangle that
2847 // is tilted either to the left or to the right.
2848
2849 // There are two kinds of rhomboid integration scopes: horizontal and
2850 // vertical.
2851
2852 /*
2853 * +3
2854 * . |
2855 * . |
2856 * . |
2857 * . +2
2858 * . .
2859 * . .
2860 * . .
2861 * 4+ .
2862 * | | .
2863 * height | | .
2864 * | | .
2865 * 1+
2866 *
2867 */
2868
2869 // As visible here, the fixed size of the rhomboid (using the S key in the
2870 // plot widget) is the *vertical* side (this is the plot context's
2871 // m_integrationScopeRhombHeight).
2872
2873 IntegrationScopeFeatures scope_features;
2874
2875 // Left vertical line
2876 QPointF point_1;
2877 scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);
2878
2879 // When the user rotates the vertical rhomboid, at some point, if the current
2880 // drag point is on the same x axis value as the start drag point, then we say
2881 // that the rhomboid is flattened on the y axis. In this case, we do not draw
2882 // anything as this is a purely unusable situation.
2883
2884 if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
2885 {
2886 // qDebug() << "The vertical rhomboid is flattened on the y axis.";
2887
2888 mp_selectionRectangeLine1->setVisible(false);
2889 mp_selectionRectangeLine2->setVisible(false);
2890 mp_selectionRectangeLine3->setVisible(false);
2891 mp_selectionRectangeLine4->setVisible(false);
2892
2893 return;
2894 }
2895
2897 qFatal("The rhomboid should be vertical!");
2898
2899 // At this point we can draw the rhomboid fine.
2900
2901 QPointF point_2;
2902 if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
2903 qFatal("Failed to getLeftMostBottomPoint.");
2904
2905 // qDebug() << "For left vertical line, two points:" << point_1 << "--"
2906 // << point_2;
2907
2908 mp_selectionRectangeLine1->start->setCoords(point_1);
2909 mp_selectionRectangeLine1->end->setCoords(point_2);
2910
2911 // Only if we are drawing a selection rectangle for integration, do we set
2912 // arrow heads to the line.
2913 if(for_integration)
2914 {
2915 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2916 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2917 }
2918 else
2919 {
2920 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2921 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2922 }
2923
2924 mp_selectionRectangeLine1->setVisible(true);
2925
2926 // Lower oblique line
2927 if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
2928 qFatal("Failed to getRightMostBottomPoint.");
2929 mp_selectionRectangeLine2->start->setCoords(point_2);
2930 mp_selectionRectangeLine2->end->setCoords(point_1);
2931 mp_selectionRectangeLine2->setVisible(true);
2932
2933 // qDebug() << "For lower oblique line, two points:" << point_2 << "--"
2934 // << point_1;
2935
2936 // Right vertical line
2937 if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
2938 qFatal("Failed to getRightMostTopPoint.");
2939 mp_selectionRectangeLine3->start->setCoords(point_1);
2940 mp_selectionRectangeLine3->end->setCoords(point_2);
2941 mp_selectionRectangeLine3->setVisible(true);
2942
2943 // qDebug() << "For right vertical line, two points:" << point_1 << "--"
2944 // << point_2;
2945
2946 // Upper oblique line
2947 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
2948 qFatal("Failed to get the LeftMostTopPoint.");
2949 mp_selectionRectangeLine4->end->setCoords(point_2);
2950 mp_selectionRectangeLine4->start->setCoords(point_1);
2951 mp_selectionRectangeLine4->setVisible(true);
2952
2953 // qDebug() << "For upper oblique line, two points:" << point_2 << "--"
2954 // << point_1;
2955}
2956
2957void
2959{
2960 // qDebug();
2961
2962 // if(for_integration)
2963 // qDebug() << "for_integration:" << for_integration;
2964
2965 // We are handling a skewed rectangle (rhomboid), that is a rectangle that
2966 // is tilted either to the left or to the right.
2967
2968 // There are two kinds of rhomboid integration scopes:
2969
2970 /*
2971 4+----------+3
2972 | |
2973 | |
2974 | |
2975 | |
2976 | |
2977 | |
2978 | |
2979 1+----------+2
2980 ----width---
2981 */
2982
2983 // As visible here, the fixed size of the rhomboid (using the S key in the
2984 // plot widget) is the *horizontal* side (this is the plot context's
2985 // m_integrationScopeRhombWidth).
2986
2987 // and
2988
2989
2990 /*
2991 * +3
2992 * . |
2993 * . |
2994 * . |
2995 * . +2
2996 * . .
2997 * . .
2998 * . .
2999 * 4+ .
3000 * | | .
3001 * height | | .
3002 * | | .
3003 * 1+
3004 *
3005 */
3006
3007 // As visible here, the fixed size of the rhomboid (using the S key in the
3008 // plot widget) is the *vertical* side (this is the plot context's
3009 // m_integrationScopeRhombHeight).
3010
3011 // qDebug() << "Before calling updateIntegrationScopeRhomb(), "
3012 // "m_integrationScopeRhombWidth:"
3013 // << m_context.m_integrationScopeRhombWidth
3014 // << "and m_integrationScopeRhombHeight:"
3015 // << m_context.m_integrationScopeRhombHeight;
3016
3017 m_context.updateIntegrationScopeRhomb();
3018
3019 // qDebug() << "After, m_integrationScopeRhombWidth:"
3020 // << m_context.m_integrationScopeRhombWidth
3021 // << "and m_integrationScopeRhombHeight:"
3022 // << m_context.m_integrationScopeRhombHeight;
3023
3024 // Now that the integration scope has been updated as a rhomboid,
3025 // use these newly set data to actually draw the integration
3026 // scope lines.
3027
3028 // We thus need to first establish if we have a horiontal or a vertical
3029 // rhomboid scope. This information is located in
3030 // m_context.m_integrationScopeRhombWidth and
3031 // m_context.m_integrationScopeRhombHeight. If width > 0, height *has to be
3032 // 0*, which indicates a horizontal rhomb.Conversely, if height is > 0, then
3033 // the rhomb is vertical.
3034
3035 if(m_context.m_integrationScopeRhombWidth > 0)
3036 // We are dealing with a horizontal scope.
3038 else if(m_context.m_integrationScopeRhombHeight > 0)
3039 // We are dealing with a vertical scope.
3040 updateIntegrationScopeVerticalRhomb(for_integration);
3041 else
3042 qFatal("Cannot be both the width or height of rhomboid scope be 0.");
3043}
3044
3045void
3047 bool for_integration)
3048{
3049 // qDebug() << "as_line_segment:" << as_line_segment;
3050 // qDebug() << "for_integration:" << for_integration;
3051
3052 // We now need to construct the selection rectangle, either for zoom or for
3053 // integration.
3054
3055 // There are two situations :
3056 //
3057 // 1. if the rectangle should look like a line segment
3058 //
3059 // 2. if the rectangle should actually look like a rectangle. In this case,
3060 // there are two sub-situations:
3061 //
3062 // a. if the Alt modifier key is down, then the rectangle is rhomboid.
3063 //
3064 // b. otherwise the rectangle is conventional.
3065
3066 if(as_line_segment)
3067 {
3068 // qDebug() << "Updating the integration scope to an IntegrationScope.";
3069 updateIntegrationScope(for_integration);
3070 }
3071 else
3072 {
3073 if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
3074 {
3075 // qDebug()
3076 // << "Updating the integration scope to an IntegrationScopeRect.";
3077 updateIntegrationScopeRect(for_integration);
3078 }
3079 else if(m_context.m_keyboardModifiers & Qt::AltModifier)
3080 {
3081 // The user might use the Alt modifier, but if no rhomboid side has
3082 // been defined using the S key, then we do not do any rhomboid
3083 // selection because we do not know the side size of that rhomboid.
3084
3085 if(!m_context.m_integrationScopeRhombHeight &&
3086 !m_context.m_integrationScopeRhombWidth)
3087 updateIntegrationScopeRect(for_integration);
3088 else
3089 // qDebug()
3090 // << "Updating the integration scope to an
3091 // IntegrationScopeRhomb.";
3092 updateIntegrationScopeRhomb(for_integration);
3093 }
3094 }
3095
3096 // Depending on the kind of IntegrationScope, (normal, rect or rhomb)
3097 // we have to measure things in different ways. We now set in the context
3098 // a number of parameters that will be used by its user.
3099
3100 QPointF point;
3101 double height;
3102 std::vector<QPointF> points;
3103
3104 // Integration scope values are sorted:
3105 // Line scope: point is left and width is right.x - left.x
3106 // Rect scope: point is bottom left.
3107 // Rhomb scope: points 1->4 are bottom left->bottom right->top right->top left
3108 // width is 2.x - 1.x.
3109
3110 if(m_context.msp_integrationScope->getPoints(points))
3111 {
3112 // We have defined a IntegrationScopeRhomb.
3113
3114 if(!m_context.msp_integrationScope->getLeftMostPoint(point))
3115 qFatal("Failed to get LeftMost point.");
3116 m_context.m_xRegionRangeStart = point.x();
3117
3118 if(!m_context.msp_integrationScope->getRightMostPoint(point))
3119 qFatal("Failed to get RightMost point.");
3120 m_context.m_xRegionRangeEnd = point.x();
3121 }
3122 else if(m_context.msp_integrationScope->getHeight(height))
3123 {
3124 // We have defined a IntegrationScopeRect.
3125
3126 if(!m_context.msp_integrationScope->getPoint(point))
3127 qFatal("Failed to get point.");
3128 m_context.m_xRegionRangeStart = point.x();
3129
3130 double width;
3131
3132 if(!m_context.msp_integrationScope->getWidth(width))
3133 qFatal("Failed to get width.");
3134
3135 m_context.m_xRegionRangeEnd = m_context.m_xRegionRangeStart + width;
3136
3137 m_context.m_yRegionRangeStart = point.y();
3138
3139 m_context.m_yRegionRangeEnd = point.y() + height;
3140 }
3141 else
3142 {
3143 // We have defined a IntegrationScope.
3144
3145 if(!m_context.msp_integrationScope->getPoint(point))
3146 qFatal("Failed to get point.");
3147 m_context.m_xRegionRangeStart = point.x();
3148
3149 double width;
3150
3151 if(!m_context.msp_integrationScope->getWidth(width))
3152 qFatal("Failed to get width.");
3153 m_context.m_xRegionRangeEnd = m_context.m_xRegionRangeStart + width;
3154 }
3155
3156 // At this point, draw the text describing the widths.
3157
3158 // We want the x-delta on the bottom of the rectangle, inside it
3159 // and the y-delta on the vertical side of the rectangle, inside it.
3160
3161 // Draw the selection width text
3163}
3164
3165void
3167{
3168 mp_selectionRectangeLine1->setVisible(false);
3169 mp_selectionRectangeLine2->setVisible(false);
3170 mp_selectionRectangeLine3->setVisible(false);
3171 mp_selectionRectangeLine4->setVisible(false);
3172
3173 if(reset_values)
3174 {
3176 }
3177}
3178
3179void
3181{
3182 std::const_pointer_cast<IntegrationScopeBase>(m_context.msp_integrationScope)
3183 ->reset();
3184}
3185
3188{
3189 // There are four lines that make the selection polygon. We want to know
3190 // which lines are visible.
3191
3192 int current_selection_polygon =
3193 static_cast<int>(SelectionDrawingLines::NOT_SET);
3194
3195 if(mp_selectionRectangeLine1->visible())
3196 {
3197 current_selection_polygon |=
3198 static_cast<int>(SelectionDrawingLines::TOP_LINE);
3199 // qDebug() << "current_selection_polygon:" <<
3200 // current_selection_polygon;
3201 }
3202 if(mp_selectionRectangeLine2->visible())
3203 {
3204 current_selection_polygon |=
3205 static_cast<int>(SelectionDrawingLines::RIGHT_LINE);
3206 // qDebug() << "current_selection_polygon:" <<
3207 // current_selection_polygon;
3208 }
3209 if(mp_selectionRectangeLine3->visible())
3210 {
3211 current_selection_polygon |=
3212 static_cast<int>(SelectionDrawingLines::BOTTOM_LINE);
3213 // qDebug() << "current_selection_polygon:" <<
3214 // current_selection_polygon;
3215 }
3216 if(mp_selectionRectangeLine4->visible())
3217 {
3218 current_selection_polygon |=
3219 static_cast<int>(SelectionDrawingLines::LEFT_LINE);
3220 // qDebug() << "current_selection_polygon:" <<
3221 // current_selection_polygon;
3222 }
3223
3224 // qDebug() << "returning visibility:" << current_selection_polygon;
3225
3226 return static_cast<SelectionDrawingLines>(current_selection_polygon);
3227}
3228
3229bool
3231{
3232 // Sanity check
3233 int check = 0;
3234
3235 check += mp_selectionRectangeLine1->visible();
3236 check += mp_selectionRectangeLine2->visible();
3237 check += mp_selectionRectangeLine3->visible();
3238 check += mp_selectionRectangeLine4->visible();
3239
3240 if(check > 0)
3241 return true;
3242
3243 return false;
3244}
3245
3246void
3248{
3249 // qDebug() << "Setting focus to the QCustomPlot:" << this;
3250
3251 QCustomPlot::setFocus();
3252
3253 // qDebug() << "Emitting setFocusSignal().";
3254
3255 emit setFocusSignal();
3256}
3257
3258//! Redraw the background of the \p focusedPlotWidget plot widget.
3259void
3260BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3261{
3262 if(focusedPlotWidget == nullptr)
3264 "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3265 "-- "
3266 "ERROR focusedPlotWidget cannot be nullptr.");
3267
3268 if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3269 {
3270 // The focused widget is not *this widget. We should make sure that
3271 // we were not the one that had the focus, because in this case we
3272 // need to redraw an unfocused background.
3273
3274 axisRect()->setBackground(m_unfocusedBrush);
3275 }
3276 else
3277 {
3278 axisRect()->setBackground(m_focusedBrush);
3279 }
3280
3281 replot();
3282}
3283
3284void
3286{
3287 m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3288 m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3289
3290 // qDebug() << "The new updated context: " << m_context.toString();
3291}
3292
3293const BasePlotContext &
3295{
3296 return m_context;
3297}
3298
3299
3300} // 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)
bool m_shouldTracersBeVisible
Tells if the tracers should be visible.
virtual void hideSelectionRectangle(bool reset_values=false)
virtual void mouseMoveHandlerDraggingCursor()
virtual void updateIntegrationScopeDrawing(bool as_line_segment=false, bool for_integration=false)
virtual void directionKeyReleaseEvent(QKeyEvent *event)
QCPItemText * mp_yDeltaTextItem
QCPItemLine * mp_selectionRectangeLine1
Rectangle defining the borders of zoomed-in/out data.
virtual QCPRange getOutermostRangeX(bool &found_range) const
void lastCursorHoveredPointSignal(const QPointF &pointf)
void plottableDestructionRequestedSignal(BasePlotWidget *base_plot_widget_p, QCPAbstractPlottable *plottable_p, const BasePlotContext &context)
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) const
virtual void keyPressEvent(QKeyEvent *event)
KEYBOARD-related EVENTS.
virtual ~BasePlotWidget()
Destruct this BasePlotWidget instance.
virtual void updateIntegrationScope(bool for_integration=false)
QCPItemLine * mp_selectionRectangeLine2
QCPItemText * mp_xDeltaTextItem
Text describing the x-axis delta value during a drag operation.
void mousePressEventSignal(const BasePlotContext &context)
virtual void setAxisLabelX(const QString &label)
virtual void mouseMoveHandlerLeftButtonDraggingCursor()
int m_mouseMoveHandlerSkipCount
Counter to handle the "fat data" mouse move event handling.
virtual QCPRange getOutermostRangeY(bool &found_range) const
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 mouseReleaseHandlerRightButton()
virtual QCPRange getInnermostRangeX(bool &found_range) const
virtual void mouseMoveHandlerNotDraggingCursor()
virtual void redrawPlotBackground(QWidget *focusedPlotWidget)
Redraw the background of the focusedPlotWidget plot widget.
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
virtual void mouseReleaseHandlerLeftButton()
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(const 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)
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
void mouseReleaseEventSignal(const BasePlotContext &context)
virtual void setFocus()
PLOT ITEMS : TRACER TEXT ITEMS...
virtual void drawYScopeSpanFeatures()
void keyReleaseEventSignal(const BasePlotContext &context)
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)
void plotRangesChangedSignal(const BasePlotContext &context)
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.
virtual void moveMouseCursorGraphCoordToGlobal(QPointF plot_coordinates)
virtual QString allLayerNamesToString() const
QCPItemLine * mp_selectionRectangeLine3
virtual void axisDoubleClickHandler(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
virtual void mouseMoveHandlerRightButtonDraggingCursor()
QCPItemLine * mp_vEndTracerItem
Vertical selection end tracer (typically in red).
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 xAxisMeasurementSignal(const BasePlotContext &context, bool with_delta)
virtual void replotWithAxisRangeY(double lower, double upper)
virtual void hideTracers()
Hide the traces (vertical and horizontal).
virtual void updateIntegrationScopeVerticalRhomb(bool for_integration=false)
virtual void mousePseudoButtonKeyReleaseEvent(QKeyEvent *event)
virtual void hideAllPlotItems()
PLOTTING / REPLOTTING functions.
virtual QCPRange getRangeX(bool &found_range, int index) const
MOUSE MOVEMENTS mouse/keyboard-triggered.
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:102
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition aa.cpp:39
SelectionDrawingLines