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