1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "newformwidget_p.h"
30 #include "ui_newformwidget.h"
31 #include "qdesigner_formbuilder_p.h"
32 #include "sheet_delegate_p.h"
33 #include "widgetdatabase_p.h"
34 #include "shared_settings_p.h"
35 
36 #include <QtDesigner/abstractformeditor.h>
37 #include <QtDesigner/abstractformwindow.h>
38 #include <QtDesigner/qextensionmanager.h>
39 #include <QtDesigner/abstractlanguage.h>
40 #include <QtDesigner/abstractwidgetdatabase.h>
41 
42 #include <QtCore/qdir.h>
43 #include <QtCore/qfile.h>
44 #include <QtCore/qfileinfo.h>
45 #include <QtCore/qdebug.h>
46 #include <QtCore/qbytearray.h>
47 #include <QtCore/qbuffer.h>
48 #include <QtCore/qdir.h>
49 #include <QtCore/qtextstream.h>
50 
51 #include <QtWidgets/qapplication.h>
52 #include <QtWidgets/qdesktopwidget.h>
53 #include <QtWidgets/qheaderview.h>
54 #include <QtWidgets/qtreewidget.h>
55 #include <QtGui/qpainter.h>
56 #include <QtWidgets/qpushbutton.h>
57 
58 QT_BEGIN_NAMESPACE
59 
60 enum { profileComboIndexOffset = 1 };
61 enum { debugNewFormWidget = 0 };
62 
63 enum NewForm_CustomRole {
64     // File name (templates from resources, paths)
65     TemplateNameRole = Qt::UserRole + 100,
66     // Class name (widgets from Widget data base)
67     ClassNameRole = Qt::UserRole + 101
68 };
69 
70 static const char *newFormObjectNameC = "Form";
71 
72 // Create a form name for an arbitrary class. If it is Qt, qtify it,
73 //  else return "Form".
formName(const QString & className)74 static QString formName(const QString &className)
75 {
76     if (!className.startsWith(QLatin1Char('Q')))
77         return QLatin1String(newFormObjectNameC);
78     QString rc = className;
79     rc.remove(0, 1);
80     return rc;
81 }
82 
83 namespace qdesigner_internal {
84 
85 struct TemplateSize {
86     const char *name;
87     int width;
88     int height;
89 };
90 
91 static const struct TemplateSize templateSizes[] =
92 {
93     { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "Default size"), 0, 0 },
94     { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA portrait (240x320)"), 240, 320  },
95     { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA landscape (320x240)"), 320, 240 },
96     { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA portrait (480x640)"), 480, 640 },
97     { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA landscape (640x480)"), 640, 480 }
98 };
99 
100 /* -------------- NewForm dialog.
101  * Designer takes new form templates from:
102  * 1) Files located in directories specified in resources
103  * 2) Files located in directories specified as user templates
104  * 3) XML from container widgets deemed usable for form templates by the widget
105  *    database
106  * 4) XML from custom container widgets deemed usable for form templates by the
107  *    widget database
108  *
109  * The widget database provides helper functions to obtain lists of names
110  * and xml for 3,4.
111  *
112  * Fixed-size forms for embedded platforms are obtained as follows:
113  * 1) If the origin is a file:
114  *    - Check if the file exists in the subdirectory "/<width>x<height>/" of
115  *      the path (currently the case for the dialog box because the button box
116  *      needs to be positioned)
117  *    - Scale the form using the QWidgetDatabase::scaleFormTemplate routine.
118  * 2) If the origin is XML:
119  *     - Scale the form using the QWidgetDatabase::scaleFormTemplate routine.
120  *
121  * The tree widget item roles indicate which type of entry it is
122  * (TemplateNameRole = file name 1,2, ClassNameRole = class name 3,4)
123  */
124 
NewFormWidget(QDesignerFormEditorInterface * core,QWidget * parentWidget)125 NewFormWidget::NewFormWidget(QDesignerFormEditorInterface *core, QWidget *parentWidget) :
126     QDesignerNewFormWidgetInterface(parentWidget),
127     m_core(core),
128     m_ui(new Ui::NewFormWidget),
129     m_currentItem(nullptr),
130     m_acceptedItem(nullptr)
131 {
132     m_ui->setupUi(this);
133     m_ui->treeWidget->setItemDelegate(new qdesigner_internal::SheetDelegate(m_ui->treeWidget, this));
134     m_ui->treeWidget->header()->hide();
135     m_ui->treeWidget->header()->setStretchLastSection(true);
136     m_ui->lblPreview->setBackgroundRole(QPalette::Base);
137     QDesignerSharedSettings settings(m_core);
138 
139     QString uiExtension = QStringLiteral("ui");
140     QString templatePath = QStringLiteral(":/qt-project.org/designer/templates/forms");
141 
142     QDesignerLanguageExtension *lang = qt_extension<QDesignerLanguageExtension *>(core->extensionManager(), core);
143     if (lang) {
144         templatePath = QStringLiteral(":/templates/forms");
145         uiExtension = lang->uiExtension();
146     }
147 
148     // Resource templates
149     const QString formTemplate = settings.formTemplate();
150     QTreeWidgetItem *selectedItem = nullptr;
151     loadFrom(templatePath, true, uiExtension, formTemplate, selectedItem);
152     // Additional template paths
153     const QStringList formTemplatePaths = settings.formTemplatePaths();
154     const QStringList::const_iterator ftcend = formTemplatePaths.constEnd();
155     for (QStringList::const_iterator it = formTemplatePaths.constBegin(); it != ftcend; ++it)
156         loadFrom(*it, false, uiExtension, formTemplate, selectedItem);
157 
158     // Widgets/custom widgets
159     if (!lang) {
160         //: New Form Dialog Categories
161         loadFrom(tr("Widgets"), qdesigner_internal::WidgetDataBase::formWidgetClasses(core), formTemplate, selectedItem);
162         loadFrom(tr("Custom Widgets"), qdesigner_internal::WidgetDataBase::customFormWidgetClasses(core), formTemplate, selectedItem);
163     }
164 
165     // Still no selection - default to first item
166     if (selectedItem == nullptr && m_ui->treeWidget->topLevelItemCount() != 0) {
167         QTreeWidgetItem *firstTopLevel = m_ui->treeWidget->topLevelItem(0);
168         if (firstTopLevel->childCount() > 0)
169             selectedItem = firstTopLevel->child(0);
170     }
171 
172     // Open parent, select and make visible
173     if (selectedItem) {
174         m_ui->treeWidget->setCurrentItem(selectedItem);
175         selectedItem->setSelected(true);
176         m_ui->treeWidget->scrollToItem(selectedItem->parent());
177     }
178     // Fill profile combo
179     m_deviceProfiles = settings.deviceProfiles();
180     m_ui->profileComboBox->addItem(tr("None"));
181     connect(m_ui->profileComboBox,
182             QOverload<int>::of(&QComboBox::currentIndexChanged),
183             this, &NewFormWidget::slotDeviceProfileIndexChanged);
184     if (m_deviceProfiles.isEmpty()) {
185         m_ui->profileComboBox->setEnabled(false);
186     } else {
187         for (const auto &deviceProfile : qAsConst(m_deviceProfiles))
188             m_ui->profileComboBox->addItem(deviceProfile.name());
189         const int ci = settings.currentDeviceProfileIndex();
190         if (ci >= 0)
191             m_ui->profileComboBox->setCurrentIndex(ci + profileComboIndexOffset);
192     }
193     // Fill size combo
194     for (const TemplateSize &t : templateSizes)
195         m_ui->sizeComboBox->addItem(tr(t.name), QSize(t.width, t.height));
196 
197     setTemplateSize(settings.newFormSize());
198 
199     if (debugNewFormWidget)
200         qDebug() << Q_FUNC_INFO << "Leaving";
201 }
202 
~NewFormWidget()203 NewFormWidget::~NewFormWidget()
204 {
205     QDesignerSharedSettings settings (m_core);
206     settings.setNewFormSize(templateSize());
207     // Do not change previously stored item if dialog was rejected
208     if (m_acceptedItem)
209         settings.setFormTemplate(m_acceptedItem->text(0));
210     delete m_ui;
211 }
212 
on_treeWidget_currentItemChanged(QTreeWidgetItem * current,QTreeWidgetItem *)213 void NewFormWidget::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *)
214 {
215     if (debugNewFormWidget)
216         qDebug() << Q_FUNC_INFO << current;
217     if (!current)
218         return;
219 
220     if (!current->parent()) { // Top level item: Ensure expanded when browsing down
221         return;
222     }
223 
224     m_currentItem = current;
225 
226     emit currentTemplateChanged(showCurrentItemPixmap());
227 }
228 
showCurrentItemPixmap()229 bool NewFormWidget::showCurrentItemPixmap()
230 {
231     bool rc = false;
232     if (m_currentItem) {
233         const QPixmap pixmap = formPreviewPixmap(m_currentItem);
234         if (pixmap.isNull()) {
235             m_ui->lblPreview->setText(tr("Error loading form"));
236         } else {
237             m_ui->lblPreview->setPixmap(pixmap);
238             rc = true;
239         }
240     }
241     return rc;
242 }
243 
on_treeWidget_itemActivated(QTreeWidgetItem * item)244 void NewFormWidget::on_treeWidget_itemActivated(QTreeWidgetItem *item)
245 {
246     if (debugNewFormWidget)
247         qDebug() << Q_FUNC_INFO << item;
248 
249     if (item->data(0, TemplateNameRole).isValid() || item->data(0, ClassNameRole).isValid())
250         emit templateActivated();
251 }
252 
formPreviewPixmap(const QTreeWidgetItem * item)253 QPixmap  NewFormWidget::formPreviewPixmap(const QTreeWidgetItem *item)
254 {
255     // Cache pixmaps per item/device profile
256     const ItemPixmapCacheKey cacheKey(item, profileComboIndex());
257     ItemPixmapCache::iterator it = m_itemPixmapCache.find(cacheKey);
258     if (it == m_itemPixmapCache.end()) {
259         // file or string?
260         const QVariant fileName = item->data(0, TemplateNameRole);
261         QPixmap rc;
262         if (fileName.type() == QVariant::String) {
263             rc = formPreviewPixmap(fileName.toString());
264         } else {
265             const QVariant classNameV = item->data(0, ClassNameRole);
266             Q_ASSERT(classNameV.type() == QVariant::String);
267             const QString className = classNameV.toString();
268             QByteArray data =  qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className)).toUtf8();
269             QBuffer buffer(&data);
270             buffer.open(QIODevice::ReadOnly);
271             rc = formPreviewPixmap(buffer);
272         }
273         if (rc.isNull()) // Retry invalid ones
274             return rc;
275         it = m_itemPixmapCache.insert(cacheKey, rc);
276     }
277     return it.value();
278 }
279 
formPreviewPixmap(const QString & fileName) const280 QPixmap NewFormWidget::formPreviewPixmap(const QString &fileName) const
281 {
282     QFile f(fileName);
283     if (f.open(QFile::ReadOnly)) {
284         QFileInfo fi(fileName);
285         const QPixmap rc = formPreviewPixmap(f, fi.absolutePath());
286         f.close();
287         return rc;
288     }
289     qWarning() << "The file " << fileName << " could not be opened: " << f.errorString();
290     return QPixmap();
291 }
292 
grabForm(QDesignerFormEditorInterface * core,QIODevice & file,const QString & workingDir,const qdesigner_internal::DeviceProfile & dp)293 QImage NewFormWidget::grabForm(QDesignerFormEditorInterface *core,
294                          QIODevice &file,
295                          const QString &workingDir,
296                          const qdesigner_internal::DeviceProfile &dp)
297 {
298     qdesigner_internal::NewFormWidgetFormBuilder
299         formBuilder(core, dp);
300     if (!workingDir.isEmpty())
301         formBuilder.setWorkingDirectory(workingDir);
302 
303     QWidget *widget = formBuilder.load(&file, nullptr);
304     if (!widget)
305         return QImage();
306 
307     const QPixmap pixmap = widget->grab(QRect(0, 0, -1, -1));
308     widget->deleteLater();
309     return pixmap.toImage();
310 }
311 
formPreviewPixmap(QIODevice & file,const QString & workingDir) const312 QPixmap NewFormWidget::formPreviewPixmap(QIODevice &file, const QString &workingDir) const
313 {
314     const QSizeF screenSize(QApplication::desktop()->screenGeometry(this).size());
315     const int previewSize = qRound(screenSize.width() / 7.5); // 256 on 1920px screens.
316     const int margin = previewSize / 32 - 1; // 7 on 1920px screens.
317     const int shadow = margin;
318 
319     const QImage wimage = grabForm(m_core, file, workingDir,  currentDeviceProfile());
320     if (wimage.isNull())
321         return QPixmap();
322     const qreal devicePixelRatio = wimage.devicePixelRatioF();
323     const QSize imageSize(previewSize - margin * 2, previewSize - margin * 2);
324     QImage image = wimage.scaled((QSizeF(imageSize) * devicePixelRatio).toSize(),
325                                  Qt::KeepAspectRatio, Qt::SmoothTransformation);
326     image.setDevicePixelRatio(devicePixelRatio);
327 
328     QImage dest((QSizeF(previewSize, previewSize) * devicePixelRatio).toSize(),
329                 QImage::Format_ARGB32_Premultiplied);
330     dest.setDevicePixelRatio(devicePixelRatio);
331     dest.fill(0);
332 
333     QPainter p(&dest);
334     p.drawImage(margin, margin, image);
335 
336     p.setPen(QPen(palette().brush(QPalette::WindowText), 0));
337 
338     p.drawRect(QRectF(margin - 1, margin - 1, imageSize.width() + 1.5, imageSize.height() + 1.5));
339 
340     const QColor dark(Qt::darkGray);
341     const QColor light(Qt::transparent);
342 
343     // right shadow
344     {
345         const QRect rect(margin + imageSize.width() + 1, margin + shadow, shadow, imageSize.height() - shadow + 1);
346         QLinearGradient lg(rect.topLeft(), rect.topRight());
347         lg.setColorAt(0, dark);
348         lg.setColorAt(1, light);
349         p.fillRect(rect, lg);
350     }
351 
352     // bottom shadow
353     {
354         const QRect rect(margin + shadow, margin + imageSize.height() + 1, imageSize.width() - shadow + 1, shadow);
355         QLinearGradient lg(rect.topLeft(), rect.bottomLeft());
356         lg.setColorAt(0, dark);
357         lg.setColorAt(1, light);
358         p.fillRect(rect, lg);
359     }
360 
361     // bottom/right corner shadow
362     {
363         const QRect rect(margin + imageSize.width() + 1, margin + imageSize.height() + 1, shadow, shadow);
364         QRadialGradient g(rect.topLeft(), shadow - 1);
365         g.setColorAt(0, dark);
366         g.setColorAt(1, light);
367         p.fillRect(rect, g);
368     }
369 
370     // top/right corner
371     {
372         const QRect rect(margin + imageSize.width() + 1, margin, shadow, shadow);
373         QRadialGradient g(rect.bottomLeft(), shadow - 1);
374         g.setColorAt(0, dark);
375         g.setColorAt(1, light);
376         p.fillRect(rect, g);
377     }
378 
379     // bottom/left corner
380     {
381         const QRect rect(margin, margin + imageSize.height() + 1, shadow, shadow);
382         QRadialGradient g(rect.topRight(), shadow - 1);
383         g.setColorAt(0, dark);
384         g.setColorAt(1, light);
385         p.fillRect(rect, g);
386     }
387 
388     p.end();
389 
390     return QPixmap::fromImage(dest);
391 }
392 
loadFrom(const QString & path,bool resourceFile,const QString & uiExtension,const QString & selectedItem,QTreeWidgetItem * & selectedItemFound)393 void NewFormWidget::loadFrom(const QString &path, bool resourceFile, const QString &uiExtension,
394                        const QString &selectedItem, QTreeWidgetItem *&selectedItemFound)
395 {
396     const QDir dir(path);
397 
398     if (!dir.exists())
399         return;
400 
401     // Iterate through the directory and add the templates
402     const QFileInfoList list = dir.entryInfoList(QStringList(QStringLiteral("*.") + uiExtension),
403                                                  QDir::Files);
404 
405     if (list.isEmpty())
406         return;
407 
408     const QChar separator = resourceFile ? QChar(QLatin1Char('/'))
409                                          : QDir::separator();
410     QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget);
411     root->setFlags(root->flags() & ~Qt::ItemIsSelectable);
412     // Try to get something that is easy to read.
413     QString visiblePath = path;
414     int index = visiblePath.lastIndexOf(separator);
415     if (index != -1) {
416         // try to find a second slash, just to be a bit better.
417         const int index2 = visiblePath.lastIndexOf(separator, index - 1);
418         if (index2 != -1)
419             index = index2;
420         visiblePath = visiblePath.mid(index + 1);
421         visiblePath = QDir::toNativeSeparators(visiblePath);
422     }
423 
424     const QChar underscore = QLatin1Char('_');
425     const QChar blank = QLatin1Char(' ');
426     root->setText(0, visiblePath.replace(underscore, blank));
427     root->setToolTip(0, path);
428 
429     const QFileInfoList::const_iterator lcend = list.constEnd();
430     for (QFileInfoList::const_iterator it = list.constBegin(); it != lcend; ++it) {
431         if (!it->isFile())
432             continue;
433 
434         QTreeWidgetItem *item = new QTreeWidgetItem(root);
435         const QString text = it->baseName().replace(underscore, blank);
436         if (selectedItemFound == nullptr && text == selectedItem)
437             selectedItemFound = item;
438         item->setText(0, text);
439         item->setData(0, TemplateNameRole, it->absoluteFilePath());
440     }
441 }
442 
loadFrom(const QString & title,const QStringList & nameList,const QString & selectedItem,QTreeWidgetItem * & selectedItemFound)443 void NewFormWidget::loadFrom(const QString &title, const QStringList &nameList,
444                        const QString &selectedItem, QTreeWidgetItem *&selectedItemFound)
445 {
446     if (nameList.isEmpty())
447         return;
448     QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget);
449     root->setFlags(root->flags() & ~Qt::ItemIsSelectable);
450     root->setText(0, title);
451     const QStringList::const_iterator cend = nameList.constEnd();
452     for (QStringList::const_iterator it = nameList.constBegin(); it != cend; ++it) {
453         const QString text = *it;
454         QTreeWidgetItem *item = new QTreeWidgetItem(root);
455         item->setText(0, text);
456         if (selectedItemFound == nullptr && text == selectedItem)
457             selectedItemFound = item;
458         item->setData(0, ClassNameRole, *it);
459     }
460 }
461 
on_treeWidget_itemPressed(QTreeWidgetItem * item)462 void NewFormWidget::on_treeWidget_itemPressed(QTreeWidgetItem *item)
463 {
464     if (item && !item->parent())
465         item->setExpanded(!item->isExpanded());
466 }
467 
templateSize() const468 QSize NewFormWidget::templateSize() const
469 {
470     return m_ui->sizeComboBox->itemData(m_ui->sizeComboBox->currentIndex()).toSize();
471 }
472 
setTemplateSize(const QSize & s)473 void NewFormWidget::setTemplateSize(const QSize &s)
474 {
475     const int index = s.isNull() ? 0 : m_ui->sizeComboBox->findData(s);
476     if (index != -1)
477         m_ui->sizeComboBox->setCurrentIndex(index);
478 }
479 
readAll(const QString & fileName,QString * errorMessage)480 static QString readAll(const QString &fileName, QString *errorMessage)
481 {
482     QFile file(fileName);
483     if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
484         *errorMessage = NewFormWidget::tr("Unable to open the form template file '%1': %2").arg(fileName, file.errorString());
485         return QString();
486     }
487     return QString::fromUtf8(file.readAll());
488 }
489 
itemToTemplate(const QTreeWidgetItem * item,QString * errorMessage) const490 QString NewFormWidget::itemToTemplate(const QTreeWidgetItem *item, QString *errorMessage) const
491 {
492     const QSize size = templateSize();
493     // file name or string contents?
494     const QVariant templateFileName = item->data(0, TemplateNameRole);
495     if (templateFileName.type() == QVariant::String) {
496         const QString fileName = templateFileName.toString();
497         // No fixed size: just open.
498         if (size.isNull())
499             return readAll(fileName, errorMessage);
500         // try to find a file matching the size, like "../640x480/xx.ui"
501         const QFileInfo fiBase(fileName);
502         QString sizeFileName;
503         QTextStream(&sizeFileName) << fiBase.path() << QDir::separator()
504                                    << size.width() << QLatin1Char('x') << size.height() << QDir::separator()
505                                    << fiBase.fileName();
506         if (QFileInfo(sizeFileName).isFile())
507             return readAll(sizeFileName, errorMessage);
508         // Nothing found, scale via DOM/temporary file
509         QString contents = readAll(fileName, errorMessage);
510         if (!contents.isEmpty())
511             contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false);
512         return contents;
513     }
514     // Content.
515     const QString className = item->data(0, ClassNameRole).toString();
516     QString contents = qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className));
517     if (!size.isNull())
518         contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false);
519     return contents;
520 }
521 
slotDeviceProfileIndexChanged(int idx)522 void NewFormWidget::slotDeviceProfileIndexChanged(int idx)
523 {
524     // Store index for form windows to take effect and refresh pixmap
525     QDesignerSharedSettings settings(m_core);
526     settings.setCurrentDeviceProfileIndex(idx - profileComboIndexOffset);
527     showCurrentItemPixmap();
528 }
529 
profileComboIndex() const530 int NewFormWidget::profileComboIndex() const
531 {
532     return m_ui->profileComboBox->currentIndex();
533 }
534 
currentDeviceProfile() const535 qdesigner_internal::DeviceProfile NewFormWidget::currentDeviceProfile() const
536 {
537     const int ci = profileComboIndex();
538     if (ci > 0)
539         return m_deviceProfiles.at(ci - profileComboIndexOffset);
540     return qdesigner_internal::DeviceProfile();
541 }
542 
hasCurrentTemplate() const543 bool NewFormWidget::hasCurrentTemplate() const
544 {
545     return m_currentItem != nullptr;
546 }
547 
currentTemplateI(QString * ptrToErrorMessage)548 QString NewFormWidget::currentTemplateI(QString *ptrToErrorMessage)
549 {
550     if (m_currentItem == nullptr) {
551         *ptrToErrorMessage = tr("Internal error: No template selected.");
552         return QString();
553     }
554     const QString contents = itemToTemplate(m_currentItem, ptrToErrorMessage);
555     if (contents.isEmpty())
556         return contents;
557 
558     m_acceptedItem = m_currentItem;
559     return contents;
560 }
561 
currentTemplate(QString * ptrToErrorMessage)562 QString NewFormWidget::currentTemplate(QString *ptrToErrorMessage)
563 {
564     if (ptrToErrorMessage)
565         return currentTemplateI(ptrToErrorMessage);
566     // Do not loose the error
567     QString errorMessage;
568     const QString contents = currentTemplateI(&errorMessage);
569     if (!errorMessage.isEmpty())
570         qWarning("%s", errorMessage.toUtf8().constData());
571     return contents;
572 }
573 
574 }
575 
576 QT_END_NAMESPACE
577