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