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(this,
389  &QCustomPlot::axisDoubleClick,
390  this,
392 
393  return true;
394 }
395 
396 
397 void
398 BasePlotWidget::setPen(const QPen &pen)
399 {
400  m_pen = pen;
401 }
402 
403 
404 const QPen &
406 {
407  return m_pen;
408 }
409 
410 
411 void
412 BasePlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p,
413  const QColor &new_color)
414 {
415  if(plottable_p == nullptr)
416  qFatal("Pointer cannot be nullptr.");
417 
418  // First this single-graph widget
419  QPen pen;
420 
421  pen = plottable_p->pen();
422  pen.setColor(new_color);
423  plottable_p->setPen(pen);
424 
425  replot();
426 }
427 
428 
429 void
430 BasePlotWidget::setPlottingColor(int index, const QColor &new_color)
431 {
432  if(!new_color.isValid())
433  return;
434 
435  QCPGraph *graph_p = graph(index);
436 
437  if(graph_p == nullptr)
438  qFatal("Programming error.");
439 
440  return setPlottingColor(graph_p, new_color);
441 }
442 
443 
444 QColor
445 BasePlotWidget::getPlottingColor(QCPAbstractPlottable *plottable_p) const
446 {
447  if(plottable_p == nullptr)
448  qFatal("Programming error.");
449 
450  return plottable_p->pen().color();
451 }
452 
453 
454 QColor
456 {
457  QCPGraph *graph_p = graph(index);
458 
459  if(graph_p == nullptr)
460  qFatal("Programming error.");
461 
462  return getPlottingColor(graph_p);
463 }
464 
465 
466 void
467 BasePlotWidget::setAxisLabelX(const QString &label)
468 {
469  xAxis->setLabel(label);
470 }
471 
472 
473 void
474 BasePlotWidget::setAxisLabelY(const QString &label)
475 {
476  yAxis->setLabel(label);
477 }
478 
479 
480 // AXES RANGE HISTORY-related functions
481 void
483 {
484  m_xAxisRangeHistory.clear();
485  m_yAxisRangeHistory.clear();
486 
487  m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
488  m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
489 
490  // qDebug() << "size of history:" << m_xAxisRangeHistory.size()
491  //<< "setting index to 0";
492 
493  // qDebug() << "resetting axes history to values:" << xAxis->range().lower
494  //<< "--" << xAxis->range().upper << "and" << yAxis->range().lower
495  //<< "--" << yAxis->range().upper;
496 
498 }
499 
500 
501 //! Create new axis range history items and append them to the history.
502 /*!
503 
504  The plot widget is queried to get the current x/y-axis ranges and the
505  current ranges are appended to the history for x-axis and for y-axis.
506 
507 */
508 void
510 {
511  m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
512  m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
513 
515 
516  // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
517  //<< "current index:" << m_lastAxisRangeHistoryIndex
518  //<< xAxis->range().lower << "--" << xAxis->range().upper
519  //<< "and"
520  //<< yAxis->range().lower << "--" << yAxis->range().upper;
521 }
522 
523 
524 //! Go up one history element in the axis history.
525 /*!
526 
527  If possible, back up one history item in the axis histories and update the
528  plot's x/y-axis ranges to match that history item.
529 
530 */
531 void
533 {
534  // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
535  //<< "current index:" << m_lastAxisRangeHistoryIndex;
536 
538  {
539  // qDebug() << "current index is 0 returning doing nothing";
540 
541  return;
542  }
543 
544  // qDebug() << "setting index to:" << m_lastAxisRangeHistoryIndex - 1
545  //<< "and restoring axes history to that index";
546 
548 }
549 
550 
551 //! Get the axis histories at index \p index and update the plot ranges.
552 /*!
553 
554  \param index index at which to select the axis history item.
555 
556  \sa updateAxesRangeHistory().
557 
558 */
559 void
561 {
562  // qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
563  //<< "current index:" << m_lastAxisRangeHistoryIndex
564  //<< "asking to restore index:" << index;
565 
566  if(index >= m_xAxisRangeHistory.size())
567  {
568  // qDebug() << "index >= history size. Returning.";
569  return;
570  }
571 
572  xAxis->setRange(*(m_xAxisRangeHistory.at(index)));
573  yAxis->setRange(*(m_yAxisRangeHistory.at(index)));
574 
576 
577  mp_vPosTracerItem->setVisible(false);
578  mp_hPosTracerItem->setVisible(false);
579 
580  mp_vStartTracerItem->setVisible(false);
581  mp_vEndTracerItem->setVisible(false);
582 
583 
584  // The start trace will keep beeing represented at the last position and last
585  // size even if we call this function repetitively. So actually do not show,
586  // it will reappare as soon as the mouse is moved.
587  // if(m_shouldTracersBeVisible)
588  //{
589  // mp_vStartTracerItem->setVisible(true);
590  //}
591 
592  replot();
593 
595 
596  // qDebug() << "restored axes history to index:" << index
597  //<< "with values:" << xAxis->range().lower << "--"
598  //<< xAxis->range().upper << "and" << yAxis->range().lower << "--"
599  //<< yAxis->range().upper;
600 
602 }
603 // AXES RANGE HISTORY-related functions
604 
605 
606 /// KEYBOARD-related EVENTS
607 void
609 {
610  // qDebug() << "ENTER";
611 
612  // We need this because some keys modify our behaviour.
613  m_context.m_pressedKeyCode = event->key();
614  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
615 
616  if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
617  event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
618  {
619  return directionKeyPressEvent(event);
620  }
621  else if(event->key() == m_leftMousePseudoButtonKey ||
622  event->key() == m_rightMousePseudoButtonKey)
623  {
624  return mousePseudoButtonKeyPressEvent(event);
625  }
626 
627  // Do not do anything here, because this function is used by derived classes
628  // that will emit the signal below. Otherwise there are going to be multiple
629  // signals sent.
630  // qDebug() << "Going to emit keyPressEventSignal(m_context);";
631  // emit keyPressEventSignal(m_context);
632 }
633 
634 
635 //! Handle specific key codes and trigger respective actions.
636 void
638 {
639  m_context.m_releasedKeyCode = event->key();
640 
641  // The keyboard key is being released, set the key code to 0.
643 
644  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
645 
646  // Now test if the key that was released is one of the housekeeping keys.
647  if(event->key() == Qt::Key_Backspace)
648  {
649  // The user wants to iterate back in the x/y axis range history.
651 
652  event->accept();
653  }
654  else if(event->key() == Qt::Key_Space)
655  {
656  return spaceKeyReleaseEvent(event);
657  }
658  else if(event->key() == Qt::Key_Delete)
659  {
660  // The user wants to delete a graph. What graph is to be determined
661  // programmatically:
662 
663  // If there is a single graph, then that is the graph to be removed.
664  // If there are more than one graph, then only the ones that are selected
665  // are to be removed.
666 
667  // Note that the user of this widget might want to provide the user with
668  // the ability to specify if all the children graph needs to be removed
669  // also. This can be coded in key modifiers. So provide the context.
670 
671  int graph_count = plottableCount();
672 
673  if(!graph_count)
674  {
675  // qDebug() << "Not a single graph in the plot widget. Doing
676  // nothing.";
677 
678  event->accept();
679  return;
680  }
681 
682  if(graph_count == 1)
683  {
684  // qDebug() << "A single graph is in the plot widget. Emitting a graph
685  // " "destruction requested signal for it:"
686  //<< graph();
687 
688  emit plottableDestructionRequestedSignal(this, graph(), m_context);
689  }
690  else
691  {
692  // At this point we know there are more than one graph in the plot
693  // widget. We need to get the selected one (if any).
694  QList<QCPGraph *> selected_graph_list;
695 
696  selected_graph_list = selectedGraphs();
697 
698  if(!selected_graph_list.size())
699  {
700  event->accept();
701  return;
702  }
703 
704  // qDebug() << "Number of selected graphs to be destrobyed:"
705  //<< selected_graph_list.size();
706 
707  for(int iter = 0; iter < selected_graph_list.size(); ++iter)
708  {
709  // qDebug()
710  //<< "Emitting a graph destruction requested signal for graph:"
711  //<< selected_graph_list.at(iter);
712 
714  this, selected_graph_list.at(iter), m_context);
715 
716  // We do not do this, because we want the slot called by the
717  // signal above to handle that removal. Remember that it is not
718  // possible to delete graphs manually.
719  //
720  // removeGraph(selected_graph_list.at(iter));
721  }
722  event->accept();
723  }
724  }
725  // End of
726  // else if(event->key() == Qt::Key_Delete)
727  else if(event->key() == Qt::Key_T)
728  {
729  // The user wants to toggle the visibiity of the tracers.
731 
733  hideTracers();
734  else
735  showTracers();
736 
737  event->accept();
738  }
739  else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
740  event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
741  {
742  return directionKeyReleaseEvent(event);
743  }
744  else if(event->key() == m_leftMousePseudoButtonKey ||
745  event->key() == m_rightMousePseudoButtonKey)
746  {
747  return mousePseudoButtonKeyReleaseEvent(event);
748  }
749  else if(event->key() == Qt::Key_S)
750  {
751  // The user has asked to measure the horizontal size of the rectangle and
752  // to start making a skewed selection rectangle.
753 
756 
757  // qDebug() << "Set m_context.selectRectangleWidth to"
758  //<< m_context.m_selectRectangleWidth << "upon release of S key";
759  }
760 
761  // At this point emit the signal, since we did not treat it. Maybe the
762  // consumer widget wants to know that the keyboard key was released.
763 
765 }
766 
767 
768 void
769 BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
770 {
771  // qDebug();
772 }
773 
774 
775 void
777 {
778  // qDebug() << "event key:" << event->key();
779 
780  // The user is trying to move the positional cursor/markers. There are
781  // multiple way they can do that:
782  //
783  // 1.a. Hitting the arrow left/right keys alone will search for next pixel.
784  // 1.b. Hitting the arrow left/right keys with Alt modifier will search for a
785  // multiple of pixels that might be equivalent to one 20th of the pixel width
786  // of the plot widget.
787  // 1.c Hitting the left/right keys with Alt and Shift modifiers will search
788  // for a multiple of pixels that might be the equivalent to half of the pixel
789  // width.
790  //
791  // 2. Hitting the Control modifier will move the cursor to the next data point
792  // of the graph.
793 
794  int pixel_increment = 0;
795 
796  if(m_context.m_keyboardModifiers == Qt::NoModifier)
797  pixel_increment = 1;
798  else if(m_context.m_keyboardModifiers == Qt::AltModifier)
799  pixel_increment = 50;
800 
801  // The user is moving the positional markers. This is equivalent to a
802  // non-dragging cursor movement to the next pixel. Note that the origin is
803  // located at the top left, so key down increments and key up decrements.
804 
805  if(event->key() == Qt::Key_Left)
806  horizontalMoveMouseCursorCountPixels(-pixel_increment);
807  else if(event->key() == Qt::Key_Right)
808  horizontalMoveMouseCursorCountPixels(pixel_increment);
809  else if(event->key() == Qt::Key_Up)
810  verticalMoveMouseCursorCountPixels(-pixel_increment);
811  else if(event->key() == Qt::Key_Down)
812  verticalMoveMouseCursorCountPixels(pixel_increment);
813 
814  event->accept();
815 }
816 
817 
818 void
820 {
821  // qDebug() << "event key:" << event->key();
822  event->accept();
823 }
824 
825 
826 void
828  [[maybe_unused]] QKeyEvent *event)
829 {
830  // qDebug();
831 }
832 
833 
834 void
836 {
837 
838  QPointF pixel_coordinates(
839  xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
840  yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
841 
842  Qt::MouseButton button = Qt::NoButton;
843  QEvent::Type q_event_type = QEvent::MouseButtonPress;
844 
845  if(event->key() == m_leftMousePseudoButtonKey)
846  {
847  // Toggles the left mouse button on/off
848 
849  button = Qt::LeftButton;
850 
853 
855  q_event_type = QEvent::MouseButtonPress;
856  else
857  q_event_type = QEvent::MouseButtonRelease;
858  }
859  else if(event->key() == m_rightMousePseudoButtonKey)
860  {
861  // Toggles the right mouse button.
862 
863  button = Qt::RightButton;
864 
867 
869  q_event_type = QEvent::MouseButtonPress;
870  else
871  q_event_type = QEvent::MouseButtonRelease;
872  }
873 
874  // qDebug() << "pressed/released pseudo button:" << button
875  //<< "q_event_type:" << q_event_type;
876 
877  // Synthesize a QMouseEvent and use it.
878 
879  QMouseEvent *mouse_event_p =
880  new QMouseEvent(q_event_type,
881  pixel_coordinates,
882  mapToGlobal(pixel_coordinates.toPoint()),
883  mapToGlobal(pixel_coordinates.toPoint()),
884  button,
885  button,
887  Qt::MouseEventSynthesizedByApplication);
888 
889  if(q_event_type == QEvent::MouseButtonPress)
890  mousePressHandler(mouse_event_p);
891  else
892  mouseReleaseHandler(mouse_event_p);
893 
894  // event->accept();
895 }
896 /// KEYBOARD-related EVENTS
897 
898 
899 /// MOUSE-related EVENTS
900 
901 void
903 {
904 
905  // If we have no focus, then get it. See setFocus() to understand why asking
906  // for focus is cosly and thus why we want to make this decision first.
907  if(!hasFocus())
908  setFocus();
909 
910  // The event->button() must be by Qt instructions considered to be 0.
911 
912  // Whatever happens, we want to store the plot coordinates of the current
913  // mouse cursor position (will be useful later for countless needs).
914 
915  QPointF mousePoint = event->localPos();
916 
917  // qDebug() << "local mousePoint position in pixels:" << mousePoint;
918 
919  m_context.m_lastCursorHoveredPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
920  m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
921 
922  // qDebug() << "lastCursorHoveredPoint coord:"
923  //<< m_context.lastCursorHoveredPoint;
924 
925  // Now, depending on the button(s) (if any) that are pressed or not, we have
926  // a different processing.
927 
928  if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
929  m_context.m_pressedMouseButtons & Qt::RightButton)
931  else
933 
934  event->accept();
935 }
936 
937 
938 void
940 {
941 
943 
944  // We are not dragging the mouse (no button pressed), simply let this
945  // widget's consumer know the position of the cursor and update the markers.
946  // The consumer of this widget will update mouse cursor position at
947  // m_context.m_lastCursorHoveredPoint if so needed.
948 
950 
951  // We are not dragging, so we do not show the region end tracer we only show
952  // the anchoring start trace that might be of use if the user starts using
953  // the arrow keys to move the cursor.
954  mp_vEndTracerItem->setVisible(false);
955 
956  // Only bother with the tracers if the user wants them to be visible. Their
957  // crossing point must be exactly at the last cursor-hovered point.
958 
960  {
961  // We are not dragging, so only show the position markers (v and h);
962 
963  // Horizontal position tracer.
964  mp_hPosTracerItem->setVisible(true);
965  mp_hPosTracerItem->start->setCoords(
966  xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
967  mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
969 
970  // Vertical position tracer.
971  mp_vPosTracerItem->setVisible(true);
972 
973  mp_vPosTracerItem->setVisible(true);
974  mp_vPosTracerItem->start->setCoords(
975  m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
977  yAxis->range().lower);
978 
979  replot();
980  }
981 
982  return;
983 }
984 
985 
986 void
988 {
990 
991  // Now store the mouse position data into the the current drag point
992  // member datum, that will be used in countless occasions later.
994  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
995 
996  // When we drag (either keyboard or mouse), we hide the position markers
997  // (black) and we show the start and end vertical markers for the region.
998  // Then, we draw the horizontal region range marker that delimits
999  // horizontally the dragged-over region.
1000 
1001  mp_hPosTracerItem->setVisible(false);
1002  mp_vPosTracerItem->setVisible(false);
1003 
1004  // Only bother with the tracers if the user wants them to be visible.
1006  {
1007 
1008  // The vertical end tracer position must be refreshed.
1009  mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
1010  yAxis->range().upper);
1011 
1012  mp_vEndTracerItem->end->setCoords(m_context.m_currentDragPoint.x(),
1013  yAxis->range().lower);
1014 
1015  mp_vEndTracerItem->setVisible(true);
1016  }
1017 
1018  // Whatever the button, when we are dealing with the axes, we do not
1019  // want to show any of the tracers.
1020 
1022  {
1023  mp_hPosTracerItem->setVisible(false);
1024  mp_vPosTracerItem->setVisible(false);
1025 
1026  mp_vStartTracerItem->setVisible(false);
1027  mp_vEndTracerItem->setVisible(false);
1028  }
1029  else
1030  {
1031  // Since we are not dragging the mouse cursor over the axes, make sure we
1032  // store the drag directions in the context, as this might be useful for
1033  // later operations.
1034 
1036 
1037  // qDebug() << m_context.toString();
1038  }
1039 
1040  // Because when we drag the mouse button (whatever the button) we need to know
1041  // what is the drag delta (distance between start point and current point of
1042  // the drag operation) on both axes, ask that these x|y deltas be computed.
1044 
1045  // Now deal with the BUTTON-SPECIFIC CODE.
1046 
1047  if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
1048  {
1050  }
1051  else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
1052  {
1054  }
1055 }
1056 
1057 
1058 void
1060 {
1061  // qDebug() << "the left button is dragging.";
1062 
1063  // Set the context.m_isMeasuringDistance to false, which later might be set to
1064  // true if effectively we are measuring a distance. This is required because
1065  // the derived widget classes might want to know if they have to perform
1066  // some action on the basis that context is measuring a distance, for
1067  // example the mass spectrum-specific widget might want to compute
1068  // deconvolutions.
1069 
1071 
1072  // Let's first check if the mouse drag operation originated on either
1073  // axis. In that case, the user is performing axis reframing or rescaling.
1074 
1076  {
1077  // qDebug() << __FILE__ << __LINE__ << "Click was on one of the axes.";
1078 
1079  if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1080  {
1081  // The user is asking a rescale of the plot.
1082 
1083  // We know that we do not want the tracers when we perform axis
1084  // rescaling operations.
1085 
1086  mp_hPosTracerItem->setVisible(false);
1087  mp_vPosTracerItem->setVisible(false);
1088 
1089  mp_vStartTracerItem->setVisible(false);
1090  mp_vEndTracerItem->setVisible(false);
1091 
1092  // This operation is particularly intensive, thus we want to
1093  // reduce the number of calculations by skipping this calculation
1094  // a number of times. The user can ask for this feature by
1095  // clicking the 'Q' letter.
1096 
1097  if(m_context.m_pressedKeyCode == Qt::Key_Q)
1098  {
1100  {
1102  return;
1103  }
1104  else
1105  {
1107  }
1108  }
1109 
1110  // qDebug() << "Asking that the axes be rescaled.";
1111 
1112  axisRescale();
1113  }
1114  else
1115  {
1116  // The user was simply dragging the axis. Just pan, that is slide
1117  // the plot in the same direction as the mouse movement and with the
1118  // same amplitude.
1119 
1120  // qDebug() << "Asking that the axes be panned.";
1121 
1122  axisPan();
1123  }
1124 
1125  return;
1126  }
1127 
1128  // At this point we understand that the user was not performing any
1129  // panning/rescaling operation by clicking on any one of the axes.. Go on
1130  // with other possibilities.
1131 
1132  // Let's check if the user is actually drawing a rectangle (covering a
1133  // real area) or is drawing a line.
1134 
1135  // qDebug() << "The mouse dragging did not originate on an axis.";
1136 
1138  {
1139  // qDebug() << "Apparently the selection is a real rectangle.";
1140 
1141  // When we draw a rectangle the tracers are of no use.
1142 
1143  mp_hPosTracerItem->setVisible(false);
1144  mp_vPosTracerItem->setVisible(false);
1145 
1146  mp_vStartTracerItem->setVisible(false);
1147  mp_vEndTracerItem->setVisible(false);
1148 
1149  // Draw the rectangle, false, not as line segment and
1150  // false, not for integration
1152 
1153  // Draw the selection width/height text
1156 
1157  // qDebug() << "The selection polygon:"
1158  //<< m_context.m_selectionPolygon.toString();
1159  }
1160  else
1161  {
1162  // qDebug() << "Apparently we are measuring a delta.";
1163 
1164  // Draw the rectangle, true, as line segment and
1165  // false, not for integration
1167 
1168  // qDebug() << "The selection polygon:"
1169  //<< m_context.m_selectionPolygon.toString();
1170 
1171  // The pure position tracers should be hidden.
1172  mp_hPosTracerItem->setVisible(true);
1173  mp_vPosTracerItem->setVisible(true);
1174 
1175  // Then, make sure the region range vertical tracers are visible.
1176  mp_vStartTracerItem->setVisible(true);
1177  mp_vEndTracerItem->setVisible(true);
1178 
1179  // Draw the selection width text
1181  }
1182 }
1183 
1184 
1185 void
1187 {
1188  // qDebug() << "the right button is dragging.";
1189 
1190  // Set the context.m_isMeasuringDistance to false, which later might be set to
1191  // true if effectively we are measuring a distance. This is required because
1192  // the derived widgets might want to know if they have to perform some
1193  // action on the basis that context is measuring a distance, for example the
1194  // mass spectrum-specific widget might want to compute deconvolutions.
1195 
1197 
1199  {
1200  // qDebug() << "Apparently the selection is a real rectangle.";
1201 
1202  // When we draw a rectangle the tracers are of no use.
1203 
1204  mp_hPosTracerItem->setVisible(false);
1205  mp_vPosTracerItem->setVisible(false);
1206 
1207  mp_vStartTracerItem->setVisible(false);
1208  mp_vEndTracerItem->setVisible(false);
1209 
1210  // Draw the rectangle, false for as_line_segment and true, for
1211  // integration.
1213 
1214  // Draw the selection width/height text
1217  }
1218  else
1219  {
1220  // Draw the rectangle, true, as line segment and
1221  // false, true for integration
1223 
1224  // Draw the selection width text
1226  }
1227 
1228  // Draw the selection width text
1230 }
1231 
1232 
1233 void
1235 {
1236  // When the user clicks this widget it has to take focus.
1237  setFocus();
1238 
1239  QPointF mousePoint = event->localPos();
1240 
1241  m_context.m_lastPressedMouseButton = event->button();
1242  m_context.m_mouseButtonsAtMousePress = event->buttons();
1243 
1244  // The pressedMouseButtons must continually inform on the status of pressed
1245  // buttons so add the pressed button.
1246  m_context.m_pressedMouseButtons |= event->button();
1247 
1248  // qDebug().noquote() << m_context.toString();
1249 
1250  // In all the processing of the events, we need to know if the user is
1251  // clicking somewhere with the intent to change the plot ranges (reframing
1252  // or rescaling the plot).
1253  //
1254  // Reframing the plot means that the new x and y axes ranges are modified so
1255  // that they match the region that the user has encompassed by left clicking
1256  // the mouse and dragging it over the plot. That is we reframe the plot so
1257  // that it contains only the "selected" region.
1258  //
1259  // Rescaling the plot means the the new x|y axis range is modified such that
1260  // the lower axis range is constant and the upper axis range is moved either
1261  // left or right by the same amont as the x|y delta encompassed by the user
1262  // moving the mouse. The axis is thus either compressed (mouse movement is
1263  // leftwards) or un-compressed (mouse movement is rightwards).
1264 
1265  // There are two ways to perform axis range modifications:
1266  //
1267  // 1. By clicking on any of the axes
1268  // 2. By clicking on the plot region but using keyboard key modifiers, like
1269  // Alt and Ctrl.
1270  //
1271  // We need to know both cases separately which is why we need to perform a
1272  // number of tests below.
1273 
1274  // Let's check if the click is on the axes, either X or Y, because that
1275  // will allow us to take proper actions.
1276 
1277  if(isClickOntoXAxis(mousePoint))
1278  {
1279  // The X axis was clicked upon, we need to document that:
1280  // qDebug() << __FILE__ << __LINE__
1281  //<< "Layout element is axisRect and actually on an X axis part.";
1282 
1284 
1285  // int currentInteractions = interactions();
1286  // currentInteractions |= QCP::iRangeDrag;
1287  // setInteractions((QCP::Interaction)currentInteractions);
1288  // axisRect()->setRangeDrag(xAxis->orientation());
1289  }
1290  else
1291  m_context.m_wasClickOnXAxis = false;
1292 
1293  if(isClickOntoYAxis(mousePoint))
1294  {
1295  // The Y axis was clicked upon, we need to document that:
1296  // qDebug() << __FILE__ << __LINE__
1297  //<< "Layout element is axisRect and actually on an Y axis part.";
1298 
1300 
1301  // int currentInteractions = interactions();
1302  // currentInteractions |= QCP::iRangeDrag;
1303  // setInteractions((QCP::Interaction)currentInteractions);
1304  // axisRect()->setRangeDrag(yAxis->orientation());
1305  }
1306  else
1307  m_context.m_wasClickOnYAxis = false;
1308 
1309  // At this point, let's see if we need to remove the QCP::iRangeDrag bit:
1310 
1312  {
1313  // qDebug() << __FILE__ << __LINE__
1314  // << "Click outside of axes.";
1315 
1316  // int currentInteractions = interactions();
1317  // currentInteractions = currentInteractions & ~QCP::iRangeDrag;
1318  // setInteractions((QCP::Interaction)currentInteractions);
1319  }
1320 
1321  m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
1322  m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
1323 
1324  // Now install the vertical start tracer at the last cursor hovered
1325  // position.
1327  mp_vStartTracerItem->setVisible(true);
1328 
1330  yAxis->range().upper);
1332  yAxis->range().lower);
1333 
1334  replot();
1335 }
1336 
1337 
1338 void
1340 {
1341  // Now the real code of this function.
1342 
1343  m_context.m_lastReleasedMouseButton = event->button();
1344 
1345  // The event->buttons() is the description of the buttons that are pressed at
1346  // the moment the handler is invoked, that is now. If left and right were
1347  // pressed, and left was released, event->buttons() would be right.
1348  m_context.m_mouseButtonsAtMouseRelease = event->buttons();
1349 
1350  // The pressedMouseButtons must continually inform on the status of pressed
1351  // buttons so remove the released button.
1352  m_context.m_pressedMouseButtons ^= event->button();
1353 
1354  // qDebug().noquote() << m_context.toString();
1355 
1356  // We'll need to know if modifiers were pressed a the moment the user
1357  // released the mouse button.
1358  m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
1359 
1361  {
1362  // Let the user know that the mouse was *not* being dragged.
1363  m_context.m_wasMouseDragging = false;
1364 
1365  event->accept();
1366 
1367  return;
1368  }
1369 
1370  // Let the user know that the mouse was being dragged.
1372 
1373  // We cannot hide all items in one go because we rely on their visibility
1374  // to know what kind of dragging operation we need to perform (line-only
1375  // X-based zoom or rectangle-based X- and Y-based zoom, for example). The
1376  // only thing we know is that we can make the text invisible.
1377 
1378  // Same for the x delta text item
1379  mp_xDeltaTextItem->setVisible(false);
1380  mp_yDeltaTextItem->setVisible(false);
1381 
1382  // We do not show the end vertical region range marker.
1383  mp_vEndTracerItem->setVisible(false);
1384 
1385  // Horizontal position tracer.
1386  mp_hPosTracerItem->setVisible(true);
1387  mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
1389  mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
1391 
1392  // Vertical position tracer.
1393  mp_vPosTracerItem->setVisible(true);
1394 
1395  mp_vPosTracerItem->setVisible(true);
1397  yAxis->range().upper);
1399  yAxis->range().lower);
1400 
1401  // Force replot now because later that call might not be performed.
1402  replot();
1403 
1404  // If we were using the "quantum" display for the rescale of the axes
1405  // using the Ctrl-modified left button click drag in the axes, then reset
1406  // the count to 0.
1408 
1409  // Now that we have computed the useful ranges, we need to check what to do
1410  // depending on the button that was pressed.
1411 
1412  if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
1413  {
1415  }
1416  else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
1417  {
1419  }
1420 
1421  // By definition we are stopping the drag operation by releasing the mouse
1422  // button. Whatever that mouse button was pressed before and if there was
1423  // one pressed before. We cannot set that boolean value to false before
1424  // this place, because we call a number of routines above that need to know
1425  // that dragging was occurring. Like mouseReleaseHandledEvent(event) for
1426  // example.
1427 
1428  m_context.m_isMouseDragging = false;
1429 
1430  event->accept();
1431 
1432  return;
1433 }
1434 
1435 
1436 void
1438 {
1439 
1441  {
1442 
1443  // When the mouse move handler pans the plot, we cannot store each axes
1444  // range history element that would mean store a huge amount of such
1445  // elements, as many element as there are mouse move event handled by
1446  // the Qt event queue. But we can store an axis range history element
1447  // for the last situation of the mouse move: when the button is
1448  // released:
1449 
1451 
1453 
1454  replot();
1455 
1456  // Nothing else to do.
1457  return;
1458  }
1459 
1460  // There are two possibilities:
1461  //
1462  // 1. The full selection polygon (four lines) were currently drawn, which
1463  // means the user was willing to perform a zoom operation
1464  //
1465  // 2. Only the first top line was drawn, which means the user was dragging
1466  // the cursor horizontally. That might have two ends, as shown below.
1467 
1468  // So, first check what is drawn of the selection polygon.
1469 
1470  PolygonType current_selection_polygon_type =
1472 
1473  // Now that we know what was currently drawn of the selection polygon, we can
1474  // remove it. true to reset the values to 0.
1475  hideSelectionRectangle(true);
1476 
1477  // Force replot now because later that call might not be performed.
1478  replot();
1479 
1480  if(current_selection_polygon_type == PolygonType::FULL_POLYGON)
1481  {
1482  // qDebug() << "Yes, the full polygon was visible";
1483 
1484  // If we were dragging with the left button pressed and could draw a
1485  // rectangle, then we were preparing a zoom operation. Let's bring that
1486  // operation to its accomplishment.
1487 
1488  axisZoom();
1489 
1490  // qDebug() << "The selection polygon:"
1491  //<< m_context.m_selectionPolygon.toString();
1492 
1493  return;
1494  }
1495  else if(current_selection_polygon_type == PolygonType::TOP_LINE)
1496  {
1497  // qDebug() << "No, only the top line of the full polygon was visible";
1498 
1499  // The user was dragging the left mouse cursor and that may mean they were
1500  // measuring a distance or willing to perform a special zoom operation if
1501  // the Ctrl key was down.
1502 
1503  // If the user started by clicking in the plot region, dragged the mouse
1504  // cursor with the left button and pressed the Ctrl modifier, then that
1505  // means that they wanted to do a rescale over the x-axis in the form of a
1506  // reframing.
1507 
1508  if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1509  {
1510  return axisReframe();
1511 
1512  // qDebug() << "The selection polygon:"
1513  //<< m_context.m_selectionPolygon.toString();
1514  }
1515  }
1516  else
1517  qDebug() << "Another possibility.";
1518 }
1519 
1520 
1521 void
1523 {
1524 
1525  // The right button is used for the integrations. Not for axis range
1526  // operations. So all we have to do is remove the various graphics items and
1527  // send a signal with the context that contains all the data required by the
1528  // user to perform the integrations over the right plot regions.
1529 
1530  // Whatever we were doing we need to make the selection line invisible:
1531 
1532  if(mp_xDeltaTextItem->visible())
1533  mp_xDeltaTextItem->setVisible(false);
1534  if(mp_yDeltaTextItem->visible())
1535  mp_yDeltaTextItem->setVisible(false);
1536 
1537  // Also make the vertical end tracer invisible.
1538  mp_vEndTracerItem->setVisible(false);
1539 
1540  // Once the integration is asked for, then the selection rectangle if of no
1541  // more use.
1543 
1544  // Force replot now because later that call might not be performed.
1545  replot();
1546 
1547  // Note that we only request an integration if the x-axis delta is enough.
1548 
1549  double x_delta_pixel =
1550  fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1551  xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1552 
1553  if(x_delta_pixel > 3)
1555  // else
1556  // qDebug() << "Not asking for integration.";
1557 }
1558 
1559 
1560 void
1562  QCPAxis *axis,
1563  [[maybe_unused]] QCPAxis::SelectablePart part,
1564  QMouseEvent *event)
1565 {
1566  qDebug();
1567 
1568  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
1569 
1570  if(m_context.m_keyboardModifiers & Qt::ControlModifier)
1571  {
1572  qDebug();
1573 
1574  // If the Ctrl modifiers is active, then both axes are to be reset. Also
1575  // the histories are reset also.
1576 
1577  rescaleAxes();
1579  }
1580  else
1581  {
1582  qDebug();
1583  // Only the axis passed as parameter is to be rescaled.
1584  // Reset the range of that axis to the max view possible.
1585 
1586  axis->rescale();
1587 
1589 
1590  event->accept();
1591  }
1592 
1593  // The double-click event does not cancel the mouse press event. That is, if
1594  // left-double-clicking, at the end of the operation the button still
1595  // "pressed". We need to remove manually the button from the pressed buttons
1596  // context member.
1597 
1598  m_context.m_pressedMouseButtons ^= event->button();
1599 
1601 
1603 
1604  replot();
1605 }
1606 
1607 
1608 bool
1609 BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
1610 {
1611  QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1612 
1613  if(layoutElement &&
1614  layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1615  {
1616  // The graph is *inside* the axisRect that is the outermost envelope of
1617  // the graph. Thus, if we want to know if the click was indeed on an
1618  // axis, we need to check what selectable part of the the axisRect we
1619  // were
1620  // clicking:
1621  QCPAxis::SelectablePart selectablePart;
1622 
1623  selectablePart = xAxis->getPartAt(mousePoint);
1624 
1625  if(selectablePart == QCPAxis::spAxisLabel ||
1626  selectablePart == QCPAxis::spAxis ||
1627  selectablePart == QCPAxis::spTickLabels)
1628  return true;
1629  }
1630 
1631  return false;
1632 }
1633 
1634 
1635 bool
1636 BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
1637 {
1638  QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
1639 
1640  if(layoutElement &&
1641  layoutElement == dynamic_cast<QCPLayoutElement *>(axisRect()))
1642  {
1643  // The graph is *inside* the axisRect that is the outermost envelope of
1644  // the graph. Thus, if we want to know if the click was indeed on an
1645  // axis, we need to check what selectable part of the the axisRect we
1646  // were
1647  // clicking:
1648  QCPAxis::SelectablePart selectablePart;
1649 
1650  selectablePart = yAxis->getPartAt(mousePoint);
1651 
1652  if(selectablePart == QCPAxis::spAxisLabel ||
1653  selectablePart == QCPAxis::spAxis ||
1654  selectablePart == QCPAxis::spTickLabels)
1655  return true;
1656  }
1657 
1658  return false;
1659 }
1660 
1661 /// MOUSE-related EVENTS
1662 
1663 
1664 /// MOUSE MOVEMENTS mouse/keyboard-triggered
1665 
1666 int
1668 {
1669  // The user is dragging the mouse, probably to rescale the axes, but we need
1670  // to sort out in which direction the drag is happening.
1671 
1672  // This function should be called after calculateDragDeltas, so that
1673  // m_context has the proper x/y delta values that we'll compare.
1674 
1675  // Note that we cannot compare simply x or y deltas because the y axis might
1676  // have a different scale that the x axis. So we first need to convert the
1677  // positions to pixels.
1678 
1679  double x_delta_pixel =
1680  fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
1681  xAxis->coordToPixel(m_context.m_startDragPoint.x()));
1682 
1683  double y_delta_pixel =
1684  fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
1685  yAxis->coordToPixel(m_context.m_startDragPoint.y()));
1686 
1687  if(x_delta_pixel > y_delta_pixel)
1688  return Qt::Horizontal;
1689 
1690  return Qt::Vertical;
1691 }
1692 
1693 
1694 void
1696 {
1697  // First convert the graph coordinates to pixel coordinates.
1698 
1699  QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
1700  yAxis->coordToPixel(graph_coordinates.y()));
1701 
1702  moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
1703 }
1704 
1705 
1706 void
1708 {
1709  // qDebug() << "Calling set pos with new cursor position.";
1710  QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
1711 }
1712 
1713 
1714 void
1716 {
1717  QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
1718 
1719  QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1720  yAxis->coordToPixel(graph_coord.y()));
1721 
1722  // Now we need ton convert the new coordinates to the global position system
1723  // and to move the cursor to that new position. That will create an event to
1724  // move the mouse cursor.
1725 
1726  moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1727 }
1728 
1729 
1730 QPointF
1732 {
1733  QPointF pixel_coordinates(
1734  xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
1735  yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
1736 
1737  // Now convert back to local coordinates.
1738 
1739  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1740  yAxis->pixelToCoord(pixel_coordinates.y()));
1741 
1742  return graph_coordinates;
1743 }
1744 
1745 
1746 void
1748 {
1749 
1750  QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
1751 
1752  QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
1753  yAxis->coordToPixel(graph_coord.y()));
1754 
1755  // Now we need ton convert the new coordinates to the global position system
1756  // and to move the cursor to that new position. That will create an event to
1757  // move the mouse cursor.
1758 
1759  moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
1760 }
1761 
1762 
1763 QPointF
1765 {
1766  QPointF pixel_coordinates(
1767  xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
1768  yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
1769 
1770  // Now convert back to local coordinates.
1771 
1772  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
1773  yAxis->pixelToCoord(pixel_coordinates.y()));
1774 
1775  return graph_coordinates;
1776 }
1777 
1778 /// MOUSE MOVEMENTS mouse/keyboard-triggered
1779 
1780 
1781 /// RANGE-related functions
1782 
1783 QCPRange
1784 BasePlotWidget::getRangeX(bool &found_range, int index) const
1785 {
1786  QCPGraph *graph_p = graph(index);
1787 
1788  if(graph_p == nullptr)
1789  qFatal("Programming error.");
1790 
1791  return graph_p->getKeyRange(found_range);
1792 }
1793 
1794 
1795 QCPRange
1796 BasePlotWidget::getRangeY(bool &found_range, int index) const
1797 {
1798  QCPGraph *graph_p = graph(index);
1799 
1800  if(graph_p == nullptr)
1801  qFatal("Programming error.");
1802 
1803  return graph_p->getValueRange(found_range);
1804 }
1805 
1806 
1807 QCPRange
1809  RangeType range_type,
1810  bool &found_range) const
1811 {
1812 
1813  // Iterate in all the graphs in this widget and return a QCPRange that has
1814  // its lower member as the greatest lower value of all
1815  // its upper member as the smallest upper value of all
1816 
1817  if(!graphCount())
1818  {
1819  found_range = false;
1820 
1821  return QCPRange(0, 1);
1822  }
1823 
1824  if(graphCount() == 1)
1825  return graph()->getKeyRange(found_range);
1826 
1827  bool found_at_least_one_range = false;
1828 
1829  // Create an invalid range.
1830  QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
1831 
1832  for(int iter = 0; iter < graphCount(); ++iter)
1833  {
1834  QCPRange temp_range;
1835 
1836  bool found_range_for_iter = false;
1837 
1838  QCPGraph *graph_p = graph(iter);
1839 
1840  // Depending on the axis param, select the key or value range.
1841 
1842  if(axis == Axis::x)
1843  temp_range = graph_p->getKeyRange(found_range_for_iter);
1844  else if(axis == Axis::y)
1845  temp_range = graph_p->getValueRange(found_range_for_iter);
1846  else
1847  qFatal("Cannot reach this point. Programming error.");
1848 
1849  // Was a range found for the iterated graph ? If not skip this
1850  // iteration.
1851 
1852  if(!found_range_for_iter)
1853  continue;
1854 
1855  // While the innermost_range is invalid, we need to seed it with a good
1856  // one. So check this.
1857 
1858  if(!QCPRange::validRange(result_range))
1859  qFatal("The obtained range is invalid !");
1860 
1861  // At this point we know the obtained range is OK.
1862  result_range = temp_range;
1863 
1864  // We found at least one valid range!
1865  found_at_least_one_range = true;
1866 
1867  // At this point we have two valid ranges to compare. Depending on
1868  // range_type, we need to perform distinct comparisons.
1869 
1870  if(range_type == RangeType::innermost)
1871  {
1872  if(temp_range.lower > result_range.lower)
1873  result_range.lower = temp_range.lower;
1874  if(temp_range.upper < result_range.upper)
1875  result_range.upper = temp_range.upper;
1876  }
1877  else if(range_type == RangeType::outermost)
1878  {
1879  if(temp_range.lower < result_range.lower)
1880  result_range.lower = temp_range.lower;
1881  if(temp_range.upper > result_range.upper)
1882  result_range.upper = temp_range.upper;
1883  }
1884  else
1885  qFatal("Cannot reach this point. Programming error.");
1886 
1887  // Continue to next graph, if any.
1888  }
1889  // End of
1890  // for(int iter = 0; iter < graphCount(); ++iter)
1891 
1892  // Let the caller know if we found at least one range.
1893  found_range = found_at_least_one_range;
1894 
1895  return result_range;
1896 }
1897 
1898 
1899 QCPRange
1900 BasePlotWidget::getInnermostRangeX(bool &found_range) const
1901 {
1902 
1903  return getRange(Axis::x, RangeType::innermost, found_range);
1904 }
1905 
1906 
1907 QCPRange
1908 BasePlotWidget::getOutermostRangeX(bool &found_range) const
1909 {
1910  return getRange(Axis::x, RangeType::outermost, found_range);
1911 }
1912 
1913 
1914 QCPRange
1915 BasePlotWidget::getInnermostRangeY(bool &found_range) const
1916 {
1917 
1918  return getRange(Axis::y, RangeType::innermost, found_range);
1919 }
1920 
1921 
1922 QCPRange
1923 BasePlotWidget::getOutermostRangeY(bool &found_range) const
1924 {
1925  return getRange(Axis::y, RangeType::outermost, found_range);
1926 }
1927 
1928 
1929 /// RANGE-related functions
1930 
1931 
1932 /// PLOTTING / REPLOTTING functions
1933 
1934 void
1936 {
1937  // Get the current x lower/upper range, that is, leftmost/rightmost x
1938  // coordinate.
1939  double xLower = xAxis->range().lower;
1940  double xUpper = xAxis->range().upper;
1941 
1942  // Get the current y lower/upper range, that is, bottommost/topmost y
1943  // coordinate.
1944  double yLower = yAxis->range().lower;
1945  double yUpper = yAxis->range().upper;
1946 
1947  // This function is called only when the user has clicked on the x/y axis or
1948  // when the user has dragged the left mouse button with the Ctrl key
1949  // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
1950  // move handler. So we need to test which axis was clicked-on.
1951 
1953  {
1954 
1955  // We are changing the range of the X axis.
1956 
1957  // What is the x delta ?
1958  double xDelta =
1960 
1961  // If xDelta is < 0, the we were dragging from right to left, we are
1962  // compressing the view on the x axis, by adding new data to the right
1963  // hand size of the graph. So we add xDelta to the upper bound of the
1964  // range. Otherwise we are uncompressing the view on the x axis and
1965  // remove the xDelta from the upper bound of the range. This is why we
1966  // have the
1967  // '-'
1968  // and not '+' below;
1969 
1970  // qDebug() << "Setting xaxis:" << xLower << "--" << xUpper - xDelta;
1971 
1972  xAxis->setRange(xLower, xUpper - xDelta);
1973  }
1974  // End of
1975  // if(m_context.m_wasClickOnXAxis)
1976  else // that is, if(m_context.m_wasClickOnYAxis)
1977  {
1978  // We are changing the range of the Y axis.
1979 
1980  // What is the y delta ?
1981  double yDelta =
1983 
1984  // See above for an explanation of the computation.
1985 
1986  yAxis->setRange(yLower, yUpper - yDelta);
1987 
1988  // Old version
1989  // if(yDelta < 0)
1990  //{
1991  //// The dragging operation was from top to bottom, we are enlarging
1992  //// the range (thus, we are unzooming the view, since the widget
1993  //// always has the same size).
1994 
1995  // yAxis->setRange(yLower, yUpper + fabs(yDelta));
1996  //}
1997  // else
1998  //{
1999  //// The dragging operation was from bottom to top, we are reducing
2000  //// the range (thus, we are zooming the view, since the widget
2001  //// always has the same size).
2002 
2003  // yAxis->setRange(yLower, yUpper - fabs(yDelta));
2004  //}
2005  }
2006  // End of
2007  // else // that is, if(m_context.m_wasClickOnYAxis)
2008 
2009  // Update the context with the current axes ranges
2010 
2012 
2014 
2015  replot();
2016 }
2017 
2018 
2019 void
2021 {
2022 
2023  // double sorted_start_drag_point_x =
2024  // std::min(m_context.m_startDragPoint.x(), m_context.m_currentDragPoint.x());
2025 
2026  // xAxis->setRange(sorted_start_drag_point_x,
2027  // sorted_start_drag_point_x + fabs(m_context.m_xDelta));
2028 
2029  xAxis->setRange(
2031 
2032  // Note that the y axis should be rescaled from current lower value to new
2033  // upper value matching the y-axis position of the cursor when the mouse
2034  // button was released.
2035 
2036  yAxis->setRange(xAxis->range().lower,
2037  std::max<double>(m_context.m_yRegionRangeStart,
2039 
2040  // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
2041  // xAxis->range().upper
2042  //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
2043 
2045 
2048 
2049  replot();
2050 }
2051 
2052 
2053 void
2055 {
2056 
2057  // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
2058  // values before using them, because now we want to really have the lower x
2059  // value. Simply craft a QCPRange that will swap the values if lower is not
2060  // < than upper QCustomPlot calls this normalization).
2061 
2062  xAxis->setRange(
2064 
2065  yAxis->setRange(
2067 
2069 
2072 
2073  replot();
2074 }
2075 
2076 
2077 void
2079 {
2080  // Sanity check
2082  qFatal(
2083  "This function can only be called if the mouse click was on one of the "
2084  "axes");
2085 
2086  // First update the x&y axis ranges that we'll need for panning.
2088 
2090  {
2091  xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
2093  }
2094 
2096  {
2097  yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
2099  }
2100 
2101  // We cannot store the new ranges in the history, because the pan operation
2102  // involved a huge quantity of micro-movements elicited upon each mouse move
2103  // cursor event so we would have a huge history.
2104  // updateAxesRangeHistory();
2105 
2106  // Now that the contex has the right range values, we can emit the
2107  // signal that will be used by this plot widget users, typically to
2108  // abide by the x/y range lock required by the user.
2109 
2111 
2112  replot();
2113 }
2114 
2115 
2116 void
2118  QCPRange yAxisRange,
2119  Axis axis)
2120 {
2121  if(static_cast<int>(axis) & static_cast<int>(Axis::x))
2122  {
2123  xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
2124  }
2125 
2126  if(static_cast<int>(axis) & static_cast<int>(Axis::y))
2127  {
2128  yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
2129  }
2130 
2131  // We do not want to update the history, because there would be way too
2132  // much history items, since this function is called upon mouse moving
2133  // handling and not only during mouse release events.
2134  // updateAxesRangeHistory();
2135 
2136  replot();
2137 }
2138 
2139 
2140 void
2141 BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
2142 {
2143  // qDebug();
2144 
2145  xAxis->setRange(lower, upper);
2146 
2147  replot();
2148 }
2149 
2150 
2151 void
2152 BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
2153 {
2154  // qDebug();
2155 
2156  yAxis->setRange(lower, upper);
2157 
2158  replot();
2159 }
2160 
2161 /// PLOTTING / REPLOTTING functions
2162 
2163 
2164 /// PLOT ITEMS : TRACER TEXT ITEMS...
2165 
2166 //! Hide the selection line, the xDelta text and the zoom rectangle items.
2167 void
2169 {
2170  mp_xDeltaTextItem->setVisible(false);
2171  mp_yDeltaTextItem->setVisible(false);
2172 
2173  // mp_zoomRectItem->setVisible(false);
2175 
2176  // Force a replot to make sure the action is immediately visible by the
2177  // user, even without moving the mouse.
2178  replot();
2179 }
2180 
2181 
2182 //! Show the traces (vertical and horizontal).
2183 void
2185 {
2186  m_shouldTracersBeVisible = true;
2187 
2188  mp_vPosTracerItem->setVisible(true);
2189  mp_hPosTracerItem->setVisible(true);
2190 
2191  mp_vStartTracerItem->setVisible(true);
2192  mp_vEndTracerItem->setVisible(true);
2193 
2194  // Force a replot to make sure the action is immediately visible by the
2195  // user, even without moving the mouse.
2196  replot();
2197 }
2198 
2199 
2200 //! Hide the traces (vertical and horizontal).
2201 void
2203 {
2204  m_shouldTracersBeVisible = false;
2205  mp_hPosTracerItem->setVisible(false);
2206  mp_vPosTracerItem->setVisible(false);
2207 
2208  mp_vStartTracerItem->setVisible(false);
2209  mp_vEndTracerItem->setVisible(false);
2210 
2211  // Force a replot to make sure the action is immediately visible by the
2212  // user, even without moving the mouse.
2213  replot();
2214 }
2215 
2216 
2217 void
2219  bool for_integration)
2220 {
2221  // The user has dragged the mouse left button on the graph, which means he
2222  // is willing to draw a selection rectangle, either for zooming-in or for
2223  // integration.
2224 
2225  mp_xDeltaTextItem->setVisible(false);
2226  mp_yDeltaTextItem->setVisible(false);
2227 
2228  // Ensure the right selection rectangle is drawn.
2229 
2230  updateSelectionRectangle(as_line_segment, for_integration);
2231 
2232  // Note that if we draw a zoom rectangle, then we are certainly not
2233  // measuring anything. So set the boolean value to false so that the user of
2234  // this widget or derived classes know that there is nothing to perform upon
2235  // (like deconvolution, for example).
2236 
2238 
2239  // Also remove the delta value from the pipeline by sending a simple
2240  // distance without measurement signal.
2241 
2242  emit xAxisMeasurementSignal(m_context, false);
2243 
2244  replot();
2245 }
2246 
2247 
2248 void
2250 {
2251  // The user is dragging the mouse over the graph and we want them to know what
2252  // is the x delta value, that is the span between the point at the start of
2253  // the drag and the current drag position.
2254 
2255  // FIXME: is this still true?
2256  //
2257  // We do not want to show the position markers because the only horiontal
2258  // line to be visible must be contained between the start and end vertiacal
2259  // tracer items.
2260  mp_hPosTracerItem->setVisible(false);
2261  mp_vPosTracerItem->setVisible(false);
2262 
2263  // We want to draw the text in the middle position of the leftmost-rightmost
2264  // point, even with skewed rectangle selection.
2265 
2266  QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2267 
2268  // qDebug() << "leftmost_point:" << leftmost_point;
2269 
2270  QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2271 
2272  // qDebug() << "rightmost_point:" << rightmost_point;
2273 
2274  double x_axis_center_position =
2275  leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2276 
2277  // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2278 
2279  // We want the text to print inside the rectangle, always at the current drag
2280  // point so the eye can follow the delta value while looking where to drag the
2281  // mouse. To position the text inside the rectangle, we need to know what is
2282  // the drag direction.
2283 
2284  // Set aside a point instance to store the pixel coordinates of the text.
2285  QPointF pixel_coordinates;
2286 
2287  // What is the distance between the rectangle line at current drag point and
2288  // the text itself.
2289  int pixels_away_from_line = 15;
2290 
2291  // ATTENTION: the pixel coordinates for the vertical direction go in reverse
2292  // order with respect to the y axis values !!! That is pixel(0,0) is top left
2293  // of the graph.
2294  if(static_cast<int>(m_context.m_dragDirections) &
2295  static_cast<int>(DragDirections::TOP_TO_BOTTOM))
2296  {
2297  // We need to print inside the rectangle, that is pixels_above_line pixels
2298  // to the bottom, so with pixel y value decremented of that
2299  // pixels_above_line value (one would have expected to increment that
2300  // value, along the y axis, but the coordinates in pixel go in reverse
2301  // order).
2302 
2303  pixels_away_from_line *= -1;
2304  }
2305 
2306  double y_axis_pixel_coordinate =
2307  yAxis->coordToPixel(m_context.m_currentDragPoint.y());
2308 
2309  double y_axis_modified_pixel_coordinate =
2310  y_axis_pixel_coordinate + pixels_away_from_line;
2311 
2312  pixel_coordinates.setX(x_axis_center_position);
2313  pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
2314 
2315  // Now convert back to graph coordinates.
2316 
2317  QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
2318  yAxis->pixelToCoord(pixel_coordinates.y()));
2319  mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
2320  graph_coordinates.y());
2321  mp_xDeltaTextItem->setText(QString("%1").arg(m_context.m_xDelta, 0, 'f', 3));
2322  mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
2323  mp_xDeltaTextItem->setVisible(true);
2324 
2325  // Set the boolean to true so that derived widgets know that something is
2326  // being measured, and they can act accordingly, for example by computing
2327  // deconvolutions in a mass spectrum.
2329 
2330  replot();
2331 
2332  // Let the caller know that we were measuring something.
2333  emit xAxisMeasurementSignal(m_context, true);
2334 
2335  return;
2336 }
2337 
2338 
2339 void
2341 {
2343  return;
2344 
2345  // The user is dragging the mouse over the graph and we want them to know what
2346  // is the y delta value, that is the span between the point at the top of
2347  // the selection polygon and the point at its bottom.
2348 
2349  // FIXME: is this still true?
2350  //
2351  // We do not want to show the position markers because the only horiontal
2352  // line to be visible must be contained between the start and end vertiacal
2353  // tracer items.
2354  mp_hPosTracerItem->setVisible(false);
2355  mp_vPosTracerItem->setVisible(false);
2356 
2357  // We want to draw the text in the middle position of the leftmost-rightmost
2358  // point, even with skewed rectangle selection.
2359 
2360  QPointF leftmost_point = m_context.m_selectionPolygon.getLeftMostPoint();
2361  QPointF topmost_point = m_context.m_selectionPolygon.getTopMostPoint();
2362 
2363  // qDebug() << "leftmost_point:" << leftmost_point;
2364 
2365  QPointF rightmost_point = m_context.m_selectionPolygon.getRightMostPoint();
2366  QPointF bottommost_point = m_context.m_selectionPolygon.getBottomMostPoint();
2367 
2368  // qDebug() << "rightmost_point:" << rightmost_point;
2369 
2370  double x_axis_center_position =
2371  leftmost_point.x() + (rightmost_point.x() - leftmost_point.x()) / 2;
2372 
2373  double y_axis_center_position =
2374  bottommost_point.y() + (topmost_point.y() - bottommost_point.y()) / 2;
2375 
2376  // qDebug() << "x_axis_center_position:" << x_axis_center_position;
2377 
2378  mp_yDeltaTextItem->position->setCoords(x_axis_center_position,
2379  y_axis_center_position);
2380  mp_yDeltaTextItem->setText(QString("%1").arg(m_context.m_yDelta, 0, 'f', 3));
2381  mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
2382  mp_yDeltaTextItem->setVisible(true);
2383  mp_yDeltaTextItem->setRotation(90);
2384 
2385  // Set the boolean to true so that derived widgets know that something is
2386  // being measured, and they can act accordingly, for example by computing
2387  // deconvolutions in a mass spectrum.
2389 
2390  replot();
2391 
2392  // Let the caller know that we were measuring something.
2393  emit xAxisMeasurementSignal(m_context, true);
2394 }
2395 
2396 
2397 void
2399 {
2400 
2401  // We compute signed differentials. If the user does not want the sign,
2402  // fabs(double) is their friend.
2403 
2404  // Compute the xAxis differential:
2405 
2408 
2409  // Same with the Y-axis range:
2410 
2413 
2414  // qDebug() << "xDelta:" << m_context.m_xDelta
2415  //<< "and yDelta:" << m_context.m_yDelta;
2416 
2417  return;
2418 }
2419 
2420 
2421 bool
2423 {
2424  // First get the height of the plot.
2425  double plotHeight = yAxis->range().upper - yAxis->range().lower;
2426 
2427  double heightDiff =
2429 
2430  double heightDiffRatio = (heightDiff / plotHeight) * 100;
2431 
2432  if(heightDiffRatio > 10)
2433  {
2434  // qDebug() << "isVerticalDisplacementAboveThreshold: true";
2435  return true;
2436  }
2437 
2438  // qDebug() << "isVerticalDisplacementAboveThreshold: false";
2439  return false;
2440 }
2441 
2442 
2443 void
2445 {
2446 
2447  // if(for_integration)
2448  // qDebug() << "for_integration:" << for_integration;
2449 
2450  // When we make a linear selection, the selection polygon is a polygon that
2451  // has the following characteristics:
2452  //
2453  // the x range is the linear selection span
2454  //
2455  // the y range is the widest std::min -> std::max possible.
2456 
2457  // This is how the selection polygon logic knows if its is mono-
2458  // two-dimensional.
2459 
2460  // We want the top left point to effectively be the top left point, so check
2461  // the direction of the mouse cursor drag.
2462 
2463  double x_range_start =
2465  double x_range_end =
2467 
2468  double y_position = m_context.m_startDragPoint.y();
2469 
2470  m_context.m_selectionPolygon.set1D(x_range_start, x_range_end);
2471 
2472  // Top line
2473  mp_selectionRectangeLine1->start->setCoords(
2474  QPointF(x_range_start, y_position));
2475  mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
2476 
2477  // Only if we are drawing a selection rectangle for integration, do we set
2478  // arrow heads to the line.
2479  if(for_integration)
2480  {
2481  mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2482  mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2483  }
2484  else
2485  {
2486  mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2487  mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2488  }
2489  mp_selectionRectangeLine1->setVisible(true);
2490 
2491  // Right line: does not exist, start and end are the same end point of the top
2492  // line.
2493  mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
2494  mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
2495  mp_selectionRectangeLine2->setVisible(false);
2496 
2497  // Bottom line: identical to the top line, but invisible
2498  mp_selectionRectangeLine3->start->setCoords(
2499  QPointF(x_range_start, y_position));
2500  mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
2501  mp_selectionRectangeLine3->setVisible(false);
2502 
2503  // Left line: does not exist: start and end are the same end point of the top
2504  // line.
2505  mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
2506  mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
2507  mp_selectionRectangeLine4->setVisible(false);
2508 }
2509 
2510 
2511 void
2513 {
2514 
2515  // if(for_integration)
2516  // qDebug() << "for_integration:" << for_integration;
2517 
2518  // We are handling a conventional rectangle. Just create four points
2519  // from top left to bottom right. But we want the top left point to be
2520  // effectively the top left point and the bottom point to be the bottom point.
2521  // So we need to try all four direction combinations, left to right or
2522  // converse versus top to bottom or converse.
2523 
2525 
2527  {
2528  // qDebug() << "Dragging from right to left";
2529 
2531  {
2532  // qDebug() << "Dragging from top to bottom";
2533 
2534  // TOP_LEFT_POINT
2539 
2540  // TOP_RIGHT_POINT
2544 
2545  // BOTTOM_RIGHT_POINT
2550 
2551  // BOTTOM_LEFT_POINT
2556  }
2557  // End of
2558  // if(m_context.m_currentDragPoint.y() < m_context.m_startDragPoint.y())
2559  else
2560  {
2561  // qDebug() << "Dragging from bottom to top";
2562 
2563  // TOP_LEFT_POINT
2568 
2569  // TOP_RIGHT_POINT
2574 
2575  // BOTTOM_RIGHT_POINT
2579 
2580  // BOTTOM_LEFT_POINT
2585  }
2586  }
2587  // End of
2588  // if(m_context.m_currentDragPoint.x() < m_context.m_startDragPoint.x())
2589  else
2590  {
2591  // qDebug() << "Dragging from left to right";
2592 
2594  {
2595  // qDebug() << "Dragging from top to bottom";
2596 
2597  // TOP_LEFT_POINT
2601 
2602  // TOP_RIGHT_POINT
2607 
2608  // BOTTOM_RIGHT_POINT
2613 
2614  // BOTTOM_LEFT_POINT
2619  }
2620  else
2621  {
2622  // qDebug() << "Dragging from bottom to top";
2623 
2624  // TOP_LEFT_POINT
2629 
2630  // TOP_RIGHT_POINT
2635 
2636  // BOTTOM_RIGHT_POINT
2641 
2642  // BOTTOM_LEFT_POINT
2646  }
2647  }
2648 
2649  // qDebug() << "Now draw the lines with points:"
2650  //<< m_context.m_selectionPolygon.toString();
2651 
2652  // Top line
2653  mp_selectionRectangeLine1->start->setCoords(
2655  mp_selectionRectangeLine1->end->setCoords(
2657 
2658  // Only if we are drawing a selection rectangle for integration, do we
2659  // set arrow heads to the line.
2660  if(for_integration)
2661  {
2662  mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2663  mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2664  }
2665  else
2666  {
2667  mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2668  mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2669  }
2670 
2671  mp_selectionRectangeLine1->setVisible(true);
2672 
2673  // Right line
2674  mp_selectionRectangeLine2->start->setCoords(
2676  mp_selectionRectangeLine2->end->setCoords(
2678  mp_selectionRectangeLine2->setVisible(true);
2679 
2680  // Bottom line
2681  mp_selectionRectangeLine3->start->setCoords(
2683  mp_selectionRectangeLine3->end->setCoords(
2685  mp_selectionRectangeLine3->setVisible(true);
2686 
2687  // Left line
2688  mp_selectionRectangeLine4->start->setCoords(
2690  mp_selectionRectangeLine4->end->setCoords(
2692  mp_selectionRectangeLine4->setVisible(true);
2693 }
2694 
2695 
2696 void
2698 {
2699 
2700  // if(for_integration)
2701  // qDebug() << "for_integration:" << for_integration;
2702 
2703  // We are handling a skewed rectangle, that is a rectangle that is
2704  // tilted either to the left or to the right.
2705 
2706  // qDebug() << "m_context.m_selectRectangleWidth: "
2707  //<< m_context.m_selectRectangleWidth;
2708 
2709  // Top line
2710  // start
2711 
2712  // qDebug() << "m_context.m_startDragPoint: " <<
2713  // m_context.m_startDragPoint.x()
2714  //<< "-" << m_context.m_startDragPoint.y();
2715 
2716  // qDebug() << "m_context.m_currentDragPoint: "
2717  //<< m_context.m_currentDragPoint.x() << "-"
2718  //<< m_context.m_currentDragPoint.y();
2719 
2721 
2723  {
2724  // qDebug() << "Dragging from right to left";
2725 
2727  {
2728  // qDebug() << "Dragging from top to bottom";
2729 
2734 
2735  // m_context.m_selRectTopLeftPoint.setX(
2736  // m_context.m_startDragPoint.x() -
2737  // m_context.m_selectRectangleWidth);
2738  // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2739 
2743 
2744  // m_context.m_selRectTopRightPoint.setX(m_context.m_startDragPoint.x());
2745  // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2746 
2751 
2752  // m_context.m_selRectBottomRightPoint.setX(
2753  // m_context.m_currentDragPoint.x() +
2754  // m_context.m_selectRectangleWidth);
2755  // m_context.m_selRectBottomRightPoint.setY(
2756  // m_context.m_currentDragPoint.y());
2757 
2762 
2763  // m_context.m_selRectBottomLeftPoint.setX(
2764  // m_context.m_currentDragPoint.x());
2765  // m_context.m_selRectBottomLeftPoint.setY(
2766  // m_context.m_currentDragPoint.y());
2767  }
2768  else
2769  {
2770  // qDebug() << "Dragging from bottom to top";
2771 
2776 
2777  // m_context.m_selRectTopLeftPoint.setX(
2778  // m_context.m_currentDragPoint.x());
2779  // m_context.m_selRectTopLeftPoint.setY(
2780  // m_context.m_currentDragPoint.y());
2781 
2786 
2787  // m_context.m_selRectTopRightPoint.setX(
2788  // m_context.m_currentDragPoint.x() +
2789  // m_context.m_selectRectangleWidth);
2790  // m_context.m_selRectTopRightPoint.setY(
2791  // m_context.m_currentDragPoint.y());
2792 
2793 
2797 
2798  // m_context.m_selRectBottomRightPoint.setX(
2799  // m_context.m_startDragPoint.x());
2800  // m_context.m_selRectBottomRightPoint.setY(
2801  // m_context.m_startDragPoint.y());
2802 
2807 
2808  // m_context.m_selRectBottomLeftPoint.setX(
2809  // m_context.m_startDragPoint.x() -
2810  // m_context.m_selectRectangleWidth);
2811  // m_context.m_selRectBottomLeftPoint.setY(
2812  // m_context.m_startDragPoint.y());
2813  }
2814  }
2815  // End of
2816  // Dragging from right to left.
2817  else
2818  {
2819  // qDebug() << "Dragging from left to right";
2820 
2822  {
2823  // qDebug() << "Dragging from top to bottom";
2824 
2828 
2829  // m_context.m_selRectTopLeftPoint.setX(m_context.m_startDragPoint.x());
2830  // m_context.m_selRectTopLeftPoint.setY(m_context.m_startDragPoint.y());
2831 
2836 
2837  // m_context.m_selRectTopRightPoint.setX(
2838  // m_context.m_startDragPoint.x() +
2839  // m_context.m_selectRectangleWidth);
2840  // m_context.m_selRectTopRightPoint.setY(m_context.m_startDragPoint.y());
2841 
2846 
2847  // m_context.m_selRectBottomRightPoint.setX(
2848  // m_context.m_currentDragPoint.x());
2849  // m_context.m_selRectBottomRightPoint.setY(
2850  // m_context.m_currentDragPoint.y());
2851 
2856 
2857  // m_context.m_selRectBottomLeftPoint.setX(
2858  // m_context.m_currentDragPoint.x() -
2859  // m_context.m_selectRectangleWidth);
2860  // m_context.m_selRectBottomLeftPoint.setY(
2861  // m_context.m_currentDragPoint.y());
2862  }
2863  else
2864  {
2865  // qDebug() << "Dragging from bottom to top";
2866 
2871 
2872  // m_context.m_selRectTopLeftPoint.setX(
2873  // m_context.m_currentDragPoint.x() -
2874  // m_context.m_selectRectangleWidth);
2875  // m_context.m_selRectTopLeftPoint.setY(
2876  // m_context.m_currentDragPoint.y());
2877 
2882 
2883  // m_context.m_selRectTopRightPoint.setX(
2884  // m_context.m_currentDragPoint.x());
2885  // m_context.m_selRectTopRightPoint.setY(
2886  // m_context.m_currentDragPoint.y());
2887 
2892 
2893  // m_context.m_selRectBottomRightPoint.setX(
2894  // m_context.m_startDragPoint.x() +
2895  // m_context.m_selectRectangleWidth);
2896  // m_context.m_selRectBottomRightPoint.setY(
2897  // m_context.m_startDragPoint.y());
2898 
2902 
2903  // m_context.m_selRectBottomLeftPoint.setX(
2904  // m_context.m_startDragPoint.x());
2905  // m_context.m_selRectBottomLeftPoint.setY(
2906  // m_context.m_startDragPoint.y());
2907  }
2908  }
2909  // End of Dragging from left to right.
2910 
2911  // qDebug() << "Now draw the lines with points:"
2912  //<< m_context.m_selectionPolygon.toString();
2913 
2914  // Top line
2915  mp_selectionRectangeLine1->start->setCoords(
2917  mp_selectionRectangeLine1->end->setCoords(
2919 
2920  // Only if we are drawing a selection rectangle for integration, do we set
2921  // arrow heads to the line.
2922  if(for_integration)
2923  {
2924  mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
2925  mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
2926  }
2927  else
2928  {
2929  mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
2930  mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
2931  }
2932 
2933  mp_selectionRectangeLine1->setVisible(true);
2934 
2935  // Right line
2936  mp_selectionRectangeLine2->start->setCoords(
2938  mp_selectionRectangeLine2->end->setCoords(
2940  mp_selectionRectangeLine2->setVisible(true);
2941 
2942  // Bottom line
2943  mp_selectionRectangeLine3->start->setCoords(
2945  mp_selectionRectangeLine3->end->setCoords(
2947  mp_selectionRectangeLine3->setVisible(true);
2948 
2949  // Left line
2950  mp_selectionRectangeLine4->end->setCoords(
2952  mp_selectionRectangeLine4->start->setCoords(
2954  mp_selectionRectangeLine4->setVisible(true);
2955 }
2956 
2957 
2958 void
2960  bool for_integration)
2961 {
2962 
2963  // qDebug() << "as_line_segment:" << as_line_segment;
2964  // qDebug() << "for_integration:" << for_integration;
2965 
2966  // We now need to construct the selection rectangle, either for zoom or for
2967  // integration.
2968 
2969  // There are two situations :
2970  //
2971  // 1. if the rectangle should look like a line segment
2972  //
2973  // 2. if the rectangle should actually look like a rectangle. In this case,
2974  // there are two sub-situations:
2975  //
2976  // a. if the S key is down, then the rectangle is
2977  // skewed, that is its vertical sides are not parallel to the y axis.
2978  //
2979  // b. otherwise the rectangle is conventional.
2980 
2981  if(as_line_segment)
2982  {
2983  update1DSelectionRectangle(for_integration);
2984  }
2985  else
2986  {
2987  if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
2988  {
2989  update2DSelectionRectangleSquare(for_integration);
2990  }
2991  else if(m_context.m_keyboardModifiers & Qt::AltModifier)
2992  {
2993  update2DSelectionRectangleSkewed(for_integration);
2994  }
2995  }
2996 
2997  // This code automatically sorts the ranges (range start is always less than
2998  // range end) even if the user actually selects from high to low (right to
2999  // left or bottom to top). This has implications in code that uses the
3000  // m_context data to perform some computations. This is why it is important
3001  // that m_dragDirections be set correctly to establish where the current drag
3002  // point is actually located (at which point).
3003 
3008 
3013 
3014  // At this point, draw the text describing the widths.
3015 
3016  // We want the x-delta on the bottom of the rectangle, inside it
3017  // and the y-delta on the vertical side of the rectangle, inside it.
3018 
3019  // Draw the selection width text
3021 }
3022 
3023 void
3025 {
3026  mp_selectionRectangeLine1->setVisible(false);
3027  mp_selectionRectangeLine2->setVisible(false);
3028  mp_selectionRectangeLine3->setVisible(false);
3029  mp_selectionRectangeLine4->setVisible(false);
3030 
3031  if(reset_values)
3032  {
3034  }
3035 }
3036 
3037 
3038 void
3040 {
3042 }
3043 
3044 
3047 {
3048  // There are four lines that make the selection polygon. We want to know
3049  // which lines are visible.
3050 
3051  int current_selection_polygon = static_cast<int>(PolygonType::NOT_SET);
3052 
3053  if(mp_selectionRectangeLine1->visible())
3054  {
3055  current_selection_polygon |= static_cast<int>(PolygonType::TOP_LINE);
3056  // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3057  }
3058  if(mp_selectionRectangeLine2->visible())
3059  {
3060  current_selection_polygon |= static_cast<int>(PolygonType::RIGHT_LINE);
3061  // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3062  }
3063  if(mp_selectionRectangeLine3->visible())
3064  {
3065  current_selection_polygon |= static_cast<int>(PolygonType::BOTTOM_LINE);
3066  // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3067  }
3068  if(mp_selectionRectangeLine4->visible())
3069  {
3070  current_selection_polygon |= static_cast<int>(PolygonType::LEFT_LINE);
3071  // qDebug() << "current_selection_polygon:" << current_selection_polygon;
3072  }
3073 
3074  // qDebug() << "returning visibility:" << current_selection_polygon;
3075 
3076  return static_cast<PolygonType>(current_selection_polygon);
3077 }
3078 
3079 
3080 bool
3082 {
3083  // Sanity check
3084  int check = 0;
3085 
3086  check += mp_selectionRectangeLine1->visible();
3087  check += mp_selectionRectangeLine2->visible();
3088  check += mp_selectionRectangeLine3->visible();
3089  check += mp_selectionRectangeLine4->visible();
3090 
3091  if(check > 0)
3092  return true;
3093 
3094  return false;
3095 }
3096 
3097 
3098 void
3100 {
3101  // qDebug() << "Setting focus to the QCustomPlot:" << this;
3102 
3103  QCustomPlot::setFocus();
3104 
3105  // qDebug() << "Emitting setFocusSignal().";
3106 
3107  emit setFocusSignal();
3108 }
3109 
3110 
3111 //! Redraw the background of the \p focusedPlotWidget plot widget.
3112 void
3113 BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
3114 {
3115  if(focusedPlotWidget == nullptr)
3116  throw ExceptionNotPossible(
3117  "baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
3118  "-- "
3119  "ERROR focusedPlotWidget cannot be nullptr.");
3120 
3121  if(dynamic_cast<QWidget *>(this) != focusedPlotWidget)
3122  {
3123  // The focused widget is not *this widget. We should make sure that
3124  // we were not the one that had the focus, because in this case we
3125  // need to redraw an unfocused background.
3126 
3127  axisRect()->setBackground(m_unfocusedBrush);
3128  }
3129  else
3130  {
3131  axisRect()->setBackground(m_focusedBrush);
3132  }
3133 
3134  replot();
3135 }
3136 
3137 
3138 void
3140 {
3141  m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
3142  m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
3143 }
3144 
3145 
3146 const BasePlotContext &
3148 {
3149  return m_context;
3150 }
3151 
3152 
3153 } // 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.
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 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)
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 replotWithAxesRanges(QCPRange xAxisRange, QCPRange yAxisRange, Axis whichAxis)
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:180