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