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
52 static const char *skinResourcePathC = ":/skins/";
53
54 QT_BEGIN_NAMESPACE
55
56 static const char *skinExtensionC = "skin";
57
58 // Pair of skin name, path
59 typedef QPair<QString, QString> SkinNamePath;
60 using Skins = QVector<SkinNamePath>;
61 enum { SkinComboNoneIndex = 0 };
62
63 // find default skins (resources)
defaultSkins()64 static 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(QDir::Dirs|QDir::NoDotAndDotDot, 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(SkinNamePath(it->baseName(), it->filePath()));
77 }
78 return rc;
79 }
80
81 namespace qdesigner_internal {
82
83 // ------------- PreviewConfigurationWidgetPrivate
84 class PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate {
85 public:
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
appStyleSheetChangeButton() const95 QAbstractButton *appStyleSheetChangeButton() const { return m_ui.m_appStyleSheetChangeButton; }
skinRemoveButton() const96 QAbstractButton *skinRemoveButton() const { return m_ui.m_skinRemoveButton; }
skinCombo() const97 QComboBox *skinCombo() const { return m_ui.m_skinCombo; }
98
99 QDesignerFormEditorInterface *m_core;
100
101 private:
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
PreviewConfigurationWidgetPrivate(QDesignerFormEditorInterface * core,QGroupBox * g)119 PreviewConfigurationWidget::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 for (const auto &skin : qAsConst(skins))
148 m_ui.m_skinCombo->addItem(skin.first, QVariant(skin.second));
149 m_browseSkinIndex = m_firstUserSkinIndex = skins.size();
150 m_ui.m_skinCombo->addItem(PreviewConfigurationWidget::tr("Browse..."), QString());
151
152 m_ui.m_skinCombo->setMaxVisibleItems (qMax(15, 2 * m_browseSkinIndex));
153 m_ui.m_skinCombo->setEditable(false);
154
155 retrieveSettings();
156 }
157
canRemoveSkin(int index) const158 bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const
159 {
160 return index >= m_firstUserSkinIndex && index != m_browseSkinIndex;
161 }
162
userSkins() const163 QStringList PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::userSkins() const
164 {
165 QStringList rc;
166 for (int i = m_firstUserSkinIndex; i < m_browseSkinIndex; i++)
167 rc.push_back(m_ui.m_skinCombo->itemData(i).toString());
168 return rc;
169 }
170
addUserSkins(const QStringList & files)171 void 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(m_browseSkinIndex++, fi.baseName(), QVariant(*it));
180 } else {
181 qWarning() << "Unable to access the skin directory '" << *it << "'.";
182 }
183 }
184 }
185
previewConfiguration() const186 PreviewConfiguration 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(skinIndex).toString();
198
199 return PreviewConfiguration(style, applicationStyleSheet, deviceSkin);
200 }
201
setPreviewConfiguration(const PreviewConfiguration & pc)202 void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::setPreviewConfiguration(const PreviewConfiguration &pc)
203 {
204 int styleIndex = m_ui.m_styleCombo->findText(pc.style());
205 if (styleIndex == -1)
206 styleIndex = m_ui.m_styleCombo->findText(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(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
slotEditAppStyleSheet()219 void 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
slotDeleteSkinEntry()227 void 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
slotSkinChanged(int index)237 void 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
retrieveSettings()248 void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::retrieveSettings()
249 {
250 QDesignerSharedSettings settings(m_core);
251 m_parent->setChecked(settings.isCustomPreviewConfigurationEnabled());
252 setPreviewConfiguration(settings.customPreviewConfiguration());
253 addUserSkins(settings.userDeviceSkins());
254 }
255
storeSettings() const256 void 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
browseSkin()264 int PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::browseSkin()
265 {
266 QFileDialog dlg(m_parent);
267 dlg.setFileMode(QFileDialog::Directory);
268 dlg.setOption(QFileDialog::ShowDirsOnly);
269 const QString title = tr("Load Custom Device Skin");
270 dlg.setWindowTitle(title);
271 dlg.setNameFilter(tr("All QVFB Skins (*.%1)").arg(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(name);
286 if (existingIndex != -1 && existingIndex != SkinComboNoneIndex && existingIndex != m_browseSkinIndex) {
287 const QString msgTitle = tr("%1 - Duplicate Skin").arg(title);
288 const QString msg = tr("The skin '%1' already exists.").arg(name);
289 QMessageBox::information(m_parent, msgTitle, msg);
290 break;
291 }
292 // check: 2) can read
293 DeviceSkinParameters parameters;
294 QString readError;
295 if (parameters.read(directory, DeviceSkinParameters::ReadSizeOnly, &readError)) {
296 const QString name = QFileInfo(directory).baseName();
297 m_ui.m_skinCombo->insertItem(m_browseSkinIndex, name, QVariant(directory));
298 rc = m_browseSkinIndex++;
299
300 break;
301 }
302 const QString msgTitle = tr("%1 - Error").arg(title);
303 const QString msg = tr("%1 is not a valid skin directory:\n%2")
304 .arg(directory, readError);
305 QMessageBox::warning (m_parent, msgTitle, msg);
306 } while (true);
307 return rc;
308 }
309
310 // ------------- PreviewConfigurationWidget
PreviewConfigurationWidget(QDesignerFormEditorInterface * core,QWidget * parent)311 PreviewConfigurationWidget::PreviewConfigurationWidget(QDesignerFormEditorInterface *core,
312 QWidget *parent) :
313 QGroupBox(parent),
314 m_impl(new PreviewConfigurationWidgetPrivate(core, this))
315 {
316 connect(m_impl->appStyleSheetChangeButton(), &QAbstractButton::clicked,
317 this, &PreviewConfigurationWidget::slotEditAppStyleSheet);
318 connect(m_impl->skinRemoveButton(), &QAbstractButton::clicked,
319 this, &PreviewConfigurationWidget::slotDeleteSkinEntry);
320 connect(m_impl->skinCombo(), QOverload<int>::of(&QComboBox::currentIndexChanged),
321 this, &PreviewConfigurationWidget::slotSkinChanged);
322
323 m_impl->retrieveSettings();
324 }
325
~PreviewConfigurationWidget()326 PreviewConfigurationWidget::~PreviewConfigurationWidget()
327 {
328 delete m_impl;
329 }
330
saveState()331 void PreviewConfigurationWidget::saveState()
332 {
333 m_impl->storeSettings();
334 }
335
slotEditAppStyleSheet()336 void PreviewConfigurationWidget::slotEditAppStyleSheet()
337 {
338 m_impl->slotEditAppStyleSheet();
339 }
340
slotDeleteSkinEntry()341 void PreviewConfigurationWidget::slotDeleteSkinEntry()
342 {
343 m_impl->slotDeleteSkinEntry();
344 }
345
slotSkinChanged(int index)346 void PreviewConfigurationWidget::slotSkinChanged(int index)
347 {
348 m_impl->slotSkinChanged(index);
349 }
350
351 }
352
353 QT_END_NAMESPACE
354