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