1/* This file is part of the KDE project
2 Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
3 Copyright 2005-2006 Inge Wallin <inge@lysator.liu.se>
4 Copyright 2004 Ariya Hidayat <ariya@kde.org>
5 Copyright 2002-2003 Norbert Andres <nandres@web.de>
6 Copyright 2000-2002 Laurent Montel <montel@kde.org>
7 Copyright 2002 John Dailey <dailey@vt.edu>
8 Copyright 2002 Phillip Mueller <philipp.mueller@gmx.de>
9 Copyright 2000 Werner Trobin <trobin@kde.org>
10 Copyright 1999-2000 Simon Hausmann <hausmann@kde.org>
11 Copyright 1999 David Faure <faure@kde.org>
12 Copyright 1998-2000 Torben Weis <weis@kde.org>
13
14 This library is free software; you can redistribute it and/or
15 modify it under the terms of the GNU Library General Public
16 License as published by the Free Software Foundation; either
17 version 2 of the License, or (at your option) any later version.
18
19 This library is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 Library General Public License for more details.
23
24 You should have received a copy of the GNU Library General Public License
25 along with this library; see the file COPYING.LIB. If not, write to
26 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27 Boston, MA 02110-1301, USA.
28*/
29
30// Local
31#include "NamedAreaManager.h"
32
33// Qt
34#include <QHash>
35
36// Calligra
37#include <KoXmlNS.h>
38#include <KoXmlWriter.h>
39
40// KSpread
41#include "CellStorage.h"
42#include "FormulaStorage.h"
43#include "LoadingInfo.h"
44#include "Map.h"
45#include "Region.h"
46#include "Sheet.h"
47#include "Util.h"
48
49using namespace Calligra::Sheets;
50
51struct NamedArea {
52 QString name;
53 Sheet* sheet;
54 QRect range;
55};
56
57class NamedAreaManager::Private
58{
59public:
60 const Map* map;
61 QHash<QString, NamedArea> namedAreas;
62};
63
64NamedAreaManager::NamedAreaManager(const Map* map)
65 : d(new Private)
66{
67 d->map = map;
68 connect(this, SIGNAL(namedAreaAdded(QString)),
69 this, SIGNAL(namedAreaModified(QString)));
70 connect(this, SIGNAL(namedAreaRemoved(QString)),
71 this, SIGNAL(namedAreaModified(QString)));
72}
73
74NamedAreaManager::~NamedAreaManager()
75{
76 delete d;
77}
78
79void NamedAreaManager::insert(const Region& region, const QString& name)
80{
81 // NOTE Stefan: Only contiguous regions are supported (OpenDocument compatibility).
82 Q_ASSERT(!name.isEmpty());
83 NamedArea namedArea;
84 namedArea.range = region.lastRange();
85 namedArea.sheet = region.lastSheet();
86 namedArea.name = name;
87 namedArea.sheet->cellStorage()->setNamedArea(Region(region.lastRange(), region.lastSheet()), name);
88 d->namedAreas[name] = namedArea;
89 emit namedAreaAdded(name);
90}
91
92void NamedAreaManager::remove(const QString& name)
93{
94 if (!d->namedAreas.contains(name))
95 return;
96 NamedArea namedArea = d->namedAreas.value(name);
97 namedArea.sheet->cellStorage()->removeNamedArea(Region(namedArea.range, namedArea.sheet), name);
98 d->namedAreas.remove(name);
99 emit namedAreaRemoved(name);
100 const QList<Sheet*> sheets = namedArea.sheet->map()->sheetList();
101 foreach(Sheet* sheet, sheets) {
102 const QString tmp = '\'' + name + '\'';
103 const FormulaStorage* const storage = sheet->formulaStorage();
104 for (int c = 0; c < storage->count(); ++c) {
105 if (storage->data(c).expression().contains(tmp)) {
106 Cell(sheet, storage->col(c), storage->row(c)).makeFormula();
107 }
108 }
109 }
110}
111
112void NamedAreaManager::remove(Sheet* sheet)
113{
114 const QList<NamedArea> namedAreas = d->namedAreas.values();
115 for (int i = 0; i < namedAreas.count(); ++i) {
116 if (namedAreas[i].sheet == sheet)
117 remove(namedAreas[i].name);
118 }
119}
120
121Calligra::Sheets::Region NamedAreaManager::namedArea(const QString& name) const
122{
123 if (!d->namedAreas.contains(name))
124 return Region();
125 const NamedArea namedArea = d->namedAreas.value(name);
126 return Region(namedArea.range, namedArea.sheet);
127}
128
129Sheet* NamedAreaManager::sheet(const QString& name) const
130{
131 if (!d->namedAreas.contains(name))
132 return 0;
133 return d->namedAreas.value(name).sheet;
134}
135
136bool NamedAreaManager::contains(const QString& name) const
137{
138 return d->namedAreas.contains(name);
139}
140
141QList<QString> NamedAreaManager::areaNames() const
142{
143 return d->namedAreas.keys();
144}
145
146void NamedAreaManager::regionChanged(const Region& region)
147{
148 Sheet* sheet;
149 QList< QPair<QRectF, QString> > namedAreas;
150 Region::ConstIterator end(region.constEnd());
151 for (Region::ConstIterator it = region.constBegin(); it != end; ++it) {
152 sheet = (*it)->sheet();
153 namedAreas = sheet->cellStorage()->namedAreas(Region((*it)->rect(), sheet));
154 for (int j = 0; j < namedAreas.count(); ++j) {
155 Q_ASSERT(d->namedAreas.contains(namedAreas[j].second));
156 d->namedAreas[namedAreas[j].second].range = namedAreas[j].first.toRect();
157 emit namedAreaModified(namedAreas[j].second);
158 }
159 }
160}
161
162void NamedAreaManager::updateAllNamedAreas()
163{
164 QList< QPair<QRectF, QString> > namedAreas;
165 const QRect rect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax));
166 const QList<Sheet*> sheets = d->map->sheetList();
167 for (int i = 0; i < sheets.count(); ++i) {
168 namedAreas = sheets[i]->cellStorage()->namedAreas(Region(rect, sheets[i]));
169 for (int j = 0; j < namedAreas.count(); ++j) {
170 Q_ASSERT(d->namedAreas.contains(namedAreas[j].second));
171 d->namedAreas[namedAreas[j].second].range = namedAreas[j].first.toRect();
172 emit namedAreaModified(namedAreas[j].second);
173 }
174 }
175}
176
177void NamedAreaManager::loadOdf(const KoXmlElement& body)
178{
179 KoXmlNode namedAreas = KoXml::namedItemNS(body, KoXmlNS::table, "named-expressions");
180 if (!namedAreas.isNull()) {
181 kDebug(36003) << "Loading named areas...";
182 KoXmlElement element;
183 forEachElement(element, namedAreas) {
184 if (element.namespaceURI() != KoXmlNS::table)
185 continue;
186 if (element.localName() == "named-range") {
187 if (!element.hasAttributeNS(KoXmlNS::table, "name"))
188 continue;
189 if (!element.hasAttributeNS(KoXmlNS::table, "cell-range-address"))
190 continue;
191
192 // TODO: what is: table:base-cell-address
193 const QString base = element.attributeNS(KoXmlNS::table, "base-cell-address", QString());
194
195 // Handle the case where the table:base-cell-address does contain the referenced sheetname
196 // while it's missing in the table:cell-range-address. See bug #194386 for an example.
197 Sheet* fallbackSheet = 0;
198 if (!base.isEmpty()) {
199 Region region(Region::loadOdf(base), d->map);
200 fallbackSheet = region.lastSheet();
201 }
202
203 const QString name = element.attributeNS(KoXmlNS::table, "name", QString());
204 const QString range = element.attributeNS(KoXmlNS::table, "cell-range-address", QString());
205 kDebug(36003) << "Named area found, name:" << name << ", area:" << range;
206
207 Region region(Region::loadOdf(range), d->map, fallbackSheet);
208 if (!region.isValid() || !region.lastSheet()) {
209 kDebug(36003) << "invalid area";
210 continue;
211 }
212
213 insert(region, name);
214 } else if (element.localName() == "named-expression") {
215 kDebug(36003) << "Named expression found.";
216 // TODO
217 }
218 }
219 }
220}
221
222void NamedAreaManager::saveOdf(KoXmlWriter& xmlWriter) const
223{
224 if (d->namedAreas.isEmpty())
225 return;
226 Region region;
227 xmlWriter.startElement("table:named-expressions");
228 const QList<NamedArea> namedAreas = d->namedAreas.values();
229 for (int i = 0; i < namedAreas.count(); ++i) {
230 region = Region(namedAreas[i].range, namedAreas[i].sheet);
231 xmlWriter.startElement("table:named-range");
232 xmlWriter.addAttribute("table:name", namedAreas[i].name);
233 xmlWriter.addAttribute("table:base-cell-address", Region(1, 1, namedAreas[i].sheet).saveOdf());
234 xmlWriter.addAttribute("table:cell-range-address", region.saveOdf());
235 xmlWriter.endElement();
236 }
237 xmlWriter.endElement();
238}
239
240void NamedAreaManager::loadXML(const KoXmlElement& parent)
241{
242 KoXmlElement element;
243 forEachElement(element, parent) {
244 if (element.tagName() == "reference") {
245 Sheet* sheet = 0;
246 QString tabname;
247 QString refname;
248 int left = 0;
249 int right = 0;
250 int top = 0;
251 int bottom = 0;
252 KoXmlElement sheetName = element.namedItem("tabname").toElement();
253 if (!sheetName.isNull())
254 sheet = d->map->findSheet(sheetName.text());
255 if (!sheet)
256 continue;
257 KoXmlElement referenceName = element.namedItem("refname").toElement();
258 if (!referenceName.isNull())
259 refname = referenceName.text();
260 KoXmlElement rect = element.namedItem("rect").toElement();
261 if (!rect.isNull()) {
262 bool ok;
263 if (rect.hasAttribute("left-rect"))
264 left = rect.attribute("left-rect").toInt(&ok);
265 if (rect.hasAttribute("right-rect"))
266 right = rect.attribute("right-rect").toInt(&ok);
267 if (rect.hasAttribute("top-rect"))
268 top = rect.attribute("top-rect").toInt(&ok);
269 if (rect.hasAttribute("bottom-rect"))
270 bottom = rect.attribute("bottom-rect").toInt(&ok);
271 }
272 insert(Region(QRect(QPoint(left, top), QPoint(right, bottom)), sheet), refname);
273 }
274 }
275}
276
277QDomElement NamedAreaManager::saveXML(QDomDocument& doc) const
278{
279 QDomElement element = doc.createElement("areaname");
280 const QList<NamedArea> namedAreas = d->namedAreas.values();
281 for (int i = 0; i < namedAreas.count(); ++i) {
282 QDomElement e = doc.createElement("reference");
283 QDomElement tabname = doc.createElement("tabname");
284 tabname.appendChild(doc.createTextNode(namedAreas[i].sheet->sheetName()));
285 e.appendChild(tabname);
286
287 QDomElement refname = doc.createElement("refname");
288 refname.appendChild(doc.createTextNode(namedAreas[i].name));
289 e.appendChild(refname);
290
291 QDomElement rect = doc.createElement("rect");
292 rect.setAttribute("left-rect", (namedAreas[i].range).left());
293 rect.setAttribute("right-rect", (namedAreas[i].range).right());
294 rect.setAttribute("top-rect", (namedAreas[i].range).top());
295 rect.setAttribute("bottom-rect", (namedAreas[i].range).bottom());
296 e.appendChild(rect);
297 element.appendChild(e);
298 }
299 return element;
300}
301
302#include "NamedAreaManager.moc"
303