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