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