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