1 /*
2 * SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 */
7 #include "fcitxtheme.h"
8 #include "font.h"
9 #include <QDebug>
10 #include <QMargins>
11 #include <QPixmap>
12 #include <QSettings>
13 #include <QStandardPaths>
14
15 namespace fcitx {
16
readBool(const QSettings & settings,const QString & name,bool defaultValue)17 bool readBool(const QSettings &settings, const QString &name,
18 bool defaultValue) {
19 return settings.value(name, defaultValue ? "True" : "False").toString() ==
20 "True";
21 }
22
readMargin(const QSettings & settings)23 QMargins readMargin(const QSettings &settings) {
24 settings.allKeys();
25 return QMargins(settings.value("Left", 0).toInt(),
26 settings.value("Top", 0).toInt(),
27 settings.value("Right", 0).toInt(),
28 settings.value("Bottom", 0).toInt());
29 }
30
readColor(const QSettings & settings,const QString & name,const QString & defaultValue)31 QColor readColor(const QSettings &settings, const QString &name,
32 const QString &defaultValue) {
33 QString colorString = settings.value(name, defaultValue).toString();
34 QColor color;
35 color.setNamedColor(defaultValue);
36 if (colorString.startsWith("#")) {
37 if (colorString.size() == 7) {
38 // Parse #RRGGBB
39 color.setNamedColor(colorString.toUpper());
40 } else if (colorString.size() == 9) {
41 // Qt accept "#AARRGGBB"
42 auto newColorString =
43 QString("#%1%2")
44 .arg(colorString.mid(7, 2), colorString.mid(1, 6))
45 .toUpper();
46 color.setNamedColor(newColorString);
47 }
48 }
49 return color;
50 }
51
load(const QString & name,QSettings & settings)52 void BackgroundImage::load(const QString &name, QSettings &settings) {
53 settings.allKeys();
54 image_ = QPixmap();
55 overlay_ = QPixmap();
56 if (auto image = settings.value("Image").toString(); !image.isEmpty()) {
57 auto file = QStandardPaths::locate(
58 QStandardPaths::GenericDataLocation,
59 QString("fcitx5/themes/%1/%2").arg(name, image));
60 image_.load(file);
61 }
62 if (auto image = settings.value("Overlay").toString(); !image.isEmpty()) {
63 auto file = QStandardPaths::locate(
64 QStandardPaths::GenericDataLocation,
65 QString("fcitx5/themes/%1/%2").arg(name, image));
66 overlay_.load(file);
67 }
68
69 settings.beginGroup("Margin");
70 margin_ = readMargin(settings);
71 settings.endGroup();
72
73 if (image_.isNull()) {
74 QColor color = readColor(settings, "Color", "#ffffff");
75 QColor borderColor = readColor(settings, "BorderColor", "#00ffffff");
76 int borderWidth = settings.value("BorderWidth", 0).toInt();
77 fillBackground(borderColor, color, borderWidth);
78 }
79
80 settings.beginGroup("OverlayClipMargin");
81 overlayClipMargin_ = readMargin(settings);
82 settings.endGroup();
83
84 hideOverlayIfOversize_ =
85 settings.value("HideOverlayIfOversize", "False").toString() == "True";
86 overlayOffsetX_ = settings.value("OverlayOffsetX", 0).toInt();
87 overlayOffsetY_ = settings.value("OverlayOffsetY", 0).toInt();
88 gravity_ = settings.value("Gravity", "TopLeft").toString();
89 }
90
loadFromValue(const QColor & border,const QColor & background,QMargins margin,int borderWidth)91 void BackgroundImage::loadFromValue(const QColor &border,
92 const QColor &background, QMargins margin,
93 int borderWidth) {
94 image_ = QPixmap();
95 overlay_ = QPixmap();
96 margin_ = margin;
97 fillBackground(border, background, borderWidth);
98 overlayClipMargin_ = QMargins();
99 hideOverlayIfOversize_ = false;
100 overlayOffsetX_ = 0;
101 overlayOffsetY_ = 0;
102 gravity_ = QString();
103 }
104
fillBackground(const QColor & border,const QColor & background,int borderWidth)105 void BackgroundImage::fillBackground(const QColor &border,
106 const QColor &background,
107 int borderWidth) {
108 image_ = QPixmap(margin_.left() + margin_.right() + 1,
109 margin_.top() + margin_.bottom() + 1);
110 borderWidth = std::min({borderWidth, margin_.left(), margin_.right(),
111 margin_.top(), margin_.bottom()});
112 borderWidth = std::max(0, borderWidth);
113
114 QPainter painter;
115 painter.begin(&image_);
116 painter.setCompositionMode(QPainter::CompositionMode_Source);
117 if (borderWidth) {
118 painter.fillRect(image_.rect(), border);
119 }
120 painter.fillRect(QRect(borderWidth, borderWidth,
121 image_.width() - borderWidth * 2,
122 image_.height() - borderWidth * 2),
123 background);
124 painter.end();
125 }
126
load(const QString & name,QSettings & settings)127 void ActionImage::load(const QString &name, QSettings &settings) {
128 settings.allKeys();
129 image_ = QPixmap();
130 valid_ = false;
131 if (auto image = settings.value("Image").toString(); !image.isEmpty()) {
132 auto file = QStandardPaths::locate(
133 QStandardPaths::GenericDataLocation,
134 QString("fcitx5/themes/%1/%2").arg(name, image));
135 image_.load(file);
136 valid_ = !image_.isNull();
137 }
138
139 settings.beginGroup("ClickMargin");
140 margin_ = readMargin(settings);
141 settings.endGroup();
142 }
143
reset()144 void ActionImage::reset() {
145 image_ = QPixmap();
146 valid_ = false;
147 margin_ = QMargins(0, 0, 0, 0);
148 }
149
FcitxTheme(QObject * parent)150 FcitxTheme::FcitxTheme(QObject *parent)
151 : QObject(parent), configPath_(QStandardPaths::writableLocation(
152 QStandardPaths::GenericConfigLocation)
153 .append("/fcitx5/conf/classicui.conf")),
154 watcher_(new QFileSystemWatcher) {
155 connect(watcher_, &QFileSystemWatcher::fileChanged, this,
156 &FcitxTheme::configChanged);
157 watcher_->addPath(configPath_);
158
159 configChanged();
160 }
161
~FcitxTheme()162 FcitxTheme::~FcitxTheme() {}
163
configChanged()164 void FcitxTheme::configChanged() {
165 // Since fcitx is doing things like delete and move, we need to re-add the
166 // path.
167 watcher_->removePath(configPath_);
168 watcher_->addPath(configPath_);
169 QSettings settings(configPath_, QSettings::IniFormat);
170 settings.childGroups();
171 font_ = parseFont(settings.value("Font", "Sans Serif 9").toString());
172 fontMetrics_ = QFontMetrics(font_);
173 vertical_ =
174 settings.value("Vertical Candidate List", "False").toString() == "True";
175 wheelForPaging_ =
176 settings.value("WheelForPaging", "True").toString() == "True";
177 theme_ = settings.value("Theme", "default").toString();
178
179 themeChanged();
180 }
181
themeChanged()182 void FcitxTheme::themeChanged() {
183 if (!themeConfigPath_.isEmpty()) {
184 watcher_->removePath(themeConfigPath_);
185 }
186 auto themeConfig = QString("/fcitx5/themes/%1/theme.conf").arg(theme_);
187 themeConfigPath_ =
188 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
189 .append(themeConfig);
190 auto file = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
191 themeConfig);
192 if (file.isEmpty()) {
193 file = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
194 "fcitx5/themes/default/theme.conf");
195 themeConfigPath_ = QStandardPaths::writableLocation(
196 QStandardPaths::GenericDataLocation)
197 .append("fcitx5/themes/default/theme.conf");
198 theme_ = "default";
199 }
200
201 watcher_->addPath(themeConfigPath_);
202
203 // We can not locate default theme.
204 if (file.isEmpty()) {
205 normalColor_.setNamedColor("#000000");
206 highlightCandidateColor_.setNamedColor("#ffffff");
207 fullWidthHighlight_ = true;
208 highlightColor_.setNamedColor("#ffffff");
209 highlightBackgroundColor_.setNamedColor("#a5a5a5");
210 contentMargin_ = QMargins{2, 2, 2, 2};
211 textMargin_ = QMargins{5, 5, 5, 5};
212 highlightClickMargin_ = QMargins{0, 0, 0, 0};
213 background_.loadFromValue(highlightBackgroundColor_, highlightColor_,
214 contentMargin_, 2);
215 highlight_.loadFromValue(highlightBackgroundColor_,
216 highlightBackgroundColor_, textMargin_, 0);
217 prev_.reset();
218 next_.reset();
219 return;
220 }
221
222 QSettings settings(file, QSettings::IniFormat);
223 settings.childGroups();
224 settings.beginGroup("InputPanel");
225 normalColor_ = readColor(settings, "NormalColor", "#000000");
226 highlightCandidateColor_ =
227 readColor(settings, "HighlightCandidateColor", "#ffffff");
228 fullWidthHighlight_ = readBool(settings, "FullWidthHighlight", true);
229 highlightColor_ = readColor(settings, "HighlightColor", "#ffffff");
230 highlightBackgroundColor_ =
231 readColor(settings, "HighlightColor", "#a5a5a5");
232
233 settings.beginGroup("ContentMargin");
234 contentMargin_ = readMargin(settings);
235 settings.endGroup();
236 settings.beginGroup("TextMargin");
237 textMargin_ = readMargin(settings);
238 settings.endGroup();
239
240 settings.beginGroup("Background");
241 background_.load(theme_, settings);
242 settings.endGroup();
243
244 settings.beginGroup("Highlight");
245 highlight_.load(theme_, settings);
246 settings.beginGroup("HighlightClickMargin");
247 highlightClickMargin_ = readMargin(settings);
248 settings.endGroup();
249 settings.endGroup();
250
251 settings.beginGroup("PrevPage");
252 prev_.load(theme_, settings);
253 settings.endGroup();
254
255 settings.beginGroup("NextPage");
256 next_.load(theme_, settings);
257 settings.endGroup();
258 }
259
260 } // namespace fcitx
261
paint(QPainter * painter,const fcitx::BackgroundImage & image,QRect region)262 void fcitx::FcitxTheme::paint(QPainter *painter,
263 const fcitx::BackgroundImage &image,
264 QRect region) {
265 auto marginTop = image.margin_.top();
266 auto marginBottom = image.margin_.bottom();
267 auto marginLeft = image.margin_.left();
268 auto marginRight = image.margin_.right();
269 int resizeHeight = image.image_.height() - marginTop - marginBottom;
270 int resizeWidth = image.image_.width() - marginLeft - marginRight;
271
272 if (resizeHeight <= 0) {
273 resizeHeight = 1;
274 }
275
276 if (resizeWidth <= 0) {
277 resizeWidth = 1;
278 }
279
280 if (region.height() < 0) {
281 region.setHeight(resizeHeight);
282 }
283
284 if (region.width() < 0) {
285 region.setWidth(resizeWidth);
286 }
287
288 /*
289 * 7 8 9
290 * 4 5 6
291 * 1 2 3
292 */
293
294 if (marginLeft && marginBottom) {
295 /* part 1 */
296 painter->drawPixmap(
297 QRect(0, region.height() - marginBottom, marginLeft, marginBottom)
298 .translated(region.topLeft()),
299 image.image_,
300 QRect(0, marginTop + resizeHeight, marginLeft, marginBottom));
301 }
302
303 if (marginRight && marginBottom) {
304 /* part 3 */
305 painter->drawPixmap(
306 QRect(region.width() - marginRight, region.height() - marginBottom,
307 marginRight, marginBottom)
308 .translated(region.topLeft()),
309 image.image_,
310 QRect(marginLeft + resizeWidth, marginTop + resizeHeight,
311 marginRight, marginBottom));
312 }
313
314 if (marginLeft && marginTop) {
315 /* part 7 */
316 painter->drawPixmap(
317 QRect(0, 0, marginLeft, marginTop).translated(region.topLeft()),
318 image.image_, QRect(0, 0, marginLeft, marginTop));
319 }
320
321 if (marginRight && marginTop) {
322 /* part 9 */
323 painter->drawPixmap(
324 QRect(region.width() - marginRight, 0, marginRight, marginTop)
325 .translated(region.topLeft()),
326 image.image_,
327 QRect(marginLeft + resizeWidth, 0, marginRight, marginTop));
328 }
329
330 /* part 2 & 8 */
331 if (marginTop) {
332 painter->drawPixmap(
333 QRect(marginLeft, 0, region.width() - marginLeft - marginRight,
334 marginTop)
335 .translated(region.topLeft()),
336 image.image_, QRect(marginLeft, 0, resizeWidth, marginTop));
337 }
338
339 if (marginBottom) {
340 painter->drawPixmap(QRect(marginLeft, region.height() - marginBottom,
341 region.width() - marginLeft - marginRight,
342 marginBottom)
343 .translated(region.topLeft()),
344 image.image_,
345 QRect(marginLeft, marginTop + resizeHeight,
346 resizeWidth, marginBottom));
347 }
348
349 /* part 4 & 6 */
350 if (marginLeft) {
351 painter->drawPixmap(QRect(0, marginTop, marginLeft,
352 region.height() - marginTop - marginBottom)
353 .translated(region.topLeft()),
354 image.image_,
355 QRect(0, marginTop, marginLeft, resizeHeight));
356 }
357
358 if (marginRight) {
359 painter->drawPixmap(QRect(region.width() - marginRight, marginTop,
360 marginRight,
361 region.height() - marginTop - marginBottom)
362 .translated(region.topLeft()),
363 image.image_,
364 QRect(marginLeft + resizeWidth, marginTop,
365 marginRight, resizeHeight));
366 }
367
368 /* part 5 */
369 {
370 painter->drawPixmap(
371 QRect(marginLeft, marginTop,
372 region.width() - marginLeft - marginRight,
373 region.height() - marginTop - marginBottom)
374 .translated(region.topLeft()),
375 image.image_,
376 QRect(marginLeft, marginTop, resizeWidth, resizeHeight));
377 }
378
379 if (image.overlay_.isNull()) {
380 return;
381 }
382
383 auto clipWidth = region.width() - image.overlayClipMargin_.left() -
384 image.overlayClipMargin_.right();
385 auto clipHeight = region.height() - image.overlayClipMargin_.top() -
386 image.overlayClipMargin_.bottom();
387 if (clipWidth <= 0 || clipHeight <= 0) {
388 return;
389 }
390 QRect clipRect(region.topLeft() + QPoint(image.overlayClipMargin_.left(),
391 image.overlayClipMargin_.top()),
392 QSize(clipWidth, clipHeight));
393
394 int x = 0, y = 0;
395 if (image.gravity_ == "Top Left" || image.gravity_ == "Center Left" ||
396 image.gravity_ == "Bottom Left") {
397 x = image.overlayOffsetX_;
398 } else if (image.gravity_ == "Top Center" || image.gravity_ == "Center" ||
399 image.gravity_ == "Bottom Center") {
400 x = (region.width() - image.overlay_.width()) / 2 +
401 image.overlayOffsetX_;
402 } else {
403 x = region.width() - image.overlay_.width() - image.overlayOffsetX_;
404 }
405
406 if (image.gravity_ == "Top Left" || image.gravity_ == "Top Center" ||
407 image.gravity_ == "Top Right") {
408 y = image.overlayOffsetY_;
409 } else if (image.gravity_ == "Center Left" || image.gravity_ == "Center" ||
410 image.gravity_ == "Center Right") {
411 y = (region.height() - image.overlay_.height()) / 2 +
412 image.overlayOffsetY_;
413 } else {
414 y = region.height() - image.overlay_.height() - image.overlayOffsetY_;
415 }
416 QRect rect(QPoint(x, y) + region.topLeft(), image.overlay_.size());
417 QRect finalRect = rect.intersected(clipRect);
418 if (finalRect.isEmpty()) {
419 return;
420 }
421
422 if (image.hideOverlayIfOversize_ && !clipRect.contains(rect)) {
423 return;
424 }
425
426 painter->save();
427 painter->setClipRect(clipRect);
428 painter->drawPixmap(rect, image.overlay_);
429 painter->restore();
430 }
431
paint(QPainter * painter,const fcitx::ActionImage & image,QPoint position,float alpha)432 void fcitx::FcitxTheme::paint(QPainter *painter,
433 const fcitx::ActionImage &image, QPoint position,
434 float alpha) {
435 painter->save();
436 painter->setOpacity(alpha);
437 painter->drawPixmap(position, image.image_);
438 painter->restore();
439 }
440
highlightMargin() const441 QMargins fcitx::FcitxTheme::highlightMargin() const {
442 return highlight_.margin_;
443 }
444