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 //qDebug();
941 m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
942 //qDebug();
943
944 // qDebug() << "lastCursorHoveredPoint coord:"
945 //<< m_context.lastCursorHoveredPoint;
946
947 // Now, depending on the button(s) (if any) that are pressed or not, we
948 // have a different processing.
949
950 //qDebug();
951
952 if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
953 m_context.m_pressedMouseButtons & Qt::RightButton)
955 else
957 // }
958 //qDebug();
959 event->accept();
960}
961
962
963void
965{
966
967 //qDebug();
969
970 //qDebug();
971 // We are not dragging the mouse (no button pressed), simply let this
972 // widget's consumer know the position of the cursor and update the markers.
973 // The consumer of this widget will update mouse cursor position at
974 // m_context.m_lastCursorHoveredPoint if so needed.
975
977
978 //qDebug();
979
980 // We are not dragging, so we do not show the region end tracer we only
981 // show the anchoring start trace that might be of use if the user starts
982 // using the arrow keys to move the cursor.
983 if(mp_vEndTracerItem != nullptr)
984 mp_vEndTracerItem->setVisible(false);
985
986 //qDebug();
987 // Only bother with the tracers if the user wants them to be visible.
988 // Their crossing point must be exactly at the last cursor-hovered point.
989
991 {
992 // We are not dragging, so only show the position markers (v and h);
993
994 //qDebug();
995 if(mp_hPosTracerItem != nullptr)
996 {
997 // Horizontal position tracer.
998 mp_hPosTracerItem->setVisible(true);
999 mp_hPosTracerItem->start->setCoords(
1000 xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
1001 mp_hPosTracerItem->end->setCoords(
1002 xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
1003 }
1004
1005 //qDebug();
1006 // Vertical position tracer.
1007 if(mp_vPosTracerItem != nullptr)
1008 {
1009 mp_vPosTracerItem->setVisible(true);
1010
1011 mp_vPosTracerItem->setVisible(true);
1012 mp_vPosTracerItem->start->setCoords(
1013 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1014 mp_vPosTracerItem->end->setCoords(
1015 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1016 }
1017
1018 //qDebug();
1019 replot();
1020 }
1021
1022
1023 return;
1024}
1025
1026
1027void
1029{
1030 qDebug();
1032
1033 // Now store the mouse position data into the the current drag point
1034 // member datum, that will be used in countless occasions later.
1036 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1037
1038 // When we drag (either keyboard or mouse), we hide the position markers
1039 // (black) and we show the start and end vertical markers for the region.
1040 // Then, we draw the horizontal region range marker that delimits
1041 // horizontally the dragged-over region.
1042
1043 if(mp_hPosTracerItem != nullptr)
1044 mp_hPosTracerItem->setVisible(false);
1045 if(mp_vPosTracerItem != nullptr)
1046 mp_vPosTracerItem->setVisible(false);
1047
1048 // Only bother with the tracers if the user wants them to be visible.
1050 {
1051
1052 // The vertical end tracer position must be refreshed.
1053 mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
1054 yAxis->range().upper);
1055
1057 yAxis->range().lower);
1058
1059 mp_vEndTracerItem->setVisible(true);
1060 }
1061
1062 // Whatever the button, when we are dealing with the axes, we do not
1063 // want to show any of the tracers.
1064
1066 {
1067 qDebug();
1068 if(mp_hPosTracerItem != nullptr)
1069 mp_hPosTracerItem->setVisible(false);
1070 if(mp_vPosTracerItem != nullptr)
1071 mp_vPosTracerItem->setVisible(false);
1072
1073 if(mp_vStartTracerItem != nullptr)
1074 mp_vStartTracerItem->setVisible(false);
1075 if(mp_vEndTracerItem != nullptr)
1076 mp_vEndTracerItem->setVisible(false);
1077 }
1078 else
1079 {
1080 qDebug();
1081 // Since we are not dragging the mouse cursor over the axes, make sure
1082 // we store the drag directions in the context, as this might be
1083 // useful for later operations.
1084
1086
1087 // qDebug() << m_context.toString();
1088 }
1089
1090 // Because when we drag the mouse button (whatever the button) we need to
1091 // know what is the drag delta (distance between start point and current
1092 // point of the drag operation) on both axes, ask that these x|y deltas be
1093 // computed.
1094 qDebug();
1096
1097 // Now deal with the BUTTON-SPECIFIC CODE.
1098
1099 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
1100 {
1101 qDebug();
1103 }
1104 else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
1105 {
1106 qDebug();
1108 }
1109
1110 qDebug();
1111}
1112
1113
1114void
1116{
1117 qDebug() << "the left button is dragging.";
1118
1119 // Set the context.m_isMeasuringDistance to false, which later might be set to
1120 // true if effectively we are measuring a distance. This is required because
1121 // the derived widget classes might want to know if they have to perform
1122 // some action on the basis that context is measuring a distance, for
1123 // example the mass spectrum-specific widget might want to compute
1124 // deconvolutions.
1125
1127
1128 // Let's first check if the mouse drag operation originated on either
1129 // axis. In that case, the user is performing axis reframing or rescaling.
1130
1132 {
1133 qDebug() << "Click was on one of the axes.";
1134
1135 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1136 {
1137 // The user is asking a rescale of the plot.
1138
1139 // We know that we do not want the tracers when we perform axis
1140 // rescaling operations.
1141
1142 if(mp_hPosTracerItem != nullptr)
1143 mp_hPosTracerItem->setVisible(false);
1144 if(mp_vPosTracerItem != nullptr)
1145 mp_vPosTracerItem->setVisible(false);
1146
1147 if(mp_vStartTracerItem != nullptr)
1148 mp_vStartTracerItem->setVisible(false);
1149 if(mp_vEndTracerItem != nullptr)
1150 mp_vEndTracerItem->setVisible(false);
1151
1152 // This operation is particularly intensive, thus we want to
1153 // reduce the number of calculations by skipping this calculation
1154 // a number of times. The user can ask for this feature by
1155 // clicking the 'Q' letter.
1156
1157 if(m_context.m_pressedKeyCode == Qt::Key_Q)
1158 {
1160 {
1162 return;
1163 }
1164 else
1165 {
1167 }
1168 }
1169
1170 qDebug() << "Asking that the axes be rescaled.";
1171
1172 axisRescale();
1173 }
1174 else
1175 {
1176 // The user was simply dragging the axis. Just pan, that is slide
1177 // the plot in the same direction as the mouse movement and with the
1178 // same amplitude.
1179
1180 qDebug() << "Asking that the axes be panned.";
1181
1182 axisPan();
1183 }
1184
1185 return;
1186 }
1187
1188 // At this point we understand that the user was not performing any
1189 // panning/rescaling operation by clicking on any one of the axes.. Go on
1190 // with other possibilities.
1191
1192 // Let's check if the user is actually drawing a rectangle (covering a
1193 // real area) or is drawing a line.
1194
1195 // qDebug() << "The mouse dragging did not originate on an axis.";
1196
1198 {
1199 qDebug() << "Apparently the selection is a real rectangle.";
1200
1201 // When we draw a rectangle the tracers are of no use.
1202
1203 if(mp_hPosTracerItem != nullptr)
1204 mp_hPosTracerItem->setVisible(false);
1205 if(mp_vPosTracerItem != nullptr)
1206 mp_vPosTracerItem->setVisible(false);
1207
1208 if(mp_vStartTracerItem != nullptr)
1209 mp_vStartTracerItem->setVisible(false);
1210 if(mp_vEndTracerItem != nullptr)
1211 mp_vEndTracerItem->setVisible(false);
1212
1213 // Draw the rectangle, false, not as line segment and
1214 // false, not for integration
1216
1217 // Draw the selection width/height text
1220
1221 // qDebug() << "The selection polygon:"
1222 //<< m_context.m_selectionPolygon.toString();
1223 }
1224 else
1225 {
1226 qDebug() << "Apparently we are measuring a delta.";
1227
1228 // Draw the rectangle, true, as line segment and
1229 // false, not for integration
1231
1232 // qDebug() << "The selection polygon:"
1233 //<< m_context.m_selectionPolygon.toString();
1234
1235 // The pure position tracers should be hidden.
1236 if(mp_hPosTracerItem != nullptr)
1237 mp_hPosTracerItem->setVisible(true);
1238 if(mp_vPosTracerItem != nullptr)
1239 mp_vPosTracerItem->setVisible(true);
1240
1241 // Then, make sure the region range vertical tracers are visible.
1242 if(mp_vStartTracerItem != nullptr)
1243 mp_vStartTracerItem->setVisible(true);
1244 if(mp_vEndTracerItem != nullptr)
1245 mp_vEndTracerItem->setVisible(true);
1246
1247 // Draw the selection width text
1249 }
1250 qDebug();
1251}
1252
1253
1254void
1256{
1257 qDebug() << "the right button is dragging.";
1258
1259 // Set the context.m_isMeasuringDistance to false, which later might be set to
1260 // true if effectively we are measuring a distance. This is required because
1261 // the derived widgets might want to know if they have to perform some
1262 // action on the basis that context is measuring a distance, for example the
1263 // mass spectrum-specific widget might want to compute deconvolutions.
1264
1266
1268 {
1269 // qDebug() << "Apparently the selection is a real rectangle.";
1270
1271 // When we draw a rectangle the tracers are of no use.
1272
1273 if(mp_hPosTracerItem != nullptr)
1274 mp_hPosTracerItem->setVisible(false);
1275 if(mp_vPosTracerItem != nullptr)
1276 mp_vPosTracerItem->setVisible(false);
1277
1278 if(mp_vStartTracerItem != nullptr)
1279 mp_vStartTracerItem->setVisible(false);
1280 if(mp_vEndTracerItem != nullptr)
1281 mp_vEndTracerItem->setVisible(false);
1282
1283 // Draw the rectangle, false for as_line_segment and true, for
1284 // integration.
1286
1287 // Draw the selection width/height text
1290 }
1291 else
1292 {
1293 // qDebug() << "Apparently the selection is a not a rectangle.";
1294
1295 // Draw the rectangle, true, as line segment and
1296 // false, true for integration
1298
1299 // Draw the selection width text
1301 }
1302
1303 // Draw the selection width text
1305}
1306
1307
1308void
1310{
1311 // When the user clicks this widget it has to take focus.
1312 setFocus();
1313
1314 // Fix from Qt5 to Qt6
1315 // QPointF mousePoint = event->localPos();
1316
1317#if QT_VERSION < 0x060000
1318 QPointF mousePoint = event->localPos();
1319#else
1320 QPointF mousePoint = event->position();
1321#endif
1322
1323 m_context.m_lastPressedMouseButton = event->button();
1324 m_context.m_mouseButtonsAtMousePress = event->buttons();
1325
1326 // The pressedMouseButtons must continually inform on the status of
1327 // pressed buttons so add the pressed button.
1328 m_context.m_pressedMouseButtons |= event->button();
1329
1330 qDebug().noquote() << m_context.toString();
1331
1332 // In all the processing of the events, we need to know if the user is
1333 // clicking somewhere with the intent to change the plot ranges (reframing
1334 // or rescaling the plot).
1335 //
1336 // Reframing the plot means that the new x and y axes ranges are modified
1337 // so that they match the region that the user has encompassed by left
1338 // clicking the mouse and dragging it over the plot. That is we reframe
1339 // the plot so that it contains only the "selected" region.
1340 //
1341 // Rescaling the plot means the the new x|y axis range is modified such
1342 // that the lower axis range is constant and the upper axis range is moved
1343 // either left or right by the same amont as the x|y delta encompassed by
1344 // the user moving the mouse. The axis is thus either compressed (mouse
1345 // movement is leftwards) or un-compressed (mouse movement is rightwards).
1346
1347 // There are two ways to perform axis range modifications:
1348 //
1349 // 1. By clicking on any of the axes
1350 // 2. By clicking on the plot region but using keyboard key modifiers,
1351 // like Alt and Ctrl.
1352 //
1353 // We need to know both cases separately which is why we need to perform a
1354 // number of tests below.
1355
1356 // Let's check if the click is on the axes, either X or Y, because that
1357 // will allow us to take proper actions.
1358
1359 if(isClickOntoXAxis(mousePoint))
1360 {
1361 // The X axis was clicked upon, we need to document that:
1362 // qDebug() << __FILE__ << __LINE__
1363 //<< "Layout element is axisRect and actually on an X axis part.";
1364
1366
1367 // int currentInteractions = interactions();
1368 // currentInteractions |= QCP::iRangeDrag;
1369 // setInteractions((QCP::Interaction)currentInteractions);
1370 // axisRect()->setRangeDrag(xAxis->orientation());
1371 }
1372 else
1374
1375 if(isClickOntoYAxis(mousePoint))
1376 {
1377 // The Y axis was clicked upon, we need to document that:
1378 // qDebug() << __FILE__ << __LINE__
1379 //<< "Layout element is axisRect and actually on an Y axis part.";
1380
1382
1383 // int currentInteractions = interactions();
1384 // currentInteractions |= QCP::iRangeDrag;
1385 // setInteractions((QCP::Interaction)currentInteractions);
1386 // axisRect()->setRangeDrag(yAxis->orientation());
1387 }
1388 else
1390
1391 // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1392
1394 {
1395 // qDebug() << __FILE__ << __LINE__
1396 // << "Click outside of axes.";
1397
1398 // int currentInteractions = interactions();
1399 // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1400 // setInteractions((QCP::Interaction)currentInteractions);
1401 }
1402
1403 m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1404 m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1405
1406 // Now install the vertical start tracer at the last cursor hovered
1407 // position.
1408 if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
1409 mp_vStartTracerItem->setVisible(true);
1410
1411 if(mp_vStartTracerItem != nullptr)
1412 {
1413 mp_vStartTracerItem->start->setCoords(
1414 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1415 mp_vStartTracerItem->end->setCoords(
1416 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1417 }
1418
1419 replot();
1420}
1421
1422
1423void
1425{
1426 // Now the real code of this function.
1427
1428 m_context.m_lastReleasedMouseButton = event->button();
1429
1430 // The event->buttons() is the description of the buttons that are pressed at
1431 // the moment the handler is invoked, that is now. If left and right were
1432 // pressed, and left was released, event->buttons() would be right.
1433 m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1434
1435 // The pressedMouseButtons must continually inform on the status of pressed
1436 // buttons so remove the released button.
1437 m_context.m_pressedMouseButtons ^= event->button();
1438
1439 // qDebug().noquote() << m_context.toString();
1440
1441 // We'll need to know if modifiers were pressed a the moment the user
1442 // released the mouse button.
1443 m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1444
1446 {
1447 // Let the user know that the mouse was *not* being dragged.
1449
1450 event->accept();
1451
1452 return;
1453 }
1454
1455 // Let the user know that the mouse was being dragged.
1457
1458 // We cannot hide all items in one go because we rely on their visibility
1459 // to know what kind of dragging operation we need to perform (line-only
1460 // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1461 // only thing we know is that we can make the text invisible.
1462
1463 // Same for the x delta text item
1464 mp_xDeltaTextItem->setVisible(false);
1465 mp_yDeltaTextItem->setVisible(false);
1466
1467 // We do not show the end vertical region range marker.
1468 mp_vEndTracerItem->setVisible(false);
1469
1470 // Horizontal position tracer.
1471 mp_hPosTracerItem->setVisible(true);
1472 mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1474 mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1476
1477 // Vertical position tracer.
1478 mp_vPosTracerItem->setVisible(true);
1479
1480 mp_vPosTracerItem->setVisible(true);
1482 yAxis->range().upper);
1484 yAxis->range().lower);
1485
1486 // Force replot now because later that call might not be performed.
1487 replot();
1488
1489 // If we were using the "quantum" display for the rescale of the axes
1490 // using the Ctrl-modified left button click drag in the axes, then reset
1491 // the count to 0.
1493
1494 // Now that we have computed the useful ranges, we need to check what to do
1495 // depending on the button that was pressed.
1496
1497 if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1498 {
1500 }
1501 else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1502 {
1504 }
1505
1506 // By definition we are stopping the drag operation by releasing the mouse
1507 // button. Whatever that mouse button was pressed before and if there was
1508 // one pressed before. We cannot set that boolean value to false before
1509 // this place, because we call a number of routines above that need to know
1510 // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1511 // example.
1512
1514
1515 event->accept();
1516
1517 return;
1518}
1519
1520
1521void
1523{
1524
1526 {
1527
1528 // When the mouse move handler pans the plot, we cannot store each axes
1529 // range history element that would mean store a huge amount of such
1530 // elements, as many element as there are mouse move event handled by
1531 // the Qt event queue. But we can store an axis range history element
1532 // for the last situation of the mouse move: when the button is
1533 // released:
1534
1536
1538
1539 replot();
1540
1541 // Nothing else to do.
1542 return;
1543 }
1544
1545 // There are two possibilities:
1546 //
1547 // 1. The full selection polygon (four lines) were currently drawn, which
1548 // means the user was willing to perform a zoom operation
1549 //
1550 // 2. Only the first top line was drawn, which means the user was dragging
1551 // the cursor horizontally. That might have two ends, as shown below.
1552
1553 // So, first check what is drawn of the selection polygon.
1554
1555 PolygonType current_selection_polygon_type =
1557
1558 // Now that we know what was currently drawn of the selection polygon, we can
1559 // remove it. true to reset the values to 0.
1561
1562 // Force replot now because later that call might not be performed.
1563 replot();
1564
1565 if(current_selection_polygon_type == PolygonType::FULL_POLYGON)
1566 {
1567 // qDebug() << "Yes, the full polygon was visible";
1568
1569 // If we were dragging with the left button pressed and could draw a
1570 // rectangle, then we were preparing a zoom operation. Let's bring that
1571 // operation to its accomplishment.
1572
1573 axisZoom();
1574
1575 // qDebug() << "The selection polygon:"
1576 //<< m_context.m_selectionPolygon.toString();
1577
1578 return;
1579 }
1580 else if(current_selection_polygon_type == PolygonType::TOP_LINE)
1581 {
1582 // qDebug() << "No, only the top line of the full polygon was visible";
1583
1584 // The user was dragging the left mouse cursor and that may mean they were
1585 // measuring a distance or willing to perform a special zoom operation if
1586 // the Ctrl key was down.
1587
1588 // If the user started by clicking in the plot region, dragged the mouse
1589 // cursor with the left button and pressed the Ctrl modifier, then that
1590 // means that they wanted to do a rescale over the x-axis in the form of a
1591 // reframing.
1592
1593 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1594 {
1595 return axisReframe();
1596
1597 // qDebug() << "The selection polygon:"
1598 //<< m_context.m_selectionPolygon.toString();
1599 }
1600 }
1601 // else
1602 // qDebug() << "Another possibility.";
1603}
1604
1605
1606void
1608{
1609 qDebug();
1610 // The right button is used for the integrations. Not for axis range
1611 // operations. So all we have to do is remove the various graphics items and
1612 // send a signal with the context that contains all the data required by the
1613 // user to perform the integrations over the right plot regions.
1614
1615 // Whatever we were doing we need to make the selection line invisible:
1616
1617 if(mp_xDeltaTextItem->visible())
1618 mp_xDeltaTextItem->setVisible(false);
1619 if(mp_yDeltaTextItem->visible())
1620 mp_yDeltaTextItem->setVisible(false);
1621
1622 // Also make the vertical end tracer invisible.
1623 mp_vEndTracerItem->setVisible(false);
1624
1625 // Once the integration is asked for, then the selection rectangle if of no
1626 // more use.
1628
1629 // Force replot now because later that call might not be performed.
1630 replot();
1631
1632 // Note that we only request an integration if the x-axis delta is enough.
1633
1634 double x_delta_pixel =
1635 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1636 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1637
1638 if(x_delta_pixel > 3)
1640 // else
1641 qDebug() << "Not asking for integration.";
1642}
1643
1644
1645void
1646BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
1647{
1648 // We should record the new range values each time the wheel is used to
1649 // zoom/unzoom.
1650
1651 m_context.m_xRange = QCPRange(xAxis->range());
1652 m_context.m_yRange = QCPRange(yAxis->range());
1653
1654 // qDebug() << "New x range: " << m_context.m_xRange;
1655 // qDebug() << "New y range: " << m_context.m_yRange;
1656
1658
1661
1662 event->accept();
1663}
1664
1665
1666void
1668 QCPAxis *axis,
1669 [[maybe_unused]] QCPAxis::SelectablePart part,
1670 QMouseEvent *event)
1671{
1672 // qDebug();
1673
1674 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1675
1676 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1677 {
1678 // qDebug();
1679
1680 // If the Ctrl modifiers is active, then both axes are to be reset. Also
1681 // the histories are reset also.
1682
1683 rescaleAxes();
1685 }
1686 else
1687 {
1688 // qDebug();
1689
1690 // Only the axis passed as parameter is to be rescaled.
1691 // Reset the range of that axis to the max view possible.
1692
1693 axis->rescale();
1694
1696
1697 event->accept();
1698 }
1699
1700 // The double-click event does not cancel the mouse press event. That is, if
1701 // left-double-clicking, at the end of the operation the button still
1702 // "pressed". We need to remove manually the button from the pressed buttons
1703 // context member.
1704
1705 m_context.m_pressedMouseButtons ^= event->button();
1706
1708
1710
1711 replot();
1712}
1713
1714
1715bool
1716BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1717{
1718 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1719
1720 if(layoutElement &&
1721 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1722 {
1723 // The graph is *inside* the axisRect that is the outermost envelope of
1724 // the graph. Thus, if we want to know if the click was indeed on an
1725 // axis, we need to check what selectable part of the the axisRect we
1726 // were
1727 // clicking:
1728 QCPAxis::SelectablePart selectablePart;
1729
1730 selectablePart = xAxis->getPartAt(mousePoint);
1731
1732 if(selectablePart == QCPAxis::spAxisLabel ||
1733 selectablePart == QCPAxis::spAxis ||
1734 selectablePart == QCPAxis::spTickLabels)
1735 return true;
1736 }
1737
1738 return false;
1739}
1740
1741
1742bool
1743BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1744{
1745 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1746
1747 if(layoutElement &&
1748 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1749 {
1750 // The graph is *inside* the axisRect that is the outermost envelope of
1751 // the graph. Thus, if we want to know if the click was indeed on an
1752 // axis, we need to check what selectable part of the the axisRect we
1753 // were
1754 // clicking:
1755 QCPAxis::SelectablePart selectablePart;
1756
1757 selectablePart = yAxis->getPartAt(mousePoint);
1758
1759 if(selectablePart == QCPAxis::spAxisLabel ||
1760 selectablePart == QCPAxis::spAxis ||
1761 selectablePart == QCPAxis::spTickLabels)
1762 return true;
1763 }
1764
1765 return false;
1766}
1767
1768/// MOUSE-related EVENTS
1769
1770
1771/// MOUSE MOVEMENTS mouse/keyboard-triggered
1772
1773int
1775{
1776 // The user is dragging the mouse, probably to rescale the axes, but we need
1777 // to sort out in which direction the drag is happening.
1778
1779 // This function should be called after calculateDragDeltas, so that
1780 // m_context has the proper x/y delta values that we'll compare.
1781
1782 // Note that we cannot compare simply x or y deltas because the y axis might
1783 // have a different scale that the x axis. So we first need to convert the
1784 // positions to pixels.
1785
1786 double x_delta_pixel =
1787 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1788 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1789
1790 double y_delta_pixel =
1791 fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1792 yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1793
1794 if(x_delta_pixel > y_delta_pixel)
1795 return Qt::Horizontal;
1796
1797 return Qt::Vertical;
1798}
1799
1800
1801void
1803{
1804 // First convert the graph coordinates to pixel coordinates.
1805
1806 QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1807 yAxis->coordToPixel(graph_coordinates.y()));
1808
1809 moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1810}
1811
1812
1813void
1815{
1816 // qDebug() << "Calling set pos with new cursor position.";
1817 QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1818}
1819
1820
1821void
1823{
1824 QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1825
1826 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1827 yAxis->coordToPixel(graph_coord.y()));
1828
1829 // Now we need ton convert the new coordinates to the global position system
1830 // and to move the cursor to that new position. That will create an event to
1831 // move the mouse cursor.
1832
1833 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1834}
1835
1836
1837QPointF
1839{
1840 QPointF pixel_coordinates(
1841 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1842 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1843
1844 // Now convert back to local coordinates.
1845
1846 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1847 yAxis->pixelToCoord(pixel_coordinates.y()));
1848
1849 return graph_coordinates;
1850}
1851
1852
1853void
1855{
1856
1857 QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1858
1859 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1860 yAxis->coordToPixel(graph_coord.y()));
1861
1862 // Now we need ton convert the new coordinates to the global position system
1863 // and to move the cursor to that new position. That will create an event to
1864 // move the mouse cursor.
1865
1866 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1867}
1868
1869
1870QPointF
1872{
1873 QPointF pixel_coordinates(
1874 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1875 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1876
1877 // Now convert back to local coordinates.
1878
1879 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1880 yAxis->pixelToCoord(pixel_coordinates.y()));
1881
1882 return graph_coordinates;
1883}
1884
1885/// MOUSE MOVEMENTS mouse/keyboard-triggered
1886
1887
1888/// RANGE-related functions
1889
1890QCPRange
1891BasePlotWidget::getRangeX(bool &found_range, int index) const
1892{
1893 QCPGraph *graph_p = graph(index);
1894
1895 if(graph_p == nullptr)
1896 qFatal("Programming error.");
1897
1898 return graph_p->getKeyRange(found_range);
1899}
1900
1901
1902QCPRange
1903BasePlotWidget::getRangeY(bool &found_range, int index) const
1904{
1905 QCPGraph *graph_p = graph(index);
1906
1907 if(graph_p == nullptr)
1908 qFatal("Programming error.");
1909
1910 return graph_p->getValueRange(found_range);
1911}
1912
1913
1914QCPRange
1916 RangeType range_type,
1917 bool &found_range) const
1918{
1919
1920 // Iterate in all the graphs in this widget and return a QCPRange that has
1921 // its lower member as the greatest lower value of all
1922 // its upper member as the smallest upper value of all
1923
1924 if(!graphCount())
1925 {
1926 found_range = false;
1927
1928 return QCPRange(0, 1);
1929 }
1930
1931 if(graphCount() == 1)
1932 return graph()->getKeyRange(found_range);
1933
1934 bool found_at_least_one_range = false;
1935
1936 // Create an invalid range.
1937 QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
1938
1939 for(int iter = 0; iter < graphCount(); ++iter)
1940 {
1941 QCPRange temp_range;
1942
1943 bool found_range_for_iter = false;
1944
1945 QCPGraph *graph_p = graph(iter);
1946
1947 // Depending on the axis param, select the key or value range.
1948
1949 if(axis == Axis::x)
1950 temp_range = graph_p->getKeyRange(found_range_for_iter);
1951 else if(axis == Axis::y)
1952 temp_range = graph_p->getValueRange(found_range_for_iter);
1953 else
1954 qFatal("Cannot reach this point. Programming error.");
1955
1956 // Was a range found for the iterated graph ? If not skip this
1957 // iteration.
1958
1959 if(!found_range_for_iter)
1960 continue;
1961
1962 // While the innermost_range is invalid, we need to seed it with a good
1963 // one. So check this.
1964
1965 if(!QCPRange::validRange(result_range))
1966 qFatal("The obtained range is invalid !");
1967
1968 // At this point we know the obtained range is OK.
1969 result_range = temp_range;
1970
1971 // We found at least one valid range!
1972 found_at_least_one_range = true;
1973
1974 // At this point we have two valid ranges to compare. Depending on
1975 // range_type, we need to perform distinct comparisons.
1976
1977 if(range_type == RangeType::innermost)
1978 {
1979 if(temp_range.lower > result_range.lower)
1980 result_range.lower = temp_range.lower;
1981 if(temp_range.upper < result_range.upper)
1982 result_range.upper = temp_range.upper;
1983 }
1984 else if(range_type == RangeType::outermost)
1985 {
1986 if(temp_range.lower < result_range.lower)
1987 result_range.lower = temp_range.lower;
1988 if(temp_range.upper > result_range.upper)
1989 result_range.upper = temp_range.upper;
1990 }
1991 else
1992 qFatal("Cannot reach this point. Programming error.");
1993
1994 // Continue to next graph, if any.
1995 }
1996 // End of
1997 // for(int iter = 0; iter < graphCount(); ++iter)
1998
1999 // Let the caller know if we found at least one range.
2000 found_range = found_at_least_one_range;
2001
2002 return result_range;
2003}
2004
2005
2006QCPRange
2008{
2009
2010 return getRange(Axis::x, RangeType::innermost, found_range);
2011}
2012
2013
2014QCPRange
2016{
2017 return getRange(Axis::x, RangeType::outermost, found_range);
2018}
2019
2020
2021QCPRange
2023{
2024
2025 return getRange(Axis::y, RangeType::innermost, found_range);
2026}
2027
2028
2029QCPRange
2031{
2032 return getRange(Axis::y, RangeType::outermost, found_range);
2033}
2034
2035
2036/// RANGE-related functions
2037
2038
2039/// PLOTTING / REPLOTTING functions
2040
2041void
2043{
2044 // Get the current x lower/upper range, that is, leftmost/rightmost x
2045 // coordinate.
2046 double xLower = xAxis->range().lower;
2047 double xUpper = xAxis->range().upper;
2048
2049 // Get the current y lower/upper range, that is, bottommost/topmost y
2050 // coordinate.
2051 double yLower = yAxis->range().lower;
2052 double yUpper = yAxis->range().upper;
2053
2054 // This function is called only when the user has clicked on the x/y axis or
2055 // when the user has dragged the left mouse button with the Ctrl key
2056 // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
2057 // move handler. So we need to test which axis was clicked-on.
2058
2060 {
2061
2062 // We are changing the range of the X axis.
2063
2064 // What is the x delta ?
2065 double xDelta =
2067
2068 // If xDelta is < 0, the we were dragging from right to left, we are
2069 // compressing the view on the x axis, by adding new data to the right
2070 // hand size of the graph. So we add xDelta to the upper bound of the
2071 // range. Otherwise we are uncompressing the view on the x axis and
2072 // remove the xDelta from the upper bound of the range. This is why we
2073 // have the
2074 // '-'
2075 // and not '+' below;
2076
2077 // qDebug() << "Setting xaxis:" << xLower << "--" << xUpper - xDelta;
2078
2079 xAxis->setRange(xLower, xUpper - xDelta);
2080 }
2081 // End of
2082 // if(m_context.m_wasClickOnXAxis)
2083 else // that is, if(m_context.m_wasClickOnYAxis)
2084 {
2085 // We are changing the range of the Y axis.
2086
2087 // What is the y delta ?
2088 double yDelta =
2090
2091 // See above for an explanation of the computation.
2092
2093 yAxis->setRange(yLower, yUpper - yDelta);
2094
2095 // Old version
2096 // if(yDelta < 0)
2097 //{
2098 //// The dragging operation was from top to bottom, we are enlarging
2099 //// the range (thus, we are unzooming the view, since the widget
2100 //// always has the same size).
2101
2102 // yAxis->setRange(yLower, yUpper + fabs(yDelta));
2103 //}
2104 // else
2105 //{
2106 //// The dragging operation was from bottom to top, we are reducing
2107 //// the range (thus, we are zooming the view, since the widget
2108 //// always has the same size).
2109
2110 // yAxis->setRange(yLower, yUpper - fabs(yDelta));
2111 //}
2112 }
2113 // End of
2114 // else // that is, if(m_context.m_wasClickOnYAxis)
2115
2116 // Update the context with the current axes ranges
2117
2119
2121
2122 replot();
2123}
2124
2125
2126void
2128{
2129
2130 // double sorted_start_drag_point_x =
2131 // std::min(m_context.m_startDragPoint.x(), m_context.m_currentDragPoint.x());
2132
2133 // xAxis->setRange(sorted_start_drag_point_x,
2134 // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2135
2136 xAxis->setRange(
2138
2139 // Note that the y axis should be rescaled from current lower value to new
2140 // upper value matching the y-axis position of the cursor when the mouse
2141 // button was released.
2142
2143 yAxis->setRange(xAxis->range().lower,
2144 std::max<double>(m_context.m_yRegionRangeStart,
2146
2147 // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2148 // xAxis->range().upper
2149 //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2150
2152
2155
2156 replot();
2157}
2158
2159
2160void
2162{
2163
2164 // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2165 // values before using them, because now we want to really have the lower x
2166 // value. Simply craft a QCPRange that will swap the values if lower is not
2167 // < than upper QCustomPlot calls this normalization).
2168
2169 xAxis->setRange(
2171
2172 yAxis->setRange(
2174
2176
2179
2180 replot();
2181}
2182
2183
2184void
2186{
2187 qDebug();
2188
2189 // Sanity check
2191 qFatal(
2192 "This function can only be called if the mouse click was on one of the "
2193 "axes");
2194
2196 {
2197 xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2199 }
2200
2202 {
2203 yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2205 }
2206
2208
2209 // qDebug() << "The updated context:" << m_context.toString();
2210
2211 // We cannot store the new ranges in the history, because the pan operation
2212 // involved a huge quantity of micro-movements elicited upon each mouse move
2213 // cursor event so we would have a huge history.
2214 // updateAxesRangeHistory();
2215
2216 // Now that the context has the right range values, we can emit the
2217 // signal that will be used by this plot widget users, typically to
2218 // abide by the x/y range lock required by the user.
2219
2221
2222 replot();
2223}
2224
2225
2226void
2228 QCPRange yAxisRange,
2229 Axis axis)
2230{
2231 // qDebug() << "With axis:" << (int)axis;
2232
2233 if(static_cast<int>(axis) & static_cast<int>(Axis::x))
2234 {
2235 xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2236 }
2237
2238 if(static_cast<int>(axis) & static_cast<int>(Axis::y))
2239 {
2240 yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2241 }
2242
2243 // We do not want to update the history, because there would be way too
2244 // much history items, since this function is called upon mouse moving
2245 // handling and not only during mouse release events.
2246 // updateAxesRangeHistory();
2247
2248 replot();
2249}
2250
2251
2252void
2253BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2254{
2255 // qDebug();
2256
2257 xAxis->setRange(lower, upper);
2258
2259 replot();
2260}
2261
2262
2263void
2264BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2265{
2266 // qDebug();
2267
2268 yAxis->setRange(lower, upper);
2269
2270 replot();
2271}
2272
2273/// PLOTTING / REPLOTTING functions
2274
2275
2276/// PLOT ITEMS : TRACER TEXT ITEMS...
2277
2278//! Hide the selection line, the xDelta text and the zoom rectangle items.
2279void
2281{
2282 mp_xDeltaTextItem->setVisible(false);
2283 mp_yDeltaTextItem->setVisible(false);
2284
2285 // mp_zoomRectItem->setVisible(false);
2287
2288 // Force a replot to make sure the action is immediately visible by the
2289 // user, even without moving the mouse.
2290 replot();
2291}
2292
2293
2294//! Show the traces (vertical and horizontal).
2295void
2297{
2299
2300 mp_vPosTracerItem->setVisible(true);
2301 mp_hPosTracerItem->setVisible(true);
2302
2303 mp_vStartTracerItem->setVisible(true);
2304 mp_vEndTracerItem->setVisible(true);
2305
2306 // Force a replot to make sure the action is immediately visible by the
2307 // user, even without moving the mouse.
2308 replot();
2309}
2310
2311
2312//! Hide the traces (vertical and horizontal).
2313void
2315{
2317 mp_hPosTracerItem->setVisible(false);
2318 mp_vPosTracerItem->setVisible(false);
2319
2320 mp_vStartTracerItem->setVisible(false);
2321 mp_vEndTracerItem->setVisible(false);
2322
2323 // Force a replot to make sure the action is immediately visible by the
2324 // user, even without moving the mouse.
2325 replot();
2326}
2327
2328
2329void
2331 bool for_integration)
2332{
2333 // The user has dragged the mouse left button on the graph, which means he
2334 // is willing to draw a selection rectangle, either for zooming-in or for
2335 // integration.
2336
2337 if(mp_xDeltaTextItem != nullptr)
2338 mp_xDeltaTextItem->setVisible(false);
2339 if(mp_yDeltaTextItem != nullptr)
2340 mp_yDeltaTextItem->setVisible(false);
2341
2342 // Ensure the right selection rectangle is drawn.
2343
2344 updateSelectionRectangle(as_line_segment, for_integration);
2345
2346 // Note that if we draw a zoom rectangle, then we are certainly not
2347 // measuring anything. So set the boolean value to false so that the user of
2348 // this widget or derived classes know that there is nothing to perform upon
2349 // (like deconvolution, for example).
2350
2352
2353 // Also remove the delta value from the pipeline by sending a simple
2354 // distance without measurement signal.
2355
2356 emit xAxisMeasurementSignal(m_context, false);
2357
2358 replot();
2359}
2360
2361
2362void
2364{
2365 // The user is dragging the mouse over the graph and we want them to know what
2366 // is the x delta value, that is the span between the point at the start of
2367 // the drag and the current drag position.
2368
2369 // FIXME: is this still true?
2370 //
2371 // We do not want to show the position markers because the only horiontal
2372 // line to be visible must be contained between the start and end vertiacal
2373 // tracer items.
2374 if(mp_hPosTracerItem != nullptr)
2375 mp_hPosTracerItem->setVisible(false);
2376 if(mp_vPosTracerItem != nullptr)
2377 mp_vPosTracerItem->setVisible(false);
2378
2379 // We want to draw the text in the middle position of the leftmost-rightmost
2380 // point, even with skewed rectangle selection.
2381
2382 QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2383
2384 // qDebug() << "leftmost_point:" << leftmost_point;
2385
2386 QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2387
2388 // qDebug() << "rightmost_point:" << rightmost_point;
2389
2390 double x_axis_center_position =
2391 leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2392
2393 // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2394
2395 // We want the text to print inside the rectangle, always at the current drag
2396 // point so the eye can follow the delta value while looking where to drag the
2397 // mouse. To position the text inside the rectangle, we need to know what is
2398 // the drag direction.
2399
2400 // Set aside a point instance to store the pixel coordinates of the text.
2401 QPointF pixel_coordinates;
2402
2403 // What is the distance between the rectangle line at current drag point and
2404 // the text itself.
2405 int pixels_away_from_line = 15;
2406
2407 // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2408 // order with respect to the y axis values !!! That is pixel(0,0) is top left
2409 // of the graph.
2410 if(static_cast<int>(m_context.m_dragDirections) &
2411 static_cast<int>(DragDirections::TOP_TO_BOTTOM))
2412 {
2413 // We need to print inside the rectangle, that is pixels_above_line pixels
2414 // to the bottom, so with pixel y value decremented of that
2415 // pixels_above_line value (one would have expected to increment that
2416 // value, along the y axis, but the coordinates in pixel go in reverse
2417 // order).
2418
2419 pixels_away_from_line *= -1;
2420 }
2421
2422 double y_axis_pixel_coordinate =
2423 yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2424
2425 double y_axis_modified_pixel_coordinate =
2426 y_axis_pixel_coordinate + pixels_away_from_line;
2427
2428 pixel_coordinates.setX(x_axis_center_position);
2429 pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2430
2431 // Now convert back to graph coordinates.
2432
2433 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2434 yAxis->pixelToCoord(pixel_coordinates.y()));
2435 if(mp_xDeltaTextItem != nullptr)
2436 {
2437 mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2438 graph_coordinates.y());
2439 mp_xDeltaTextItem->setText(
2440 QString("%1").arg(m_context.m_xDelta, 0, 'f', 3));
2441 mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2442 mp_xDeltaTextItem->setVisible(true);
2443 }
2444
2445 // Set the boolean to true so that derived widgets know that something is
2446 // being measured, and they can act accordingly, for example by computing
2447 // deconvolutions in a mass spectrum.
2449
2450 replot();
2451
2452 // Let the caller know that we were measuring something.
2454
2455 return;
2456}
2457
2458
2459void
2461{
2463 return;
2464
2465 // The user is dragging the mouse over the graph and we want them to know what
2466 // is the y delta value, that is the span between the point at the top of
2467 // the selection polygon and the point at its bottom.
2468
2469 // FIXME: is this still true?
2470 //
2471 // We do not want to show the position markers because the only horiontal
2472 // line to be visible must be contained between the start and end vertiacal
2473 // tracer items.
2474 mp_hPosTracerItem->setVisible(false);
2475 mp_vPosTracerItem->setVisible(false);
2476
2477 // We want to draw the text in the middle position of the leftmost-rightmost
2478 // point, even with skewed rectangle selection.
2479
2480 QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2481 QPointF topmost_point = m_context.m_selectionPolygon.getTopMostPoint();
2482
2483 // qDebug() << "leftmost_point:" << leftmost_point;
2484
2485 QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2486 QPointF bottommost_point = m_context.m_selectionPolygon.getBottomMostPoint();
2487
2488 // qDebug() << "rightmost_point:" << rightmost_point;
2489
2490 double x_axis_center_position =
2491 leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2492
2493 double y_axis_center_position =
2494 bottommost_point.y() + (topmost_point.y() - bottommost_point.y()) / 2;
2495
2496 // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2497
2498 mp_yDeltaTextItem->position->setCoords(x_axis_center_position,
2499 y_axis_center_position);
2500 mp_yDeltaTextItem->setText(QString("%1").arg(m_context.m_yDelta, 0, 'f', 3));
2501 mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2502 mp_yDeltaTextItem->setVisible(true);
2503 mp_yDeltaTextItem->setRotation(90);
2504
2505 // Set the boolean to true so that derived widgets know that something is
2506 // being measured, and they can act accordingly, for example by computing
2507 // deconvolutions in a mass spectrum.
2509
2510 replot();
2511
2512 // Let the caller know that we were measuring something.
2514}
2515
2516
2517void
2519{
2520
2521 // We compute signed differentials. If the user does not want the sign,
2522 // fabs(double) is their friend.
2523
2524 // Compute the xAxis differential:
2525
2528
2529 // Same with the Y-axis range:
2530
2533
2534 // qDebug() << "xDelta:" << m_context.m_xDelta
2535 //<< "and yDelta:" << m_context.m_yDelta;
2536
2537 return;
2538}
2539
2540
2541bool
2543{
2544 // First get the height of the plot.
2545 double plotHeight = yAxis->range().upper - yAxis->range().lower;
2546
2547 double heightDiff =
2549
2550 double heightDiffRatio = (heightDiff / plotHeight) * 100;
2551
2552 if(heightDiffRatio > 10)
2553 {
2554 // qDebug() << "isVerticalDisplacementAboveThreshold: true";
2555 return true;
2556 }
2557
2558 // qDebug() << "isVerticalDisplacementAboveThreshold: false";
2559 return false;
2560}
2561
2562
2563void
2565{
2566
2567 // if(for_integration)
2568 // qDebug() << "for_integration:" << for_integration;
2569
2570 // When we make a linear selection, the selection polygon is a polygon that
2571 // has the following characteristics:
2572 //
2573 // the x range is the linear selection span
2574 //
2575 // the y range is the widest std::min -> std::max possible.
2576
2577 // This is how the selection polygon logic knows if its is mono-
2578 // two-dimensional.
2579
2580 // We want the top left point to effectively be the top left point, so check
2581 // the direction of the mouse cursor drag.
2582
2583 double x_range_start =
2585 double x_range_end =
2587
2588 double y_position = m_context.m_startDragPoint.y();
2589
2590 m_context.m_selectionPolygon.set1D(x_range_start, x_range_end);
2591
2592 // Top line
2593 mp_selectionRectangeLine1->start->setCoords(
2594 QPointF(x_range_start, y_position));
2595 mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2596
2597 // Only if we are drawing a selection rectangle for integration, do we set
2598 // arrow heads to the line.
2599 if(for_integration)
2600 {
2601 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2602 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2603 }
2604 else
2605 {
2606 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2607 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2608 }
2609 mp_selectionRectangeLine1->setVisible(true);
2610
2611 // Right line: does not exist, start and end are the same end point of the top
2612 // line.
2613 mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2614 mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2615 mp_selectionRectangeLine2->setVisible(false);
2616
2617 // Bottom line: identical to the top line, but invisible
2618 mp_selectionRectangeLine3->start->setCoords(
2619 QPointF(x_range_start, y_position));
2620 mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2621 mp_selectionRectangeLine3->setVisible(false);
2622
2623 // Left line: does not exist: start and end are the same end point of the top
2624 // line.
2625 mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2626 mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2627 mp_selectionRectangeLine4->setVisible(false);
2628}
2629
2630
2631void
2633{
2634
2635 // if(for_integration)
2636 // qDebug() << "for_integration:" << for_integration;
2637
2638 // We are handling a conventional rectangle. Just create four points
2639 // from top left to bottom right. But we want the top left point to be
2640 // effectively the top left point and the bottom point to be the bottom point.
2641 // So we need to try all four direction combinations, left to right or
2642 // converse versus top to bottom or converse.
2643
2645
2647 {
2648 // qDebug() << "Dragging from right to left";
2649
2651 {
2652 // qDebug() << "Dragging from top to bottom";
2653
2654 // TOP_LEFT_POINT
2659
2660 // TOP_RIGHT_POINT
2664
2665 // BOTTOM_RIGHT_POINT
2670
2671 // BOTTOM_LEFT_POINT
2676 }
2677 // End of
2678 // if(m_context.m_currentDragPoint.y() < m_context.m_startDragPoint.y())
2679 else
2680 {
2681 // qDebug() << "Dragging from bottom to top";
2682
2683 // TOP_LEFT_POINT
2688
2689 // TOP_RIGHT_POINT
2694
2695 // BOTTOM_RIGHT_POINT
2699
2700 // BOTTOM_LEFT_POINT
2705 }
2706 }
2707 // End of
2708 // if(m_context.m_currentDragPoint.x() < m_context.m_startDragPoint.x())
2709 else
2710 {
2711 // qDebug() << "Dragging from left to right";
2712
2714 {
2715 // qDebug() << "Dragging from top to bottom";
2716
2717 // TOP_LEFT_POINT
2721
2722 // TOP_RIGHT_POINT
2727
2728 // BOTTOM_RIGHT_POINT
2733
2734 // BOTTOM_LEFT_POINT
2739 }
2740 else
2741 {
2742 // qDebug() << "Dragging from bottom to top";
2743
2744 // TOP_LEFT_POINT
2749
2750 // TOP_RIGHT_POINT
2755
2756 // BOTTOM_RIGHT_POINT
2761
2762 // BOTTOM_LEFT_POINT
2766 }
2767 }
2768
2769 // qDebug() << "Now draw the lines with points:"
2770 //<< m_context.m_selectionPolygon.toString();
2771
2772 // Top line
2773 mp_selectionRectangeLine1->start->setCoords(
2775 mp_selectionRectangeLine1->end->setCoords(
2777
2778 // Only if we are drawing a selection rectangle for integration, do we
2779 // set arrow heads to the line.
2780 if(for_integration)
2781 {
2782 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2783 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2784 }
2785 else
2786 {
2787 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2788 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2789 }
2790
2791 mp_selectionRectangeLine1->setVisible(true);
2792
2793 // Right line
2794 mp_selectionRectangeLine2->start->setCoords(
2796 mp_selectionRectangeLine2->end->setCoords(
2798 mp_selectionRectangeLine2->setVisible(true);
2799
2800 // Bottom line
2801 mp_selectionRectangeLine3->start->setCoords(
2803 mp_selectionRectangeLine3->end->setCoords(
2805 mp_selectionRectangeLine3->setVisible(true);
2806
2807 // Left line
2808 mp_selectionRectangeLine4->start->setCoords(
2810 mp_selectionRectangeLine4->end->setCoords(
2812 mp_selectionRectangeLine4->setVisible(true);
2813}
2814
2815
2816void
2818{
2819
2820 // if(for_integration)
2821 // qDebug() << "for_integration:" << for_integration;
2822
2823 // We are handling a skewed rectangle, that is a rectangle that is
2824 // tilted either to the left or to the right.
2825
2826 // qDebug() << "m_context.m_selectRectangleWidth: "
2827 //<< m_context.m_selectRectangleWidth;
2828
2829 // Top line
2830 // start
2831
2832 // qDebug() << "m_context.m_startDragPoint: " <<
2833 // m_context.m_startDragPoint.x()
2834 //<< "-" << m_context.m_startDragPoint.y();
2835
2836 // qDebug() << "m_context.m_currentDragPoint: "
2837 //<< m_context.m_currentDragPoint.x() << "-"
2838 //<< m_context.m_currentDragPoint.y();
2839
2841
2843 {
2844 // qDebug() << "Dragging from right to left";
2845
2847 {
2848 // qDebug() << "Dragging from top to bottom";
2849
2854
2855 // m_context.m_selRectTopLeftPoint.setX(
2856 // m_context.m_startDragPoint.x() -
2857 // m_context.m_selectRectangleWidth);
2858 // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2859
2863
2864 // m_context.m_selRectTopRightPoint.setX(m_context.m_startDragPoint.x());
2865 // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2866
2871
2872 // m_context.m_selRectBottomRightPoint.setX(
2873 // m_context.m_currentDragPoint.x() +
2874 // m_context.m_selectRectangleWidth);
2875 // m_context.m_selRectBottomRightPoint.setY(
2876 // m_context.m_currentDragPoint.y());
2877
2882
2883 // m_context.m_selRectBottomLeftPoint.setX(
2884 // m_context.m_currentDragPoint.x());
2885 // m_context.m_selRectBottomLeftPoint.setY(
2886 // m_context.m_currentDragPoint.y());
2887 }
2888 else
2889 {
2890 // qDebug() << "Dragging from bottom to top";
2891
2896
2897 // m_context.m_selRectTopLeftPoint.setX(
2898 // m_context.m_currentDragPoint.x());
2899 // m_context.m_selRectTopLeftPoint.setY(
2900 // m_context.m_currentDragPoint.y());
2901
2906
2907 // m_context.m_selRectTopRightPoint.setX(
2908 // m_context.m_currentDragPoint.x() +
2909 // m_context.m_selectRectangleWidth);
2910 // m_context.m_selRectTopRightPoint.setY(
2911 // m_context.m_currentDragPoint.y());
2912
2913
2917
2918 // m_context.m_selRectBottomRightPoint.setX(
2919 // m_context.m_startDragPoint.x());
2920 // m_context.m_selRectBottomRightPoint.setY(
2921 // m_context.m_startDragPoint.y());
2922
2927
2928 // m_context.m_selRectBottomLeftPoint.setX(
2929 // m_context.m_startDragPoint.x() -
2930 // m_context.m_selectRectangleWidth);
2931 // m_context.m_selRectBottomLeftPoint.setY(
2932 // m_context.m_startDragPoint.y());
2933 }
2934 }
2935 // End of
2936 // Dragging from right to left.
2937 else
2938 {
2939 // qDebug() << "Dragging from left to right";
2940
2942 {
2943 // qDebug() << "Dragging from top to bottom";
2944
2948
2949 // m_context.m_selRectTopLeftPoint.setX(m_context.m_startDragPoint.x());
2950 // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2951
2956
2957 // m_context.m_selRectTopRightPoint.setX(
2958 // m_context.m_startDragPoint.x() +
2959 // m_context.m_selectRectangleWidth);
2960 // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2961
2966
2967 // m_context.m_selRectBottomRightPoint.setX(
2968 // m_context.m_currentDragPoint.x());
2969 // m_context.m_selRectBottomRightPoint.setY(
2970 // m_context.m_currentDragPoint.y());
2971
2976
2977 // m_context.m_selRectBottomLeftPoint.setX(
2978 // m_context.m_currentDragPoint.x() -
2979 // m_context.m_selectRectangleWidth);
2980 // m_context.m_selRectBottomLeftPoint.setY(
2981 // m_context.m_currentDragPoint.y());
2982 }
2983 else
2984 {
2985 // qDebug() << "Dragging from bottom to top";
2986
2991
2992 // m_context.m_selRectTopLeftPoint.setX(
2993 // m_context.m_currentDragPoint.x() -
2994 // m_context.m_selectRectangleWidth);
2995 // m_context.m_selRectTopLeftPoint.setY(
2996 // m_context.m_currentDragPoint.y());
2997
3002
3003 // m_context.m_selRectTopRightPoint.setX(
3004 // m_context.m_currentDragPoint.x());
3005 // m_context.m_selRectTopRightPoint.setY(
3006 // m_context.m_currentDragPoint.y());
3007
3012
3013 // m_context.m_selRectBottomRightPoint.setX(
3014 // m_context.m_startDragPoint.x() +
3015 // m_context.m_selectRectangleWidth);
3016 // m_context.m_selRectBottomRightPoint.setY(
3017 // m_context.m_startDragPoint.y());
3018
3022
3023 // m_context.m_selRectBottomLeftPoint.setX(
3024 // m_context.m_startDragPoint.x());
3025 // m_context.m_selRectBottomLeftPoint.setY(
3026 // m_context.m_startDragPoint.y());
3027 }
3028 }
3029 // End of Dragging from left to right.
3030
3031 // qDebug() << "Now draw the lines with points:"
3032 //<< m_context.m_selectionPolygon.toString();
3033
3034 // Top line
3035 mp_selectionRectangeLine1->start->setCoords(
3037 mp_selectionRectangeLine1->end->setCoords(
3039
3040 // Only if we are drawing a selection rectangle for integration, do we set
3041 // arrow heads to the line.
3042 if(for_integration)
3043 {
3044 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
3045 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
3046 }
3047 else
3048 {
3049 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
3050 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
3051 }
3052
3053 mp_selectionRectangeLine1->setVisible(true);
3054
3055 // Right line
3056 mp_selectionRectangeLine2->start->setCoords(
3058 mp_selectionRectangeLine2->end->setCoords(
3060 mp_selectionRectangeLine2->setVisible(true);
3061
3062 // Bottom line
3063 mp_selectionRectangeLine3->start->setCoords(
3065 mp_selectionRectangeLine3->end->setCoords(
3067 mp_selectionRectangeLine3->setVisible(true);
3068
3069 // Left line
3070 mp_selectionRectangeLine4->end->setCoords(
3072 mp_selectionRectangeLine4->start->setCoords(
3074 mp_selectionRectangeLine4->setVisible(true);
3075}
3076
3077
3078void
3080 bool for_integration)
3081{
3082
3083 // qDebug() << "as_line_segment:" << as_line_segment;
3084 // qDebug() << "for_integration:" << for_integration;
3085
3086 // We now need to construct the selection rectangle, either for zoom or for
3087 // integration.
3088
3089 // There are two situations :
3090 //
3091 // 1. if the rectangle should look like a line segment
3092 //
3093 // 2. if the rectangle should actually look like a rectangle. In this case,
3094 // there are two sub-situations:
3095 //
3096 // a. if the S key is down, then the rectangle is
3097 // skewed, that is its vertical sides are not parallel to the y axis.
3098 //
3099 // b. otherwise the rectangle is conventional.
3100
3101 if(as_line_segment)
3102 {
3103 update1DSelectionRectangle(for_integration);
3104 }
3105 else
3106 {
3107 if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
3108 {
3109 update2DSelectionRectangleSquare(for_integration);
3110 }
3111 else if(m_context.m_keyboardModifiers & Qt::AltModifier)
3112 {
3113 update2DSelectionRectangleSkewed(for_integration);
3114 }
3115 }
3116
3117 // This code automatically sorts the ranges (range start is always less than
3118 // range end) even if the user actually selects from high to low (right to
3119 // left or bottom to top). This has implications in code that uses the
3120 // m_context data to perform some computations. This is why it is important
3121 // that m_dragDirections be set correctly to establish where the current drag
3122 // point is actually located (at which point).
3123
3128
3133
3134 // At this point, draw the text describing the widths.
3135
3136 // We want the x-delta on the bottom of the rectangle, inside it
3137 // and the y-delta on the vertical side of the rectangle, inside it.
3138
3139 // Draw the selection width text
3141}
3142
3143void
3145{
3146 mp_selectionRectangeLine1->setVisible(false);
3147 mp_selectionRectangeLine2->setVisible(false);
3148 mp_selectionRectangeLine3->setVisible(false);
3149 mp_selectionRectangeLine4->setVisible(false);
3150
3151 if(reset_values)
3152 {
3154 }
3155}
3156
3157
3158void
3160{
3162}
3163
3164
3167{
3168 // There are four lines that make the selection polygon. We want to know
3169 // which lines are visible.
3170
3171 int current_selection_polygon = static_cast<int>(PolygonType::NOT_SET);
3172
3173 if(mp_selectionRectangeLine1->visible())
3174 {
3175 current_selection_polygon |= static_cast<int>(PolygonType::TOP_LINE);
3176 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3177 }
3178 if(mp_selectionRectangeLine2->visible())
3179 {
3180 current_selection_polygon |= static_cast<int>(PolygonType::RIGHT_LINE);
3181 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3182 }
3183 if(mp_selectionRectangeLine3->visible())
3184 {
3185 current_selection_polygon |= static_cast<int>(PolygonType::BOTTOM_LINE);
3186 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3187 }
3188 if(mp_selectionRectangeLine4->visible())
3189 {
3190 current_selection_polygon |= static_cast<int>(PolygonType::LEFT_LINE);
3191 // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3192 }
3193
3194 // qDebug() << "returning visibility:" << current_selection_polygon;
3195
3196 return static_cast<PolygonType>(current_selection_polygon);
3197}
3198
3199
3200bool
3202{
3203 // Sanity check
3204 int check = 0;
3205
3206 check += mp_selectionRectangeLine1->visible();
3207 check += mp_selectionRectangeLine2->visible();
3208 check += mp_selectionRectangeLine3->visible();
3209 check += mp_selectionRectangeLine4->visible();
3210
3211 if(check > 0)
3212 return true;
3213
3214 return false;
3215}
3216
3217
3218void
3220{
3221 // qDebug() << "Setting focus to the QCustomPlot:" << this;
3222
3223 QCustomPlot::setFocus();
3224
3225 // qDebug() << "Emitting setFocusSignal().";
3226
3227 emit setFocusSignal();
3228}
3229
3230
3231//! Redraw the background of the \p focusedPlotWidget plot widget.
3232void
3233BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3234{
3235 if(focusedPlotWidget == nullptr)
3237 "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3238 "-- "
3239 "ERROR focusedPlotWidget cannot be nullptr.");
3240
3241 if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3242 {
3243 // The focused widget is not *this widget. We should make sure that
3244 // we were not the one that had the focus, because in this case we
3245 // need to redraw an unfocused background.
3246
3247 axisRect()->setBackground(m_unfocusedBrush);
3248 }
3249 else
3250 {
3251 axisRect()->setBackground(m_focusedBrush);
3252 }
3253
3254 replot();
3255}
3256
3257
3258void
3260{
3261 m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3262 m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3263
3264 // qDebug() << "The new updated context: " << m_context.toString();
3265}
3266
3267
3268const BasePlotContext &
3270{
3271 return m_context;
3272}
3273
3274
3275} // 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