23#include <glibmm/regex.h>
24#include <glibmm/ustring.h>
25#include <gdkmm/display.h>
27#include <gtkmm/cssprovider.h>
28#include <gtkmm/csssection.h>
29#include <gtkmm/settings.h>
30#include <gtkmm/styleprovider.h>
31#include <gtkmm/window.h>
32#include <pangomm/font.h>
33#include <pangomm/fontdescription.h>
50# include <gtksourceview/gtksource.h>
56 : _fontsizeprovider{
Gtk::CssProvider::
create()}
67 const gchar *dir_entry;
68 GDir *dir = g_dir_open(path, 0,
nullptr);
71 while ((dir_entry = g_dir_read_name(dir))) {
72 gchar *filename = g_build_filename(path, dir_entry,
"gtk-4.0",
"gtk.css",
nullptr);
73 bool has_prefer_dark =
false;
75 Glib::ustring theme = dir_entry;
76 gchar *filenamedark = g_build_filename(path, dir_entry,
"gtk-4.0",
"gtk-dark.css",
nullptr);
77 if (g_file_test(filenamedark, G_FILE_TEST_IS_REGULAR))
78 has_prefer_dark =
true;
79 if (themes.find(theme) != themes.end() && !has_prefer_dark) {
82 if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
83 themes[theme] = has_prefer_dark;
95std::map<Glib::ustring, bool>
104 Glib::ustring theme =
"";
106 gchar **builtin_themes;
108 const gchar *
const *dirs;
111 builtin_themes = g_resources_enumerate_children(
"/org/gtk/libgtk/theme", G_RESOURCE_LOOKUP_FLAGS_NONE,
nullptr);
112 for (i = 0; builtin_themes[i] != NULL; i++) {
113 if (g_str_has_suffix(builtin_themes[i],
"/")) {
114 theme = builtin_themes[i];
115 theme.resize(theme.size() - 1);
116 Glib::ustring theme_path =
"/org/gtk/libgtk/theme";
117 theme_path +=
"/" + theme;
118 gchar **builtin_themes_files =
119 g_resources_enumerate_children(theme_path.c_str(), G_RESOURCE_LOOKUP_FLAGS_NONE,
nullptr);
120 bool has_prefer_dark =
false;
121 if (builtin_themes_files != NULL) {
122 for (j = 0; builtin_themes_files[j] != NULL; j++) {
123 Glib::ustring file = builtin_themes_files[j];
124 if (file ==
"gtk-dark.css") {
125 has_prefer_dark =
true;
129 g_strfreev(builtin_themes_files);
130 themes[theme] = has_prefer_dark;
134 g_strfreev(builtin_themes);
136 path = g_build_filename(g_get_user_data_dir(),
"themes",
nullptr);
140 path = g_build_filename(g_get_home_dir(),
".themes",
nullptr);
144 dirs = g_get_system_data_dirs();
145 for (i = 0; dirs[i]; i++) {
146 path = g_build_filename(dirs[i],
"themes",
nullptr);
156 Glib::ustring css_str;
158 Glib::ustring themeiconname = prefs->
getString(
"/theme/iconTheme", prefs->
getString(
"/theme/defaultIconTheme",
""));
159 guint32 colorsetbase = 0x2E3436ff;
161 guint32 colorsetsuccess = 0x4AD589ff;
162 guint32 colorsetwarning = 0xF57900ff;
163 guint32 colorseterror = 0xCC0000ff;
164 colorsetbase = prefs->
getUInt(
"/theme/" + themeiconname +
"/symbolicBaseColor", colorsetbase);
165 colorsetsuccess = prefs->
getUInt(
"/theme/" + themeiconname +
"/symbolicSuccessColor", colorsetsuccess);
166 colorsetwarning = prefs->
getUInt(
"/theme/" + themeiconname +
"/symbolicWarningColor", colorsetwarning);
167 colorseterror = prefs->
getUInt(
"/theme/" + themeiconname +
"/symbolicErrorColor", colorseterror);
168 colorsetbase_inverse = colorsetbase ^ 0xffffff00;
176 bool overridebasecolor = !prefs->
getBool(
"/theme/symbolicDefaultBaseColors",
true);
177 if (overridebasecolor) {
178 css_str +=
"#InkRuler:not(.shadow):not(.page):not(.selection),";
179 css_str +=
":not(.rawstyle) > image:not(.arrow),";
180 css_str +=
":not(.rawstyle) treeview.image";
181 css_str +=
"{color:";
185 css_str +=
".dark .forcebright :not(.rawstyle) > image,";
186 css_str +=
".dark .forcebright image:not(.rawstyle),";
187 css_str +=
".bright .forcedark :not(.rawstyle) > image,";
188 css_str +=
".bright .forcedark image:not(.rawstyle),";
189 css_str +=
".dark :not(.rawstyle) > image.forcebright,";
190 css_str +=
".dark image.forcebright:not(.rawstyle),";
191 css_str +=
".bright :not(.rawstyle) > image.forcedark,";
192 css_str +=
".bright image.forcedark:not(.rawstyle),";
193 css_str +=
".inverse :not(.rawstyle) > image,";
194 css_str +=
".inverse image:not(.rawstyle)";
195 css_str +=
"{color:";
196 if (overridebasecolor) {
200 css_str +=
"@theme_bg_color";
208 static std::regex re_no_affect(
"(inherit|unset|initial|none|url)");
209 static std::regex re_color(
"background-color( ){0,3}:(.*?);");
210 static std::regex re_image(
"background-image( ){0,3}:(.*?\\)) *?;");
211 std::string sub =
"";
213 std::regex_search(cssstring, m, re_no_affect);
215 if (cssstring.find(
"background-color") != std::string::npos) {
217 cssstring = std::regex_replace(cssstring, re_color, sub);
218 }
else if (cssstring.find(
"background-image") != std::string::npos) {
220 contrast = std::clamp((
int)((contrast) * 27), 0, 100);
223 contrast = std::clamp((
int)((contrast) * 90), 0 , 100);
226 cssstring = std::regex_replace(cssstring, re_image, sub);
238 g_warning(
"There is a warning parsing theme CSS:: %s", error.what());
244 NarrowSpinbuttonObserver(
const char* path, Glib::RefPtr<Gtk::CssProvider> provider):
247 void notify(Preferences::Entry
const& new_val)
override {
248 auto const display = Gdk::Display::get_default();
249 if (new_val.getBool()) {
250 Gtk::StyleProvider::add_provider_for_display(display, _provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
253 Gtk::StyleProvider::remove_provider_for_display(display, _provider);
257 Glib::RefPtr<Gtk::CssProvider> _provider;
268 auto const display = Gdk::Display::get_default();
270 gchar *gtkThemeName =
nullptr;
271 gchar *gtkIconThemeName =
nullptr;
272 Glib::ustring themeiconname;
273 gboolean gtkApplicationPreferDarkTheme;
274 GtkSettings *settings = gtk_settings_get_default();
275 if (settings && !only_providers) {
276 g_object_get(settings,
"gtk-icon-theme-name", >kIconThemeName,
nullptr);
277 g_object_get(settings,
"gtk-theme-name", >kThemeName,
nullptr);
278 g_object_get(settings,
"gtk-application-prefer-dark-theme", >kApplicationPreferDarkTheme,
nullptr);
279 prefs->
setBool(
"/theme/defaultPreferDarkTheme", gtkApplicationPreferDarkTheme);
280 prefs->
setString(
"/theme/defaultGtkTheme", Glib::ustring(gtkThemeName));
281 prefs->
setString(
"/theme/defaultIconTheme", Glib::ustring(gtkIconThemeName));
282 Glib::ustring gtkthemename = prefs->
getString(
"/theme/gtkTheme");
283 if (gtkthemename !=
"") {
284 g_object_set(settings,
"gtk-theme-name", gtkthemename.c_str(),
nullptr);
286 bool preferdarktheme = prefs->
getBool(
"/theme/preferDarkTheme",
false);
287 g_object_set(settings,
"gtk-application-prefer-dark-theme", preferdarktheme,
nullptr);
288 themeiconname = prefs->
getString(
"/theme/iconTheme");
289 if (themeiconname !=
"") {
290 g_object_set(settings,
"gtk-icon-theme-name", themeiconname.c_str(),
nullptr);
294 g_free(gtkThemeName);
295 g_free(gtkIconThemeName);
297 int themecontrast = prefs->
getInt(
"/theme/contrast", 10);
303 static std::string cssstringcached =
"";
305 if (themecontrast < 10) {
306 Glib::ustring css_contrast =
"";
307 double contrast = (10 - themecontrast) / 30.0;
308 double shade = 1 - contrast;
309 const gchar *variant =
nullptr;
310 if (prefs->
getBool(
"/theme/preferDarkTheme",
false)) {
313 bool dark = prefs->
getBool(
"/theme/darkTheme",
false);
316 shade = 1 + contrast;
318 Glib::ustring current_theme = prefs->
getString(
"/theme/gtkTheme", prefs->
getString(
"/theme/defaultGtkTheme",
""));
320 std::string cssstring =
"";
321 if (cached && !cssstringcached.empty()) {
322 cssstring = cssstringcached;
324 auto current_themeprovider = Gtk::CssProvider::create();
325 current_themeprovider->load_named(current_theme, variant);
326 cssstring = current_themeprovider->to_string();
329 std::string cssdefined =
"";
333 std::istringstream f(cssstring);
335 while (std::getline(f, line)) {
338 if (line.find(
";") != std::string::npos &&
339 line.find(
"background-image") == std::string::npos &&
340 line.find(
"background-color") == std::string::npos)
347 cssstringcached += line;
348 cssstringcached +=
"\n";
353 std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(
"[}{]", cssstringcached.c_str());
357 cssstringcached =
"";
358 for (
unsigned i = 0; i < tokens.size() - 1; i += 2) {
359 Glib::ustring selector = tokens[i];
360 Glib::ustring properties =
"";
361 if ((i + 1) < tokens.size()) {
362 properties = tokens[i + 1];
364 if (properties.find(
";") != Glib::ustring::npos) {
365 cssstringcached += selector;
366 cssstringcached +=
"{\n";
367 cssstringcached += properties;
368 cssstringcached +=
"}\n";
372 cssstring = cssdefined;
374 if (!cssstring.empty()) {
376 Gtk::StyleProvider::add_provider_for_display(display,
_contrastthemeprovider, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
379 cssstringcached =
"";
384 Glib::ustring style = get_filename(UIS,
"style.css");
385 if (!style.empty()) {
387 Gtk::StyleProvider::remove_provider_for_display(display,
_styleprovider);
394 }
catch (
const Gtk::CssParserError &ex) {
395 g_critical(
"CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(),
399 Gtk::StyleProvider::add_provider_for_display(display,
_styleprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
404 Glib::ustring style = get_filename(UIS,
"spinbutton.css");
405 if (!style.empty()) {
408 }
catch (
const Gtk::CssParserError &ex) {
409 g_critical(
"CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(), ex.what());
422 Glib::ustring gtkthemename = prefs->
getString(
"/theme/gtkTheme", prefs->
getString(
"/theme/defaultGtkTheme",
""));
423 gtkthemename +=
".css";
424 style = get_filename(UIS, gtkthemename.c_str(),
false,
true);
425 if (!style.empty()) {
427 Gtk::StyleProvider::remove_provider_for_display(display,
_themeprovider);
434 }
catch (
const Gtk::CssParserError &ex) {
435 g_critical(
"CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(),
438 Gtk::StyleProvider::add_provider_for_display(display,
_themeprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
444 Glib::ustring css_str =
"";
445 if (prefs->
getBool(
"/theme/symbolicIcons",
false)) {
450 }
catch (
const Gtk::CssParserError &ex) {
451 g_critical(
"CSSProviderError::load_from_data(): failed to load '%s'\n(%s)", css_str.c_str(), ex.what());
453 Gtk::StyleProvider::add_provider_for_display(display,
_colorizeprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
456 Glib::ustring macstyle = get_filename(UIS,
"mac.css");
457 if (!macstyle.empty()) {
466 }
catch (
const Gtk::CssParserError &ex) {
467 g_critical(
"CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", macstyle.c_str(), ex.what());
469 Gtk::StyleProvider::add_provider_for_display(display,
_macstyleprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
473 style = get_filename(UIS,
"user.css");
474 if (!style.empty()) {
476 Gtk::StyleProvider::remove_provider_for_display(display,
_userprovider);
483 }
catch (
const Gtk::CssParserError &ex) {
484 g_critical(
"CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(),
487 Gtk::StyleProvider::add_provider_for_display(display,
_userprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
500 if (!window)
return false;
503 Glib::ustring current_theme =
506 if (
auto const settings = Gtk::Settings::get_default()) {
507 settings->property_gtk_application_prefer_dark_theme() = prefs->
getBool(
"/theme/preferDarkTheme",
false);
510 auto dark = current_theme.find(
":dark") != std::string::npos;
514 dark = dark || (prefs->
getInt(
"/theme/contrast", 10) != 10 && prefs->
getBool(
"/theme/preferDarkTheme",
false));
515 if (dark)
return true;
529 auto dark = prefs->
getBool(
"/theme/darkTheme",
false);
530 std::vector<Gtk::Window *> winds;
532 winds.push_back(
dynamic_cast<Gtk::Window *
>(wnd));
534 if (
auto desktops = INKSCAPE.get_desktops()) {
535 for (
auto &
desktop : *desktops) {
536 if (
desktop == SP_ACTIVE_DESKTOP) {
543 for (
auto wnd : winds) {
544 if (
auto w = wnd->get_surface()) {
548 wnd->add_css_class(
"dark");
549 wnd->remove_css_class(
"bright");
551 wnd->add_css_class(
"bright");
552 wnd->remove_css_class(
"dark");
554 if (prefs->
getBool(
"/theme/symbolicIcons",
false)) {
555 wnd->add_css_class(
"symbolic");
556 wnd->remove_css_class(
"regular");
558 wnd->add_css_class(
"regular");
559 wnd->remove_css_class(
"symbolic");
561#if (defined (_WIN32) || defined (_WIN64))
567 if (!winds.empty()) {
572 if (
auto desktop = INKSCAPE.active_desktop()) {
583 std::vector<guint32> colors;
584 if (!window)
return colors;
586 auto const child = window->get_child();
587 if (!
child)
return colors;
589 Glib::ustring
name =
"highlight-color-";
591 for (
int i = 1; i <= 8; ++i) {
596 auto const css_class =
name + std::to_string(i);
597 child->add_css_class(css_class);
599 auto const rgba =
child->get_color();
602 child->remove_css_class(css_class);
609 if (factor < 0.1 || factor > 10) {
610 g_warning(
"Invalid font scaling factor %f in ThemeContext::adjust_global_font_scale", factor);
614 auto display = Gdk::Display::get_default();
619 os <<
"widget, menuitem, popover, box { font-size: " << factor <<
"rem; }\n";
621 os <<
".mono-font {";
623 os <<
"font-family: " << desc.get_family() <<
";";
624 switch (desc.get_style()) {
625 case Pango::Style::ITALIC:
626 os <<
"font-style: italic;";
628 case Pango::Style::OBLIQUE:
629 os <<
"font-style: oblique;";
632 os <<
"font-weight: " <<
static_cast<int>(desc.get_weight()) <<
";";
633 double size = desc.get_size();
634 os <<
"font-size: " << factor * (desc.get_size_is_absolute() ?
size :
size / Pango::SCALE) <<
"px;";
640 Gtk::StyleProvider::add_provider_for_display(display,
_fontsizeprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
645 auto manager = gtk_source_style_scheme_manager_get_default();
648 gtk_source_style_scheme_manager_prepend_search_path(manager, themes.c_str());
656 auto default_theme = prefs->getString(
"/theme/syntax-color-theme");
657 auto light =
"inkscape-light";
658 auto dark =
"inkscape-dark";
659 if (default_theme.empty() || default_theme == light || default_theme == dark) {
660 prefs->setString(
"/theme/syntax-color-theme", dark_theme ? dark : light);
673 return Pango::FontDescription(font);
static constexpr uint16_t get_luminance(uint32_t r, uint32_t g, uint32_t b)
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
std::streamsize precision() const
bool isValidBool() const
Check if the preference value can be interpreted as a Boolean.
Base class for preference observers.
Observer(Glib::ustring path)
Constructor.
Preference storage class.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
unsigned int getUInt(Glib::ustring const &pref_path, unsigned int def=0)
Retrieve an unsigned integer.
static Preferences * get()
Access the singleton Preferences object.
Entry const getEntry(Glib::ustring const &pref_path)
Retrieve a preference entry without specifying its type.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
void setDouble(Glib::ustring const &pref_path, double value)
Set a floating point value.
void addObserver(Observer &)
Register a preference observer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
static DialogManager & singleton()
std::vector< DialogWindow * > get_all_floating_dialog_windows()
static std::vector< guint32 > getHighlightColors(Gtk::Window *window)
Load the highlight colours from the current theme.
Glib::RefPtr< Gtk::CssProvider > _styleprovider
Glib::RefPtr< Gtk::CssProvider > _userprovider
Pango::FontDescription getMonospacedFont() const
User-selected monospaced font used by XML dialog and attribute editor.
Glib::RefPtr< Gtk::CssProvider > _themeprovider
void themechangecallback()
std::unique_ptr< Preferences::Observer > _spinbutton_observer
std::map< Glib::ustring, bool > gtkThemeList
Glib::RefPtr< Gtk::CssProvider > _colorizeprovider
Glib::ustring get_symbolic_colors()
bool isCurrentThemeDark(Gtk::Window *window)
Check if current applied theme is dark or not by looking at style context.
static void select_default_syntax_style(bool dark_theme)
void inkscape_fill_gtk(const gchar *path, gtkThemeList &themes)
Inkscape fill gtk, taken from glib/gtk code with our own checks.
std::map< Glib::ustring, bool > get_available_themes()
Get available themes based on locations of gtk directories.
void add_gtk_css(bool only_providers, bool cached=false)
Add our CSS style sheets.
Glib::RefPtr< Gtk::CssProvider > _macstyleprovider
void adjustGlobalFontScale(double factor)
Set application-wide font size adjustment by a factor, where 1 is 100% (no change)
Glib::RefPtr< Gtk::CssProvider > _spinbuttonprovider
Glib::RefPtr< Gtk::CssProvider > _fontsizeprovider
double getFontScale() const
Get current font scaling factor (50 - 150, percent of "normal" size)
static void initialize_source_syntax_styles()
void saveMonospacedFont(Pango::FontDescription desc)
static Glib::ustring get_monospaced_font_pref_path()
void saveFontScale(double scale)
Save font scaling factor in preferences.
static Glib::ustring get_font_scale_pref_path()
Glib::RefPtr< Gtk::CssProvider > _contrastthemeprovider
InkscapeWindow const * getInkscapeWindow() const
TODO: insert short description here.
Editable view implementation.
A window for floating docks.
Inkscape - An SVG editor.
std::string rgba_to_hex(uint32_t value, bool alpha)
Output the RGBA value as a #RRGGBB hex color, if alpha is true then the output will be #RRGGBBAA inst...
std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
static void show_parsing_error(const Glib::RefPtr< const Gtk::CssSection > §ion, const Glib::Error &error)
std::string sp_tweak_background_colors(std::string cssstring, double crossfade, double contrast, bool dark)
Glib::ustring format_classic(T const &... args)
Singleton class to access the preferences file in a convenient way.
Inkscape::IO::Resource - simple resource API.
void set_default_highlight_colors(std::vector< guint32 > colors)
Gtk <themes> helper code.
guint32 to_guint32(Gdk::RGBA const &rgba)
Gdk::RGBA get_color_with_class(Gtk::Widget &widget, Glib::ustring const &css_class)
void set_dark_titlebar(Glib::RefPtr< Gdk::Surface > const &surface, bool is_dark)