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