git-svn-id: file:///svnroot/family-shop-mgr@26 26eb2498-383b-47a6-be48-5d6f36779e85
[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)->setData(column, columnData[column]);\r
94         }\r
95         m_domElementForItem.insert(rootItem->child(rootItem->childCount() - 1),\r
96                                    child);\r
97         child = child.nextSiblingElement("item");\r
98     }\r
99 \r
100 \r
101     connect(rootItem, SIGNAL(childInserted(ShoppingTreeItem*)), this,\r
102             SLOT(registerInsertedChild(ShoppingTreeItem*)));\r
103     connect(rootItem, SIGNAL(childRemoved(ShoppingTreeItem*)), this,\r
104             SLOT(deleteRemovedChild(ShoppingTreeItem*)));\r
105 \r
106     QHashIterator<ShoppingTreeItem*,QDomElement> i(m_domElementForItem);\r
107     while(i.hasNext())\r
108     {\r
109         i.next();\r
110         connect(i.key(), SIGNAL(childInserted(ShoppingTreeItem*)), this,\r
111                 SLOT(registerInsertedChild(ShoppingTreeItem*)));\r
112         connect(i.key(), SIGNAL(childRemoved(ShoppingTreeItem*)), this,\r
113                 SLOT(deleteRemovedChild(ShoppingTreeItem*)));\r
114     }\r
115 }\r
116 \r
117 /*******************************************************************/\r
118 ShoppingTreeModel::~ShoppingTreeModel()\r
119 {\r
120     delete rootItem;\r
121 }\r
122 \r
123 /*******************************************************************/\r
124 bool ShoppingTreeModel::addCategory(QString name)\r
125 {\r
126     QModelIndex parent = QModelIndex();\r
127 \r
128     if (!this->insertRow(parent.row()+1, parent))\r
129         return false;\r
130 \r
131     int column = 0;\r
132     QModelIndex child = this->index(parent.row()+1, column, parent);\r
133     ShoppingTreeItem* item = this->getItem(child);\r
134     item->setItemType(ShoppingTreeItem::Category);\r
135     this->setData(child, QVariant(name), Qt::EditRole);\r
136     return true;\r
137 }\r
138 \r
139 /*******************************************************************/\r
140 bool ShoppingTreeModel::addSubCategory(QString name, int position,\r
141                     const QModelIndex &parent)\r
142 {\r
143     if (!this->insertRow(position, parent))\r
144         return false;\r
145 \r
146     for(int column = 0; column < this->columnCount(parent); ++column)\r
147     {\r
148         QModelIndex child = this->index(parent.row()+1, column, parent);\r
149         ShoppingTreeItem* item = this->getItem(child);\r
150         item->setItemType(ShoppingTreeItem::Category);\r
151         this->setData(child, QVariant(name), Qt::EditRole);\r
152     }\r
153     return true;\r
154 }\r
155 \r
156 /*******************************************************************/\r
157 bool ShoppingTreeModel::addItem(QString name, int position,\r
158              const QModelIndex &parent)\r
159 {\r
160     if (!this->insertRow(position, parent))\r
161         return false;\r
162 \r
163     int column = 0;\r
164     QModelIndex child = this->index(parent.row()+1, column, parent);\r
165     ShoppingTreeItem* item = this->getItem(child);\r
166     item->setItemType(ShoppingTreeItem::Item);\r
167     this->setData(child, QVariant(name), Qt::EditRole);\r
168 \r
169     return true;\r
170 }\r
171 \r
172 /*******************************************************************/\r
173 bool ShoppingTreeModel::removeCategoryOrItem(const QModelIndex &index)\r
174 {\r
175     return this->removeRow(index.row(), index.parent());\r
176 }\r
177 \r
178 /*******************************************************************/\r
179 QVariant ShoppingTreeModel::data(const QModelIndex &index, int role) const\r
180 {\r
181     if(!index.isValid())\r
182         return QVariant();\r
183 \r
184     if(role != Qt::DisplayRole && role != Qt::EditRole)\r
185         return QVariant();\r
186 \r
187     ShoppingTreeItem *item = getItem(index);\r
188     return item->data(index.column());\r
189 }\r
190 \r
191 /*******************************************************************/\r
192 Qt::ItemFlags ShoppingTreeModel::flags(const QModelIndex &index) const\r
193 {\r
194     if(!index.isValid())\r
195         return 0;\r
196 \r
197     return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;\r
198 }\r
199 \r
200 /*******************************************************************/\r
201 ShoppingTreeItem *ShoppingTreeModel::getItem(const QModelIndex &index) const\r
202 {\r
203     if(index.isValid()){\r
204         ShoppingTreeItem *item =\r
205                 static_cast<ShoppingTreeItem*>(index.internalPointer());\r
206         if(item) return item;\r
207     }\r
208 \r
209     return rootItem;\r
210 }\r
211 \r
212 /*******************************************************************/\r
213 QVariant ShoppingTreeModel::headerData(int section, Qt::Orientation orientation,\r
214                                        int role) const\r
215 {\r
216     if(orientation == Qt::Horizontal && role == Qt::DisplayRole)\r
217         return rootItem->data(section);\r
218 \r
219     return QVariant();\r
220 }\r
221 \r
222 /*******************************************************************/\r
223 QModelIndex ShoppingTreeModel::index(int row, int column, const QModelIndex &parent) const\r
224 {\r
225     if(parent.isValid() && parent.column() != 0)\r
226         return QModelIndex();\r
227 \r
228     ShoppingTreeItem *parentItem = getItem(parent);\r
229 \r
230     ShoppingTreeItem *childItem = parentItem->child(row);\r
231     if(childItem)\r
232         return createIndex(row, column, childItem);\r
233     else\r
234         return QModelIndex();\r
235 }\r
236 \r
237 /*******************************************************************/\r
238 bool ShoppingTreeModel::insertColumns(int position, int columns, const QModelIndex &parent)\r
239 {\r
240     bool success;\r
241 \r
242     beginInsertColumns(parent, position, position + columns - 1);\r
243     success = rootItem->insertColumns(position, columns);\r
244     endInsertColumns();\r
245 \r
246     return success;\r
247 }\r
248 \r
249 /*******************************************************************/\r
250 bool ShoppingTreeModel::insertRows(int position, int rows, const QModelIndex &parent)\r
251 {\r
252     ShoppingTreeItem *parentItem = getItem(parent);\r
253     bool success;\r
254 \r
255     beginInsertRows(parent, position, position + rows - 1);\r
256     success = parentItem->insertChildren(position, rows, rootItem->columnCount());\r
257     endInsertRows();\r
258 \r
259     return success;\r
260 }\r
261 \r
262 /*******************************************************************/\r
263 QModelIndex ShoppingTreeModel::parent(const QModelIndex &index) const\r
264 {\r
265     if(!index.isValid())\r
266         return QModelIndex();\r
267 \r
268     ShoppingTreeItem *childItem = getItem(index);\r
269     ShoppingTreeItem *parentItem = childItem->parent();\r
270 \r
271     if(parentItem == rootItem)\r
272         return QModelIndex();\r
273 \r
274     return createIndex(parentItem->childNumber(), 0, parentItem);\r
275 }\r
276 \r
277 /*******************************************************************/\r
278 bool ShoppingTreeModel::removeColumns(int position, int columns, const QModelIndex &parent)\r
279 {\r
280     bool success;\r
281 \r
282     beginRemoveColumns(parent, position, position + columns - 1);\r
283     success = rootItem->removeColumns(position, columns);\r
284     endRemoveColumns();\r
285 \r
286     if (rootItem->columnCount() == 0)\r
287         removeRows(0, rowCount());\r
288 \r
289     return success;\r
290 }\r
291 \r
292 /*******************************************************************/\r
293 bool ShoppingTreeModel::removeRows(int position, int rows, const QModelIndex &parent)\r
294 {\r
295     ShoppingTreeItem *parentItem = getItem(parent);\r
296     bool success;\r
297 \r
298     beginRemoveRows(parent, position, position + rows - 1);\r
299     success = parentItem->removeChildren(position, rows);\r
300     endRemoveRows();\r
301 \r
302     return success;\r
303 }\r
304 \r
305 /*******************************************************************/\r
306 int ShoppingTreeModel::rowCount(const QModelIndex &parent) const\r
307 {\r
308     ShoppingTreeItem *parentItem = getItem(parent);\r
309 \r
310     return parentItem->childCount();\r
311 }\r
312 \r
313 /*******************************************************************/\r
314 int ShoppingTreeModel::columnCount(const QModelIndex &parent) const\r
315 {\r
316     if(parent.isValid())\r
317         return getItem(parent)->columnCount();\r
318     else\r
319         return rootItem->columnCount();\r
320 }\r
321 \r
322 /*******************************************************************/\r
323 bool ShoppingTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)\r
324 {\r
325     if(role != Qt::EditRole)\r
326         return false;\r
327 \r
328     ShoppingTreeItem *item = getItem(index);\r
329 \r
330     // only "items" have more than one editable column\r
331     if(index.column() != 0 && m_domElementForItem.value(item).tagName() != "item")\r
332         return false;\r
333 \r
334     // edit item\r
335     bool result = (item->setData(index.column(),value) &&\r
336                    updateDomElement(item, index.column()));\r
337 \r
338     if(result)\r
339         emit dataChanged(index, index);\r
340 \r
341     return result;\r
342 }\r
343 \r
344 /*******************************************************************/\r
345 bool ShoppingTreeModel::setHeaderData(int section, Qt::Orientation orientation,\r
346                                       const QVariant &value, int role)\r
347 {\r
348     if(role != Qt::EditRole || orientation != Qt::Horizontal)\r
349         return false;\r
350 \r
351     bool result = rootItem->setData(section, value);\r
352 \r
353     if(result)\r
354         emit headerDataChanged(orientation, section, section);\r
355 \r
356     return result;\r
357 }\r
358 \r
359 /*******************************************************************/\r
360 void ShoppingTreeModel::registerInsertedChild(ShoppingTreeItem *item)\r
361 {\r
362     // wait until item type is defined\r
363     item->waitItemTypeDefinition();\r
364 \r
365     QDomElement parentElement = m_domElementForItem.value(item->parent());\r
366     QDomElement element;\r
367     if(item->getItemType() == ShoppingTreeItem::Category)\r
368         element = m_document.createElement("category");\r
369     else if(item->getItemType() == ShoppingTreeItem::Item)\r
370         element = m_document.createElement("item");\r
371     else\r
372         return;\r
373 \r
374     parentElement.appendChild(element);\r
375     updateXmlFile();\r
376     m_domElementForItem.insert(item, element);\r
377     connect(item, SIGNAL(childInserted(ShoppingTreeItem*)), this,\r
378             SLOT(registerInsertedChild(ShoppingTreeItem*)));\r
379     connect(item, SIGNAL(childRemoved(ShoppingTreeItem*)), this,\r
380             SLOT(deleteRemovedChild(ShoppingTreeItem*)));\r
381 }\r
382 \r
383 /*******************************************************************/\r
384 void ShoppingTreeModel::deleteRemovedChild(ShoppingTreeItem *item)\r
385 {\r
386     QDomElement element = m_domElementForItem.value(item);\r
387     QDomNode parentNode = element.parentNode();\r
388     parentNode.removeChild(element);\r
389     updateXmlFile();\r
390     m_domElementForItem.remove(item);\r
391 }\r
392 \r
393 /*******************************************************************/\r
394 void ShoppingTreeModel::parseCategoryElement(const QDomElement &element,\r
395                                              ShoppingTreeItem *parentItem)\r
396 {\r
397     // if parent is null then add category to root\r
398     if(!parentItem)\r
399         parentItem = rootItem;\r
400 \r
401     ShoppingTreeItem *item;\r
402     QDomElement child = element.firstChildElement("title");\r
403     QString title = child.text();\r
404     if(!title.isEmpty())\r
405     {\r
406         parentItem->insertChildren(parentItem->childCount(), 1,\r
407                                    rootItem->columnCount());\r
408 \r
409         parentItem->child(parentItem->childCount() - 1)->\r
410                 setItemType(ShoppingTreeItem::Category);\r
411         parentItem->child(parentItem->childCount() - 1)->setData(0, title);\r
412         m_domElementForItem.insert(parentItem->child(parentItem->childCount() - 1),\r
413                                    element);\r
414         item = parentItem->child(parentItem->childCount() - 1);\r
415     }\r
416     else\r
417     {\r
418         emit invalidDocument();\r
419         return;\r
420     }\r
421 \r
422     // add each sub category and item to the tree\r
423     child = child.nextSiblingElement();\r
424     while(!child.isNull())\r
425     {\r
426         if(child.tagName() == "category")\r
427         {\r
428             parseCategoryElement(child, parentItem);\r
429         }\r
430         else if(child.tagName() == "item")\r
431         {\r
432             item->insertChildren(\r
433                     item->childCount(), 1,\r
434                     rootItem->columnCount());\r
435             QVector<QVariant> columnData =\r
436                     getColumnsFromItemElement(child);\r
437             item->child(item->childCount() - 1)->setItemType(ShoppingTreeItem::Item);\r
438             for(int column = 0; column < columnData.size(); column++)\r
439             {\r
440                 item->child(item->childCount() - 1)->setData(column, columnData[column]);\r
441             }\r
442             m_domElementForItem.insert(item->child(item->childCount() - 1),\r
443                                        child);\r
444         }\r
445         else\r
446         {\r
447             emit invalidDocument();\r
448             return;\r
449         }\r
450 \r
451         child = child.nextSiblingElement();\r
452     }\r
453 }\r
454 \r
455 /*******************************************************************/\r
456 QVector<QVariant> ShoppingTreeModel::getColumnsFromItemElement(const QDomElement &element)\r
457 {\r
458     QVector<QVariant> data;\r
459     QString title = element.firstChildElement("title").text();\r
460     int quantity = element.firstChildElement("quantity").text().toInt();\r
461     QString store = element.firstChildElement("store").text();\r
462     if(title.isEmpty() || quantity < 0)\r
463     {\r
464         emit invalidDocument();\r
465         return data;\r
466     }\r
467 \r
468     data << title << quantity << store;\r
469     return data;\r
470 }\r
471 \r
472 /*******************************************************************/\r
473 bool ShoppingTreeModel::updateDomElement(ShoppingTreeItem *item, int column)\r
474 {\r
475     QDomElement element = m_domElementForItem.value(item);\r
476 \r
477     if(element.isNull())\r
478      return false;\r
479 \r
480     bool success = false;\r
481     switch(column)\r
482     {\r
483         case 0:\r
484         {\r
485             QDomElement oldTitleElement = element.firstChildElement("title");\r
486             QDomElement newTitleElement = m_document.createElement("title");\r
487 \r
488             QDomText newTitleText = m_document.createTextNode(item->data(0).toString());\r
489             newTitleElement.appendChild(newTitleText);\r
490 \r
491             element.replaceChild(newTitleElement, oldTitleElement);\r
492             success = true;\r
493             break;\r
494         }\r
495         case 1:\r
496         {\r
497             QDomElement oldQuantityElement = element.firstChildElement("quantity");\r
498             QDomElement newQuantityElement = m_document.createElement("quantity");\r
499 \r
500             QDomText newQuantityText = m_document.createTextNode(item->data(1).toString());\r
501             newQuantityElement.appendChild(newQuantityText);\r
502 \r
503             element.replaceChild(newQuantityElement, oldQuantityElement);\r
504             success = true;\r
505             break;\r
506         }\r
507         case 2:\r
508         {\r
509             QDomElement oldStoreElement = element.firstChildElement("store");\r
510             QDomElement newStoreElement = m_document.createElement("store");\r
511 \r
512             QDomText newStoreText = m_document.createTextNode(item->data(0).toString());\r
513             newStoreElement.appendChild(newStoreText);\r
514 \r
515             element.replaceChild(newStoreElement, oldStoreElement);\r
516             success = true;\r
517             break;\r
518         }\r
519         default:\r
520             success = false;\r
521     }\r
522 \r
523     QDomElement oldDateElement = element.firstChildElement("lastModified");\r
524     if(!oldDateElement.isNull())\r
525     {\r
526         QDomElement newDateElement = m_document.createElement("lastModified");\r
527 \r
528         QDomText newDateText = m_document.createTextNode(\r
529                 QDateTime::currentDateTime().toString("dd/MM/yyyy-hh:mm:ss"));\r
530         newDateElement.appendChild(newDateText);\r
531 \r
532         element.replaceChild(newDateElement, oldDateElement);\r
533     }\r
534 \r
535     updateXmlFile();\r
536 \r
537     return success;\r
538 }\r
539 \r
540 /*******************************************************************/\r
541 void ShoppingTreeModel::updateXmlFile() const\r
542 {\r
543     QFile xmlFile(m_xmlFileName);\r
544     xmlFile.remove();\r
545     xmlFile.open(QIODevice::WriteOnly);\r
546     xmlFile.write(m_document.toByteArray(4));\r
547     xmlFile.close();\r
548 }\r