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