libpappsomspp
Library for mass spectrometry
baseplotwidget.cpp
Go to the documentation of this file.
1/* This code comes right from the msXpertSuite software project.
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * END software license
23 */
24
25
26/////////////////////// StdLib includes
27#include <vector>
28
29
30/////////////////////// Qt includes
31#include <QVector>
32
33
34/////////////////////// Local includes
35#include "../../types.h"
36#include "../../utils.h"
37#include "baseplotwidget.h"
38#include "../../pappsoexception.h"
39#include "../../exception/exceptionnotpossible.h"
40
41
43 qRegisterMetaType<pappso::BasePlotContext>("pappso::BasePlotContext");
45 qRegisterMetaType<pappso::BasePlotContext *>("pappso::BasePlotContext *");
46
47
48namespace pappso
49{
50BasePlotWidget::BasePlotWidget(QWidget *parent) : QCustomPlot(parent)
51{
52 if(parent == nullptr)
53 qFatal("Programming error.");
54
55 // Default settings for the pen used to graph the data.
56 m_pen.setStyle(Qt::SolidLine);
57 m_pen.setBrush(Qt::black);
58 m_pen.setWidth(1);
59
60 // qDebug() << "Created new BasePlotWidget with" << layerCount()
61 //<< "layers before setting up widget.";
62 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
63
64 // As of today 20210313, the QCustomPlot is created with the following 6
65 // layers:
66 //
67 // All layers' name:
68 //
69 // Layer index 0 name: background
70 // Layer index 1 name: grid
71 // Layer index 2 name: main
72 // Layer index 3 name: axes
73 // Layer index 4 name: legend
74 // Layer index 5 name: overlay
75
76 if(!setupWidget())
77 qFatal("Programming error.");
78
79 // Do not call createAllAncillaryItems() in this base class because all the
80 // items will have been created *before* the addition of plots and then the
81 // rendering order will hide them to the viewer, since the rendering order is
82 // according to the order in which the items have been created.
83 //
84 // The fact that the ancillary items are created before trace plots is not a
85 // problem because the trace plots are sparse and do not effectively hide the
86 // data.
87 //
88 // But, in the color map plot widgets, we cannot afford to create the
89 // ancillary items *before* the plot itself because then, the rendering of the
90 // plot (created after) would screen off the ancillary items (created before).
91 //
92 // So, the createAllAncillaryItems() function needs to be called in the
93 // derived classes at the most appropriate moment in the setting up of the
94 // widget.
95 //
96 // All this is only a workaround of a bug in QCustomPlot. See
97 // https://www.qcustomplot.com/index.php/support/forum/2283.
98 //
99 // I initially wanted to have a plots layer on top of the default background
100 // layer and a items layer on top of it. But that setting prevented the
101 // selection of graphs.
102
103 // qDebug() << "Created new BasePlotWidget with" << layerCount()
104 //<< "layers after setting up widget.";
105 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
106
107 show();
108}
109
110
112 const QString &x_axis_label,
113 const QString &y_axis_label)
114 : QCustomPlot(parent), m_axisLabelX(x_axis_label), m_axisLabelY(y_axis_label)
115{
116 // qDebug();
117
118 if(parent == nullptr)
119 qFatal("Programming error.");
120
121 // Default settings for the pen used to graph the data.
122 m_pen.setStyle(Qt::SolidLine);
123 m_pen.setBrush(Qt::black);
124 m_pen.setWidth(1);
125
126 xAxis->setLabel(x_axis_label);
127 yAxis->setLabel(y_axis_label);
128
129 // qDebug() << "Created new BasePlotWidget with" << layerCount()
130 //<< "layers before setting up widget.";
131 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
132
133 // As of today 20210313, the QCustomPlot is created with the following 6
134 // layers:
135 //
136 // All layers' name:
137 //
138 // Layer index 0 name: background
139 // Layer index 1 name: grid
140 // Layer index 2 name: main
141 // Layer index 3 name: axes
142 // Layer index 4 name: legend
143 // Layer index 5 name: overlay
144
145 if(!setupWidget())
146 qFatal("Programming error.");
147
148 // qDebug() << "Created new BasePlotWidget with" << layerCount()
149 //<< "layers after setting up widget.";
150 // qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
151
152 show();
153}
154
155
156//! Destruct \c this BasePlotWidget instance.
157/*!
158
159 The destruction involves clearing the history, deleting all the axis range
160 history items for x and y axes.
161
162*/
164{
165 // qDebug() << "In the destructor of plot widget:" << this;
166
167 m_xAxisRangeHistory.clear();
168 m_yAxisRangeHistory.clear();
169
170 // Note that the QCustomPlot xxxItem objects are allocated with (this) which
171 // means their destruction is automatically handled upon *this' destruction.
172}
173
174
175QString
177{
178
179 QString text;
180
181 for(int iter = 0; iter < layerCount(); ++iter)
182 {
183 text +=
184 QString("Layer index %1: %2\n").arg(iter).arg(layer(iter)->name());
185 }
186
187 return text;
188}
189
190
191QString
192BasePlotWidget::layerableLayerName(QCPLayerable *layerable_p) const
193{
194 if(layerable_p == nullptr)
195 qFatal("Programming error.");
196
197 QCPLayer *layer_p = layerable_p->layer();
198
199 return layer_p->name();
200}
201
202
203int
204BasePlotWidget::layerableLayerIndex(QCPLayerable *layerable_p) const
205{
206 if(layerable_p == nullptr)
207 qFatal("Programming error.");
208
209 QCPLayer *layer_p = layerable_p->layer();
210
211 for(int iter = 0; iter < layerCount(); ++iter)
212 {
213 if(layer(iter) == layer_p)
214 return iter;
215 }
216
217 return -1;
218}
219
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 is defining the size of the rhomboid fixed side. That could be
765 // either a vertical side (less intuitive) or a horizontal size (more
766 // intuitive, first exclusive implementation). But, in order to be able to
767 // perform identical integrations starting from non-transposed color maps
768 // and transposed color maps, the ability to define a vertical fixed size
769 // side of the rhomboid integration scope has become necessary.
770
771 // Check if the vertical displacement is significant (>= 10% of the color
772 // map height.
773
775 {
776 // The user is dragging the cursor vertically in a sufficient delta to
777 // consider that they are willing to define a vertical fixed size
778 // of the rhomboid integration scope.
779
783
784 // qDebug() << "Set m_context.m_integrationScopePolyHeight to"
785 // << m_context.m_integrationScopeRhombHeight
786 // << "upon release of S key";
787 }
788 else
789 {
790 // The user is dragging the cursor horiontally to define a horizontal
791 // fixed size of the rhomboid integration scope.
792
796
797 // qDebug() << "Set m_context.m_integrationScopePolyWidth to"
798 // << m_context.m_integrationScopeRhombWidth
799 // << "upon release of S key";
800 }
801 }
802 // At this point emit the signal, since we did not treat it. Maybe the
803 // consumer widget wants to know that the keyboard key was released.
804
806}
807
808
809void
810BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
811{
812 // qDebug();
813}
814
815
816void
818{
819 // qDebug() << "event key:" << event->key();
820
821 // The user is trying to move the positional cursor/markers. There are
822 // multiple way they can do that:
823 //
824 // 1.a. Hitting the arrow left/right keys alone will search for next pixel.
825 // 1.b. Hitting the arrow left/right keys with Alt modifier will search for
826 // a multiple of pixels that might be equivalent to one 20th of the pixel
827 // width of the plot widget. 1.c Hitting the left/right keys with Alt and
828 // Shift modifiers will search for a multiple of pixels that might be the
829 // equivalent to half of the pixel width.
830 //
831 // 2. Hitting the Control modifier will move the cursor to the next data
832 // point of the graph.
833
834 int pixel_increment = 0;
835
836 if(m_context.m_keyboardModifiers == Qt::NoModifier)
837 pixel_increment = 1;
838 else if(m_context.m_keyboardModifiers == Qt::AltModifier)
839 pixel_increment = 50;
840
841 // The user is moving the positional markers. This is equivalent to a
842 // non-dragging cursor movement to the next pixel. Note that the origin is
843 // located at the top left, so key down increments and key up decrements.
844
845 if(event->key() == Qt::Key_Left)
846 horizontalMoveMouseCursorCountPixels(-pixel_increment);
847 else if(event->key() == Qt::Key_Right)
849 else if(event->key() == Qt::Key_Up)
850 verticalMoveMouseCursorCountPixels(-pixel_increment);
851 else if(event->key() == Qt::Key_Down)
852 verticalMoveMouseCursorCountPixels(pixel_increment);
853
854 event->accept();
855}
856
857
858void
860{
861 // qDebug() << "event key:" << event->key();
862 event->accept();
863}
864
865
866void
868 [[maybe_unused]] QKeyEvent *event)
869{
870 // qDebug();
871}
872
873
874void
876{
877
878 QPointF pixel_coordinates(
879 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
880 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
881
882 Qt::MouseButton button = Qt::NoButton;
883 QEvent::Type q_event_type = QEvent::MouseButtonPress;
884
885 if(event->key() == m_leftMousePseudoButtonKey)
886 {
887 // Toggles the left mouse button on/off
888
889 button = Qt::LeftButton;
890
893
895 q_event_type = QEvent::MouseButtonPress;
896 else
897 q_event_type = QEvent::MouseButtonRelease;
898 }
899 else if(event->key() == m_rightMousePseudoButtonKey)
900 {
901 // Toggles the right mouse button.
902
903 button = Qt::RightButton;
904
907
909 q_event_type = QEvent::MouseButtonPress;
910 else
911 q_event_type = QEvent::MouseButtonRelease;
912 }
913
914 // qDebug() << "pressed/released pseudo button:" << button
915 //<< "q_event_type:" << q_event_type;
916
917 // Synthesize a QMouseEvent and use it.
918
919 QMouseEvent *mouse_event_p =
920 new QMouseEvent(q_event_type,
921 pixel_coordinates,
922 mapToGlobal(pixel_coordinates.toPoint()),
923 mapToGlobal(pixel_coordinates.toPoint()),
924 button,
925 button,
927 Qt::MouseEventSynthesizedByApplication);
928
929 if(q_event_type == QEvent::MouseButtonPress)
930 mousePressHandler(mouse_event_p);
931 else
932 mouseReleaseHandler(mouse_event_p);
933
934 // event->accept();
935}
936/// KEYBOARD-related EVENTS
937
938
939/// MOUSE-related EVENTS
940
941void
943{
944
945 // If we have no focus, then get it. See setFocus() to understand why asking
946 // for focus is cosly and thus why we want to make this decision first.
947 if(!hasFocus())
948 setFocus();
949
950 // qDebug() << (graph() != nullptr);
951 // if(graph(0) != nullptr)
952 // { // check if the widget contains some graphs
953
954 // The event->button() must be by Qt instructions considered to be 0.
955
956 // Whatever happens, we want to store the plot coordinates of the current
957 // mouse cursor position (will be useful later for countless needs).
958
959 // Fix from Qt5 to Qt6
960#if QT_VERSION < 0x060000
961 QPointF mousePoint = event->localPos();
962#else
963 QPointF mousePoint = event->position();
964#endif
965 // qDebug() << "local mousePoint position in pixels:" << mousePoint;
966
967 m_context.m_lastCursorHoveredPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
968 m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
969
970 // qDebug() << "lastCursorHoveredPoint coord:"
971 //<< m_context.m_lastCursorHoveredPoint;
972
973 // Now, depending on the button(s) (if any) that are pressed or not, we
974 // have a different processing.
975
976 // qDebug();
977
978 if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
979 m_context.m_pressedMouseButtons & Qt::RightButton)
981 else
983 // }
984 // qDebug();
985 event->accept();
986}
987
988
989void
991{
992
993 // qDebug();
995
996 // qDebug();
997 // We are not dragging the mouse (no button pressed), simply let this
998 // widget's consumer know the position of the cursor and update the markers.
999 // The consumer of this widget will update mouse cursor position at
1000 // m_context.m_lastCursorHoveredPoint if so needed.
1001
1003
1004 // qDebug();
1005
1006 // We are not dragging, so we do not show the region end tracer we only
1007 // show the anchoring start trace that might be of use if the user starts
1008 // using the arrow keys to move the cursor.
1009 if(mp_vEndTracerItem != nullptr)
1010 mp_vEndTracerItem->setVisible(false);
1011
1012 // qDebug();
1013 // Only bother with the tracers if the user wants them to be visible.
1014 // Their crossing point must be exactly at the last cursor-hovered point.
1015
1017 {
1018 // We are not dragging, so only show the position markers (v and h);
1019
1020 // qDebug();
1021 if(mp_hPosTracerItem != nullptr)
1022 {
1023 // Horizontal position tracer.
1024 mp_hPosTracerItem->setVisible(true);
1025 mp_hPosTracerItem->start->setCoords(
1026 xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
1027 mp_hPosTracerItem->end->setCoords(
1028 xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
1029 }
1030
1031 // qDebug();
1032 // Vertical position tracer.
1033 if(mp_vPosTracerItem != nullptr)
1034 {
1035 mp_vPosTracerItem->setVisible(true);
1036
1037 mp_vPosTracerItem->setVisible(true);
1038 mp_vPosTracerItem->start->setCoords(
1039 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1040 mp_vPosTracerItem->end->setCoords(
1041 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1042 }
1043
1044 // qDebug();
1045 replot();
1046 }
1047
1048
1049 return;
1050}
1051
1052
1053void
1055{
1056 // qDebug();
1058
1059 // Now store the mouse position data into the the current drag point
1060 // member datum, that will be used in countless occasions later.
1062 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1063
1064 // When we drag (either keyboard or mouse), we hide the position markers
1065 // (black) and we show the start and end vertical markers for the region.
1066 // Then, we draw the horizontal region range marker that delimits
1067 // horizontally the dragged-over region.
1068
1069 if(mp_hPosTracerItem != nullptr)
1070 mp_hPosTracerItem->setVisible(false);
1071 if(mp_vPosTracerItem != nullptr)
1072 mp_vPosTracerItem->setVisible(false);
1073
1074 // Only bother with the tracers if the user wants them to be visible.
1076 {
1077
1078 // The vertical end tracer position must be refreshed.
1079 mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
1080 yAxis->range().upper);
1081
1083 yAxis->range().lower);
1084
1085 mp_vEndTracerItem->setVisible(true);
1086 }
1087
1088 // Whatever the button, when we are dealing with the axes, we do not
1089 // want to show any of the tracers.
1090
1092 {
1093 if(mp_hPosTracerItem != nullptr)
1094 mp_hPosTracerItem->setVisible(false);
1095 if(mp_vPosTracerItem != nullptr)
1096 mp_vPosTracerItem->setVisible(false);
1097
1098 if(mp_vStartTracerItem != nullptr)
1099 mp_vStartTracerItem->setVisible(false);
1100 if(mp_vEndTracerItem != nullptr)
1101 mp_vEndTracerItem->setVisible(false);
1102 }
1103 else
1104 {
1105 // Since we are not dragging the mouse cursor over the axes, make sure
1106 // we store the drag directions in the context, as this might be
1107 // useful for later operations.
1108
1110
1111 // qDebug() << m_context.toString();
1112 }
1113
1114 // Because when we drag the mouse button (whatever the button) we need to
1115 // know what is the drag delta (distance between start point and current
1116 // point of the drag operation) on both axes, ask that these x|y deltas be
1117 // computed.
1119
1120 // Now deal with the BUTTON-SPECIFIC CODE.
1121
1122 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
1123 {
1125 }
1126 else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
1127 {
1129 }
1130}
1131
1132
1133void
1135{
1136 // qDebug() << "The left button is dragging.";
1137
1138 // Set the context.m_isMeasuringDistance to false, which later might be set
1139 // to true if effectively we are measuring a distance. This is required
1140 // because the derived widget classes might want to know if they have to
1141 // perform some action on the basis that context is measuring a distance,
1142 // for example the mass spectrum-specific widget might want to compute
1143 // deconvolutions.
1144
1146
1147 // Let's first check if the mouse drag operation originated on either
1148 // axis. In that case, the user is performing axis reframing or rescaling.
1149
1151 {
1152 // qDebug() << "Click was on one of the axes.";
1153
1154 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1155 {
1156 // The user is asking a rescale of the plot.
1157
1158 // We know that we do not want the tracers when we perform axis
1159 // rescaling operations.
1160
1161 if(mp_hPosTracerItem != nullptr)
1162 mp_hPosTracerItem->setVisible(false);
1163 if(mp_vPosTracerItem != nullptr)
1164 mp_vPosTracerItem->setVisible(false);
1165
1166 if(mp_vStartTracerItem != nullptr)
1167 mp_vStartTracerItem->setVisible(false);
1168 if(mp_vEndTracerItem != nullptr)
1169 mp_vEndTracerItem->setVisible(false);
1170
1171 // This operation is particularly intensive, thus we want to
1172 // reduce the number of calculations by skipping this calculation
1173 // a number of times. The user can ask for this feature by
1174 // clicking the 'Q' letter.
1175
1176 if(m_context.m_pressedKeyCode == Qt::Key_Q)
1177 {
1179 {
1181 return;
1182 }
1183 else
1184 {
1186 }
1187 }
1188
1189 // qDebug() << "Asking that the axes be rescaled.";
1190
1191 axisRescale();
1192 }
1193 else
1194 {
1195 // The user was simply dragging the axis. Just pan, that is slide
1196 // the plot in the same direction as the mouse movement and with the
1197 // same amplitude.
1198
1199 // qDebug() << "Asking that the axes be panned.";
1200
1201 axisPan();
1202 }
1203
1204 return;
1205 }
1206
1207 // At this point we understand that the user was not performing any
1208 // panning/rescaling operation by clicking on any one of the axes.. Go on
1209 // with other possibilities.
1210
1211 // Let's check if the user is actually drawing a rectangle (covering a
1212 // real area) or is drawing a line.
1213
1214 // qDebug() << "The mouse dragging did not originate on an axis.";
1215
1217 {
1218 // qDebug() << "Apparently the selection is two-dimensional.";
1219
1220 // When we draw a two-dimensional integration scope, the tracers are of no
1221 // use.
1222
1223 if(mp_hPosTracerItem != nullptr)
1224 mp_hPosTracerItem->setVisible(false);
1225 if(mp_vPosTracerItem != nullptr)
1226 mp_vPosTracerItem->setVisible(false);
1227
1228 if(mp_vStartTracerItem != nullptr)
1229 mp_vStartTracerItem->setVisible(false);
1230 if(mp_vEndTracerItem != nullptr)
1231 mp_vEndTracerItem->setVisible(false);
1232
1233 // Draw the rectangle, false, not as line segment and
1234 // false, not for integration
1236
1237 // Draw the selection width/height text
1240 }
1241 else
1242 {
1243 // qDebug() << "Apparently we are measuring a delta.";
1244
1245 // Draw the rectangle, true, as line segment and
1246 // false, not for integration
1248
1249 // The pure position tracers should be hidden.
1250 if(mp_hPosTracerItem != nullptr)
1251 mp_hPosTracerItem->setVisible(true);
1252 if(mp_vPosTracerItem != nullptr)
1253 mp_vPosTracerItem->setVisible(true);
1254
1255 // Then, make sure the region range vertical tracers are visible.
1256 if(mp_vStartTracerItem != nullptr)
1257 mp_vStartTracerItem->setVisible(true);
1258 if(mp_vEndTracerItem != nullptr)
1259 mp_vEndTracerItem->setVisible(true);
1260
1261 // Draw the selection width text
1263 }
1264}
1265
1266
1267void
1269{
1270 // qDebug() << "The right button is dragging.";
1271
1272 // Set the context.m_isMeasuringDistance to false, which later might be set
1273 // to true if effectively we are measuring a distance. This is required
1274 // because the derived widgets might want to know if they have to perform
1275 // some action on the basis that context is measuring a distance, for
1276 // example the mass spectrum-specific widget might want to compute
1277 // deconvolutions.
1278
1280
1282 {
1283 // qDebug() << "Apparently the selection has height.";
1284
1285 // When we draw a rectangle the tracers are of no use.
1286
1287 if(mp_hPosTracerItem != nullptr)
1288 mp_hPosTracerItem->setVisible(false);
1289 if(mp_vPosTracerItem != nullptr)
1290 mp_vPosTracerItem->setVisible(false);
1291
1292 if(mp_vStartTracerItem != nullptr)
1293 mp_vStartTracerItem->setVisible(false);
1294 if(mp_vEndTracerItem != nullptr)
1295 mp_vEndTracerItem->setVisible(false);
1296
1297 // Draw the rectangle, false for as_line_segment and true for
1298 // integration.
1300
1301 // Draw the selection width/height text
1304 }
1305 else
1306 {
1307 // qDebug() << "Apparently the selection is a not a rectangle.";
1308
1309 // Draw the rectangle, true as line segment and
1310 // true for integration
1312
1313 // Draw the selection width text
1315 }
1316}
1317
1318
1319void
1321{
1322 // qDebug() << "Entering";
1323
1324 // When the user clicks this widget it has to take focus.
1325 setFocus();
1326
1327 // Fix from Qt5 to Qt6
1328 // QPointF mousePoint = event->localPos();
1329
1330#if QT_VERSION < 0x060000
1331 QPointF mousePoint = event->localPos();
1332#else
1333 QPointF mousePoint = event->position();
1334#endif
1335
1336 m_context.m_lastPressedMouseButton = event->button();
1337 m_context.m_mouseButtonsAtMousePress = event->buttons();
1338
1339 // The pressedMouseButtons must continually inform on the status of
1340 // pressed buttons so add the pressed button.
1341 m_context.m_pressedMouseButtons |= event->button();
1342
1343 // qDebug().noquote() << m_context.toString();
1344
1345 // In all the processing of the events, we need to know if the user is
1346 // clicking somewhere with the intent to change the plot ranges (reframing
1347 // or rescaling the plot).
1348 //
1349 // Reframing the plot means that the new x and y axes ranges are modified
1350 // so that they match the region that the user has encompassed by left
1351 // clicking the mouse and dragging it over the plot. That is we reframe
1352 // the plot so that it contains only the "selected" region.
1353 //
1354 // Rescaling the plot means the the new x|y axis range is modified such
1355 // that the lower axis range is constant and the upper axis range is moved
1356 // either left or right by the same amont as the x|y delta encompassed by
1357 // the user moving the mouse. The axis is thus either compressed (mouse
1358 // movement is leftwards) or un-compressed (mouse movement is rightwards).
1359
1360 // There are two ways to perform axis range modifications:
1361 //
1362 // 1. By clicking on any of the axes
1363 // 2. By clicking on the plot region but using keyboard key modifiers,
1364 // like Alt and Ctrl.
1365 //
1366 // We need to know both cases separately which is why we need to perform a
1367 // number of tests below.
1368
1369 // Let's check if the click is on the axes, either X or Y, because that
1370 // will allow us to take proper actions.
1371
1372 if(isClickOntoXAxis(mousePoint))
1373 {
1374 // The X axis was clicked upon, we need to document that:
1375 // qDebug() << __FILE__ << __LINE__
1376 //<< "Layout element is axisRect and actually on an X axis part.";
1377
1379
1380 // int currentInteractions = interactions();
1381 // currentInteractions |= QCP::iRangeDrag;
1382 // setInteractions((QCP::Interaction)currentInteractions);
1383 // axisRect()->setRangeDrag(xAxis->orientation());
1384 }
1385 else
1387
1388 if(isClickOntoYAxis(mousePoint))
1389 {
1390 // The Y axis was clicked upon, we need to document that:
1391 // qDebug() << __FILE__ << __LINE__
1392 //<< "Layout element is axisRect and actually on an Y axis part.";
1393
1395
1396 // int currentInteractions = interactions();
1397 // currentInteractions |= QCP::iRangeDrag;
1398 // setInteractions((QCP::Interaction)currentInteractions);
1399 // axisRect()->setRangeDrag(yAxis->orientation());
1400 }
1401 else
1403
1404 // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1405
1407 {
1408 // qDebug() << __FILE__ << __LINE__
1409 // << "Click outside of axes.";
1410
1411 // int currentInteractions = interactions();
1412 // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1413 // setInteractions((QCP::Interaction)currentInteractions);
1414 }
1415
1416 m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1417 m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1418
1419 // Now install the vertical start tracer at the last cursor hovered
1420 // position.
1421 if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
1422 mp_vStartTracerItem->setVisible(true);
1423
1424 if(mp_vStartTracerItem != nullptr)
1425 {
1426 mp_vStartTracerItem->start->setCoords(
1427 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
1428 mp_vStartTracerItem->end->setCoords(
1429 m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
1430 }
1431
1432 replot();
1433
1435
1436 // qDebug() << "Exiting after having emitted mousePressEventSignal with base context:"
1437 // << m_context.toString();
1438}
1439
1440
1441void
1443{
1444 // qDebug() << "Entering";
1445
1446 // Now the real code of this function.
1447
1448 m_context.m_lastReleasedMouseButton = event->button();
1449
1450 // The event->buttons() is the description of the buttons that are pressed
1451 // at the moment the handler is invoked, that is now. If left and right were
1452 // pressed, and left was released, event->buttons() would be right.
1453 m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1454
1455 // The pressedMouseButtons must continually inform on the status of pressed
1456 // buttons so remove the released button.
1457 m_context.m_pressedMouseButtons ^= event->button();
1458
1459 // qDebug().noquote() << m_context.toString();
1460
1461 // We'll need to know if modifiers were pressed a the moment the user
1462 // released the mouse button.
1463 m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1464
1466 {
1467 // Let the user know that the mouse was *not* being dragged.
1469
1470 event->accept();
1471
1472 return;
1473 }
1474
1475 // Let the user know that the mouse was being dragged.
1477
1478 // We cannot hide all items in one go because we rely on their visibility
1479 // to know what kind of dragging operation we need to perform (line-only
1480 // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1481 // only thing we know is that we can make the text invisible.
1482
1483 // Same for the x delta text item
1484 mp_xDeltaTextItem->setVisible(false);
1485 mp_yDeltaTextItem->setVisible(false);
1486
1487 // We do not show the end vertical region range marker.
1488 mp_vEndTracerItem->setVisible(false);
1489
1490 // Horizontal position tracer.
1491 mp_hPosTracerItem->setVisible(true);
1492 mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1494 mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1496
1497 // Vertical position tracer.
1498 mp_vPosTracerItem->setVisible(true);
1499
1500 mp_vPosTracerItem->setVisible(true);
1502 yAxis->range().upper);
1504 yAxis->range().lower);
1505
1506 // Force replot now because later that call might not be performed.
1507 replot();
1508
1509 // If we were using the "quantum" display for the rescale of the axes
1510 // using the Ctrl-modified left button click drag in the axes, then reset
1511 // the count to 0.
1513
1514 // By definition we are stopping the drag operation by releasing the mouse
1515 // button. Whatever that mouse button was pressed before and if there was
1516 // one pressed before. We cannot set that boolean value to false before
1517 // this place, because we call a number of routines above that need to know
1518 // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1519 // example.
1520
1522
1523 // Now that we have computed the useful ranges, we need to check what to do
1524 // depending on the button that was pressed.
1525
1526 if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1527 {
1529 }
1530 else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1531 {
1533 }
1534
1535 event->accept();
1536
1537 // Before returning, emit the signal for the user of
1538 // this class consumption.
1539 // qDebug() << "Emitting mouseReleaseEventSignal.";
1541
1542 // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with base context:"
1543 // << m_context.toString();
1544
1545 return;
1546}
1547
1548
1549void
1551{
1552 // qDebug();
1553
1555 {
1556
1557 // When the mouse move handler pans the plot, we cannot store each axes
1558 // range history element that would mean store a huge amount of such
1559 // elements, as many element as there are mouse move event handled by
1560 // the Qt event queue. But we can store an axis range history element
1561 // for the last situation of the mouse move: when the button is
1562 // released:
1563
1565
1566 // qDebug() << "emit plotRangesChangedSignal(m_context);"
1567
1569
1570 replot();
1571
1572 // Nothing else to do.
1573 return;
1574 }
1575
1576 // There are two possibilities:
1577 //
1578 // 1. The full integration scope (four lines) were currently drawn, which
1579 // means the user was willing to perform a zoom operation.
1580 //
1581 // 2. Only the first top line was drawn, which means the user was dragging
1582 // the cursor horizontally. That might have two ends, as shown below.
1583
1584 // So, first check what is drawn of the selection polygon.
1585
1586 SelectionDrawingLines selection_drawing_lines =
1588
1589 // Now that we know what was currently drawn of the selection polygon, we
1590 // can remove it. true to reset the values to 0.
1592
1593 // Force replot now because later that call might not be performed.
1594 replot();
1595
1596 if(selection_drawing_lines == SelectionDrawingLines::FULL_POLYGON)
1597 {
1598 // qDebug() << "Yes, the full polygon was visible";
1599
1600 // If we were dragging with the left button pressed and could draw a
1601 // rectangle, then we were preparing a zoom operation. Let's bring that
1602 // operation to its accomplishment.
1603
1604 axisZoom();
1605
1606 return;
1607 }
1608 else if(selection_drawing_lines == SelectionDrawingLines::TOP_LINE)
1609 {
1610 // qDebug() << "No, only the top line of the full polygon was visible";
1611
1612 // The user was dragging the left mouse cursor and that may mean they
1613 // were measuring a distance or willing to perform a special zoom
1614 // operation if the Ctrl key was down.
1615
1616 // If the user started by clicking in the plot region, dragged the mouse
1617 // cursor with the left button and pressed the Ctrl modifier, then that
1618 // means that they wanted to do a rescale over the x-axis in the form of
1619 // a reframing.
1620
1621 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1622 {
1623 return axisReframe();
1624 }
1625 }
1626 // else
1627 // qDebug() << "Another possibility.";
1628}
1629
1630
1631void
1633{
1634 // qDebug();
1635 // The right button is used for the integrations. Not for axis range
1636 // operations. So all we have to do is remove the various graphics items and
1637 // send a signal with the context that contains all the data required by the
1638 // user to perform the integrations over the right plot regions.
1639
1640 // Whatever we were doing we need to make the selection line invisible:
1641
1642 if(mp_xDeltaTextItem->visible())
1643 mp_xDeltaTextItem->setVisible(false);
1644 if(mp_yDeltaTextItem->visible())
1645 mp_yDeltaTextItem->setVisible(false);
1646
1647 // Also make the vertical end tracer invisible.
1648 mp_vEndTracerItem->setVisible(false);
1649
1650 // Once the integration is asked for, then the selection rectangle if of no
1651 // more use.
1653
1654 // Force replot now because later that call might not be performed.
1655 replot();
1656
1657 // Note that we only request an integration if the x-axis delta is enough.
1658
1659 double x_delta_pixel =
1660 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1661 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1662
1663 if(x_delta_pixel > 3)
1664 {
1665 // qDebug() << "Emitting integrationRequestedSignal(m_context)";
1667 }
1668 // else
1669 // qDebug() << "Not asking for integration.";
1670}
1671
1672
1673void
1674BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
1675{
1676 // We should record the new range values each time the wheel is used to
1677 // zoom/unzoom.
1678
1679 m_context.m_xRange = QCPRange(xAxis->range());
1680 m_context.m_yRange = QCPRange(yAxis->range());
1681
1682 // qDebug() << "New x range: " << m_context.m_xRange;
1683 // qDebug() << "New y range: " << m_context.m_yRange;
1684
1686
1689
1690 event->accept();
1691}
1692
1693
1694void
1696 QCPAxis *axis,
1697 [[maybe_unused]] QCPAxis::SelectablePart part,
1698 QMouseEvent *event)
1699{
1700 // qDebug();
1701
1702 m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1703
1704 if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1705 {
1706 // qDebug();
1707
1708 // If the Ctrl modifiers is active, then both axes are to be reset. Also
1709 // the histories are reset also.
1710
1711 rescaleAxes();
1713 }
1714 else
1715 {
1716 // qDebug();
1717
1718 // Only the axis passed as parameter is to be rescaled.
1719 // Reset the range of that axis to the max view possible.
1720
1721 axis->rescale();
1722
1724
1725 event->accept();
1726 }
1727
1728 // The double-click event does not cancel the mouse press event. That is, if
1729 // left-double-clicking, at the end of the operation the button still
1730 // "pressed". We need to remove manually the button from the pressed buttons
1731 // context member.
1732
1733 m_context.m_pressedMouseButtons ^= event->button();
1734
1736
1738
1739 replot();
1740}
1741
1742
1743bool
1744BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1745{
1746 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1747
1748 if(layoutElement &&
1749 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1750 {
1751 // The graph is *inside* the axisRect that is the outermost envelope of
1752 // the graph. Thus, if we want to know if the click was indeed on an
1753 // axis, we need to check what selectable part of the the axisRect we
1754 // were clicking:
1755 QCPAxis::SelectablePart selectablePart;
1756
1757 selectablePart = xAxis->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
1769bool
1770BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1771{
1772 QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1773
1774 if(layoutElement &&
1775 layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1776 {
1777 // The graph is *inside* the axisRect that is the outermost envelope of
1778 // the graph. Thus, if we want to know if the click was indeed on an
1779 // axis, we need to check what selectable part of the the axisRect we
1780 // were clicking:
1781 QCPAxis::SelectablePart selectablePart;
1782
1783 selectablePart = yAxis->getPartAt(mousePoint);
1784
1785 if(selectablePart == QCPAxis::spAxisLabel ||
1786 selectablePart == QCPAxis::spAxis ||
1787 selectablePart == QCPAxis::spTickLabels)
1788 return true;
1789 }
1790
1791 return false;
1792}
1793
1794/// MOUSE-related EVENTS
1795
1796
1797/// MOUSE MOVEMENTS mouse/keyboard-triggered
1798
1799int
1801{
1802 // The user is dragging the mouse, probably to rescale the axes, but we need
1803 // to sort out in which direction the drag is happening.
1804
1805 // This function should be called after calculateDragDeltas, so that
1806 // m_context has the proper x/y delta values that we'll compare.
1807
1808 // Note that we cannot compare simply x or y deltas because the y axis might
1809 // have a different scale that the x axis. So we first need to convert the
1810 // positions to pixels.
1811
1812 double x_delta_pixel =
1813 fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1814 xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1815
1816 double y_delta_pixel =
1817 fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1818 yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1819
1820 if(x_delta_pixel > y_delta_pixel)
1821 return Qt::Horizontal;
1822
1823 return Qt::Vertical;
1824}
1825
1826
1827void
1829{
1830 // First convert the graph coordinates to pixel coordinates.
1831
1832 QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1833 yAxis->coordToPixel(graph_coordinates.y()));
1834
1835 moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1836}
1837
1838
1839void
1841{
1842 // qDebug() << "Calling set pos with new cursor position.";
1843 QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1844}
1845
1846
1847void
1849{
1850 QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1851
1852 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1853 yAxis->coordToPixel(graph_coord.y()));
1854
1855 // Now we need ton convert the new coordinates to the global position system
1856 // and to move the cursor to that new position. That will create an event to
1857 // move the mouse cursor.
1858
1859 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1860}
1861
1862
1863QPointF
1865{
1866 QPointF pixel_coordinates(
1867 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1868 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1869
1870 // Now convert back to local coordinates.
1871
1872 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1873 yAxis->pixelToCoord(pixel_coordinates.y()));
1874
1875 return graph_coordinates;
1876}
1877
1878
1879void
1881{
1882
1883 QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1884
1885 QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1886 yAxis->coordToPixel(graph_coord.y()));
1887
1888 // Now we need ton convert the new coordinates to the global position system
1889 // and to move the cursor to that new position. That will create an event to
1890 // move the mouse cursor.
1891
1892 moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1893}
1894
1895
1896QPointF
1898{
1899 QPointF pixel_coordinates(
1900 xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1901 yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1902
1903 // Now convert back to local coordinates.
1904
1905 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1906 yAxis->pixelToCoord(pixel_coordinates.y()));
1907
1908 return graph_coordinates;
1909}
1910
1911/// MOUSE MOVEMENTS mouse/keyboard-triggered
1912
1913
1914/// RANGE-related functions
1915
1916QCPRange
1917BasePlotWidget::getRangeX(bool &found_range, int index) const
1918{
1919 QCPGraph *graph_p = graph(index);
1920
1921 if(graph_p == nullptr)
1922 qFatal("Programming error.");
1923
1924 return graph_p->getKeyRange(found_range);
1925}
1926
1927
1928QCPRange
1929BasePlotWidget::getRangeY(bool &found_range, int index) const
1930{
1931 QCPGraph *graph_p = graph(index);
1932
1933 if(graph_p == nullptr)
1934 qFatal("Programming error.");
1935
1936 return graph_p->getValueRange(found_range);
1937}
1938
1939
1940QCPRange
1942 RangeType range_type,
1943 bool &found_range) const
1944{
1945
1946 // Iterate in all the graphs in this widget and return a QCPRange that has
1947 // its lower member as the greatest lower value of all
1948 // its upper member as the smallest upper value of all
1949
1950 if(!graphCount())
1951 {
1952 found_range = false;
1953
1954 return QCPRange(0, 1);
1955 }
1956
1957 if(graphCount() == 1)
1958 return graph()->getKeyRange(found_range);
1959
1960 bool found_at_least_one_range = false;
1961
1962 // Create an invalid range.
1963 QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
1964
1965 for(int iter = 0; iter < graphCount(); ++iter)
1966 {
1967 QCPRange temp_range;
1968
1969 bool found_range_for_iter = false;
1970
1971 QCPGraph *graph_p = graph(iter);
1972
1973 // Depending on the axis param, select the key or value range.
1974
1975 if(axis == Axis::x)
1976 temp_range = graph_p->getKeyRange(found_range_for_iter);
1977 else if(axis == Axis::y)
1978 temp_range = graph_p->getValueRange(found_range_for_iter);
1979 else
1980 qFatal("Cannot reach this point. Programming error.");
1981
1982 // Was a range found for the iterated graph ? If not skip this
1983 // iteration.
1984
1985 if(!found_range_for_iter)
1986 continue;
1987
1988 // While the innermost_range is invalid, we need to seed it with a good
1989 // one. So check this.
1990
1991 if(!QCPRange::validRange(result_range))
1992 qFatal("The obtained range is invalid !");
1993
1994 // At this point we know the obtained range is OK.
1995 result_range = temp_range;
1996
1997 // We found at least one valid range!
1998 found_at_least_one_range = true;
1999
2000 // At this point we have two valid ranges to compare. Depending on
2001 // range_type, we need to perform distinct comparisons.
2002
2003 if(range_type == RangeType::innermost)
2004 {
2005 if(temp_range.lower > result_range.lower)
2006 result_range.lower = temp_range.lower;
2007 if(temp_range.upper < result_range.upper)
2008 result_range.upper = temp_range.upper;
2009 }
2010 else if(range_type == RangeType::outermost)
2011 {
2012 if(temp_range.lower < result_range.lower)
2013 result_range.lower = temp_range.lower;
2014 if(temp_range.upper > result_range.upper)
2015 result_range.upper = temp_range.upper;
2016 }
2017 else
2018 qFatal("Cannot reach this point. Programming error.");
2019
2020 // Continue to next graph, if any.
2021 }
2022 // End of
2023 // for(int iter = 0; iter < graphCount(); ++iter)
2024
2025 // Let the caller know if we found at least one range.
2026 found_range = found_at_least_one_range;
2027
2028 return result_range;
2029}
2030
2031
2032QCPRange
2034{
2035
2036 return getRange(Axis::x, RangeType::innermost, found_range);
2037}
2038
2039
2040QCPRange
2042{
2043 return getRange(Axis::x, RangeType::outermost, found_range);
2044}
2045
2046
2047QCPRange
2049{
2050
2051 return getRange(Axis::y, RangeType::innermost, found_range);
2052}
2053
2054
2055QCPRange
2057{
2058 return getRange(Axis::y, RangeType::outermost, found_range);
2059}
2060
2061
2062/// RANGE-related functions
2063
2064
2065/// PLOTTING / REPLOTTING functions
2066
2067void
2069{
2070 // Get the current x lower/upper range, that is, leftmost/rightmost x
2071 // coordinate.
2072 double xLower = xAxis->range().lower;
2073 double xUpper = xAxis->range().upper;
2074
2075 // Get the current y lower/upper range, that is, bottommost/topmost y
2076 // coordinate.
2077 double yLower = yAxis->range().lower;
2078 double yUpper = yAxis->range().upper;
2079
2080 // This function is called only when the user has clicked on the x/y axis or
2081 // when the user has dragged the left mouse button with the Ctrl key
2082 // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
2083 // move handler. So we need to test which axis was clicked-on.
2084
2086 {
2087 // We are changing the range of the X axis.
2088
2089 // If xDelta is < 0, then we were dragging from right to left, we are
2090 // compressing the view on the x axis, by adding new data to the right
2091 // hand size of the graph. So we add xDelta to the upper bound of the
2092 // range. Otherwise we are uncompressing the view on the x axis and
2093 // remove the xDelta from the upper bound of the range. This is why we
2094 // have the
2095 // '-'
2096 // and not '+' below;
2097
2098 xAxis->setRange(xLower, xUpper - m_context.m_xDelta);
2099 }
2100 // End of
2101 // if(m_context.m_wasClickOnXAxis)
2102 else // that is, if(m_context.m_wasClickOnYAxis)
2103 {
2104 // We are changing the range of the Y axis.
2105
2106 // See above for an explanation of the computation (the - sign below).
2107
2108 yAxis->setRange(yLower, yUpper - m_context.m_yDelta);
2109 }
2110 // End of
2111 // else // that is, if(m_context.m_wasClickOnYAxis)
2112
2113 // Update the context with the current axes ranges
2114
2116
2118
2119 replot();
2120}
2121
2122
2123void
2125{
2126
2127 // double sorted_start_drag_point_x =
2128 // std::min(m_context.m_startDragPoint.x(),
2129 // m_context.m_currentDragPoint.x());
2130
2131 // xAxis->setRange(sorted_start_drag_point_x,
2132 // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2133
2134 xAxis->setRange(
2136
2137 // Note that the y axis should be rescaled from current lower value to new
2138 // upper value matching the y-axis position of the cursor when the mouse
2139 // button was released.
2140
2141 yAxis->setRange(xAxis->range().lower,
2142 std::max<double>(m_context.m_yRegionRangeStart,
2144
2145 // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2146 // xAxis->range().upper
2147 //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2148
2150
2153
2154 replot();
2155}
2156
2157
2158void
2160{
2161
2162 // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2163 // values before using them, because now we want to really have the lower x
2164 // value. Simply craft a QCPRange that will swap the values if lower is not
2165 // < than upper QCustomPlot calls this normalization).
2166
2167 xAxis->setRange(
2169
2170 yAxis->setRange(
2172
2174
2177
2178 replot();
2179}
2180
2181
2182void
2184{
2185 // Sanity check
2187 qFatal(
2188 "This function can only be called if the mouse click was on one of the "
2189 "axes");
2190
2192 {
2193 xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2195 }
2196
2198 {
2199 yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2201 }
2202
2204
2205 // qDebug() << "The updated context:" << m_context.toString();
2206
2207 // We cannot store the new ranges in the history, because the pan operation
2208 // involved a huge quantity of micro-movements elicited upon each mouse move
2209 // cursor event so we would have a huge history.
2210 // updateAxesRangeHistory();
2211
2212 // Now that the context has the right range values, we can emit the
2213 // signal that will be used by this plot widget users, typically to
2214 // abide by the x/y range lock required by the user.
2215
2217
2218 replot();
2219}
2220
2221
2222void
2224 QCPRange yAxisRange,
2225 Axis axis)
2226{
2227 // qDebug() << "With axis:" << (int)axis;
2228
2229 if(static_cast<int>(axis) & static_cast<int>(Axis::x))
2230 {
2231 xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2232 }
2233
2234 if(static_cast<int>(axis) & static_cast<int>(Axis::y))
2235 {
2236 yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2237 }
2238
2239 // We do not want to update the history, because there would be way too
2240 // much history items, since this function is called upon mouse moving
2241 // handling and not only during mouse release events.
2242 // updateAxesRangeHistory();
2243
2244 replot();
2245}
2246
2247
2248void
2249BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2250{
2251 // qDebug();
2252
2253 xAxis->setRange(lower, upper);
2254
2255 replot();
2256}
2257
2258
2259void
2260BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2261{
2262 // qDebug();
2263
2264 yAxis->setRange(lower, upper);
2265
2266 replot();
2267}
2268
2269/// PLOTTING / REPLOTTING functions
2270
2271
2272/// PLOT ITEMS : TRACER TEXT ITEMS...
2273
2274//! Hide the selection line, the xDelta text and the zoom rectangle items.
2275void
2277{
2278 mp_xDeltaTextItem->setVisible(false);
2279 mp_yDeltaTextItem->setVisible(false);
2280
2281 // mp_zoomRectItem->setVisible(false);
2283
2284 // Force a replot to make sure the action is immediately visible by the
2285 // user, even without moving the mouse.
2286 replot();
2287}
2288
2289
2290//! Show the traces (vertical and horizontal).
2291void
2293{
2295
2296 mp_vPosTracerItem->setVisible(true);
2297 mp_hPosTracerItem->setVisible(true);
2298
2299 mp_vStartTracerItem->setVisible(true);
2300 mp_vEndTracerItem->setVisible(true);
2301
2302 // Force a replot to make sure the action is immediately visible by the
2303 // user, even without moving the mouse.
2304 replot();
2305}
2306
2307
2308//! Hide the traces (vertical and horizontal).
2309void
2311{
2313 mp_hPosTracerItem->setVisible(false);
2314 mp_vPosTracerItem->setVisible(false);
2315
2316 mp_vStartTracerItem->setVisible(false);
2317 mp_vEndTracerItem->setVisible(false);
2318
2319 // Force a replot to make sure the action is immediately visible by the
2320 // user, even without moving the mouse.
2321 replot();
2322}
2323
2324
2325void
2327 bool for_integration)
2328{
2329 // The user has dragged the mouse left button on the graph, which means he
2330 // is willing to draw a selection rectangle, either for zooming-in or for
2331 // integration.
2332
2333 if(mp_xDeltaTextItem != nullptr)
2334 mp_xDeltaTextItem->setVisible(false);
2335 if(mp_yDeltaTextItem != nullptr)
2336 mp_yDeltaTextItem->setVisible(false);
2337
2338 // Ensure the right selection rectangle is drawn.
2339
2340 updateIntegrationScopeDrawing(as_line_segment, for_integration);
2341
2342 // Note that if we draw a zoom rectangle, then we are certainly not
2343 // measuring anything. So set the boolean value to false so that the user of
2344 // this widget or derived classes know that there is nothing to perform upon
2345 // (like deconvolution, for example).
2346
2348
2349 // Also remove the delta value from the pipeline by sending a simple
2350 // distance without measurement signal.
2351
2352 emit xAxisMeasurementSignal(m_context, false);
2353
2354 replot();
2355}
2356
2357
2358void
2360{
2361 // Depending on the kind of integration scope, we will have to display
2362 // differently calculated values. We want to provide the user with
2363 // the horizontal span of the integration scope. There are different
2364 // situations.
2365
2366 // 1. The scope is mono-dimensional across the x axis: the span
2367 // is thus simply the width.
2368
2369 // 2. The scope is bi-dimensional and is a rectangle: the span is
2370 // thus simply the width.
2371
2372 // 3. The socpe is bi-dimensional and is a rhomboid: the span is
2373 // the width.
2374
2375 // In the first and second cases above, the width is equal to the
2376 // m_context.m_xDelta.
2377
2378 // In the case of the rhomboid, the span is not m_context.m_xDelta,
2379 // it is more than that if the rhomboid is horizontal because it is
2380 // the m_context.m_xDelta plus the rhomboid's horizontal size.
2381
2382 // FIXME: is this still true?
2383 //
2384 // We do not want to show the position markers because the only horiontal
2385 // line to be visible must be contained between the start and end vertical
2386 // tracer items.
2387 mp_hPosTracerItem->setVisible(false);
2388 mp_vPosTracerItem->setVisible(false);
2389
2390 // We want to draw the text in the middle position of the leftmost-rightmost
2391 // point, even with rhomboid scopes.
2392
2393 QPointF leftmost_point;
2394 if(!m_context.msp_integrationScope->getLeftMostPoint(leftmost_point))
2395 qFatal("Could not get the left-most point.");
2396
2397 double width;
2398 if(!m_context.msp_integrationScope->getWidth(width))
2399 qFatal("Could not get width.");
2400 // qDebug() << "width:" << width;
2401
2402 double x_axis_center_position = leftmost_point.x() + width / 2;
2403
2404 // We want the text to print inside the rectangle, always at the current
2405 // drag point so the eye can follow the delta value while looking where to
2406 // drag the mouse. To position the text inside the rectangle, we need to
2407 // know what is the drag direction.
2408
2409 // What is the distance between the rectangle line at current drag point and
2410 // the text itself. Think of this as a margin distance between the
2411 // point of interest and the actual position of the text.
2412 int pixels_away_from_line = 15;
2413
2414 QPointF reference_point_for_y_axis_label_position;
2415
2416 // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2417 // order with respect to the y axis values !!! That is, pixel(0,0) is top
2418 // left of the graph.
2419 if(static_cast<int>(m_context.m_dragDirections) &
2420 static_cast<int>(DragDirections::BOTTOM_TO_TOP))
2421 {
2422 // We need to print outside the rectangle, that is pixels_away_from_line
2423 // pixels to the top, so with pixel y value decremented of that
2424 // pixels_above_line value (one would have expected to increment that
2425 // value, along the y axis, but the coordinates in pixel go in reverse
2426 // order).
2427
2428 pixels_away_from_line *= -1;
2429
2430 if(!m_context.msp_integrationScope->getTopMostPoint(
2431 reference_point_for_y_axis_label_position))
2432 qFatal("Failed to get top most point.");
2433 }
2434 else
2435 {
2436 if(!m_context.msp_integrationScope->getBottomMostPoint(
2437 reference_point_for_y_axis_label_position))
2438 qFatal("Failed to get bottom most point.");
2439 }
2440
2441 // double y_axis_pixel_coordinate =
2442 // yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2443 double y_axis_pixel_coordinate =
2444 yAxis->coordToPixel(reference_point_for_y_axis_label_position.y());
2445
2446 // Now that we have the coordinate in pixel units, we can correct
2447 // it by the value of the margin we want to give.
2448 double y_axis_modified_pixel_coordinate =
2449 y_axis_pixel_coordinate + pixels_away_from_line;
2450
2451 // Set aside a point instance to store the pixel coordinates of the text.
2452 QPointF pixel_coordinates;
2453
2454 pixel_coordinates.setX(x_axis_center_position);
2455 pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2456
2457 // Now convert back to graph coordinates.
2458 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2459 yAxis->pixelToCoord(pixel_coordinates.y()));
2460
2461 // qDebug() << "Should print the label at point:" << graph_coordinates;
2462
2463 if(mp_xDeltaTextItem != nullptr)
2464 {
2465 mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2466 graph_coordinates.y());
2467
2468 // Dynamically set the number of decimals to ensure we can read
2469 // a meaning full delta value even if it is very very very small.
2470 // That is, allow one to read 0.00333, 0.000333, 1.333 and so on.
2471
2472 // The computation below only works properly when the passed
2473 // value is fabs() (not negative !!!).
2474
2475 int decimals = Utils::zeroDecimalsInValue(width) + 3;
2476
2477 QString label_text = QString("full x span %1 -- x drag delta %2")
2478 .arg(width, 0, 'f', decimals)
2479 .arg(fabs(m_context.m_xDelta), 0, 'f', decimals);
2480
2481 mp_xDeltaTextItem->setText(label_text);
2482
2483 mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2484 mp_xDeltaTextItem->setVisible(true);
2485 }
2486
2487 // Set the boolean to true so that derived widgets know that something is
2488 // being measured, and they can act accordingly, for example by computing
2489 // deconvolutions in a mass spectrum.
2491
2492 replot();
2493
2494 // Let the caller know that we were measuring something.
2496
2497 return;
2498}
2499
2500void
2502{
2503 // See drawXScopeSpanFeatures() for explanations.
2504
2505 // Check right away if there is height!
2506 double height;
2507 if(!m_context.msp_integrationScope->getHeight(height))
2508 qFatal("Could not get height.");
2509
2510 // If there is no height, we have nothing to do here.
2511 if(!height)
2512 return;
2513 // qDebug() << "height:" << height;
2514
2515 // FIXME: is this still true?
2516 //
2517 // We do not want to show the position markers because the only horiontal
2518 // line to be visible must be contained between the start and end vertical
2519 // tracer items.
2520 mp_hPosTracerItem->setVisible(false);
2521 mp_vPosTracerItem->setVisible(false);
2522
2523 // First the easy part: the vertical position: centered on the
2524 // scope Y span.
2525 QPointF bottom_most_point;
2526 if(!m_context.msp_integrationScope->getBottomMostPoint(bottom_most_point))
2527 qFatal("Could not get the bottom-most bottom point.");
2528
2529 double y_axis_center_position = bottom_most_point.y() + height / 2;
2530
2531 // We want to draw the text outside the rectangle (if normal rectangle)
2532 // at a small distance from the vertical limit of the scope at the
2533 // position of the current drag point. We need to check the horizontal
2534 // drag direction to put the text at the right place (left of
2535 // current drag point if dragging right to left, for example).
2536
2537 // What is the distance between the rectangle line at current drag point and
2538 // the text itself.
2539 int pixels_away_from_line = 15;
2540 double x_axis_coordinate;
2541 double x_axis_pixel_coordinate;
2542
2543 if(static_cast<int>(m_context.m_dragDirections) &
2544 static_cast<int>(DragDirections::RIGHT_TO_LEFT))
2545 {
2546 QPointF left_most_point;
2547
2548 if(!m_context.msp_integrationScope->getLeftMostPoint(left_most_point))
2549 qFatal("Failed to get left most point.");
2550
2551 x_axis_coordinate = left_most_point.x();
2552
2553 pixels_away_from_line *= -1;
2554 }
2555 else
2556 {
2557 QPointF right_most_point;
2558
2559 if(!m_context.msp_integrationScope->getRightMostPoint(right_most_point))
2560 qFatal("Failed to get right most point.");
2561
2562 x_axis_coordinate = right_most_point.x();
2563 }
2564 x_axis_pixel_coordinate = xAxis->coordToPixel(x_axis_coordinate);
2565
2566 double x_axis_modified_pixel_coordinate =
2567 x_axis_pixel_coordinate + pixels_away_from_line;
2568
2569 // Set aside a point instance to store the pixel coordinates of the text.
2570 QPointF pixel_coordinates;
2571
2572 pixel_coordinates.setX(x_axis_modified_pixel_coordinate);
2573 pixel_coordinates.setY(y_axis_center_position);
2574
2575 // Now convert back to graph coordinates.
2576
2577 QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2578 yAxis->pixelToCoord(pixel_coordinates.y()));
2579
2580 mp_yDeltaTextItem->position->setCoords(graph_coordinates.x(),
2581 y_axis_center_position);
2582
2583 int decimals = Utils::zeroDecimalsInValue(height) + 3;
2584
2585 QString label_text = QString("full y span %1 -- y drag delta %2")
2586 .arg(height, 0, 'f', decimals)
2587 .arg(fabs(m_context.m_yDelta), 0, 'f', decimals);
2588
2589 mp_yDeltaTextItem->setText(label_text);
2590 mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2591 mp_yDeltaTextItem->setVisible(true);
2592 mp_yDeltaTextItem->setRotation(90);
2593
2594 // Set the boolean to true so that derived widgets know that something is
2595 // being measured, and they can act accordingly, for example by computing
2596 // deconvolutions in a mass spectrum.
2598
2599 replot();
2600
2601 // Let the caller know that we were measuring something.
2603}
2604
2605
2606void
2608{
2609
2610 // We compute signed differentials. If the user does not want the sign,
2611 // fabs(double) is their friend.
2612
2613 // Compute the xAxis differential:
2614
2617
2618 // Same with the Y-axis range:
2619
2622
2623 return;
2624}
2625
2626
2627bool
2629{
2630 // First get the height of the plot.
2631 double plotHeight = yAxis->range().upper - yAxis->range().lower;
2632
2633 double heightDiff =
2635
2636 double heightDiffRatio = (heightDiff / plotHeight) * 100;
2637
2638 if(heightDiffRatio > 10)
2639 {
2640 return true;
2641 }
2642
2643 return false;
2644}
2645
2646
2647void
2649{
2650
2651 // if(for_integration)
2652 // qDebug() << "for_integration:" << for_integration;
2653
2654 // By essence, the one-dimension IntegrationScope is characterized
2655 // by the left-most point and the width. Using these two data bits
2656 // it is possible to compute the x value of the right-most point.
2657
2658 double x_range_start =
2660 double x_range_end =
2662
2663 double y_position = m_context.m_startDragPoint.y();
2664
2666
2667 // Top line
2668 mp_selectionRectangeLine1->start->setCoords(
2669 QPointF(x_range_start, y_position));
2670 mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2671
2672 // Only if we are drawing a selection rectangle for integration, do we set
2673 // arrow heads to the line.
2674 if(for_integration)
2675 {
2676 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2677 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2678 }
2679 else
2680 {
2681 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2682 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2683 }
2684 mp_selectionRectangeLine1->setVisible(true);
2685
2686 // Right line: does not exist, start and end are the same end point of the
2687 // top line.
2688 mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2689 mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2690 mp_selectionRectangeLine2->setVisible(false);
2691
2692 // Bottom line: identical to the top line, but invisible
2693 mp_selectionRectangeLine3->start->setCoords(
2694 QPointF(x_range_start, y_position));
2695 mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2696 mp_selectionRectangeLine3->setVisible(false);
2697
2698 // Left line: does not exist: start and end are the same end point of the
2699 // top line.
2700 mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2701 mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2702 mp_selectionRectangeLine4->setVisible(false);
2703}
2704
2705
2706void
2708{
2709 // qDebug();
2710
2711 // if(for_integration)
2712 // qDebug() << "for_integration:" << for_integration;
2713
2714 // We are handling a conventional rectangle. Just create four points
2715 // from top left to bottom right. But we want the top left point to be
2716 // effectively the top left point and the bottom point to be the bottom
2717 // point. So we need to try all four direction combinations, left to right
2718 // or converse versus top to bottom or converse.
2719
2721
2722 // Now that the integration scope has been updated as a rectangle,
2723 // use these newly set data to actually draw the integration
2724 // scope lines.
2725
2726 QPointF bottom_left_point;
2727 if(!m_context.msp_integrationScope->getPoint(bottom_left_point))
2728 qFatal("Failed to get point.");
2729 // qDebug() << "Starting point is left bottom point:" << bottom_left_point;
2730
2731 double width;
2732 if(!m_context.msp_integrationScope->getWidth(width))
2733 qFatal("Failed to get width.");
2734 // qDebug() << "Width:" << width;
2735
2736 double height;
2737 if(!m_context.msp_integrationScope->getHeight(height))
2738 qFatal("Failed to get height.");
2739 // qDebug() << "Height:" << height;
2740
2741 QPointF bottom_right_point(bottom_left_point.x() + width,
2742 bottom_left_point.y());
2743 // qDebug() << "bottom_right_point:" << bottom_right_point;
2744
2745 QPointF top_right_point(bottom_left_point.x() + width,
2746 bottom_left_point.y() + height);
2747 // qDebug() << "top_right_point:" << top_right_point;
2748
2749 QPointF top_left_point(bottom_left_point.x(), bottom_left_point.y() + height);
2750
2751 // qDebug() << "top_left_point:" << top_left_point;
2752
2753 // Start by drawing the bottom line because the IntegrationScopeRect has the
2754 // left bottom point and the width and the height to fully characterize it.
2755
2756 // Bottom line (left to right)
2757 mp_selectionRectangeLine3->start->setCoords(bottom_left_point);
2758 mp_selectionRectangeLine3->end->setCoords(bottom_right_point);
2759 mp_selectionRectangeLine3->setVisible(true);
2760
2761 // Right line (bottom to top)
2762 mp_selectionRectangeLine2->start->setCoords(bottom_right_point);
2763 mp_selectionRectangeLine2->end->setCoords(top_right_point);
2764 mp_selectionRectangeLine2->setVisible(true);
2765
2766 // Top line (right to left)
2767 mp_selectionRectangeLine1->start->setCoords(top_right_point);
2768 mp_selectionRectangeLine1->end->setCoords(top_left_point);
2769 mp_selectionRectangeLine1->setVisible(true);
2770
2771 // Left line (top to bottom)
2772 mp_selectionRectangeLine4->start->setCoords(top_left_point);
2773 mp_selectionRectangeLine4->end->setCoords(bottom_left_point);
2774 mp_selectionRectangeLine4->setVisible(true);
2775
2776 // Only if we are drawing a selection rectangle for integration, do we
2777 // set arrow heads to the line.
2778 if(for_integration)
2779 {
2780 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2781 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2782 }
2783 else
2784 {
2785 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2786 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2787 }
2788}
2789
2790
2791void
2793{
2794 // We are handling a rhomboid scope, that is, a rectangle that
2795 // is tilted either to the left or to the right.
2796
2797 // There are two kinds of rhomboid integration scopes: horizontal and
2798 // vertical.
2799
2800 /*
2801 * +----------+
2802 * | |
2803 * | |
2804 * | |
2805 * | |
2806 * | |
2807 * | |
2808 * | |
2809 * +----------+
2810 * ----width---
2811 */
2812
2813 // As visible here, the fixed size of the rhomboid (using the S key in the
2814 // plot widget) is the *horizontal* side (this is the plot context's
2815 // m_integrationScopeRhombWidth).
2816
2817 IntegrationScopeFeatures scope_features;
2818
2819 // Top horizontal line
2820 QPointF point_1;
2821 scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);
2822
2823 // When the user rotates the horizontal rhomboid, at some point, if the
2824 // current drag point has the same y axis value as the start drag point, then
2825 // we say that the rhomboid is flattened on the x axis. In this case, we do
2826 // not draw anything as this is a purely unusable situation.
2827
2828 if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
2829 {
2830 // qDebug() << "The horizontal rhomboid is flattened on the x axis.";
2831
2832 mp_selectionRectangeLine1->setVisible(false);
2833 mp_selectionRectangeLine2->setVisible(false);
2834 mp_selectionRectangeLine3->setVisible(false);
2835 mp_selectionRectangeLine4->setVisible(false);
2836
2837 return;
2838 }
2839
2841 qFatal("The rhomboid should be horizontal!");
2842
2843 // At this point we can draw the rhomboid fine.
2844
2845 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
2846 qFatal("Failed to getLeftMostTopPoint.");
2847 QPointF point_2;
2848 if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
2849 qFatal("Failed to getRightMostTopPoint.");
2850
2851 // qDebug() << "For top line, two points:" << point_1 << "--" << point_2;
2852
2853 mp_selectionRectangeLine1->start->setCoords(point_1);
2854 mp_selectionRectangeLine1->end->setCoords(point_2);
2855
2856 // Only if we are drawing a selection rectangle for integration, do we set
2857 // arrow heads to the line.
2858 if(for_integration)
2859 {
2860 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2861 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2862 }
2863 else
2864 {
2865 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2866 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2867 }
2868
2869 mp_selectionRectangeLine1->setVisible(true);
2870
2871 // Right line
2872 if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
2873 qFatal("Failed to getRightMostBottomPoint.");
2874 mp_selectionRectangeLine2->start->setCoords(point_2);
2875 mp_selectionRectangeLine2->end->setCoords(point_1);
2876 mp_selectionRectangeLine2->setVisible(true);
2877
2878 // qDebug() << "For right line, two points:" << point_2 << "--" << point_1;
2879
2880 // Bottom horizontal line
2881 if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
2882 qFatal("Failed to getLeftMostBottomPoint.");
2883 mp_selectionRectangeLine3->start->setCoords(point_1);
2884 mp_selectionRectangeLine3->end->setCoords(point_2);
2885 mp_selectionRectangeLine3->setVisible(true);
2886
2887 // qDebug() << "For bottom line, two points:" << point_1 << "--" << point_2;
2888
2889 // Left line
2890 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
2891 qFatal("Failed to getLeftMostTopPoint.");
2892 mp_selectionRectangeLine4->end->setCoords(point_2);
2893 mp_selectionRectangeLine4->start->setCoords(point_1);
2894 mp_selectionRectangeLine4->setVisible(true);
2895
2896 // qDebug() << "For left line, two points:" << point_2 << "--" << point_1;
2897}
2898
2899
2900void
2902{
2903 // We are handling a rhomboid scope, that is, a rectangle that
2904 // is tilted either to the left or to the right.
2905
2906 // There are two kinds of rhomboid integration scopes: horizontal and
2907 // vertical.
2908
2909 /*
2910 * +3
2911 * . |
2912 * . |
2913 * . |
2914 * . +2
2915 * . .
2916 * . .
2917 * . .
2918 * 4+ .
2919 * | | .
2920 * height | | .
2921 * | | .
2922 * 1+
2923 *
2924 */
2925
2926 // As visible here, the fixed size of the rhomboid (using the S key in the
2927 // plot widget) is the *vertical* side (this is the plot context's
2928 // m_integrationScopeRhombHeight).
2929
2930 IntegrationScopeFeatures scope_features;
2931
2932 // Left vertical line
2933 QPointF point_1;
2934 scope_features = m_context.msp_integrationScope->getLeftMostTopPoint(point_1);
2935
2936 // When the user rotates the vertical rhomboid, at some point, if the current
2937 // drag point is on the same x axis value as the start drag point, then we say
2938 // that the rhomboid is flattened on the y axis. In this case, we do not draw
2939 // anything as this is a purely unusable situation.
2940
2941 if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
2942 {
2943 // qDebug() << "The vertical rhomboid is flattened on the y axis.";
2944
2945 mp_selectionRectangeLine1->setVisible(false);
2946 mp_selectionRectangeLine2->setVisible(false);
2947 mp_selectionRectangeLine3->setVisible(false);
2948 mp_selectionRectangeLine4->setVisible(false);
2949
2950 return;
2951 }
2952
2954 qFatal("The rhomboid should be vertical!");
2955
2956 // At this point we can draw the rhomboid fine.
2957
2958 QPointF point_2;
2959 if(!m_context.msp_integrationScope->getLeftMostBottomPoint(point_2))
2960 qFatal("Failed to getLeftMostBottomPoint.");
2961
2962 // qDebug() << "For left vertical line, two points:" << point_1 << "--"
2963 // << point_2;
2964
2965 mp_selectionRectangeLine1->start->setCoords(point_1);
2966 mp_selectionRectangeLine1->end->setCoords(point_2);
2967
2968 // Only if we are drawing a selection rectangle for integration, do we set
2969 // arrow heads to the line.
2970 if(for_integration)
2971 {
2972 mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2973 mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2974 }
2975 else
2976 {
2977 mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2978 mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2979 }
2980
2981 mp_selectionRectangeLine1->setVisible(true);
2982
2983 // Lower oblique line
2984 if(!m_context.msp_integrationScope->getRightMostBottomPoint(point_1))
2985 qFatal("Failed to getRightMostBottomPoint.");
2986 mp_selectionRectangeLine2->start->setCoords(point_2);
2987 mp_selectionRectangeLine2->end->setCoords(point_1);
2988 mp_selectionRectangeLine2->setVisible(true);
2989
2990 // qDebug() << "For lower oblique line, two points:" << point_2 << "--"
2991 // << point_1;
2992
2993 // Right vertical line
2994 if(!m_context.msp_integrationScope->getRightMostTopPoint(point_2))
2995 qFatal("Failed to getRightMostTopPoint.");
2996 mp_selectionRectangeLine3->start->setCoords(point_1);
2997 mp_selectionRectangeLine3->end->setCoords(point_2);
2998 mp_selectionRectangeLine3->setVisible(true);
2999
3000 // qDebug() << "For right vertical line, two points:" << point_1 << "--"
3001 // << point_2;
3002
3003 // Upper oblique line
3004 if(!m_context.msp_integrationScope->getLeftMostTopPoint(point_1))
3005 qFatal("Failed to get the LeftMostTopPoint.");
3006 mp_selectionRectangeLine4->end->setCoords(point_2);
3007 mp_selectionRectangeLine4->start->setCoords(point_1);
3008 mp_selectionRectangeLine4->setVisible(true);
3009
3010 // qDebug() << "For upper oblique line, two points:" << point_2 << "--"
3011 // << point_1;
3012}
3013
3014
3015void
3017{
3018 // qDebug();
3019
3020 // if(for_integration)
3021 // qDebug() << "for_integration:" << for_integration;
3022
3023 // We are handling a skewed rectangle (rhomboid), that is a rectangle that
3024 // is tilted either to the left or to the right.
3025
3026 // There are two kinds of rhomboid integration scopes:
3027
3028 /*
3029 4+----------+3
3030 | |
3031 | |
3032 | |
3033 | |
3034 | |
3035 | |
3036 | |
3037 1+----------+2
3038 ----width---
3039 */
3040
3041 // As visible here, the fixed size of the rhomboid (using the S key in the
3042 // plot widget) is the *horizontal* side (this is the plot context's
3043 // m_integrationScopeRhombWidth).
3044
3045 // and
3046
3047
3048 /*
3049 * +3
3050 * . |
3051 * . |
3052 * . |
3053 * . +2
3054 * . .
3055 * . .
3056 * . .
3057 * 4+ .
3058 * | | .
3059 * height | | .
3060 * | | .
3061 * 1+
3062 *
3063 */
3064
3065 // As visible here, the fixed size of the rhomboid (using the S key in the
3066 // plot widget) is the *vertical* side (this is the plot context's
3067 // m_integrationScopeRhombHeight).
3068
3069 // qDebug() << "Before calling updateIntegrationScopeRhomb(), "
3070 // "m_integrationScopeRhombWidth:"
3071 // << m_context.m_integrationScopeRhombWidth
3072 // << "and m_integrationScopeRhombHeight:"
3073 // << m_context.m_integrationScopeRhombHeight;
3074
3076
3077 // qDebug() << "After, m_integrationScopeRhombWidth:"
3078 // << m_context.m_integrationScopeRhombWidth
3079 // << "and m_integrationScopeRhombHeight:"
3080 // << m_context.m_integrationScopeRhombHeight;
3081
3082 // Now that the integration scope has been updated as a rhomboid,
3083 // use these newly set data to actually draw the integration
3084 // scope lines.
3085
3086 // We thus need to first establish if we have a horiontal or a vertical
3087 // rhomboid scope. This information is located in
3088 // m_context.m_integrationScopeRhombWidth and
3089 // m_context.m_integrationScopeRhombHeight. If width > 0, height *has to be
3090 // 0*, which indicates a horizontal rhomb.Conversely, if height is > 0, then
3091 // the rhomb is vertical.
3092
3094 // We are dealing with a horizontal scope.
3097 // We are dealing with a vertical scope.
3098 updateIntegrationScopeVerticalRhomb(for_integration);
3099 else
3100 qFatal("Cannot be both the width or height of rhomboid scope be 0.");
3101}
3102
3103void
3105 bool for_integration)
3106{
3107 // qDebug() << "as_line_segment:" << as_line_segment;
3108 // qDebug() << "for_integration:" << for_integration;
3109
3110 // We now need to construct the selection rectangle, either for zoom or for
3111 // integration.
3112
3113 // There are two situations :
3114 //
3115 // 1. if the rectangle should look like a line segment
3116 //
3117 // 2. if the rectangle should actually look like a rectangle. In this case,
3118 // there are two sub-situations:
3119 //
3120 // a. if the Alt modifier key is down, then the rectangle is rhomboid.
3121 //
3122 // b. otherwise the rectangle is conventional.
3123
3124 if(as_line_segment)
3125 {
3126 // qDebug() << "Updating the integration scope to an IntegrationScope.";
3127 updateIntegrationScope(for_integration);
3128 }
3129 else
3130 {
3131 if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
3132 {
3133 // qDebug()
3134 // << "Updating the integration scope to an IntegrationScopeRect.";
3135 updateIntegrationScopeRect(for_integration);
3136 }
3137 else if(m_context.m_keyboardModifiers & Qt::AltModifier)
3138 {
3139 // The user might use the Alt modifier, but if no rhomboid side has
3140 // been defined using the S key, then we do not do any rhomboid
3141 // selection because we do not know the side size of that rhomboid.
3142
3145 updateIntegrationScopeRect(for_integration);
3146 else
3147 // qDebug()
3148 // << "Updating the integration scope to an
3149 // IntegrationScopeRhomb.";
3150 updateIntegrationScopeRhomb(for_integration);
3151 }
3152 }
3153
3154 // Depending on the kind of IntegrationScope, (normal, rect or rhomb)
3155 // we have to measure things in different ways. We now set in the context
3156 // a number of parameters that will be used by its user.
3157
3158 QPointF point;
3159 double height;
3160 std::vector<QPointF> points;
3161
3162 if(m_context.msp_integrationScope->getPoints(points))
3163 {
3164 // We have defined a IntegrationScopeRhomb.
3165
3166 if(!m_context.msp_integrationScope->getLeftMostPoint(point))
3167 qFatal("Failed to get LeftMost point.");
3168 m_context.m_xRegionRangeStart = point.x();
3169
3170 if(!m_context.msp_integrationScope->getRightMostPoint(point))
3171 qFatal("Failed to get RightMost point.");
3172 m_context.m_xRegionRangeEnd = point.x();
3173 }
3174 else if(m_context.msp_integrationScope->getHeight(height))
3175 {
3176 // We have defined a IntegrationScopeRect.
3177
3178 if(!m_context.msp_integrationScope->getPoint(point))
3179 qFatal("Failed to get point.");
3180 m_context.m_xRegionRangeStart = point.x();
3181
3182 double width;
3183
3184 if(!m_context.msp_integrationScope->getWidth(width))
3185 qFatal("Failed to get width.");
3186
3188
3189 m_context.m_yRegionRangeStart = point.y();
3190
3191 m_context.m_yRegionRangeEnd = point.y() + height;
3192 }
3193 else
3194 {
3195 // We have defined a IntegrationScope.
3196
3197 if(!m_context.msp_integrationScope->getPoint(point))
3198 qFatal("Failed to get point.");
3199 m_context.m_xRegionRangeStart = point.x();
3200
3201 double width;
3202
3203 if(!m_context.msp_integrationScope->getWidth(width))
3204 qFatal("Failed to get width.");
3206 }
3207
3208 // At this point, draw the text describing the widths.
3209
3210 // We want the x-delta on the bottom of the rectangle, inside it
3211 // and the y-delta on the vertical side of the rectangle, inside it.
3212
3213 // Draw the selection width text
3215}
3216
3217void
3219{
3220 mp_selectionRectangeLine1->setVisible(false);
3221 mp_selectionRectangeLine2->setVisible(false);
3222 mp_selectionRectangeLine3->setVisible(false);
3223 mp_selectionRectangeLine4->setVisible(false);
3224
3225 if(reset_values)
3226 {
3228 }
3229}
3230
3231
3232void
3234{
3235 std::const_pointer_cast<IntegrationScopeBase>(m_context.msp_integrationScope)
3236 ->reset();
3237}
3238
3241{
3242 // There are four lines that make the selection polygon. We want to know
3243 // which lines are visible.
3244
3245 int current_selection_polygon =
3246 static_cast<int>(SelectionDrawingLines::NOT_SET);
3247
3248 if(mp_selectionRectangeLine1->visible())
3249 {
3250 current_selection_polygon |=
3251 static_cast<int>(SelectionDrawingLines::TOP_LINE);
3252 // qDebug() << "current_selection_polygon:" <<
3253 // current_selection_polygon;
3254 }
3255 if(mp_selectionRectangeLine2->visible())
3256 {
3257 current_selection_polygon |=
3258 static_cast<int>(SelectionDrawingLines::RIGHT_LINE);
3259 // qDebug() << "current_selection_polygon:" <<
3260 // current_selection_polygon;
3261 }
3262 if(mp_selectionRectangeLine3->visible())
3263 {
3264 current_selection_polygon |=
3265 static_cast<int>(SelectionDrawingLines::BOTTOM_LINE);
3266 // qDebug() << "current_selection_polygon:" <<
3267 // current_selection_polygon;
3268 }
3269 if(mp_selectionRectangeLine4->visible())
3270 {
3271 current_selection_polygon |=
3272 static_cast<int>(SelectionDrawingLines::LEFT_LINE);
3273 // qDebug() << "current_selection_polygon:" <<
3274 // current_selection_polygon;
3275 }
3276
3277 // qDebug() << "returning visibility:" << current_selection_polygon;
3278
3279 return static_cast<SelectionDrawingLines>(current_selection_polygon);
3280}
3281
3282
3283bool
3285{
3286 // Sanity check
3287 int check = 0;
3288
3289 check += mp_selectionRectangeLine1->visible();
3290 check += mp_selectionRectangeLine2->visible();
3291 check += mp_selectionRectangeLine3->visible();
3292 check += mp_selectionRectangeLine4->visible();
3293
3294 if(check > 0)
3295 return true;
3296
3297 return false;
3298}
3299
3300
3301void
3303{
3304 // qDebug() << "Setting focus to the QCustomPlot:" << this;
3305
3306 QCustomPlot::setFocus();
3307
3308 // qDebug() << "Emitting setFocusSignal().";
3309
3310 emit setFocusSignal();
3311}
3312
3313
3314//! Redraw the background of the \p focusedPlotWidget plot widget.
3315void
3316BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3317{
3318 if(focusedPlotWidget == nullptr)
3320 "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3321 "-- "
3322 "ERROR focusedPlotWidget cannot be nullptr.");
3323
3324 if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3325 {
3326 // The focused widget is not *this widget. We should make sure that
3327 // we were not the one that had the focus, because in this case we
3328 // need to redraw an unfocused background.
3329
3330 axisRect()->setBackground(m_unfocusedBrush);
3331 }
3332 else
3333 {
3334 axisRect()->setBackground(m_focusedBrush);
3335 }
3336
3337 replot();
3338}
3339
3340
3341void
3343{
3344 m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3345 m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3346
3347 // qDebug() << "The new updated context: " << m_context.toString();
3348}
3349
3350
3351const BasePlotContext &
3353{
3354 return m_context;
3355}
3356
3357
3358} // namespace pappso
int basePlotContextPtrMetaTypeId
int basePlotContextMetaTypeId
Qt::MouseButtons m_mouseButtonsAtMousePress
IntegrationScopeBaseCstSPtr msp_integrationScope
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
virtual void updateIntegrationScopeRect(bool for_integration=false)
int m_mouseMoveHandlerSkipAmount
How many mouse move events must be skipped *‍/.
std::size_t m_lastAxisRangeHistoryIndex
Index of the last axis range history item.
virtual void updateAxesRangeHistory()
Create new axis range history items and append them to the history.
virtual void mouseWheelHandler(QWheelEvent *event)
bool m_shouldTracersBeVisible
Tells if the tracers should be visible.
virtual void hideSelectionRectangle(bool reset_values=false)
virtual void mouseMoveHandlerDraggingCursor()
virtual void updateIntegrationScopeDrawing(bool as_line_segment=false, bool for_integration=false)
virtual void directionKeyReleaseEvent(QKeyEvent *event)
QCPItemText * mp_yDeltaTextItem
QCPItemLine * mp_selectionRectangeLine1
Rectangle defining the borders of zoomed-in/out data.
virtual QCPRange getOutermostRangeX(bool &found_range) const
void lastCursorHoveredPointSignal(const QPointF &pointf)
void plottableDestructionRequestedSignal(BasePlotWidget *base_plot_widget_p, QCPAbstractPlottable *plottable_p, const BasePlotContext &context)
virtual const BasePlotContext & getContext() const
virtual void drawSelectionRectangleAndPrepareZoom(bool as_line_segment=false, bool for_integration=false)
virtual QCPRange getRangeY(bool &found_range, int index) const
virtual void keyPressEvent(QKeyEvent *event)
KEYBOARD-related EVENTS.
virtual ~BasePlotWidget()
Destruct this BasePlotWidget instance.
virtual void updateIntegrationScope(bool for_integration=false)
QCPItemLine * mp_selectionRectangeLine2
QCPItemText * mp_xDeltaTextItem
Text describing the x-axis delta value during a drag operation.
void mousePressEventSignal(const BasePlotContext &context)
virtual void setAxisLabelX(const QString &label)
virtual void mouseMoveHandlerLeftButtonDraggingCursor()
int m_mouseMoveHandlerSkipCount
Counter to handle the "fat data" mouse move event handling.
virtual QCPRange getOutermostRangeY(bool &found_range) const
int dragDirection()
MOUSE-related EVENTS.
bool isClickOntoYAxis(const QPointF &mousePoint)
virtual void moveMouseCursorPixelCoordToGlobal(QPointF local_coordinates)
QCPItemLine * mp_hPosTracerItem
Horizontal position tracer.
QCPItemLine * mp_vPosTracerItem
Vertical position tracer.
virtual 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 drawXScopeSpanFeatures()
virtual void spaceKeyReleaseEvent(QKeyEvent *event)
virtual void replotWithAxisRangeX(double lower, double upper)
virtual void createAllAncillaryItems()
virtual QColor getPlottingColor(QCPAbstractPlottable *plottable_p) const
virtual void mouseReleaseHandlerLeftButton()
QBrush m_focusedBrush
Color used for the background of focused plot.
QPen m_pen
Pen used to draw the graph and textual elements in the plot widget.
virtual bool isSelectionRectangleVisible()
virtual bool isVerticalDisplacementAboveThreshold()
virtual void mousePressHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void verticalMoveMouseCursorCountPixels(int pixel_count)
virtual void updateIntegrationScopeRhomb(bool for_integration=false)
void mouseWheelEventSignal(const BasePlotContext &context)
virtual void resetAxesRangeHistory()
virtual SelectionDrawingLines whatIsVisibleOfTheSelectionRectangle()
virtual void showTracers()
Show the traces (vertical and horizontal).
virtual QPointF horizontalGetGraphCoordNewPointCountPixels(int pixel_count)
QCPItemLine * mp_selectionRectangeLine4
virtual void horizontalMoveMouseCursorCountPixels(int pixel_count)
BasePlotWidget(QWidget *parent)
std::vector< QCPRange * > m_yAxisRangeHistory
List of y axis ranges occurring during the panning zooming actions.
virtual QCPRange getInnermostRangeY(bool &found_range) const
void mouseReleaseEventSignal(const BasePlotContext &context)
virtual void setFocus()
PLOT ITEMS : TRACER TEXT ITEMS...
virtual void drawYScopeSpanFeatures()
void keyReleaseEventSignal(const BasePlotContext &context)
virtual const QPen & getPen() const
virtual void updateContextXandYAxisRanges()
virtual void updateIntegrationScopeHorizontalRhomb(bool for_integration=false)
virtual void mousePseudoButtonKeyPressEvent(QKeyEvent *event)
virtual void setPlottingColor(QCPAbstractPlottable *plottable_p, const QColor &new_color)
virtual void calculateDragDeltas()
virtual QPointF verticalGetGraphCoordNewPointCountPixels(int pixel_count)
void plotRangesChangedSignal(const BasePlotContext &context)
QCPItemLine * mp_vStartTracerItem
Vertical selection start tracer (typically in green).
virtual void mouseReleaseHandler(QMouseEvent *event)
QBrush m_unfocusedBrush
Color used for the background of unfocused plot.
virtual void axisRescale()
RANGE-related functions.
virtual void moveMouseCursorGraphCoordToGlobal(QPointF plot_coordinates)
virtual QString allLayerNamesToString() const
QCPItemLine * mp_selectionRectangeLine3
virtual void axisDoubleClickHandler(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
virtual void mouseMoveHandlerRightButtonDraggingCursor()
QCPItemLine * mp_vEndTracerItem
Vertical selection end tracer (typically in red).
virtual void mouseMoveHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void directionKeyPressEvent(QKeyEvent *event)
virtual QString layerableLayerName(QCPLayerable *layerable_p) const
virtual void keyReleaseEvent(QKeyEvent *event)
Handle specific key codes and trigger respective actions.
virtual void resetSelectionRectangle()
virtual void restorePreviousAxesRangeHistory()
Go up one history element in the axis history.
virtual int layerableLayerIndex(QCPLayerable *layerable_p) const
void integrationRequestedSignal(const BasePlotContext &context)
void xAxisMeasurementSignal(const BasePlotContext &context, bool with_delta)
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 updateIntegrationScopeVerticalRhomb(bool for_integration=false)
virtual void mousePseudoButtonKeyReleaseEvent(QKeyEvent *event)
virtual void hideAllPlotItems()
PLOTTING / REPLOTTING functions.
virtual QCPRange getRangeX(bool &found_range, int index) const
MOUSE MOVEMENTS mouse/keyboard-triggered.
std::vector< QCPRange * > m_xAxisRangeHistory
List of x axis ranges occurring during the panning zooming actions.
BasePlotContext m_context
static int zeroDecimalsInValue(pappso_double value)
0.11 would return 0 (no empty decimal) 2.001 would return 2 1000.0001254 would return 3
Definition: utils.cpp:102
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition: aa.cpp:39
Axis
Definition: types.h:238
SelectionDrawingLines