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