libpappsomspp
Library for mass spectrometry
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
1106
1107void
1109{
1110 //qDebug() << "The left button is dragging.";
1111
1112 // Set the context.m_isMeasuringDistance to false, which later might be set to
1113 // true if effectively we are measuring a distance. This is required because
1114 // the derived widget classes might want to know if they have to perform
1115 // some action on the basis that context is measuring a distance, for
1116 // example the mass spectrum-specific widget might want to compute
1117 // deconvolutions.
1118
1120
1121 // Let's first check if the mouse drag operation originated on either
1122 // axis. In that case, the user is performing axis reframing or rescaling.
1123
1125 {
1126 //qDebug() << "Click was on one of the axes.";
1127
1128 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1129 {
1130 // The user is asking a rescale of the plot.
1131
1132 // We know that we do not want the tracers when we perform axis
1133 // rescaling operations.
1134
1135 if(mp_hPosTracerItem != nullptr)
1136 mp_hPosTracerItem->setVisible(false);
1137 if(mp_vPosTracerItem != nullptr)
1138 mp_vPosTracerItem->setVisible(false);
1139
1140 if(mp_vStartTracerItem != nullptr)
1141 mp_vStartTracerItem->setVisible(false);
1142 if(mp_vEndTracerItem != nullptr)
1143 mp_vEndTracerItem->setVisible(false);
1144
1145 // This operation is particularly intensive, thus we want to
1146 // reduce the number of calculations by skipping this calculation
1147 // a number of times. The user can ask for this feature by
1148 // clicking the 'Q' letter.
1149
1150 if(m_context.m_pressedKeyCode == Qt::Key_Q)
1151 {
1153 {
1155 return;
1156 }
1157 else
1158 {
1160 }
1161 }
1162
1163 //qDebug() << "Asking that the axes be rescaled.";
1164
1165 axisRescale();
1166 }
1167 else
1168 {
1169 // The user was simply dragging the axis. Just pan, that is slide
1170 // the plot in the same direction as the mouse movement and with the
1171 // same amplitude.
1172
1173 //qDebug() << "Asking that the axes be panned.";
1174
1175 axisPan();
1176 }
1177
1178 return;
1179 }
1180
1181 // At this point we understand that the user was not performing any
1182 // panning/rescaling operation by clicking on any one of the axes.. Go on
1183 // with other possibilities.
1184
1185 // Let's check if the user is actually drawing a rectangle (covering a
1186 // real area) or is drawing a line.
1187
1188 // qDebug() << "The mouse dragging did not originate on an axis.";
1189
1191 {
1192 //qDebug() << "Apparently the selection is a real rectangle.";
1193
1194 // When we draw a rectangle the tracers are of no use.
1195
1196 if(mp_hPosTracerItem != nullptr)
1197 mp_hPosTracerItem->setVisible(false);
1198 if(mp_vPosTracerItem != nullptr)
1199 mp_vPosTracerItem->setVisible(false);
1200
1201 if(mp_vStartTracerItem != nullptr)
1202 mp_vStartTracerItem->setVisible(false);
1203 if(mp_vEndTracerItem != nullptr)
1204 mp_vEndTracerItem->setVisible(false);
1205
1206 // Draw the rectangle, false, not as line segment and
1207 // false, not for integration
1209
1210 // Draw the selection width/height text
1213
1214 // qDebug() << "The selection polygon:"
1215 //<< m_context.m_selectionPolygon.toString();
1216 }
1217 else
1218 {
1219 //qDebug() << "Apparently we are measuring a delta.";
1220
1221 // Draw the rectangle, true, as line segment and
1222 // false, not for integration
1224
1225 // qDebug() << "The selection polygon:"
1226 //<< m_context.m_selectionPolygon.toString();
1227
1228 // The pure position tracers should be hidden.
1229 if(mp_hPosTracerItem != nullptr)
1230 mp_hPosTracerItem->setVisible(true);
1231 if(mp_vPosTracerItem != nullptr)
1232 mp_vPosTracerItem->setVisible(true);
1233
1234 // Then, make sure the region range vertical tracers are visible.
1235 if(mp_vStartTracerItem != nullptr)
1236 mp_vStartTracerItem->setVisible(true);
1237 if(mp_vEndTracerItem != nullptr)
1238 mp_vEndTracerItem->setVisible(true);
1239
1240 // Draw the selection width text
1242 }
1243}
1244
1245
1246void
1248{
1249 //qDebug() << "The right button is dragging.";
1250
1251 // Set the context.m_isMeasuringDistance to false, which later might be set to
1252 // true if effectively we are measuring a distance. This is required because
1253 // the derived widgets might want to know if they have to perform some
1254 // action on the basis that context is measuring a distance, for example the
1255 // mass spectrum-specific widget might want to compute deconvolutions.
1256
1258
1260 {
1261 // qDebug() << "Apparently the selection is a real rectangle.";
1262
1263 // When we draw a rectangle the tracers are of no use.
1264
1265 if(mp_hPosTracerItem != nullptr)
1266 mp_hPosTracerItem->setVisible(false);
1267 if(mp_vPosTracerItem != nullptr)
1268 mp_vPosTracerItem->setVisible(false);
1269
1270 if(mp_vStartTracerItem != nullptr)
1271 mp_vStartTracerItem->setVisible(false);
1272 if(mp_vEndTracerItem != nullptr)
1273 mp_vEndTracerItem->setVisible(false);
1274
1275 // Draw the rectangle, false for as_line_segment and true, for
1276 // integration.
1278
1279 // Draw the selection width/height text
1282 }
1283 else
1284 {
1285 // qDebug() << "Apparently the selection is a not a rectangle.";
1286
1287 // Draw the rectangle, true, as line segment and
1288 // false, true for integration
1290
1291 // Draw the selection width text
1293 }
1294
1295 // Draw the selection width text
1297}
1298
1299
1300void
1302{
1303 // When the user clicks this widget it has to take focus.
1304 setFocus();
1305
1306 // Fix from Qt5 to Qt6
1307 // QPointF mousePoint = event->localPos();
1308
1309#if QT_VERSION < 0x060000
1310 QPointF mousePoint = event->localPos();
1311#else
1312 QPointF mousePoint = event->position();
1313#endif
1314
1315 m_context.m_lastPressedMouseButton = event->button();
1316 m_context.m_mouseButtonsAtMousePress = event->buttons();
1317
1318 // The pressedMouseButtons must continually inform on the status of
1319 // pressed buttons so add the pressed button.
1320 m_context.m_pressedMouseButtons |= event->button();
1321
1322 //qDebug().noquote() << m_context.toString();
1323
1324 // In all the processing of the events, we need to know if the user is
1325 // clicking somewhere with the intent to change the plot ranges (reframing
1326 // or rescaling the plot).
1327 //
1328 // Reframing the plot means that the new x and y axes ranges are modified
1329 // so that they match the region that the user has encompassed by left
1330 // clicking the mouse and dragging it over the plot. That is we reframe
1331 // the plot so that it contains only the "selected" region.
1332 //
1333 // Rescaling the plot means the the new x|y axis range is modified such
1334 // that the lower axis range is constant and the upper axis range is moved
1335 // either left or right by the same amont as the x|y delta encompassed by
1336 // the user moving the mouse. The axis is thus either compressed (mouse
1337 // movement is leftwards) or un-compressed (mouse movement is rightwards).
1338
1339 // There are two ways to perform axis range modifications:
1340 //
1341 // 1. By clicking on any of the axes
1342 // 2. By clicking on the plot region but using keyboard key modifiers,
1343 // like Alt and Ctrl.
1344 //
1345 // We need to know both cases separately which is why we need to perform a
1346 // number of tests below.
1347
1348 // Let's check if the click is on the axes, either X or Y, because that
1349 // will allow us to take proper actions.
1350
1351 if(isClickOntoXAxis(mousePoint))
1352 {
1353 // The X axis was clicked upon, we need to document that:
1354 // qDebug() << __FILE__ << __LINE__
1355 //<< "Layout element is axisRect and actually on an X axis part.";
1356
1358
1359 // int currentInteractions = interactions();
1360 // currentInteractions |= QCP::iRangeDrag;
1361 // setInteractions((QCP::Interaction)currentInteractions);
1362 // axisRect()->setRangeDrag(xAxis->orientation());
1363 }
1364 else
1366
1367 if(isClickOntoYAxis(mousePoint))
1368 {
1369 // The Y axis was clicked upon, we need to document that:
1370 // qDebug() << __FILE__ << __LINE__
1371 //<< "Layout element is axisRect and actually on an Y axis part.";
1372
1374
1375 // int currentInteractions = interactions();
1376 // currentInteractions |= QCP::iRangeDrag;
1377 // setInteractions((QCP::Interaction)currentInteractions);
1378 // axisRect()->setRangeDrag(yAxis->orientation());
1379 }
1380 else
1382
1383 // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1384
1386 {
1387 // qDebug() << __FILE__ << __LINE__
1388 // << "Click outside of axes.";
1389
1390 // int currentInteractions = interactions();
1391 // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1392 // setInteractions((QCP::Interaction)currentInteractions);
1393 }
1394
1395 m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1396 m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1397
1398 // Now install the vertical start tracer at the last cursor hovered
1399 // position.
1400 if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
1401 mp_vStartTracerItem->setVisible(true);
1402
1403 if(mp_vStartTracerItem != nullptr)
1404 {
1405 mp_vStartTracerItem->start->setCoords(
1406 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1407 mp_vStartTracerItem->end->setCoords(
1408 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1409 }
1410
1411 replot();
1412}
1413
1414
1415void
1417{
1418 // Now the real code of this function.
1419
1420 m_context.m_lastReleasedMouseButton = event->button();
1421
1422 // The event->buttons() is the description of the buttons that are pressed at
1423 // the moment the handler is invoked, that is now. If left and right were
1424 // pressed, and left was released, event->buttons() would be right.
1425 m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1426
1427 // The pressedMouseButtons must continually inform on the status of pressed
1428 // buttons so remove the released button.
1429 m_context.m_pressedMouseButtons ^= event->button();
1430
1431 // qDebug().noquote() << m_context.toString();
1432
1433 // We'll need to know if modifiers were pressed a the moment the user
1434 // released the mouse button.
1435 m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1436
1438 {
1439 // Let the user know that the mouse was *not* being dragged.
1441
1442 event->accept();
1443
1444 return;
1445 }
1446
1447 // Let the user know that the mouse was being dragged.
1449
1450 // We cannot hide all items in one go because we rely on their visibility
1451 // to know what kind of dragging operation we need to perform (line-only
1452 // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1453 // only thing we know is that we can make the text invisible.
1454
1455 // Same for the x delta text item
1456 mp_xDeltaTextItem->setVisible(false);
1457 mp_yDeltaTextItem->setVisible(false);
1458
1459 // We do not show the end vertical region range marker.
1460 mp_vEndTracerItem->setVisible(false);
1461
1462 // Horizontal position tracer.
1463 mp_hPosTracerItem->setVisible(true);
1464 mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1466 mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1468
1469 // Vertical position tracer.
1470 mp_vPosTracerItem->setVisible(true);
1471
1472 mp_vPosTracerItem->setVisible(true);
1474 yAxis->range().upper);
1476 yAxis->range().lower);
1477
1478 // Force replot now because later that call might not be performed.
1479 replot();
1480
1481 // If we were using the "quantum" display for the rescale of the axes
1482 // using the Ctrl-modified left button click drag in the axes, then reset
1483 // the count to 0.
1485
1486 // Now that we have computed the useful ranges, we need to check what to do
1487 // depending on the button that was pressed.
1488
1489 if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1490 {
1492 }
1493 else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1494 {
1496 }
1497
1498 // By definition we are stopping the drag operation by releasing the mouse
1499 // button. Whatever that mouse button was pressed before and if there was
1500 // one pressed before. We cannot set that boolean value to false before
1501 // this place, because we call a number of routines above that need to know
1502 // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1503 // example.
1504
1506
1507 event->accept();
1508
1509 return;
1510}
1511
1512
1513void
1515{
1516
1518 {
1519
1520 // When the mouse move handler pans the plot, we cannot store each axes
1521 // range history element that would mean store a huge amount of such
1522 // elements, as many element as there are mouse move event handled by
1523 // the Qt event queue. But we can store an axis range history element
1524 // for the last situation of the mouse move: when the button is
1525 // released:
1526
1528
1530
1531 replot();
1532
1533 // Nothing else to do.
1534 return;
1535 }
1536
1537 // There are two possibilities:
1538 //
1539 // 1. The full selection polygon (four lines) were currently drawn, which
1540 // means the user was willing to perform a zoom operation
1541 //
1542 // 2. Only the first top line was drawn, which means the user was dragging
1543 // the cursor horizontally. That might have two ends, as shown below.
1544
1545 // So, first check what is drawn of the selection polygon.
1546
1547 PolygonType current_selection_polygon_type =
1549
1550 // Now that we know what was currently drawn of the selection polygon, we can
1551 // remove it. true to reset the values to 0.
1553
1554 // Force replot now because later that call might not be performed.
1555 replot();
1556
1557 if(current_selection_polygon_type == PolygonType::FULL_POLYGON)
1558 {
1559 // qDebug() << "Yes, the full polygon was visible";
1560
1561 // If we were dragging with the left button pressed and could draw a
1562 // rectangle, then we were preparing a zoom operation. Let's bring that
1563 // operation to its accomplishment.
1564
1565 axisZoom();
1566
1567 // qDebug() << "The selection polygon:"
1568 //<< m_context.m_selectionPolygon.toString();
1569
1570 return;
1571 }
1572 else if(current_selection_polygon_type == PolygonType::TOP_LINE)
1573 {
1574 // qDebug() << "No, only the top line of the full polygon was visible";
1575
1576 // The user was dragging the left mouse cursor and that may mean they were
1577 // measuring a distance or willing to perform a special zoom operation if
1578 // the Ctrl key was down.
1579
1580 // If the user started by clicking in the plot region, dragged the mouse
1581 // cursor with the left button and pressed the Ctrl modifier, then that
1582 // means that they wanted to do a rescale over the x-axis in the form of a
1583 // reframing.
1584
1585 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1586 {
1587 return axisReframe();
1588
1589 // qDebug() << "The selection polygon:"
1590 //<< m_context.m_selectionPolygon.toString();
1591 }
1592 }
1593 // else
1594 // qDebug() << "Another possibility.";
1595}
1596
1597
1598void
1600{
1601 //qDebug();
1602 // The right button is used for the integrations. Not for axis range
1603 // operations. So all we have to do is remove the various graphics items and
1604 // send a signal with the context that contains all the data required by the
1605 // user to perform the integrations over the right plot regions.
1606
1607 // Whatever we were doing we need to make the selection line invisible:
1608
1609 if(mp_xDeltaTextItem->visible())
1610 mp_xDeltaTextItem->setVisible(false);
1611 if(mp_yDeltaTextItem->visible())
1612 mp_yDeltaTextItem->setVisible(false);
1613
1614 // Also make the vertical end tracer invisible.
1615 mp_vEndTracerItem->setVisible(false);
1616
1617 // Once the integration is asked for, then the selection rectangle if of no
1618 // more use.
1620
1621 // Force replot now because later that call might not be performed.
1622 replot();
1623
1624 // Note that we only request an integration if the x-axis delta is enough.
1625
1626 double x_delta_pixel =
1627 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1628 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1629
1630 if(x_delta_pixel > 3)
1632 // else
1633 //qDebug() << "Not asking for integration.";
1634}
1635
1636
1637void
1638BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
1639{
1640 // We should record the new range values each time the wheel is used to
1641 // zoom/unzoom.
1642
1643 m_context.m_xRange = QCPRange(xAxis->range());
1644 m_context.m_yRange = QCPRange(yAxis->range());
1645
1646 // qDebug() << "New x range: " << m_context.m_xRange;
1647 // qDebug() << "New y range: " << m_context.m_yRange;
1648
1650
1653
1654 event->accept();
1655}
1656
1657
1658void
1660 QCPAxis *axis,
1661 [[maybe_unused]] QCPAxis::SelectablePart part,
1662 QMouseEvent *event)
1663{
1664 // qDebug();
1665
1666 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1667
1668 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1669 {
1670 // qDebug();
1671
1672 // If the Ctrl modifiers is active, then both axes are to be reset. Also
1673 // the histories are reset also.
1674
1675 rescaleAxes();
1677 }
1678 else
1679 {
1680 // qDebug();
1681
1682 // Only the axis passed as parameter is to be rescaled.
1683 // Reset the range of that axis to the max view possible.
1684
1685 axis->rescale();
1686
1688
1689 event->accept();
1690 }
1691
1692 // The double-click event does not cancel the mouse press event. That is, if
1693 // left-double-clicking, at the end of the operation the button still
1694 // "pressed". We need to remove manually the button from the pressed buttons
1695 // context member.
1696
1697 m_context.m_pressedMouseButtons ^= event->button();
1698
1700
1702
1703 replot();
1704}
1705
1706
1707bool
1708BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1709{
1710 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1711
1712 if(layoutElement &&
1713 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1714 {
1715 // The graph is *inside* the axisRect that is the outermost envelope of
1716 // the graph. Thus, if we want to know if the click was indeed on an
1717 // axis, we need to check what selectable part of the the axisRect we
1718 // were
1719 // clicking:
1720 QCPAxis::SelectablePart selectablePart;
1721
1722 selectablePart = xAxis->getPartAt(mousePoint);
1723
1724 if(selectablePart == QCPAxis::spAxisLabel ||
1725 selectablePart == QCPAxis::spAxis ||
1726 selectablePart == QCPAxis::spTickLabels)
1727 return true;
1728 }
1729
1730 return false;
1731}
1732
1733
1734bool
1735BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1736{
1737 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1738
1739 if(layoutElement &&
1740 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1741 {
1742 // The graph is *inside* the axisRect that is the outermost envelope of
1743 // the graph. Thus, if we want to know if the click was indeed on an
1744 // axis, we need to check what selectable part of the the axisRect we
1745 // were
1746 // clicking:
1747 QCPAxis::SelectablePart selectablePart;
1748
1749 selectablePart = yAxis->getPartAt(mousePoint);
1750
1751 if(selectablePart == QCPAxis::spAxisLabel ||
1752 selectablePart == QCPAxis::spAxis ||
1753 selectablePart == QCPAxis::spTickLabels)
1754 return true;
1755 }
1756
1757 return false;
1758}
1759
1760/// MOUSE-related EVENTS
1761
1762
1763/// MOUSE MOVEMENTS mouse/keyboard-triggered
1764
1765int
1767{
1768 // The user is dragging the mouse, probably to rescale the axes, but we need
1769 // to sort out in which direction the drag is happening.
1770
1771 // This function should be called after calculateDragDeltas, so that
1772 // m_context has the proper x/y delta values that we'll compare.
1773
1774 // Note that we cannot compare simply x or y deltas because the y axis might
1775 // have a different scale that the x axis. So we first need to convert the
1776 // positions to pixels.
1777
1778 double x_delta_pixel =
1779 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1780 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1781
1782 double y_delta_pixel =
1783 fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1784 yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1785
1786 if(x_delta_pixel > y_delta_pixel)
1787 return Qt::Horizontal;
1788
1789 return Qt::Vertical;
1790}
1791
1792
1793void
1795{
1796 // First convert the graph coordinates to pixel coordinates.
1797
1798 QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1799 yAxis->coordToPixel(graph_coordinates.y()));
1800
1801 moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1802}
1803
1804
1805void
1807{
1808 // qDebug() << "Calling set pos with new cursor position.";
1809 QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1810}
1811
1812
1813void
1815{
1816 QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1817
1818 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1819 yAxis->coordToPixel(graph_coord.y()));
1820
1821 // Now we need ton convert the new coordinates to the global position system
1822 // and to move the cursor to that new position. That will create an event to
1823 // move the mouse cursor.
1824
1825 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1826}
1827
1828
1829QPointF
1831{
1832 QPointF pixel_coordinates(
1833 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1834 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1835
1836 // Now convert back to local coordinates.
1837
1838 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1839 yAxis->pixelToCoord(pixel_coordinates.y()));
1840
1841 return graph_coordinates;
1842}
1843
1844
1845void
1847{
1848
1849 QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1850
1851 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1852 yAxis->coordToPixel(graph_coord.y()));
1853
1854 // Now we need ton convert the new coordinates to the global position system
1855 // and to move the cursor to that new position. That will create an event to
1856 // move the mouse cursor.
1857
1858 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1859}
1860
1861
1862QPointF
1864{
1865 QPointF pixel_coordinates(
1866 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1867 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1868
1869 // Now convert back to local coordinates.
1870
1871 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1872 yAxis->pixelToCoord(pixel_coordinates.y()));
1873
1874 return graph_coordinates;
1875}
1876
1877/// MOUSE MOVEMENTS mouse/keyboard-triggered
1878
1879
1880/// RANGE-related functions
1881
1882QCPRange
1883BasePlotWidget::getRangeX(bool &found_range, int index) const
1884{
1885 QCPGraph *graph_p = graph(index);
1886
1887 if(graph_p == nullptr)
1888 qFatal("Programming error.");
1889
1890 return graph_p->getKeyRange(found_range);
1891}
1892
1893
1894QCPRange
1895BasePlotWidget::getRangeY(bool &found_range, int index) const
1896{
1897 QCPGraph *graph_p = graph(index);
1898
1899 if(graph_p == nullptr)
1900 qFatal("Programming error.");
1901
1902 return graph_p->getValueRange(found_range);
1903}
1904
1905
1906QCPRange
1908 RangeType range_type,
1909 bool &found_range) const
1910{
1911
1912 // Iterate in all the graphs in this widget and return a QCPRange that has
1913 // its lower member as the greatest lower value of all
1914 // its upper member as the smallest upper value of all
1915
1916 if(!graphCount())
1917 {
1918 found_range = false;
1919
1920 return QCPRange(0, 1);
1921 }
1922
1923 if(graphCount() == 1)
1924 return graph()->getKeyRange(found_range);
1925
1926 bool found_at_least_one_range = false;
1927
1928 // Create an invalid range.
1929 QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
1930
1931 for(int iter = 0; iter < graphCount(); ++iter)
1932 {
1933 QCPRange temp_range;
1934
1935 bool found_range_for_iter = false;
1936
1937 QCPGraph *graph_p = graph(iter);
1938
1939 // Depending on the axis param, select the key or value range.
1940
1941 if(axis == Axis::x)
1942 temp_range = graph_p->getKeyRange(found_range_for_iter);
1943 else if(axis == Axis::y)
1944 temp_range = graph_p->getValueRange(found_range_for_iter);
1945 else
1946 qFatal("Cannot reach this point. Programming error.");
1947
1948 // Was a range found for the iterated graph ? If not skip this
1949 // iteration.
1950
1951 if(!found_range_for_iter)
1952 continue;
1953
1954 // While the innermost_range is invalid, we need to seed it with a good
1955 // one. So check this.
1956
1957 if(!QCPRange::validRange(result_range))
1958 qFatal("The obtained range is invalid !");
1959
1960 // At this point we know the obtained range is OK.
1961 result_range = temp_range;
1962
1963 // We found at least one valid range!
1964 found_at_least_one_range = true;
1965
1966 // At this point we have two valid ranges to compare. Depending on
1967 // range_type, we need to perform distinct comparisons.
1968
1969 if(range_type == RangeType::innermost)
1970 {
1971 if(temp_range.lower > result_range.lower)
1972 result_range.lower = temp_range.lower;
1973 if(temp_range.upper < result_range.upper)
1974 result_range.upper = temp_range.upper;
1975 }
1976 else if(range_type == RangeType::outermost)
1977 {
1978 if(temp_range.lower < result_range.lower)
1979 result_range.lower = temp_range.lower;
1980 if(temp_range.upper > result_range.upper)
1981 result_range.upper = temp_range.upper;
1982 }
1983 else
1984 qFatal("Cannot reach this point. Programming error.");
1985
1986 // Continue to next graph, if any.
1987 }
1988 // End of
1989 // for(int iter = 0; iter < graphCount(); ++iter)
1990
1991 // Let the caller know if we found at least one range.
1992 found_range = found_at_least_one_range;
1993
1994 return result_range;
1995}
1996
1997
1998QCPRange
2000{
2001
2002 return getRange(Axis::x, RangeType::innermost, found_range);
2003}
2004
2005
2006QCPRange
2008{
2009 return getRange(Axis::x, RangeType::outermost, found_range);
2010}
2011
2012
2013QCPRange
2015{
2016
2017 return getRange(Axis::y, RangeType::innermost, found_range);
2018}
2019
2020
2021QCPRange
2023{
2024 return getRange(Axis::y, RangeType::outermost, found_range);
2025}
2026
2027
2028/// RANGE-related functions
2029
2030
2031/// PLOTTING / REPLOTTING functions
2032
2033void
2035{
2036 // Get the current x lower/upper range, that is, leftmost/rightmost x
2037 // coordinate.
2038 double xLower = xAxis->range().lower;
2039 double xUpper = xAxis->range().upper;
2040
2041 // Get the current y lower/upper range, that is, bottommost/topmost y
2042 // coordinate.
2043 double yLower = yAxis->range().lower;
2044 double yUpper = yAxis->range().upper;
2045
2046 // This function is called only when the user has clicked on the x/y axis or
2047 // when the user has dragged the left mouse button with the Ctrl key
2048 // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
2049 // move handler. So we need to test which axis was clicked-on.
2050
2052 {
2053
2054 // We are changing the range of the X axis.
2055
2056 // What is the x delta ?
2057 double xDelta =
2059
2060 // If xDelta is < 0, the we were dragging from right to left, we are
2061 // compressing the view on the x axis, by adding new data to the right
2062 // hand size of the graph. So we add xDelta to the upper bound of the
2063 // range. Otherwise we are uncompressing the view on the x axis and
2064 // remove the xDelta from the upper bound of the range. This is why we
2065 // have the
2066 // '-'
2067 // and not '+' below;
2068
2069 // qDebug() << "Setting xaxis:" << xLower << "--" << xUpper - xDelta;
2070
2071 xAxis->setRange(xLower, xUpper - xDelta);
2072 }
2073 // End of
2074 // if(m_context.m_wasClickOnXAxis)
2075 else // that is, if(m_context.m_wasClickOnYAxis)
2076 {
2077 // We are changing the range of the Y axis.
2078
2079 // What is the y delta ?
2080 double yDelta =
2082
2083 // See above for an explanation of the computation.
2084
2085 yAxis->setRange(yLower, yUpper - yDelta);
2086
2087 // Old version
2088 // if(yDelta < 0)
2089 //{
2090 //// The dragging operation was from top to bottom, we are enlarging
2091 //// the range (thus, we are unzooming the view, since the widget
2092 //// always has the same size).
2093
2094 // yAxis->setRange(yLower, yUpper + fabs(yDelta));
2095 //}
2096 // else
2097 //{
2098 //// The dragging operation was from bottom to top, we are reducing
2099 //// the range (thus, we are zooming the view, since the widget
2100 //// always has the same size).
2101
2102 // yAxis->setRange(yLower, yUpper - fabs(yDelta));
2103 //}
2104 }
2105 // End of
2106 // else // that is, if(m_context.m_wasClickOnYAxis)
2107
2108 // Update the context with the current axes ranges
2109
2111
2113
2114 replot();
2115}
2116
2117
2118void
2120{
2121
2122 // double sorted_start_drag_point_x =
2123 // std::min(m_context.m_startDragPoint.x(), m_context.m_currentDragPoint.x());
2124
2125 // xAxis->setRange(sorted_start_drag_point_x,
2126 // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2127
2128 xAxis->setRange(
2130
2131 // Note that the y axis should be rescaled from current lower value to new
2132 // upper value matching the y-axis position of the cursor when the mouse
2133 // button was released.
2134
2135 yAxis->setRange(xAxis->range().lower,
2136 std::max<double>(m_context.m_yRegionRangeStart,
2138
2139 // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2140 // xAxis->range().upper
2141 //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2142
2144
2147
2148 replot();
2149}
2150
2151
2152void
2154{
2155
2156 // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2157 // values before using them, because now we want to really have the lower x
2158 // value. Simply craft a QCPRange that will swap the values if lower is not
2159 // < than upper QCustomPlot calls this normalization).
2160
2161 xAxis->setRange(
2163
2164 yAxis->setRange(
2166
2168
2171
2172 replot();
2173}
2174
2175
2176void
2178{
2179 // Sanity check
2181 qFatal(
2182 "This function can only be called if the mouse click was on one of the "
2183 "axes");
2184
2186 {
2187 xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2189 }
2190
2192 {
2193 yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2195 }
2196
2198
2199 // qDebug() << "The updated context:" << m_context.toString();
2200
2201 // We cannot store the new ranges in the history, because the pan operation
2202 // involved a huge quantity of micro-movements elicited upon each mouse move
2203 // cursor event so we would have a huge history.
2204 // updateAxesRangeHistory();
2205
2206 // Now that the context has the right range values, we can emit the
2207 // signal that will be used by this plot widget users, typically to
2208 // abide by the x/y range lock required by the user.
2209
2211
2212 replot();
2213}
2214
2215
2216void
2218 QCPRange yAxisRange,
2219 Axis axis)
2220{
2221 // qDebug() << "With axis:" << (int)axis;
2222
2223 if(static_cast<int>(axis) & static_cast<int>(Axis::x))
2224 {
2225 xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2226 }
2227
2228 if(static_cast<int>(axis) & static_cast<int>(Axis::y))
2229 {
2230 yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2231 }
2232
2233 // We do not want to update the history, because there would be way too
2234 // much history items, since this function is called upon mouse moving
2235 // handling and not only during mouse release events.
2236 // updateAxesRangeHistory();
2237
2238 replot();
2239}
2240
2241
2242void
2243BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2244{
2245 // qDebug();
2246
2247 xAxis->setRange(lower, upper);
2248
2249 replot();
2250}
2251
2252
2253void
2254BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2255{
2256 // qDebug();
2257
2258 yAxis->setRange(lower, upper);
2259
2260 replot();
2261}
2262
2263/// PLOTTING / REPLOTTING functions
2264
2265
2266/// PLOT ITEMS : TRACER TEXT ITEMS...
2267
2268//! Hide the selection line, the xDelta text and the zoom rectangle items.
2269void
2271{
2272 mp_xDeltaTextItem->setVisible(false);
2273 mp_yDeltaTextItem->setVisible(false);
2274
2275 // mp_zoomRectItem->setVisible(false);
2277
2278 // Force a replot to make sure the action is immediately visible by the
2279 // user, even without moving the mouse.
2280 replot();
2281}
2282
2283
2284//! Show the traces (vertical and horizontal).
2285void
2287{
2289
2290 mp_vPosTracerItem->setVisible(true);
2291 mp_hPosTracerItem->setVisible(true);
2292
2293 mp_vStartTracerItem->setVisible(true);
2294 mp_vEndTracerItem->setVisible(true);
2295
2296 // Force a replot to make sure the action is immediately visible by the
2297 // user, even without moving the mouse.
2298 replot();
2299}
2300
2301
2302//! Hide the traces (vertical and horizontal).
2303void
2305{
2307 mp_hPosTracerItem->setVisible(false);
2308 mp_vPosTracerItem->setVisible(false);
2309
2310 mp_vStartTracerItem->setVisible(false);
2311 mp_vEndTracerItem->setVisible(false);
2312
2313 // Force a replot to make sure the action is immediately visible by the
2314 // user, even without moving the mouse.
2315 replot();
2316}
2317
2318
2319void
2321 bool for_integration)
2322{
2323 // The user has dragged the mouse left button on the graph, which means he
2324 // is willing to draw a selection rectangle, either for zooming-in or for
2325 // integration.
2326
2327 if(mp_xDeltaTextItem != nullptr)
2328 mp_xDeltaTextItem->setVisible(false);
2329 if(mp_yDeltaTextItem != nullptr)
2330 mp_yDeltaTextItem->setVisible(false);
2331
2332 // Ensure the right selection rectangle is drawn.
2333
2334 updateSelectionRectangle(as_line_segment, for_integration);
2335
2336 // Note that if we draw a zoom rectangle, then we are certainly not
2337 // measuring anything. So set the boolean value to false so that the user of
2338 // this widget or derived classes know that there is nothing to perform upon
2339 // (like deconvolution, for example).
2340
2342
2343 // Also remove the delta value from the pipeline by sending a simple
2344 // distance without measurement signal.
2345
2346 emit xAxisMeasurementSignal(m_context, false);
2347
2348 replot();
2349}
2350
2351
2352void
2354{
2355 // The user is dragging the mouse over the graph and we want them to know what
2356 // is the x delta value, that is the span between the point at the start of
2357 // the drag and the current drag position.
2358
2359 // FIXME: is this still true?
2360 //
2361 // We do not want to show the position markers because the only horiontal
2362 // line to be visible must be contained between the start and end vertiacal
2363 // tracer items.
2364 if(mp_hPosTracerItem != nullptr)
2365 mp_hPosTracerItem->setVisible(false);
2366 if(mp_vPosTracerItem != nullptr)
2367 mp_vPosTracerItem->setVisible(false);
2368
2369 // We want to draw the text in the middle position of the leftmost-rightmost
2370 // point, even with skewed rectangle selection.
2371
2372 QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2373
2374 // qDebug() << "leftmost_point:" << leftmost_point;
2375
2376 QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2377
2378 // qDebug() << "rightmost_point:" << rightmost_point;
2379
2380 double x_axis_center_position =
2381 leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2382
2383 // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2384
2385 // We want the text to print inside the rectangle, always at the current drag
2386 // point so the eye can follow the delta value while looking where to drag the
2387 // mouse. To position the text inside the rectangle, we need to know what is
2388 // the drag direction.
2389
2390 // Set aside a point instance to store the pixel coordinates of the text.
2391 QPointF pixel_coordinates;
2392
2393 // What is the distance between the rectangle line at current drag point and
2394 // the text itself.
2395 int pixels_away_from_line = 15;
2396
2397 // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2398 // order with respect to the y axis values !!! That is pixel(0,0) is top left
2399 // of the graph.
2400 if(static_cast<int>(m_context.m_dragDirections) &
2401 static_cast<int>(DragDirections::TOP_TO_BOTTOM))
2402 {
2403 // We need to print inside the rectangle, that is pixels_above_line pixels
2404 // to the bottom, so with pixel y value decremented of that
2405 // pixels_above_line value (one would have expected to increment that
2406 // value, along the y axis, but the coordinates in pixel go in reverse
2407 // order).
2408
2409 pixels_away_from_line *= -1;
2410 }
2411
2412 double y_axis_pixel_coordinate =
2413 yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2414
2415 double y_axis_modified_pixel_coordinate =
2416 y_axis_pixel_coordinate + pixels_away_from_line;
2417
2418 pixel_coordinates.setX(x_axis_center_position);
2419 pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2420
2421 // Now convert back to graph coordinates.
2422
2423 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2424 yAxis->pixelToCoord(pixel_coordinates.y()));
2425 if(mp_xDeltaTextItem != nullptr)
2426 {
2427 mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2428 graph_coordinates.y());
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
2432 int decimals = Utils::zeroDecimalsInValue(m_context.m_xDelta) + 3;
2433
2434 mp_xDeltaTextItem->setText(
2435 QString("%1").arg(m_context.m_xDelta, 0, 'f', decimals));
2436
2437 mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2438 mp_xDeltaTextItem->setVisible(true);
2439 }
2440
2441 // Set the boolean to true so that derived widgets know that something is
2442 // being measured, and they can act accordingly, for example by computing
2443 // deconvolutions in a mass spectrum.
2445
2446 replot();
2447
2448 // Let the caller know that we were measuring something.
2450
2451 return;
2452}
2453
2454
2455void
2457{
2459 return;
2460
2461 // The user is dragging the mouse over the graph and we want them to know what
2462 // is the y delta value, that is the span between the point at the top of
2463 // the selection polygon and the point at its bottom.
2464
2465 // FIXME: is this still true?
2466 //
2467 // We do not want to show the position markers because the only horiontal
2468 // line to be visible must be contained between the start and end vertiacal
2469 // tracer items.
2470 mp_hPosTracerItem->setVisible(false);
2471 mp_vPosTracerItem->setVisible(false);
2472
2473 // We want to draw the text in the middle position of the leftmost-rightmost
2474 // point, even with skewed rectangle selection.
2475
2476 QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2477 QPointF topmost_point = m_context.m_selectionPolygon.getTopMostPoint();
2478
2479 // qDebug() << "leftmost_point:" << leftmost_point;
2480
2481 QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2482 QPointF bottommost_point = m_context.m_selectionPolygon.getBottomMostPoint();
2483
2484 // qDebug() << "rightmost_point:" << rightmost_point;
2485
2486 double x_axis_center_position =
2487 leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2488
2489 double y_axis_center_position =
2490 bottommost_point.y() + (topmost_point.y() - bottommost_point.y()) / 2;
2491
2492 // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2493
2494 mp_yDeltaTextItem->position->setCoords(x_axis_center_position,
2495 y_axis_center_position);
2496 mp_yDeltaTextItem->setText(QString("%1").arg(m_context.m_yDelta, 0, 'f', 2));
2497 mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2498 mp_yDeltaTextItem->setVisible(true);
2499 mp_yDeltaTextItem->setRotation(90);
2500
2501 // Set the boolean to true so that derived widgets know that something is
2502 // being measured, and they can act accordingly, for example by computing
2503 // deconvolutions in a mass spectrum.
2505
2506 replot();
2507
2508 // Let the caller know that we were measuring something.
2510}
2511
2512
2513void
2515{
2516
2517 // We compute signed differentials. If the user does not want the sign,
2518 // fabs(double) is their friend.
2519
2520 // Compute the xAxis differential:
2521
2524
2525 // Same with the Y-axis range:
2526
2529
2530 // qDebug() << "xDelta:" << m_context.m_xDelta
2531 //<< "and yDelta:" << m_context.m_yDelta;
2532
2533 return;
2534}
2535
2536
2537bool
2539{
2540 // First get the height of the plot.
2541 double plotHeight = yAxis->range().upper - yAxis->range().lower;
2542
2543 double heightDiff =
2545
2546 double heightDiffRatio = (heightDiff / plotHeight) * 100;
2547
2548 if(heightDiffRatio > 10)
2549 {
2550 // qDebug() << "isVerticalDisplacementAboveThreshold: true";
2551 return true;
2552 }
2553
2554 // qDebug() << "isVerticalDisplacementAboveThreshold: false";
2555 return false;
2556}
2557
2558
2559void
2561{
2562
2563 // if(for_integration)
2564 // qDebug() << "for_integration:" << for_integration;
2565
2566 // When we make a linear selection, the selection polygon is a polygon that
2567 // has the following characteristics:
2568 //
2569 // the x range is the linear selection span
2570 //
2571 // the y range is the widest std::min -> std::max possible.
2572
2573 // This is how the selection polygon logic knows if its is mono-
2574 // two-dimensional.
2575
2576 // We want the top left point to effectively be the top left point, so check
2577 // the direction of the mouse cursor drag.
2578
2579 double x_range_start =
2581 double x_range_end =
2583
2584 double y_position = m_context.m_startDragPoint.y();
2585
2586 m_context.m_selectionPolygon.set1D(x_range_start, x_range_end);
2587
2588 // Top line
2589 mp_selectionRectangeLine1->start->setCoords(
2590 QPointF(x_range_start, y_position));
2591 mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2592
2593 // Only if we are drawing a selection rectangle for integration, do we set
2594 // arrow heads to the line.
2595 if(for_integration)
2596 {
2597 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2598 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2599 }
2600 else
2601 {
2602 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2603 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2604 }
2605 mp_selectionRectangeLine1->setVisible(true);
2606
2607 // Right line: does not exist, start and end are the same end point of the top
2608 // line.
2609 mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2610 mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2611 mp_selectionRectangeLine2->setVisible(false);
2612
2613 // Bottom line: identical to the top line, but invisible
2614 mp_selectionRectangeLine3->start->setCoords(
2615 QPointF(x_range_start, y_position));
2616 mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2617 mp_selectionRectangeLine3->setVisible(false);
2618
2619 // Left line: does not exist: start and end are the same end point of the top
2620 // line.
2621 mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2622 mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2623 mp_selectionRectangeLine4->setVisible(false);
2624}
2625
2626
2627void
2629{
2630
2631 // if(for_integration)
2632 // qDebug() << "for_integration:" << for_integration;
2633
2634 // We are handling a conventional rectangle. Just create four points
2635 // from top left to bottom right. But we want the top left point to be
2636 // effectively the top left point and the bottom point to be the bottom point.
2637 // So we need to try all four direction combinations, left to right or
2638 // converse versus top to bottom or converse.
2639
2641
2643 {
2644 // qDebug() << "Dragging from right to left";
2645
2647 {
2648 // qDebug() << "Dragging from top to bottom";
2649
2650 // TOP_LEFT_POINT
2655
2656 // TOP_RIGHT_POINT
2660
2661 // BOTTOM_RIGHT_POINT
2666
2667 // BOTTOM_LEFT_POINT
2672 }
2673 // End of
2674 // if(m_context.m_currentDragPoint.y() < m_context.m_startDragPoint.y())
2675 else
2676 {
2677 // qDebug() << "Dragging from bottom to top";
2678
2679 // TOP_LEFT_POINT
2684
2685 // TOP_RIGHT_POINT
2690
2691 // BOTTOM_RIGHT_POINT
2695
2696 // BOTTOM_LEFT_POINT
2701 }
2702 }
2703 // End of
2704 // if(m_context.m_currentDragPoint.x() < m_context.m_startDragPoint.x())
2705 else
2706 {
2707 // qDebug() << "Dragging from left to right";
2708
2710 {
2711 // qDebug() << "Dragging from top to bottom";
2712
2713 // TOP_LEFT_POINT
2717
2718 // TOP_RIGHT_POINT
2723
2724 // BOTTOM_RIGHT_POINT
2729
2730 // BOTTOM_LEFT_POINT
2735 }
2736 else
2737 {
2738 // qDebug() << "Dragging from bottom to top";
2739
2740 // TOP_LEFT_POINT
2745
2746 // TOP_RIGHT_POINT
2751
2752 // BOTTOM_RIGHT_POINT
2757
2758 // BOTTOM_LEFT_POINT
2762 }
2763 }
2764
2765 // qDebug() << "Now draw the lines with points:"
2766 //<< m_context.m_selectionPolygon.toString();
2767
2768 // Top line
2769 mp_selectionRectangeLine1->start->setCoords(
2771 mp_selectionRectangeLine1->end->setCoords(
2773
2774 // Only if we are drawing a selection rectangle for integration, do we
2775 // set arrow heads to the line.
2776 if(for_integration)
2777 {
2778 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2779 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2780 }
2781 else
2782 {
2783 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2784 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2785 }
2786
2787 mp_selectionRectangeLine1->setVisible(true);
2788
2789 // Right line
2790 mp_selectionRectangeLine2->start->setCoords(
2792 mp_selectionRectangeLine2->end->setCoords(
2794 mp_selectionRectangeLine2->setVisible(true);
2795
2796 // Bottom line
2797 mp_selectionRectangeLine3->start->setCoords(
2799 mp_selectionRectangeLine3->end->setCoords(
2801 mp_selectionRectangeLine3->setVisible(true);
2802
2803 // Left line
2804 mp_selectionRectangeLine4->start->setCoords(
2806 mp_selectionRectangeLine4->end->setCoords(
2808 mp_selectionRectangeLine4->setVisible(true);
2809}
2810
2811
2812void
2814{
2815
2816 // if(for_integration)
2817 // qDebug() << "for_integration:" << for_integration;
2818
2819 // We are handling a skewed rectangle, that is a rectangle that is
2820 // tilted either to the left or to the right.
2821
2822 // qDebug() << "m_context.m_selectRectangleWidth: "
2823 //<< m_context.m_selectRectangleWidth;
2824
2825 // Top line
2826 // start
2827
2828 // qDebug() << "m_context.m_startDragPoint: " <<
2829 // m_context.m_startDragPoint.x()
2830 //<< "-" << m_context.m_startDragPoint.y();
2831
2832 // qDebug() << "m_context.m_currentDragPoint: "
2833 //<< m_context.m_currentDragPoint.x() << "-"
2834 //<< m_context.m_currentDragPoint.y();
2835
2837
2839 {
2840 // qDebug() << "Dragging from right to left";
2841
2843 {
2844 // qDebug() << "Dragging from top to bottom";
2845
2850
2851 // m_context.m_selRectTopLeftPoint.setX(
2852 // m_context.m_startDragPoint.x() -
2853 // m_context.m_selectRectangleWidth);
2854 // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2855
2859
2860 // m_context.m_selRectTopRightPoint.setX(m_context.m_startDragPoint.x());
2861 // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2862
2867
2868 // m_context.m_selRectBottomRightPoint.setX(
2869 // m_context.m_currentDragPoint.x() +
2870 // m_context.m_selectRectangleWidth);
2871 // m_context.m_selRectBottomRightPoint.setY(
2872 // m_context.m_currentDragPoint.y());
2873
2878
2879 // m_context.m_selRectBottomLeftPoint.setX(
2880 // m_context.m_currentDragPoint.x());
2881 // m_context.m_selRectBottomLeftPoint.setY(
2882 // m_context.m_currentDragPoint.y());
2883 }
2884 else
2885 {
2886 // qDebug() << "Dragging from bottom to top";
2887
2892
2893 // m_context.m_selRectTopLeftPoint.setX(
2894 // m_context.m_currentDragPoint.x());
2895 // m_context.m_selRectTopLeftPoint.setY(
2896 // m_context.m_currentDragPoint.y());
2897
2902
2903 // m_context.m_selRectTopRightPoint.setX(
2904 // m_context.m_currentDragPoint.x() +
2905 // m_context.m_selectRectangleWidth);
2906 // m_context.m_selRectTopRightPoint.setY(
2907 // m_context.m_currentDragPoint.y());
2908
2909
2913
2914 // m_context.m_selRectBottomRightPoint.setX(
2915 // m_context.m_startDragPoint.x());
2916 // m_context.m_selRectBottomRightPoint.setY(
2917 // m_context.m_startDragPoint.y());
2918
2923
2924 // m_context.m_selRectBottomLeftPoint.setX(
2925 // m_context.m_startDragPoint.x() -
2926 // m_context.m_selectRectangleWidth);
2927 // m_context.m_selRectBottomLeftPoint.setY(
2928 // m_context.m_startDragPoint.y());
2929 }
2930 }
2931 // End of
2932 // Dragging from right to left.
2933 else
2934 {
2935 // qDebug() << "Dragging from left to right";
2936
2938 {
2939 // qDebug() << "Dragging from top to bottom";
2940
2944
2945 // m_context.m_selRectTopLeftPoint.setX(m_context.m_startDragPoint.x());
2946 // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2947
2952
2953 // m_context.m_selRectTopRightPoint.setX(
2954 // m_context.m_startDragPoint.x() +
2955 // m_context.m_selectRectangleWidth);
2956 // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2957
2962
2963 // m_context.m_selRectBottomRightPoint.setX(
2964 // m_context.m_currentDragPoint.x());
2965 // m_context.m_selRectBottomRightPoint.setY(
2966 // m_context.m_currentDragPoint.y());
2967
2972
2973 // m_context.m_selRectBottomLeftPoint.setX(
2974 // m_context.m_currentDragPoint.x() -
2975 // m_context.m_selectRectangleWidth);
2976 // m_context.m_selRectBottomLeftPoint.setY(
2977 // m_context.m_currentDragPoint.y());
2978 }
2979 else
2980 {
2981 // qDebug() << "Dragging from bottom to top";
2982
2987
2988 // m_context.m_selRectTopLeftPoint.setX(
2989 // m_context.m_currentDragPoint.x() -
2990 // m_context.m_selectRectangleWidth);
2991 // m_context.m_selRectTopLeftPoint.setY(
2992 // m_context.m_currentDragPoint.y());
2993
2998
2999 // m_context.m_selRectTopRightPoint.setX(
3000 // m_context.m_currentDragPoint.x());
3001 // m_context.m_selRectTopRightPoint.setY(
3002 // m_context.m_currentDragPoint.y());
3003
3008
3009 // m_context.m_selRectBottomRightPoint.setX(
3010 // m_context.m_startDragPoint.x() +
3011 // m_context.m_selectRectangleWidth);
3012 // m_context.m_selRectBottomRightPoint.setY(
3013 // m_context.m_startDragPoint.y());
3014
3018
3019 // m_context.m_selRectBottomLeftPoint.setX(
3020 // m_context.m_startDragPoint.x());
3021 // m_context.m_selRectBottomLeftPoint.setY(
3022 // m_context.m_startDragPoint.y());
3023 }
3024 }
3025 // End of Dragging from left to right.
3026
3027 // qDebug() << "Now draw the lines with points:"
3028 //<< m_context.m_selectionPolygon.toString();
3029
3030 // Top line
3031 mp_selectionRectangeLine1->start->setCoords(
3033 mp_selectionRectangeLine1->end->setCoords(
3035
3036 // Only if we are drawing a selection rectangle for integration, do we set
3037 // arrow heads to the line.
3038 if(for_integration)
3039 {
3040 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
3041 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
3042 }
3043 else
3044 {
3045 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
3046 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
3047 }
3048
3049 mp_selectionRectangeLine1->setVisible(true);
3050
3051 // Right line
3052 mp_selectionRectangeLine2->start->setCoords(
3054 mp_selectionRectangeLine2->end->setCoords(
3056 mp_selectionRectangeLine2->setVisible(true);
3057
3058 // Bottom line
3059 mp_selectionRectangeLine3->start->setCoords(
3061 mp_selectionRectangeLine3->end->setCoords(
3063 mp_selectionRectangeLine3->setVisible(true);
3064
3065 // Left line
3066 mp_selectionRectangeLine4->end->setCoords(
3068 mp_selectionRectangeLine4->start->setCoords(
3070 mp_selectionRectangeLine4->setVisible(true);
3071}
3072
3073
3074void
3076 bool for_integration)
3077{
3078
3079 // qDebug() << "as_line_segment:" << as_line_segment;
3080 // qDebug() << "for_integration:" << for_integration;
3081
3082 // We now need to construct the selection rectangle, either for zoom or for
3083 // integration.
3084
3085 // There are two situations :
3086 //
3087 // 1. if the rectangle should look like a line segment
3088 //
3089 // 2. if the rectangle should actually look like a rectangle. In this case,
3090 // there are two sub-situations:
3091 //
3092 // a. if the S key is down, then the rectangle is
3093 // skewed, that is its vertical sides are not parallel to the y axis.
3094 //
3095 // b. otherwise the rectangle is conventional.
3096
3097 if(as_line_segment)
3098 {
3099 update1DSelectionRectangle(for_integration);
3100 }
3101 else
3102 {
3103 if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
3104 {
3105 update2DSelectionRectangleSquare(for_integration);
3106 }
3107 else if(m_context.m_keyboardModifiers & Qt::AltModifier)
3108 {
3109 update2DSelectionRectangleSkewed(for_integration);
3110 }
3111 }
3112
3113 // This code automatically sorts the ranges (range start is always less than
3114 // range end) even if the user actually selects from high to low (right to
3115 // left or bottom to top). This has implications in code that uses the
3116 // m_context data to perform some computations. This is why it is important
3117 // that m_dragDirections be set correctly to establish where the current drag
3118 // point is actually located (at which point).
3119
3124
3129
3130 // At this point, draw the text describing the widths.
3131
3132 // We want the x-delta on the bottom of the rectangle, inside it
3133 // and the y-delta on the vertical side of the rectangle, inside it.
3134
3135 // Draw the selection width text
3137}
3138
3139void
3141{
3142 mp_selectionRectangeLine1->setVisible(false);
3143 mp_selectionRectangeLine2->setVisible(false);
3144 mp_selectionRectangeLine3->setVisible(false);
3145 mp_selectionRectangeLine4->setVisible(false);
3146
3147 if(reset_values)
3148 {
3150 }
3151}
3152
3153
3154void
3156{
3158}
3159
3160
3163{
3164 // There are four lines that make the selection polygon. We want to know
3165 // which lines are visible.
3166
3167 int current_selection_polygon = static_cast<int>(PolygonType::NOT_SET);
3168
3169 if(mp_selectionRectangeLine1->visible())
3170 {
3171 current_selection_polygon |= static_cast<int>(PolygonType::TOP_LINE);
3172 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3173 }
3174 if(mp_selectionRectangeLine2->visible())
3175 {
3176 current_selection_polygon |= static_cast<int>(PolygonType::RIGHT_LINE);
3177 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3178 }
3179 if(mp_selectionRectangeLine3->visible())
3180 {
3181 current_selection_polygon |= static_cast<int>(PolygonType::BOTTOM_LINE);
3182 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3183 }
3184 if(mp_selectionRectangeLine4->visible())
3185 {
3186 current_selection_polygon |= static_cast<int>(PolygonType::LEFT_LINE);
3187 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3188 }
3189
3190 // qDebug() << "returning visibility:" << current_selection_polygon;
3191
3192 return static_cast<PolygonType>(current_selection_polygon);
3193}
3194
3195
3196bool
3198{
3199 // Sanity check
3200 int check = 0;
3201
3202 check += mp_selectionRectangeLine1->visible();
3203 check += mp_selectionRectangeLine2->visible();
3204 check += mp_selectionRectangeLine3->visible();
3205 check += mp_selectionRectangeLine4->visible();
3206
3207 if(check > 0)
3208 return true;
3209
3210 return false;
3211}
3212
3213
3214void
3216{
3217 // qDebug() << "Setting focus to the QCustomPlot:" << this;
3218
3219 QCustomPlot::setFocus();
3220
3221 // qDebug() << "Emitting setFocusSignal().";
3222
3223 emit setFocusSignal();
3224}
3225
3226
3227//! Redraw the background of the \p focusedPlotWidget plot widget.
3228void
3229BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3230{
3231 if(focusedPlotWidget == nullptr)
3233 "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3234 "-- "
3235 "ERROR focusedPlotWidget cannot be nullptr.");
3236
3237 if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3238 {
3239 // The focused widget is not *this widget. We should make sure that
3240 // we were not the one that had the focus, because in this case we
3241 // need to redraw an unfocused background.
3242
3243 axisRect()->setBackground(m_unfocusedBrush);
3244 }
3245 else
3246 {
3247 axisRect()->setBackground(m_focusedBrush);
3248 }
3249
3250 replot();
3251}
3252
3253
3254void
3256{
3257 m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3258 m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3259
3260 // qDebug() << "The new updated context: " << m_context.toString();
3261}
3262
3263
3264const BasePlotContext &
3266{
3267 return m_context;
3268}
3269
3270
3271} // 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 bool setupWidget()
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)
QPointF getRightMostPoint() const
QPointF getLeftMostPoint() const
QPointF getBottomMostPoint() const
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
Axis
Definition: types.h:181