1 /***************************************************************************
2     File                 : DataPickerTool.cpp
3     Project              : SciDAVis
4     --------------------------------------------------------------------
5     Copyright            : (C) 2006,2007 by Ion Vasilief,
6                            Tilman Benkert, Knut Franke
7     Email (use @ for *)  : ion_vasilief*yahoo.fr, thzs*gmx.net,
8                            knut.franke*gmx.de
9     Description          : Plot tool for selecting points on curves.
10 
11  ***************************************************************************/
12 
13 /***************************************************************************
14  *                                                                         *
15  *  This program is free software; you can redistribute it and/or modify   *
16  *  it under the terms of the GNU General Public License as published by   *
17  *  the Free Software Foundation; either version 2 of the License, or      *
18  *  (at your option) any later version.                                    *
19  *                                                                         *
20  *  This program is distributed in the hope that it will be useful,        *
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
23  *  GNU General Public License for more details.                           *
24  *                                                                         *
25  *   You should have received a copy of the GNU General Public License     *
26  *   along with this program; if not, write to the Free Software           *
27  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
28  *   Boston, MA  02110-1301  USA                                           *
29  *                                                                         *
30  ***************************************************************************/
31 #include "DataPickerTool.h"
32 #include "Graph.h"
33 #include "Plot.h"
34 #include "FunctionCurve.h"
35 #include "PlotCurve.h"
36 #include "QwtErrorPlotCurve.h"
37 #include "ApplicationWindow.h"
38 #include "core/column/Column.h"
39 
40 #include <qwt_symbol.h>
41 #include <qwt_plot_picker.h>
42 #include <qwt_plot_curve.h>
43 #include <qwt_scale_draw.h>
44 #include <QMessageBox>
45 #include <QLocale>
46 #include <QKeyEvent>
47 #include <QMouseEvent>
48 
DataPickerTool(Graph * graph,ApplicationWindow * app,Mode mode,const QObject * status_target,const char * status_slot)49 DataPickerTool::DataPickerTool(Graph *graph, ApplicationWindow *app, Mode mode,
50                                const QObject *status_target, const char *status_slot)
51     : QwtPlotPicker(graph->plotWidget()->canvas()),
52       PlotToolInterface(graph),
53       d_app(app),
54       d_mode(mode),
55       d_move_mode(Free)
56 {
57     d_selected_curve = NULL;
58 
59     d_selection_marker.setSymbol(QwtSymbol(QwtSymbol::Ellipse, QBrush(QColor(255, 255, 0, 128)),
60                                            QPen(Qt::black, 2), QSize(20, 20)));
61     d_selection_marker.setLineStyle(QwtPlotMarker::Cross);
62     d_selection_marker.setLinePen(QPen(Qt::red, 1));
63 
64     setTrackerMode(QwtPicker::AlwaysOn);
65     if (d_mode == Move) {
66         setSelectionFlags(QwtPicker::PointSelection | QwtPicker::DragSelection);
67         d_graph->plotWidget()->canvas()->setCursor(Qt::PointingHandCursor);
68     } else {
69         setSelectionFlags(QwtPicker::PointSelection | QwtPicker::ClickSelection);
70         d_graph->plotWidget()->canvas()->setCursor(QCursor(QPixmap(":/vizor.xpm"), -1, -1));
71     }
72 
73     if (status_target)
74         connect(this, SIGNAL(statusText(const QString &)), status_target, status_slot);
75     switch (d_mode) {
76     case Display:
77         emit statusText(tr("Click on plot or move cursor to display coordinates!"));
78         break;
79     case Move:
80         emit statusText(tr("Please, click on plot and move cursor!"));
81         break;
82     case Remove:
83         emit statusText(tr("Select point and double click to remove it!"));
84         break;
85     }
86 }
87 
~DataPickerTool()88 DataPickerTool::~DataPickerTool()
89 {
90     d_selection_marker.detach();
91     d_graph->plotWidget()->canvas()->unsetCursor();
92 }
93 
append(const QPoint & pos)94 void DataPickerTool::append(const QPoint &pos)
95 {
96     int dist, point_index;
97     const int curve = d_graph->plotWidget()->closestCurve(pos.x(), pos.y(), dist, point_index);
98     if (curve <= 0 || dist >= 5) { // 5 pixels tolerance
99         setSelection(NULL, 0);
100         return;
101     }
102     setSelection((QwtPlotCurve *)d_graph->plotWidget()->curve(curve), point_index);
103     if (!d_selected_curve)
104         return;
105 
106     QwtPlotPicker::append(transform(QwtDoublePoint(d_selected_curve->x(d_selected_point),
107                                                    d_selected_curve->y(d_selected_point))));
108 }
109 
setSelection(QwtPlotCurve * curve,int point_index)110 void DataPickerTool::setSelection(QwtPlotCurve *curve, int point_index)
111 {
112     if (curve == d_selected_curve && point_index == d_selected_point)
113         return;
114 
115     d_selected_curve = curve;
116     d_selected_point = point_index;
117 
118     if (!d_selected_curve) {
119         d_selection_marker.detach();
120         d_graph->plotWidget()->replot();
121         return;
122     }
123 
124     setAxis(d_selected_curve->xAxis(), d_selected_curve->yAxis());
125 
126     d_move_target_pos = QPoint(plot()->transform(xAxis(), d_selected_curve->x(d_selected_point)),
127                                plot()->transform(yAxis(), d_selected_curve->y(d_selected_point)));
128 
129     if (((PlotCurve *)d_selected_curve)->type() == Graph::Function) {
130         emit statusText(QString("%1[%2]: x=%3; y=%4")
131                                 .arg(d_selected_curve->title().text())
132                                 .arg(d_selected_point + 1)
133                                 .arg(QLocale().toString(d_selected_curve->x(d_selected_point), 'G',
134                                                         d_app->d_decimal_digits))
135                                 .arg(QLocale().toString(d_selected_curve->y(d_selected_point), 'G',
136                                                         d_app->d_decimal_digits)));
137     } else {
138         int row = ((DataCurve *)d_selected_curve)->tableRow(d_selected_point);
139 
140         Table *t = ((DataCurve *)d_selected_curve)->table();
141         int xCol = t->colIndex(((DataCurve *)d_selected_curve)->xColumnName());
142         int yCol = t->colIndex(d_selected_curve->title().text());
143 
144         emit statusText(QString("%1[%2]: x=%3; y=%4")
145                                 .arg(d_selected_curve->title().text())
146                                 .arg(row + 1)
147                                 .arg(t->text(row, xCol))
148                                 .arg(t->text(row, yCol)));
149     }
150 
151     QwtDoublePoint selected_point_value(d_selected_curve->x(d_selected_point),
152                                         d_selected_curve->y(d_selected_point));
153     d_selection_marker.setValue(selected_point_value);
154     if (d_selection_marker.plot() == NULL)
155         d_selection_marker.attach(d_graph->plotWidget());
156     d_graph->plotWidget()->replot();
157 }
158 
eventFilter(QObject * obj,QEvent * event)159 bool DataPickerTool::eventFilter(QObject *obj, QEvent *event)
160 {
161     switch (event->type()) {
162     case QEvent::MouseButtonDblClick:
163         switch (d_mode) {
164         case Remove:
165             removePoint();
166             return true;
167         default:
168             if (d_selected_curve)
169                 emit selected(d_selected_curve, d_selected_point);
170             return true;
171         }
172     case QEvent::MouseMove:
173         if (((QMouseEvent *)event)->modifiers() == Qt::ControlModifier)
174             d_move_mode = Vertical;
175         else if (((QMouseEvent *)event)->modifiers() == Qt::AltModifier)
176             d_move_mode = Horizontal;
177         else
178             d_move_mode = Free;
179         break;
180 
181     case QEvent::KeyPress:
182         if (keyEventFilter((QKeyEvent *)event))
183             return true;
184         break;
185     default:
186         break;
187     }
188     return QwtPlotPicker::eventFilter(obj, event);
189 }
190 
keyEventFilter(QKeyEvent * ke)191 bool DataPickerTool::keyEventFilter(QKeyEvent *ke)
192 {
193     const int delta = 5;
194     switch (ke->key()) {
195     case Qt::Key_Enter:
196     case Qt::Key_Return:
197         if (d_selected_curve)
198             emit selected(d_selected_curve, d_selected_point);
199         return true;
200 
201     case Qt::Key_Up:
202         if (d_graph && d_selected_curve) {
203             int n_curves = d_graph->curves();
204             int start = d_graph->curveIndex(d_selected_curve) + 1;
205             QwtPlotCurve *c;
206             for (int i = start; i < start + n_curves; ++i)
207                 if ((c = d_graph->curve(i % n_curves))->dataSize() > 0) {
208                     setSelection(c, qMin(c->dataSize() - 1, d_selected_point));
209                     break;
210                 }
211             d_graph->plotWidget()->replot();
212         }
213         return true;
214 
215     case Qt::Key_Down:
216         if (d_graph && d_selected_curve) {
217             int n_curves = d_graph->curves();
218             int start = d_graph->curveIndex(d_selected_curve) + n_curves - 1;
219             QwtPlotCurve *c;
220             for (int i = start; i > start - n_curves; --i)
221                 if ((c = d_graph->curve(i % n_curves))->dataSize() > 0) {
222                     setSelection(c, qMin(c->dataSize() - 1, d_selected_point));
223                     break;
224                 }
225             d_graph->plotWidget()->replot();
226         }
227         return true;
228 
229     case Qt::Key_Right:
230     case Qt::Key_Plus:
231         if (d_graph) {
232             if (d_selected_curve) {
233                 int n_points = d_selected_curve->dataSize();
234                 setSelection(d_selected_curve, (d_selected_point + 1) % n_points);
235                 d_graph->plotWidget()->replot();
236             } else
237                 setSelection(d_graph->curve(0), 0);
238         }
239         return true;
240 
241     case Qt::Key_Left:
242     case Qt::Key_Minus:
243         if (d_graph) {
244             if (d_selected_curve) {
245                 int n_points = d_selected_curve->dataSize();
246                 setSelection(d_selected_curve, (d_selected_point - 1 + n_points) % n_points);
247                 d_graph->plotWidget()->replot();
248             } else
249                 setSelection(d_graph->curve(d_graph->curves() - 1), 0);
250         }
251         return true;
252 
253     // The following keys represent a direction, they are
254     // organized on the keyboard.
255     case Qt::Key_1:
256         if (d_mode == Move) {
257             moveBy(-delta, delta);
258             return true;
259         }
260         break;
261     case Qt::Key_2:
262         if (d_mode == Move) {
263             moveBy(0, delta);
264             return true;
265         }
266         break;
267     case Qt::Key_3:
268         if (d_mode == Move) {
269             moveBy(delta, delta);
270             return true;
271         }
272         break;
273     case Qt::Key_4:
274         if (d_mode == Move) {
275             moveBy(-delta, 0);
276             return true;
277         }
278         break;
279     case Qt::Key_6:
280         if (d_mode == Move) {
281             moveBy(delta, 0);
282             return true;
283         }
284         break;
285     case Qt::Key_7:
286         if (d_mode == Move) {
287             moveBy(-delta, -delta);
288             return true;
289         }
290         break;
291     case Qt::Key_8:
292         if (d_mode == Move) {
293             moveBy(0, -delta);
294             return true;
295         }
296         break;
297     case Qt::Key_9:
298         if (d_mode == Move) {
299             moveBy(delta, -delta);
300             return true;
301         }
302         break;
303     default:
304         break;
305     }
306     return false;
307 }
308 
removePoint()309 void DataPickerTool::removePoint()
310 {
311     if (!d_selected_curve)
312         return;
313     if (((PlotCurve *)d_selected_curve)->type() == Graph::Function) {
314         QMessageBox::critical(const_cast<Graph *>(d_graph), tr("Remove point error"),
315                               tr("Sorry, but removing points of a function is not possible."));
316         return;
317     }
318 
319     Table *t = ((DataCurve *)d_selected_curve)->table();
320     if (!t)
321         return;
322 
323     int col = t->colIndex(d_selected_curve->title().text());
324     if (t->columnType(col) == SciDAVis::ColumnMode::Numeric) {
325         t->column(col)->setValueAt(((DataCurve *)d_selected_curve)->tableRow(d_selected_point),
326                                    0.0);
327         t->column(col)->setInvalid(((DataCurve *)d_selected_curve)->tableRow(d_selected_point),
328                                    true);
329     } else {
330         QMessageBox::warning(const_cast<Graph *>(d_graph), tr("Warning"),
331                              tr("This operation cannot be performed on curves plotted from columns "
332                                 "having a non-numerical format."));
333     }
334 
335     d_selection_marker.detach();
336     d_graph->plotWidget()->replot();
337     d_graph->setFocus();
338     d_selected_curve = NULL;
339 }
340 
move(const QPoint & point)341 void DataPickerTool::move(const QPoint &point)
342 {
343     if (d_mode == Move && d_selected_curve) {
344         switch (d_move_mode) {
345         case Free:
346             d_move_target_pos = point;
347             break;
348         case Vertical:
349             d_move_target_pos.setY(point.y());
350             break;
351         case Horizontal:
352             d_move_target_pos.setX(point.x());
353             break;
354         }
355         double new_x_val = d_graph->plotWidget()->invTransform(d_selected_curve->xAxis(),
356                                                                d_move_target_pos.x());
357         double new_y_val = d_graph->plotWidget()->invTransform(d_selected_curve->yAxis(),
358                                                                d_move_target_pos.y());
359         d_selection_marker.setValue(new_x_val, new_y_val);
360         if (d_selection_marker.plot() == NULL)
361             d_selection_marker.attach(d_graph->plotWidget());
362         d_graph->replot();
363 
364         int row = ((DataCurve *)d_selected_curve)->tableRow(d_selected_point);
365         emit statusText(QString("%1[%2]: x=%3; y=%4")
366                                 .arg(d_selected_curve->title().text())
367                                 .arg(row + 1)
368                                 .arg(QLocale().toString(new_x_val, 'G', d_app->d_decimal_digits))
369                                 .arg(QLocale().toString(new_y_val, 'G', d_app->d_decimal_digits)));
370     }
371 
372     QwtPlotPicker::move(d_move_target_pos);
373 }
374 
end(bool ok)375 bool DataPickerTool::end(bool ok)
376 {
377     if (d_mode == Move && d_selected_curve) {
378         if (((PlotCurve *)d_selected_curve)->type() == Graph::Function) {
379             QMessageBox::critical(d_graph, tr("Move point error"),
380                                   tr("Sorry, but moving points of a function is not possible."));
381             return QwtPlotPicker::end(ok);
382         }
383         Table *t = ((DataCurve *)d_selected_curve)->table();
384         if (!t)
385             return QwtPlotPicker::end(ok);
386         double new_x_val = d_graph->plotWidget()->invTransform(d_selected_curve->xAxis(),
387                                                                d_move_target_pos.x());
388         double new_y_val = d_graph->plotWidget()->invTransform(d_selected_curve->yAxis(),
389                                                                d_move_target_pos.y());
390         int row = ((DataCurve *)d_selected_curve)->tableRow(d_selected_point);
391         int xcol = t->colIndex(((DataCurve *)d_selected_curve)->xColumnName());
392         int ycol = t->colIndex(d_selected_curve->title().text());
393         if (t->columnType(xcol) == SciDAVis::ColumnMode::Numeric
394             && t->columnType(ycol) == SciDAVis::ColumnMode::Numeric) {
395             t->column(xcol)->setValueAt(row, new_x_val);
396             t->column(ycol)->setValueAt(row, new_y_val);
397             d_app->updateCurves(t, d_selected_curve->title().text());
398             d_app->modifiedProject();
399         } else
400             QMessageBox::warning(d_graph, tr("Warning"),
401                                  tr("This operation cannot be performed on curves plotted from "
402                                     "columns having a non-numerical format."));
403     }
404     return QwtPlotPicker::end(ok);
405 }
406 
moveBy(int dx,int dy)407 void DataPickerTool::moveBy(int dx, int dy)
408 {
409     if (!d_selected_curve)
410         return;
411     move(d_move_target_pos + QPoint(dx, dy));
412     end(true);
413 }
414 
trackerText(const QwtDoublePoint & point) const415 QwtText DataPickerTool::trackerText(const QwtDoublePoint &point) const
416 {
417     return plot()->axisScaleDraw(xAxis())->label(point.x()).text() + ", "
418             + plot()->axisScaleDraw(yAxis())->label(point.y()).text();
419 }
420