1 /***********************************************************************
2 *
3 * Copyright (C) 2014-2018 wereturtle
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include <QStringList>
21 #include <QListWidget>
22 #include <QGridLayout>
23 #include <QDialogButtonBox>
24 #include <QPushButton>
25 #include <QList>
26 #include <QString>
27 #include <QMessageBox>
28 #include <QListWidgetItem>
29 #include <QPainter>
30 #include <QIcon>
31 #include <QPixmap>
32 #include <QImage>
33 #include <QBrush>
34 #include <QColor>
35 #include <QSize>
36
37 #include "ThemeSelectionDialog.h"
38 #include "ThemeFactory.h"
39 #include "ThemePreviewer.h"
40 #include "ThemeEditorDialog.h"
41 #include "MessageBoxHelper.h"
42
43 #define GW_LIST_WIDGET_ICON_WIDTH 200
44 #define GW_LIST_WIDGET_ICON_HEIGHT 125
45
ThemeSelectionDialog(const QString & currentThemeName,QWidget * parent)46 ThemeSelectionDialog::ThemeSelectionDialog
47 (
48 const QString& currentThemeName,
49 QWidget* parent
50 )
51 : QDialog(parent)
52 {
53 qreal dpr = 1.0;
54
55 #if QT_VERSION >= 0x050600
56 dpr = devicePixelRatioF();
57 #endif
58
59 iconWidth = GW_LIST_WIDGET_ICON_WIDTH * dpr;
60 iconHeight = GW_LIST_WIDGET_ICON_HEIGHT * dpr;
61
62 this->setWindowTitle(tr("Themes"));
63 this->setAttribute(Qt::WA_DeleteOnClose);
64
65 QGridLayout* layout = new QGridLayout();
66
67 QStringList availableThemes =
68 ThemeFactory::getInstance()->getAvailableThemes();
69
70 // Find view sizes
71 int focush = style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
72 int focusv = style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
73 int frame = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
74 int scrollbar = style()->pixelMetric(QStyle::PM_SliderThickness) +
75 style()->pixelMetric(QStyle::PM_ScrollBarExtent);
76 const int spacing = 10;
77 const int rowCount = 3;
78 const int columnCount = 4;
79 QSize viewSize
80 (
81 ((GW_LIST_WIDGET_ICON_WIDTH + frame + focush + (spacing * 2)) * columnCount) + scrollbar,
82 (GW_LIST_WIDGET_ICON_HEIGHT + fontMetrics().height() + frame + focusv + (spacing * 2)) * rowCount
83 );
84
85 // Set up theme list
86 themeListWidget = new QListWidget(this);
87 themeListWidget->setSortingEnabled(true);
88 themeListWidget->setFlow(QListView::LeftToRight);
89 themeListWidget->setViewMode(QListView::IconMode);
90 themeListWidget->setIconSize(QSize(GW_LIST_WIDGET_ICON_WIDTH, GW_LIST_WIDGET_ICON_HEIGHT));
91 themeListWidget->setSpacing(spacing);
92 themeListWidget->setMovement(QListView::Static);
93 themeListWidget->setResizeMode(QListView::Adjust);
94 themeListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
95 themeListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
96 themeListWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
97 themeListWidget->setWordWrap(true);
98 themeListWidget->setUniformItemSizes(true);
99 themeListWidget->setMinimumSize(viewSize);
100
101 for (int i = 0; i < availableThemes.size(); i++)
102 {
103 QString themeName = availableThemes[i];
104 QString err;
105 QIcon themeIcon;
106
107 Theme theme = ThemeFactory::getInstance()->loadTheme(themeName, err);
108
109 if (!err.isNull())
110 {
111 themeIcon = QIcon(":resources/images/unavailable.svg");
112 }
113 else
114 {
115 ThemePreviewer previewer(theme, iconWidth, iconHeight);
116 themeIcon = previewer.getIcon();
117 }
118
119 QListWidgetItem* item = new QListWidgetItem
120 (
121 themeIcon,
122 themeName,
123 themeListWidget
124 );
125 themeListWidget->insertItem(themeListWidget->count(), item);
126
127 if (themeName == currentThemeName)
128 {
129 themeListWidget->setCurrentItem(item);
130 currentTheme = theme;
131 }
132 }
133
134 QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this);
135 QPushButton* newThemeButton = new QPushButton(" + ");
136 QPushButton* deleteThemeButton = new QPushButton(" - ");
137 QPushButton* editThemeButton = new QPushButton(tr("Edit..."));
138
139 buttonBox->addButton(newThemeButton, QDialogButtonBox::ActionRole);
140 buttonBox->addButton(deleteThemeButton, QDialogButtonBox::ActionRole);
141 buttonBox->addButton(editThemeButton, QDialogButtonBox::ActionRole);
142 buttonBox->addButton(QDialogButtonBox::Close);
143
144 layout->addWidget(themeListWidget, 0, 0, 1, 1);
145 layout->addWidget(buttonBox, 1, 0, 1, 1);
146
147 this->setLayout(layout);
148
149 connect(buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close()));
150 connect(newThemeButton, SIGNAL(clicked()), this, SLOT(onNewTheme()));
151 connect(deleteThemeButton, SIGNAL(clicked()), this, SLOT(onDeleteTheme()));
152 connect(editThemeButton, SIGNAL(clicked()), this, SLOT(onEditTheme()));
153 connect(themeListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onThemeSelected()));
154
155 currentThemeIsValid = true;
156 currentThemeIsNew = false;
157 }
158
~ThemeSelectionDialog()159 ThemeSelectionDialog::~ThemeSelectionDialog()
160 {
161 ;
162 }
163
onThemeSelected()164 void ThemeSelectionDialog::onThemeSelected()
165 {
166 QList<QListWidgetItem*> selectedThemes = themeListWidget->selectedItems();
167 QString themeName;
168 Theme theme;
169
170 if
171 (
172 selectedThemes.isEmpty() ||
173 selectedThemes[0]->text().isNull() ||
174 selectedThemes[0]->text().isEmpty()
175 )
176 {
177 return;
178 }
179
180 currentThemeIsValid = true;
181 themeName = selectedThemes[0]->text();
182
183 QString err;
184
185 if (!currentThemeIsNew)
186 {
187 theme = ThemeFactory::getInstance()->loadTheme(themeName, err);
188 currentTheme = theme;
189
190 if (!err.isNull())
191 {
192 // Show an error dialog telling the user that the theme could not
193 // be loaded.
194 //
195 MessageBoxHelper::critical
196 (
197 this,
198 tr("Unable to load theme."),
199 err
200 );
201 currentTheme.setName(themeName);
202 currentThemeIsValid = false;
203 return;
204 }
205 }
206 else
207 {
208 theme = currentTheme;
209 }
210
211 emit applyTheme(theme);
212 }
213
onNewTheme()214 void ThemeSelectionDialog::onNewTheme()
215 {
216 QString err = QString();
217 Theme newTheme;
218 QList<QListWidgetItem*> selectedThemes = themeListWidget->selectedItems();
219
220 if (selectedThemes.size() > 0)
221 {
222 newTheme = ThemeFactory::getInstance()->loadTheme
223 (
224 selectedThemes[0]->text(),
225 err
226 );
227 }
228 else
229 {
230 newTheme = ThemeFactory::getInstance()->getDefaultTheme();
231 }
232
233 newTheme.setBuiltIn(false);
234
235 // It doesn't matter if there was an error, since loadTheme will
236 // return a default, built-in theme if it fails.
237 //
238 err = QString();
239
240 QIcon themeIcon;
241
242 if (!err.isNull())
243 {
244 themeIcon = QIcon(":resources/images/unavailable.svg");
245 }
246 else
247 {
248 ThemePreviewer previewer(newTheme, iconWidth, iconHeight);
249 themeIcon = previewer.getIcon();
250 }
251
252 newTheme.setName(ThemeFactory::getInstance()->generateUntitledThemeName());
253 ThemeFactory::getInstance()->saveTheme(newTheme.getName(), newTheme, err);
254
255 QListWidgetItem* item =
256 new QListWidgetItem(themeIcon, newTheme.getName(), themeListWidget);
257 themeListWidget->insertItem(themeListWidget->count(), item);
258
259 currentThemeIsNew = true;
260 currentTheme = newTheme;
261 currentThemeIsValid = true;
262 this->themeListWidget->setCurrentItem(item);
263
264 ThemeEditorDialog themeEditorDialog(newTheme, this);
265 connect(&themeEditorDialog, SIGNAL(themeUpdated(Theme)), this, SLOT(onThemeUpdated(Theme)));
266 themeEditorDialog.exec();
267 }
268
269
onDeleteTheme()270 void ThemeSelectionDialog::onDeleteTheme()
271 {
272 QList<QListWidgetItem*> selectedThemes = themeListWidget->selectedItems();
273
274 if (selectedThemes.isEmpty())
275 {
276 // Ignore if no theme is selected.
277 return;
278 }
279
280 // If the theme is one of the built in themes, display an error.
281 //
282 if (currentTheme.isBuiltIn())
283 {
284 MessageBoxHelper::critical
285 (
286 this,
287 tr("Cannot delete theme."),
288 tr("Sorry, this is a built-in theme that cannot be deleted.")
289 );
290 return;
291 }
292
293 QString themeName = selectedThemes[0]->text();
294
295 int result =
296 MessageBoxHelper::question
297 (
298 this,
299 tr("Are you sure you want to permanently delete the '%1' theme?")
300 .arg(themeName),
301 QString(),
302 QMessageBox::Yes | QMessageBox::No,
303 QMessageBox::No
304 );
305
306 if (QMessageBox::Yes == result)
307 {
308 QString err;
309
310 ThemeFactory::getInstance()->deleteTheme(themeName, err);
311
312 if (!err.isNull())
313 {
314 MessageBoxHelper::critical
315 (
316 this,
317 tr("Failed to delete theme."),
318 err
319 );
320 }
321 else
322 {
323 int row = themeListWidget->row(selectedThemes[0]);
324 QListWidgetItem* item = themeListWidget->takeItem(row);
325
326 if (NULL != item)
327 {
328 delete item;
329 item = NULL;
330 }
331 }
332 }
333 }
334
onEditTheme()335 void ThemeSelectionDialog::onEditTheme()
336 {
337 QList<QListWidgetItem*> selectedThemes = themeListWidget->selectedItems();
338
339 if (!selectedThemes.isEmpty())
340 {
341 // If the theme is one of the built in themes, display an error.
342 //
343 if (currentTheme.isBuiltIn())
344 {
345 MessageBoxHelper::critical
346 (
347 this,
348 tr("Cannot edit theme."),
349 tr("Sorry, this is a built-in theme that cannot be edited.")
350 );
351 return;
352 }
353
354 Theme themeToEdit = currentTheme;
355
356 if (!currentThemeIsValid)
357 {
358 QString err;
359 themeToEdit = ThemeFactory::getInstance()->loadTheme("", err);
360 themeToEdit.setName(currentTheme.getName());
361 }
362
363 ThemeEditorDialog themeEditorDialog(themeToEdit, this);
364 connect(&themeEditorDialog, SIGNAL(themeUpdated(Theme)), this, SLOT(onThemeUpdated(Theme)));
365
366 themeEditorDialog.exec();
367 }
368 }
369
onThemeUpdated(const Theme & theme)370 void ThemeSelectionDialog::onThemeUpdated(const Theme& theme)
371 {
372 // Update theme name and preview icon, as applicable.
373 //
374 QList<QListWidgetItem*> selectedThemes = themeListWidget->selectedItems();
375
376 if (!selectedThemes.isEmpty())
377 {
378 QListWidgetItem* item = selectedThemes[0];
379
380 if (NULL != item)
381 {
382 if (theme.getName() != currentTheme.getName())
383 {
384 item->setText(theme.getName());
385 }
386
387 currentTheme = theme;
388 currentThemeIsValid = true;
389
390 ThemePreviewer previewer(theme, iconWidth, iconHeight);
391 QIcon themeIcon = previewer.getIcon();
392 item->setIcon(themeIcon);
393 }
394 }
395
396 currentThemeIsNew = false;
397
398 // Notify of theme change so it can be applied.
399 emit applyTheme(theme);
400 }
401