Engauge Digitizer  2
DlgEditPointAxis.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 "DlgEditPointAxis.h"
8 #include "DlgValidatorAbstract.h"
9 #include "DlgValidatorFactory.h"
10 #include "DocumentAxesPointsRequired.h"
11 #include "DocumentModelCoords.h"
12 #include "DocumentModelGeneral.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  const DocumentModelCoords &modelCoords,
40  const DocumentModelGeneral &modelGeneral,
41  const MainWindowModel &modelMainWindow,
42  const Transformation &transformation,
43  DocumentAxesPointsRequired documentAxesPointsRequired,
44  bool isXOnly,
45  const double *xInitialValue,
46  const double *yInitialValue) :
47  QDialog (&mainWindow),
48  m_documentAxesPointsRequired (documentAxesPointsRequired),
49  m_modelCoords (modelCoords),
50  m_modelGeneral (modelGeneral),
51  m_modelMainWindow (modelMainWindow)
52 {
53  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPointAxis::DlgEditPointAxis";
54 
55  // Either one or two coordinates are desired
56  bool isX = (documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) || isXOnly;
57  bool isY = (documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) || !isXOnly;
58 
59  QVBoxLayout *layout = new QVBoxLayout;
60  setLayout (layout);
61 
62  setCursor (QCursor (Qt::ArrowCursor));
63  setModal(true);
64  setWindowTitle (tr ("Edit Axis Point"));
65 
66  createCoords (layout);
67  createHint (layout);
68  createOkCancel (layout);
69 
70  initializeGraphCoordinates (xInitialValue,
71  yInitialValue,
72  transformation,
73  isX,
74  isY);
75 
76  updateControls ();
77 }
78 
79 DlgEditPointAxis::~DlgEditPointAxis()
80 {
81  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPointAxis::~DlgEditPointAxis";
82 }
83 
84 void DlgEditPointAxis::createCoords (QVBoxLayout *layoutOuter)
85 {
86  // Constraints on x and y are needed for log scaling
87  bool isConstraintX = (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG);
88  bool isConstraintY = (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG);
89  DlgValidatorFactory dlgValidatorFactory;
90  m_validatorGraphX = dlgValidatorFactory.createCartesianOrPolarWithPolarPolar (m_modelCoords.coordScaleXTheta(),
91  isCartesian (),
92  m_modelCoords.coordUnitsX(),
93  m_modelCoords.coordUnitsTheta(),
94  m_modelCoords.coordUnitsDate(),
95  m_modelCoords.coordUnitsTime(),
96  m_modelMainWindow.locale());
97  m_validatorGraphY = dlgValidatorFactory.createCartesianOrPolarWithNonPolarPolar (m_modelCoords.coordScaleYRadius(),
98  isCartesian (),
99  m_modelCoords.coordUnitsY(),
100  m_modelCoords.coordUnitsRadius(),
101  m_modelCoords.coordUnitsDate(),
102  m_modelCoords.coordUnitsTime(),
103  m_modelMainWindow.locale());
104 
105  // Label, with guidance in terms of legal ranges and units
106  QString description = QString ("%1 (%2, %3)%4%5%6%7%8%9 %10 (%11, %12):")
107  .arg (tr ("Graph Coordinates"))
108  .arg (nameXTheta ())
109  .arg (nameYRadius ())
110  .arg (isConstraintX || isConstraintY ? " with " : "")
111  .arg (isConstraintX ? QString (nameXTheta ()) : "")
112  .arg (isConstraintX ? " > 0" : "")
113  .arg (isConstraintX && isConstraintY ? " and " : "")
114  .arg ( isConstraintY ? QString (nameYRadius ()) : "")
115  .arg ( isConstraintY ? " > 0" : "")
116  .arg (tr ("as"))
117  .arg (unitsType (IS_X_THETA))
118  .arg (unitsType (IS_NOT_X_THETA));
119  QGroupBox *panel = new QGroupBox (description, this);
120  layoutOuter->addWidget (panel);
121 
122  QHBoxLayout *layout = new QHBoxLayout (panel);
123  panel->setLayout (layout);
124 
125  // Row
126  QLabel *labelGraphParLeft = new QLabel (tr ("("), this);
127  layout->addWidget(labelGraphParLeft, 0);
128 
129  m_editGraphX = new QLineEdit;
130  m_editGraphX->setMinimumWidth(MIN_WIDTH_TO_FIT_STRANGE_UNITS);
131  m_editGraphX->setAlignment (ALIGNMENT);
132  m_editGraphX->setValidator (m_validatorGraphX);
133  // setStatusTip does not work for modal dialogs
134  m_editGraphX->setWhatsThis (tr ("Enter the first graph coordinate of the axis point.\n\n"
135  "For cartesian plots this is X. For polar plots this is the radius R.\n\n"
136  "The expected format of the coordinate value is determined by the locale setting. If "
137  "typed values are not recognized as expected, check the locale setting in Settings / Main Window..."));
138  layout->addWidget(m_editGraphX, 0);
139  connect (m_editGraphX, SIGNAL (textChanged (const QString &)), this, SLOT (slotTextChanged (const QString &)));
140 
141  QLabel *labelGraphComma = new QLabel (tr (", "), this);
142  layout->addWidget(labelGraphComma, 0);
143 
144  m_editGraphY = new QLineEdit;
145  m_editGraphY->setMinimumWidth(MIN_WIDTH_TO_FIT_STRANGE_UNITS);
146  m_editGraphY->setAlignment (ALIGNMENT);
147  m_editGraphY->setValidator (m_validatorGraphY);
148  // setStatusTip does not work for modal dialogs
149  m_editGraphY->setWhatsThis (tr ("Enter the second graph coordinate of the axis point.\n\n"
150  "For cartesian plots this is Y. For polar plots this is the angle Theta.\n\n"
151  "The expected format of the coordinate value is determined by the locale setting. If "
152  "typed values are not recognized as expected, check the locale setting in Settings / Main Window..."));
153  layout->addWidget(m_editGraphY, 0);
154  connect (m_editGraphY, SIGNAL (textChanged (const QString &)), this, SLOT (slotTextChanged (const QString &)));
155 
156  QLabel *labelGraphParRight = new QLabel (tr (")"), this);
157  layout->addWidget(labelGraphParRight, 0);
158 }
159 
160 void DlgEditPointAxis::createHint (QVBoxLayout *layoutOuter)
161 {
162  // Insert a hint explaining why decimal points may not be accepted. Very confusing for user to figure out the problem at first, and
163  // then figure out which setting should change to fix it. The hint is centered so it is slightly less intrusive
164 
165  QWidget *widget = new QWidget;
166  layoutOuter->addWidget (widget, 0, Qt::AlignCenter);
167 
168  QHBoxLayout *layout = new QHBoxLayout;
169  widget->setLayout (layout);
170 
171  QString locale = QLocaleToString (m_modelMainWindow.locale ());
172  QString hint = QString ("%1: %2")
173  .arg (tr ("Number format"))
174  .arg (locale);
175  QLabel *label = new QLabel (hint);
176  layout->addWidget (label);
177 }
178 
179 void DlgEditPointAxis::createOkCancel (QVBoxLayout *layoutOuter)
180 {
181  QWidget *panel = new QWidget (this);
182  layoutOuter->addWidget (panel, 0, Qt::AlignCenter);
183 
184  QHBoxLayout *layout = new QHBoxLayout (panel);
185  panel->setLayout (layout);
186 
187  m_btnOk = new QPushButton (tr ("Ok"), this);
188  layout->addWidget(m_btnOk);
189  connect (m_btnOk, SIGNAL (released ()), this, SLOT (accept ()));
190 
191  m_btnCancel = new QPushButton (tr ("Cancel"), this);
192  layout->addWidget(m_btnCancel);
193  connect (m_btnCancel, SIGNAL (released ()), this, SLOT (reject ()));
194 }
195 
196 void DlgEditPointAxis::initializeGraphCoordinates (const double *xInitialValue,
197  const double *yInitialValue,
198  const Transformation &transformation,
199  bool isX,
200  bool isY)
201 {
202  LOG4CPP_INFO_S ((*mainCat)) << "DlgEditPointAxis::initializeGraphCoordinates";
203 
204  QString xTheta, yRadius;
205  if ((xInitialValue != 0) &&
206  (yInitialValue != 0)) {
207 
208  FormatCoordsUnits format;
209  format.unformattedToFormatted (*xInitialValue,
210  *yInitialValue,
211  m_modelCoords,
212  m_modelGeneral,
213  m_modelMainWindow,
214  xTheta,
215  yRadius,
216  transformation);
217  }
218 
219  if (isX) {
220  m_editGraphX->setText (xTheta);
221  } else {
222  m_editGraphX->setText ("");
223  }
224 
225  if (isY) {
226  m_editGraphY->setText (yRadius);
227  } else {
228  m_editGraphY->setText ("");
229  }
230 }
231 
232 bool DlgEditPointAxis::isCartesian () const
233 {
234  return (m_modelCoords.coordsType() == COORDS_TYPE_CARTESIAN);
235 }
236 
237 QChar DlgEditPointAxis::nameXTheta () const
238 {
239  return (isCartesian () ? QChar ('X') : THETA);
240 }
241 
242 QChar DlgEditPointAxis::nameYRadius () const
243 {
244  return (isCartesian () ? QChar ('Y') : QChar ('R'));
245 }
246 
247 QPointF DlgEditPointAxis::posGraph (bool &isXOnly) const
248 {
249  double xTheta, yRadius;
250 
251  FormatCoordsUnits format;
252 
253  format.formattedToUnformatted (m_editGraphX->text(),
254  m_editGraphY->text(),
255  m_modelCoords,
256  m_modelMainWindow,
257  xTheta,
258  yRadius);
259 
260  // If yRadius value is empty then this is the xTheta value only
261  isXOnly = m_editGraphY->text().isEmpty();
262 
263  return QPointF (xTheta,
264  yRadius);
265 }
266 
267 void DlgEditPointAxis::slotTextChanged (const QString &)
268 {
269  updateControls ();
270 }
271 
272 QString DlgEditPointAxis::unitsType (bool isXTheta) const
273 {
274  if (isCartesian ()) {
275  if (isXTheta) {
276  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsX());
277  } else {
278  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsY());
279  }
280  } else {
281  if (isXTheta) {
282  return coordUnitsPolarThetaToBriefType (m_modelCoords.coordUnitsTheta());
283  } else {
284  return coordUnitsNonPolarThetaToBriefType (m_modelCoords.coordUnitsRadius());
285  }
286  }
287 }
288 
289 void DlgEditPointAxis::updateControls ()
290 {
291  QString textX = m_editGraphX->text();
292  QString textY = m_editGraphY->text();
293 
294  int posX, posY;
295 
296  if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_4) {
297 
298  bool gotX = (!textX.isEmpty() &&
299  (m_validatorGraphX->validate(textX, posX) == QValidator::Acceptable));
300  bool gotY = (!textY.isEmpty() &&
301  (m_validatorGraphY->validate(textY, posY) == QValidator::Acceptable));
302 
303  // Check for not empty in one coordinate and for valid number in the other coordinate
304  m_btnOk->setEnabled ((textX.isEmpty() && gotY) ||
305  (textY.isEmpty() && gotX));
306 
307  // Emphasize that only one coordinate is to be filled in at a time
308  m_editGraphX->setEnabled (!gotY);
309  m_editGraphY->setEnabled (!gotX);
310 
311  } else {
312 
313  // Check for not empty (which allows single minus sign) and for valid number (which prevents single minus sign)
314  m_btnOk->setEnabled (!textX.isEmpty () &&
315  !textY.isEmpty () &&
316  (m_validatorGraphX->validate(textX, posX) == QValidator::Acceptable) &&
317  (m_validatorGraphY->validate(textY, posY) == QValidator::Acceptable));
318 
319  }
320 }
Model for DlgSettingsGeneral and CmdSettingsGeneral.
CoordUnitsNonPolarTheta coordUnitsRadius() const
Get method for radius units.
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.
CoordUnitsNonPolarTheta coordUnitsY() const
Get method for x units.
virtual QValidator::State validate(QString &input, int &pos) const =0
Validate according to the numeric format specific to the leaf class.
CoordUnitsPolarTheta coordUnitsTheta() const
Get method for theta unit.
DlgEditPointAxis(MainWindow &mainWindow, const DocumentModelCoords &modelCoords, const DocumentModelGeneral &modelGeneral, const MainWindowModel &modelMainWindow, 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...
QPointF posGraph(bool &isXOnly) const
Return the graph coordinates position specified by the user. Only applies if dialog was accepted...
void unformattedToFormatted(double xThetaUnformatted, double yRadiusUnformatted, const DocumentModelCoords &modelCoords, const DocumentModelGeneral &modelGeneral, 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...
CoordUnitsTime coordUnitsTime() const
Get method for time format when used.
Model for DlgSettingsMainWindow.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
CoordUnitsDate coordUnitsDate() const
Get method for date format when used.
Model for DlgSettingsCoords and CmdSettingsCoords.
Highest-level wrapper around other Formats classes.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
QLocale locale() const
Get method for locale.
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...
CoordsType coordsType() const
Get method for coordinates type.
CoordUnitsNonPolarTheta coordUnitsX() const
Get method for x units.
Validator factory.
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...
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:89