Update DBus Notify method annotation name
[lxde/liblxqt.git] / lxqtsettings.cpp
CommitLineData
4ef6cee9 1/* BEGIN_COMMON_COPYRIGHT_HEADER
c4af778e 2 * (c)LGPL2+
4ef6cee9 3 *
b9223fe7 4 * LXQt - a lightweight, Qt based, desktop toolset
4ef6cee9 5 * http://razor-qt.org
6 *
7 * Copyright: 2010-2011 Razor team
8 * Authors:
626f1f31 9 * Alexander Sokoloff <sokoloff.a@gmail.com>
4ef6cee9 10 * Petr Vanek <petr@scribus.info>
11 *
12 * This program or library is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
c4af778e 15 * version 2.1 of the License, or (at your option) any later version.
4ef6cee9 16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21
22 * You should have received a copy of the GNU Lesser General
23 * Public License along with this library; if not, write to the
24 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 * Boston, MA 02110-1301 USA
26 *
27 * END_COMMON_COPYRIGHT_HEADER */
28
82830dc2 29#include "lxqtsettings.h"
e6389bfc
HJYP
30#include <QDebug>
31#include <QEvent>
32#include <QDir>
33#include <QStringList>
34#include <QMutex>
35#include <QFileSystemWatcher>
36#include <QSharedData>
67dd270e 37#include <QTimerEvent>
4ef6cee9 38
e6389bfc 39#include <XdgDirs>
afd3b116 40
f05ba5af 41using namespace LXQt;
82830dc2 42
f05ba5af 43class LXQt::SettingsPrivate
4ef6cee9 44{
45public:
82830dc2 46 SettingsPrivate(Settings* parent):
1b34ac93
PK
47 mFileChangeTimer(0),
48 mAppChangeTimer(0),
49 mAddWatchTimer(0),
4ef6cee9 50 mParent(parent)
51 {
52 }
53
de53bc2d
AS
54 QString localizedKey(const QString& key) const;
55
7ee52b88 56 QFileSystemWatcher mWatcher;
1b34ac93
PK
57 int mFileChangeTimer;
58 int mAppChangeTimer;
59 int mAddWatchTimer;
7ee52b88 60
4ef6cee9 61private:
82830dc2 62 Settings* mParent;
4ef6cee9 63};
64
65
f05ba5af 66LXQtTheme* LXQtTheme::mInstance = 0;
4ef6cee9 67
f05ba5af 68class LXQt::LXQtThemeData: public QSharedData {
4ef6cee9 69public:
f05ba5af 70 LXQtThemeData(): mValid(false) {}
4ef6cee9 71 QString loadQss(const QString& qssFile) const;
63a934d5 72 QString findTheme(const QString &themeName);
4ef6cee9 73
63a934d5
AS
74 QString mName;
75 QString mPath;
76 QString mPreviewImg;
77 bool mValid;
4ef6cee9 78
63a934d5 79};
4ef6cee9 80
4ef6cee9 81
f05ba5af 82class LXQt::GlobalSettingsPrivate
63a934d5
AS
83{
84public:
82830dc2 85 GlobalSettingsPrivate(GlobalSettings *parent):
2b0063a8
KS
86 mParent(parent),
87 mThemeUpdated(0ull)
4ef6cee9 88 {
63a934d5 89
4ef6cee9 90 }
91
82830dc2 92 GlobalSettings *mParent;
63a934d5 93 QString mIconTheme;
f05ba5af 94 QString mLXQtTheme;
3e771e4e 95 qlonglong mThemeUpdated;
63a934d5
AS
96
97};
4ef6cee9 98
99
100/************************************************
4ef6cee9 101
102 ************************************************/
82830dc2 103Settings::Settings(const QString& module, QObject* parent) :
41e3762a 104 QSettings("lxqt", module, parent),
82830dc2 105 d_ptr(new SettingsPrivate(this))
4ef6cee9 106{
41e3762a 107 // HACK: we need to ensure that the user (~/.config/lxqt/<module>.conf)
7ee52b88 108 // exists to have functional mWatcher
d6b9c3e5
AM
109 if (!contains("__userfile__"))
110 {
111 setValue("__userfile__", true);
112 sync();
113 }
7ee52b88 114 d_ptr->mWatcher.addPath(this->fileName());
67dd270e 115 connect(&(d_ptr->mWatcher), &QFileSystemWatcher::fileChanged, this, &Settings::_fileChanged);
4ef6cee9 116}
117
118
119/************************************************
120
121 ************************************************/
82830dc2 122Settings::Settings(const QString &fileName, QSettings::Format format, QObject *parent):
e6173915 123 QSettings(fileName, format, parent),
82830dc2 124 d_ptr(new SettingsPrivate(this))
e6173915 125{
db4aaddf 126 // HACK: we need to ensure that the user (~/.config/lxqt/<module>.conf)
e6173915
AS
127 // exists to have functional mWatcher
128 if (!contains("__userfile__"))
129 {
130 setValue("__userfile__", true);
131 sync();
132 }
133 d_ptr->mWatcher.addPath(this->fileName());
67dd270e 134 connect(&(d_ptr->mWatcher), &QFileSystemWatcher::fileChanged, this, &Settings::_fileChanged);
e6173915
AS
135}
136
137
138/************************************************
139
140 ************************************************/
82830dc2 141Settings::Settings(const QSettings* parentSettings, const QString& subGroup, QObject* parent):
094599aa 142 QSettings(parentSettings->organizationName(), parentSettings->applicationName(), parent),
82830dc2 143 d_ptr(new SettingsPrivate(this))
4ef6cee9 144{
145 beginGroup(subGroup);
146}
147
148
149/************************************************
150
151 ************************************************/
82830dc2 152Settings::Settings(const QSettings& parentSettings, const QString& subGroup, QObject* parent):
094599aa 153 QSettings(parentSettings.organizationName(), parentSettings.applicationName(), parent),
82830dc2 154 d_ptr(new SettingsPrivate(this))
4ef6cee9 155{
156 beginGroup(subGroup);
157}
158
00f0a23a
AS
159
160/************************************************
161
162 ************************************************/
82830dc2 163Settings::~Settings()
4ef6cee9 164{
82830dc2 165 // because in the Settings::Settings(const QString& module, QObject* parent)
4ef6cee9 166 // constructor there is no beginGroup() called...
167 if (!group().isEmpty())
168 endGroup();
169
170 delete d_ptr;
171}
172
82830dc2 173bool Settings::event(QEvent *event)
0de9564b
PV
174{
175 if (event->type() == QEvent::UpdateRequest)
176 {
1b34ac93
PK
177 // delay the settingsChanged* signal emitting for:
178 // - checking in _fileChanged
179 // - merging emitting the signals
180 if(d_ptr->mAppChangeTimer)
181 killTimer(d_ptr->mAppChangeTimer);
182 d_ptr->mAppChangeTimer = startTimer(100);
0de9564b 183 }
67dd270e
HJYP
184 else if (event->type() == QEvent::Timer)
185 {
1b34ac93
PK
186 const int timer = static_cast<QTimerEvent*>(event)->timerId();
187 killTimer(timer);
188 if (timer == d_ptr->mFileChangeTimer)
67dd270e 189 {
1b34ac93 190 d_ptr->mFileChangeTimer = 0;
67dd270e 191 fileChanged(); // invoke the real fileChanged() handler.
1b34ac93
PK
192 } else if (timer == d_ptr->mAppChangeTimer)
193 {
194 d_ptr->mAppChangeTimer = 0;
195 // do emit the signals
196 emit settingsChangedByApp();
197 emit settingsChanged();
198 } else if (timer == d_ptr->mAddWatchTimer)
199 {
200 d_ptr->mAddWatchTimer = 0;
201 //try to re-add filename for watching
202 addWatchedFile(fileName());
67dd270e
HJYP
203 }
204 }
0de9564b
PV
205
206 return QSettings::event(event);
207}
208
82830dc2 209void Settings::fileChanged()
4ef6cee9 210{
7ee52b88 211 sync();
1b34ac93 212 emit settingsChangedFromExternal();
7ee52b88 213 emit settingsChanged();
4ef6cee9 214}
215
67dd270e
HJYP
216void Settings::_fileChanged(QString path)
217{
1b34ac93
PK
218 // check if the file isn't changed by our logic
219 // FIXME: this is poor implementation; should we rather compute some hash of values if changed by external?
220 if (0 == d_ptr->mAppChangeTimer)
221 {
222 // delay the change notification for 100 ms to avoid
223 // unnecessary repeated loading of the same config file if
224 // the file is changed for several times rapidly.
225 if(d_ptr->mFileChangeTimer)
226 killTimer(d_ptr->mFileChangeTimer);
227 d_ptr->mFileChangeTimer = startTimer(1000);
228 }
67dd270e 229
1b34ac93
PK
230 addWatchedFile(path);
231}
232
233void Settings::addWatchedFile(QString const & path)
234{
67dd270e
HJYP
235 // D*mn! yet another Qt 5.4 regression!!!
236 // See the bug report: https://github.com/lxde/lxqt/issues/441
237 // Since Qt 5.4, QSettings uses QSaveFile to save the config files.
238 // https://github.com/qtproject/qtbase/commit/8d15068911d7c0ba05732e2796aaa7a90e34a6a1#diff-e691c0405f02f3478f4f50a27bdaecde
239 // QSaveFile will save the content to a new temp file, and replace the old file later.
240 // Hence the existing config file is not changed. Instead, it's deleted and then replaced.
241 // This new behaviour unfortunately breaks QFileSystemWatcher.
242 // After file deletion, we can no longer receive any new change notifications.
243 // The most ridiculous thing is, QFileSystemWatcher does not provide a
244 // way for us to know if a file is deleted. WT*?
245 // Luckily, I found a workaround: If the file path no longer exists
246 // in the watcher's files(), this file is deleted.
247 if(!d_ptr->mWatcher.files().contains(path))
1b34ac93
PK
248 // in some situations adding fails because of non-existing file (e.g. editting file in external program)
249 if (!d_ptr->mWatcher.addPath(path) && 0 == d_ptr->mAddWatchTimer)
250 d_ptr->mAddWatchTimer = startTimer(100);
251
67dd270e
HJYP
252}
253
4ef6cee9 254
255/************************************************
256
257 ************************************************/
82830dc2 258const GlobalSettings *Settings::globalSettings()
4ef6cee9 259{
260 static QMutex mutex;
82830dc2 261 static GlobalSettings *instance = 0;
4ef6cee9 262 if (!instance)
263 {
264 mutex.lock();
265
266 if (!instance)
82830dc2 267 instance = new GlobalSettings();
4ef6cee9 268
269 mutex.unlock();
270 }
271
272 return instance;
273}
274
275
276/************************************************
85965778
JL
277 LC_MESSAGES value Possible keys in order of matching
278 lang_COUNTRY@MODIFIER lang_COUNTRY@MODIFIER, lang_COUNTRY, lang@MODIFIER, lang,
de53bc2d 279 default value
85965778
JL
280 lang_COUNTRY lang_COUNTRY, lang, default value
281 lang@MODIFIER lang@MODIFIER, lang, default value
282 lang lang, default value
de53bc2d 283 ************************************************/
82830dc2 284QString SettingsPrivate::localizedKey(const QString& key) const
de53bc2d
AS
285{
286
287 QString lang = getenv("LC_MESSAGES");
288
289 if (lang.isEmpty())
290 lang = getenv("LC_ALL");
291
292 if (lang.isEmpty())
293 lang = getenv("LANG");
294
295
296 QString modifier = lang.section('@', 1);
297 if (!modifier.isEmpty())
298 lang.truncate(lang.length() - modifier.length() - 1);
299
300 QString encoding = lang.section('.', 1);
301 if (!encoding.isEmpty())
302 lang.truncate(lang.length() - encoding.length() - 1);
303
304
305 QString country = lang.section('_', 1);
306 if (!country.isEmpty())
307 lang.truncate(lang.length() - country.length() - 1);
308
309
310
311 //qDebug() << "LC_MESSAGES: " << getenv("LC_MESSAGES");
312 //qDebug() << "Lang:" << lang;
313 //qDebug() << "Country:" << country;
314 //qDebug() << "Encoding:" << encoding;
315 //qDebug() << "Modifier:" << modifier;
316
317 if (!modifier.isEmpty() && !country.isEmpty())
318 {
319 QString k = QString("%1[%2_%3@%4]").arg(key, lang, country, modifier);
320 //qDebug() << "\t try " << k << mParent->contains(k);
321 if (mParent->contains(k))
322 return k;
323 }
324
325 if (!country.isEmpty())
326 {
327 QString k = QString("%1[%2_%3]").arg(key, lang, country);
328 //qDebug() << "\t try " << k << mParent->contains(k);
329 if (mParent->contains(k))
330 return k;
331 }
332
333 if (!modifier.isEmpty())
334 {
335 QString k = QString("%1[%2@%3]").arg(key, lang, modifier);
336 //qDebug() << "\t try " << k << mParent->contains(k);
337 if (mParent->contains(k))
338 return k;
339 }
340
341 QString k = QString("%1[%2]").arg(key, lang);
342 //qDebug() << "\t try " << k << mParent->contains(k);
343 if (mParent->contains(k))
344 return k;
345
346
347 //qDebug() << "\t try " << key << mParent->contains(key);
348 return key;
349}
350
de53bc2d
AS
351/************************************************
352
353 ************************************************/
82830dc2 354QVariant Settings::localizedValue(const QString& key, const QVariant& defaultValue) const
de53bc2d 355{
82830dc2 356 Q_D(const Settings);
de53bc2d
AS
357 return value(d->localizedKey(key), defaultValue);
358}
359
360
361/************************************************
362
363 ************************************************/
82830dc2 364void Settings::setLocalizedValue(const QString &key, const QVariant &value)
de53bc2d 365{
82830dc2 366 Q_D(const Settings);
de53bc2d
AS
367 setValue(d->localizedKey(key), value);
368}
369
370
371/************************************************
4ef6cee9 372
373 ************************************************/
f05ba5af
PL
374LXQtTheme::LXQtTheme():
375 d(new LXQtThemeData)
4ef6cee9 376{
4ef6cee9 377}
378
63a934d5
AS
379
380/************************************************
381
382 ************************************************/
f05ba5af
PL
383LXQtTheme::LXQtTheme(const QString &path):
384 d(new LXQtThemeData)
4ef6cee9 385{
63a934d5
AS
386 if (path.isEmpty())
387 return;
388
389 QFileInfo fi(path);
390 if (fi.isAbsolute())
391 {
392 d->mPath = path;
393 d->mName = fi.fileName();
394 d->mValid = fi.isDir();
395 }
396 else
397 {
398 d->mName = path;
399 d->mPath = d->findTheme(path);
400 d->mValid = !(d->mPath.isEmpty());
401 }
402
403 if (QDir(path).exists("preview.png"))
404 d->mPreviewImg = path + "/preview.png";
4ef6cee9 405}
406
407
408/************************************************
409
410 ************************************************/
f05ba5af 411QString LXQtThemeData::findTheme(const QString &themeName)
4ef6cee9 412{
63a934d5 413 if (themeName.isEmpty())
c24f8b85 414 return QString();
63a934d5
AS
415
416 QStringList paths;
26f76c3e
LP
417 QLatin1String fallback(LXQT_INSTALL_PREFIX);
418
63a934d5
AS
419 paths << XdgDirs::dataHome(false);
420 paths << XdgDirs::dataDirs();
26f76c3e
LP
421
422 if (!paths.contains(fallback))
423 paths << fallback;
4ef6cee9 424
a1a7c847 425 foreach(const QString &path, paths)
4ef6cee9 426 {
d0478f03 427 QDir dir(QString("%1/lxqt/themes/%2").arg(path, themeName));
63a934d5
AS
428 if (dir.isReadable())
429 return dir.absolutePath();
430 }
4ef6cee9 431
63a934d5
AS
432 return QString();
433}
4ef6cee9 434
4ef6cee9 435
63a934d5
AS
436/************************************************
437
438 ************************************************/
f05ba5af 439LXQtTheme::LXQtTheme(const LXQtTheme &other):
63a934d5
AS
440 d(other.d)
441{
442}
443
444
445/************************************************
446
447 ************************************************/
f05ba5af 448LXQtTheme::~LXQtTheme()
63a934d5
AS
449{
450}
451
452
453/************************************************
454
455 ************************************************/
f05ba5af 456LXQtTheme& LXQtTheme::operator=(const LXQtTheme &other)
63a934d5
AS
457{
458 d = other.d;
459 return *this;
460}
461
462
463/************************************************
464
465 ************************************************/
f05ba5af 466bool LXQtTheme::isValid() const
63a934d5
AS
467{
468 return d->mValid;
469}
470
471
472/************************************************
473
474 ************************************************/
f05ba5af 475QString LXQtTheme::name() const
63a934d5
AS
476{
477 return d->mName;
478}
479
480/************************************************
481
482 ************************************************/
f05ba5af 483QString LXQtTheme::path() const
63a934d5
AS
484{
485 return d->mPath;
486}
487
488
489/************************************************
490
491 ************************************************/
f05ba5af 492QString LXQtTheme::previewImage() const
63a934d5
AS
493{
494 return d->mPreviewImg;
4ef6cee9 495}
496
497
498/************************************************
499
500 ************************************************/
f05ba5af 501QString LXQtTheme::qss(const QString& module) const
4ef6cee9 502{
83c93de4 503 return d->loadQss(QStringLiteral("%1/%2.qss").arg(d->mPath, module));
4ef6cee9 504}
505
506
507/************************************************
508
509 ************************************************/
f05ba5af 510QString LXQtThemeData::loadQss(const QString& qssFile) const
4ef6cee9 511{
512 QFile f(qssFile);
513 if (! f.open(QIODevice::ReadOnly | QIODevice::Text))
514 {
96acff74 515 return QString();
4ef6cee9 516 }
517
518 QString qss = f.readAll();
519 f.close();
520
521 if (qss.isEmpty())
96acff74 522 return QString();
4ef6cee9 523
524 // handle relative paths
525 QString qssDir = QFileInfo(qssFile).canonicalPath();
526 qss.replace(QRegExp("url.[ \\t\\s]*", Qt::CaseInsensitive, QRegExp::RegExp2), "url(" + qssDir + "/");
527
528 return qss;
529}
530
531
532/************************************************
533
534 ************************************************/
f05ba5af 535QString LXQtTheme::desktopBackground(int screen) const
4ef6cee9 536{
fa20bdad 537 QString wallpaperCfgFileName = QString("%1/wallpaper.cfg").arg(d->mPath);
4ef6cee9 538
fa20bdad 539 if (wallpaperCfgFileName.isEmpty())
96acff74 540 return QString();
4ef6cee9 541
fa20bdad
KS
542 QSettings s(wallpaperCfgFileName, QSettings::IniFormat);
543 QString themeDir = QFileInfo(wallpaperCfgFileName).absolutePath();
4ef6cee9 544 // There is something strange... If I remove next line the wallpapers array is not found...
545 s.childKeys();
546 s.beginReadArray("wallpapers");
547
548 s.setArrayIndex(screen - 1);
549 if (s.contains("file"))
550 return QString("%1/%2").arg(themeDir, s.value("file").toString());
551
552 s.setArrayIndex(0);
553 if (s.contains("file"))
554 return QString("%1/%2").arg(themeDir, s.value("file").toString());
555
556 return QString();
557}
558
559
560/************************************************
561
562 ************************************************/
f05ba5af 563const LXQtTheme &LXQtTheme::currentTheme()
63a934d5 564{
f05ba5af 565 static LXQtTheme theme;
82830dc2 566 QString name = Settings::globalSettings()->value("theme").toString();
63a934d5
AS
567 if (theme.name() != name)
568 {
f05ba5af 569 theme = LXQtTheme(name);
63a934d5
AS
570 }
571 return theme;
572}
573
574
575/************************************************
576
577 ************************************************/
f05ba5af 578QList<LXQtTheme> LXQtTheme::allThemes()
63a934d5 579{
f05ba5af 580 QList<LXQtTheme> ret;
63a934d5
AS
581 QSet<QString> processed;
582
583 QStringList paths;
584 paths << XdgDirs::dataHome(false);
585 paths << XdgDirs::dataDirs();
586
a1a7c847 587 foreach(const QString &path, paths)
63a934d5 588 {
d0478f03 589 QDir dir(QString("%1/lxqt/themes").arg(path));
63a934d5
AS
590 QFileInfoList dirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot);
591
a1a7c847 592 foreach(const QFileInfo &dir, dirs)
63a934d5 593 {
f8267402 594 if (!processed.contains(dir.fileName()) &&
2ccbc79f 595 QDir(dir.absoluteFilePath()).exists("lxqt-panel.qss"))
63a934d5
AS
596 {
597 processed << dir.fileName();
f05ba5af 598 ret << LXQtTheme(dir.absoluteFilePath());
63a934d5
AS
599 }
600
601 }
602 }
603
604 return ret;
605}
606
607
608/************************************************
609
610 ************************************************/
82830dc2 611SettingsCache::SettingsCache(QSettings &settings) :
4ef6cee9 612 mSettings(settings)
613{
614 loadFromSettings();
615}
616
617
618/************************************************
619
620 ************************************************/
82830dc2 621SettingsCache::SettingsCache(QSettings *settings) :
4ef6cee9 622 mSettings(*settings)
623{
624 loadFromSettings();
625}
626
627
628/************************************************
629
630 ************************************************/
82830dc2 631void SettingsCache::loadFromSettings()
4ef6cee9 632{
f5ca359d
LP
633 const QStringList keys = mSettings.allKeys();
634
635 const int N = keys.size();
636 for (int i = 0; i < N; ++i) {
637 mCache.insert(keys.at(i), mSettings.value(keys.at(i)));
638 }
4ef6cee9 639}
640
641
642/************************************************
643
644 ************************************************/
82830dc2 645void SettingsCache::loadToSettings()
4ef6cee9 646{
647 QHash<QString, QVariant>::const_iterator i = mCache.constBegin();
648
649 while(i != mCache.constEnd())
650 {
651 mSettings.setValue(i.key(), i.value());
652 ++i;
653 }
654
655 mSettings.sync();
656}
657
658
659/************************************************
660
661 ************************************************/
82830dc2 662GlobalSettings::GlobalSettings():
41e3762a 663 Settings("lxqt"),
82830dc2 664 d_ptr(new GlobalSettingsPrivate(this))
4ef6cee9 665{
4ef6cee9 666 if (value("icon_theme").toString().isEmpty())
667 {
d0cf41fa 668 qWarning() << QString::fromLatin1("Icon Theme not set. Fallbacking to Oxygen, if installed");
69c061e3 669 const QString fallback(QLatin1String("oxygen"));
6ec51b5d 670
173f5903 671 const QDir dir(QLatin1String(LXQT_DATA_DIR) + QLatin1String("/icons"));
69c061e3 672 if (dir.exists(fallback))
4ef6cee9 673 {
69c061e3
LP
674 setValue("icon_theme", fallback);
675 sync();
4ef6cee9 676 }
d0cf41fa
LP
677 else
678 {
679 qWarning() << QString::fromLatin1("Fallback Icon Theme (Oxygen) not found");
4ef6cee9 680 }
681 }
682
683 fileChanged();
684}
685
82830dc2 686GlobalSettings::~GlobalSettings()
4ef6cee9 687{
688 delete d_ptr;
689}
690
691
692/************************************************
693
694 ************************************************/
82830dc2 695void GlobalSettings::fileChanged()
4ef6cee9 696{
82830dc2 697 Q_D(GlobalSettings);
4ef6cee9 698 sync();
699
700
701 QString it = value("icon_theme").toString();
702 if (d->mIconTheme != it)
703 {
4ef6cee9 704 emit iconThemeChanged();
705 }
706
63a934d5 707 QString rt = value("theme").toString();
3e771e4e 708 qlonglong themeUpdated = value("__theme_updated__").toLongLong();
f05ba5af 709 if ((d->mLXQtTheme != rt) || (d->mThemeUpdated != themeUpdated))
4ef6cee9 710 {
f05ba5af 711 d->mLXQtTheme = rt;
db4aaddf 712 emit lxqtThemeChanged();
4ef6cee9 713 }
714
1b34ac93 715 emit settingsChangedFromExternal();
c16a53ee 716 emit settingsChanged();
4ef6cee9 717}
63a934d5 718