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