diff --git a/CMakeLists.txt b/CMakeLists.txt index 8267230..3764dcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(QT Core Gui Widgets Quick QuickControls2 DBus Xml Concurrent LinguistTools) +set(QT Core Gui Widgets Quick QuickControls2 X11Extras DBus Xml Concurrent LinguistTools) find_package(Qt5 REQUIRED ${QT}) find_package(FishUI REQUIRED) find_package(Freetype REQUIRED) @@ -39,6 +39,8 @@ set(SRCS src/password.cpp src/batteryhistorymodel.cpp src/cicontheme.cpp + src/fonts/kxftconfig.cpp + src/fonts/fonts.cpp ) set(RESOURCES @@ -58,6 +60,7 @@ target_link_libraries(${PROJECT_NAME} Qt5::QuickControls2 Qt5::DBus Qt5::Xml + Qt5::X11Extras Qt5::Concurrent FishUI diff --git a/src/application.cpp b/src/application.cpp index ef71bd4..1f58521 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -6,6 +6,7 @@ #include "settingsuiadaptor.h" #include "fontsmodel.h" +#include "fonts/fonts.h" #include "appearance.h" #include "battery.h" #include "batteryhistorymodel.h" @@ -60,6 +61,7 @@ Application::Application(int &argc, char **argv) qmlRegisterType(uri, 1, 0, "About"); qmlRegisterType(uri, 1, 0, "Background"); qmlRegisterType(uri, 1, 0, "Language"); + qmlRegisterType(uri, 1, 0, "Fonts"); qmlRegisterSingletonType(uri, 1, 0, "Password", passwordSingleton); qmlRegisterType(); diff --git a/src/fonts/fonts.cpp b/src/fonts/fonts.cpp new file mode 100644 index 0000000..2dad70f --- /dev/null +++ b/src/fonts/fonts.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fonts.h" + +Fonts::Fonts(QObject *parent) + : QObject(parent) + , m_antiAliasing(false) + , m_hintingModel(new QStandardItemModel(this)) +{ + KXftConfig xft; + const auto aaState = xft.getAntiAliasing(); + m_antiAliasing = (aaState != KXftConfig::AntiAliasing::Disabled); + + // Hinting options + for (KXftConfig::Hint::Style s : {KXftConfig::Hint::None, KXftConfig::Hint::Slight, KXftConfig::Hint::Medium, KXftConfig::Hint::Full}) { + auto item = new QStandardItem(KXftConfig::description(s)); + m_hintingModel->appendRow(item); + } + + // hinting + KXftConfig::Hint::Style hStyle = KXftConfig::Hint::NotSet; + xft.getHintStyle(hStyle); + // if it is not set, we set it to slight hinting + if (hStyle == KXftConfig::Hint::NotSet) { + hStyle = KXftConfig::Hint::Slight; + } + m_hinting = hStyle; +} + +bool Fonts::antiAliasing() const +{ + return m_antiAliasing; +} + +void Fonts::setAntiAliasing(bool antiAliasing) +{ + if (m_antiAliasing != antiAliasing) { + m_antiAliasing = antiAliasing; + save(); + } +} + +int Fonts::hintingCurrentIndex() const +{ + return hinting() - KXftConfig::Hint::None; +} + +void Fonts::setHintingCurrentIndex(int index) +{ + setHinting(static_cast(KXftConfig::Hint::None + index)); +} + +KXftConfig::Hint::Style Fonts::hinting() const +{ + return m_hinting; +} + +void Fonts::setHinting(KXftConfig::Hint::Style hinting) +{ + if (m_hinting != hinting) { + m_hinting = hinting; + save(); + emit hintingChanged(); + } +} + +QStandardItemModel *Fonts::hintingModel() +{ + return m_hintingModel; +} + +void Fonts::save() +{ + KXftConfig xft; + KXftConfig::AntiAliasing::State aaState = KXftConfig::AntiAliasing::NotSet; + if (xft.antiAliasingHasLocalConfig()) { + aaState = m_antiAliasing ? KXftConfig::AntiAliasing::Enabled : KXftConfig::AntiAliasing::Disabled; + } + xft.setAntiAliasing(aaState); + xft.setHintStyle(m_hinting); + xft.apply(); +} diff --git a/src/fonts/fonts.h b/src/fonts/fonts.h new file mode 100644 index 0000000..714db7a --- /dev/null +++ b/src/fonts/fonts.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FONTS_H +#define FONTS_H + +#include +#include +#include "kxftconfig.h" + +class Fonts : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool antiAliasing READ antiAliasing WRITE setAntiAliasing NOTIFY antiAliasingChanged) + Q_PROPERTY(QStandardItemModel * hintingModel READ hintingModel CONSTANT) + Q_PROPERTY(int hintingCurrentIndex READ hintingCurrentIndex WRITE setHintingCurrentIndex NOTIFY hintingCurrentIndexChanged) + +public: + explicit Fonts(QObject *parent = nullptr); + + bool antiAliasing() const; + void setAntiAliasing(bool antiAliasing); + + int hintingCurrentIndex() const; + void setHintingCurrentIndex(int index); + + KXftConfig::Hint::Style hinting() const; + void setHinting(KXftConfig::Hint::Style hinting); + + QStandardItemModel *hintingModel(); + + void save(); + +signals: + void antiAliasingChanged(); + void hintingChanged(); + void hintingCurrentIndexChanged(); + +private: + bool m_antiAliasing; + QStandardItemModel *m_hintingModel; + KXftConfig::Hint::Style m_hinting; +}; + +#endif diff --git a/src/fonts/kxftconfig.cpp b/src/fonts/kxftconfig.cpp new file mode 100644 index 0000000..debfcfd --- /dev/null +++ b/src/fonts/kxftconfig.cpp @@ -0,0 +1,893 @@ +/* + Copyright (c) 2002 Craig Drummond + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kxftconfig.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +static int point2Pixel(double point) +{ + return (int)(((point * QX11Info::appDpiY()) / 72.0) + 0.5); +} + +static int pixel2Point(double pixel) +{ + return (int)(((pixel * 72.0) / (double)QX11Info::appDpiY()) + 0.5); +} + +static bool equal(double d1, double d2) +{ + return (fabs(d1 - d2) < 0.0001); +} + +static QString dirSyntax(const QString &d) +{ + if (d.isNull()) { + return d; + } + + QString ds(d); + ds.replace(QLatin1String("//"), QLatin1String("/")); + if (!ds.endsWith(QLatin1Char('/'))) { + ds += QLatin1Char('/'); + } + + return ds; +} + +inline bool fExists(const QString &p) +{ + return QFileInfo(p).isFile(); +} + +inline bool dWritable(const QString &p) +{ + QFileInfo info(p); + return info.isDir() && info.isWritable(); +} + +static QString getDir(const QString &path) +{ + QString str(path); + + const int slashPos = str.lastIndexOf(QLatin1Char('/')); + if (slashPos != -1) { + str.truncate(slashPos + 1); + } + + return dirSyntax(str); +} + +static QDateTime getTimeStamp(const QString &item) +{ + return QFileInfo(item).lastModified(); +} + +static QString getEntry(QDomElement element, const char *type, unsigned int numAttributes, ...) +{ + if (numAttributes == uint(element.attributes().length())) { + va_list args; + unsigned int arg; + bool ok = true; + + va_start(args, numAttributes); + + for (arg = 0; arg < numAttributes && ok; ++arg) { + const char *attr = va_arg(args, const char *); + const char *val = va_arg(args, const char *); + + if (!attr || !val || val != element.attribute(attr)) { + ok = false; + } + } + + va_end(args); + + if (ok) { + QDomNode n = element.firstChild(); + + if (!n.isNull()) { + QDomElement e = n.toElement(); + + if (!e.isNull() && type == e.tagName()) { + return e.text(); + } + } + } + } + + return QString(); +} + +static KXftConfig::SubPixel::Type strToType(const char *str) +{ + if (0 == strcmp(str, "rgb")) { + return KXftConfig::SubPixel::Rgb; + } else if (0 == strcmp(str, "bgr")) { + return KXftConfig::SubPixel::Bgr; + } else if (0 == strcmp(str, "vrgb")) { + return KXftConfig::SubPixel::Vrgb; + } else if (0 == strcmp(str, "vbgr")) { + return KXftConfig::SubPixel::Vbgr; + } else if (0 == strcmp(str, "none")) { + return KXftConfig::SubPixel::None; + } else { + return KXftConfig::SubPixel::NotSet; + } +} + +static KXftConfig::Hint::Style strToStyle(const char *str) +{ + if (0 == strcmp(str, "hintslight")) { + return KXftConfig::Hint::Slight; + } else if (0 == strcmp(str, "hintmedium")) { + return KXftConfig::Hint::Medium; + } else if (0 == strcmp(str, "hintfull")) { + return KXftConfig::Hint::Full; + } else { + return KXftConfig::Hint::None; + } +} + +KXftConfig::KXftConfig() + : m_doc("fontconfig") + , m_file(getConfigFile()) +{ + qDebug() << "Using fontconfig file:" << m_file; + reset(); +} + +KXftConfig::~KXftConfig() +{ +} + +// +// Obtain location of config file to use. +QString KXftConfig::getConfigFile() +{ + FcStrList *list = FcConfigGetConfigFiles(FcConfigGetCurrent()); + QStringList localFiles; + FcChar8 *file; + QString home(dirSyntax(QDir::homePath())); + + m_globalFiles.clear(); + + while ((file = FcStrListNext(list))) { + QString f((const char *)file); + + if (fExists(f) && 0 == f.indexOf(home)) { + localFiles.append(f); + } else { + m_globalFiles.append(f); + } + } + FcStrListDone(list); + + // + // Go through list of localFiles, looking for the preferred one... + if (!localFiles.isEmpty()) { + for (const QString &file : qAsConst(localFiles)) { + if (file.endsWith(QLatin1String("/fonts.conf")) || file.endsWith(QLatin1String("/.fonts.conf"))) { + return file; + } + } + return localFiles.front(); // Just return the 1st one... + } else { // Hmmm... no known localFiles? + if (FcGetVersion() >= 21000) { + const QString targetPath(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String("fontconfig")); + QDir target(targetPath); + if (!target.exists()) { + target.mkpath(targetPath); + } + return targetPath + QLatin1String("/fonts.conf"); + } else { + return home + QLatin1String("/.fonts.conf"); + } + } +} + +bool KXftConfig::reset() +{ + m_madeChanges = false; + m_hint.reset(); + m_hinting.reset(); + m_excludeRange.reset(); + m_excludePixelRange.reset(); + m_subPixel.reset(); + m_antiAliasing.reset(); + m_antiAliasingHasLocalConfig = false; + m_subPixelHasLocalConfig = false; + m_hintHasLocalConfig = false; + + bool ok = false; + std::for_each(m_globalFiles.cbegin(), m_globalFiles.cend(), [this, &ok](const QString &file) { + ok |= parseConfigFile(file); + }); + + AntiAliasing globalAntialiasing; + globalAntialiasing.state = m_antiAliasing.state; + SubPixel globalSubPixel; + globalSubPixel.type = m_subPixel.type; + Hint globalHint; + globalHint.style = m_hint.style; + Exclude globalExcludeRange; + globalExcludeRange.from = m_excludeRange.from; + globalExcludeRange.to = m_excludePixelRange.to; + Exclude globalExcludePixelRange; + globalExcludePixelRange.from = m_excludePixelRange.from; + globalExcludePixelRange.to = m_excludePixelRange.to; + Hinting globalHinting; + globalHinting.set = m_hinting.set; + + m_antiAliasing.reset(); + m_subPixel.reset(); + m_hint.reset(); + m_hinting.reset(); + m_excludeRange.reset(); + m_excludePixelRange.reset(); + + ok |= parseConfigFile(m_file); + + if (m_antiAliasing.node.isNull()) { + m_antiAliasing = globalAntialiasing; + } else { + m_antiAliasingHasLocalConfig = true; + } + + if (m_subPixel.node.isNull()) { + m_subPixel = globalSubPixel; + } else { + m_subPixelHasLocalConfig = true; + } + + if (m_hint.node.isNull()) { + m_hint = globalHint; + } else { + m_hintHasLocalConfig = true; + } + + if (m_hinting.node.isNull()) { + m_hinting = globalHinting; + } + if (m_excludeRange.node.isNull()) { + m_excludeRange = globalExcludeRange; + } + if (m_excludePixelRange.node.isNull()) { + m_excludePixelRange = globalExcludePixelRange; + } + + return ok; +} + +bool KXftConfig::apply() +{ + bool ok = true; + + if (m_madeChanges) { + // + // Check if file has been written since we last read it. If it has, then re-read and add any + // of our changes... + if (fExists(m_file) && getTimeStamp(m_file) != m_time) { + KXftConfig newConfig; + + newConfig.setExcludeRange(m_excludeRange.from, m_excludeRange.to); + newConfig.setSubPixelType(m_subPixel.type); + newConfig.setHintStyle(m_hint.style); + newConfig.setAntiAliasing(m_antiAliasing.state); + + ok = newConfig.changed() ? newConfig.apply() : true; + if (ok) { + reset(); + } else { + m_time = getTimeStamp(m_file); + } + } else { + // Ensure these are always equal... + m_excludePixelRange.from = (int)point2Pixel(m_excludeRange.from); + m_excludePixelRange.to = (int)point2Pixel(m_excludeRange.to); + + FcAtomic *atomic = FcAtomicCreate((const unsigned char *)(QFile::encodeName(m_file).data())); + + ok = false; + if (atomic) { + if (FcAtomicLock(atomic)) { + FILE *f = fopen((char *)FcAtomicNewFile(atomic), "w"); + + if (f) { + applySubPixelType(); + applyHintStyle(); + applyAntiAliasing(); + applyExcludeRange(false); + applyExcludeRange(true); + + // + // Check document syntax... + static const char qtXmlHeader[] = ""; + static const char xmlHeader[] = ""; + static const char qtDocTypeLine[] = ""; + static const char docTypeLine[] = + ""; + + QString str(m_doc.toString()); + int idx; + + if (0 != str.indexOf("= 0 || to >= 0) && foundFalse) { + m_excludeRange.from = from < to ? from : to; + m_excludeRange.to = from < to ? to : from; + m_excludeRange.node = n; + } else if ((pixelFrom >= 0 || pixelTo >= 0) && foundFalse) { + m_excludePixelRange.from = pixelFrom < pixelTo ? pixelFrom : pixelTo; + m_excludePixelRange.to = pixelFrom < pixelTo ? pixelTo : pixelFrom; + m_excludePixelRange.node = n; + } + } + break; + default: + break; + } + } + } + n = n.nextSibling(); + } +} + +void KXftConfig::applySubPixelType() +{ + if (SubPixel::NotSet == m_subPixel.type) { + if (!m_subPixel.node.isNull()) { + m_doc.documentElement().removeChild(m_subPixel.node); + m_subPixel.node.clear(); + } + } else { + QDomElement matchNode = m_doc.createElement("match"); + QDomElement typeNode = m_doc.createElement("const"); + QDomElement editNode = m_doc.createElement("edit"); + QDomText typeText = m_doc.createTextNode(toStr(m_subPixel.type)); + + matchNode.setAttribute("target", "font"); + editNode.setAttribute("mode", "assign"); + editNode.setAttribute("name", "rgba"); + editNode.appendChild(typeNode); + typeNode.appendChild(typeText); + matchNode.appendChild(editNode); + if (m_subPixel.node.isNull()) { + m_doc.documentElement().appendChild(matchNode); + } else { + m_doc.documentElement().replaceChild(matchNode, m_subPixel.node); + } + m_subPixel.node = matchNode; + } +} + +void KXftConfig::applyHintStyle() +{ + applyHinting(); + + if (Hint::NotSet == m_hint.style) { + if (!m_hint.node.isNull()) { + m_doc.documentElement().removeChild(m_hint.node); + m_hint.node.clear(); + } + if (!m_hinting.node.isNull()) { + m_doc.documentElement().removeChild(m_hinting.node); + m_hinting.node.clear(); + } + } else { + QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("const"), editNode = m_doc.createElement("edit"); + QDomText typeText = m_doc.createTextNode(toStr(m_hint.style)); + + matchNode.setAttribute("target", "font"); + editNode.setAttribute("mode", "assign"); + editNode.setAttribute("name", "hintstyle"); + editNode.appendChild(typeNode); + typeNode.appendChild(typeText); + matchNode.appendChild(editNode); + if (m_hint.node.isNull()) { + m_doc.documentElement().appendChild(matchNode); + } else { + m_doc.documentElement().replaceChild(matchNode, m_hint.node); + } + m_hint.node = matchNode; + } +} + +void KXftConfig::applyHinting() +{ + QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("bool"), editNode = m_doc.createElement("edit"); + QDomText typeText = m_doc.createTextNode(m_hinting.set ? "true" : "false"); + + matchNode.setAttribute("target", "font"); + editNode.setAttribute("mode", "assign"); + editNode.setAttribute("name", "hinting"); + editNode.appendChild(typeNode); + typeNode.appendChild(typeText); + matchNode.appendChild(editNode); + if (m_hinting.node.isNull()) { + m_doc.documentElement().appendChild(matchNode); + } else { + m_doc.documentElement().replaceChild(matchNode, m_hinting.node); + } + m_hinting.node = matchNode; +} + +void KXftConfig::applyExcludeRange(bool pixel) +{ + Exclude &range = pixel ? m_excludePixelRange : m_excludeRange; + + if (equal(range.from, 0) && equal(range.to, 0)) { + if (!range.node.isNull()) { + m_doc.documentElement().removeChild(range.node); + range.node.clear(); + } + } else { + QString fromString, toString; + + fromString.setNum(range.from); + toString.setNum(range.to); + + QDomElement matchNode = m_doc.createElement("match"), fromTestNode = m_doc.createElement("test"), fromNode = m_doc.createElement("double"), + toTestNode = m_doc.createElement("test"), toNode = m_doc.createElement("double"), editNode = m_doc.createElement("edit"), + boolNode = m_doc.createElement("bool"); + QDomText fromText = m_doc.createTextNode(fromString), toText = m_doc.createTextNode(toString), boolText = m_doc.createTextNode("false"); + + matchNode.setAttribute("target", "font"); // CPD: Is target "font" or "pattern" ???? + fromTestNode.setAttribute("qual", "any"); + fromTestNode.setAttribute("name", pixel ? "pixelsize" : "size"); + fromTestNode.setAttribute("compare", "more_eq"); + fromTestNode.appendChild(fromNode); + fromNode.appendChild(fromText); + toTestNode.setAttribute("qual", "any"); + toTestNode.setAttribute("name", pixel ? "pixelsize" : "size"); + toTestNode.setAttribute("compare", "less_eq"); + toTestNode.appendChild(toNode); + toNode.appendChild(toText); + editNode.setAttribute("mode", "assign"); + editNode.setAttribute("name", "antialias"); + editNode.appendChild(boolNode); + boolNode.appendChild(boolText); + matchNode.appendChild(fromTestNode); + matchNode.appendChild(toTestNode); + matchNode.appendChild(editNode); + + if (!m_antiAliasing.node.isNull()) { + m_doc.documentElement().removeChild(range.node); + } + if (range.node.isNull()) { + m_doc.documentElement().appendChild(matchNode); + } else { + m_doc.documentElement().replaceChild(matchNode, range.node); + } + range.node = matchNode; + } +} + +bool KXftConfig::antiAliasingHasLocalConfig() const +{ + return m_antiAliasingHasLocalConfig; +} + +KXftConfig::AntiAliasing::State KXftConfig::getAntiAliasing() const +{ + return m_antiAliasing.state; +} + +void KXftConfig::setAntiAliasing(AntiAliasing::State state) +{ + if (state != m_antiAliasing.state) { + m_antiAliasing.state = state; + m_madeChanges = true; + } +} + +void KXftConfig::applyAntiAliasing() +{ + if (AntiAliasing::NotSet == m_antiAliasing.state) { + if (!m_antiAliasing.node.isNull()) { + m_doc.documentElement().removeChild(m_antiAliasing.node); + m_antiAliasing.node.clear(); + } + } else { + QDomElement matchNode = m_doc.createElement("match"); + QDomElement typeNode = m_doc.createElement("bool"); + QDomElement editNode = m_doc.createElement("edit"); + QDomText typeText = m_doc.createTextNode(m_antiAliasing.state == AntiAliasing::Enabled ? "true" : "false"); + + matchNode.setAttribute("target", "font"); + editNode.setAttribute("mode", "assign"); + editNode.setAttribute("name", "antialias"); + editNode.appendChild(typeNode); + typeNode.appendChild(typeText); + matchNode.appendChild(editNode); + if (!m_antiAliasing.node.isNull()) { + m_doc.documentElement().removeChild(m_antiAliasing.node); + } + m_doc.documentElement().appendChild(matchNode); + m_antiAliasing.node = matchNode; + } +} + +// KXftConfig only parses one config file, user's .fonts.conf usually. +// If that one doesn't exist, then KXftConfig doesn't know if antialiasing +// is enabled or not. So try to find out the default value from the default font. +// Maybe there's a better way *shrug*. +bool KXftConfig::aliasingEnabled() +{ + FcPattern *pattern = FcPatternCreate(); + FcConfigSubstitute(nullptr, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult result; + FcPattern *f = FcFontMatch(nullptr, pattern, &result); + FcBool antialiased = FcTrue; + FcPatternGetBool(f, FC_ANTIALIAS, 0, &antialiased); + FcPatternDestroy(f); + FcPatternDestroy(pattern); + return antialiased == FcTrue; +} diff --git a/src/fonts/kxftconfig.h b/src/fonts/kxftconfig.h new file mode 100644 index 0000000..e0a045d --- /dev/null +++ b/src/fonts/kxftconfig.h @@ -0,0 +1,242 @@ +#ifndef __KXFTCONFIG_H__ +#define __KXFTCONFIG_H__ + +/* + Copyright (c) 2002 Craig Drummond + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include + +class KXftConfig +{ +public: + struct Item { + Item(QDomNode &n) + : node(n) + , toBeRemoved(false) + { + } + Item() + : toBeRemoved(false) + { + } + virtual void reset() + { + node.clear(); + toBeRemoved = false; + } + bool added() + { + return node.isNull(); + } + + QDomNode node; + + virtual ~Item() + { + } + bool toBeRemoved; + }; + + struct SubPixel : public Item { + enum Type { + NotSet, + None, + Rgb, + Bgr, + Vrgb, + Vbgr, + }; + + SubPixel(Type t, QDomNode &n) + : Item(n) + , type(t) + { + } + SubPixel(Type t = NotSet) + : type(t) + { + } + + void reset() override + { + Item::reset(); + type = NotSet; + } + + Type type; + }; + + struct Exclude : public Item { + Exclude(double f, double t, QDomNode &n) + : Item(n) + , from(f) + , to(t) + { + } + Exclude(double f = 0, double t = 0) + : from(f) + , to(t) + { + } + + void reset() override + { + Item::reset(); + from = to = 0; + } + + double from, to; + }; + + struct Hint : public Item { + enum Style { + NotSet, + None, + Slight, + Medium, + Full, + }; + + Hint(Style s, QDomNode &n) + : Item(n) + , style(s) + { + } + Hint(Style s = NotSet) + : style(s) + { + } + + void reset() override + { + Item::reset(); + style = NotSet; + } + + Style style; + }; + + struct Hinting : public Item { + Hinting(bool s, QDomNode &n) + : Item(n) + , set(s) + { + } + Hinting(bool s = true) + : set(s) + { + } + + void reset() override + { + Item::reset(); + set = true; + } + + bool set; + }; + + struct AntiAliasing : public Item { + enum State { + NotSet, + Enabled, + Disabled, + }; + + AntiAliasing(State s, QDomNode &n) + : Item(n) + , state(s) + { + } + AntiAliasing(State s = NotSet) + : state(s) + { + } + + void reset() override + { + Item::reset(); + state = NotSet; + } + + enum State state; + }; + +public: + explicit KXftConfig(); + + virtual ~KXftConfig(); + + bool reset(); + bool apply(); + bool getSubPixelType(SubPixel::Type &type); + void setSubPixelType(SubPixel::Type type); // SubPixel::None => turn off sub-pixel rendering + bool getExcludeRange(double &from, double &to); + void setExcludeRange(double from, double to); // from:0, to:0 => turn off exclude range + bool getHintStyle(Hint::Style &style); + void setHintStyle(Hint::Style style); + void setAntiAliasing(AntiAliasing::State state); + AntiAliasing::State getAntiAliasing() const; + bool antiAliasingHasLocalConfig() const; + bool subPixelTypeHasLocalConfig() const; + bool hintStyleHasLocalConfig() const; + bool changed() + { + return m_madeChanges; + } + static QString description(SubPixel::Type t); + static const char *toStr(SubPixel::Type t); + static QString description(Hint::Style s); + static const char *toStr(Hint::Style s); + bool aliasingEnabled(); + +private: + bool parseConfigFile(const QString &filename); + void readContents(); + void applySubPixelType(); + void applyHintStyle(); + void applyAntiAliasing(); + void setHinting(bool set); + void applyHinting(); + void applyExcludeRange(bool pixel); + QString getConfigFile(); + +private: + QStringList m_globalFiles; + + SubPixel m_subPixel; + Exclude m_excludeRange, m_excludePixelRange; + Hint m_hint; + Hinting m_hinting; + AntiAliasing m_antiAliasing; + bool m_antiAliasingHasLocalConfig; + bool m_subPixelHasLocalConfig; + bool m_hintHasLocalConfig; + QDomDocument m_doc; + QString m_file; + bool m_madeChanges; + QDateTime m_time; +}; + +Q_DECLARE_METATYPE(KXftConfig::Hint::Style) +Q_DECLARE_METATYPE(KXftConfig::SubPixel::Type) +#endif diff --git a/src/images/sidebar/dark/fonts.svg b/src/images/sidebar/dark/fonts.svg new file mode 100644 index 0000000..291c162 --- /dev/null +++ b/src/images/sidebar/dark/fonts.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/qml/Appearance/Main.qml b/src/qml/Appearance/Main.qml index a65c62c..7e0bb11 100644 --- a/src/qml/Appearance/Main.qml +++ b/src/qml/Appearance/Main.qml @@ -29,36 +29,10 @@ import "../" ItemPage { headerTitle: qsTr("Appearance") - FontsModel { - id: fontsModel - } - Appearance { id: appearance } - Connections { - target: fontsModel - - function onLoadFinished() { - for (var i in fontsModel.generalFonts) { - if (fontsModel.systemGeneralFont === fontsModel.generalFonts[i]) { - generalFontComboBox.currentIndex = i - break; - } - } - - for (i in fontsModel.fixedFonts) { - if (fontsModel.systemFixedFont === fontsModel.fixedFonts[i]) { - fixedFontComboBox.currentIndex = i - break; - } - } - - console.log("fonts load finished") - } - } - Scrollable { anchors.fill: parent contentHeight: layout.implicitHeight @@ -198,122 +172,6 @@ ItemPage { } } } - - RoundedItem { - // Font - Label { - text: qsTr("Font") - color: FishUI.Theme.disabledTextColor - bottomPadding: FishUI.Units.smallSpacing - } - - GridLayout { - rows: 3 - columns: 2 - - columnSpacing: FishUI.Units.largeSpacing * 1.5 - rowSpacing: FishUI.Units.largeSpacing - - Label { - text: qsTr("General Font") - bottomPadding: FishUI.Units.smallSpacing - } - - ComboBox { - id: generalFontComboBox - model: fontsModel.generalFonts - enabled: true - Layout.fillWidth: true - topInset: 0 - bottomInset: 0 - leftPadding: FishUI.Units.largeSpacing - rightPadding: FishUI.Units.largeSpacing - onActivated: appearance.setGenericFontFamily(currentText) - } - - Label { - text: qsTr("Fixed Font") - bottomPadding: FishUI.Units.smallSpacing - } - - ComboBox { - id: fixedFontComboBox - model: fontsModel.fixedFonts - enabled: true - Layout.fillWidth: true - topInset: 0 - bottomInset: 0 - leftPadding: FishUI.Units.largeSpacing - rightPadding: FishUI.Units.largeSpacing - onActivated: appearance.setFixedFontFamily(currentText) - } - - Label { - text: qsTr("Font Size") - bottomPadding: FishUI.Units.smallSpacing - } - - TabBar { - Layout.fillWidth: true - - TabButton { - text: qsTr("Small") - } - - TabButton { - text: qsTr("Medium") - } - - TabButton { - text: qsTr("Large") - } - - TabButton { - text: qsTr("Huge") - } - - currentIndex: { - var index = 0 - - if (appearance.fontPointSize <= 9) - index = 0 - else if (appearance.fontPointSize <= 10) - index = 1 - else if (appearance.fontPointSize <= 12) - index = 2 - else if (appearance.fontPointSize <= 15) - index = 3 - - return index - } - - onCurrentIndexChanged: { - var fontSize = 0 - - switch (currentIndex) { - case 0: - fontSize = 9 - break; - case 1: - fontSize = 10 - break; - case 2: - fontSize = 12 - break; - case 3: - fontSize = 15 - break; - } - - appearance.setFontPointSize(fontSize) - } - } - } - } - - Item { - Layout.fillHeight: true - } } } } diff --git a/src/qml/Fonts/Main.qml b/src/qml/Fonts/Main.qml new file mode 100644 index 0000000..81a6f97 --- /dev/null +++ b/src/qml/Fonts/Main.qml @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.4 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +import Cutefish.Settings 1.0 +import FishUI 1.0 as FishUI +import "../" + +ItemPage { + id: control + headerTitle: qsTr("Fonts") + + Appearance { + id: appearance + } + + FontsModel { + id: fontsModel + } + + Fonts { + id: fonts + } + + Connections { + target: fontsModel + + function onLoadFinished() { + for (var i in fontsModel.generalFonts) { + if (fontsModel.systemGeneralFont === fontsModel.generalFonts[i]) { + generalFontComboBox.currentIndex = i + break; + } + } + + for (i in fontsModel.fixedFonts) { + if (fontsModel.systemFixedFont === fontsModel.fixedFonts[i]) { + fixedFontComboBox.currentIndex = i + break; + } + } + + console.log("fonts load finished") + } + } + + Scrollable { + anchors.fill: parent + contentHeight: layout.implicitHeight + + ColumnLayout { + id: layout + anchors.fill: parent + spacing: FishUI.Units.largeSpacing * 2 + + // Font + RoundedItem { +// Label { +// text: qsTr("Font") +// color: FishUI.Theme.disabledTextColor +// bottomPadding: FishUI.Units.smallSpacing +// } + + GridLayout { + rows: 3 + columns: 2 + + columnSpacing: FishUI.Units.largeSpacing * 1.5 + rowSpacing: FishUI.Units.largeSpacing + + Label { + text: qsTr("General Font") + bottomPadding: FishUI.Units.smallSpacing + } + + ComboBox { + id: generalFontComboBox + model: fontsModel.generalFonts + enabled: true + Layout.fillWidth: true + topInset: 0 + bottomInset: 0 + leftPadding: FishUI.Units.largeSpacing + rightPadding: FishUI.Units.largeSpacing + onActivated: appearance.setGenericFontFamily(currentText) + } + + Label { + text: qsTr("Fixed Font") + bottomPadding: FishUI.Units.smallSpacing + } + + ComboBox { + id: fixedFontComboBox + model: fontsModel.fixedFonts + enabled: true + Layout.fillWidth: true + topInset: 0 + bottomInset: 0 + leftPadding: FishUI.Units.largeSpacing + rightPadding: FishUI.Units.largeSpacing + onActivated: appearance.setFixedFontFamily(currentText) + } + + Label { + text: qsTr("Font Size") + bottomPadding: FishUI.Units.smallSpacing + } + + TabBar { + Layout.fillWidth: true + + TabButton { + text: qsTr("Small") + } + + TabButton { + text: qsTr("Medium") + } + + TabButton { + text: qsTr("Large") + } + + TabButton { + text: qsTr("Huge") + } + + currentIndex: { + var index = 0 + + if (appearance.fontPointSize <= 9) + index = 0 + else if (appearance.fontPointSize <= 10) + index = 1 + else if (appearance.fontPointSize <= 12) + index = 2 + else if (appearance.fontPointSize <= 15) + index = 3 + + return index + } + + onCurrentIndexChanged: { + var fontSize = 0 + + switch (currentIndex) { + case 0: + fontSize = 9 + break; + case 1: + fontSize = 10 + break; + case 2: + fontSize = 12 + break; + case 3: + fontSize = 15 + break; + } + + appearance.setFontPointSize(fontSize) + } + } + + Label { + text: qsTr("Hinting") + bottomPadding: FishUI.Units.smallSpacing + } + + ComboBox { + model: fonts.hintingModel + textRole: "display" + Layout.fillWidth: true + currentIndex: fonts.hintingCurrentIndex + onCurrentIndexChanged: fonts.hintingCurrentIndex = currentIndex + } + + Label { + text: qsTr("Anti-Aliasing") + bottomPadding: FishUI.Units.smallSpacing + } + + Switch { + Layout.fillHeight: true + Layout.alignment: Qt.AlignRight + checked: fonts.antiAliasing + onCheckedChanged: fonts.antiAliasing = checked + } + + } + } + } + } +} diff --git a/src/qml/SideBar.qml b/src/qml/SideBar.qml index 33a74e3..39a6a3d 100644 --- a/src/qml/SideBar.qml +++ b/src/qml/SideBar.qml @@ -91,6 +91,15 @@ Item { category: qsTr("Display and appearance") } + ListElement { + title: qsTr("Fonts") + name: "fonts" + page: "qrc:/qml/Fonts/Main.qml" + iconSource: "fonts.svg" + iconColor: "#FFBF36" + category: qsTr("Display and appearance") + } + ListElement { title: qsTr("Background") name: "background" @@ -160,6 +169,7 @@ Item { Label { text: rootWindow.title + Layout.preferredHeight: rootWindow.header.height leftPadding: FishUI.Units.largeSpacing + FishUI.Units.smallSpacing topPadding: FishUI.Units.largeSpacing bottomPadding: 0 diff --git a/src/qml/WLAN/WifiItem.qml b/src/qml/WLAN/WifiItem.qml index 13ee690..5e6d81e 100644 --- a/src/qml/WLAN/WifiItem.qml +++ b/src/qml/WLAN/WifiItem.qml @@ -37,17 +37,8 @@ Item { Rectangle { anchors.fill: parent radius: FishUI.Theme.smallRadius - color: mouseArea.containsMouse ? Qt.rgba(FishUI.Theme.textColor.r, - FishUI.Theme.textColor.g, - FishUI.Theme.textColor.b, - 0.1) : "transparent" - - Behavior on color { - ColorAnimation { - duration: 125 - easing.type: Easing.InOutCubic - } - } + color: FishUI.Theme.textColor + opacity: mouseArea.pressed ? 0.15 : mouseArea.containsMouse ? 0.1 : 0.0 } MouseArea { diff --git a/src/qml/WLAN/WifiView.qml b/src/qml/WLAN/WifiView.qml index eabd6f2..3c5b4be 100644 --- a/src/qml/WLAN/WifiView.qml +++ b/src/qml/WLAN/WifiView.qml @@ -81,11 +81,11 @@ ColumnLayout { sourceModel: connectionModel } - spacing: FishUI.Units.smallSpacing * 1.5 + spacing: FishUI.Units.largeSpacing interactive: false visible: count > 0 - property var itemHeight: 45 + property var itemHeight: 38 delegate: WifiItem { width: ListView.view.width diff --git a/src/resources.qrc b/src/resources.qrc index 4a9159a..3cb3688 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -103,5 +103,7 @@ images/sidebar/dark/wlan.svg qml/Bluetooth/Main.qml qml/StandardButton.qml + qml/Fonts/Main.qml + images/sidebar/dark/fonts.svg