git-svn-id: file:///svnroot/family-shop-mgr@18 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 <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     QString title = element.firstChildElement("title").text();\r
318     if(!title.isEmpty())\r
319     {\r
320         parentItem->insertChildren(parentItem->childCount(), 1,\r
321                                    rootItem->columnCount());\r
322 \r
323         parentItem->child(parentItem->childCount() - 1)->\r
324                 setItemType(ShoppingTreeItem::Category);\r
325         parentItem->child(parentItem->childCount() - 1)->setData(0, title);\r
326         m_domElementForItem.insert(parentItem->child(parentItem->childCount() - 1),\r
327                                    element);\r
328         item = parentItem->child(parentItem->childCount() - 1);\r
329     }\r
330     else\r
331     {\r
332         emit invalidDocument();\r
333         return;\r
334     }\r
335 \r
336     // add each sub category and item to the tree\r
337     QDomElement child = element.firstChildElement();\r
338     while(!child.isNull())\r
339     {\r
340         if(child.tagName() == "category")\r
341         {\r
342             parseCategoryElement(child, parentItem);\r
343         }\r
344         else if(child.tagName() == "item")\r
345         {\r
346             item->insertChildren(\r
347                     item->childCount(), 1,\r
348                     rootItem->columnCount());\r
349             QVector<QVariant> columnData =\r
350                     getColumnsFromItemElement(child);\r
351             item->child(item->childCount() - 1)->setItemType(ShoppingTreeItem::Item);\r
352             for(int column = 0; column < columnData.size(); column++)\r
353             {\r
354                 item->child(item->childCount() - 1)->setData(column, columnData[column]);\r
355             }\r
356             m_domElementForItem.insert(item->child(item->childCount() - 1),\r
357                                        child);\r
358         }\r
359         else\r
360         {\r
361             emit invalidDocument();\r
362             return;\r
363         }\r
364 \r
365         child = child.nextSiblingElement();\r
366     }\r
367 }\r
368 \r
369 QVector<QVariant> ShoppingTreeModel::getColumnsFromItemElement(const QDomElement &element)\r
370 {\r
371     QVector<QVariant> data;\r
372     QString title = element.firstChildElement("title").text();\r
373     int quantity = element.firstChildElement("quantity").text().toInt();\r
374     QString store = element.firstChildElement("store").text();\r
375     if(title.isEmpty() || quantity < 0)\r
376     {\r
377         emit invalidDocument();\r
378         return data;\r
379     }\r
380 \r
381     data << title << quantity << store;\r
382     return data;\r
383 }\r
384 \r
385 bool ShoppingTreeModel::updateDomElement(ShoppingTreeItem *item, int column)\r
386 {\r
387     QDomElement element = m_domElementForItem.value(item);\r
388 \r
389     if(element.isNull())\r
390      return false;\r
391 \r
392     bool success;\r
393     switch(column)\r
394     {\r
395         case 0:\r
396         {\r
397             QDomElement oldTitleElement = element.firstChildElement("title");\r
398             QDomElement newTitleElement = m_document.createElement("title");\r
399 \r
400             QDomText newTitleText = m_document.createTextNode(item->data(0).toString());\r
401             newTitleElement.appendChild(newTitleText);\r
402 \r
403             element.replaceChild(newTitleElement, oldTitleElement);\r
404             success = true;\r
405             break;\r
406         }\r
407         case 1:\r
408         {\r
409             QDomElement oldQuantityElement = element.firstChildElement("quantity");\r
410             QDomElement newQuantityElement = m_document.createElement("quantity");\r
411 \r
412             QDomText newQuantityText = m_document.createTextNode(item->data(1).toString());\r
413             newQuantityElement.appendChild(newQuantityText);\r
414 \r
415             element.replaceChild(newQuantityElement, oldQuantityElement);\r
416             success = true;\r
417             break;\r
418         }\r
419         case 2:\r
420         {\r
421             QDomElement oldStoreElement = element.firstChildElement("store");\r
422             QDomElement newStoreElement = m_document.createElement("store");\r
423 \r
424             QDomText newStoreText = m_document.createTextNode(item->data(0).toString());\r
425             newStoreElement.appendChild(newStoreText);\r
426 \r
427             element.replaceChild(newStoreElement, oldStoreElement);\r
428             success = true;\r
429             break;\r
430         }\r
431         default:\r
432             success = false;\r
433     }\r
434 \r
435     updateXmlFile();\r
436 \r
437     return success;\r
438 }\r
439 \r
440 void ShoppingTreeModel::updateXmlFile() const\r
441 {\r
442     QFile xmlFile(m_xmlFileName);\r
443     xmlFile.remove();\r
444     xmlFile.open(QIODevice::WriteOnly);\r
445     xmlFile.write(m_document.toByteArray(4));\r
446     xmlFile.close();\r
447 }\r