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 (0),
44  m_viewPreview (0),
45  m_modelSegmentsBefore (0),
46  m_modelSegmentsAfter (0),
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, yLast;
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 = 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, xEnd = IMAGE_WIDTH - margin;
194  for (x = xStart; x < xEnd; x++) {
195  double s = (double) (x - xStart) / (double) (xEnd - xStart);
196  if (s <= 0.25) {
197  y = yCenter - yHeight * (4.0 * s);
198  } else if (s < 0.75) {
199  y = yCenter - yHeight * (1.0 - 4.0 * (s - 0.25));
200  } else {
201  y = yCenter + yHeight * (1.0 - 4 * (s - 0.75));
202  }
203 
204  if (!isFirst) {
205  painter.drawLine (xLast, yLast, x, y);
206  }
207  isFirst = false;
208  xLast = x;
209  yLast = y;
210  }
211 
212  return image;
213 }
214 
216 {
217  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::createSubPanel";
218 
219  QWidget *subPanel = new QWidget ();
220  QGridLayout *layout = new QGridLayout (subPanel);
221  subPanel->setLayout (layout);
222 
223  layout->setColumnStretch (0, 1); // Empty first column
224  layout->setColumnStretch (1, 0); // Labels
225  layout->setColumnStretch (2, 0); // User controls
226  layout->setColumnStretch (3, 1); // Empty last column
227 
228  int row = 0;
229  createControls(layout, row);
230  createPreview (layout, row);
231  QPixmap pixmap = QPixmap::fromImage (createPreviewImage());
232  m_scenePreview->addPixmap (pixmap);
233 
234  return subPanel;
235 }
236 
238 {
239  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::handleOk";
240 
242  cmdMediator ().document(),
243  *m_modelSegmentsBefore,
244  *m_modelSegmentsAfter);
245  cmdMediator ().push (cmd);
246 
247  hide ();
248 }
249 
251 {
252  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::load";
253 
254  // Loading starts here
255  m_loading = true;
256 
258 
259  // Flush old data
260  delete m_modelSegmentsBefore;
261  delete m_modelSegmentsAfter;
262 
263  // Save new data
264  m_modelSegmentsBefore = new DocumentModelSegments (cmdMediator.document());
265  m_modelSegmentsAfter = new DocumentModelSegments (cmdMediator.document());
266 
267  // Sanity checks. Incoming defaults must be acceptable to the local limits
268  ENGAUGE_ASSERT (MIN_LENGTH_MIN <= m_modelSegmentsAfter->minLength ());
269  ENGAUGE_ASSERT (MIN_LENGTH_MAX >= m_modelSegmentsAfter->minLength ());
270  ENGAUGE_ASSERT (POINT_SEPARATION_MIN <= m_modelSegmentsAfter->pointSeparation());
271  ENGAUGE_ASSERT (POINT_SEPARATION_MAX >= m_modelSegmentsAfter->pointSeparation());
272 
273  // Populate controls
274  m_spinPointSeparation->setValue (m_modelSegmentsAfter->pointSeparation());
275  m_spinMinLength->setValue (m_modelSegmentsAfter->minLength());
276  m_chkFillCorners->setChecked (m_modelSegmentsAfter->fillCorners ());
277  m_spinLineWidth->setValue (m_modelSegmentsAfter->lineWidth());
278 
279  int indexLineColor = m_cmbLineColor->findData(QVariant (m_modelSegmentsAfter->lineColor()));
280  ENGAUGE_ASSERT (indexLineColor >= 0);
281  m_cmbLineColor->setCurrentIndex(indexLineColor);
282 
283  // Loading finishes here
284  m_loading = false;
285 
286  updateControls();
287  enableOk (false); // Disable Ok button since there not yet any changes
288  updatePreview();
289 }
290 
292 {
293  if (!smallDialogs) {
294  setMinimumHeight (MINIMUM_HEIGHT);
295  }
296 }
297 
298 void DlgSettingsSegments::slotFillCorners (int state)
299 {
300  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotFillCorner";
301 
302  m_modelSegmentsAfter->setFillCorners(state == Qt::Checked);
303  updateControls();
304  updatePreview();
305 }
306 
307 void DlgSettingsSegments::slotLineColor (const QString &)
308 {
309  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotLineColor";
310 
311  m_modelSegmentsAfter->setLineColor((ColorPalette) m_cmbLineColor->currentData().toInt());
312  updateControls();
313  updatePreview();
314 }
315 
316 void DlgSettingsSegments::slotLineWidth (int lineWidth)
317 {
318  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotLineWidth";
319 
320  m_modelSegmentsAfter->setLineWidth(lineWidth);
321  updateControls();
322  updatePreview();
323 }
324 
325 void DlgSettingsSegments::slotMinLength (const QString &minLength)
326 {
327  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotMinLength";
328 
329  m_modelSegmentsAfter->setMinLength(minLength.toDouble());
330  updateControls();
331  updatePreview();
332 }
333 
334 void DlgSettingsSegments::slotPointSeparation (const QString &pointSeparation)
335 {
336  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::slotPointSeparation";
337 
338  m_modelSegmentsAfter->setPointSeparation(pointSeparation.toDouble());
339  updateControls();
340  updatePreview();
341 }
342 
343 void DlgSettingsSegments::updateControls()
344 {
345  enableOk (true);
346 }
347 
348 void DlgSettingsSegments::updatePreview()
349 {
350  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsSegments::updatePreview"
351  << " loading=" << (m_loading ? "true" : "false");
352 
353  const QString ARBITRARY_IDENTIFIER ("");
354  const QColor COLOR (Qt::blue);
355  const int RADIUS = 5;
356  GeometryWindow *NULL_GEOMETRY_WINDOW = 0;
357 
358  if (!m_loading) {
359 
360  SegmentFactory segmentFactory (*m_scenePreview,
361  mainWindow().isGnuplot());
362 
363  clearPoints();
364  segmentFactory.clearSegments (m_segments);
365 
366  // Create new segments
367  segmentFactory.makeSegments (createPreviewImage(),
368  *m_modelSegmentsAfter,
369  m_segments);
370 
371  // Make the segment visible
372  QList<Segment*>::iterator itrS;
373  for (itrS = m_segments.begin(); itrS != m_segments.end(); itrS++) {
374  Segment *segment = *itrS;
375  segment->slotHover (true);
376  }
377 
378  // Create some points
379  PointStyle pointStyle (POINT_SHAPE_CROSS,
380  RADIUS,
381  BRUSH_WIDTH,
382  COLOR_PALETTE_BLUE);
383  QPolygonF polygon = pointStyle.polygon();
384  QList<QPoint> points = segmentFactory.fillPoints (*m_modelSegmentsAfter,
385  m_segments);
386  QList<QPoint>::iterator itrP;
387  for (itrP = points.begin(); itrP != points.end(); itrP++) {
388  QPoint pos = *itrP;
389  GraphicsPoint *graphicsPoint = new GraphicsPoint (*m_scenePreview,
390  ARBITRARY_IDENTIFIER,
391  pos,
392  COLOR,
393  polygon,
394  BRUSH_WIDTH,
395  NULL_GEOMETRY_WINDOW);
396 
397  m_points.push_back (graphicsPoint);
398  }
399  }
400 }
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:522
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.