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 | |
49 | using namespace Calligra::Sheets; |
50 | |
51 | struct NamedArea { |
52 | QString name; |
53 | Sheet* sheet; |
54 | QRect range; |
55 | }; |
56 | |
57 | class NamedAreaManager::Private |
58 | { |
59 | public: |
60 | const Map* map; |
61 | QHash<QString, NamedArea> namedAreas; |
62 | }; |
63 | |
64 | NamedAreaManager::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 | |
74 | NamedAreaManager::~NamedAreaManager() |
75 | { |
76 | delete d; |
77 | } |
78 | |
79 | void 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 | |
92 | void 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 | |
112 | void 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 | |
121 | Calligra::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 | |
129 | Sheet* 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 | |
136 | bool NamedAreaManager::contains(const QString& name) const |
137 | { |
138 | return d->namedAreas.contains(name); |
139 | } |
140 | |
141 | QList<QString> NamedAreaManager::areaNames() const |
142 | { |
143 | return d->namedAreas.keys(); |
144 | } |
145 | |
146 | void 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 | |
162 | void 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 | |
177 | void 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 | |
222 | void 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 | |
240 | void 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 | |
277 | QDomElement 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 | |