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