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