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