source: trunk/tools/designer/src/lib/shared/newformwidget.cpp@ 1168

Last change on this file since 1168 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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