1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 2005 Ismail Donmez <ismail@kde.org>
5     SPDX-FileCopyrightText: 2006 Dario Abatianni <eisfuchs@tigress.com>
6     SPDX-FileCopyrightText: 2006 John Tapsell <johnflux@gmail.com>
7     SPDX-FileCopyrightText: 2007 Eike Hein <hein@kde.org>
8 */
9 
10 #include "theme_config.h"
11 
12 #include "preferences_base.h"
13 #include "images.h"
14 #include "nickiconset.h"
15 #include "common.h"
16 #include "application.h"
17 
18 #include <KMessageBox>
19 #include <KTar>
20 #include <KZip>
21 #include <KDesktopFile>
22 #include <KIO/DeleteJob>
23 #include <KIO/CopyJob>
24 #include <KSharedConfig>
25 #include <KNSCore/DownloadManager>
26 
27 #include <QStringList>
28 #include <QUrl>
29 #include <QFileDialog>
30 #include <QStandardPaths>
31 #include <QTemporaryFile>
32 #include <QMimeDatabase>
33 
34 using namespace Konversation;
35 
Theme_Config(QWidget * parent,const char * name)36 Theme_Config::Theme_Config(QWidget* parent, const char* name)
37   : QWidget(parent)
38 {
39     setObjectName(QString::fromLatin1(name));
40     setupUi(this);
41 
42     m_defaultThemeIndex = -1;
43 
44     // load the current settings
45     loadSettings();
46 
47     getButton->setConfigFile(QStringLiteral("konversation_nicklist_theme.knsrc"));
48 
49     connect(iconThemeIndex, &QListWidget::currentRowChanged, this, &Theme_Config::updatePreview);
50     connect(iconThemeIndex, &QListWidget::itemSelectionChanged, this, &Theme_Config::updateButtons);
51     connect(iconThemeIndex, &QListWidget::itemSelectionChanged, this, &Theme_Config::modified);
52     connect(getButton, &KNS3::Button::dialogFinished, this, &Theme_Config::gotNewSchemes);
53     connect(installButton, &QPushButton::clicked, this, &Theme_Config::installTheme);
54     connect(removeButton, &QPushButton::clicked, this, &Theme_Config::removeTheme);
55 }
56 
~Theme_Config()57 Theme_Config::~Theme_Config()
58 {
59 }
60 
loadSettings()61 void Theme_Config::loadSettings()
62 {
63     // get list of theme dirs
64     const QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("konversation/themes/"), QStandardPaths::LocateDirectory);
65     m_dirs.clear();
66 
67     for (const QString& path : paths) {
68         QDir dir(path);
69 
70         const auto themeDirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
71         for (const QString& themedir : themeDirs) {
72             QFileInfo file(path + themedir + QLatin1String("/index.desktop"));
73 
74             if(file.exists())
75             {
76                 m_dirs.append(file.absoluteFilePath());
77             }
78         }
79     }
80 
81     // if we have any themes
82     if (!m_dirs.isEmpty()) {
83         m_dirs.sort();
84 
85         QString themeName, themeComment, themeDir;
86         QString currentTheme = Preferences::self()->iconTheme();
87         int currentThemeIndex = 0;
88 
89         // clear listview
90         iconThemeIndex->clear();
91         // initialize index counter
92         int i = 0;
93         // iterate through all found theme directories
94         for (const QString& dir : qAsConst(m_dirs)) {
95             KDesktopFile themeRC(dir);
96             // get the name and comment from the theme
97             themeName = themeRC.readName();
98             themeComment = themeRC.readComment();
99 
100             // extract folder name
101             themeDir = dir.section(QLatin1Char('/'),-2,-2);
102             // is this our currently used theme?
103             if (themeDir==currentTheme)
104             {
105                 // remember for hasChanged()
106                 m_oldTheme=themeDir;
107                 // remember for updatePreview()
108                 currentThemeIndex = i;
109             }
110 
111             if (themeDir==QLatin1String("oxygen"))
112                 m_defaultThemeIndex= i;
113 
114             // if there was a comment to the theme, add it to the listview entry string
115             if(!themeComment.isEmpty())
116                 themeName = themeName + QLatin1String(" (") + themeComment + QLatin1Char(')');
117 
118             // insert entry into the listview
119             iconThemeIndex->addItem(themeName);
120 
121             // increment index counter
122             ++i;
123         }
124         // highlight currently active theme and update preview box
125         iconThemeIndex->setCurrentRow(currentThemeIndex);
126     }
127 
128     // if there was no currently used theme found, use the default theme
129     // If anyone knows how to get the default value from this, please change this!
130     if(m_oldTheme.isEmpty())
131         m_oldTheme = QStringLiteral("oxygen");
132 
133     // update enabled/disabled state of buttons
134     updateButtons();
135 }
136 
hasChanged()137 bool Theme_Config::hasChanged()
138 {
139   // return true if the theme selected is different from the saved theme
140   return ( m_oldTheme != m_currentTheme );
141 }
142 
saveSettings()143 void Theme_Config::saveSettings()
144 {
145     // if there are any themes in the listview ...
146     if(iconThemeIndex->count())
147     {
148         // and if anything has changed ...
149         if(hasChanged())
150         {
151             // save icon theme name
152             KSharedConfigPtr config = KSharedConfig::openConfig();
153             KConfigGroup grp = config->group("Themes");
154             grp.writeEntry("IconTheme",m_currentTheme);
155             // set in-memory theme to the saved theme
156             Preferences::self()->setIconTheme(m_currentTheme);
157             // update theme on runtime
158             Application::instance()->images()->initializeNickIcons();
159 
160             // remember current theme for hasChanged()
161             m_oldTheme = m_currentTheme;
162         }
163     }
164 }
165 
restorePageToDefaults()166 void Theme_Config::restorePageToDefaults()
167 {
168     if (m_defaultThemeIndex != -1)
169         iconThemeIndex->setCurrentRow(m_defaultThemeIndex);
170 }
171 
gotNewSchemes()172 void Theme_Config::gotNewSchemes()
173 {
174     loadSettings();
175 }
176 
installTheme()177 void Theme_Config::installTheme()
178 {
179     QUrl themeURL = QFileDialog::getOpenFileUrl(this,
180         i18n("Select Theme Package"), QUrl (),
181         i18n("Konversation Themes (*.tar.gz *.tar.bz2 *.tar *.zip)")
182         );
183 
184     if(themeURL.isEmpty())
185         return;
186 
187     QString themesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/konversation/themes/"));
188     QString tmpThemeFile;
189 
190     QTemporaryFile tmpFile; // file automatically deleted when object is destroyed
191 
192     if (!themeURL.isLocalFile())
193     {
194         tmpFile.open(); // create the file, and thus create tmpFile.fileName
195         tmpFile.close(); // no need to keep the file open, it isn't deleted until the destructor is called
196 
197         QUrl tmpUrl = QUrl::fromLocalFile(tmpFile.fileName());
198         KIO::FileCopyJob *fileCopyJob = KIO::file_copy(themeURL, tmpUrl, -1, KIO::Overwrite);
199         if (!fileCopyJob->exec())
200         {
201             int errorCode = fileCopyJob->error();
202             QString errorString;
203 
204             if (errorCode != 0)
205                 errorString = fileCopyJob->errorString();
206             else
207                 errorString = i18n("Unknown error (0)");
208 
209             KMessageBox::error(nullptr,
210                 errorString,
211                 i18n("Failed to Download Theme"),
212                 KMessageBox::Notify
213                 );
214             return;
215 
216         }
217         tmpThemeFile = tmpUrl.toLocalFile();
218     }
219     else
220     {
221         tmpThemeFile = themeURL.toLocalFile();
222     }
223 
224     QDir themeInstallDir(tmpThemeFile);
225 
226     if(themeInstallDir.exists()) // We got a directory not a file
227     {
228         if(themeInstallDir.exists(QStringLiteral("index.desktop")))
229         {
230             KIO::CopyJob* job = KIO::copy(QUrl(tmpThemeFile), QUrl(themesDir));
231             job->exec(); //FIXME error handling
232         }
233         else
234         {
235             KMessageBox::error(nullptr,
236                 i18n("Theme archive is invalid."),
237                 i18n("Cannot Install Theme"),
238                 KMessageBox::Notify
239                 );
240         }
241     }
242     else // we got a file
243     {
244         QMimeDatabase db;
245         QMimeType mimeType = db.mimeTypeForFile(tmpThemeFile);
246         KArchive *themeArchive;
247 
248         if (mimeType.inherits(QStringLiteral("application/zip")))
249         {
250             themeArchive = new KZip(tmpThemeFile);
251         }
252         else
253         {
254             themeArchive = new KTar(tmpThemeFile);
255         }
256 
257         themeArchive->open(QIODevice::ReadOnly);
258         qApp->processEvents();
259 
260         const KArchiveDirectory* themeDir = themeArchive->directory();
261         const QStringList allEntries = themeDir->entries();
262 
263         for (const QString& entry : allEntries) {
264             if (themeDir->entry(entry + QLatin1String("/index.desktop")) == nullptr) {
265                 KMessageBox::error(nullptr,
266                     i18n("Theme archive is invalid."),
267                     i18n("Cannot Install Theme"),
268                     KMessageBox::Notify
269                     );
270                 break;
271             }
272             else
273                 themeDir->copyTo(themesDir);
274 
275         }
276         themeArchive->close();
277         delete themeArchive;
278     }
279 
280     loadSettings();
281 }
282 
removeTheme()283 void Theme_Config::removeTheme()
284 {
285     QString dir;
286     QString themeName = iconThemeIndex->currentItem() ? iconThemeIndex->currentItem()->text() : QString();
287 
288     dir = m_dirs[iconThemeIndex->currentRow()];
289 
290     int remove = KMessageBox::warningContinueCancel(nullptr,
291         i18n("Do you want to remove %1?", themeName),
292         i18n("Remove Theme"),
293         KStandardGuiItem::del(),KStandardGuiItem::cancel(),
294         QStringLiteral("warningRemoveTheme")
295         );
296 
297     if (remove != KMessageBox::Continue) {
298         return;
299     }
300 
301     dir.chop(QLatin1String("index.desktop").size());
302     // delete the files, the RemoveDeadEntries entry in the
303     // knsrc file takes care of marking the KNS entry is as deleted
304     KIO::DeleteJob* job = KIO::del(QUrl::fromLocalFile(dir));
305     connect(job, &KIO::DeleteJob::result, this, &Theme_Config::postRemoveTheme);
306 }
307 
postRemoveTheme(KJob *)308 void Theme_Config::postRemoveTheme(KJob* /* delete_job */)
309 {
310     loadSettings();
311 }
312 
updatePreview(int id)313 void Theme_Config::updatePreview(int id)
314 {
315     if (id < 0)
316         return;
317     QString dir;
318     dir = m_dirs[id];
319     dir.remove(QStringLiteral("/index.desktop"));
320     NickIconSet nickIconSet;
321     nickIconSet.load(dir);
322 
323     // TODO: use QIcon::paint-based display, to catch icon changing on environment changes like color palette
324     previewLabel1->setPixmap(nickIconSet.nickIcon(Images::Normal).pixmap(16));
325     previewLabel2->setPixmap(nickIconSet.nickIcon(Images::Normal, NickIconSet::UserAway).pixmap(16));
326     previewLabel3->setPixmap(nickIconSet.nickIcon(Images::Voice).pixmap(16));
327     previewLabel4->setPixmap(nickIconSet.nickIcon(Images::HalfOp).pixmap(16));
328     previewLabel5->setPixmap(nickIconSet.nickIcon(Images::Op).pixmap(16));
329     previewLabel6->setPixmap(nickIconSet.nickIcon(Images::Admin).pixmap(16));
330     previewLabel7->setPixmap(nickIconSet.nickIcon(Images::Owner).pixmap(16));
331 }
332 
updateButtons()333 void Theme_Config::updateButtons()
334 {
335     // don't allow clicking "remove" if there is only one or even no theme installed
336     if(iconThemeIndex->count() < 2 || m_dirs.count() - 1 < iconThemeIndex->currentRow())
337     {
338         removeButton->setEnabled(false);
339         return;
340     }
341 
342     // get directory of current theme
343     QString dir = m_dirs[iconThemeIndex->currentRow()];
344     QFile themeRC(dir);
345     // get name for directory
346     m_currentTheme = dir.section(QLatin1Char('/'),-2,-2);
347 
348     // allow delete action only for themes that have been installed by the user
349     if(!themeRC.open(QIODevice::ReadOnly | QIODevice::WriteOnly))
350         removeButton->setEnabled(false);
351     else
352         removeButton->setEnabled(true);
353 
354     themeRC.close();
355 }
356 
357 
358