2ee4df179bfc764c78b337f6767002796cedaa4d
[family-shop-mgr] / code / family-shop-mgr / ShoppingTreeModel.cpp
1 /*\r
2  * This file is part of family-shop-mgr.\r
3  *\r
4  * family-shop-mgr is free software: you can redistribute it and/or modify\r
5  * it under the terms of the GNU General Public License as published by\r
6  * the Free Software Foundation, either version 3 of the License, or\r
7  * (at your option) any later version.\r
8  *\r
9  * family-shop-mgr is distributed in the hope that it will be useful,\r
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
12  * GNU General Public License for more details.\r
13  *\r
14  * You should have received a copy of the GNU General Public License\r
15  * along with family-shop-mgr.  If not, see <http://www.gnu.org/licenses/>.\r
16  *\r
17  * Author: Unai IRIGOYEN\r
18  * Date: 12/07/2009\r
19  *\r
20  */\r
21 \r
22 #include "ShoppingTreeModel.h"\r
23 \r
24 #include "ShoppingTreeItem.h"\r
25 #include <QFile>\r
26 #include <QApplication>\r
27 #include <QtGui>\r
28 \r
29 /*******************************************************************/\r
30 ShoppingTreeModel::ShoppingTreeModel(const QString &xmlFileName,\r
31                                      QObject *parent) :\r
32 QAbstractItemModel(parent), m_document("ShoppingList")\r
33 {\r
34     QString error;\r
35     int errLine;\r
36     int errColumn;\r
37 \r
38     m_xmlFileName = QApplication::applicationDirPath() + "/" + xmlFileName;\r
39     QFile file(m_xmlFileName);\r
40     if(!file.open(QIODevice::ReadOnly))\r
41         return;\r
42     // Parse xml file\r
43     if(!m_document.setContent(&file, true, &error, &errLine, &errColumn))\r
44     {\r
45         emit xmlParseError(error, errLine, errColumn);\r
46         file.close();\r
47         return;\r
48     }\r
49     file.close();\r
50 \r
51     QDomElement root = m_document.documentElement();\r
52     if(root.tagName() != "shoppingList" || !root.hasAttribute("version"))\r
53     {\r
54         emit invalidDocument();\r
55         return;\r
56     }\r
57     else if(root.attribute("version") == "1.0")\r
58     {\r
59         // set column titles\r
60         QVector<QVariant> rootData;\r
61         rootData << "Category/Item name"\r
62                 << "Quantity" << "Store";\r
63 \r
64         rootItem = new ShoppingTreeItem(rootData);\r
65     }\r
66     else\r
67     {\r
68         // upgrade document version if possible\r
69         ;\r
70     }\r
71 \r
72     QDomElement child = root.firstChildElement("category");\r
73     while(!child.isNull())\r
74     {\r
75         // Parse all categories\r
76         parseCategoryElement(child);\r
77         child = child.nextSiblingElement("category");\r
78     }\r
79 \r
80     child = root.firstChildElement("item");\r
81     while(!child.isNull())\r
82     {\r
83         // parse all items which don't have category\r
84         rootItem->insertChildren(\r
85                 rootItem->childCount(), 1,\r
86                 rootItem->columnCount());\r
87         QVector<QVariant> columnData =\r
88                 getColumnsFromItemElement(child);\r
89         rootItem->child(rootItem->childCount() - 1)->\r
90                 setItemType(ShoppingTreeItem::Item);\r
91         for(int column = 0; column < columnData.size(); column++)\r
92         {\r
93             rootItem->child(rootItem->childCount() - 1)->\r
94                     setData(column, columnData[column]);\r
95         }\r
96         m_domElementForItem.insert(rootItem->child(rootItem->childCount() - 1),\r
97                                    child);\r
98         child = child.nextSiblingElement("item");\r
99     }\r
100 \r
101 \r
102     connect(rootItem, SIGNAL(childItemSet(ShoppingTreeItem*)), this,\r
103             SLOT(registerInsertedChild(ShoppingTreeItem*)));\r
104     connect(rootItem, SIGNAL(childRemoved(ShoppingTreeItem*)), this,\r
105             SLOT(deleteRemovedChild(ShoppingTreeItem*)));\r
106 \r
107     QHashIterator<ShoppingTreeItem*,QDomElement> i(m_domElementForItem);\r
108     while(i.hasNext())\r
109     {\r
110         i.next();\r
111         connect(i.key(), SIGNAL(childItemSet(ShoppingTreeItem*)), this,\r
112                 SLOT(registerInsertedChild(ShoppingTreeItem*)));\r
113         connect(i.key(), SIGNAL(childRemoved(ShoppingTreeItem*)), this,\r
114                 SLOT(deleteRemovedChild(ShoppingTreeItem*)));\r
115     }\r
116 }\r
117 \r
118 /*******************************************************************/\r
119 ShoppingTreeModel::~ShoppingTreeModel()\r
120 {\r
121     delete rootItem;\r
122 }\r
123 \r
124 /*******************************************************************/\r
125 bool ShoppingTreeModel::addCategory(QString name)\r
126 {\r
127     QModelIndex parent = QModelIndex();\r
128 \r
129     if (!this->insertRow(parent.row()+1, parent))\r
130         return false;\r
131 \r
132     int column = 0;\r
133     QModelIndex child = this->index(parent.row()+1, column, parent);\r
134     ShoppingTreeItem* item = this->getItem(child);\r
135     item->setItemType(ShoppingTreeItem::Category);\r
136     this->setData(child, QVariant(name), Qt::EditRole);\r
137     return true;\r
138 }\r
139 \r
140 /*******************************************************************/\r
141 bool ShoppingTreeModel::addSubCategory(QString name, int position,\r
142                     const QModelIndex &parent)\r
143 {\r
144     if (!this->insertRow(position, parent))\r
145         return false;\r
146 \r
147     for(int column = 0; column < this->columnCount(parent); ++column)\r
148     {\r
149         QModelIndex child = this->index(parent.row()+1, column, parent);\r
150         ShoppingTreeItem* item = this->getItem(child);\r
151         item->setItemType(ShoppingTreeItem::Category);\r
152         this->setData(child, QVariant(name), Qt::EditRole);\r
153     }\r
154     return true;\r
155 }\r
156 \r
157 /*******************************************************************/\r
158 bool ShoppingTreeModel::addItem(QString name, int position,\r
159              const QModelIndex &parent)\r
160 {\r
161     if (!this->insertRow(position, parent))\r
162         return false;\r
163 \r
164     int column = 0;\r
165     QModelIndex child = this->index(parent.row()+1, column, parent);\r
166     ShoppingTreeItem* item = this->getItem(child);\r
167     item->setItemType(ShoppingTreeItem::Item);\r
168     this->setData(child, QVariant(name), Qt::EditRole);\r
169 \r
170     return true;\r
171 }\r
172 \r
173 /*******************************************************************/\r
174 bool ShoppingTreeModel::removeCategoryOrItem(const QModelIndex &index)\r
175 {\r
176     return this->removeRow(index.row(), index.parent());\r
177 }\r
178 \r
179 /*******************************************************************/\r
180 QVariant ShoppingTreeModel::data(const QModelIndex &index, int role) const\r
181 {\r
182     if(!index.isValid())\r
183         return QVariant();\r
184 \r
185     if(role != Qt::DisplayRole && role != Qt::EditRole)\r
186         return QVariant();\r
187 \r
188     ShoppingTreeItem *item = getItem(index);\r
189     return item->data(index.column());\r
190 }\r
191 \r
192 /*******************************************************************/\r
193 Qt::ItemFlags ShoppingTreeModel::flags(const QModelIndex &index) const\r
194 {\r
195     if(!index.isValid())\r
196         return 0;\r
197 \r
198     return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;\r
199 }\r
200 \r
201 /*******************************************************************/\r
202 ShoppingTreeItem *ShoppingTreeModel::getItem(const QModelIndex &index) const\r
203 {\r
204     if(index.isValid()){\r
205         ShoppingTreeItem *item =\r
206                 static_cast<ShoppingTreeItem*>(index.internalPointer());\r
207         if(item) return item;\r
208     }\r
209 \r
210     return rootItem;\r
211 }\r
212 \r
213 /*******************************************************************/\r
214 QVariant ShoppingTreeModel::headerData(int section,\r
215                                        Qt::Orientation orientation,\r
216                                        int role) const\r
217 {\r
218     if(orientation == Qt::Horizontal && role == Qt::DisplayRole)\r
219         return rootItem->data(section);\r
220 \r
221     return QVariant();\r
222 }\r
223 \r
224 /*******************************************************************/\r
225 QModelIndex ShoppingTreeModel::index(int row, int column,\r
226                                      const QModelIndex &parent) const\r
227 {\r
228     if(parent.isValid() && parent.column() != 0)\r
229         return QModelIndex();\r
230 \r
231     ShoppingTreeItem *parentItem = getItem(parent);\r
232 \r
233     ShoppingTreeItem *childItem = parentItem->child(row);\r
234     if(childItem)\r
235         return createIndex(row, column, childItem);\r
236     else\r
237         return QModelIndex();\r
238 }\r
239 \r
240 /*******************************************************************/\r
241 bool ShoppingTreeModel::insertColumns(int position, int columns,\r
242                                       const QModelIndex &parent)\r
243 {\r
244     bool success;\r
245 \r
246     beginInsertColumns(parent, position, position + columns - 1);\r
247     success = rootItem->insertColumns(position, columns);\r
248     endInsertColumns();\r
249 \r
250     return success;\r
251 }\r
252 \r
253 /*******************************************************************/\r
254 bool ShoppingTreeModel::insertRows(int position, int rows,\r
255                                    const QModelIndex &parent)\r
256 {\r
257     ShoppingTreeItem *parentItem = getItem(parent);\r
258     bool success;\r
259 \r
260     beginInsertRows(parent, position, position + rows - 1);\r
261     success = parentItem->insertChildren(position, rows,\r
262                                          rootItem->columnCount(), this);\r
263     endInsertRows();\r
264 \r
265     return success;\r
266 }\r
267 \r
268 /*******************************************************************/\r
269 QModelIndex ShoppingTreeModel::parent(const QModelIndex &index) const\r
270 {\r
271     if(!index.isValid())\r
272         return QModelIndex();\r
273 \r
274     ShoppingTreeItem *childItem = getItem(index);\r
275     ShoppingTreeItem *parentItem = childItem->parent();\r
276 \r
277     if(parentItem == rootItem)\r
278         return QModelIndex();\r
279 \r
280     return createIndex(parentItem->childNumber(), 0, parentItem);\r
281 }\r
282 \r
283 /*******************************************************************/\r
284 bool ShoppingTreeModel::removeColumns(int position, int columns, const QModelIndex &parent)\r
285 {\r
286     bool success;\r
287 \r
288     beginRemoveColumns(parent, position, position + columns - 1);\r
289     success = rootItem->removeColumns(position, columns);\r
290     endRemoveColumns();\r
291 \r
292     if (rootItem->columnCount() == 0)\r
293         removeRows(0, rowCount());\r
294 \r
295     return success;\r
296 }\r
297 \r
298 /*******************************************************************/\r
299 bool ShoppingTreeModel::removeRows(int position, int rows, const QModelIndex &parent)\r
300 {\r
301     ShoppingTreeItem *parentItem = getItem(parent);\r
302     bool success;\r
303 \r
304     beginRemoveRows(parent, position, position + rows - 1);\r
305     success = parentItem->removeChildren(position, rows);\r
306     endRemoveRows();\r
307 \r
308     return success;\r
309 }\r
310 \r
311 /*******************************************************************/\r
312 int ShoppingTreeModel::rowCount(const QModelIndex &parent) const\r
313 {\r
314     ShoppingTreeItem *parentItem = getItem(parent);\r
315 \r
316     return parentItem->childCount();\r
317 }\r
318 \r
319 /*******************************************************************/\r
320 int ShoppingTreeModel::columnCount(const QModelIndex &parent) const\r
321 {\r
322     if(parent.isValid())\r
323         return getItem(parent)->columnCount();\r
324     else\r
325         return rootItem->columnCount();\r
326 }\r
327 \r
328 /*******************************************************************/\r
329 bool ShoppingTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)\r
330 {\r
331     if(role != Qt::EditRole)\r
332         return false;\r
333 \r
334     ShoppingTreeItem *item = getItem(index);\r
335 \r
336     // only "items" have more than one editable column\r
337     if(index.column() != 0 && m_domElementForItem.value(item).tagName() != "item")\r
338         return false;\r
339 \r
340     // edit item\r
341     bool result = (item->setData(index.column(),value) &&\r
342                    updateDomElement(item, index.column()));\r
343 \r
344     if(result)\r
345         emit dataChanged(index, index);\r
346 \r
347     return result;\r
348 }\r
349 \r
350 /*******************************************************************/\r
351 bool ShoppingTreeModel::setHeaderData(int section, Qt::Orientation orientation,\r
352                                       const QVariant &value, int role)\r
353 {\r
354     if(role != Qt::EditRole || orientation != Qt::Horizontal)\r
355         return false;\r
356 \r
357     bool result = rootItem->setData(section, value);\r
358 \r
359     if(result)\r
360         emit headerDataChanged(orientation, section, section);\r
361 \r
362     return result;\r
363 }\r
364 \r
365 /*******************************************************************/\r
366 void ShoppingTreeModel::registerInsertedChild(ShoppingTreeItem *item)\r
367 {\r
368     QDomElement parentElement = m_domElementForItem.value(item->parent());\r
369     QDomElement element;\r
370     if(item->getItemType() == ShoppingTreeItem::Category)\r
371         element = m_document.createElement("category");\r
372     else if(item->getItemType() == ShoppingTreeItem::Item)\r
373         element = m_document.createElement("item");\r
374     else\r
375         return;\r
376 \r
377     parentElement.appendChild(element);\r
378     updateXmlFile();\r
379     m_domElementForItem.insert(item, element);\r
380 }\r
381 \r
382 /*******************************************************************/\r
383 void ShoppingTreeModel::deleteRemovedChild(ShoppingTreeItem *item)\r
384 {\r
385     QDomElement element = m_domElementForItem.value(item);\r
386     QDomNode parentNode = element.parentNode();\r
387     parentNode.removeChild(element);\r
388     updateXmlFile();\r
389     m_domElementForItem.remove(item);\r
390 }\r
391 \r
392 /*******************************************************************/\r
393 void ShoppingTreeModel::parseCategoryElement(const QDomElement &element,\r
394                                              ShoppingTreeItem *parentItem)\r
395 {\r
396     // if parent is null then add category to root\r
397     if(!parentItem)\r
398         parentItem = rootItem;\r
399 \r
400     ShoppingTreeItem *item;\r
401     QDomElement child = element.firstChildElement("title");\r
402     QString title = child.text();\r
403     if(!title.isEmpty())\r
404     {\r
405         parentItem->insertChildren(parentItem->childCount(), 1,\r
406                                    rootItem->columnCount());\r
407 \r
408         parentItem->child(parentItem->childCount() - 1)->\r
409                 setItemType(ShoppingTreeItem::Category);\r
410         parentItem->child(parentItem->childCount() - 1)->setData(0, title);\r
411         m_domElementForItem.insert(parentItem->child(parentItem->childCount() - 1),\r
412                                    element);\r
413         item = parentItem->child(parentItem->childCount() - 1);\r
414     }\r
415     else\r
416     {\r
417         emit invalidDocument();\r
418         return;\r
419     }\r
420 \r
421     // add each sub category and item to the tree\r
422     child = child.nextSiblingElement();\r
423     while(!child.isNull())\r
424     {\r
425         if(child.tagName() == "category")\r
426         {\r
427             parseCategoryElement(child, parentItem);\r
428         }\r
429         else if(child.tagName() == "item")\r
430         {\r
431             item->insertChildren(\r
432                     item->childCount(), 1,\r
433                     rootItem->columnCount());\r
434             QVector<QVariant> columnData =\r
435                     getColumnsFromItemElement(child);\r
436             item->child(item->childCount() - 1)->setItemType(ShoppingTreeItem::Item);\r
437             for(int column = 0; column < columnData.size(); column++)\r
438             {\r
439                 item->child(item->childCount() - 1)->setData(column, columnData[column]);\r
440             }\r
441             m_domElementForItem.insert(item->child(item->childCount() - 1),\r
442                                        child);\r
443         }\r
444         else\r
445         {\r
446             emit invalidDocument();\r
447             return;\r
448         }\r
449 \r
450         child = child.nextSiblingElement();\r
451     }\r
452 }\r
453 \r
454 /*******************************************************************/\r
455 QVector<QVariant> ShoppingTreeModel::getColumnsFromItemElement(const QDomElement &element)\r
456 {\r
457     QVector<QVariant> data;\r
458     QString title = element.firstChildElement("title").text();\r
459     int quantity = element.firstChildElement("quantity").text().toInt();\r
460     QString store = element.firstChildElement("store").text();\r
461     if(title.isEmpty() || quantity < 0)\r
462     {\r
463         emit invalidDocument();\r
464         return data;\r
465     }\r
466 \r
467     data << title << quantity << store;\r
468     return data;\r
469 }\r
470 \r
471 /*******************************************************************/\r
472 bool ShoppingTreeModel::updateDomElement(ShoppingTreeItem *item, int column)\r
473 {\r
474     QDomElement element = m_domElementForItem.value(item);\r
475 \r
476     if(element.isNull())\r
477      return false;\r
478 \r
479     bool success = false;\r
480     switch(column)\r
481     {\r
482         case 0:\r
483         {\r
484             QDomElement oldTitleElement = element.firstChildElement("title");\r
485             QDomElement newTitleElement = m_document.createElement("title");\r
486 \r
487             QDomText newTitleText = m_document.createTextNode(item->data(0).toString());\r
488             newTitleElement.appendChild(newTitleText);\r
489 \r
490             element.replaceChild(newTitleElement, oldTitleElement);\r
491             success = true;\r
492             break;\r
493         }\r
494         case 1:\r
495         {\r
496             QDomElement oldQuantityElement = element.firstChildElement("quantity");\r
497             QDomElement newQuantityElement = m_document.createElement("quantity");\r
498 \r
499             QDomText newQuantityText = m_document.createTextNode(item->data(1).toString());\r
500             newQuantityElement.appendChild(newQuantityText);\r
501 \r
502             element.replaceChild(newQuantityElement, oldQuantityElement);\r
503             success = true;\r
504             break;\r
505         }\r
506         case 2:\r
507         {\r
508             QDomElement oldStoreElement = element.firstChildElement("store");\r
509             QDomElement newStoreElement = m_document.createElement("store");\r
510 \r
511             QDomText newStoreText = m_document.createTextNode(item->data(0).toString());\r
512             newStoreElement.appendChild(newStoreText);\r
513 \r
514             element.replaceChild(newStoreElement, oldStoreElement);\r
515             success = true;\r
516             break;\r
517         }\r
518         default:\r
519             success = false;\r
520     }\r
521 \r
522     QDomElement oldDateElement = element.firstChildElement("lastModified");\r
523     if(!oldDateElement.isNull())\r
524     {\r
525         QDomElement newDateElement = m_document.createElement("lastModified");\r
526 \r
527         QDomText newDateText = m_document.createTextNode(\r
528                 QDateTime::currentDateTime().toString("dd/MM/yyyy-hh:mm:ss"));\r
529         newDateElement.appendChild(newDateText);\r
530 \r
531         element.replaceChild(newDateElement, oldDateElement);\r
532     }\r
533 \r
534     updateXmlFile();\r
535 \r
536     return success;\r
537 }\r
538 \r
539 /*******************************************************************/\r
540 void ShoppingTreeModel::updateXmlFile() const\r
541 {\r
542     QFile xmlFile(m_xmlFileName);\r
543     xmlFile.remove();\r
544     xmlFile.open(QIODevice::WriteOnly);\r
545     xmlFile.write(m_document.toByteArray(4));\r
546     xmlFile.close();\r
547 }\r