File indexing completed on 2024-04-14 03:41:14

0001 /*
0002     This file is a part of digiKam project
0003     https://www.digikam.org
0004 
0005     SPDX-FileCopyrightText: 2006-2018 Gilles Caulier <caulier dot gilles at gmail dot com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "thememanager.h"
0011 
0012 #include "kstars.h"
0013 #include "kstars_debug.h"
0014 #include "Options.h"
0015 #include "schememanager.h"
0016 #include "widgets/dmsbox.h"
0017 
0018 #include <KLocalizedString>
0019 #include <KActionCollection>
0020 #include <KConfigGroup>
0021 #include <KXmlGuiWindow>
0022 
0023 #include <QTabBar>
0024 #include <QPixmapCache>
0025 #include <QStringList>
0026 #include <QStyleFactory>
0027 #include <QFileInfo>
0028 #include <QPalette>
0029 #include <QColor>
0030 #include <QActionGroup>
0031 #include <QBitmap>
0032 #include <QPainter>
0033 #include <QPixmap>
0034 #include <QApplication>
0035 #include <QAction>
0036 #include <QStandardPaths>
0037 #include <QDirIterator>
0038 #include <QMenu>
0039 #include <QStyle>
0040 #include <QResource>
0041 
0042 namespace KSTheme
0043 {
0044 
0045 class ThemeManagerCreator
0046 {
0047     public:
0048 
0049         Manager object;
0050 };
0051 
0052 Q_GLOBAL_STATIC(ThemeManagerCreator, creator)
0053 
0054 // ---------------------------------------------------------------
0055 
0056 
0057 class Manager::Private
0058 {
0059     public:
0060 
0061         Private()
0062             : defaultThemeName(i18nc("default theme name", "Default")),
0063               themeMenuActionGroup(0),
0064               themeMenuAction(0)
0065         {
0066         }
0067 
0068         const QString          defaultThemeName;
0069         QMap<QString, QString> themeMap;            // map<theme name, theme config path>
0070 
0071         QActionGroup*          themeMenuActionGroup;
0072         QMenu*                 themeMenuAction;
0073 };
0074 
0075 Manager::Manager()
0076     : d(new Private)
0077 {
0078 }
0079 
0080 Manager::~Manager()
0081 {
0082     delete d;
0083 }
0084 
0085 Manager* Manager::instance()
0086 {
0087     return &creator->object;
0088 }
0089 
0090 QString Manager::defaultThemeName() const
0091 {
0092     return d->defaultThemeName;
0093 }
0094 
0095 QString Manager::currentThemeName() const
0096 {
0097     if (!d->themeMenuAction || !d->themeMenuActionGroup)
0098     {
0099         return defaultThemeName();
0100     }
0101 
0102     QAction* const action = d->themeMenuActionGroup->checkedAction();
0103     return (!action ? defaultThemeName()
0104             : action->text().remove(QLatin1Char('&')));
0105 }
0106 
0107 void Manager::setCurrentTheme(const QString &name)
0108 {
0109     if (!d->themeMenuAction || !d->themeMenuActionGroup)
0110     {
0111         return;
0112     }
0113 
0114     QList<QAction*> list = d->themeMenuActionGroup->actions();
0115 
0116     foreach(QAction* const action, list)
0117     {
0118         if (action->text().remove(QLatin1Char('&')) == name)
0119         {
0120             action->setChecked(true);
0121             slotChangePalette();
0122         }
0123     }
0124 }
0125 
0126 void Manager::slotChangePalette()
0127 {
0128     updateCurrentDesktopDefaultThemePreview();
0129 
0130     QString theme(currentThemeName());
0131 
0132     /*if (theme == defaultThemeName() || theme.isEmpty())
0133     {
0134         theme = currentDesktopdefaultTheme();
0135     }*/
0136 
0137     //QString themeIconName("breeze-dark");
0138     IconTheme themeIconType = BREEZE_DARK_THEME;
0139 
0140     if (theme == "Macintosh" || theme == "White Balance" || theme == "High Key" || (theme == "Default"
0141             && currentDesktopdefaultTheme().contains("Dark") == false))
0142         themeIconType = BREEZE_THEME;
0143 
0144     setIconTheme(themeIconType);
0145 
0146     QString filename        = d->themeMap.value(theme);
0147     KSharedConfigPtr config = KSharedConfig::openConfig(filename);
0148     // hint for the style to synchronize the color scheme with the window manager/compositor
0149     qApp->setProperty("KDE_COLOR_SCHEME_PATH", filename);
0150     QPalette palette = SchemeManager::createApplicationPalette(config);
0151 
0152     qApp->setPalette(palette);
0153 
0154     QList<QWidget *> widgets = qApp->allWidgets();
0155     foreach(QWidget *w, widgets)
0156     {
0157         dmsBox *box = qobject_cast<dmsBox *>(w);
0158         if(box)
0159             box->setPalette(palette);
0160         QTabBar *bar = qobject_cast<QTabBar *>(w);
0161         if(bar)
0162             bar->setPalette(palette);
0163     }
0164 
0165     if(theme == "Macintosh")
0166         qApp->setStyle(QStyleFactory::create("macintosh"));
0167     else
0168     {
0169         if (qgetenv("XDG_CURRENT_DESKTOP") != "KDE")
0170             qApp->setStyle(QStyleFactory::create("Fusion"));
0171     }
0172 
0173     // Special case. For Night Vision theme, we also change the Sky Color Scheme
0174     if (theme == "Default" && Options::colorSchemeFile() == "night.colors")
0175         KStars::Instance()->actionCollection()->action("cs_moonless-night")->setChecked(true);
0176     else if (theme == "Night Vision")
0177         KStars::Instance()->actionCollection()->action("cs_night")->setChecked(true);
0178 
0179     QPixmapCache::clear();
0180 
0181     qApp->style()->polish(qApp);
0182 
0183     emit signalThemeChanged();
0184 }
0185 
0186 void Manager::setThemeMenuAction(QMenu* const action)
0187 {
0188     d->themeMenuAction = action;
0189 
0190     action->setStyleSheet("QMenu::icon:checked {background: gray;border: 1px inset gray;position: absolute;"
0191                           "top: 1px;right: 1px;bottom: 1px;left: 1px;}");
0192 
0193     populateThemeMenu();
0194 }
0195 
0196 void Manager::registerThemeActions(KXmlGuiWindow* const win)
0197 {
0198     if (!win)
0199     {
0200         return;
0201     }
0202 
0203     if (!d->themeMenuAction)
0204     {
0205         qCDebug(KSTARS) << "Cannot register theme actions to " << win->windowTitle();
0206         return;
0207     }
0208 
0209     win->actionCollection()->addAction(QLatin1String("themes"), d->themeMenuAction->menuAction());
0210 }
0211 
0212 void Manager::populateThemeQListWidget(QListWidget *themeWidget)
0213 {
0214     themeWidget->clear();
0215 
0216     QList<QAction*> list = d->themeMenuActionGroup->actions();
0217 
0218     foreach(QAction* const action, list)
0219     {
0220         QListWidgetItem *item = new QListWidgetItem();
0221         item->setText( action->text().remove('&') );
0222         item->setIcon( action->icon() );
0223         themeWidget->addItem(item);
0224     }
0225     themeWidget->sortItems();
0226 }
0227 
0228 void Manager::populateThemeMenu()
0229 {
0230     if (!d->themeMenuAction)
0231     {
0232         return;
0233     }
0234 
0235     //QString theme(currentThemeName());
0236 
0237     d->themeMenuAction->clear();
0238     delete d->themeMenuActionGroup;
0239 
0240     d->themeMenuActionGroup = new QActionGroup(d->themeMenuAction);
0241 
0242     connect(d->themeMenuActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangePalette()));
0243 
0244     QAction* const action   = new QAction(defaultThemeName(), d->themeMenuActionGroup);
0245     action->setCheckable(true);
0246     d->themeMenuAction->addAction(action);
0247 
0248     QStringList schemeFiles;
0249     QStringList dirs;
0250 
0251     // kstars themes
0252     dirs << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0253                                       QString::fromLatin1("kstars/themes"),
0254                                       QStandardPaths::LocateDirectory);
0255 
0256     qCDebug(KSTARS) << "Paths to color scheme : " << dirs;
0257 
0258     Q_FOREACH (const QString &dir, dirs)
0259     {
0260         QDirIterator it(dir, QStringList() << QLatin1String("*.colors"));
0261 
0262         while (it.hasNext())
0263         {
0264             schemeFiles.append(it.next());
0265         }
0266     }
0267 
0268     QMap<QString, QAction*> actionMap;
0269 
0270     for (int i = 0; i < schemeFiles.size(); ++i)
0271     {
0272         const QString filename  = schemeFiles.at(i);
0273         const QFileInfo info(filename);
0274         KSharedConfigPtr config = KSharedConfig::openConfig(filename);
0275         QIcon icon              = createSchemePreviewIcon(config);
0276         KConfigGroup group(config, "General");
0277         const QString name      = group.readEntry("Name", info.baseName());
0278         QAction* const ac       = new QAction(name, d->themeMenuActionGroup);
0279         d->themeMap.insert(name, filename);
0280         ac->setIcon(icon);
0281         ac->setCheckable(true);
0282         actionMap.insert(name, ac);
0283     }
0284 
0285 #ifdef Q_OS_MAC
0286     QAction* const macAction   = new QAction(QLatin1String("Macintosh"), d->themeMenuActionGroup);
0287     macAction->setCheckable(true);
0288     //TODO Set appropriate icon
0289     //macAction->setAction(..)
0290     actionMap.insert(QLatin1String("Macintosh"), macAction);
0291 #endif
0292 
0293     // sort the list
0294     QStringList actionMapKeys = actionMap.keys();
0295     actionMapKeys.sort();
0296 
0297     foreach(const QString &name, actionMapKeys)
0298     {
0299         d->themeMenuAction->addAction(actionMap.value(name));
0300     }
0301 
0302     updateCurrentDesktopDefaultThemePreview();
0303     //setCurrentTheme(theme);
0304 }
0305 
0306 void Manager::updateCurrentDesktopDefaultThemePreview()
0307 {
0308     QList<QAction*> list = d->themeMenuActionGroup->actions();
0309 
0310     foreach(QAction* const action, list)
0311     {
0312         if (action->text().remove(QLatin1Char('&')) == defaultThemeName())
0313         {
0314             KSharedConfigPtr config = KSharedConfig::openConfig(d->themeMap.value(currentDesktopdefaultTheme()));
0315             QIcon icon              = createSchemePreviewIcon(config);
0316             action->setIcon(icon);
0317         }
0318     }
0319 }
0320 
0321 QPixmap Manager::createSchemePreviewIcon(const KSharedConfigPtr &config) const
0322 {
0323     const uchar bits1[] = { 0xff, 0xff, 0xff, 0x2c, 0x16, 0x0b };
0324     const uchar bits2[] = { 0x68, 0x34, 0x1a, 0xff, 0xff, 0xff };
0325     const QSize bitsSize(24, 2);
0326     const QBitmap b1    = QBitmap::fromData(bitsSize, bits1);
0327     const QBitmap b2    = QBitmap::fromData(bitsSize, bits2);
0328 
0329     QPixmap pixmap(23, 16);
0330     pixmap.fill(Qt::black); // FIXME use some color other than black for borders?
0331 
0332     KConfigGroup group(config, QLatin1String("WM"));
0333     QPainter p(&pixmap);
0334     SchemeManager windowScheme(QPalette::Active, SchemeManager::Window, config);
0335     p.fillRect(1,  1, 7, 7, windowScheme.background());
0336     p.fillRect(2,  2, 5, 2, QBrush(windowScheme.foreground().color(), b1));
0337 
0338     SchemeManager buttonScheme(QPalette::Active, SchemeManager::Button, config);
0339     p.fillRect(8,   1, 7, 7, buttonScheme.background());
0340     p.fillRect(9,   2, 5, 2, QBrush(buttonScheme.foreground().color(), b1));
0341 
0342     p.fillRect(15,  1, 7, 7, group.readEntry(QLatin1String("activeBackground"),        QColor(96, 148, 207)));
0343     p.fillRect(16,  2, 5, 2, QBrush(group.readEntry(QLatin1String("activeForeground"), QColor(255, 255, 255)), b1));
0344 
0345     SchemeManager viewScheme(QPalette::Active, SchemeManager::View, config);
0346     p.fillRect(1,   8, 7, 7, viewScheme.background());
0347     p.fillRect(2,  12, 5, 2, QBrush(viewScheme.foreground().color(), b2));
0348 
0349     SchemeManager selectionScheme(QPalette::Active, SchemeManager::Selection, config);
0350     p.fillRect(8,   8, 7, 7, selectionScheme.background());
0351     p.fillRect(9,  12, 5, 2, QBrush(selectionScheme.foreground().color(), b2));
0352 
0353     p.fillRect(15,  8, 7, 7, group.readEntry(QLatin1String("inactiveBackground"),        QColor(224, 223, 222)));
0354     p.fillRect(16, 12, 5, 2, QBrush(group.readEntry(QLatin1String("inactiveForeground"), QColor(20,  19,  18)), b2));
0355 
0356     p.end();
0357     return pixmap;
0358 }
0359 
0360 QString Manager::currentDesktopdefaultTheme() const
0361 {
0362     KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("kdeglobals"));
0363     KConfigGroup group(config, "General");
0364     return group.readEntry("ColorScheme");
0365 }
0366 
0367 void Manager::slotSettingsChanged()
0368 {
0369     populateThemeMenu();
0370     slotChangePalette();
0371 }
0372 
0373 void Manager::setIconTheme(IconTheme theme)
0374 {
0375     QString rccFile("breeze-icons.rcc");
0376     QString iconTheme("breeze");
0377 
0378 #if QT_VERSION < 0x051200
0379     qCWarning(KSTARS) << "Current icon theme is" << QIcon::themeName();
0380 #else
0381     qCWarning(KSTARS) << "Current icon theme is" << QIcon::themeName() << "(fallback" << QIcon::fallbackThemeName() << ")";
0382 #endif
0383 
0384     if (theme == BREEZE_DARK_THEME)
0385     {
0386         rccFile = "breeze-icons-dark.rcc";
0387         iconTheme = "breeze-dark";
0388     }
0389 
0390     QStringList themeSearchPaths = (QStringList() << QIcon::themeSearchPaths());
0391 #ifdef Q_OS_OSX
0392     themeSearchPaths = themeSearchPaths << QDir(QCoreApplication::applicationDirPath() + "/../Resources/icons").absolutePath();
0393     QString resourcePath = QDir(QCoreApplication::applicationDirPath() + "/../Resources/icons/" + rccFile).absolutePath();
0394     QResource::registerResource(resourcePath, "/icons/" + iconTheme);
0395 #elif defined(Q_OS_WIN)
0396     themeSearchPaths = themeSearchPaths << QStandardPaths::locate(QStandardPaths::GenericDataLocation, "icons",
0397                        QStandardPaths::LocateDirectory);
0398     QString resourcePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "icons",
0399                            QStandardPaths::LocateDirectory) + QDir::separator() + iconTheme + QDir::separator() + rccFile;
0400     QResource::registerResource(resourcePath, "/icons/" + iconTheme);
0401 #else
0402     // On Linux on non-KDE Distros, find out if the themes are installed or not and perhaps warn the user
0403     // The important point is that the current theme must be left as is to avoid empty icons
0404     {
0405         bool missing = true;
0406         foreach (auto &path, themeSearchPaths)
0407             if (QFile(path + '/' + iconTheme + "/index.theme").exists())
0408                 missing = false;
0409 
0410         if (missing)
0411         {
0412             qCWarning(KSTARS) << "Warning: icon theme" << iconTheme << "not found, keeping original" << QIcon::themeName() << ".";
0413             iconTheme = QIcon::themeName();
0414         }
0415     }
0416 #endif
0417 
0418     QIcon::setThemeSearchPaths(themeSearchPaths);
0419     //Note: in order to get it to actually load breeze from resources on mac, we had to add the index.theme, and just one icon from breeze into the qrc.  Not sure why this was needed, but it works.
0420     QIcon::setThemeName(iconTheme);
0421 }
0422 
0423 }