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