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