Engauge Digitizer  2
DlgSettingsCurveAddRemove.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 "CmdSettingsCurveAddRemove.h"
9 #include "CurveNameList.h"
10 #include "DlgSettingsCurveAddRemove.h"
11 #include "EngaugeAssert.h"
12 #include "Logger.h"
13 #include "MainWindow.h"
14 #include <QCheckBox>
15 #include <QDebug>
16 #include <QGridLayout>
17 #include <QLabel>
18 #include <QListView>
19 #include <QMessageBox>
20 #include <QPushButton>
21 #include <QSettings>
22 #include <QSpacerItem>
23 #include "QtToString.h"
24 #include "Settings.h"
25 #include "SettingsForGraph.h"
26 
28  DlgSettingsAbstractBase (tr ("Curve Add/Remove"),
29  "DlgSettingsCurveAddRemove",
30  mainWindow)
31 {
32  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::DlgSettingsCurveAddRemove";
33 
34  QWidget *subPanel = createSubPanel ();
35  finishPanel (subPanel);
36 }
37 
38 DlgSettingsCurveAddRemove::~DlgSettingsCurveAddRemove()
39 {
40  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::~DlgSettingsCurveAddRemove";
41 }
42 
43 void DlgSettingsCurveAddRemove::appendCurveName (const QString &curveNameNew,
44  const QString &curveNameOriginal,
45  int numPoints)
46 {
47  ENGAUGE_CHECK_PTR (m_curveNameList);
48 
49  int row = m_curveNameList->rowCount ();
50  insertCurveName (row,
51  curveNameNew,
52  curveNameOriginal,
53  numPoints);
54 }
55 
56 void DlgSettingsCurveAddRemove::createButtons (QGridLayout *layout,
57  int &row)
58 {
59  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::createButtons";
60 
61  m_btnAdd = new QPushButton (tr ("Add..."));
62  m_btnAdd->setWhatsThis (tr ("Adds a new curve to the curve list. The curve name can be edited in the curve name list.\n\n"
63  "Every curve name must be unique"));
64  m_btnAdd->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
65  connect (m_btnAdd, SIGNAL (released ()), this, SLOT (slotNew()));
66  layout->addWidget (m_btnAdd, row, 1, 1, 1, Qt::AlignLeft);
67 
68  m_btnRemove = new QPushButton (tr ("Remove"));
69  m_btnRemove->setWhatsThis (tr ("Removes the currently selected curve from the curve list.\n\n"
70  "There must always be at least one curve"));
71  m_btnRemove->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
72  connect (m_btnRemove, SIGNAL (released ()), this, SLOT (slotRemove()));
73  layout->addWidget (m_btnRemove, row++, 2, 1, 1, Qt::AlignRight);
74 }
75 
76 void DlgSettingsCurveAddRemove::createListCurves (QGridLayout *layout,
77  int &row)
78 {
79  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::createListCurves";
80 
81  QLabel *label = new QLabel (tr ("Curve Names:"));
82  layout->addWidget (label, row++, 1);
83 
84  m_curveNameList = new CurveNameList;
85 
86  // There is no Qt::ItemIsEditable flag for QListView, so instead we set that flag for the QListViewItems
87  m_listCurves = new QListView;
88  m_listCurves->setWhatsThis (tr ("List of the curves belonging to this document.\n\n"
89  "Click on a curve name to edit it. Each curve name must be unique.\n\n"
90  "Reorder curves by dragging them around."));
91  m_listCurves->setMinimumHeight (200);
92  m_listCurves->setSelectionMode (QAbstractItemView::ExtendedSelection);
93  m_listCurves->setDefaultDropAction (Qt::MoveAction);
94  m_listCurves->setDragDropOverwriteMode (true);
95  m_listCurves->setDragEnabled (true);
96  m_listCurves->setDropIndicatorShown (true);
97  m_listCurves->setDragDropMode (QAbstractItemView::InternalMove);
98  m_listCurves->setViewMode (QListView::ListMode);
99  m_listCurves->setMovement (QListView::Snap);
100  m_listCurves->setModel (m_curveNameList);
101  layout->addWidget (m_listCurves, row++, 1, 1, 2);
102  connect (m_curveNameList, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex &, const QVector<int> &)),
103  this, SLOT (slotDataChanged (const QModelIndex &, const QModelIndex &, const QVector<int> &)));
104  connect (m_listCurves->selectionModel (), SIGNAL (selectionChanged (QItemSelection, QItemSelection)),
105  this, SLOT (slotSelectionChanged (QItemSelection, QItemSelection)));
106 }
107 
109 {
110  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::createOptionalSaveDefault";
111 
112  m_btnSaveDefault = new QPushButton (tr ("Save As Default"));
113  m_btnSaveDefault->setWhatsThis (tr ("Save the curve names for use as defaults for future graph curves."));
114  connect (m_btnSaveDefault, SIGNAL (released ()), this, SLOT (slotSaveDefault ()));
115  layout->addWidget (m_btnSaveDefault, 0, Qt::AlignLeft);
116 
117  QSpacerItem *spacer = new QSpacerItem (40, 2);
118  layout->addItem (spacer);
119 }
120 
122 {
123  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::createSubPanel";
124 
125  const int EMPTY_COLUMN_WIDTH = 30;
126 
127  QWidget *subPanel = new QWidget ();
128  QGridLayout *layout = new QGridLayout (subPanel);
129  subPanel->setLayout (layout);
130 
131  int row = 1;
132  createListCurves (layout, row);
133  createButtons (layout, row);
134 
135  layout->setColumnStretch (0, 0); // Empty first column
136  layout->setColumnMinimumWidth (0, EMPTY_COLUMN_WIDTH);
137  layout->setColumnStretch (1, 1); // New
138  layout->setColumnStretch (2, 1); // Remove
139  layout->setColumnStretch (3, 0); // Empty last column
140  layout->setColumnMinimumWidth (3, EMPTY_COLUMN_WIDTH);
141 
142  return subPanel;
143 }
144 
145 bool DlgSettingsCurveAddRemove::endsWithNumber (const QString &str) const
146 {
147  bool success = false;
148 
149  if (!str.isEmpty ()) {
150 
151  success = (str.right (1).at (0).digitValue() >= 0);
152  }
153 
154  return success;
155 }
156 
158 {
159  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::handleOk";
160 
162  cmdMediator ().document(),
163  *m_curveNameList);
164  cmdMediator ().push (cmd);
165 
166  hide ();
167 }
168 
169 void DlgSettingsCurveAddRemove::insertCurveName (int row,
170  const QString &curveNameNew,
171  const QString &curveNameOriginal,
172  int numPoints)
173 {
174  if (m_curveNameList->insertRow (row)) {
175 
176  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::insertCurveName curveName=" << curveNameNew.toLatin1 ().data ();
177 
178  CurveNameListEntry curvesEntry (curveNameNew,
179  curveNameOriginal,
180  numPoints);
181 
182  m_curveNameList->setData (m_curveNameList->index (row, 0),
183  curvesEntry.curveNameCurrent ());
184  m_curveNameList->setData (m_curveNameList->index (row, 1),
185  curvesEntry.curveNameOriginal ());
186  m_curveNameList->setData (m_curveNameList->index (row, 2),
187  numPoints);
188 
189  } else {
190 
191  LOG4CPP_ERROR_S ((*mainCat)) << "DlgSettingsCurveAddRemove::insertCurveName failed curveName="
192  << curveNameNew.toLatin1 ().data ();
193 
194  }
195 }
196 
198 {
199  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::load";
200 
201  setCmdMediator (cmdMediator);
202 
203  // Remove any data from previous showing of dialog
204  while (m_curveNameList->rowCount () > 0) {
205  m_curveNameList->removeRow (0);
206  }
207 
208  QStringList curveNames = cmdMediator.curvesGraphsNames ();
209  QStringList::const_iterator itr;
210  for (itr = curveNames.begin (); itr != curveNames.end (); itr++) {
211  QString curveName = *itr;
212  appendCurveName (curveName,
213  curveName,
214  cmdMediator.curvesGraphsNumPoints (curveName));
215  }
216 
217  enableOk (false); // Disable Ok button since there not yet any changes
218 }
219 
220 QString DlgSettingsCurveAddRemove::nextCurveName () const
221 {
222  const QString DASH_ONE ("-1"); // Nice value to start a new range at a lower level than the current level
223 
224  ENGAUGE_CHECK_PTR (m_listCurves);
225 
226  int numSelectedItems = m_listCurves->selectionModel ()->selectedIndexes ().count ();
227  int numItems = m_listCurves->model ()->rowCount ();
228 
229  // Determine index where new entry will be inserted
230  int currentIndex = -1;
231  if ((numSelectedItems == 0) &&
232  (numItems > 0)) {
233 
234  // Append after list which has at least one entry
235  currentIndex = numItems;
236 
237  } else if (numSelectedItems == 1) {
238 
239  // Insert before the selected index
240  currentIndex = m_listCurves->selectionModel ()->selectedIndexes ().at (0).row ();
241 
242  }
243 
244  // Curves names of existing before/after curves
245  QString curveNameBefore, curveNameAfter;
246  if (currentIndex > 0) {
247 
248  QModelIndex index = m_curveNameList->index (currentIndex - 1, 0);
249  curveNameBefore = m_curveNameList->data (index).toString ();
250 
251  }
252 
253  if ((0 <= currentIndex) && (currentIndex < numItems)) {
254 
255  QModelIndex index = m_curveNameList->index (currentIndex, 0);
256  curveNameAfter = m_curveNameList->data (index).toString ();
257 
258  }
259 
260  // New curve name computed from previous curve name
261  QString curveNameNext;
262  if (curveNameBefore.isEmpty () && !curveNameAfter.isEmpty () && endsWithNumber (curveNameAfter)) {
263 
264  // Pick a name before curveNameAfter
265  int numberAfter = numberAtEnd (curveNameAfter);
266  int numberNew = numberAfter - 1;
267  int pos = curveNameAfter.lastIndexOf (QString::number (numberAfter));
268  if (pos >= 0) {
269 
270  curveNameNext = QString ("%1%2")
271  .arg (curveNameAfter.left (pos))
272  .arg (numberNew);
273 
274  } else {
275 
276  curveNameNext = curveNameAfter; // Better than nothing
277 
278  }
279 
280  } else if (curveNameBefore.isEmpty ()) {
281 
282  curveNameNext = DEFAULT_GRAPH_CURVE_NAME; // If necessary, this will be deconflicted below
283 
284  } else {
285 
286  curveNameNext = curveNameBefore; // This will be deconflicted below
287 
288  if (endsWithNumber (curveNameBefore)) {
289 
290  // Curve name ends with a number. Pick a name after curveNameBefore, being sure to not match curveNameAfter
291  int numberBefore = numberAtEnd (curveNameBefore);
292  int numberNew = numberBefore + 1;
293  int pos = curveNameBefore.lastIndexOf (QString::number (numberBefore));
294  if (pos >= 0) {
295 
296  curveNameNext = QString ("%1%2")
297  .arg (curveNameBefore.left (pos))
298  .arg (numberNew);
299  if (curveNameNext == curveNameAfter) {
300 
301  // The difference between before and after is exactly one so we go to a lower level
302  curveNameNext = QString ("%1%2")
303  .arg (curveNameBefore)
304  .arg (DASH_ONE);
305  }
306  }
307  }
308  }
309 
310  // Curve name from settings takes precedence
311  SettingsForGraph settingsForGraph;
312  int indexOneBasedNext = numItems + 1;
313  curveNameNext = settingsForGraph.defaultCurveName (indexOneBasedNext,
314  curveNameNext);
315 
316  // At this point we have curveNameNext which does not conflict with curveNameBefore or
317  // curveNameAfter, but it may in rare cases conflict with some other curve name. We keep
318  // adding to the name until there is no conflict
319  while (m_curveNameList->containsCurveNameCurrent (curveNameNext)) {
320  curveNameNext += DASH_ONE;
321  }
322 
323  return curveNameNext;
324 }
325 
326 int DlgSettingsCurveAddRemove::numberAtEnd (const QString &str) const
327 {
328  ENGAUGE_ASSERT (endsWithNumber (str));
329 
330  // Go backward until the first nondigit
331  int sign = +1;
332  int ch = str.size () - 1;
333  while (str.at (ch).digitValue() >= 0) {
334  --ch;
335 
336  if (ch < 0) {
337  break;
338  }
339  }
340  ++ch;
341 
342  return sign * str.mid (ch).toInt ();
343 }
344 
345 void DlgSettingsCurveAddRemove::removeSelectedCurves ()
346 {
347  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::removeSelectedCurves";
348 
349  for (int i = m_listCurves->selectionModel ()->selectedIndexes ().count () - 1; i >= 0; i--) {
350 
351  int row = m_listCurves->selectionModel ()->selectedIndexes ().at (i).row ();
352 
353  m_curveNameList->removeRow (row);
354  }
355 }
356 
357 void DlgSettingsCurveAddRemove::slotDataChanged (const QModelIndex &topLeft,
358  const QModelIndex &bottomRight,
359  const QVector<int> &roles)
360 {
361  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::slotDataChanged"
362  << " topLeft=(" << topLeft.row () << "," << topLeft.column () << ")"
363  << " bottomRight=(" << bottomRight.row () << "," << bottomRight.column () << ")"
364  << " roles=" << rolesAsString (roles).toLatin1 ().data ();
365 
366  updateControls ();
367 }
368 
369 void DlgSettingsCurveAddRemove::slotNew ()
370 {
371  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::slotNew";
372 
373  const QString NO_ORIGINAL_CURVE_NAME;
374  const int NO_POINTS = 0;
375 
376  QString curveNameSuggestion = nextCurveName ();
377 
378  appendCurveName (curveNameSuggestion,
379  NO_ORIGINAL_CURVE_NAME,
380  NO_POINTS);
381 
382  updateControls();
383 }
384 
385 void DlgSettingsCurveAddRemove::slotRemove ()
386 {
387  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::slotRemove";
388 
389  int numPoints = 0;
390  for (int i = 0; i < m_listCurves->selectionModel ()->selectedIndexes ().count (); i++) {
391 
392  int row = m_listCurves->selectionModel ()->selectedIndexes ().at (i).row ();
393  QModelIndex idx = m_curveNameList->index (row, CurveNameListEntry::COL_NUM_POINTS ());
394  int curvePoints = m_curveNameList->data (idx, Qt::DisplayRole).toInt ();
395 
396  numPoints += curvePoints;
397  }
398 
399  int rtn = QMessageBox::Ok;
400  if (numPoints > 0) {
401 
402  QString msg;
403  if (m_listCurves->selectionModel ()->selectedIndexes ().count () == 1) {
404  msg = QString ("%1 %2 %3")
405  .arg (tr ("Removing this curve will also remove"))
406  .arg (numPoints)
407  .arg (tr ("points. Continue?"));
408  } else {
409  msg = QString ("%1 %2 %3")
410  .arg (tr ("Removing these curves will also remove"))
411  .arg (numPoints)
412  .arg (tr ("points. Continue?"));
413  }
414 
415  rtn = QMessageBox::warning (0,
416  tr ("Curves With Points"),
417  msg,
418  QMessageBox::Ok,
419  QMessageBox::Cancel);
420  }
421 
422  if (rtn == QMessageBox::Ok) {
423  removeSelectedCurves ();
424  }
425 
426  updateControls();
427 }
428 
429 void DlgSettingsCurveAddRemove::slotSaveDefault()
430 {
431  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::slotSaveDefault";
432 
433  QSettings settings (SETTINGS_ENGAUGE, SETTINGS_DIGITIZER);
434 
435  for (int row = 0; row < m_curveNameList->rowCount (); row++) {
436 
437  QModelIndex idxCurrent = m_curveNameList->index (row, 0);
438 
439  QString curveNameCurrent = m_curveNameList->data (idxCurrent).toString ();
440 
441  int indexOneBased = row + 1;
442 
443  SettingsForGraph settingsForGraph;
444  QString groupName = settingsForGraph.groupNameForNthCurve (indexOneBased);
445 
446  settings.beginGroup (groupName);
447  settings.setValue (SETTINGS_CURVE_NAME,
448  curveNameCurrent);
449  settings.endGroup ();
450  }
451 }
452 
453 void DlgSettingsCurveAddRemove::slotSelectionChanged (QItemSelection, QItemSelection)
454 {
455  updateControls ();
456 }
457 
458 void DlgSettingsCurveAddRemove::updateControls ()
459 {
460  LOG4CPP_INFO_S ((*mainCat)) << "DlgSettingsCurveAddRemove::updateControls";
461 
462  enableOk (true);
463 
464  ENGAUGE_CHECK_PTR (m_listCurves);
465 
466  int numSelectedItems = m_listCurves->selectionModel ()->selectedIndexes ().count ();
467  int numItems = m_curveNameList->rowCount ();
468 
469  // Leave at least one curve
470  m_btnRemove->setEnabled ((numSelectedItems > 0) && (numSelectedItems < numItems));
471 }
Manage storage and retrieval of the settings for the curves.
void setCmdMediator(CmdMediator &cmdMediator)
Store CmdMediator for easy access by the leaf class.
bool containsCurveNameCurrent(const QString &curveName) const
Return true if specified curve name is already in the list.
Utility class for converting the QVariant in CurveNameList to/from the curve names as QStrings...
QString defaultCurveName(int indexOneBased, const QString &defaultName) const
Default graph name for the specified curve index.
Command for DlgSettingsCurveAddRemove.
virtual void createOptionalSaveDefault(QHBoxLayout *layout)
Let subclass define an optional Save As Default button.
virtual QWidget * createSubPanel()
Create dialog-specific panel to which base class will add Ok and Cancel buttons.
QString groupNameForNthCurve(int indexOneBased) const
Return the group name, that appears in the settings file/registry, for the specified curve index...
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole)
Store one curve name data.
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Retrieve data from model.
void finishPanel(QWidget *subPanel)
Add Ok and Cancel buttons to subpanel to get the whole dialog.
static int COL_NUM_POINTS()
Get method for number of points constant.
void load(CmdMediator &cmdMediator)
Load settings from Document.
void enableOk(bool enable)
Let leaf subclass control the Ok button.
Command queue stack.
Definition: CmdMediator.h:23
Abstract base class for all Settings dialogs.
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
One row per curve name.
Model for DlgSettingsCurveAddRemove and CmdSettingsCurveAddRemove.
Definition: CurveNameList.h:16
int curvesGraphsNumPoints(const QString &curveName) const
See CurvesGraphs::curvesGraphsNumPoints.
Definition: CmdMediator.cpp:67
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
DlgSettingsCurveAddRemove(MainWindow &mainWindow)
Single constructor.
QStringList curvesGraphsNames() const
See CurvesGraphs::curvesGraphsNames.
Definition: CmdMediator.cpp:62
CmdMediator & cmdMediator()
Provide access to Document information wrapped inside CmdMediator.
virtual void handleOk()
Process slotOk.