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