Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
DlgEditPoint.cpp
1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "DigitizeStateAbstractBase.h"
8 #include "DlgEditPoint.h"
9 #include "DlgValidatorAbstract.h"
10 #include "DlgValidatorFactory.h"
11 #include "DocumentAxesPointsRequired.h"
12 #include "DocumentModelCoords.h"
13 #include "EngaugeAssert.h"
14 #include "FormatCoordsUnits.h"
15 #include "FormatDateTime.h"
16 #include "FormatDegreesMinutesSecondsNonPolarTheta.h"
17 #include "FormatDegreesMinutesSecondsPolarTheta.h"
18 #include "Logger.h"
19 #include "MainWindow.h"
20 #include "MainWindowModel.h"
21 #include <QDoubleValidator>
22 #include <QGridLayout>
23 #include <QGroupBox>
24 #include <QHBoxLayout>
25 #include <QLabel>
26 #include <QRect>
27 #include "QtToString.h"
28 #include <QVBoxLayout>
29 #include "Transformation.h"
30 
31 const Qt::Alignment ALIGNMENT = Qt::AlignCenter;
32 
33 const int MIN_WIDTH_TO_FIT_STRANGE_UNITS = 200;
34 
35 const bool IS_X_THETA = true;
36 const bool IS_NOT_X_THETA = false;
37 
39  DigitizeStateAbstractBase &digitizeState,
40  const DocumentModelCoords &modelCoords,
41  const MainWindowModel &modelMainWindow,
42  const QCursor &cursorShape,
43  const Transformation &transformation,
44  DocumentAxesPointsRequired documentAxesPointsRequired,
45  bool isXOnly,
46  const double *xInitialValue,
47  const double *yInitialValue) :
48  QDialog (&mainWindow),
49  m_cursorShape (cursorShape),
50  m_documentAxesPointsRequired (documentAxesPointsRequired),
51  m_modelCoords (modelCoords),
52  m_modelMainWindow (modelMainWindow)
53 {
54  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPoint::DlgEditPoint";
55 
56  // Either one or two coordinates are desired
57  bool isX = (documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) || isXOnly;
58  bool isY = (documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) || !isXOnly;
59 
60  // To guarantee the override cursor is always removed, we call removeOverrideCursor here rather than in the code that
61  // allocates this DlgEditPoint. The digitizeState argument is otherwise unused.
62  digitizeState.removeOverrideCursor();
63 
64  connect (this, SIGNAL (signalSetOverrideCursor (QCursor)), &mainWindow, SLOT (slotSetOverrideCursor (QCursor)));
65 
66  QVBoxLayout *layout = new QVBoxLayout;
67  setLayout (layout);
68 
69  setCursor (QCursor (Qt::ArrowCursor));
70  setModal(true);
71  setWindowTitle (tr ("Edit Axis Point"));
72 
73  createCoords (layout);
74  createHint (layout);
75  createOkCancel (layout);
76 
77  initializeGraphCoordinates (xInitialValue,
78  yInitialValue,
79  transformation,
80  isX,
81  isY);
82 
83  updateControls ();
84 }
85 
86 DlgEditPoint::~DlgEditPoint()
87 {
88  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPoint::~DlgEditPoint";
89 
90  emit signalSetOverrideCursor (m_cursorShape);
91 }
92 
93 void DlgEditPoint::createCoords (QVBoxLayout *layoutOuter)
94 {
95  // Constraints on x and y are needed for log scaling
96  bool isConstraintX = (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG);
97  bool isConstraintY = (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG);
98  DlgValidatorFactory dlgValidatorFactory;
99  m_validatorGraphX = dlgValidatorFactory.createCartesianOrPolarWithPolarPolar (m_modelCoords.coordScaleXTheta(),
100  isCartesian (),
101  m_modelCoords.coordUnitsX(),
102  m_modelCoords.coordUnitsTheta(),
103  m_modelCoords.coordUnitsDate(),
104  m_modelCoords.coordUnitsTime(),
105  m_modelMainWindow.locale());
106  m_validatorGraphY = dlgValidatorFactory.createCartesianOrPolarWithNonPolarPolar (m_modelCoords.coordScaleYRadius(),
107  isCartesian (),
108  m_modelCoords.coordUnitsY(),
109  m_modelCoords.coordUnitsRadius(),
110  m_modelCoords.coordUnitsDate(),
111  m_modelCoords.coordUnitsTime(),
112  m_modelMainWindow.locale());
113 
114  // Label, with guidance in terms of legal ranges and units
115  QString description = QString ("%1 (%2, %3)%4%5%6%7%8%9 %10 (%11, %12):")
116  .arg (tr ("Graph Coordinates"))
117  .arg (nameXTheta ())
118  .arg (nameYRadius ())
119  .arg (isConstraintX || isConstraintY ? " with " : "")
120  .arg (isConstraintX ? QString (nameXTheta ()) : "")
121  .arg (isConstraintX ? " > 0" : "")
122  .arg (isConstraintX && isConstraintY ? " and " : "")
123  .arg ( isConstraintY ? QString (nameYRadius ()) : "")
124  .arg ( isConstraintY ? " > 0" : "")
125  .arg (tr ("as"))
126  .arg (unitsType (IS_X_THETA))
127  .arg (unitsType (IS_NOT_X_THETA));
128  QGroupBox *panel = new QGroupBox (description, this);
129  layoutOuter->addWidget (panel);
130 
131  QHBoxLayout *layout = new QHBoxLayout (panel);
132  panel->setLayout (layout);
133 
134  // Row
135  QLabel *labelGraphParLeft = new QLabel (tr ("("), this);
136  layout->addWidget(labelGraphParLeft, 0);
137 
138  m_editGraphX = new QLineEdit;
139  m_editGraphX->setMinimumWidth(MIN_WIDTH_TO_FIT_STRANGE_UNITS);
140  m_editGraphX->setAlignment (ALIGNMENT);
141  m_editGraphX->setValidator (m_validatorGraphX);
142  // setStatusTip does not work for modal dialogs
143  m_editGraphX->setWhatsThis (tr ("Enter the first graph coordinate of the axis point.\n\n"
144  "For cartesian plots this is X. For polar plots this is the radius R.\n\n"
145  "The expected format of the coordinate value is determined by the locale setting. If "
146  "typed values are not recognized as expected, check the locale setting in Settings / Main Window..."));
147  layout->addWidget(m_editGraphX, 0);
148  connect (m_editGraphX, SIGNAL (textChanged (const QString &)), this, SLOT (slotTextChanged (const QString &)));
149 
150  QLabel *labelGraphComma = new QLabel (tr (", "), this);
151  layout->addWidget(labelGraphComma, 0);
152 
153  m_editGraphY = new QLineEdit;
154  m_editGraphY->setMinimumWidth(MIN_WIDTH_TO_FIT_STRANGE_UNITS);
155  m_editGraphY->setAlignment (ALIGNMENT);
156  m_editGraphY->setValidator (m_validatorGraphY);
157  // setStatusTip does not work for modal dialogs
158  m_editGraphY->setWhatsThis (tr ("Enter the second graph coordinate of the axis point.\n\n"
159  "For cartesian plots this is Y. For plot plots this is the angle Theta.\n\n"
160  "The expected format of the coordinate value is determined by the locale setting. If "
161  "typed values are not recognized as expected, check the locale setting in Settings / Main Window..."));
162  layout->addWidget(m_editGraphY, 0);
163  connect (m_editGraphY, SIGNAL (textChanged (const QString &)), this, SLOT (slotTextChanged (const QString &)));
164 
165  QLabel *labelGraphParRight = new QLabel (tr (")"), this);
166  layout->addWidget(labelGraphParRight, 0);
167 }
168 
169 void DlgEditPoint::createHint (QVBoxLayout *layoutOuter)
170 {
171  // Insert a hint explaining why decimal points may not be accepted. Very confusing for user to figure out the problem at first, and
172  // then figure out which setting should change to fix it. The hint is centered so it is slightly less intrusive
173 
174  QWidget *widget = new QWidget;
175  layoutOuter->addWidget (widget, 0, Qt::AlignCenter);
176 
177  QHBoxLayout *layout = new QHBoxLayout;
178  widget->setLayout (layout);
179 
180  QString locale = QLocaleToString (m_modelMainWindow.locale ());
181  QString hint = QString ("%1: %2")
182  .arg (tr ("Number format"))
183  .arg (locale);
184  QLabel *label = new QLabel (hint);
185  layout->addWidget (label);
186 }
187 
188 void DlgEditPoint::createOkCancel (QVBoxLayout *layoutOuter)
189 {
190  QWidget *panel = new QWidget (this);
191  layoutOuter->addWidget (panel, 0, Qt::AlignCenter);
192 
193  QHBoxLayout *layout = new QHBoxLayout (panel);
194  panel->setLayout (layout);
195 
196  m_btnOk = new QPushButton (tr ("Ok"), this);
197  layout->addWidget(m_btnOk);
198  connect (m_btnOk, SIGNAL (released ()), this, SLOT (accept ()));
199 
200  m_btnCancel = new QPushButton (tr ("Cancel"), this);
201  layout->addWidget(m_btnCancel);
202  connect (m_btnCancel, SIGNAL (released ()), this, SLOT (reject ()));
203 }
204 
205 void DlgEditPoint::initializeGraphCoordinates (const double *xInitialValue,
206  const double *yInitialValue,
207  const Transformation &transformation,
208  bool isX,
209  bool isY)
210 {
211  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPoint::initializeGraphCoordinates";
212 
213  QString xTheta, yRadius;
214  if ((xInitialValue != 0) &&
215  (yInitialValue != 0)) {
216 
217  FormatCoordsUnits format;
218  format.unformattedToFormatted (*xInitialValue,
219  *yInitialValue,
220  m_modelCoords,
221  m_modelMainWindow,
222  xTheta,
223  yRadius,
224  transformation);
225  }
226 
227  if (isX) {
228  m_editGraphX->setText (xTheta);
229  } else {
230  m_editGraphX->setText ("");
231  }
232 
233  if (isY) {
234  m_editGraphY->setText (yRadius);
235  } else {
236  m_editGraphY->setText ("");
237  }
238 }
239 
240 bool DlgEditPoint::isCartesian () const
241 {
242  return (m_modelCoords.coordsType() == COORDS_TYPE_CARTESIAN);
243 }
244 
245 QChar DlgEditPoint::nameXTheta () const
246 {
247  return (isCartesian () ? QChar ('X') : THETA);
248 }
249 
250 QChar DlgEditPoint::nameYRadius () const
251 {
252  return (isCartesian () ? QChar ('Y') : QChar ('R'));
253 }
254 
255 QPointF DlgEditPoint::posGraph (bool &isXOnly) const
256 {
257  double xTheta, yRadius;
258 
259  FormatCoordsUnits format;
260 
261  format.formattedToUnformatted (m_editGraphX->text(),
262  m_editGraphY->text(),
263  m_modelCoords,
264  m_modelMainWindow,
265  xTheta,
266  yRadius);
267 
268  // If yRadius value is empty then this is the xTheta value only
269  isXOnly = m_editGraphY->text().isEmpty();
270 
271  return QPointF (xTheta,
272  yRadius);
273 }
274 
275 void DlgEditPoint::slotTextChanged (const QString &)
276 {
277  updateControls ();
278 }
279 
280 QString DlgEditPoint::unitsType (bool isXTheta) const
281 {
282  if (isCartesian ()) {
283  if (isXTheta) {
284  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsX());
285  } else {
286  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsY());
287  }
288  } else {
289  if (isXTheta) {
290  return coordUnitsPolarThetaToBriefType (m_modelCoords.coordUnitsTheta());
291  } else {
292  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsRadius());
293  }
294  }
295 }
296 
297 void DlgEditPoint::updateControls ()
298 {
299  QString textX = m_editGraphX->text();
300  QString textY = m_editGraphY->text();
301 
302  int posX, posY;
303 
304  if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_4) {
305 
306  bool gotX = (!textX.isEmpty() &&
307  (m_validatorGraphX->validate(textX, posX) == QValidator::Acceptable));
308  bool gotY = (!textY.isEmpty() &&
309  (m_validatorGraphY->validate(textY, posY) == QValidator::Acceptable));
310 
311  // Check for not empty in one coordinate and for valid number in the other coordinate
312  m_btnOk->setEnabled ((textX.isEmpty() && gotY) ||
313  (textY.isEmpty() && gotX));
314 
315  } else {
316 
317  // Check for not empty (which allows single minus sign) and for valid number (which prevents single minus sign)
318  m_btnOk->setEnabled (!textX.isEmpty () &&
319  !textY.isEmpty () &&
320  (m_validatorGraphX->validate(textX, posX) == QValidator::Acceptable) &&
321  (m_validatorGraphY->validate(textY, posY) == QValidator::Acceptable));
322 
323  }
324 }
void formattedToUnformatted(const QString &xThetaFormatted, const QString &yRadiusFormatted, const DocumentModelCoords &modelCoords, const MainWindowModel &mainWindowModel, double &xThetaUnformatted, double &yRadiusUnformatted) const
Convert formatted string to unformatted numeric value.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
virtual QValidator::State validate(QString &input, int &pos) const =0
Validate according to the numeric format specific to the leaf class.
QPointF posGraph(bool &isXOnly) const
Return the graph coordinates position specified by the user. Only applies if dialog was accepted...
CoordUnitsNonPolarTheta coordUnitsRadius() const
Get method for radius units.
CoordUnitsTime coordUnitsTime() const
Get method for time format when used.
DlgValidatorAbstract * createCartesianOrPolarWithPolarPolar(CoordScale coordScale, bool isCartesian, CoordUnitsNonPolarTheta coordUnitsCartesian, CoordUnitsPolarTheta coordUnitsPolar, CoordUnitsDate coordUnitsDate, CoordUnitsTime coordUnitsTime, const QLocale &locale) const
Factory method for generating validators for either cartesian or polar case, when polar format is spe...
void signalSetOverrideCursor(QCursor)
Send a signal to trigger the setting of the override cursor.
DlgEditPoint(MainWindow &mainWindow, DigitizeStateAbstractBase &digitizeState, const DocumentModelCoords &modelCoords, const MainWindowModel &modelMainWindow, const QCursor &cursorShape, const Transformation &transformation, DocumentAxesPointsRequired documentAxesPointsRequired, bool isXOnly=false, const double *xInitialValue=0, const double *yInitialValue=0)
Constructor for existing point which already has graph coordinates (which may be changed using this d...
void unformattedToFormatted(double xThetaUnformatted, double yRadiusUnformatted, const DocumentModelCoords &modelCoords, const MainWindowModel &mainWindowModel, QString &xThetaFormatted, QString &yRadiusFormatted, const Transformation &transformation) const
Convert unformatted numeric value to formatted string. Transformation is used to determine best resol...
Affine transformation between screen and graph coordinates, based on digitized axis points...
CoordUnitsNonPolarTheta coordUnitsY() const
Get method for x units.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
Model for DlgSettingsMainWindow.
CoordsType coordsType() const
Get method for coordinates type.
CoordUnitsNonPolarTheta coordUnitsX() const
Get method for x units.
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordUnitsDate coordUnitsDate() const
Get method for date format when used.
Highest-level wrapper around other Formats classes.
DlgValidatorAbstract * createCartesianOrPolarWithNonPolarPolar(CoordScale coordScale, bool isCartesian, CoordUnitsNonPolarTheta coordUnitsCartesian, CoordUnitsNonPolarTheta coordUnitsPolar, CoordUnitsDate coordUnitsDate, CoordUnitsTime coordUnitsTime, const QLocale &locale) const
Factory method for generating validators for either cartesian or polar case, when polar format is spe...
void removeOverrideCursor()
Remove the override cursor if it is in use. This is called after a leave event, and prior to displayi...
Base class for all digitizing states. This serves as an interface to DigitizeStateContext.
Validator factory.
QLocale locale() const
Get method for locale.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:82
CoordUnitsPolarTheta coordUnitsTheta() const
Get method for theta unit.