File indexing completed on 2024-12-08 11:02:06

0001 /***************************************************************************
0002  *  Copyright (C) 2020 by Renaud Guezennec                               *
0003  *   http://www.rolisteam.org/contact                                      *
0004  *                                                                         *
0005  *   This software is free software; you can redistribute it and/or modify *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
0019  ***************************************************************************/
0020 #include <common_qml/theme.h>
0021 
0022 #include <QColor>
0023 #include <QDebug>
0024 #include <QFile>
0025 #include <QFileInfo>
0026 #include <QRegularExpression>
0027 #include <QTextStream>
0028 
0029 namespace customization
0030 {
0031 
0032 namespace
0033 {
0034 QString pathToDarkMode(const QString& url, bool darkMode)
0035 {
0036     auto path= url;
0037     path= path.replace("qrc", "");
0038     if(!darkMode || !QFileInfo::exists(path))
0039         return url;
0040 
0041     QFileInfo info(path);
0042     auto fileName= info.fileName();
0043     auto prefixPath= info.absolutePath();
0044 
0045     auto combo= QString("%1/+dark/%2").arg(prefixPath, fileName);
0046 
0047     if(QFileInfo::exists(combo))
0048         return QString("qrc%1").arg(combo);
0049     else
0050         return url;
0051 }
0052 } // namespace
0053 StyleSheet::StyleSheet(Theme* parent) : QQmlPropertyMap(parent) {}
0054 
0055 void StyleSheet::insertOrUpdate(const QString& key, const QVariant& value)
0056 {
0057     insert(key, value);
0058 
0059     emit valueChanged(key, value);
0060 }
0061 
0062 QString Theme::m_dataPath= "";
0063 
0064 Theme::Theme(QObject* parent) : QObject(parent)
0065 {
0066     loadData(m_dataPath);
0067 }
0068 
0069 StyleSheet* Theme::styleSheet(const QString& id)
0070 {
0071     auto it= m_styleSheets.find(id);
0072     if(it == m_styleSheets.end())
0073         return nullptr;
0074     return it->second;
0075 }
0076 
0077 bool Theme::nightMode() const
0078 {
0079     return m_nightMode;
0080 }
0081 
0082 QString Theme::folder() const
0083 {
0084     return m_nightMode ? QStringLiteral("+dark/") : QString();
0085 }
0086 
0087 void Theme::setPath(const QString& path)
0088 {
0089     m_dataPath= path;
0090     Q_ASSERT(QFileInfo::exists(m_dataPath));
0091 }
0092 
0093 void Theme::setNightMode(bool b)
0094 {
0095     if(b == m_nightMode)
0096         return;
0097     m_nightMode= b;
0098     emit nightModeChanged(m_nightMode);
0099     emit folderChanged(folder());
0100     loadData(m_dataPath);
0101 }
0102 
0103 Theme* Theme::instance()
0104 {
0105     static Theme theme;
0106     return &theme;
0107 }
0108 
0109 void Theme::loadData(const QString& source)
0110 {
0111     QFile file(source);
0112     if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
0113         return;
0114 
0115     QTextStream in(&file);
0116     StyleSheet* currentStyleSheet= nullptr;
0117     while(!in.atEnd())
0118     {
0119         auto line= file.readLine();
0120         if(line.isEmpty())
0121             continue;
0122 
0123         auto str= line;
0124 
0125         if(str.startsWith("["))
0126         {
0127             auto label= str.mid(1, str.length() - 3);
0128             currentStyleSheet= addStyleSheet(label);
0129         }
0130         else if(currentStyleSheet)
0131         {
0132             QRegularExpression exp("^(\\w+)=(.*)$");
0133             auto match= exp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
0134             if(match.hasMatch())
0135             {
0136                 auto key= match.captured(1);
0137                 auto value= match.captured(2);
0138                 bool ok;
0139                 auto intVal= value.toInt(&ok);
0140                 if(ok)
0141                 {
0142                     currentStyleSheet->insertOrUpdate(key, intVal);
0143                     continue;
0144                 }
0145 
0146                 auto realVal= value.toDouble(&ok);
0147                 if(ok)
0148                 {
0149                     currentStyleSheet->insertOrUpdate(key, realVal);
0150                     continue;
0151                 }
0152 
0153                 QColor color;
0154                 color.setNamedColor(value);
0155                 if(color.isValid())
0156                 {
0157                     if(!key.endsWith("_dark"))
0158                     {
0159                         currentStyleSheet->insertOrUpdate(key, darkColor(color));
0160                     }
0161                     else if(m_nightMode)
0162                     {
0163                         currentStyleSheet->insertOrUpdate(key.remove("_dark"), color);
0164                     }
0165                 }
0166                 else
0167                 {
0168                     currentStyleSheet->insertOrUpdate(key, pathToDarkMode(value, m_nightMode));
0169                 }
0170             }
0171         }
0172     }
0173 }
0174 
0175 QColor Theme::darkColor(const QColor& color)
0176 {
0177     if(!m_nightMode)
0178         return color;
0179 
0180     std::vector<int> c({color.red(), color.green(), color.blue()});
0181     auto brighness= [](const QColor& current)
0182     { return ((current.red() * 299) + (current.green() * 587) + (current.blue() * 114)) / 1000; };
0183     auto avg= std::accumulate(c.begin(), c.end(), 0.0) / static_cast<double>(c.size());
0184     auto allEqual= std::all_of(c.begin(), c.end(), [color](int tmp) { return color.red() == tmp; });
0185     QColor result= color;
0186     if(allEqual)
0187     {
0188         auto cVal= static_cast<int>(255 - avg);
0189         result= QColor(cVal, cVal, cVal);
0190     }
0191     else
0192     {
0193         auto brighnessVal= brighness(result);
0194         auto target= 255 - brighness(result);
0195         auto darker= brighnessVal > 128;
0196         int i= 0;
0197         while(std::abs(brighnessVal - target) > 50 && i < 8)
0198         {
0199             result= darker ? result.darker(120) : result.lighter(120);
0200             brighnessVal= brighness(result);
0201             ++i;
0202         }
0203     }
0204     result.setAlpha(color.alpha());
0205     return result;
0206 }
0207 
0208 StyleSheet* Theme::addStyleSheet(const QString& name)
0209 {
0210     auto styleSheet= Theme::styleSheet(name);
0211     if(nullptr == styleSheet)
0212     {
0213         styleSheet= new StyleSheet(this);
0214         m_styleSheets.insert({name, styleSheet});
0215     }
0216     return styleSheet;
0217 }
0218 
0219 QFont Theme::imFont() const
0220 {
0221     return m_imFont;
0222 }
0223 
0224 void Theme::setImFont(const QFont& newImFont)
0225 {
0226     if(m_imFont == newImFont)
0227         return;
0228     m_imFont= newImFont;
0229     emit imFontChanged();
0230 }
0231 
0232 QFont Theme::imLittleFont() const
0233 {
0234     QFont res= m_imFont;
0235     res.setPointSize(res.pointSize() * 0.8);
0236     return res;
0237 }
0238 
0239 QFont Theme::imBigFont() const
0240 {
0241     QFont res= m_imFont;
0242     res.setPointSize(res.pointSize() * 2);
0243     res.setBold(true);
0244     return res;
0245 }
0246 
0247 } // namespace customization