Engauge Digitizer  2
CurveNameList.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 "CurveNameListEntry.h"
8 #include "CurveNameList.h"
9 #include "DocumentSerialize.h"
10 #include "EngaugeAssert.h"
11 #include "Logger.h"
12 #include "QtToString.h"
13 #include <QVariant>
14 #include <QXmlStreamWriter>
15 
16 const QString PIPE ("|");
17 const QString SPACE (" ");
18 const QString TAB ("\t");
19 
21 {
22 }
23 
24 int CurveNameList::columnCount (const QModelIndex & /* parent */) const
25 {
26  return 3;
27 }
28 
29 bool CurveNameList::containsCurveNameCurrent (const QString &curveName) const
30 {
31  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::containsCurveNameCurrent"
32  << " entryCount=" << m_modelCurvesEntries.count();
33 
34  // Search for curve with matching name
35  QStringList::const_iterator itr;
36  for (itr = m_modelCurvesEntries.begin (); itr != m_modelCurvesEntries.end (); itr++) {
37 
38  CurveNameListEntry curvesEntry (*itr);
39  if (curveName == curvesEntry.curveNameCurrent()) {
40 
41  return true;
42  }
43  }
44 
45  return false;
46 }
47 
48 bool CurveNameList::curveNameIsAcceptable (const QString &curveNameNew,
49  int row) const
50 {
51  // First test is to verify curve name is not empty
52  bool success = (!curveNameNew.isEmpty ());
53 
54  if (success) {
55 
56  // First test was passed. Second test is to check for duplication
57  for (int row1 = 0; row1 < m_modelCurvesEntries.count(); row1++) {
58 
59  // Use table entry except for the one row that gets overridden
60  CurveNameListEntry curvesEntry1 (m_modelCurvesEntries [row1]); // Retrieve entry
61  QString curveNameCurrent1 = (row1 == row ?
62  curveNameNew :
63  curvesEntry1.curveNameCurrent());
64 
65  for (int row2 = row1 + 1; row2 < m_modelCurvesEntries.count(); row2++) {
66 
67  // Use table entry except for the one row that gets overridden
68  CurveNameListEntry curvesEntry2 (m_modelCurvesEntries [row2]); // Retrieve entry
69  QString curveNameCurrent2 = (row2 == row ?
70  curveNameNew :
71  curvesEntry2.curveNameCurrent());
72 
73  if (curveNameCurrent1 == curveNameCurrent2) {
74 
75  // Duplicate!
76  success = false;
77  break;
78  }
79  }
80  }
81  }
82 
83  return success;
84 }
85 
86 QVariant CurveNameList::data (const QModelIndex &index,
87  int role) const
88 {
89  LOG4CPP_DEBUG_S ((*mainCat)) << "CurveNameList::data"
90  << " isRoot=" << (index.isValid () ? "no" : "yes")
91  << " role=" << roleAsString (role).toLatin1 ().data ();
92 
93  if (!index.isValid ()) {
94  // Root item
95  return QVariant ();
96  }
97 
98  int row = index.row ();
99  if (row < 0 || row >= m_modelCurvesEntries.count ()) {
100  return QVariant();
101  }
102 
103  if ((role != Qt::DisplayRole) &&
104  (role != Qt::EditRole)) {
105  return QVariant();
106  }
107 
108  CurveNameListEntry curvesEntry (m_modelCurvesEntries.at (row));
109 
110  if (index.column () == 0) {
111  return curvesEntry.curveNameCurrent();
112  } else if (index.column () == 1) {
113  return curvesEntry.curveNameOriginal();
114  } else if (index.column () == 2) {
115  return curvesEntry.numPoints ();
116  } else {
117  ENGAUGE_ASSERT (false);
118  return curvesEntry.curveNameOriginal(); // Default if asserts are disabled
119  }
120 }
121 
122 
123 Qt::ItemFlags CurveNameList::flags (const QModelIndex &index) const
124 {
125  if (index.isValid ()) {
126 
127  // Not root item. ItemIsDropEnabled is unwanted during dragging since dragged entry would overwrite
128  // another entry if user forgets to drop into the space between successive entries
129  return QAbstractTableModel::flags (index) |
130  Qt::ItemIsDragEnabled |
131  Qt::ItemIsEnabled |
132  Qt::ItemIsSelectable |
133  Qt::ItemIsEditable;
134 
135  } else {
136 
137  // Root item
138  return QAbstractTableModel::flags (index) |
139  Qt::ItemIsDropEnabled;
140 
141  }
142 }
143 
144 QModelIndex CurveNameList::indexForValue (const QModelIndex &indexToSkip,
145  const QVariant &value) const
146 {
147  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::indexForValue";
148 
149  for (int row = 0; row < rowCount(); row++) {
150 
151  QModelIndex indexSearch = index (row, 0);
152 
153  if (indexToSkip != indexSearch) {
154 
155  if (data (indexSearch) == value) {
156 
157  return indexSearch;
158 
159  }
160  }
161  }
162 
163  QModelIndex invalid;
164  return invalid;
165 }
166 
168  int count,
169  const QModelIndex &parent)
170 {
171  bool skip = (count != 1 || row < 0 || row > rowCount () || parent.isValid());
172 
173  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::insertRows"
174  << " row=" << row
175  << " count=" << count
176  << " parentRow=" << parent.row()
177  << " parentCol=" << parent.column()
178  << " isRoot=" << (parent.isValid () ? "no" : "yes")
179  << " skip=" << (skip ? "yes" : "no");
180 
181  if (skip) {
182 
183  // Comments:
184  // 1) Row=-1 means the drag is AFTER the last entry. Although this method can deal with that easily by treating it
185  // as a drag to row=rowCount(), the later call to setData will fail since the row gets set to 0 (which is ambiguous
186  // with a drag to the 0th entry)
187  // 2) Valid parent means we are not adding to the root node, which is what we want to do
188  return false;
189  }
190 
191  QString before = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
192 
193  beginInsertRows (QModelIndex (),
194  row,
195  row + count - 1);
196 
197  CurveNameListEntry emptyCurvesEntry;
198 
199  m_modelCurvesEntries.insert (row,
200  emptyCurvesEntry.toString ());
201 
202  endInsertRows ();
203 
204  QString after = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
205 
206  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::insertRows"
207  << " before=" << before.toLatin1().data()
208  << " after=" << after.toLatin1().data();
209 
210  return true;
211 }
212 
214  int count,
215  const QModelIndex &parent)
216 {
217  bool skip = (count != 1 || row < 0 || row > rowCount () || parent.isValid());
218 
219  LOG4CPP_DEBUG_S ((*mainCat)) << "CurveNameList::removeRows"
220  << " row=" << row
221  << " count=" << count
222  << " isRoot=" << (parent.isValid () ? "no" : "yes")
223  << " skip=" << (skip ? "yes" : "no");
224 
225  bool success = false;
226 
227  beginRemoveRows (QModelIndex (),
228  row,
229  row + count - 1);
230 
231  m_modelCurvesEntries.removeAt (row);
232 
233  endRemoveRows ();
234 
235  return success;
236 }
237 
238 int CurveNameList::rowCount (const QModelIndex & /* parent */) const
239 {
240  int count = m_modelCurvesEntries.count ();
241 
242  LOG4CPP_DEBUG_S ((*mainCat)) << "CurveNameList::rowCount count=" << count;
243 
244  return count;
245 }
246 
247 bool CurveNameList::rowIsUnpopulated (int row) const
248 {
249  // Special case - in a drag to the white space after the last entry (and NOT to the legal drop line just after
250  // the last entry), our insertRows method has already rejected the insert. In this case, at this
251  // point row=0 so we need to check if variable 'row' points to an empty entry (empty+empty+0) or
252  // to a fully-populated entry (nonempty+maybeEmpty+number)
253  QString fields = m_modelCurvesEntries.at (row);
254  CurveNameListEntry entryAtRow (fields);
255  return entryAtRow.entryHasNotBeenPopulated ();
256 }
257 
258 bool CurveNameList::setData (const QModelIndex &index,
259  const QVariant &value,
260  int role)
261 {
262  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::setData"
263  << " indexRow=" << index.row ()
264  << " indexCol=" << index.column ()
265  << " indexValid=" << (index.isValid() ? "valid" : "invalid")
266  << " valueValid=" << (value.isValid () ? "valid" : "invalid")
267  << " value=" << value.toString().toLatin1().data()
268  << " role=" << roleAsString (role).toLatin1 ().data ();
269 
270  bool success = false;
271 
272  if (index.isValid()) {
273 
274  // Process the new entry
275  int row = index.row ();
276  if (row < m_modelCurvesEntries.count ()) {
277 
278  // Variable 'row' points to an empty entry (created by insertRows) so we populate it here
279  success = true;
280 
281  QString before = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
282 
283  if (!value.isValid () && (role == Qt::EditRole)) {
284 
285  // Remove the entry
286  m_modelCurvesEntries.removeAt (row);
287 
288  } else {
289 
290  // Modify the entry
291  CurveNameListEntry curvesEntry (m_modelCurvesEntries [row]); // Retrieve entry
292 
293  if (index.column () == 0) {
294 
295  if (role == Qt::EditRole) {
296 
297  // Does new curve name meet the requirements
298  if (curveNameIsAcceptable (value.toString (),
299  row)) {
300 
301  curvesEntry.setCurveNameCurrent (value.toString ());
302  m_modelCurvesEntries [row] = curvesEntry.toString (); // Save update entry
303 
304  } else {
305 
306  success = false;
307  }
308 
309  } else if ((role == Qt::DisplayRole) ||
310  (curveNameIsAcceptable (value.toString(),
311  row))) {
312 
313  // Above we skipped curve name uniqueness check for Qt::DisplayRole since that role is used when dragging
314  // curve from one place to another, since for a short time the new curve name will coexist
315  // with the old curve name (until the old entry is removed)
316 
317  if (rowIsUnpopulated (row)) {
318  success = true;
319  curvesEntry.setCurveNameCurrent (value.toString ());
320  curvesEntry.setNumPoints (0);
321  m_modelCurvesEntries [row] = curvesEntry.toString (); // Save update entry
322  tryToRemoveOriginalCopy (index,
323  value,
324  role);
325  } else {
326  success = false;
327  }
328  }
329  } else if (index.column () == 1) {
330  curvesEntry.setCurveNameOriginal (value.toString ());
331  m_modelCurvesEntries [row] = curvesEntry.toString (); // Save update entry
332  } else if (index.column () == 2) {
333  curvesEntry.setNumPoints (value.toInt ());
334  m_modelCurvesEntries [row] = curvesEntry.toString (); // Save update entry
335  } else {
336  ENGAUGE_ASSERT (false);
337  }
338 
339  if (success) {
340  emit dataChanged (index,
341  index);
342  }
343 
344  QString after = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
345 
346  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::setData setting"
347  << " before=" << before.toLatin1().data()
348  << " after=" << after.toLatin1().data();
349 
350  }
351  }
352  }
353 
354  return success;
355 }
356 
357 Qt::DropActions CurveNameList::supportedDropActions () const
358 {
359  return Qt::MoveAction;
360 }
361 
362 void CurveNameList::tryToRemoveOriginalCopy (const QModelIndex &index,
363  const QVariant &value,
364  int role)
365 {
366  // After the copy part of a move maneuver, the old entry must be identified and removed
367  if (index.column () == 0 && role == Qt::DisplayRole) {
368  QModelIndex indexToRemove = indexForValue (index,
369  value); // Returns Invalid if no duplicate entry was found
370  if (indexToRemove.isValid()) {
371 
372  QString before = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
373 
374  beginRemoveRows (QModelIndex (),
375  indexToRemove.row(),
376  indexToRemove.row());
377  m_modelCurvesEntries.removeAt (indexToRemove.row ());
378  endRemoveRows ();
379 
380  emit dataChanged (indexToRemove,
381  indexToRemove);
382 
383  QString after = m_modelCurvesEntries.join (PIPE).replace (TAB, SPACE);
384 
385  LOG4CPP_INFO_S ((*mainCat)) << "CurveNameList::setData removed"
386  << " indexRow=" << indexToRemove.row ()
387  << " indexCol=" << indexToRemove.column ()
388  << " before=" << before.toLatin1().data()
389  << " after=" << after.toLatin1().data();
390  }
391  }
392 }
bool containsCurveNameCurrent(const QString &curveName) const
Return true if specified curve name is already in the list.
virtual bool insertRows(int row, int count, const QModelIndex &parent=QModelIndex())
Insert one row.
Utility class for converting the QVariant in CurveNameList to/from the curve names as QStrings...
bool entryHasNotBeenPopulated() const
Return true if entry is unpopulated. This is true between insertRows (where added to model) and setDa...
virtual Qt::DropActions supportedDropActions() const
Allow dragging for reordering.
void setCurveNameCurrent(const QString &curveNameCurrent)
Set method for current curve name.
virtual Qt::ItemFlags flags(const QModelIndex &index) const
Override normal flags with additional editing flags.
QString toString() const
QString for creating QVariant.
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.
virtual bool removeRows(int row, int count, const QModelIndex &parent)
Remove one row.
void setCurveNameOriginal(const QString &curveNameOriginal)
Set method for original curve name.
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
One row per curve name.
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const
Columns are current curve name in first column, and original curve name in second column...
QString curveNameCurrent() const
Curve name displayed in DlgSettingsCurveAddRemove.
CurveNameList()
Default constructor.
void setNumPoints(int numPoints)
Set method for point count.