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 "previewconfigurationwidget_p.h"
30#include "ui_previewconfigurationwidget.h"
31#include "previewmanager_p.h"
32#include "shared_settings_p.h"
33
34#include <iconloader_p.h>
35#include <stylesheeteditor_p.h>
36
37#include <deviceskin.h>
38
39#include <QtDesigner/abstractsettings.h>
40
41#include <QtWidgets/qfiledialog.h>
42#include <QtWidgets/qstylefactory.h>
43#include <QtWidgets/qfiledialog.h>
44#include <QtWidgets/qmessagebox.h>
45#include <QtCore/qpair.h>
46#include <QtCore/qlist.h>
47#include <QtCore/qdebug.h>
48#include <QtCore/qfileinfo.h>
49#include <QtCore/qshareddata.h>
50
51
52static const char *skinResourcePathC = ":/skins/";
53
54QT_BEGIN_NAMESPACE
55
56static const char *skinExtensionC = "skin";
57
58// Pair of skin name, path
59typedef QPair<QString, QString> SkinNamePath;
60using Skins = QVector<SkinNamePath>;
61enum { SkinComboNoneIndex = 0 };
62
63// find default skins (resources)
64static const Skins &defaultSkins() {
65 static Skins rc;
66 if (rc.isEmpty()) {
67 const QString skinPath = QLatin1String(skinResourcePathC);
68 QString pattern = QStringLiteral("*.");
69 pattern += QLatin1String(skinExtensionC);
70 const QDir dir(skinPath, pattern);
71 const QFileInfoList list = dir.entryInfoList(filters: QDir::Dirs|QDir::NoDotAndDotDot, sort: QDir::Name);
72 if (list.isEmpty())
73 return rc;
74 const QFileInfoList::const_iterator lcend = list.constEnd();
75 for (QFileInfoList::const_iterator it = list.constBegin(); it != lcend; ++it)
76 rc.push_back(t: SkinNamePath(it->baseName(), it->filePath()));
77 }
78 return rc;
79}
80
81namespace qdesigner_internal {
82
83// ------------- PreviewConfigurationWidgetPrivate
84class PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate {
85public:
86 PreviewConfigurationWidgetPrivate(QDesignerFormEditorInterface *core, QGroupBox *g);
87
88 void slotEditAppStyleSheet();
89 void slotDeleteSkinEntry();
90 void slotSkinChanged(int index);
91
92 void retrieveSettings();
93 void storeSettings() const;
94
95 QAbstractButton *appStyleSheetChangeButton() const { return m_ui.m_appStyleSheetChangeButton; }
96 QAbstractButton *skinRemoveButton() const { return m_ui.m_skinRemoveButton; }
97 QComboBox *skinCombo() const { return m_ui.m_skinCombo; }
98
99 QDesignerFormEditorInterface *m_core;
100
101private:
102 PreviewConfiguration previewConfiguration() const;
103 void setPreviewConfiguration(const PreviewConfiguration &pc);
104
105 QStringList userSkins() const;
106 void addUserSkins(const QStringList &files);
107 bool canRemoveSkin(int index) const;
108 int browseSkin();
109
110 const QString m_defaultStyle;
111 QGroupBox *m_parent;
112 Ui::PreviewConfigurationWidget m_ui;
113
114 int m_firstUserSkinIndex;
115 int m_browseSkinIndex;
116 int m_lastSkinIndex; // required in case browse fails
117};
118
119PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::PreviewConfigurationWidgetPrivate(
120 QDesignerFormEditorInterface *core, QGroupBox *g) :
121 m_core(core),
122 m_defaultStyle(PreviewConfigurationWidget::tr(s: "Default")),
123 m_parent(g),
124 m_firstUserSkinIndex(0),
125 m_browseSkinIndex(0),
126 m_lastSkinIndex(0)
127{
128 m_ui.setupUi(g);
129 // styles
130 m_ui.m_styleCombo->setEditable(false);
131 QStringList styleItems(m_defaultStyle);
132 styleItems += QStyleFactory::keys();
133 m_ui.m_styleCombo->addItems(texts: styleItems);
134
135 // sheet
136 m_ui.m_appStyleSheetLineEdit->setTextPropertyValidationMode(qdesigner_internal::ValidationStyleSheet);
137 m_ui.m_appStyleSheetClearButton->setIcon(qdesigner_internal::createIconSet(name: QString::fromUtf8(str: "resetproperty.png")));
138 QObject::connect(sender: m_ui.m_appStyleSheetClearButton, signal: &QAbstractButton::clicked,
139 receiver: m_ui.m_appStyleSheetLineEdit, slot: &qdesigner_internal::TextPropertyEditor::clear);
140
141 m_ui.m_skinRemoveButton->setIcon(qdesigner_internal::createIconSet(name: QString::fromUtf8(str: "editdelete.png")));
142 // skins: find default skins (resources)
143 m_ui.m_skinRemoveButton->setEnabled(false);
144 Skins skins = defaultSkins();
145 skins.push_front(t: SkinNamePath(PreviewConfigurationWidget::tr(s: "None"), QString()));
146
147 for (const auto &skin : qAsConst(t&: skins))
148 m_ui.m_skinCombo->addItem(atext: skin.first, auserData: QVariant(skin.second));
149 m_browseSkinIndex = m_firstUserSkinIndex = skins.size();
150 m_ui.m_skinCombo->addItem(atext: PreviewConfigurationWidget::tr(s: "Browse..."), auserData: QString());
151
152 m_ui.m_skinCombo->setMaxVisibleItems (qMax(a: 15, b: 2 * m_browseSkinIndex));
153 m_ui.m_skinCombo->setEditable(false);
154
155 retrieveSettings();
156}
157
158bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const
159{
160 return index >= m_firstUserSkinIndex && index != m_browseSkinIndex;
161}
162
163QStringList PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::userSkins() const
164{
165 QStringList rc;
166 for (int i = m_firstUserSkinIndex; i < m_browseSkinIndex; i++)
167 rc.push_back(t: m_ui.m_skinCombo->itemData(index: i).toString());
168 return rc;
169}
170
171void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::addUserSkins(const QStringList &files)
172{
173 if (files.isEmpty())
174 return;
175 const QStringList ::const_iterator fcend = files.constEnd();
176 for (QStringList::const_iterator it = files.constBegin(); it != fcend; ++it) {
177 const QFileInfo fi(*it);
178 if (fi.isDir() && fi.isReadable()) {
179 m_ui.m_skinCombo->insertItem(aindex: m_browseSkinIndex++, atext: fi.baseName(), auserData: QVariant(*it));
180 } else {
181 qWarning() << "Unable to access the skin directory '" << *it << "'.";
182 }
183 }
184}
185
186PreviewConfiguration PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::previewConfiguration() const
187{
188 PreviewConfiguration rc;
189 QString style = m_ui.m_styleCombo->currentText();
190 if (style == m_defaultStyle)
191 style.clear();
192 const QString applicationStyleSheet = m_ui.m_appStyleSheetLineEdit->text();
193 // Figure out skin. 0 is None by definition..
194 const int skinIndex = m_ui.m_skinCombo->currentIndex();
195 QString deviceSkin;
196 if (skinIndex != SkinComboNoneIndex && skinIndex != m_browseSkinIndex)
197 deviceSkin = m_ui.m_skinCombo->itemData(index: skinIndex).toString();
198
199 return PreviewConfiguration(style, applicationStyleSheet, deviceSkin);
200}
201
202void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::setPreviewConfiguration(const PreviewConfiguration &pc)
203{
204 int styleIndex = m_ui.m_styleCombo->findText(text: pc.style());
205 if (styleIndex == -1)
206 styleIndex = m_ui.m_styleCombo->findText(text: m_defaultStyle);
207 m_ui.m_styleCombo->setCurrentIndex(styleIndex);
208 m_ui.m_appStyleSheetLineEdit->setText(pc.applicationStyleSheet());
209 // find skin by file name. 0 is "none"
210 const QString deviceSkin = pc.deviceSkin();
211 int skinIndex = deviceSkin.isEmpty() ? 0 : m_ui.m_skinCombo->findData(data: QVariant(deviceSkin));
212 if (skinIndex == -1) {
213 qWarning() << "Unable to find skin '" << deviceSkin << "'.";
214 skinIndex = 0;
215 }
216 m_ui.m_skinCombo->setCurrentIndex(skinIndex);
217}
218
219void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotEditAppStyleSheet()
220{
221 StyleSheetEditorDialog dlg(m_core, m_parent, StyleSheetEditorDialog::ModeGlobal);
222 dlg.setText(m_ui.m_appStyleSheetLineEdit->text());
223 if (dlg.exec() == QDialog::Accepted)
224 m_ui.m_appStyleSheetLineEdit->setText(dlg.text());
225}
226
227void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotDeleteSkinEntry()
228{
229 const int index = m_ui.m_skinCombo->currentIndex();
230 if (canRemoveSkin(index)) {
231 m_ui.m_skinCombo->setCurrentIndex(SkinComboNoneIndex);
232 m_ui.m_skinCombo->removeItem(index);
233 m_browseSkinIndex--;
234 }
235}
236
237void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotSkinChanged(int index)
238{
239 if (index == m_browseSkinIndex) {
240 m_ui.m_skinCombo->setCurrentIndex(browseSkin());
241 } else {
242 m_lastSkinIndex = index;
243 m_ui.m_skinRemoveButton->setEnabled(canRemoveSkin(index));
244 m_ui.m_skinCombo->setToolTip(index != SkinComboNoneIndex ? m_ui.m_skinCombo->itemData(index).toString() : QString());
245 }
246}
247
248void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::retrieveSettings()
249{
250 QDesignerSharedSettings settings(m_core);
251 m_parent->setChecked(settings.isCustomPreviewConfigurationEnabled());
252 setPreviewConfiguration(settings.customPreviewConfiguration());
253 addUserSkins(files: settings.userDeviceSkins());
254}
255
256void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::storeSettings() const
257{
258 QDesignerSharedSettings settings(m_core);
259 settings.setCustomPreviewConfigurationEnabled(m_parent->isChecked());
260 settings.setCustomPreviewConfiguration(previewConfiguration());
261 settings.setUserDeviceSkins(userSkins());
262}
263
264int PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::browseSkin()
265{
266 QFileDialog dlg(m_parent);
267 dlg.setFileMode(QFileDialog::Directory);
268 dlg.setOption(option: QFileDialog::ShowDirsOnly);
269 const QString title = tr(s: "Load Custom Device Skin");
270 dlg.setWindowTitle(title);
271 dlg.setNameFilter(tr(s: "All QVFB Skins (*.%1)").arg(a: QLatin1String(skinExtensionC)));
272
273 int rc = m_lastSkinIndex;
274 do {
275 if (!dlg.exec())
276 break;
277
278 const QStringList directories = dlg.selectedFiles();
279 if (directories.size() != 1)
280 break;
281
282 // check: 1) name already there
283 const QString directory = directories.constFirst();
284 const QString name = QFileInfo(directory).baseName();
285 const int existingIndex = m_ui.m_skinCombo->findText(text: name);
286 if (existingIndex != -1 && existingIndex != SkinComboNoneIndex && existingIndex != m_browseSkinIndex) {
287 const QString msgTitle = tr(s: "%1 - Duplicate Skin").arg(a: title);
288 const QString msg = tr(s: "The skin '%1' already exists.").arg(a: name);
289 QMessageBox::information(parent: m_parent, title: msgTitle, text: msg);
290 break;
291 }
292 // check: 2) can read
293 DeviceSkinParameters parameters;
294 QString readError;
295 if (parameters.read(skinDirectory: directory, rm: DeviceSkinParameters::ReadSizeOnly, errorMessage: &readError)) {
296 const QString name = QFileInfo(directory).baseName();
297 m_ui.m_skinCombo->insertItem(aindex: m_browseSkinIndex, atext: name, auserData: QVariant(directory));
298 rc = m_browseSkinIndex++;
299
300 break;
301 }
302 const QString msgTitle = tr(s: "%1 - Error").arg(a: title);
303 const QString msg = tr(s: "%1 is not a valid skin directory:\n%2")
304 .arg(args: directory, args&: readError);
305 QMessageBox::warning (parent: m_parent, title: msgTitle, text: msg);
306 } while (true);
307 return rc;
308}
309
310// ------------- PreviewConfigurationWidget
311PreviewConfigurationWidget::PreviewConfigurationWidget(QDesignerFormEditorInterface *core,
312 QWidget *parent) :
313 QGroupBox(parent),
314 m_impl(new PreviewConfigurationWidgetPrivate(core, this))
315{
316 connect(sender: m_impl->appStyleSheetChangeButton(), signal: &QAbstractButton::clicked,
317 receiver: this, slot: &PreviewConfigurationWidget::slotEditAppStyleSheet);
318 connect(sender: m_impl->skinRemoveButton(), signal: &QAbstractButton::clicked,
319 receiver: this, slot: &PreviewConfigurationWidget::slotDeleteSkinEntry);
320 connect(sender: m_impl->skinCombo(), signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged),
321 receiver: this, slot: &PreviewConfigurationWidget::slotSkinChanged);
322
323 m_impl->retrieveSettings();
324}
325
326PreviewConfigurationWidget::~PreviewConfigurationWidget()
327{
328 delete m_impl;
329}
330
331void PreviewConfigurationWidget::saveState()
332{
333 m_impl->storeSettings();
334}
335
336void PreviewConfigurationWidget::slotEditAppStyleSheet()
337{
338 m_impl->slotEditAppStyleSheet();
339}
340
341void PreviewConfigurationWidget::slotDeleteSkinEntry()
342{
343 m_impl->slotDeleteSkinEntry();
344}
345
346void PreviewConfigurationWidget::slotSkinChanged(int index)
347{
348 m_impl->slotSkinChanged(index);
349}
350
351}
352
353QT_END_NAMESPACE
354

source code of qttools/src/designer/src/lib/shared/previewconfigurationwidget.cpp