fcitx5-qt/qt5/platforminputcontext/fcitxtheme.cpp

453 lines
16 KiB
C++

/*
* SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "fcitxtheme.h"
#include "font.h"
#include <QDebug>
#include <QMargins>
#include <QPixmap>
#include <QSettings>
#include <QStandardPaths>
namespace fcitx {
bool readBool(const QSettings &settings, const QString &name,
bool defaultValue) {
return settings.value(name, defaultValue ? "True" : "False").toString() ==
"True";
}
QMargins readMargin(const QSettings &settings) {
settings.allKeys();
return QMargins(settings.value("Left", 0).toInt(),
settings.value("Top", 0).toInt(),
settings.value("Right", 0).toInt(),
settings.value("Bottom", 0).toInt());
}
QColor readColor(const QSettings &settings, const QString &name,
const QString &defaultValue) {
QString colorString = settings.value(name, defaultValue).toString();
QColor color(defaultValue);
if (colorString.startsWith("#")) {
if (colorString.size() == 7) {
// Parse #RRGGBB
color = QColor(colorString.toUpper());
} else if (colorString.size() == 9) {
// Qt accept "#AARRGGBB"
auto newColorString =
QStringLiteral("#%1%2")
.arg(colorString.mid(7, 2), colorString.mid(1, 6))
.toUpper();
color = QColor(newColorString);
}
}
return color;
}
void BackgroundImage::load(const QString &name, QSettings &settings) {
settings.allKeys();
image_ = QPixmap();
overlay_ = QPixmap();
if (auto image = settings.value("Image").toString(); !image.isEmpty()) {
auto file = QStandardPaths::locate(
QStandardPaths::GenericDataLocation,
QStringLiteral("fcitx5/themes/%1/%2").arg(name, image));
image_.load(file);
}
if (auto image = settings.value("Overlay").toString(); !image.isEmpty()) {
auto file = QStandardPaths::locate(
QStandardPaths::GenericDataLocation,
QStringLiteral("fcitx5/themes/%1/%2").arg(name, image));
overlay_.load(file);
}
settings.beginGroup("Margin");
margin_ = readMargin(settings);
settings.endGroup();
if (image_.isNull()) {
QColor color = readColor(settings, "Color", "#ffffff");
QColor borderColor = readColor(settings, "BorderColor", "#00ffffff");
int borderWidth = settings.value("BorderWidth", 0).toInt();
fillBackground(borderColor, color, borderWidth);
}
settings.beginGroup("OverlayClipMargin");
overlayClipMargin_ = readMargin(settings);
settings.endGroup();
hideOverlayIfOversize_ =
settings.value("HideOverlayIfOversize", "False").toString() == "True";
overlayOffsetX_ = settings.value("OverlayOffsetX", 0).toInt();
overlayOffsetY_ = settings.value("OverlayOffsetY", 0).toInt();
gravity_ = settings.value("Gravity", "TopLeft").toString();
}
void BackgroundImage::loadFromValue(const QColor &border,
const QColor &background, QMargins margin,
int borderWidth) {
image_ = QPixmap();
overlay_ = QPixmap();
margin_ = margin;
fillBackground(border, background, borderWidth);
overlayClipMargin_ = QMargins();
hideOverlayIfOversize_ = false;
overlayOffsetX_ = 0;
overlayOffsetY_ = 0;
gravity_ = QString();
}
void BackgroundImage::fillBackground(const QColor &border,
const QColor &background,
int borderWidth) {
image_ = QPixmap(margin_.left() + margin_.right() + 1,
margin_.top() + margin_.bottom() + 1);
borderWidth = std::min({borderWidth, margin_.left(), margin_.right(),
margin_.top(), margin_.bottom()});
borderWidth = std::max(0, borderWidth);
QPainter painter;
painter.begin(&image_);
painter.setCompositionMode(QPainter::CompositionMode_Source);
if (borderWidth) {
painter.fillRect(image_.rect(), border);
}
painter.fillRect(QRect(borderWidth, borderWidth,
image_.width() - borderWidth * 2,
image_.height() - borderWidth * 2),
background);
painter.end();
}
void ActionImage::load(const QString &name, QSettings &settings) {
settings.allKeys();
image_ = QPixmap();
valid_ = false;
if (auto image = settings.value("Image").toString(); !image.isEmpty()) {
auto file = QStandardPaths::locate(
QStandardPaths::GenericDataLocation,
QStringLiteral("fcitx5/themes/%1/%2").arg(name, image));
image_.load(file);
valid_ = !image_.isNull();
}
settings.beginGroup("ClickMargin");
margin_ = readMargin(settings);
settings.endGroup();
}
void ActionImage::reset() {
image_ = QPixmap();
valid_ = false;
margin_ = QMargins(0, 0, 0, 0);
}
FcitxTheme::FcitxTheme(QObject *parent)
: QObject(parent), configPath_(QStandardPaths::writableLocation(
QStandardPaths::GenericConfigLocation)
.append("/fcitx5/conf/classicui.conf")),
watcher_(new QFileSystemWatcher) {
connect(watcher_, &QFileSystemWatcher::fileChanged, this,
&FcitxTheme::configChanged);
watcher_->addPath(configPath_);
configChanged();
}
FcitxTheme::~FcitxTheme() {}
void FcitxTheme::configChanged() {
// Since fcitx is doing things like delete and move, we need to re-add the
// path.
watcher_->removePath(configPath_);
watcher_->addPath(configPath_);
QSettings settings(configPath_, QSettings::IniFormat);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
settings.setIniCodec("UTF-8");
#endif
settings.childGroups();
font_ = parseFont(settings.value("Font", "Sans Serif 9").toString());
fontMetrics_ = QFontMetrics(font_);
vertical_ =
settings.value("Vertical Candidate List", "False").toString() == "True";
wheelForPaging_ =
settings.value("WheelForPaging", "True").toString() == "True";
theme_ = settings.value("Theme", "default").toString();
themeChanged();
}
void FcitxTheme::themeChanged() {
if (!themeConfigPath_.isEmpty()) {
watcher_->removePath(themeConfigPath_);
}
auto themeConfig =
QStringLiteral("/fcitx5/themes/%1/theme.conf").arg(theme_);
themeConfigPath_ =
QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
.append(themeConfig);
auto file = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
themeConfig);
if (file.isEmpty()) {
file = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
"fcitx5/themes/default/theme.conf");
themeConfigPath_ = QStandardPaths::writableLocation(
QStandardPaths::GenericDataLocation)
.append("fcitx5/themes/default/theme.conf");
theme_ = "default";
}
watcher_->addPath(themeConfigPath_);
// We can not locate default theme.
if (file.isEmpty()) {
normalColor_ = QColor("#000000");
highlightCandidateColor_ = QColor("#ffffff");
fullWidthHighlight_ = true;
highlightColor_ = QColor("#ffffff");
highlightBackgroundColor_ = QColor("#a5a5a5");
contentMargin_ = QMargins{2, 2, 2, 2};
textMargin_ = QMargins{5, 5, 5, 5};
highlightClickMargin_ = QMargins{0, 0, 0, 0};
shadowMargin_ = QMargins{0, 0, 0, 0};
background_.loadFromValue(highlightBackgroundColor_, highlightColor_,
contentMargin_, 2);
highlight_.loadFromValue(highlightBackgroundColor_,
highlightBackgroundColor_, textMargin_, 0);
prev_.reset();
next_.reset();
return;
}
QSettings settings(file, QSettings::IniFormat);
settings.childGroups();
settings.beginGroup("InputPanel");
normalColor_ = readColor(settings, "NormalColor", "#000000");
highlightCandidateColor_ =
readColor(settings, "HighlightCandidateColor", "#ffffff");
fullWidthHighlight_ = readBool(settings, "FullWidthHighlight", true);
highlightColor_ = readColor(settings, "HighlightColor", "#ffffff");
highlightBackgroundColor_ =
readColor(settings, "HighlightBackgroundColor", "#a5a5a5");
buttonAlignment_ =
settings.value("PageButtonAlignment", "Bottom").toString();
settings.beginGroup("ContentMargin");
contentMargin_ = readMargin(settings);
settings.endGroup();
settings.beginGroup("TextMargin");
textMargin_ = readMargin(settings);
settings.endGroup();
settings.beginGroup("ShadowMargin");
shadowMargin_ = readMargin(settings);
settings.endGroup();
settings.beginGroup("Background");
background_.load(theme_, settings);
settings.endGroup();
settings.beginGroup("Highlight");
highlight_.load(theme_, settings);
settings.beginGroup("HighlightClickMargin");
highlightClickMargin_ = readMargin(settings);
settings.endGroup();
settings.endGroup();
settings.beginGroup("PrevPage");
prev_.load(theme_, settings);
settings.endGroup();
settings.beginGroup("NextPage");
next_.load(theme_, settings);
settings.endGroup();
}
} // namespace fcitx
void fcitx::FcitxTheme::paint(QPainter *painter,
const fcitx::BackgroundImage &image,
QRect region) {
auto marginTop = image.margin_.top();
auto marginBottom = image.margin_.bottom();
auto marginLeft = image.margin_.left();
auto marginRight = image.margin_.right();
int resizeHeight = image.image_.height() - marginTop - marginBottom;
int resizeWidth = image.image_.width() - marginLeft - marginRight;
if (resizeHeight <= 0) {
resizeHeight = 1;
}
if (resizeWidth <= 0) {
resizeWidth = 1;
}
if (region.height() < 0) {
region.setHeight(resizeHeight);
}
if (region.width() < 0) {
region.setWidth(resizeWidth);
}
/*
* 7 8 9
* 4 5 6
* 1 2 3
*/
if (marginLeft && marginBottom) {
/* part 1 */
painter->drawPixmap(
QRect(0, region.height() - marginBottom, marginLeft, marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(0, marginTop + resizeHeight, marginLeft, marginBottom));
}
if (marginRight && marginBottom) {
/* part 3 */
painter->drawPixmap(
QRect(region.width() - marginRight, region.height() - marginBottom,
marginRight, marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(marginLeft + resizeWidth, marginTop + resizeHeight,
marginRight, marginBottom));
}
if (marginLeft && marginTop) {
/* part 7 */
painter->drawPixmap(
QRect(0, 0, marginLeft, marginTop).translated(region.topLeft()),
image.image_, QRect(0, 0, marginLeft, marginTop));
}
if (marginRight && marginTop) {
/* part 9 */
painter->drawPixmap(
QRect(region.width() - marginRight, 0, marginRight, marginTop)
.translated(region.topLeft()),
image.image_,
QRect(marginLeft + resizeWidth, 0, marginRight, marginTop));
}
/* part 2 & 8 */
if (marginTop) {
painter->drawPixmap(
QRect(marginLeft, 0, region.width() - marginLeft - marginRight,
marginTop)
.translated(region.topLeft()),
image.image_, QRect(marginLeft, 0, resizeWidth, marginTop));
}
if (marginBottom) {
painter->drawPixmap(QRect(marginLeft, region.height() - marginBottom,
region.width() - marginLeft - marginRight,
marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(marginLeft, marginTop + resizeHeight,
resizeWidth, marginBottom));
}
/* part 4 & 6 */
if (marginLeft) {
painter->drawPixmap(QRect(0, marginTop, marginLeft,
region.height() - marginTop - marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(0, marginTop, marginLeft, resizeHeight));
}
if (marginRight) {
painter->drawPixmap(QRect(region.width() - marginRight, marginTop,
marginRight,
region.height() - marginTop - marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(marginLeft + resizeWidth, marginTop,
marginRight, resizeHeight));
}
/* part 5 */
{
painter->drawPixmap(
QRect(marginLeft, marginTop,
region.width() - marginLeft - marginRight,
region.height() - marginTop - marginBottom)
.translated(region.topLeft()),
image.image_,
QRect(marginLeft, marginTop, resizeWidth, resizeHeight));
}
if (image.overlay_.isNull()) {
return;
}
auto clipWidth = region.width() - image.overlayClipMargin_.left() -
image.overlayClipMargin_.right();
auto clipHeight = region.height() - image.overlayClipMargin_.top() -
image.overlayClipMargin_.bottom();
if (clipWidth <= 0 || clipHeight <= 0) {
return;
}
QRect clipRect(region.topLeft() + QPoint(image.overlayClipMargin_.left(),
image.overlayClipMargin_.top()),
QSize(clipWidth, clipHeight));
int x = 0, y = 0;
if (image.gravity_ == "Top Left" || image.gravity_ == "Center Left" ||
image.gravity_ == "Bottom Left") {
x = image.overlayOffsetX_;
} else if (image.gravity_ == "Top Center" || image.gravity_ == "Center" ||
image.gravity_ == "Bottom Center") {
x = (region.width() - image.overlay_.width()) / 2 +
image.overlayOffsetX_;
} else {
x = region.width() - image.overlay_.width() - image.overlayOffsetX_;
}
if (image.gravity_ == "Top Left" || image.gravity_ == "Top Center" ||
image.gravity_ == "Top Right") {
y = image.overlayOffsetY_;
} else if (image.gravity_ == "Center Left" || image.gravity_ == "Center" ||
image.gravity_ == "Center Right") {
y = (region.height() - image.overlay_.height()) / 2 +
image.overlayOffsetY_;
} else {
y = region.height() - image.overlay_.height() - image.overlayOffsetY_;
}
QRect rect(QPoint(x, y) + region.topLeft(), image.overlay_.size());
QRect finalRect = rect.intersected(clipRect);
if (finalRect.isEmpty()) {
return;
}
if (image.hideOverlayIfOversize_ && !clipRect.contains(rect)) {
return;
}
painter->save();
painter->setClipRect(clipRect);
painter->drawPixmap(rect, image.overlay_);
painter->restore();
}
void fcitx::FcitxTheme::paint(QPainter *painter,
const fcitx::ActionImage &image, QPoint position,
float alpha) {
painter->save();
painter->setOpacity(alpha);
painter->drawPixmap(position, image.image_);
painter->restore();
}
QMargins fcitx::FcitxTheme::highlightMargin() const {
return highlight_.margin_;
}