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 = QList<SkinNamePath>;
61enum { SkinComboNoneIndex = 0 };
62
63// find default skins (resources)
64static const Skins &defaultSkins() {
65 static Skins rc;
66 if (rc.empty()) {
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(QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name);
72 if (list.empty())
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(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("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(styleItems);
134
135 // sheet
136 m_ui.m_appStyleSheetLineEdit->setTextPropertyValidationMode(qdesigner_internal::ValidationStyleSheet);
137 m_ui.m_appStyleSheetClearButton->setIcon(qdesigner_internal::createIconSet(QString::fromUtf8("resetproperty.png")));
138 QObject::connect(m_ui.m_appStyleSheetClearButton, &QAbstractButton::clicked,
139 m_ui.m_appStyleSheetLineEdit, &qdesigner_internal::TextPropertyEditor::clear);
140
141 m_ui.m_skinRemoveButton->setIcon(qdesigner_internal::createIconSet(QString::fromUtf8("editdelete.png")));
142 // skins: find default skins (resources)
143 m_ui.m_skinRemoveButton->setEnabled(false);
144 Skins skins = defaultSkins();
145 skins.push_front(SkinNamePath(PreviewConfigurationWidget::tr("None"), QString()));
146
147 const Skins::const_iterator scend = skins.constEnd();
148 for (Skins::const_iterator it = skins.constBegin(); it != scend; ++it)
149 m_ui.m_skinCombo->addItem (it->first, QVariant(it->second));
150 m_browseSkinIndex = m_firstUserSkinIndex = skins.size();
151 m_ui.m_skinCombo->addItem(PreviewConfigurationWidget::tr("Browse..."), QString());
152
153 m_ui.m_skinCombo->setMaxVisibleItems (qMax(15, 2 * m_browseSkinIndex));
154 m_ui.m_skinCombo->setEditable(false);
155
156 retrieveSettings();
157}
158
159bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const
160{
161 return index >= m_firstUserSkinIndex && index != m_browseSkinIndex;
162}
163
164QStringList PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::userSkins() const
165{
166 QStringList rc;
167 for (int i = m_firstUserSkinIndex; i < m_browseSkinIndex; i++)
168 rc.push_back(m_ui.m_skinCombo->itemData(i).toString());
169 return rc;
170}
171
172void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::addUserSkins(const QStringList &files)
173{
174 if (files.empty())
175 return;
176 const QStringList ::const_iterator fcend = files.constEnd();
177 for (QStringList::const_iterator it = files.constBegin(); it != fcend; ++it) {
178 const QFileInfo fi(*it);
179 if (fi.isDir() && fi.isReadable()) {
180 m_ui.m_skinCombo->insertItem(m_browseSkinIndex++, fi.baseName(), QVariant(*it));
181 } else {
182 qWarning() << "Unable to access the skin directory '" << *it << "'.";
183 }
184 }
185}
186
187PreviewConfiguration PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::previewConfiguration() const
188{
189 PreviewConfiguration rc;
190 QString style = m_ui.m_styleCombo->currentText();
191 if (style == m_defaultStyle)
192 style.clear();
193 const QString applicationStyleSheet = m_ui.m_appStyleSheetLineEdit->text();
194 // Figure out skin. 0 is None by definition..
195 const int skinIndex = m_ui.m_skinCombo->currentIndex();
196 QString deviceSkin;
197 if (skinIndex != SkinComboNoneIndex && skinIndex != m_browseSkinIndex)
198 deviceSkin = m_ui.m_skinCombo->itemData(skinIndex).toString();
199
200 return PreviewConfiguration(style, applicationStyleSheet, deviceSkin);
201}
202
203void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::setPreviewConfiguration(const PreviewConfiguration &pc)
204{
205 int styleIndex = m_ui.m_styleCombo->findText(pc.style());
206 if (styleIndex == -1)
207 styleIndex = m_ui.m_styleCombo->findText(m_defaultStyle);
208 m_ui.m_styleCombo->setCurrentIndex(styleIndex);
209 m_ui.m_appStyleSheetLineEdit->setText(pc.applicationStyleSheet());
210 // find skin by file name. 0 is "none"
211 const QString deviceSkin = pc.deviceSkin();
212 int skinIndex = deviceSkin.isEmpty() ? 0 : m_ui.m_skinCombo->findData(QVariant(deviceSkin));
213 if (skinIndex == -1) {
214 qWarning() << "Unable to find skin '" << deviceSkin << "'.";
215 skinIndex = 0;
216 }
217 m_ui.m_skinCombo->setCurrentIndex(skinIndex);
218}
219
220void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotEditAppStyleSheet()
221{
222 StyleSheetEditorDialog dlg(m_core, m_parent, StyleSheetEditorDialog::ModeGlobal);
223 dlg.setText(m_ui.m_appStyleSheetLineEdit->text());
224 if (dlg.exec() == QDialog::Accepted)
225 m_ui.m_appStyleSheetLineEdit->setText(dlg.text());
226}
227
228void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotDeleteSkinEntry()
229{
230 const int index = m_ui.m_skinCombo->currentIndex();
231 if (canRemoveSkin(index)) {
232 m_ui.m_skinCombo->setCurrentIndex(SkinComboNoneIndex);
233 m_ui.m_skinCombo->removeItem(index);
234 m_browseSkinIndex--;
235 }
236}
237
238void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotSkinChanged(int index)
239{
240 if (index == m_browseSkinIndex) {
241 m_ui.m_skinCombo->setCurrentIndex(browseSkin());
242 } else {
243 m_lastSkinIndex = index;
244 m_ui.m_skinRemoveButton->setEnabled(canRemoveSkin(index));
245 m_ui.m_skinCombo->setToolTip(index != SkinComboNoneIndex ? m_ui.m_skinCombo->itemData(index).toString() : QString());
246 }
247}
248
249void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::retrieveSettings()
250{
251 QDesignerSharedSettings settings(m_core);
252 m_parent->setChecked(settings.isCustomPreviewConfigurationEnabled());
253 setPreviewConfiguration(settings.customPreviewConfiguration());
254 addUserSkins(settings.userDeviceSkins());
255}
256
257void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::storeSettings() const
258{
259 QDesignerSharedSettings settings(m_core);
260 settings.setCustomPreviewConfigurationEnabled(m_parent->isChecked());
261 settings.setCustomPreviewConfiguration(previewConfiguration());
262 settings.setUserDeviceSkins(userSkins());
263}
264
265int PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::browseSkin()
266{
267 QFileDialog dlg(m_parent);
268 dlg.setFileMode(QFileDialog::Directory);
269 dlg.setOption(QFileDialog::ShowDirsOnly);
270 const QString title = tr("Load Custom Device Skin");
271 dlg.setWindowTitle(title);
272 dlg.setNameFilter(tr("All QVFB Skins (*.%1)").arg(QLatin1String(skinExtensionC)));
273
274 int rc = m_lastSkinIndex;
275 do {
276 if (!dlg.exec())
277 break;
278
279 const QStringList directories = dlg.selectedFiles();
280 if (directories.size() != 1)
281 break;
282
283 // check: 1) name already there
284 const QString directory = directories.front();
285 const QString name = QFileInfo(directory).baseName();
286 const int existingIndex = m_ui.m_skinCombo->findText(name);
287 if (existingIndex != -1 && existingIndex != SkinComboNoneIndex && existingIndex != m_browseSkinIndex) {
288 const QString msgTitle = tr("%1 - Duplicate Skin").arg(title);
289 const QString msg = tr("The skin '%1' already exists.").arg(name);
290 QMessageBox::information(m_parent, msgTitle, msg);
291 break;
292 }
293 // check: 2) can read
294 DeviceSkinParameters parameters;
295 QString readError;
296 if (parameters.read(directory, DeviceSkinParameters::ReadSizeOnly, &readError)) {
297 const QString name = QFileInfo(directory).baseName();
298 m_ui.m_skinCombo->insertItem(m_browseSkinIndex, name, QVariant(directory));
299 rc = m_browseSkinIndex++;
300
301 break;
302 }
303 const QString msgTitle = tr("%1 - Error").arg(title);
304 const QString msg = tr("%1 is not a valid skin directory:\n%2")
305 .arg(directory, readError);
306 QMessageBox::warning (m_parent, msgTitle, msg);
307 } while (true);
308 return rc;
309}
310
311// ------------- PreviewConfigurationWidget
312PreviewConfigurationWidget::PreviewConfigurationWidget(QDesignerFormEditorInterface *core,
313 QWidget *parent) :
314 QGroupBox(parent),
315 m_impl(new PreviewConfigurationWidgetPrivate(core, this))
316{
317 connect(m_impl->appStyleSheetChangeButton(), &QAbstractButton::clicked,
318 this, &PreviewConfigurationWidget::slotEditAppStyleSheet);
319 connect(m_impl->skinRemoveButton(), &QAbstractButton::clicked,
320 this, &PreviewConfigurationWidget::slotDeleteSkinEntry);
321 connect(m_impl->skinCombo(), QOverload<int>::of(&QComboBox::currentIndexChanged),
322 this, &PreviewConfigurationWidget::slotSkinChanged);
323
324 m_impl->retrieveSettings();
325}
326
327PreviewConfigurationWidget::~PreviewConfigurationWidget()
328{
329 delete m_impl;
330}
331
332void PreviewConfigurationWidget::saveState()
333{
334 m_impl->storeSettings();
335}
336
337void PreviewConfigurationWidget::slotEditAppStyleSheet()
338{
339 m_impl->slotEditAppStyleSheet();
340}
341
342void PreviewConfigurationWidget::slotDeleteSkinEntry()
343{
344 m_impl->slotDeleteSkinEntry();
345}
346
347void PreviewConfigurationWidget::slotSkinChanged(int index)
348{
349 m_impl->slotSkinChanged(index);
350}
351
352}
353
354QT_END_NAMESPACE
355