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) |
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(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 | |
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 | |
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 | |
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 | |
119 | PreviewConfigurationWidget::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 | |
158 | bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const |
159 | { |
160 | return index >= m_firstUserSkinIndex && index != m_browseSkinIndex; |
161 | } |
162 | |
163 | QStringList 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 | |
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(aindex: m_browseSkinIndex++, atext: fi.baseName(), auserData: QVariant(*it)); |
180 | } else { |
181 | qWarning() << "Unable to access the skin directory '" << *it << "'." ; |
182 | } |
183 | } |
184 | } |
185 | |
186 | 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(index: skinIndex).toString(); |
198 | |
199 | return PreviewConfiguration(style, applicationStyleSheet, deviceSkin); |
200 | } |
201 | |
202 | void 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 | |
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 | |
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 | |
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 | |
248 | void 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 | |
256 | 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 | |
264 | int 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 |
311 | PreviewConfigurationWidget::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 | |
326 | PreviewConfigurationWidget::~PreviewConfigurationWidget() |
327 | { |
328 | delete m_impl; |
329 | } |
330 | |
331 | void PreviewConfigurationWidget::saveState() |
332 | { |
333 | m_impl->storeSettings(); |
334 | } |
335 | |
336 | void PreviewConfigurationWidget::slotEditAppStyleSheet() |
337 | { |
338 | m_impl->slotEditAppStyleSheet(); |
339 | } |
340 | |
341 | void PreviewConfigurationWidget::slotDeleteSkinEntry() |
342 | { |
343 | m_impl->slotDeleteSkinEntry(); |
344 | } |
345 | |
346 | void PreviewConfigurationWidget::slotSkinChanged(int index) |
347 | { |
348 | m_impl->slotSkinChanged(index); |
349 | } |
350 | |
351 | } |
352 | |
353 | QT_END_NAMESPACE |
354 | |