Engauge Digitizer  2
DlgSettingsSegments.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 "CmdMediator.h"
8 #include "CmdSettingsSegments.h"
9 #include "DlgSettingsSegments.h"
10 #include "EngaugeAssert.h"
11 #include "GeometryWindow.h"
12 #include "Logger.h"
13 #include "MainWindow.h"
14 #include "PointStyle.h"
15 #include <QCheckBox>
16 #include <QComboBox>
17 #include <QGridLayout>
18 #include <QGraphicsScene>
19 #include <QLabel>
20 #include <qmath.h>
21 #include <QSpinBox>
22 #include "Segment.h"
23 #include "SegmentFactory.h"
24 #include "ViewPreview.h"
25 
26 const int MINIMUM_HEIGHT = 540;
27 const int MIN_LENGTH_MIN = 1;
28 const int MIN_LENGTH_MAX = 10000;
29 const int POINT_SEPARATION_MIN = 5;
30 const int POINT_SEPARATION_MAX = 10000;
31 
32 const int IMAGE_WIDTH = 400;
33 const int IMAGE_HEIGHT = 350;
34 
35 const double TWOPI = 2.0 * 3.1415926535;
36 
37 const double BRUSH_WIDTH = 2.0;
38 
40  DlgSettingsAbstractBase (tr ("Segment Fill"),
41  "DlgSettingsSegments",
42  mainWindow),
43  m_scenePreview (nullptr),
44  m_viewPreview (nullptr),
45  m_modelSegmentsBefore (nullptr),
46  m_modelSegmentsAfter (nullptr),
47  m_loading (false)
48 {
49  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::DlgSettingsSegments";
50 
51  QWidget *subPanel = createSubPanel ();
52  finishPanel (subPanel);
53 }
54 
55 DlgSettingsSegments::~DlgSettingsSegments()
56 {
57  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::~DlgSettingsSegments";
58 }
59 
60 void DlgSettingsSegments::clearPoints ()
61 {
62  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::clearPoints";
63 
64  QList<GraphicsPoint*>::iterator itrP;
65  for (itrP = m_points.begin(); itrP != m_points.end(); itrP++) {
66  GraphicsPoint *point = *itrP;
67  delete point;
68  }
69 
70  m_points.clear();
71 }
72 
73 void DlgSettingsSegments::createControls (QGridLayout *layout,
74  int &row)
75 {
76  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::createControls";
77 
78  QLabel *labelMinLength = new QLabel(QString ("%1:").arg (tr ("Minimum length (points)")));
79  layout->addWidget(labelMinLength, row, 1);
80 
81  m_spinMinLength = new QSpinBox;
82  m_spinMinLength->setRange (MIN_LENGTH_MIN, MIN_LENGTH_MAX);
83  m_spinMinLength->setWhatsThis (tr ("Select a minimum number of points in a segment.\n\n"
84  "Only segments with more points will be created.\n\n"
85  "This value should be as large as possible to reduce memory usage. This value has "
86  "a lower limit"));
87  connect (m_spinMinLength, SIGNAL (valueChanged (const QString &)), this, SLOT (slotMinLength (const QString &)));
88  layout->addWidget(m_spinMinLength, row++, 2);
89 
90  QLabel *labelPointSeparation = new QLabel(QString ("%1:").arg (tr ("Point separation (pixels)")));
91  layout->addWidget (labelPointSeparation, row, 1);
92 
93  m_spinPointSeparation = new QSpinBox;
94  m_spinPointSeparation->setRange (POINT_SEPARATION_MIN, POINT_SEPARATION_MAX);
95  m_spinPointSeparation->setWhatsThis (tr ("Select a point separation in pixels.\n\n"
96  "Successive points added to a segment will be separated by this number of pixels. "
97  "If Fill Corners is enabled, then additional points will be inserted at corners so some points "
98  "will be closer.\n\n"
99  "This value has a lower limit"));
100  connect (m_spinPointSeparation, SIGNAL (valueChanged (const QString &)), this, SLOT (slotPointSeparation (const QString &)));
101  layout->addWidget (m_spinPointSeparation, row++, 2);
102 
103  QLabel *labelFillCorners = new QLabel (QString ("%1:").arg (tr ("Fill corners")));
104  layout->addWidget (labelFillCorners, row, 1);
105 
106  m_chkFillCorners = new QCheckBox;
107  m_chkFillCorners->setWhatsThis (tr ("Fill corners.\n\n"
108  "In addition to the points placed at regular intervals, this option causes a point to be "
109  "placed at each corner. This option can capture important information in piecewise linear graphs, "
110  "but gradually curving graphs may not benefit from the additional points"));
111  connect (m_chkFillCorners, SIGNAL (stateChanged (int)), this, SLOT (slotFillCorners (int)));
112  layout->addWidget (m_chkFillCorners, row++, 2);
113 
114  QLabel *labelLineWidth = new QLabel(QString ("%1:").arg (tr ("Line width")));
115  layout->addWidget (labelLineWidth, row, 1);
116 
117  m_spinLineWidth = new QSpinBox;
118  m_spinLineWidth->setWhatsThis (tr ("Select a size for the lines drawn along a segment"));
119  m_spinLineWidth->setMinimum(1);
120  connect (m_spinLineWidth, SIGNAL (valueChanged (int)), this, SLOT (slotLineWidth (int)));
121  layout->addWidget (m_spinLineWidth, row++, 2);
122 
123  QLabel *labelLineColor = new QLabel(QString ("%1:").arg (tr ("Line color")));
124  layout->addWidget (labelLineColor, row, 1);
125 
126  m_cmbLineColor = new QComboBox;
127  m_cmbLineColor->setWhatsThis (tr ("Select a color for the lines drawn along a segment"));
128  populateColorComboWithTransparent (*m_cmbLineColor);
129  connect (m_cmbLineColor, SIGNAL (activated (const QString &)), this, SLOT (slotLineColor (const QString &))); // activated() ignores code changes
130  layout->addWidget (m_cmbLineColor, row++, 2);
131 }
132 
133 void DlgSettingsSegments::createOptionalSaveDefault (QHBoxLayout * /* layout */)
134 {
135 }
136 
137 void DlgSettingsSegments::createPreview (QGridLayout *layout,
138  int &row)
139 {
140  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::createPreview";
141 
142  QLabel *labelPreview = new QLabel (tr ("Preview"));
143  layout->addWidget (labelPreview, row++, 0, 1, 4);
144 
145  m_scenePreview = new QGraphicsScene (this);
146  m_viewPreview = new ViewPreview (m_scenePreview,
147  ViewPreview::VIEW_ASPECT_RATIO_VARIABLE,
148  this);
149  m_viewPreview->setWhatsThis (tr ("Preview window shows the shortest line that can be segment filled, "
150  "and the effects of current settings on segments and points generated by segment fill"));
151  m_viewPreview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
152  m_viewPreview->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
153  m_viewPreview->setMinimumHeight (MINIMUM_PREVIEW_HEIGHT);
154 
155  layout->addWidget (m_viewPreview, row++, 0, 1, 4);
156 }
157 
158 QImage DlgSettingsSegments::createPreviewImage () const
159 {
160  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::createPreviewImage";
161 
162  QImage image (IMAGE_WIDTH,
163  IMAGE_HEIGHT,
164  QImage::Format_RGB32);
165  image.fill (Qt::white);
166  QPainter painter (&image);
167  painter.setRenderHint(QPainter::Antialiasing);
168  painter.setPen (QPen (QBrush (Qt::black), BRUSH_WIDTH));
169 
170  int margin = IMAGE_WIDTH / 15;
171  int yCenter = IMAGE_HEIGHT / 2;
172  int yHeight = IMAGE_HEIGHT / 4;
173  int x, y, xLast = 0, yLast = 0;
174  bool isFirst;
175 
176  // Draw sinusoid
177  isFirst = true;
178  int xStart = margin, xEnd = IMAGE_WIDTH / 2 - margin;
179  for (x = xStart; x < xEnd; x++) {
180  double s = double (x - xStart) / double (xEnd - xStart);
181  int y = qFloor (yCenter - yHeight * qSin (TWOPI * s));
182 
183  if (!isFirst) {
184  painter.drawLine (xLast, yLast, x, y);
185  }
186  isFirst = false;
187  xLast = x;
188  yLast = y;
189  }
190 
191  // Draw triangular waveform that looks like sinusoid straightened up into line segments
192  isFirst = true;
193  xStart = IMAGE_WIDTH / 2 + margin;
194  xEnd = IMAGE_WIDTH - margin;
195  for (x = xStart; x < xEnd; x++) {
196  double s = double (x - xStart) / double (xEnd - xStart);
197  if (s <= 0.25) {
198  y = qFloor (yCenter - yHeight * (4.0 * s));
199  } else if (s < 0.75) {
200  y = qFloor (yCenter - yHeight * (1.0 - 4.0 * (s - 0.25)));
201  } else {
202  y = qFloor (yCenter + yHeight * (1.0 - 4 * (s - 0.75)));
203  }
204 
205  if (!isFirst) {
206  painter.drawLine (xLast, yLast, x, y);
207  }
208  isFirst = false;
209  xLast = x;
210  yLast = y;
211  }
212 
213  return image;
214 }
215 
217 {
218  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::createSubPanel";
219 
220  QWidget *subPanel = new QWidget ();
221  QGridLayout *layout = new QGridLayout (subPanel);
222  subPanel->setLayout (layout);
223 
224  layout->setColumnStretch (0, 1); // Empty first column
225  layout->setColumnStretch (1, 0); // Labels
226  layout->setColumnStretch (2, 0); // User controls
227  layout->setColumnStretch (3, 1); // Empty last column
228 
229  int row = 0;
230  createControls(layout, row);
231  createPreview (layout, row);
232  QPixmap pixmap = QPixmap::fromImage (createPreviewImage());
233  m_scenePreview->addPixmap (pixmap);
234 
235  return subPanel;
236 }
237 
239 {
240  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::handleOk";
241 
243  cmdMediator ().document(),
244  *m_modelSegmentsBefore,
245  *m_modelSegmentsAfter);
246  cmdMediator ().push (cmd);
247 
248  hide ();
249 }
250 
252 {
253  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::load";
254 
255  // Loading starts here
256  m_loading = true;
257 
259 
260  // Flush old data
261  delete m_modelSegmentsBefore;
262  delete m_modelSegmentsAfter;
263 
264  // Save new data
265  m_modelSegmentsBefore = new DocumentModelSegments (cmdMediator.document());
266  m_modelSegmentsAfter = new DocumentModelSegments (cmdMediator.document());
267 
268  // Sanity checks. Incoming defaults must be acceptable to the local limits
269  ENGAUGE_ASSERT (MIN_LENGTH_MIN <= m_modelSegmentsAfter->minLength ());
270  ENGAUGE_ASSERT (MIN_LENGTH_MAX >= m_modelSegmentsAfter->minLength ());
271  ENGAUGE_ASSERT (POINT_SEPARATION_MIN <= m_modelSegmentsAfter->pointSeparation());
272  ENGAUGE_ASSERT (POINT_SEPARATION_MAX >= m_modelSegmentsAfter->pointSeparation());
273 
274  // Populate controls
275  m_spinPointSeparation->setValue (qFloor (m_modelSegmentsAfter->pointSeparation()));
276  m_spinMinLength->setValue (qFloor (m_modelSegmentsAfter->minLength()));
277  m_chkFillCorners->setChecked (m_modelSegmentsAfter->fillCorners ());
278  m_spinLineWidth->setValue (qFloor (m_modelSegmentsAfter->lineWidth()));
279 
280  int indexLineColor = m_cmbLineColor->findData(QVariant (m_modelSegmentsAfter->lineColor()));
281  ENGAUGE_ASSERT (indexLineColor >= 0);
282  m_cmbLineColor->setCurrentIndex(indexLineColor);
283 
284  // Loading finishes here
285  m_loading = false;
286 
287  updateControls();
288  enableOk (false); // Disable Ok button since there not yet any changes
289  updatePreview();
290 }
291 
293 {
294  if (!smallDialogs) {
295  setMinimumHeight (MINIMUM_HEIGHT);
296  }
297 }
298 
299 void DlgSettingsSegments::slotFillCorners (int state)
300 {
301  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotFillCorner";
302 
303  m_modelSegmentsAfter->setFillCorners(state == Qt::Checked);
304  updateControls();
305  updatePreview();
306 }
307 
308 void DlgSettingsSegments::slotLineColor (const QString &)
309 {
310  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotLineColor";
311 
312  m_modelSegmentsAfter->setLineColor(static_cast<ColorPalette> (m_cmbLineColor->currentData().toInt()));
313  updateControls();
314  updatePreview();
315 }
316 
317 void DlgSettingsSegments::slotLineWidth (int lineWidth)
318 {
319  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotLineWidth";
320 
321  m_modelSegmentsAfter->setLineWidth(lineWidth);
322  updateControls();
323  updatePreview();
324 }
325 
326 void DlgSettingsSegments::slotMinLength (const QString &minLength)
327 {
328  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotMinLength";
329 
330  m_modelSegmentsAfter->setMinLength(minLength.toDouble());
331  updateControls();
332  updatePreview();
333 }
334 
335 void DlgSettingsSegments::slotPointSeparation (const QString &pointSeparation)
336 {
337  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotPointSeparation";
338 
339  m_modelSegmentsAfter->setPointSeparation(pointSeparation.toDouble());
340  updateControls();
341  updatePreview();
342 }
343 
344 void DlgSettingsSegments::updateControls()
345 {
346  enableOk (true);
347 }
348 
349 void DlgSettingsSegments::updatePreview()
350 {
351  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::updatePreview"
352  << " loading=" << (m_loading ? "true" : "false");
353 
354  const QString ARBITRARY_IDENTIFIER ("");
355  const QColor COLOR (Qt::blue);
356  const int RADIUS = 5;
357  GeometryWindow *NULL_GEOMETRY_WINDOW = nullptr;
358 
359  if (!m_loading) {
360 
361  SegmentFactory segmentFactory (*m_scenePreview,
362  mainWindow().isGnuplot());
363 
364  clearPoints();
365  segmentFactory.clearSegments (m_segments);
366 
367  // Create new segments
368  segmentFactory.makeSegments (createPreviewImage(),
369  *m_modelSegmentsAfter,
370  m_segments);
371 
372  // Make the segment visible
373  QList<Segment*>::iterator itrS;
374  for (itrS = m_segments.begin(); itrS != m_segments.end(); itrS++) {
375  Segment *segment = *itrS;
376  segment->slotHover (true);
377  }
378 
379  // Create some points
380  PointStyle pointStyle (POINT_SHAPE_CROSS,
381  RADIUS,
382  qFloor (BRUSH_WIDTH),
383  COLOR_PALETTE_BLUE);
384  QPolygonF polygon = pointStyle.polygon();
385  QList<QPoint> points = segmentFactory.fillPoints (*m_modelSegmentsAfter,
386  m_segments);
387  QList<QPoint>::iterator itrP;
388  for (itrP = points.begin(); itrP != points.end(); itrP++) {
389  QPoint pos = *itrP;
390  GraphicsPoint *graphicsPoint = new GraphicsPoint (*m_scenePreview,
391  ARBITRARY_IDENTIFIER,
392  pos,
393  COLOR,
394  polygon,
395  BRUSH_WIDTH,
396  NULL_GEOMETRY_WINDOW);
397 
398  m_points.push_back (graphicsPoint);
399  }
400  }
401 }
virtual void setSmallDialogs(bool smallDialogs)
If false then dialogs have a minimum size so all controls are visible.
double pointSeparation() const
Get method for point separation.
void setLineColor(ColorPalette lineColor)
Set method for line color.
void setMinLength(double minLength)
Set method for min length.
double lineWidth() const
Get method for line width.
void setCmdMediator(CmdMediator &cmdMediator)
Store CmdMediator for easy access by the leaf class.
double minLength() const
Get method for min length.
bool fillCorners() const
Get method for fill corners.
Window that displays the geometry information, as a table, for the current curve.
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:72
void finishPanel(QWidget *subPanel, int minimumWidth=MINIMUM_DIALOG_WIDTH, int minimumHeightOrZero=0)
Add Ok and Cancel buttons to subpanel to get the whole dialog.
virtual void load(CmdMediator &cmdMediator)
Load settings from Document.
void setLineWidth(double lineWidth)
Set method for line width.
Factory class for Segment objects.
void slotHover(bool hover)
Slot for hover enter/leave events in the associated SegmentLines.
Definition: Segment.cpp:528
Class that modifies QGraphicsView to automatically expand/shrink the view to fit the window,...
Definition: ViewPreview.h:14
void setFillCorners(bool fillCorners)
Set method for fill corners.
Details for a specific Point.
Definition: PointStyle.h:20
Selectable piecewise-defined line that follows a filtered line in the image.
Definition: Segment.h:21
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:43
static int MINIMUM_PREVIEW_HEIGHT
Dialog layout constant that guarantees preview has sufficent room.
Command for DlgSettingsSegments.
void enableOk(bool enable)
Let leaf subclass control the Ok button.
Command queue stack.
Definition: CmdMediator.h:23
ColorPalette lineColor() const
Get method for line color.
void populateColorComboWithTransparent(QComboBox &combo)
Add colors in color palette to combobox, with transparent entry at end.
DlgSettingsSegments(MainWindow &mainWindow)
Single constructor.
Model for DlgSettingsSegments and CmdSettingsSegments.
Abstract base class for all Settings dialogs.
virtual void handleOk()
Process slotOk.
virtual QWidget * createSubPanel()
Create dialog-specific panel to which base class will add Ok and Cancel buttons.
void setPointSeparation(double pointSeparation)
Set method for point separation.
MainWindow & mainWindow()
Get method for MainWindow.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:91
CmdMediator & cmdMediator()
Provide access to Document information wrapped inside CmdMediator.
virtual void createOptionalSaveDefault(QHBoxLayout *layout)
Let subclass define an optional Save As Default button.