1 /* This file is part of the KDE project
2    Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
3    Copyright (C) 2005-2016 Jarosław Staniek <staniek@kde.org>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kformdesigner_export.h"
22 
23 #include <QDomDocument>
24 #include <QFile>
25 #include <QTextStream>
26 #include <QCursor>
27 #include <QBuffer>
28 #include <QImage>
29 #include <QLayout>
30 #include <QObject>
31 #include <QDateTime>
32 #include <QPainter>
33 #include <QPaintEvent>
34 #include <QVBoxLayout>
35 #include <QHBoxLayout>
36 #include <QPixmap>
37 #include <QImageWriter>
38 #include <QDebug>
39 
40 #include <KexiFileDialog.h>
41 #include <KAcceleratorManager>
42 #include <KLocalizedString>
43 
44 #include <kexiutils/utils.h>
45 #include "FormWidget.h"
46 #include "form.h"
47 #include "container.h"
48 #include "objecttree.h"
49 #include "widgetlibrary.h"
50 #include "events.h"
51 #include "utils.h"
52 #include "widgetwithsubpropertiesinterface.h"
53 #include "formIO.h"
54 #include "KexiVersion.h"
55 
56 //! @todo KEXI3 TODO pixmapcollection
57 #ifdef KEXI_PIXMAP_COLLECTIONS_SUPPORT
58 #include "pixmapcollection.h"
59 #endif
60 
61 //! @return Value of attribute "name", or "objectName" in absence of "name".
62 //!         This is for compatibility with Kexi 1.x.
nameAttribute(const QDomElement & el)63 static QString nameAttribute(const QDomElement& el)
64 {
65     QString res( el.attribute("name") );
66     if (res.isEmpty()) {
67         res = el.attribute("objectName");
68     }
69     return res;
70 }
71 
72 //! A blank widget used when the class name is not supported
CustomWidget(const QByteArray & className,QWidget * parent)73 CustomWidget::CustomWidget(const QByteArray &className, QWidget *parent)
74         : QWidget(parent), m_className(className)
75 {
76     setBackgroundRole(QPalette::Dark);
77 }
78 
~CustomWidget()79 CustomWidget::~CustomWidget()
80 {
81 }
82 
83 void
paintEvent(QPaintEvent *)84 CustomWidget::paintEvent(QPaintEvent *)
85 {
86     QPainter p(this);
87     p.setBrush(palette().text());
88     QRect r(rect());
89     r.setX(r.x() + 2);
90     p.drawText(r, Qt::AlignTop, m_className);
91 }
92 
93 using namespace KFormDesigner;
94 
95 // FormIO itself
96 
version()97 KFORMDESIGNER_EXPORT QString KFormDesigner::version()
98 {
99     return QString::fromLatin1("%1.%2").arg(KEXI_STABLE_VERSION_MAJOR).arg(KEXI_STABLE_VERSION_MINOR);
100 }
101 
102 /////////////////////////////////////////////////////////////////////////////
103 ///////////// Saving/loading functions //////////////////////////////////////
104 /////////////////////////////////////////////////////////////////////////////
105 
FormIO()106 FormIO::FormIO()
107 {
108 }
109 
~FormIO()110 FormIO::~FormIO()
111 {
112 }
113 
114 bool
saveFormToFile(Form * form,const QString & filename)115 FormIO::saveFormToFile(Form *form, const QString &filename)
116 {
117     QString _filename;
118     if (!form->fileName().isEmpty() && filename.isEmpty()) {
119         _filename = form->fileName();
120     }
121 
122     if (filename.isEmpty()) {
123         KexiFileDialog dlg(0, KexiFileDialog::SaveFile, "SaveForm");
124         dlg.setNameFilter("*.ui|" + xi18n("Qt Designer UI Files"));
125         _filename = dlg.fileName();
126         if (_filename.isEmpty()) {
127             return false;
128         }
129     }
130     else {
131         _filename = filename;
132     }
133     form->setFileName(_filename);
134 
135     QDomDocument domDoc;
136     if (!saveFormToDom(form, domDoc))
137         return false;
138 
139     QFile file(_filename);
140     if (!file.open(QIODevice::WriteOnly))
141         return false;
142 
143     QTextStream stream(&file);
144     stream << domDoc.toString(3);
145     file.close();
146 
147     return true;
148 }
149 
150 bool
saveFormToByteArray(Form * form,QByteArray & dest)151 FormIO::saveFormToByteArray(Form *form, QByteArray &dest)
152 {
153     QDomDocument domDoc;
154     if (!saveFormToDom(form, domDoc))
155         return false;
156     dest = domDoc.toByteArray();
157     return true;
158 }
159 
160 bool
saveFormToString(Form * form,QString & dest,int indent)161 FormIO::saveFormToString(Form *form, QString &dest, int indent)
162 {
163     QDomDocument domDoc;
164     if (!saveFormToDom(form, domDoc))
165         return false;
166     dest = domDoc.toString(indent);
167     return true;
168 }
169 
170 bool
saveFormToDom(Form * form,QDomDocument & domDoc)171 FormIO::saveFormToDom(Form *form, QDomDocument &domDoc)
172 {
173     domDoc = QDomDocument("UI");
174     QDomElement uiElement = domDoc.createElement("UI");
175     domDoc.appendChild(uiElement);
176     uiElement.setAttribute("version", "3.1");
177     uiElement.setAttribute("stdsetdef", 1);
178 
179     //update format version information
180     form->headerProperties()->insert("version", form->formatVersion());
181     //custom properties
182     QDomElement headerPropertiesEl = domDoc.createElement("kfd:customHeader");
183     QHash<QByteArray, QString>::ConstIterator itEnd = form->headerProperties()->constEnd();
184     for (QHash<QByteArray, QString>::ConstIterator it = form->headerProperties()->constBegin();
185         it != itEnd; ++it)
186     {
187         headerPropertiesEl.setAttribute(it.key(), it.value());
188     }
189     uiElement.appendChild(headerPropertiesEl);
190 
191     // We save the savePixmapsInline property in the Form
192     QDomElement inlinePix = domDoc.createElement("pixmapinproject");
193     uiElement.appendChild(inlinePix);
194 
195     // We create the top class element
196     QDomElement baseClass = domDoc.createElement("class");
197     uiElement.appendChild(baseClass);
198     QDomText baseClassV = domDoc.createTextNode("QWidget");
199     baseClass.appendChild(baseClassV);
200 
201     // Save the toplevel widgets, and so the whole Form
202     saveWidget(form->objectTree(), uiElement, domDoc);
203 
204     // We then save the layoutdefaults element
205     QDomElement layoutDefaults = domDoc.createElement("layoutDefaults");
206     layoutDefaults.setAttribute("spacing", QString::number(form->defaultSpacing()));
207     layoutDefaults.setAttribute("margin", QString::number(form->defaultMargin()));
208     uiElement.appendChild(layoutDefaults);
209 
210     // Save tab Stops
211     if (form->autoTabStops())
212         form->autoAssignTabStops();
213     QDomElement tabStops = domDoc.createElement("tabstops");
214     uiElement.appendChild(tabStops);
215     foreach (ObjectTreeItem *item, *form->tabStops()) {
216         QDomElement tabstop = domDoc.createElement("tabstop");
217         tabStops.appendChild(tabstop);
218         QDomText tabStopText = domDoc.createTextNode(item->name());
219         tabstop.appendChild(tabStopText);
220     }
221 
222 //! @todo KEXI3 TODO pixmapcollection
223 #ifdef KEXI_PIXMAP_COLLECTIONS_SUPPORT
224     // Save the Form 's PixmapCollection
225     form->pixmapCollection()->save(uiElement);
226 #endif
227 #ifdef KFD_SIGSLOTS
228     // Save the Form connections
229     form->connectionBuffer()->save(uiElement);
230 #endif
231 
232     form->setUndoStackClean();
233     return true;
234 }
235 
236 bool
loadFormFromByteArray(Form * form,QWidget * container,QByteArray & src,bool preview)237 FormIO::loadFormFromByteArray(Form *form, QWidget *container, QByteArray &src, bool preview)
238 {
239     QString errMsg;
240     int errLine;
241     int errCol;
242 
243     QDomDocument domDoc;
244     bool parsed = domDoc.setContent(src, false, &errMsg, &errLine, &errCol);
245 
246     if (!parsed) {
247         qDebug() << errMsg;
248         qDebug() << "line:" << errLine << "col:" << errCol;
249         return false;
250     }
251 
252     if (!loadFormFromDom(form, container, domDoc)) {
253         return false;
254     }
255     if (preview) {
256         form->setMode(Form::DataMode);
257     }
258     return true;
259 }
260 
261 bool
loadFormFromString(Form * form,QWidget * container,const QString & src,bool preview)262 FormIO::loadFormFromString(Form *form, QWidget *container, const QString &src, bool preview)
263 {
264     QString errMsg;
265     int errLine;
266     int errCol;
267 
268 #ifdef KEXI_DEBUG_GUI
269     form->m_recentlyLoadedUICode = src;
270 #endif
271 
272     QDomDocument domDoc;
273     //qDebug() << qPrintable(src);
274     bool parsed = domDoc.setContent(src, false, &errMsg, &errLine, &errCol);
275 
276     if (!parsed) {
277         qWarning() << errMsg;
278         qWarning() << "line:" << errLine << "col: " << errCol;
279         return false;
280     }
281 
282     if (!loadFormFromDom(form, container, domDoc)) {
283         return false;
284     }
285     if (preview) {
286         form->setMode(Form::DataMode);
287     }
288     return true;
289 }
290 
291 bool
loadFormFromFile(Form * form,QWidget * container,const QString & filename)292 FormIO::loadFormFromFile(Form *form, QWidget *container, const QString &filename)
293 {
294     QString errMsg;
295     int errLine;
296     int errCol;
297     QString _filename;
298 
299     if (filename.isEmpty()) {
300         KexiFileDialog dlg(0, KexiFileDialog::OpenFile, "LoadForm");
301         dlg.setNameFilter("*.ui|" + xi18n("Qt Designer UI Files"));
302         _filename = dlg.fileName();
303         if (_filename.isEmpty()) {
304             return false;
305         }
306     }
307     else {
308         _filename = filename;
309     }
310 
311     QFile file(_filename);
312     if (!file.open(QIODevice::ReadOnly)) {
313 //! @todo show err msg to the user
314         qWarning() << "Cannot open the file " << _filename;
315         return false;
316     }
317     QDomDocument doc;
318     if (!doc.setContent(&file, false/* !namespaceProcessing*/,
319                         &errMsg, &errLine, &errCol)) {
320 //! @todo show err msg to the user
321         qWarning() << errMsg;
322         qWarning() << errLine << "col:" << errCol;
323         return false;
324     }
325 
326     return loadFormFromDom(form, container, doc);
327 }
328 
loadFormFromDom(Form * form,QWidget * container,const QDomDocument & domDoc)329 bool FormIO::loadFormFromDom(Form *form, QWidget *container, const QDomDocument &domDoc)
330 {
331     QDomElement ui = domDoc.firstChildElement("UI");
332 
333     //custom properties
334     form->headerProperties()->clear();
335     QDomElement headerPropertiesEl = ui.firstChildElement("kfd:customHeader");
336     QDomAttr attr = headerPropertiesEl.firstChild().toAttr();
337     QDomNamedNodeMap attrs(headerPropertiesEl.attributes());
338     for(int i = 0; i < attrs.count(); ++i) {
339         const QDomAttr attr(attrs.item(i).toAttr());
340         if (!attr.isNull()) {
341             form->headerProperties()->insert(attr.name().toLatin1(), attr.value());
342         }
343     }
344     //update format version information
345     QString ver = form->headerProperties()->value("version");
346     qDebug() << "Original format version: " << ver;
347     form->setOriginalFormatVersion(ver);
348     bool verOk;
349     const double verNum = ver.toDouble(&verOk);
350     const double currentVerNum = KFormDesigner::version().toDouble();
351     if (verOk) {
352         if (verNum < currentVerNum) {
353             //! @todo We can either 1) convert from old format and later save in a new one or 2) keep old format.
354             //!     To do this we may need to look at the original format version number.
355             qDebug() << "The original format version is:" << ver
356                      << "current version:" << KFormDesigner::version();
357             //return false;
358         }
359     }
360     form->setFormatVersion(ver);
361 
362     if (verNum > currentVerNum) {
363 //! @todo display information about too new format and that "some information will not be available".
364         qDebug() << "The original format is version" << ver
365                  << "is newer than current version:" << KFormDesigner::version();
366     }
367 
368     // Load the pixmap collection
369     form->setPixmapsStoredInline(ui.firstChildElement("pixmapinproject").isNull()
370                                  || !ui.firstChildElement("images").isNull());
371 //! @todo pixmapcollection
372 #ifdef KEXI_PIXMAP_COLLECTIONS_SUPPORT
373     form->pixmapCollection()->load(ui.namedItem("collection"));
374 #endif
375 
376     QDomElement element = ui.firstChildElement("widget");
377     createToplevelWidget(form, container, element);
378 
379     // Loading the tabstops
380     QDomElement tabStops = ui.firstChildElement("tabstops");
381     if (!tabStops.isNull()) {
382         int i = 0;
383         int itemsNotFound = 0;
384         for (QDomNode n = tabStops.firstChild(); !n.isNull(); n = n.nextSibling(), i++) {
385             QString name = n.toElement().text();
386             ObjectTreeItem *item = form->objectTree()->lookup(name);
387             if (!item) {
388                 qWarning() << "Tabstops loading: no item" << name;
389                 continue;
390             }
391             const int index = form->tabStops()->indexOf(item);
392             /* Compute a real destination index: "a number of not found items so far". */
393             const int realIndex = i - itemsNotFound;
394             if ((index != -1) && (index != realIndex)) { // the widget is not in the same place, so we move it
395                 form->tabStops()->removeOne(item);
396                 form->tabStops()->insert(realIndex, item);
397             }
398             if (index == -1) {
399                 itemsNotFound++;
400                 qDebug() << "Tabstops loading: item" << name << "not on the list";
401             }
402         }
403     }
404 
405 #ifdef KFD_SIGSLOTS
406     // Load the form connections
407     form->connectionBuffer()->load(ui.namedItem("connections"));
408 #endif
409     return true;
410 }
411 
412 /////////////////////////////////////////////////////////////////////////////
413 ///////////// Functions to save/load properties /////////////////////////////
414 /////////////////////////////////////////////////////////////////////////////
415 
416 void
savePropertyValue(ObjectTreeItem * item,QDomElement & parentNode,QDomDocument & parent,const char * name,const QVariant & value)417 FormIO::savePropertyValue(ObjectTreeItem *item, QDomElement &parentNode, QDomDocument &parent,
418                           const char *name, const QVariant &value)
419 {
420     // Widget specific properties and attributes
421 // qDebug() << "Saving the property: " << name;
422     Form *form = item->container() ? item->container()->form() : item->parent()->container()->form();
423     WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(item->widget());
424     QWidget *subwidget = item->widget();
425     bool addSubwidgetFlag = false;
426     int propertyId = item->widget()->metaObject()->indexOfProperty(name);
427     const bool propertyIsName = qstrcmp(name, "objectName") == 0 || qstrcmp(name, "name") == 0;
428     if (!propertyIsName && propertyId == -1 && subpropIface && subpropIface->subwidget()) { // try property from subwidget
429         subwidget = subpropIface->subwidget();
430         propertyId = subpropIface->subwidget()->metaObject()->indexOfProperty(name);
431         addSubwidgetFlag = true;
432     }
433     if (!propertyIsName && propertyId == -1) {
434         qDebug() << "The object doesn't have this property. Let's try the WidgetLibrary.";
435         if (form->library())
436             form->library()->saveSpecialProperty(item->widget()->metaObject()->className(), name, value,
437                                                  item->widget(), parentNode, parent);
438         return;
439     }
440 
441     QMetaProperty meta;
442     if (!propertyIsName) {
443         meta = subwidget->metaObject()->property(propertyId);
444     }
445     if (!propertyIsName && (!meta.isValid() || !meta.isStored(subwidget)))   //not storable
446         return;
447     QDomElement propertyE = parent.createElement("property");
448     propertyE.setAttribute("name", propertyIsName ? "name" /* compat with 1.x */ : name);
449     if (addSubwidgetFlag)
450         propertyE.setAttribute("subwidget", "true");
451 
452     if (meta.isValid() && meta.isEnumType()) {
453         // this property is enum or set type
454         QDomElement type;
455         QDomText valueE;
456 
457         if (meta.isFlagType()) {
458             type = parent.createElement("set");
459             valueE = parent.createTextNode(
460                          meta.enumerator().valueToKeys(value.toInt()));
461             type.appendChild(valueE);
462         } else {
463             QString s = meta.enumerator().valueToKey(value.toInt());
464             type = parent.createElement("enum");
465             valueE = parent.createTextNode(s);
466             type.appendChild(valueE);
467         }
468         propertyE.appendChild(type);
469         parentNode.appendChild(propertyE);
470         return;
471     }
472 
473     if (value.type() == QVariant::Pixmap) {
474         QDomText valueE;
475         QDomElement type = parent.createElement("pixmap");
476         QByteArray property = propertyE.attribute("name").toLatin1();
477 //! @todo  QCString pixmapName = m_currentRecord->widget()->property("pixmapName").toCString();
478         if (form->pixmapsStoredInline() /* (js)too risky: || m_currentRecord->pixmapName(property).isNull() */)
479             valueE = parent.createTextNode(saveImage(parent, value.value<QPixmap>()));
480         else
481             valueE = parent.createTextNode(item->pixmapName(property));
482         type.appendChild(valueE);
483         propertyE.appendChild(type);
484         parentNode.appendChild(propertyE);
485         return;
486     }
487 
488     // Saving a "normal" property
489     writeVariant(parent, propertyE, value);
490     parentNode.appendChild(propertyE);
491 }
492 
493 void
writeVariant(QDomDocument & parent,QDomElement & parentNode,const QVariant & value)494 FormIO::writeVariant(QDomDocument &parent, QDomElement &parentNode, const QVariant& value)
495 {
496     QDomElement type;
497     QDomText valueE;
498 
499     switch (value.type()) {
500     case QVariant::String: {
501         type = parent.createElement("string");
502         valueE = parent.createTextNode(value.toString());
503         type.appendChild(valueE);
504         break;
505     }
506     case QVariant::ByteArray: {
507         type = parent.createElement("cstring");
508         valueE = parent.createTextNode(value.toString());
509         type.appendChild(valueE);
510         break;
511     }
512     case QVariant::Rect: {
513         type = parent.createElement("rect");
514         QDomElement x = parent.createElement("x");
515         QDomElement y = parent.createElement("y");
516         QDomElement w = parent.createElement("width");
517         QDomElement h = parent.createElement("height");
518         QDomText valueX = parent.createTextNode(QString::number(value.toRect().x()));
519         QDomText valueY = parent.createTextNode(QString::number(value.toRect().y()));
520         QDomText valueW = parent.createTextNode(QString::number(value.toRect().width()));
521         QDomText valueH = parent.createTextNode(QString::number(value.toRect().height()));
522 
523         x.appendChild(valueX);
524         y.appendChild(valueY);
525         w.appendChild(valueW);
526         h.appendChild(valueH);
527 
528         type.appendChild(x);
529         type.appendChild(y);
530         type.appendChild(w);
531         type.appendChild(h);
532         break;
533     }
534     case QVariant::Color: {
535         type = parent.createElement("color");
536         QDomElement r = parent.createElement("red");
537         QDomElement g = parent.createElement("green");
538         QDomElement b = parent.createElement("blue");
539 
540         const QColor color(value.value<QColor>());
541         QDomText valueR = parent.createTextNode(QString::number(color.red()));
542         QDomText valueG = parent.createTextNode(QString::number(color.green()));
543         QDomText valueB = parent.createTextNode(QString::number(color.blue()));
544 
545         r.appendChild(valueR);
546         g.appendChild(valueG);
547         b.appendChild(valueB);
548 
549         type.appendChild(r);
550         type.appendChild(g);
551         type.appendChild(b);
552         break;
553     }
554     case QVariant::Bool: {
555         type = parent.createElement("bool");
556         //valueE = parent.createTextNode(QString::number(value.toBool()));
557         valueE = parent.createTextNode(value.toBool() ? "true" : "false");
558         type.appendChild(valueE);
559         break;
560     }
561     case QVariant::Int:
562     case QVariant::UInt: {
563         type = parent.createElement("number");
564         valueE = parent.createTextNode(QString::number(value.toInt()));
565         type.appendChild(valueE);
566         break;
567     }
568     case QVariant::Size: {
569         type = parent.createElement("size");
570         QDomElement w = parent.createElement("width");
571         QDomElement h = parent.createElement("height");
572         QDomText valueW = parent.createTextNode(QString::number(value.toSize().width()));
573         QDomText valueH = parent.createTextNode(QString::number(value.toSize().height()));
574 
575         w.appendChild(valueW);
576         h.appendChild(valueH);
577 
578         type.appendChild(w);
579         type.appendChild(h);
580         break;
581     }
582     case QVariant::Point: {
583         type = parent.createElement("point");
584         QDomElement x = parent.createElement("x");
585         QDomElement y = parent.createElement("y");
586         QDomText valueX = parent.createTextNode(QString::number(value.toPoint().x()));
587         QDomText valueY = parent.createTextNode(QString::number(value.toPoint().y()));
588 
589         x.appendChild(valueX);
590         y.appendChild(valueY);
591 
592         type.appendChild(x);
593         type.appendChild(y);
594         break;
595     }
596     case QVariant::Font: {
597         type = parent.createElement("font");
598         QDomElement f = parent.createElement("family");
599         QDomElement p = parent.createElement("pointsize");
600         QDomElement w = parent.createElement("weight");
601         QDomElement b = parent.createElement("bold");
602         QDomElement i = parent.createElement("italic");
603         QDomElement u = parent.createElement("underline");
604         QDomElement s = parent.createElement("strikeout");
605 
606         const QFont font(value.value<QFont>());
607         QDomText valueF = parent.createTextNode(font.family());
608         QDomText valueP = parent.createTextNode(QString::number(font.pointSize()));
609         QDomText valueW = parent.createTextNode(QString::number(font.weight()));
610         QDomText valueB = parent.createTextNode(QString::number(font.bold()));
611         QDomText valueI = parent.createTextNode(QString::number(font.italic()));
612         QDomText valueU = parent.createTextNode(QString::number(font.underline()));
613         QDomText valueS = parent.createTextNode(QString::number(font.strikeOut()));
614 
615         f.appendChild(valueF);
616         p.appendChild(valueP);
617         w.appendChild(valueW);
618         b.appendChild(valueB);
619         i.appendChild(valueI);
620         u.appendChild(valueU);
621         s.appendChild(valueS);
622 
623         type.appendChild(f);
624         type.appendChild(p);
625         type.appendChild(w);
626         type.appendChild(b);
627         type.appendChild(i);
628         type.appendChild(u);
629         type.appendChild(s);
630         break;
631     }
632     case QVariant::Cursor: {
633         type = parent.createElement("cursor");
634         valueE = parent.createTextNode(QString::number(value.value<QCursor>().shape()));
635         type.appendChild(valueE);
636         break;
637     }
638     case QVariant::SizePolicy: {
639         type = parent.createElement("sizepolicy");
640         QDomElement h(parent.createElement("hsizetype"));
641         QDomElement v(parent.createElement("vsizetype"));
642         QDomElement hs(parent.createElement("horstretch"));
643         QDomElement vs(parent.createElement("verstretch"));
644 
645         const QSizePolicy sizePolicy(value.value<QSizePolicy>());
646         const QDomText valueH(parent.createTextNode(QString::number(sizePolicy.horizontalPolicy())));
647         const QDomText valueV(parent.createTextNode(QString::number(sizePolicy.verticalPolicy())));
648         const QDomText valueHS(parent.createTextNode(QString::number(sizePolicy.horizontalStretch())));
649         const QDomText valueVS(parent.createTextNode(QString::number(sizePolicy.verticalStretch())));
650 
651         h.appendChild(valueH);
652         v.appendChild(valueV);
653         hs.appendChild(valueHS);
654         vs.appendChild(valueVS);
655 
656         type.appendChild(h);
657         type.appendChild(v);
658         type.appendChild(hs);
659         type.appendChild(vs);
660         break;
661     }
662     case QVariant::Time: {
663         type = parent.createElement("time");
664         QDomElement h = parent.createElement("hour");
665         QDomElement m = parent.createElement("minute");
666         QDomElement s = parent.createElement("second");
667         QDomText valueH = parent.createTextNode(QString::number(value.toTime().hour()));
668         QDomText valueM = parent.createTextNode(QString::number(value.toTime().minute()));
669         QDomText valueS = parent.createTextNode(QString::number(value.toTime().second()));
670 
671         h.appendChild(valueH);
672         m.appendChild(valueM);
673         s.appendChild(valueS);
674 
675         type.appendChild(h);
676         type.appendChild(m);
677         type.appendChild(s);
678         break;
679     }
680     case QVariant::Date: {
681         type = parent.createElement("date");
682         QDomElement y = parent.createElement("year");
683         QDomElement m = parent.createElement("month");
684         QDomElement d = parent.createElement("day");
685         QDomText valueY = parent.createTextNode(QString::number(value.toDate().year()));
686         QDomText valueM = parent.createTextNode(QString::number(value.toDate().month()));
687         QDomText valueD = parent.createTextNode(QString::number(value.toDate().day()));
688 
689         y.appendChild(valueY);
690         m.appendChild(valueM);
691         d.appendChild(valueD);
692 
693         type.appendChild(y);
694         type.appendChild(m);
695         type.appendChild(d);
696         break;
697     }
698     case QVariant::DateTime: {
699         type = parent.createElement("datetime");
700         QDomElement h = parent.createElement("hour");
701         QDomElement m = parent.createElement("minute");
702         QDomElement s = parent.createElement("second");
703         QDomElement y = parent.createElement("year");
704         QDomElement mo = parent.createElement("month");
705         QDomElement d = parent.createElement("day");
706         QDomText valueH = parent.createTextNode(QString::number(value.toDateTime().time().hour()));
707         QDomText valueM = parent.createTextNode(QString::number(value.toDateTime().time().minute()));
708         QDomText valueS = parent.createTextNode(QString::number(value.toDateTime().time().second()));
709         QDomText valueY = parent.createTextNode(QString::number(value.toDateTime().date().year()));
710         QDomText valueMo = parent.createTextNode(QString::number(value.toDateTime().date().month()));
711         QDomText valueD = parent.createTextNode(QString::number(value.toDateTime().date().day()));
712 
713         h.appendChild(valueH);
714         m.appendChild(valueM);
715         s.appendChild(valueS);
716         y.appendChild(valueY);
717         mo.appendChild(valueMo);
718         d.appendChild(valueD);
719 
720         type.appendChild(h);
721         type.appendChild(m);
722         type.appendChild(s);
723         type.appendChild(y);
724         type.appendChild(mo);
725         type.appendChild(d);
726         break;
727     }
728     default:
729         break;
730     }
731 
732     parentNode.appendChild(type);
733 }
734 
735 void
savePropertyElement(QDomElement & parentNode,QDomDocument & domDoc,const QString & tagName,const QString & property,const QVariant & value)736 FormIO::savePropertyElement(QDomElement &parentNode, QDomDocument &domDoc, const QString &tagName,
737     const QString &property, const QVariant &value)
738 {
739     QDomElement propertyE = domDoc.createElement(tagName);
740     propertyE.setAttribute("name", property);
741     writeVariant(domDoc, propertyE, value);
742     parentNode.appendChild(propertyE);
743 }
744 
readPropertyValue(Form * form,QDomNode node,QObject * obj,const QString & name)745 QVariant FormIO::readPropertyValue(Form *form, QDomNode node, QObject *obj, const QString &name)
746 {
747     QDomElement tag = node.toElement();
748     QString text = tag.text();
749     QString type = tag.tagName();
750 
751     if (type == "string" || type == "cstring")
752         return text;
753     else if (type == "rect") {
754         QDomElement x = node.firstChildElement("x");
755         QDomElement y = node.firstChildElement("y");
756         QDomElement w = node.firstChildElement("width");
757         QDomElement h = node.firstChildElement("height");
758 
759         int rx = x.text().toInt();
760         int ry = y.text().toInt();
761         int rw = w.text().toInt();
762         int rh = h.text().toInt();
763 
764         return QRect(rx, ry, rw, rh);
765     } else if (type == "color") {
766         const QDomElement r(node.firstChildElement("red"));
767         const QDomElement g(node.firstChildElement("green"));
768         const QDomElement b(node.firstChildElement("blue"));
769 
770         return QColor(r.text().toInt(), g.text().toInt(), b.text().toInt());
771     } else if (type == "bool") {
772         if (text == "true")
773             return true;
774         else if (text == "false")
775             return false;
776         return text.toInt() != 0;
777     } else if (type == "number") {
778         return text.toInt();
779     } else if (type == "size") {
780         QDomElement w = node.firstChildElement("width");
781         QDomElement h = node.firstChildElement("height");
782 
783         return QSize(w.text().toInt(), h.text().toInt());
784     } else if (type == "point") {
785         QDomElement x = node.firstChildElement("x");
786         QDomElement y = node.firstChildElement("y");
787 
788         return QPoint(x.text().toInt(), y.text().toInt());
789     } else if (type == "font") {
790         QDomElement fa = node.firstChildElement("family");
791         QDomElement p = node.firstChildElement("pointsize");
792         QDomElement w = node.firstChildElement("weight");
793         QDomElement b = node.firstChildElement("bold");
794         QDomElement i = node.firstChildElement("italic");
795         QDomElement u = node.firstChildElement("underline");
796         QDomElement s = node.firstChildElement("strikeout");
797 
798         QFont f;
799         f.setFamily(fa.text());
800         f.setPointSize(p.text().toInt());
801         f.setWeight(w.text().toInt());
802         f.setBold(b.text().toInt());
803         f.setItalic(i.text().toInt());
804         f.setUnderline(u.text().toInt());
805         f.setStrikeOut(s.text().toInt());
806 
807         return f;
808     } else if (type == "cursor") {
809         return QCursor((Qt::CursorShape) text.toInt());
810     } else if (type == "time") {
811         QDomElement h = node.firstChildElement("hour");
812         QDomElement m = node.firstChildElement("minute");
813         QDomElement s = node.firstChildElement("second");
814 
815         return QTime(h.text().toInt(), m.text().toInt(), s.text().toInt());
816     } else if (type == "date") {
817         QDomElement y = node.firstChildElement("year");
818         QDomElement m = node.firstChildElement("month");
819         QDomElement d = node.firstChildElement("day");
820 
821         return QDate(y.text().toInt(), m.text().toInt(), d.text().toInt());
822     } else if (type == "datetime") {
823         QDomElement h = node.firstChildElement("hour");
824         QDomElement m = node.firstChildElement("minute");
825         QDomElement s = node.firstChildElement("second");
826         QDomElement y = node.firstChildElement("year");
827         QDomElement mo = node.firstChildElement("month");
828         QDomElement d = node.firstChildElement("day");
829 
830         QTime t(h.text().toInt(), m.text().toInt(), s.text().toInt());
831         QDate da(y.text().toInt(), mo.text().toInt(), d.text().toInt());
832 
833         return QDateTime(da, t);
834     } else if (type == "sizepolicy") {
835         QDomElement h = node.firstChildElement("hsizetype");
836         QDomElement v = node.firstChildElement("vsizetype");
837         QDomElement hs = node.firstChildElement("horstretch");
838         QDomElement vs = node.firstChildElement("verstretch");
839 
840         QSizePolicy s;
841         s.setHorizontalPolicy((QSizePolicy::Policy)h.text().toInt());
842         s.setVerticalPolicy((QSizePolicy::Policy)v.text().toInt());
843         s.setHorizontalStretch(hs.text().toInt());
844         s.setVerticalStretch(vs.text().toInt());
845         return s;
846     } else if (type == "pixmap") {
847 //! @todo pixmapcollection
848 #ifdef KEXI_PIXMAP_COLLECTIONS_SUPPORT
849         if (form->pixmapsStoredInline() || !m_currentForm || !m_currentRecord || !m_currentForm->pixmapCollection()->contains(text))
850             return loadImage(tag.ownerDocument(), text);
851         else {
852             m_currentRecord->setPixmapName(name.toLatin1(), text);
853             return form->pixmapCollection()->getPixmap(text);
854         }
855 #else
856         Q_UNUSED(form);
857 #endif
858         return QPixmap();
859     }
860     else if (type == "enum") {
861         return text;
862     }
863     else if (type == "set") {
864         WidgetWithSubpropertiesInterface* subpropIface
865             = dynamic_cast<WidgetWithSubpropertiesInterface*>(obj);
866         QObject *subobject = (subpropIface && subpropIface->subwidget())
867                              ? subpropIface->subwidget() : obj;
868         const QMetaProperty meta(KexiUtils::findPropertyWithSuperclasses(subobject, name.toLatin1()));
869         if (meta.isValid()) {
870             if (meta.isFlagType()) {
871                 return meta.enumerator().keysToValue(text.toLatin1());
872             } else {
873                 // Metaproperty not found, probably because subwidget is not created.
874                 // We will return a string list here with hope that names will
875                 // be resolved and translated into an integer value later when subwidget is created,
876                 // e.g. near KexiFormView::updateValuesForSubproperties()
877                 return text.split('|');
878             }
879         }
880     }
881     return QVariant();
882 }
883 
884 /////////////////////////////////////////////////////////////////////////////
885 ///////////// Functions to save/load widgets ////////////////////////////////
886 /////////////////////////////////////////////////////////////////////////////
887 
888 void
saveWidget(ObjectTreeItem * item,QDomElement & parent,QDomDocument & domDoc,bool insideGridLayout)889 FormIO::saveWidget(ObjectTreeItem *item, QDomElement &parent, QDomDocument &domDoc, bool insideGridLayout)
890 {
891     if (!item)
892         return;
893     //qDebug() << item->className() << item->widget()->objectName();
894     Form *form = item->container() ? item->container()->form() : item->parent()->container()->form();
895     WidgetLibrary *lib = form->library();
896 
897     // We create the "widget" element
898     QDomElement tclass = domDoc.createElement("widget");
899     parent.appendChild(tclass);
900 
901     if (insideGridLayout) {
902         tclass.setAttribute("row", item->gridRow());
903         tclass.setAttribute("column", item->gridCol());
904         if (item->spanMultipleCells()) {
905             tclass.setAttribute("rowspan", item->gridRowSpan());
906             tclass.setAttribute("colspan", item->gridColSpan());
907         }
908     }
909 
910     if (!item->parent()) // Toplevel widget
911         tclass.setAttribute("class", "QWidget");
912     // For compatibility, HBox, VBox and Grid are saved as "QLayoutWidget"
913     else if (KexiUtils::objectIsA(item->widget(),
914                                   QList<QByteArray>() << "HBox" << "VBox" << "Grid" << "HFlow" << "VFlow"))
915         tclass.setAttribute("class", "QLayoutWidget");
916     else if (KexiUtils::objectIsA(item->widget(), "CustomWidget"))
917         tclass.setAttribute("class", item->className());
918     else // Normal widgets
919         tclass.setAttribute("class", lib->savingName(item->widget()->metaObject()->className()));
920 
921     // We save every property in the modifProp list of the ObjectTreeItem
922     QHash<QString, QVariant> hash(*(item->modifiedProperties()));
923     QStringList names(hash.keys());
924     savePropertyValue(item, tclass, domDoc, "objectName", item->widget()->objectName());
925     names.removeOne("objectName");
926 
927     // Important: save dataSource property FIRST before properties like "alignment"
928     // - needed when subproperties are defined after subwidget creation, and subwidget is created after setting "dataSource"
929     //   (this is the case for KexiDBAutoField)
930 //! @todo more properties like "dataSource" may needed here...
931 // if (-1 != item->widget()->metaObject()->findProperty("dataSource"))
932     // savePropertyValue(tclass, domDoc, "dataSource", item->widget()->property("dataSource"), item->widget());
933 
934     // We don't want to save the geometry if the widget is inside a layout (so parent.tagName() == "grid" for example)
935     if (item && !item->parent()) {
936         // save form widget size, but not its position
937         savePropertyValue(item, tclass, domDoc, "geometry",
938                           QRect(QPoint(0, 0), item->widget()->size()));
939     }
940     // normal widget (if == "UI', it means we're copying widget)
941     else if (parent.tagName() == "widget" || parent.tagName() == "UI")
942         savePropertyValue(item, tclass, domDoc, "geometry", item->widget()->property("geometry"));
943 
944     names.removeOne("geometry");
945     names.removeOne("layout");
946 
947     // Save the buddy widget for a label
948     if (   qobject_cast<QLabel*>(item->widget())
949         && qobject_cast<QLabel*>(item->widget())->buddy())
950     {
951         savePropertyElement(
952             tclass, domDoc, "property", "buddy",
953             qobject_cast<QLabel*>(item->widget())->buddy()->objectName());
954     }
955 
956     if (names.contains("paletteBackgroundColor")) {
957         savePropertyElement(
958             tclass, domDoc, "property", "paletteBackgroundColor",
959             item->widget()->palette().color(item->widget()->backgroundRole()));
960         names.removeOne("paletteBackgroundColor");
961     }
962     if (names.contains("paletteForegroundColor")) {
963         savePropertyElement(
964             tclass, domDoc, "property", "paletteForegroundColor",
965             item->widget()->palette().color(item->widget()->foregroundRole()));
966         names.removeOne("paletteForegroundColor");
967     }
968 
969     QStringList alignProperties;
970     alignProperties << "hAlign" << "vAlign" << "alignment";
971     foreach (const QString& name, alignProperties) {
972         if (names.contains(name)) {
973             names.removeOne(name);
974             savePropertyValue(
975                 item, tclass, domDoc, "alignment",
976                 item->widget()->property("alignment"));
977             break;
978         }
979     }
980 
981     foreach (const QString& name, names) {
982         savePropertyValue(item, tclass, domDoc, name.toLatin1(),
983                           item->widget()->property(name.toLatin1()));
984     }
985     hash.clear();
986 
987     if (KexiUtils::objectIsA(item->widget(), "CustomWidget")) {
988         QDomDocument doc("TEMP");
989         doc.setContent(item->unknownProperties());
990         for (QDomNode n = doc.firstChild(); !n.isNull(); n = n.nextSibling()) {
991             tclass.appendChild(n.cloneNode());
992         }
993 
994     }
995     // Saving container 's layout if there is one
996     QDomElement layout;
997     if (item->container() && item->container()->layoutType() != Form::NoLayout) {
998         if (item->container()->layout()) { // there is a layout
999             layout = domDoc.createElement("temp");
1000             savePropertyValue(item, layout, domDoc, "objectName", "unnamed");
1001             if (item->modifiedProperties()->contains("layoutMargin"))
1002                 savePropertyElement(layout, domDoc, "property", "margin", item->container()->layoutMargin());
1003             if (item->modifiedProperties()->contains("layoutSpacing"))
1004                 savePropertyElement(layout, domDoc, "property", "spacing", item->container()->layoutSpacing());
1005             tclass.appendChild(layout);
1006         }
1007     }
1008 
1009     Form::LayoutType layoutType = item->container() ? item->container()->layoutType() : Form::NoLayout;
1010     switch (layoutType) {
1011     case Form::Grid: { // grid layout
1012         layout.setTagName("grid");
1013         foreach (ObjectTreeItem *titem, *item->children()) {
1014             saveWidget(titem, layout, domDoc, true);
1015         }
1016         break;
1017     }
1018     case Form::HBox:
1019     case Form::VBox: {
1020         // as we don't save geometry, we need to sort widgets in the right order, not creation order
1021         CustomSortableWidgetList *list;
1022         if (layout.tagName() == "hbox") {
1023             list = new HorizontalWidgetList(item->container()->form()->toplevelContainer()->widget());
1024             layout.setTagName("hbox");
1025         } else {
1026             list = new HorizontalWidgetList(item->container()->form()->toplevelContainer()->widget());
1027             layout.setTagName("vbox");
1028         }
1029 
1030         foreach (ObjectTreeItem *titem, *item->children()) {
1031             list->append(titem->widget());
1032         }
1033         list->sort();
1034 
1035         foreach (QWidget *w, *list) {
1036             ObjectTreeItem *titem = item->container()->form()->objectTree()->lookup(
1037                 w->objectName()
1038             );
1039             if (item)
1040                 saveWidget(titem, layout, domDoc);
1041         }
1042         delete list;
1043         break;
1044     }
1045     case Form::HFlow:
1046     case Form::VFlow: {
1047         break;
1048     }
1049     default: {
1050         foreach (ObjectTreeItem *titem, *item->children()) {
1051             saveWidget(titem, tclass, domDoc);
1052         }
1053     }
1054     }
1055 
1056     addIncludeFileName(lib->includeFileName(
1057                            item->widget()->metaObject()->className()), domDoc);
1058 }
1059 
1060 void
cleanClipboard(QDomElement & uiElement)1061 FormIO::cleanClipboard(QDomElement &uiElement)
1062 {
1063     // remove includehints element not needed
1064     if (!uiElement.firstChildElement("includehints").isNull())
1065         uiElement.removeChild(uiElement.firstChildElement("includehints"));
1066     // and ensure images and connection are at the end
1067     if (!uiElement.firstChildElement("connections").isNull())
1068         uiElement.insertAfter(uiElement.firstChildElement("connections"), QDomNode());
1069     if (!uiElement.firstChildElement("images").isNull())
1070         uiElement.insertAfter(uiElement.firstChildElement("images"), QDomNode());
1071 }
1072 
loadWidget(Container * container,const QDomElement & el,QWidget * parent,QHash<QString,QLabel * > * buddies)1073 void FormIO::loadWidget(Container *container, const QDomElement &el, QWidget *parent,
1074                         QHash<QString, QLabel*> *buddies)
1075 {
1076     Form *form = container->form();
1077 
1078     // We first look for the widget's name
1079     QString wname;
1080     for (QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling()) {
1081         if (   n.toElement().tagName() == "property"
1082             && nameAttribute(n.toElement()) == "name" /* compat with 1.x */)
1083         {
1084             wname = n.toElement().text();
1085             break;
1086         }
1087     }
1088     ObjectTreeItem *item = container->form()->objectTree()->lookup(wname);
1089     if (item) {
1090         qWarning() << "Widget" << wname << "already exists! Skipping...";
1091         return;
1092     }
1093 
1094     QByteArray classname, alternate;
1095     // check if this classname is an alternate one, and replace it if necessary
1096     classname = el.attribute("class").toLatin1();
1097     alternate = container->form()->library()->classNameForAlternate(classname);
1098 
1099     QWidget *w;
1100     if (alternate == "CustomWidget") {
1101         w = new CustomWidget(classname, container->widget());
1102         w->setObjectName(wname);
1103     } else {
1104         if (!alternate.isEmpty()) {
1105             classname = alternate;
1106         }
1107 
1108         WidgetFactory::CreateWidgetOptions widgetOptions = WidgetFactory::DefaultOptions;
1109         if (container->form()->mode() != Form::DesignMode) {
1110             widgetOptions ^= WidgetFactory::DesignViewMode;
1111         }
1112 
1113         if (!parent)
1114             w = container->form()->library()->createWidget(classname, container->widget(),
1115                     wname.toLatin1(), container, widgetOptions);
1116         else
1117             w = container->form()->library()->createWidget(classname, parent, wname.toLatin1(),
1118                     container, widgetOptions);
1119     }
1120 
1121     if (!w)
1122         return;
1123 //! @todo allow setting this for data view mode as well
1124     if (form->mode() == Form::DesignMode) {
1125         //don't generate accelerators for widgets in design mode
1126         KAcceleratorManager::setNoAccel(w);
1127     }
1128     w->show();
1129 
1130     // We create and insert the ObjectTreeItem at the good place in the ObjectTree
1131     item = container->form()->objectTree()->lookup(wname);
1132     //qDebug() << wname << item << classname << (parent ? parent->objectName() : QString());
1133     if (!item)  {
1134         // not yet created
1135         //qDebug() << "Creating ObjectTreeItem:";
1136         item =  new ObjectTreeItem(container->form()->library()->displayName(classname),
1137                                    wname, w, container);
1138         if (parent)  {
1139             ObjectTreeItem *titem = container->form()->objectTree()->lookup(parent->objectName());
1140             if (titem)
1141                 container->form()->objectTree()->addItem(titem, item);
1142             else
1143                 qWarning() << "ERROR no parent widget";
1144         } else
1145             container->form()->objectTree()->addItem(container->objectTree(), item);
1146     }
1147     //assign item for its widget if it supports DesignTimeDynamicChildWidgetHandler interface
1148     //(e.g. KexiDBAutoField)
1149     DesignTimeDynamicChildWidgetHandler *childHandler = dynamic_cast<DesignTimeDynamicChildWidgetHandler*>(w);
1150     if (container->form()->mode() == Form::DesignMode && childHandler) {
1151         childHandler->assignItem(item);
1152     }
1153 
1154     // if we are inside a Grid, we need to insert the widget in the good cell
1155     if (container->layoutType() == Form::Grid)  {
1156         QGridLayout *layout = (QGridLayout*)container->layout();
1157         if (el.hasAttribute("rowspan")) { // widget spans multiple cells
1158             if (layout) {
1159                 layout->addWidget(
1160                     w,
1161                     el.attribute("row").toInt(), el.attribute("column").toInt(),
1162                     el.attribute("rowspan").toInt(), el.attribute("colspan").toInt());
1163 //! @todo alignment attribute?
1164             }
1165             item->setGridPos(el.attribute("row").toInt(),  el.attribute("column").toInt(), el.attribute("rowspan").toInt(),
1166                              el.attribute("colspan").toInt());
1167         } else  {
1168             if (layout) {
1169                 layout->addWidget(w, el.attribute("row").toInt(), el.attribute("column").toInt());
1170             }
1171             item->setGridPos(el.attribute("row").toInt(),  el.attribute("column").toInt(), 0, 0);
1172         }
1173     } else if (container->layout())
1174         container->layout()->addWidget(w);
1175 
1176     readChildNodes(item, container, el, w, buddies);
1177 
1178     if (item->container() && item->container()->layout())
1179         item->container()->layout()->activate();
1180 
1181     // add the autoSaveProperties in the modifProp list of the ObjectTreeItem, so that they are saved later
1182     const QList<QByteArray> autoSaveProperties(
1183         container->form()->library()->autoSaveProperties(w->metaObject()->className()) );
1184     KFormDesigner::WidgetWithSubpropertiesInterface* subpropIface
1185         = dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>(w);
1186     QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : w;
1187     foreach (const QByteArray &propName, autoSaveProperties) {
1188         if (subwidget && -1 != subwidget->metaObject()->indexOfProperty(propName)) {
1189             item->addModifiedProperty(propName, subwidget->property(propName));
1190         }
1191     }
1192 
1193     const QVariant autoFillBackgroundValue = item->modifiedProperties()->value("autoFillBackground");
1194     const QVariant paletteForegroundColorValue = item->modifiedProperties()->value("paletteForegroundColor");
1195     if (!paletteForegroundColorValue.isNull() && autoFillBackgroundValue.isNull()) {
1196         // Sanity: force fill background if there's color but not 'fill background' set
1197         w->setAutoFillBackground(true);
1198     }
1199 }
1200 
1201 void
createToplevelWidget(Form * form,QWidget * container,QDomElement & el)1202 FormIO::createToplevelWidget(Form *form, QWidget *container, QDomElement &el)
1203 {
1204     // We first look for the widget's name
1205     QString wname;
1206     for (QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling()) {
1207         if (    n.toElement().tagName() == "property"
1208              && nameAttribute(n.toElement()) == "name" /* compat with 1.x */)
1209         {
1210             wname = n.toElement().text();
1211             break;
1212         }
1213     }
1214     // And rename the widget and its ObjectTreeItem
1215     container->setObjectName(wname);
1216     if (form->objectTree())
1217         form->objectTree()->rename(form->objectTree()->name(), wname);
1218     form->setInteractiveMode(false);
1219 
1220     QHash<QString, QLabel*> buddies;
1221     readChildNodes(form->objectTree(), form->toplevelContainer(), el, container, &buddies);
1222 
1223     // Now the Form is fully loaded, we can assign the buddies
1224     for (QHash<QString, QLabel*>::ConstIterator it(buddies.constBegin());
1225         it!=buddies.constEnd(); ++it)
1226     {
1227         ObjectTreeItem *item = form->objectTree()->lookup(it.key());
1228         if (!item || !item->widget()) {
1229             qDebug() << "Cannot assign buddy for widget "
1230                 << it.value()->objectName() << " to " << it.key();
1231             continue;
1232         }
1233         it.value()->setBuddy(item->widget());
1234     }
1235     form->setInteractiveMode(true);
1236 }
1237 
readChildNodes(ObjectTreeItem * item,Container * container,const QDomElement & el,QWidget * w,QHash<QString,QLabel * > * buddies)1238 void FormIO::readChildNodes(ObjectTreeItem *item, Container *container, const QDomElement &el,
1239                        QWidget *w, QHash<QString, QLabel*> *buddies)
1240 {
1241     QString eltag = el.tagName();
1242 
1243     WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
1244     QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : w;
1245 
1246     for (QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling()) {
1247         QString tag = n.toElement().tagName();
1248         QDomElement node = n.toElement();
1249 
1250         if ((tag == "property") || (tag == "attribute")) {
1251             const QString name( node.attribute("name") );
1252             const bool isQt3NameProperty = name == QLatin1String("name");
1253             //if(name == "geometry")
1254             // hasGeometryProp = true;
1255             if (   (eltag == "grid" || eltag == "hbox" || eltag == "vbox")
1256                 && (isQt3NameProperty || name == "objectName")
1257                )
1258             {
1259                 // we don't care about layout names
1260                 continue;
1261             }
1262 
1263             if (node.attribute("subwidget") == "true") {
1264                 //this is property for subwidget: remember it for delayed setting
1265                 //because now the subwidget could be not created yet (true e.g. for KexiDBAutoField)
1266                 item->addSubproperty(name.toLatin1(),
1267                                      readPropertyValue(container->form(), node.firstChild(), w, name));
1268                 const QVariant val(readPropertyValue(container->form(), node.firstChild(), w, name));
1269                 //qDebug() << val.toStringList();
1270                 item->addSubproperty(name.toLatin1(), val);
1271                 //subwidget->setProperty(name.toLatin1(), val);
1272                 item->addModifiedProperty(name.toLatin1(), val);
1273                 continue;
1274             }
1275 
1276             // We cannot assign the buddy now as the buddy widget may not be created yet
1277             if (name == "buddy") {
1278                 if (buddies && qobject_cast<QLabel*>(w)) {
1279                     buddies->insert(readPropertyValue(
1280                                     container->form(), node.firstChild(), w, name).toString(),
1281                                     qobject_cast<QLabel*>(w));
1282                 }
1283             }
1284             else if (    (eltag == "grid" || eltag == "hbox" || eltag == "vbox")
1285                       && item->container()
1286                       && item->container()->layout() )
1287             {
1288                 // We load the margin of a Layout
1289                 if (name == "margin")  {
1290                     int margin = readPropertyValue(container->form(), node.firstChild(), w, name).toInt();
1291                     item->container()->setLayoutMargin(margin);
1292                     item->container()->layout()->setMargin(margin);
1293                 }
1294                 // We load the spacing of a Layout
1295                 else if (name == "spacing")  {
1296                     int spacing = readPropertyValue(container->form(), node.firstChild(), w, name).toInt();
1297                     item->container()->setLayoutSpacing(spacing);
1298                     item->container()->layout()->setSpacing(spacing);
1299                 }
1300             }
1301             else if (name == "paletteBackgroundColor" || name == "paletteForegroundColor") {
1302                 QPalette widgetPalette(w->palette());
1303                 QVariant val(readPropertyValue(container->form(), node.firstChild(), w, name));
1304                 if (!val.isNull())
1305                     widgetPalette.setColor(
1306                         name == "paletteBackgroundColor" ? w->backgroundRole() : w->foregroundRole(),
1307                         val.value<QColor>());
1308                 w->setPalette(widgetPalette);
1309                 if (name == "paletteBackgroundColor") {
1310                     w->setAutoFillBackground(val.value<QColor>().isValid());
1311                 }
1312                 item->addModifiedProperty(name.toLatin1(), val);
1313             }
1314             else if (!isQt3NameProperty && -1 == subwidget->metaObject()->indexOfProperty(name.toLatin1()))
1315             {
1316                 // If the object doesn't have this property, we let the Factory handle it (maybe a special property)
1317                 if (w->metaObject()->className() == QString::fromLatin1("CustomWidget"))
1318                     item->storeUnknownProperty(node);
1319                 else {
1320                     bool read = container->form()->library()->readSpecialProperty(
1321                                     w->metaObject()->className(), node, w, item);
1322                     if (!read) // the factory doesn't support this property neither
1323                         item->storeUnknownProperty(node);
1324                 }
1325             }
1326             else { // we have a normal property, let's load it
1327                 QVariant val(readPropertyValue(container->form(), node.firstChild(), w, name));
1328                 if (name == "geometry" && dynamic_cast<FormWidget*>(w)) {
1329                     //fix geometry if needed - this is top level form widget
1330                     QRect r(val.toRect());
1331                     if (r.left() < 0) //negative X!
1332                         r.moveLeft(0);
1333                     if (r.top() < 0) //negative Y!
1334                         r.moveTop(0);
1335                     val = r;
1336                 }
1337                 QByteArray realName;
1338                 if (isQt3NameProperty) {
1339                     realName = "objectName";
1340                 }
1341                 else {
1342                     realName = name.toLatin1();
1343                 }
1344                 subwidget->setProperty(realName, val);
1345 //    int count = w->metaObject()->findProperty(name, true);
1346 //    const QMetaProperty *meta = w->metaObject()->property(count, true);
1347 //    if(meta && meta->isEnumType()) {
1348 //     val = w->property(name.toLatin1()); //update: we want a numeric value of enum
1349 //    }
1350                 item->addModifiedProperty(realName, val);
1351             }
1352         }
1353         else if (tag == "widget") { // a child widget
1354             if (item->container()) // we are a Container
1355                 loadWidget(item->container(), node, 0, buddies);
1356             else
1357                 loadWidget(container, node, w, buddies);
1358         }
1359         else if (tag == "spacer")  {
1360             loadWidget(container, node, w, buddies);
1361         }
1362         else if (tag == "grid") {
1363             // first, see if it is flow layout
1364             QString layoutName;
1365             for (QDomNode child = node.firstChild(); !child.isNull(); child = child.nextSibling())  {
1366                 if (   child.toElement().tagName() == "property"
1367                     && child.toElement().attribute("name") == "customLayout")
1368                 {
1369                     layoutName = child.toElement().text();
1370                     break;
1371                 }
1372             }
1373 
1374             if (layoutName == "HFlow") {
1375             } else if (layoutName == "VFlow") {
1376             } else { // grid layout
1377                 item->container()->setLayoutType(Form::Grid);
1378                 QGridLayout *layout = new QGridLayout(item->widget());
1379                 item->container()->setLayout((QLayout*)layout);
1380             }
1381             readChildNodes(item, container, node, w, buddies);
1382         } else if (tag == "vbox")  {
1383             item->container()->setLayoutType(Form::VBox);
1384             QVBoxLayout *layout = new QVBoxLayout(item->widget());
1385             item->container()->setLayout((QLayout*)layout);
1386             readChildNodes(item, container, node, w, buddies);
1387         } else if (tag == "hbox") {
1388             item->container()->setLayoutType(Form::HBox);
1389             QHBoxLayout *layout = new QHBoxLayout(item->widget());
1390             item->container()->setLayout((QLayout*)layout);
1391             readChildNodes(item, container, node, w, buddies);
1392         } else {// unknown tag, we let the Factory handle it
1393             if (w->metaObject()->className() == QString::fromLatin1("CustomWidget"))
1394                 item->storeUnknownProperty(node);
1395             else {
1396                 bool read = container->form()->library()->readSpecialProperty(
1397                                 w->metaObject()->className(), node, w, item);
1398                 if (!read) // the factory doesn't suport this property neither
1399                     item->storeUnknownProperty(node);
1400             }
1401         }
1402     }
1403 }
1404 
1405 /////////////////////////////////////////////////////////////////////////////
1406 ///////////// Helper functions //////////////////////////////////////
1407 /////////////////////////////////////////////////////////////////////////////
1408 
1409 void
addIncludeFileName(const QString & include,QDomDocument & domDoc)1410 FormIO::addIncludeFileName(const QString &include, QDomDocument &domDoc)
1411 {
1412     if (include.isEmpty())
1413         return;
1414 
1415     QDomElement includes;
1416     QDomElement uiEl = domDoc.firstChildElement("UI");
1417     if (uiEl.firstChildElement("includehints").isNull()) {
1418         includes = domDoc.createElement("includehints");
1419         uiEl.appendChild(includes);
1420     } else {
1421         includes = uiEl.firstChildElement("includehints");
1422     }
1423 
1424     // Check if this include has already been saved, and return if it is the case
1425     for (QDomNode n = includes.firstChild(); !n.isNull(); n = n.nextSibling()) {
1426         if (n.toElement().text() == include)
1427             return;
1428     }
1429 
1430     QDomElement includeHint = domDoc.createElement("includehint");
1431     includes.appendChild(includeHint);
1432     QDomText includeText = domDoc.createTextNode(include);
1433     includeHint.appendChild(includeText);
1434 }
1435 
1436 QString
saveImage(QDomDocument & domDoc,const QPixmap & pixmap)1437 FormIO::saveImage(QDomDocument &domDoc, const QPixmap &pixmap)
1438 {
1439     QDomElement images = domDoc.firstChildElement("images");
1440     if (images.isNull()) {
1441         images = domDoc.createElement("images");
1442         QDomElement ui = domDoc.firstChildElement("UI");
1443         ui.appendChild(images);
1444     }
1445 
1446     int count = images.childNodes().count();
1447     QDomElement image = domDoc.createElement("image");
1448     QString name = "image" + QString::number(count);
1449     image.setAttribute("name", name);
1450 
1451     const QImage img(pixmap.toImage());
1452     QByteArray ba;
1453     QBuffer buf(&ba);
1454     buf.open(QIODevice::WriteOnly | QIODevice::Text);
1455     const QByteArray format(img.depth() > 1 ? "XPM" : "XBM");
1456     QImageWriter imageWriter(&buf, format);
1457     imageWriter.write(img);
1458     buf.close();
1459     const QByteArray bazip = qCompress(ba);
1460     const int len = bazip.size();
1461 
1462     QDomElement data = domDoc.createElement("data");
1463     data.setAttribute("format", QString(format + ".GZ"));
1464     data.setAttribute("length", ba.size());
1465 
1466     static const char hexchars[] = "0123456789abcdef";
1467     QString content;
1468     for (int i = 4; i < len; i++) {
1469         uchar s = (uchar) bazip[i];
1470         content += hexchars[s >> 4];
1471         content += hexchars[s & 0x0f];
1472     }
1473 
1474     data.appendChild(domDoc.createTextNode(content));
1475     image.appendChild(data);
1476     images.appendChild(image);
1477 
1478     return name;
1479 }
1480 
1481 QPixmap
loadImage(QDomDocument domDoc,const QString & name)1482 FormIO::loadImage(QDomDocument domDoc, const QString& name)
1483 {
1484     QDomElement images = domDoc.firstChildElement("UI").firstChildElement("images");
1485     if (images.isNull())
1486         return QPixmap();
1487 
1488     QDomElement image;
1489     for (QDomNode n = images.firstChild(); !n.isNull(); n = n.nextSibling()) {
1490         if ((n.toElement().tagName() == "image") && (n.toElement().attribute("name") == name)) {
1491             image = n.toElement();
1492             break;
1493         }
1494     }
1495 
1496     QPixmap pix;
1497     QString data(image.firstChildElement("data").text());
1498     const int lengthOffset = 4;
1499     int baSize = data.length() / 2 + lengthOffset;
1500     uchar *ba = new uchar[baSize];
1501     for (int i = lengthOffset; i < baSize; ++i) {
1502         char h = data[2 * (i-lengthOffset)].toLatin1();
1503         char l = data[2 * (i-lengthOffset) + 1].toLatin1();
1504         uchar r = 0;
1505         if (h <= '9')
1506             r += h - '0';
1507         else
1508             r += h - 'a' + 10;
1509         r = r << 4;
1510         if (l <= '9')
1511             r += l - '0';
1512         else
1513             r += l - 'a' + 10;
1514         ba[i] = r;
1515     }
1516 
1517     QString format = image.firstChildElement("data").attribute("format", "PNG");
1518     if ((format == "XPM.GZ") || (format == "XBM.GZ")) {
1519         int len = image.attribute("length").toInt();
1520         if (len < data.length() * 5)
1521             len = data.length() * 5;
1522         // qUncompress() expects the first 4 bytes to be the expected length of
1523         // the uncompressed data
1524         ba[0] = (len & 0xff000000) >> 24;
1525         ba[1] = (len & 0x00ff0000) >> 16;
1526         ba[2] = (len & 0x0000ff00) >> 8;
1527         ba[3] = (len & 0x000000ff);
1528         QByteArray baunzip = qUncompress(ba, baSize);
1529         KexiUtils::loadPixmapFromData(&pix, baunzip, format.left(format.indexOf('.')).toLatin1());
1530     } else {
1531         QByteArray b(QByteArray::fromRawData((const char*)ba + lengthOffset, baSize - lengthOffset));
1532         KexiUtils::loadPixmapFromData(&pix, b, format.toLatin1());
1533     }
1534 
1535     delete[] ba;
1536 
1537     return pix;
1538 }
1539 
1540