diff options
Diffstat (limited to '')
32 files changed, 5158 insertions, 0 deletions
diff --git a/panels/background/background-selected-symbolic.svg b/panels/background/background-selected-symbolic.svg new file mode 100644 index 0000000..9e820d1 --- /dev/null +++ b/panels/background/background-selected-symbolic.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 4.233 4.233" xmlns="http://www.w3.org/2000/svg"><path d="M3.843.627a.397.397 0 0 0-.56.034L1.45 2.73l-.775-.763a.397.397 0 0 0-.56.004.397.397 0 0 0 .003.562L1.191 3.59a.397.397 0 0 0 .576-.02l2.11-2.382a.397.397 0 0 0-.034-.56Z" style="fill:#3d3846"/></svg>
\ No newline at end of file diff --git a/panels/background/background.gresource.xml b/panels/background/background.gresource.xml new file mode 100644 index 0000000..1699244 --- /dev/null +++ b/panels/background/background.gresource.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/background"> + <file preprocess="xml-stripblanks">cc-background-chooser.ui</file> + <file preprocess="xml-stripblanks">cc-background-panel.ui</file> + <file preprocess="xml-stripblanks">cc-background-preview.ui</file> + <file>preview.css</file> + </gresource> + + <gresource prefix="/org/gnome/Settings/icons/scalable/actions"> + <file preprocess="xml-stripblanks">background-selected-symbolic.svg</file> + <file preprocess="xml-stripblanks">slideshow-symbolic.svg</file> + </gresource> +</gresources> diff --git a/panels/background/bg-colors-source.c b/panels/background/bg-colors-source.c new file mode 100644 index 0000000..fbf116a --- /dev/null +++ b/panels/background/bg-colors-source.c @@ -0,0 +1,219 @@ +/* bg-colors-source.c */ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#include <config.h> +#include "bg-colors-source.h" + +#include "cc-background-item.h" + +#include <cairo-gobject.h> +#include <glib/gi18n-lib.h> +#include <gdesktop-enums.h> + +struct _BgColorsSource +{ + BgSource parent_instance; +}; + +G_DEFINE_TYPE (BgColorsSource, bg_colors_source, BG_TYPE_SOURCE) + +struct { + GDesktopBackgroundShading type; + int orientation; + const char *pcolor; +} items[] = { + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#000000" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#db5d33" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#008094" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#5d479d" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ab2876" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#fad166" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#437740" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#d272c4" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ed9116" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ff89a9" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#7a8aa2" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#888888" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#475b52" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#425265" }, + { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#7a634b" }, +}; + +static gchar * +get_colors_path (void) +{ + return g_build_filename (g_get_user_config_dir (), "gnome-control-center", "backgrounds", "colors.ini", NULL); +} + +static char * +get_colors_dir (void) +{ + return g_build_filename (g_get_user_config_dir (), "gnome-control-center", "backgrounds", NULL); +} + +static void +bg_colors_source_add_color (BgColorsSource *self, + GListStore *store, + const char *color) +{ + CcBackgroundItemFlags flags; + g_autoptr(CcBackgroundItem) item = NULL; + + item = cc_background_item_new (NULL); + flags = CC_BACKGROUND_ITEM_HAS_PCOLOR | + CC_BACKGROUND_ITEM_HAS_SCOLOR | + CC_BACKGROUND_ITEM_HAS_SHADING | + CC_BACKGROUND_ITEM_HAS_PLACEMENT | + CC_BACKGROUND_ITEM_HAS_URI; + /* It does have a URI, it's "none" */ + + g_object_set (G_OBJECT (item), + "uri", "file:///" DATADIR "/gnome-control-center/pixmaps/noise-texture-light.png", + "primary-color", color, + "secondary-color", color, + "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID, + "placement", G_DESKTOP_BACKGROUND_STYLE_WALLPAPER, + "flags", flags, + NULL); + cc_background_item_load (item, NULL); + + /* insert the item into the liststore */ + g_list_store_append (store, item); +} + +static void +bg_colors_source_constructed (GObject *object) +{ + BgColorsSource *self = BG_COLORS_SOURCE (object); + guint i; + GListStore *store; + g_autoptr(GKeyFile) keyfile = NULL; + g_autofree gchar *path = NULL; + + G_OBJECT_CLASS (bg_colors_source_parent_class)->constructed (object); + + store = bg_source_get_liststore (BG_SOURCE (self)); + + for (i = 0; i < G_N_ELEMENTS (items); i++) + bg_colors_source_add_color (self, store, items[i].pcolor); + + keyfile = g_key_file_new (); + path = get_colors_path (); + if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) + { + g_auto(GStrv) colors = NULL; + + colors = g_key_file_get_string_list (keyfile, "Colors", "custom-colors", NULL, NULL); + for (i = 0; colors != NULL && colors[i] != NULL; i++) + bg_colors_source_add_color (self, store, colors[i]); + } +} + +gboolean +bg_colors_source_add (BgColorsSource *self, + GdkRGBA *rgba, + GtkTreeRowReference **ret_row_ref) +{ + GListStore *store; + g_autofree gchar *c = NULL; + g_auto(GStrv) colors = NULL; + gsize len; + g_autoptr(GKeyFile) keyfile = NULL; + g_autoptr(GError) error = NULL; + g_autofree gchar *dir = NULL; + g_autofree gchar *path = NULL; + + c = g_strdup_printf ("#%02x%02x%02x", + (int)(255*rgba->red), + (int)(255*rgba->green), + (int)(255*rgba->blue)); + + store = bg_source_get_liststore (BG_SOURCE (self)); + + bg_colors_source_add_color (self, store, c); + + /* Save to the keyfile */ + dir = get_colors_dir (); + g_mkdir_with_parents (dir, 0700); + + path = get_colors_path (); + colors = NULL; + len = 0; + + keyfile = g_key_file_new (); + if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) + colors = g_key_file_get_string_list (keyfile, "Colors", "custom-colors", &len, NULL); + + if (len == 0 && colors != NULL) + g_clear_pointer (&colors, g_strfreev); + + if (colors == NULL) + { + colors = g_new0 (char *, 2); + colors[0] = g_steal_pointer (&c); + len = 1; + } + else + { + char **new_colors; + guint i; + + new_colors = g_new0 (char *, len + 2); + for (i = 0; colors[i] != NULL; i++) + { + new_colors[i] = colors[i]; + colors[i] = NULL; + } + + new_colors[len] = g_steal_pointer (&c); + len++; + + g_strfreev (colors); + colors = new_colors; + } + + g_key_file_set_string_list (keyfile, "Colors", "custom-colors", (const gchar * const*) colors, len); + + if (!g_key_file_save_to_file (keyfile, path, &error)) + g_warning ("Could not save custom color: %s", error->message); + + return TRUE; +} + +static void +bg_colors_source_init (BgColorsSource *self) +{ +} + +static void +bg_colors_source_class_init (BgColorsSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = bg_colors_source_constructed; +} + +BgColorsSource * +bg_colors_source_new (GtkWidget *widget) +{ + return g_object_new (BG_TYPE_COLORS_SOURCE, "widget", widget, NULL); +} + diff --git a/panels/background/bg-colors-source.h b/panels/background/bg-colors-source.h new file mode 100644 index 0000000..8e2575d --- /dev/null +++ b/panels/background/bg-colors-source.h @@ -0,0 +1,38 @@ +/* bg-colors-source.h */ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include "bg-source.h" + +G_BEGIN_DECLS + +#define BG_TYPE_COLORS_SOURCE (bg_colors_source_get_type ()) +G_DECLARE_FINAL_TYPE (BgColorsSource, bg_colors_source, BG, COLORS_SOURCE, BgSource) + +BgColorsSource *bg_colors_source_new (GtkWidget *widget); + +gboolean bg_colors_source_add (BgColorsSource *self, + GdkRGBA *rgba, + GtkTreeRowReference **ret_row_ref); + +G_END_DECLS diff --git a/panels/background/bg-recent-source.c b/panels/background/bg-recent-source.c new file mode 100644 index 0000000..32d1854 --- /dev/null +++ b/panels/background/bg-recent-source.c @@ -0,0 +1,459 @@ +/* bg-recent-source.c + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "bg-recent-source" + +#include "bg-recent-source.h" +#include "cc-background-item.h" + +#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ + G_FILE_ATTRIBUTE_TIME_MODIFIED + +struct _BgRecentSource +{ + BgSource parent; + + GFile *backgrounds_folder; + GFileMonitor *monitor; + + GCancellable *cancellable; + GHashTable *items; +}; + +G_DEFINE_TYPE (BgRecentSource, bg_recent_source, BG_TYPE_SOURCE) + +static int +sort_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + CcBackgroundItem *item_a; + CcBackgroundItem *item_b; + guint64 modified_a; + guint64 modified_b; + int retval; + + item_a = (CcBackgroundItem *) a; + item_b = (CcBackgroundItem *) b; + modified_a = cc_background_item_get_modified (item_a); + modified_b = cc_background_item_get_modified (item_b); + + retval = modified_b - modified_a; + + return retval; +} + +static void +add_file_from_info (BgRecentSource *self, + GFile *file, + GFileInfo *info) +{ + g_autoptr(CcBackgroundItem) item = NULL; + CcBackgroundItemFlags flags = 0; + g_autofree gchar *source_uri = NULL; + g_autofree gchar *uri = NULL; + GListStore *store; + const gchar *content_type; + guint64 mtime; + + content_type = g_file_info_get_content_type (info); + mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + + if (!content_type || !g_content_type_is_a (content_type, "image/*")) + return; + + uri = g_file_get_uri (file); + item = cc_background_item_new (uri); + flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT; + g_object_set (G_OBJECT (item), + "flags", flags, + "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID, + "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM, + "modified", mtime, + "needs-download", FALSE, + "source-url", source_uri, + NULL); + + store = bg_source_get_liststore (BG_SOURCE (self)); + g_list_store_insert_sorted (store, item, sort_func, self); + + g_hash_table_insert (self->items, g_strdup (uri), g_object_ref (item)); +} + +static void +remove_item (BgRecentSource *self, + CcBackgroundItem *item) +{ + GListStore *store; + const gchar *uri; + guint i; + + g_return_if_fail (BG_IS_RECENT_SOURCE (self)); + g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); + + uri = cc_background_item_get_uri (item); + store = bg_source_get_liststore (BG_SOURCE (self)); + + g_debug ("Removing wallpaper %s", uri); + + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) + { + g_autoptr(CcBackgroundItem) tmp = NULL; + + tmp = g_list_model_get_item (G_LIST_MODEL (store), i); + + if (tmp == item) + { + g_list_store_remove (store, i); + break; + } + } + + g_hash_table_remove (self->items, cc_background_item_get_uri (item)); +} + +static void +query_info_finished_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + BgRecentSource *self; + g_autoptr(GFileInfo) file_info = NULL; + g_autoptr(GError) error = NULL; + GFile *file = NULL; + + file = G_FILE (source); + file_info = g_file_query_info_finish (file, result, &error); + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get pictures file information: %s", error->message); + return; + } + + self = BG_RECENT_SOURCE (user_data); + + g_debug ("Adding wallpaper %s (%d)", + g_file_info_get_name (file_info), + G_IS_FILE (self->backgrounds_folder)); + + add_file_from_info (self, file, file_info); +} + +static void +on_file_changed_cb (BgRecentSource *self, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type) +{ + g_autofree gchar *uri = NULL; + + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + g_file_query_info_async (file, + ATTRIBUTES, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + G_PRIORITY_DEFAULT, + self->cancellable, + query_info_finished_cb, + self); + break; + + case G_FILE_MONITOR_EVENT_DELETED: + uri = g_file_get_uri (file); + remove_item (self, g_hash_table_lookup (self->items, uri)); + break; + + default: + return; + } +} + +static int +file_sort_func (gconstpointer a, + gconstpointer b) +{ + GFileInfo *file_a = G_FILE_INFO (a); + GFileInfo *file_b = G_FILE_INFO (b); + guint64 modified_a, modified_b; + + modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED); + modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED); + + return modified_b - modified_a; +} + +static void +file_info_async_ready_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + BgRecentSource *self; + g_autolist(GFileInfo) file_infos = NULL; + g_autoptr(GError) error = NULL; + GFile *parent = NULL; + GList *l; + + file_infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source), + result, + &error); + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get pictures file information: %s", error->message); + return; + } + + self = BG_RECENT_SOURCE (user_data); + parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source)); + + file_infos = g_list_sort (file_infos, file_sort_func); + + for (l = file_infos; l; l = l->next) + { + g_autoptr(GFile) file = NULL; + GFileInfo *info; + + info = l->data; + file = g_file_get_child (parent, g_file_info_get_name (info)); + + g_debug ("Found recent wallpaper %s", g_file_info_get_name (info)); + + add_file_from_info (self, file, info); + } + + g_file_enumerator_close (G_FILE_ENUMERATOR (source), self->cancellable, &error); + + if (error) + g_warning ("Error closing file enumerator: %s", error->message); +} + +static void +enumerate_children_finished_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + BgRecentSource *self; + g_autoptr(GFileEnumerator) enumerator = NULL; + g_autoptr(GError) error = NULL; + + enumerator = g_file_enumerate_children_finish (G_FILE (source), result, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not fill pictures source: %s", error->message); + return; + } + + self = BG_RECENT_SOURCE (user_data); + g_file_enumerator_next_files_async (enumerator, + G_MAXINT, + G_PRIORITY_DEFAULT, + self->cancellable, + file_info_async_ready_cb, + self); +} + +static void +load_backgrounds (BgRecentSource *self) +{ + g_autofree gchar *backgrounds_path = NULL; + g_autoptr(GError) error = NULL; + + if (!g_file_make_directory_with_parents (self->backgrounds_folder, self->cancellable, &error) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_critical ("Failed to create local background directory: %s", error->message); + return; + } + + backgrounds_path = g_file_get_path (self->backgrounds_folder); + g_debug ("Enumerating wallpapers under %s", backgrounds_path); + + g_file_enumerate_children_async (self->backgrounds_folder, + ATTRIBUTES, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + G_PRIORITY_DEFAULT, + self->cancellable, + enumerate_children_finished_cb, + self); + + self->monitor = g_file_monitor_directory (self->backgrounds_folder, + G_FILE_MONITOR_WATCH_MOVES, + self->cancellable, + &error); + + if (!self->monitor) + { + g_critical ("Failed to monitor background directory: %s", error->message); + return; + } + + g_signal_connect_object (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self, G_CONNECT_SWAPPED); +} + +/* Callbacks */ + +static void +on_file_copied_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data); + g_autofree gchar *original_file = NULL; + g_autoptr(GError) error = NULL; + + g_file_copy_finish (G_FILE (source), result, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_critical ("Failed to copy file: %s", error->message); + return; + } + + original_file = g_file_get_path (G_FILE (source)); + g_debug ("Successfully copied wallpaper: %s", original_file); +} + +static void +on_file_deleted_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data); + g_autofree gchar *original_file = NULL; + g_autoptr(GError) error = NULL; + + g_file_delete_finish (G_FILE (source), result, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_critical ("Failed to delete wallpaper: %s", error->message); + return; + } + + original_file = g_file_get_path (G_FILE (source)); + g_debug ("Successfully deleted wallpaper: %s", original_file); +} + +/* GObject overrides */ + +static void +bg_recent_source_finalize (GObject *object) +{ + BgRecentSource *self = (BgRecentSource *)object; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->monitor); + + G_OBJECT_CLASS (bg_recent_source_parent_class)->finalize (object); +} + +static void +bg_recent_source_class_init (BgRecentSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = bg_recent_source_finalize; +} + +static void +bg_recent_source_init (BgRecentSource *self) +{ + g_autofree gchar *backgrounds_path = NULL; + + backgrounds_path = g_build_filename (g_get_user_data_dir (), "backgrounds", NULL); + + self->items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + self->cancellable = g_cancellable_new (); + self->backgrounds_folder = g_file_new_for_path (backgrounds_path); + + load_backgrounds (self); +} + +BgRecentSource* +bg_recent_source_new (GtkWidget *widget) +{ + return g_object_new (BG_TYPE_RECENT_SOURCE, + "widget", widget, + NULL); +} + +void +bg_recent_source_add_file (BgRecentSource *self, + const gchar *path) +{ + g_autoptr(GDateTime) now = NULL; + g_autofree gchar *destination_name = NULL; + g_autofree gchar *formatted_now = NULL; + g_autofree gchar *basename = NULL; + g_autoptr(GFile) destination = NULL; + g_autoptr(GFile) file = NULL; + + g_return_if_fail (BG_IS_RECENT_SOURCE (self)); + g_return_if_fail (path && *path); + + g_debug ("Importing wallpaper %s", path); + + now = g_date_time_new_now_local (); + formatted_now = g_date_time_format (now, "%Y-%m-%d-%H-%M-%S"); + + file = g_file_new_for_path (path); + + basename = g_file_get_basename (file); + destination_name = g_strdup_printf ("%s-%s", formatted_now, basename); + destination = g_file_get_child (self->backgrounds_folder, destination_name); + + g_file_copy_async (file, + destination, + G_FILE_COPY_NONE, + G_PRIORITY_DEFAULT, + self->cancellable, + NULL, NULL, + on_file_copied_cb, + g_object_ref (self)); +} + +void +bg_recent_source_remove_item (BgRecentSource *self, + CcBackgroundItem *item) +{ + g_autoptr(GFile) file = NULL; + const gchar *uri; + + g_return_if_fail (BG_IS_RECENT_SOURCE (self)); + g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); + + uri = cc_background_item_get_uri (item); + file = g_file_new_for_uri (uri); + + g_file_delete_async (file, + G_PRIORITY_DEFAULT, + self->cancellable, + on_file_deleted_cb, + g_object_ref (self)); +} diff --git a/panels/background/bg-recent-source.h b/panels/background/bg-recent-source.h new file mode 100644 index 0000000..58d4c04 --- /dev/null +++ b/panels/background/bg-recent-source.h @@ -0,0 +1,39 @@ +/* bg-recent-source.h + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "bg-source.h" +#include "cc-background-item.h" + +G_BEGIN_DECLS + +#define BG_TYPE_RECENT_SOURCE (bg_recent_source_get_type()) +G_DECLARE_FINAL_TYPE (BgRecentSource, bg_recent_source, BG, RECENT_SOURCE, BgSource) + +BgRecentSource* bg_recent_source_new (GtkWidget *widget); + +void bg_recent_source_add_file (BgRecentSource *self, + const gchar *path); + +void bg_recent_source_remove_item (BgRecentSource *self, + CcBackgroundItem *item); + +G_END_DECLS diff --git a/panels/background/bg-source.c b/panels/background/bg-source.c new file mode 100644 index 0000000..dacf82f --- /dev/null +++ b/panels/background/bg-source.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#include "bg-source.h" +#include "cc-background-item.h" + +#include <cairo-gobject.h> + +#define THUMBNAIL_WIDTH 144 +#define THUMBNAIL_HEIGHT (THUMBNAIL_WIDTH * 3 / 4) + +typedef struct +{ + GnomeDesktopThumbnailFactory *thumbnail_factory; + GListStore *store; + GtkWidget *widget; + gint thumbnail_height; + gint thumbnail_width; +} BgSourcePrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (BgSource, bg_source, G_TYPE_OBJECT) + +enum +{ + PROP_LISTSTORE = 1, + PROP_WIDGET +}; + + +static void +bg_source_calculate_thumbnail_dimensions (BgSource *source) +{ + BgSourcePrivate *priv = bg_source_get_instance_private (source); + gint scale_factor; + + priv->thumbnail_height = THUMBNAIL_HEIGHT; + priv->thumbnail_width = THUMBNAIL_WIDTH; + + if (priv->widget == NULL) + return; + + scale_factor = gtk_widget_get_scale_factor (priv->widget); + if (scale_factor > 1) + { + priv->thumbnail_height *= scale_factor; + priv->thumbnail_width *= scale_factor; + } +} + +static void +bg_source_constructed (GObject *object) +{ + G_OBJECT_CLASS (bg_source_parent_class)->constructed (object); + + bg_source_calculate_thumbnail_dimensions (BG_SOURCE (object)); +} + +static void +bg_source_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + BgSource *source = BG_SOURCE (object); + + switch (property_id) + { + case PROP_LISTSTORE: + g_value_set_object (value, bg_source_get_liststore (source)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bg_source_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + BgSource *source = BG_SOURCE (object); + BgSourcePrivate *priv = bg_source_get_instance_private (source); + + switch (property_id) + { + case PROP_WIDGET: + priv->widget = GTK_WIDGET (g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bg_source_dispose (GObject *object) +{ + BgSource *source = BG_SOURCE (object); + BgSourcePrivate *priv = bg_source_get_instance_private (source); + + g_clear_object (&priv->thumbnail_factory); + g_clear_object (&priv->store); + + G_OBJECT_CLASS (bg_source_parent_class)->dispose (object); +} + +static void +bg_source_class_init (BgSourceClass *klass) +{ + GParamSpec *pspec; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = bg_source_constructed; + object_class->get_property = bg_source_get_property; + object_class->set_property = bg_source_set_property; + object_class->dispose = bg_source_dispose; + + pspec = g_param_spec_object ("liststore", + "Liststore", + "Liststore used in the source", + G_TYPE_LIST_STORE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_LISTSTORE, pspec); + + pspec = g_param_spec_object ("widget", + "Widget", + "Widget used to view the source", + GTK_TYPE_WIDGET, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_WIDGET, pspec); +} + +static void +bg_source_init (BgSource *self) +{ + BgSourcePrivate *priv = bg_source_get_instance_private (self); + priv->store = g_list_store_new (CC_TYPE_BACKGROUND_ITEM); + priv->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); +} + +GListStore* +bg_source_get_liststore (BgSource *source) +{ + BgSourcePrivate *priv; + + g_return_val_if_fail (BG_IS_SOURCE (source), NULL); + + priv = bg_source_get_instance_private (source); + return priv->store; +} + +gint +bg_source_get_scale_factor (BgSource *source) +{ + BgSourcePrivate *priv; + + g_return_val_if_fail (BG_IS_SOURCE (source), 1); + + priv = bg_source_get_instance_private (source); + return gtk_widget_get_scale_factor (priv->widget); +} + +gint +bg_source_get_thumbnail_height (BgSource *source) +{ + BgSourcePrivate *priv; + + g_return_val_if_fail (BG_IS_SOURCE (source), THUMBNAIL_HEIGHT); + + priv = bg_source_get_instance_private (source); + return priv->thumbnail_height; +} + +gint +bg_source_get_thumbnail_width (BgSource *source) +{ + BgSourcePrivate *priv; + + g_return_val_if_fail (BG_IS_SOURCE (source), THUMBNAIL_WIDTH); + + priv = bg_source_get_instance_private (source); + return priv->thumbnail_width; +} + +GnomeDesktopThumbnailFactory* +bg_source_get_thumbnail_factory (BgSource *source) +{ + BgSourcePrivate *priv; + + g_return_val_if_fail (BG_IS_SOURCE (source), NULL); + + priv = bg_source_get_instance_private (source); + return priv->thumbnail_factory; +} diff --git a/panels/background/bg-source.h b/panels/background/bg-source.h new file mode 100644 index 0000000..ff86a85 --- /dev/null +++ b/panels/background/bg-source.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include <libgnome-desktop/gnome-desktop-thumbnail.h> + +G_BEGIN_DECLS + +#define BG_TYPE_SOURCE (bg_source_get_type ()) +G_DECLARE_DERIVABLE_TYPE (BgSource, bg_source, BG, SOURCE, GObject) + +struct _BgSourceClass +{ + GObjectClass parent_class; +}; + +GListStore* bg_source_get_liststore (BgSource *source); + +gint bg_source_get_scale_factor (BgSource *source); + +gint bg_source_get_thumbnail_height (BgSource *source); + +gint bg_source_get_thumbnail_width (BgSource *source); + +GnomeDesktopThumbnailFactory* bg_source_get_thumbnail_factory (BgSource *source); + +G_END_DECLS diff --git a/panels/background/bg-wallpapers-source.c b/panels/background/bg-wallpapers-source.c new file mode 100644 index 0000000..20ca4b7 --- /dev/null +++ b/panels/background/bg-wallpapers-source.c @@ -0,0 +1,164 @@ +/* bg-wallpapers-source.c */ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#include "bg-wallpapers-source.h" + +#include "cc-background-item.h" +#include "cc-background-xml.h" + +#include <cairo-gobject.h> +#include <gio/gio.h> + +struct _BgWallpapersSource +{ + BgSource parent_instance; + CcBackgroundXml *xml; +}; + +G_DEFINE_TYPE (BgWallpapersSource, bg_wallpapers_source, BG_TYPE_SOURCE) + +static int +sort_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + CcBackgroundItem *item_a; + CcBackgroundItem *item_b; + const char *name_a; + const char *name_b; + + item_a = (CcBackgroundItem *) a; + item_b = (CcBackgroundItem *) b; + + name_a = cc_background_item_get_name (item_a); + name_b = cc_background_item_get_name (item_b); + + if (name_a && strcmp (name_a, "Default Background") == 0) + return -1; + if (name_b && strcmp (name_b, "Default Background") == 0) + return 1; + + + return strcmp (cc_background_item_get_name (item_a), + cc_background_item_get_name (item_b)); +} + +static void +load_wallpapers (gchar *key, + CcBackgroundItem *item, + BgWallpapersSource *source) +{ + GListStore *store = bg_source_get_liststore (BG_SOURCE (source)); + gboolean deleted; + + g_object_get (G_OBJECT (item), "is-deleted", &deleted, NULL); + + if (deleted) + return; + + g_list_store_insert_sorted (store, item, sort_func, NULL); +} + +static void +list_load_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + if (!cc_background_xml_load_list_finish (CC_BACKGROUND_XML (source_object), res, &error)) + g_warning ("Failed to load background list: %s", error->message); +} + +static void +item_added (BgWallpapersSource *self, + CcBackgroundItem *item) +{ + load_wallpapers (NULL, item, self); +} + +static void +load_default_bg (BgWallpapersSource *self) +{ + const char * const *system_data_dirs; + guint i; + + /* FIXME We could do this nicer if we had the XML source in GSettings */ + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_autofree gchar *filename = NULL; + + filename = g_build_filename (system_data_dirs[i], + "gnome-background-properties", + "adwaita.xml", + NULL); + if (cc_background_xml_load_xml (self->xml, filename)) + break; + } +} + +static void +bg_wallpapers_source_constructed (GObject *object) +{ + BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object); + + G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->constructed (object); + + g_signal_connect_object (G_OBJECT (self->xml), "added", + G_CALLBACK (item_added), self, G_CONNECT_SWAPPED); + + /* Try adding the default background first */ + load_default_bg (self); + + cc_background_xml_load_list_async (self->xml, NULL, list_load_cb, self); +} + +static void +bg_wallpapers_source_dispose (GObject *object) +{ + BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object); + + g_clear_object (&self->xml); + + G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->dispose (object); +} + +static void +bg_wallpapers_source_init (BgWallpapersSource *self) +{ + self->xml = cc_background_xml_new (); +} + +static void +bg_wallpapers_source_class_init (BgWallpapersSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = bg_wallpapers_source_constructed; + object_class->dispose = bg_wallpapers_source_dispose; +} + +BgWallpapersSource * +bg_wallpapers_source_new (GtkWidget *widget) +{ + return g_object_new (BG_TYPE_WALLPAPERS_SOURCE, "widget", widget, NULL); +} + diff --git a/panels/background/bg-wallpapers-source.h b/panels/background/bg-wallpapers-source.h new file mode 100644 index 0000000..3ca222b --- /dev/null +++ b/panels/background/bg-wallpapers-source.h @@ -0,0 +1,34 @@ +/* bg-wallpapers-source.h */ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include "bg-source.h" + +G_BEGIN_DECLS + +#define BG_TYPE_WALLPAPERS_SOURCE (bg_wallpapers_source_get_type ()) +G_DECLARE_FINAL_TYPE (BgWallpapersSource, bg_wallpapers_source, BG, WALLPAPERS_SOURCE, BgSource) + +BgWallpapersSource *bg_wallpapers_source_new (GtkWidget *widget); + +G_END_DECLS diff --git a/panels/background/cc-background-chooser.c b/panels/background/cc-background-chooser.c new file mode 100644 index 0000000..8d24e45 --- /dev/null +++ b/panels/background/cc-background-chooser.c @@ -0,0 +1,337 @@ +/* cc-background-chooser.c + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-background-chooser" + +#include <glib/gi18n.h> +#include <libgnome-desktop/gnome-desktop-thumbnail.h> + +#include "bg-colors-source.h" +#include "bg-recent-source.h" +#include "bg-wallpapers-source.h" +#include "cc-background-chooser.h" +#include "cc-background-paintable.h" + +struct _CcBackgroundChooser +{ + GtkBox parent; + + GtkFlowBox *flowbox; + GtkWidget *recent_box; + GtkFlowBox *recent_flowbox; + + gboolean recent_selected; + + BgWallpapersSource *wallpapers_source; + BgRecentSource *recent_source; +}; + +G_DEFINE_TYPE (CcBackgroundChooser, cc_background_chooser, GTK_TYPE_BOX) + +enum +{ + BACKGROUND_CHOSEN, + N_SIGNALS, +}; + +static guint signals [N_SIGNALS]; + +static void +emit_background_chosen (CcBackgroundChooser *self) +{ + g_autoptr(GList) list = NULL; + CcBackgroundItem *item; + GtkFlowBox *flowbox; + + flowbox = self->recent_selected ? self->recent_flowbox : self->flowbox; + list = gtk_flow_box_get_selected_children (flowbox); + g_assert (g_list_length (list) == 1); + + item = g_object_get_data (list->data, "item"); + + g_signal_emit (self, signals[BACKGROUND_CHOSEN], 0, item); +} + +static void +on_delete_background_clicked_cb (GtkButton *button, + BgRecentSource *source) +{ + GtkWidget *parent; + CcBackgroundItem *item; + + parent = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (button))); + g_assert (GTK_IS_FLOW_BOX_CHILD (parent)); + + item = g_object_get_data (G_OBJECT (parent), "item"); + + bg_recent_source_remove_item (source, item); +} + +static void +direction_changed_cb (GtkWidget *widget, + GtkTextDirection *previous_direction, + GdkPaintable *paintable) +{ + g_object_set (paintable, + "text-direction", gtk_widget_get_direction (widget), + NULL); +} + +static GtkWidget* +create_widget_func (gpointer model_item, + gpointer user_data) +{ + g_autoptr(CcBackgroundPaintable) paintable = NULL; + CcBackgroundItem *item; + GtkWidget *overlay; + GtkWidget *child; + GtkWidget *picture; + GtkWidget *icon; + GtkWidget *check; + GtkWidget *button = NULL; + BgSource *source; + + source = BG_SOURCE (user_data); + item = CC_BACKGROUND_ITEM (model_item); + + paintable = cc_background_paintable_new (source, item); + + picture = gtk_picture_new_for_paintable (GDK_PAINTABLE (paintable)); + gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE); + + g_object_bind_property (picture, "scale-factor", + paintable, "scale-factor", G_BINDING_SYNC_CREATE); + g_signal_connect_object (picture, "direction-changed", + G_CALLBACK (direction_changed_cb), paintable, 0); + + icon = gtk_image_new_from_icon_name ("slideshow-symbolic"); + gtk_widget_set_halign (icon, GTK_ALIGN_START); + gtk_widget_set_valign (icon, GTK_ALIGN_END); + gtk_widget_set_visible (icon, cc_background_item_changes_with_time (item)); + gtk_widget_add_css_class (icon, "slideshow-icon"); + + check = gtk_image_new_from_icon_name ("background-selected-symbolic"); + gtk_widget_set_halign (check, GTK_ALIGN_END); + gtk_widget_set_valign (check, GTK_ALIGN_END); + gtk_widget_add_css_class (check, "selected-check"); + + if (BG_IS_RECENT_SOURCE (source)) + { + button = gtk_button_new_from_icon_name ("window-close-symbolic"); + gtk_widget_set_halign (button, GTK_ALIGN_END); + gtk_widget_set_valign (button, GTK_ALIGN_START); + + gtk_widget_add_css_class (button, "osd"); + gtk_widget_add_css_class (button, "circular"); + gtk_widget_add_css_class (button, "remove-button"); + + g_signal_connect (button, + "clicked", + G_CALLBACK (on_delete_background_clicked_cb), + source); + } + + overlay = gtk_overlay_new (); + gtk_widget_set_overflow (overlay, GTK_OVERFLOW_HIDDEN); + gtk_widget_add_css_class (overlay, "background-thumbnail"); + gtk_overlay_set_child (GTK_OVERLAY (overlay), picture); + gtk_overlay_add_overlay (GTK_OVERLAY (overlay), icon); + gtk_overlay_add_overlay (GTK_OVERLAY (overlay), check); + if (button) + gtk_overlay_add_overlay (GTK_OVERLAY (overlay), button); + gtk_accessible_update_property (GTK_ACCESSIBLE (overlay), + GTK_ACCESSIBLE_PROPERTY_LABEL, + cc_background_item_get_name (item), + -1); + + + child = gtk_flow_box_child_new (); + gtk_widget_set_halign (child, GTK_ALIGN_CENTER); + gtk_widget_set_valign (child, GTK_ALIGN_CENTER); + gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (child), overlay); + + g_object_set_data_full (G_OBJECT (child), "item", g_object_ref (item), g_object_unref); + + return child; +} + +static void +update_recent_visibility (CcBackgroundChooser *self) +{ + GListStore *store; + gboolean has_items; + + store = bg_source_get_liststore (BG_SOURCE (self->recent_source)); + has_items = g_list_model_get_n_items (G_LIST_MODEL (store)) != 0; + + gtk_widget_set_visible (self->recent_box, has_items); +} + +static void +setup_flowbox (CcBackgroundChooser *self) +{ + GListStore *store; + + store = bg_source_get_liststore (BG_SOURCE (self->wallpapers_source)); + + gtk_flow_box_bind_model (self->flowbox, + G_LIST_MODEL (store), + create_widget_func, + self->wallpapers_source, + NULL); + + store = bg_source_get_liststore (BG_SOURCE (self->recent_source)); + + gtk_flow_box_bind_model (self->recent_flowbox, + G_LIST_MODEL (store), + create_widget_func, + self->recent_source, + NULL); + + update_recent_visibility (self); + g_signal_connect_object (store, + "items-changed", + G_CALLBACK (update_recent_visibility), + self, + G_CONNECT_SWAPPED); +} + +static void +on_item_activated_cb (GtkFlowBox *flowbox, + GtkFlowBoxChild *child, + CcBackgroundChooser *self) +{ + self->recent_selected = flowbox == self->recent_flowbox; + if (self->recent_selected) + gtk_flow_box_unselect_all (self->flowbox); + else + gtk_flow_box_unselect_all (self->recent_flowbox); + emit_background_chosen (self); +} + +static void +on_file_chooser_response_cb (GtkDialog *filechooser, + gint response, + CcBackgroundChooser *self) +{ + if (response == GTK_RESPONSE_ACCEPT) + { + g_autoptr(GListModel) files = NULL; + guint i; + + files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (filechooser)); + for (i = 0; i < g_list_model_get_n_items (files); i++) + { + g_autoptr(GFile) file = g_list_model_get_item (files, i); + g_autofree gchar *filename = g_file_get_path (file); + + bg_recent_source_add_file (self->recent_source, filename); + } + } + + gtk_window_destroy (GTK_WINDOW (filechooser)); +} + +/* GObject overrides */ + +static void +cc_background_chooser_finalize (GObject *object) +{ + CcBackgroundChooser *self = (CcBackgroundChooser *)object; + + g_clear_object (&self->recent_source); + g_clear_object (&self->wallpapers_source); + + G_OBJECT_CLASS (cc_background_chooser_parent_class)->finalize (object); +} + +static void +cc_background_chooser_class_init (CcBackgroundChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_background_chooser_finalize; + + signals[BACKGROUND_CHOSEN] = g_signal_new ("background-chosen", + CC_TYPE_BACKGROUND_CHOOSER, + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, + CC_TYPE_BACKGROUND_ITEM); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-chooser.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, flowbox); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_box); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_flowbox); + + gtk_widget_class_bind_template_callback (widget_class, on_item_activated_cb); +} + +static void +cc_background_chooser_init (CcBackgroundChooser *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->recent_source = bg_recent_source_new (GTK_WIDGET (self)); + self->wallpapers_source = bg_wallpapers_source_new (GTK_WIDGET (self)); + setup_flowbox (self); +} + +void +cc_background_chooser_select_file (CcBackgroundChooser *self) +{ + g_autoptr(GFile) pictures_folder = NULL; + GtkFileFilter *filter; + GtkWidget *filechooser; + GtkWindow *toplevel; + + g_return_if_fail (CC_IS_BACKGROUND_CHOOSER (self)); + + toplevel = (GtkWindow*) gtk_widget_get_native (GTK_WIDGET (self)); + filechooser = gtk_file_chooser_dialog_new (_("Select a picture"), + toplevel, + GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_modal (GTK_WINDOW (filechooser), TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (filechooser), filter); + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (filechooser), TRUE); + + pictures_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES)); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser), + pictures_folder, + NULL); + + g_signal_connect_object (filechooser, + "response", + G_CALLBACK (on_file_chooser_response_cb), + self, + 0); + + gtk_window_present (GTK_WINDOW (filechooser)); +} diff --git a/panels/background/cc-background-chooser.h b/panels/background/cc-background-chooser.h new file mode 100644 index 0000000..1751098 --- /dev/null +++ b/panels/background/cc-background-chooser.h @@ -0,0 +1,32 @@ +/* cc-background-chooser.h + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_CHOOSER (cc_background_chooser_get_type()) +G_DECLARE_FINAL_TYPE (CcBackgroundChooser, cc_background_chooser, CC, BACKGROUND_CHOOSER, GtkBox) + +void cc_background_chooser_select_file (CcBackgroundChooser *self); + +G_END_DECLS diff --git a/panels/background/cc-background-chooser.ui b/panels/background/cc-background-chooser.ui new file mode 100644 index 0000000..edaf1dd --- /dev/null +++ b/panels/background/cc-background-chooser.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcBackgroundChooser" parent="GtkBox"> + <property name="orientation">vertical</property> + + <!-- Recent --> + <child> + <object class="GtkBox" id="recent_box"> + <property name="orientation">vertical</property> + <property name="halign">center</property> + + <child> + <object class="GtkFlowBox" id="recent_flowbox"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="column-spacing">12</property> + <property name="row-spacing">12</property> + <property name="homogeneous">True</property> + <property name="halign">center</property> + <property name="min-children-per-line">1</property> + <property name="max-children-per-line">8</property> + <property name="activate-on-single-click">True</property> + <property name="selection-mode">single</property> + <signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="no" /> + <style> + <class name="background-flowbox"/> + </style> + </object> + </child> + + <child> + <object class="GtkSeparator"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkFlowBox" id="flowbox"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="column-spacing">12</property> + <property name="row-spacing">12</property> + <property name="homogeneous">True</property> + <property name="halign">center</property> + <property name="min-children-per-line">1</property> + <property name="max-children-per-line">8</property> + <property name="activate-on-single-click">True</property> + <property name="selection-mode">single</property> + <signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="no" /> + <style> + <class name="background-flowbox"/> + </style> + </object> + </child> + + </template> +</interface> diff --git a/panels/background/cc-background-item.c b/panels/background/cc-background-item.c new file mode 100644 index 0000000..e25c8c4 --- /dev/null +++ b/panels/background/cc-background-item.c @@ -0,0 +1,1062 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010-2011 Red Hat, Inc. + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <glib/gi18n-lib.h> + +#include <gnome-bg/gnome-bg.h> +#include <gdesktop-enums.h> + +#include "cc-background-item.h" +#include "gdesktop-enums-types.h" + +typedef struct { + int width; + int height; + int frame; + int scale_factor; + GdkPixbuf *thumbnail; +} CachedThumbnail; + +struct _CcBackgroundItem +{ + GObject parent_instance; + + /* properties */ + char *name; + char *uri; + char *uri_dark; + char *size; + GDesktopBackgroundStyle placement; + GDesktopBackgroundShading shading; + char *primary_color; + char *secondary_color; + char *source_url; /* Used by the Flickr source */ + char *source_xml; /* Used by the Wallpapers source */ + gboolean is_deleted; + gboolean needs_download; + CcBackgroundItemFlags flags; + guint64 modified; + + /* internal */ + GnomeBG *bg; + char *mime_type; + int width; + int height; + + GnomeBG *bg_dark; + + CachedThumbnail cached_thumbnail; + CachedThumbnail cached_thumbnail_dark; +}; + +enum { + PROP_0, + PROP_NAME, + PROP_URI, + PROP_URI_DARK, + PROP_PLACEMENT, + PROP_SHADING, + PROP_PRIMARY_COLOR, + PROP_SECONDARY_COLOR, + PROP_IS_DELETED, + PROP_SOURCE_URL, + PROP_SOURCE_XML, + PROP_FLAGS, + PROP_SIZE, + PROP_NEEDS_DOWNLOAD, + PROP_MODIFIED +}; + +static void cc_background_item_finalize (GObject *object); + +G_DEFINE_TYPE (CcBackgroundItem, cc_background_item, G_TYPE_OBJECT) + +static void +set_bg_properties (CcBackgroundItem *item) +{ + GdkRGBA pcolor = { 0, 0, 0, 0 }; + GdkRGBA scolor = { 0, 0, 0, 0 }; + + if (item->uri) { + g_autoptr(GFile) file = NULL; + g_autofree gchar *filename = NULL; + + file = g_file_new_for_commandline_arg (item->uri); + filename = g_file_get_path (file); + gnome_bg_set_filename (item->bg, filename); + } + + if (item->uri_dark) { + g_autoptr(GFile) file = NULL; + g_autofree gchar *filename = NULL; + + file = g_file_new_for_commandline_arg (item->uri_dark); + filename = g_file_get_path (file); + gnome_bg_set_filename (item->bg_dark, filename); + } + + if (item->primary_color != NULL) { + gdk_rgba_parse (&pcolor, item->primary_color); + } + if (item->secondary_color != NULL) { + gdk_rgba_parse (&scolor, item->secondary_color); + } + + gnome_bg_set_rgba (item->bg, item->shading, &pcolor, &scolor); + gnome_bg_set_rgba (item->bg_dark, item->shading, &pcolor, &scolor); + gnome_bg_set_placement (item->bg, item->placement); + gnome_bg_set_placement (item->bg_dark, item->placement); +} + + +gboolean +cc_background_item_changes_with_time (CcBackgroundItem *item) +{ + gboolean changes; + + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE); + + changes = FALSE; + if (item->bg != NULL) { + changes = gnome_bg_changes_with_time (item->bg); + } + if (item->bg_dark != NULL) { + changes |= gnome_bg_changes_with_time (item->bg_dark); + } + return changes; +} + +gboolean +cc_background_item_has_dark_version (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE); + + return item->uri && item->uri_dark; +} + +static void +update_size (CcBackgroundItem *item) +{ + g_clear_pointer (&item->size, g_free); + + if (item->uri == NULL) { + item->size = g_strdup (""); + } else { + if (gnome_bg_has_multiple_sizes (item->bg) || gnome_bg_changes_with_time (item->bg)) { + item->size = g_strdup (_("multiple sizes")); + } else { + /* translators: 100 × 100px + * Note that this is not an "x", but U+00D7 MULTIPLICATION SIGN */ + item->size = g_strdup_printf (_("%d × %d"), + item->width, + item->height); + } + } +} + +static GdkPixbuf * +render_at_size (GnomeBG *bg, + gint width, + gint height) +{ + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height); + gnome_bg_draw (bg, pixbuf); + + return pixbuf; +} + +GdkPixbuf * +cc_background_item_get_frame_thumbnail (CcBackgroundItem *item, + GnomeDesktopThumbnailFactory *thumbs, + int width, + int height, + int scale_factor, + int frame, + gboolean force_size, + gboolean dark) +{ + g_autoptr(GdkPixbuf) pixbuf = NULL; + g_autoptr(GdkPixbuf) retval = NULL; + CachedThumbnail *thumbnail; + GnomeBG *bg; + + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + g_return_val_if_fail (width > 0 && height > 0, NULL); + + thumbnail = dark ? &item->cached_thumbnail_dark : &item->cached_thumbnail; + bg = dark ? item->bg_dark : item->bg; + + /* Use the cached thumbnail if the sizes match */ + if (thumbnail->thumbnail && + thumbnail->width == width && + thumbnail->height == height && + thumbnail->scale_factor == scale_factor && + thumbnail->frame == frame) + return g_object_ref (thumbnail->thumbnail); + + set_bg_properties (item); + + if (force_size) { + /* FIXME: this doesn't play nice with slideshow stepping at all, + * because it will always render the current slideshow frame, which + * might not be what we want. + * We're lacking an API to draw a high-res GnomeBG manually choosing + * the slideshow frame though, so we can't do much better than this + * for now. + */ + pixbuf = render_at_size (bg, width, height); + } else { + g_autoptr(GdkMonitor) monitor = NULL; + GdkDisplay *display; + GListModel *monitors; + GdkRectangle monitor_layout; + + + display = gdk_display_get_default (); + monitors = gdk_display_get_monitors (display); + monitor = g_list_model_get_item (monitors, 0); + gdk_monitor_get_geometry (monitor, &monitor_layout); + + if (frame >= 0) { + pixbuf = gnome_bg_create_frame_thumbnail (bg, + thumbs, + &monitor_layout, + width, + height, + frame); + } else { + pixbuf = gnome_bg_create_thumbnail (bg, + thumbs, + &monitor_layout, + width, + height); + } + } + + retval = g_steal_pointer (&pixbuf); + + gnome_bg_get_image_size (bg, + thumbs, + width, + height, + &item->width, + &item->height); + + update_size (item); + + /* Cache the new thumbnail */ + g_set_object (&thumbnail->thumbnail, retval); + thumbnail->width = width; + thumbnail->height = height; + thumbnail->scale_factor = scale_factor; + thumbnail->frame = frame; + + return g_steal_pointer (&retval); +} + + +GdkPixbuf * +cc_background_item_get_thumbnail (CcBackgroundItem *item, + GnomeDesktopThumbnailFactory *thumbs, + int width, + int height, + int scale_factor, + gboolean dark) +{ + return cc_background_item_get_frame_thumbnail (item, thumbs, width, height, scale_factor, -1, FALSE, dark); +} + +static void +update_info (CcBackgroundItem *item, + GFileInfo *_info) +{ + g_autoptr(GFileInfo) info = NULL; + + if (_info == NULL) { + g_autoptr(GFile) file = NULL; + + file = g_file_new_for_uri (item->uri); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + } else { + info = g_object_ref (_info); + } + + g_clear_pointer (&item->mime_type, g_free); + + if (info == NULL + || g_file_info_get_content_type (info) == NULL) { + if (item->uri == NULL) { + item->mime_type = g_strdup ("image/x-no-data"); + g_free (item->name); + item->name = g_strdup (_("No Desktop Background")); + } + } else { + if (item->name == NULL) + item->name = g_strdup (g_file_info_get_display_name (info)); + + item->mime_type = g_strdup (g_file_info_get_content_type (info)); + if (item->modified == 0) + item->modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + } +} + +gboolean +cc_background_item_load (CcBackgroundItem *item, + GFileInfo *info) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE); + + if (item->uri == NULL) + return TRUE; + + update_info (item, info); + + if (item->mime_type != NULL + && (g_str_has_prefix (item->mime_type, "image/") + || strcmp (item->mime_type, "application/xml") == 0)) { + set_bg_properties (item); + } else { + return FALSE; + } + + /* FIXME we should handle XML files as well */ + if (item->mime_type != NULL && + g_str_has_prefix (item->mime_type, "image/")) { + g_autofree gchar *filename = NULL; + + filename = g_filename_from_uri (item->uri, NULL, NULL); + gdk_pixbuf_get_file_info (filename, + &item->width, + &item->height); + update_size (item); + } + + return TRUE; +} + +static void +_set_name (CcBackgroundItem *item, + const char *value) +{ + g_free (item->name); + item->name = g_strdup (value); +} + +const char * +cc_background_item_get_name (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->name; +} + +static void +_set_uri (CcBackgroundItem *item, + const char *value) +{ + g_free (item->uri); + if (value && *value == '\0') { + item->uri = NULL; + } else { + if (value && strstr (value, "://") == NULL) + g_warning ("URI '%s' is invalid", value); + item->uri = g_strdup (value); + } +} + + +static void +_set_uri_dark (CcBackgroundItem *item, + const char *value) +{ + g_free (item->uri_dark); + if (value && *value == '\0') { + item->uri_dark = NULL; + } else { + if (value && strstr (value, "://") == NULL) + g_warning ("URI '%s' is invalid", value); + item->uri_dark = g_strdup (value); + } +} + +const char * +cc_background_item_get_uri (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->uri; +} + +const char * +cc_background_item_get_uri_dark (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->uri_dark; +} + +static void +_set_placement (CcBackgroundItem *item, + GDesktopBackgroundStyle value) +{ + item->placement = value; +} + +static void +_set_shading (CcBackgroundItem *item, + GDesktopBackgroundShading value) +{ + item->shading = value; +} + +static void +_set_primary_color (CcBackgroundItem *item, + const char *value) +{ + g_free (item->primary_color); + item->primary_color = g_strdup (value); +} + +const char * +cc_background_item_get_pcolor (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->primary_color; +} + +static void +_set_secondary_color (CcBackgroundItem *item, + const char *value) +{ + g_free (item->secondary_color); + item->secondary_color = g_strdup (value); +} + +const char * +cc_background_item_get_scolor (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->secondary_color; +} + +GDesktopBackgroundStyle +cc_background_item_get_placement (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), G_DESKTOP_BACKGROUND_STYLE_SCALED); + + return item->placement; +} + +GDesktopBackgroundShading +cc_background_item_get_shading (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), G_DESKTOP_BACKGROUND_SHADING_SOLID); + + return item->shading; +} + +static void +_set_is_deleted (CcBackgroundItem *item, + gboolean value) +{ + item->is_deleted = value; +} + +static void +_set_source_url (CcBackgroundItem *item, + const char *value) +{ + g_free (item->source_url); + item->source_url = g_strdup (value); +} + +const char * +cc_background_item_get_source_url (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->source_url; +} + +static void +_set_source_xml (CcBackgroundItem *item, + const char *value) +{ + g_free (item->source_xml); + item->source_xml = g_strdup (value); +} + +const char * +cc_background_item_get_source_xml (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->source_xml; +} + +static void +_set_flags (CcBackgroundItem *item, + CcBackgroundItemFlags value) +{ + item->flags = value; +} + +CcBackgroundItemFlags +cc_background_item_get_flags (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0); + + return item->flags; +} + +const char * +cc_background_item_get_size (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return item->size; +} + +static void +_set_needs_download (CcBackgroundItem *item, + gboolean value) +{ + item->needs_download = value; +} + +gboolean +cc_background_item_get_needs_download (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0); + + return item->needs_download; +} + +static void +_set_modified (CcBackgroundItem *item, + guint64 value) +{ + item->modified = value; +} + +guint64 +cc_background_item_get_modified (CcBackgroundItem *item) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0); + + return item->modified; +} + +static void +cc_background_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcBackgroundItem *self; + + self = CC_BACKGROUND_ITEM (object); + + switch (prop_id) { + case PROP_NAME: + _set_name (self, g_value_get_string (value)); + break; + case PROP_URI: + _set_uri (self, g_value_get_string (value)); + break; + case PROP_URI_DARK: + _set_uri_dark (self, g_value_get_string (value)); + break; + case PROP_PLACEMENT: + _set_placement (self, g_value_get_enum (value)); + break; + case PROP_SHADING: + _set_shading (self, g_value_get_enum (value)); + break; + case PROP_PRIMARY_COLOR: + _set_primary_color (self, g_value_get_string (value)); + break; + case PROP_SECONDARY_COLOR: + _set_secondary_color (self, g_value_get_string (value)); + break; + case PROP_IS_DELETED: + _set_is_deleted (self, g_value_get_boolean (value)); + break; + case PROP_SOURCE_URL: + _set_source_url (self, g_value_get_string (value)); + break; + case PROP_SOURCE_XML: + _set_source_xml (self, g_value_get_string (value)); + break; + case PROP_FLAGS: + _set_flags (self, g_value_get_flags (value)); + break; + case PROP_NEEDS_DOWNLOAD: + _set_needs_download (self, g_value_get_boolean (value)); + break; + case PROP_MODIFIED: + _set_modified (self, g_value_get_uint64 (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_background_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcBackgroundItem *self; + + self = CC_BACKGROUND_ITEM (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, self->name); + break; + case PROP_URI: + g_value_set_string (value, self->uri); + break; + case PROP_URI_DARK: + g_value_set_string (value, self->uri_dark); + break; + case PROP_PLACEMENT: + g_value_set_enum (value, self->placement); + break; + case PROP_SHADING: + g_value_set_enum (value, self->shading); + break; + case PROP_PRIMARY_COLOR: + g_value_set_string (value, self->primary_color); + break; + case PROP_SECONDARY_COLOR: + g_value_set_string (value, self->secondary_color); + break; + case PROP_IS_DELETED: + g_value_set_boolean (value, self->is_deleted); + break; + case PROP_SOURCE_URL: + g_value_set_string (value, self->source_url); + break; + case PROP_SOURCE_XML: + g_value_set_string (value, self->source_xml); + break; + case PROP_FLAGS: + g_value_set_flags (value, self->flags); + break; + case PROP_SIZE: + g_value_set_string (value, self->size); + break; + case PROP_NEEDS_DOWNLOAD: + g_value_set_boolean (value, self->needs_download); + break; + case PROP_MODIFIED: + g_value_set_uint64 (value, self->modified); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +cc_background_item_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + CcBackgroundItem *background_item; + + background_item = CC_BACKGROUND_ITEM (G_OBJECT_CLASS (cc_background_item_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (background_item); +} + +static void +cc_background_item_class_init (CcBackgroundItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_background_item_get_property; + object_class->set_property = cc_background_item_set_property; + object_class->constructor = cc_background_item_constructor; + object_class->finalize = cc_background_item_finalize; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "name", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_URI, + g_param_spec_string ("uri", + "uri", + "uri", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_URI_DARK, + g_param_spec_string ("uri-dark", + "uri-dark", + "uri-dark", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_PLACEMENT, + g_param_spec_enum ("placement", + "placement", + "placement", + G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, + G_DESKTOP_BACKGROUND_STYLE_SCALED, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SHADING, + g_param_spec_enum ("shading", + "shading", + "shading", + G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, + G_DESKTOP_BACKGROUND_SHADING_SOLID, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_PRIMARY_COLOR, + g_param_spec_string ("primary-color", + "primary-color", + "primary-color", + "#000000000000", + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_SECONDARY_COLOR, + g_param_spec_string ("secondary-color", + "secondary-color", + "secondary-color", + "#000000000000", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_IS_DELETED, + g_param_spec_boolean ("is-deleted", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SOURCE_URL, + g_param_spec_string ("source-url", + "source-url", + "source-url", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SOURCE_XML, + g_param_spec_string ("source-xml", + "source-xml", + "source-xml", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_FLAGS, + g_param_spec_flags ("flags", + "flags", + "flags", + G_DESKTOP_TYPE_BACKGROUND_ITEM_FLAGS, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SIZE, + g_param_spec_string ("size", + "size", + "size", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_NEEDS_DOWNLOAD, + g_param_spec_boolean ("needs-download", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_MODIFIED, + g_param_spec_uint64 ("modified", + "modified", + NULL, + 0, + G_MAXUINT64, + 0, + G_PARAM_READWRITE)); +} + +static void +cc_background_item_init (CcBackgroundItem *item) +{ + item->bg = gnome_bg_new (); + item->bg_dark = gnome_bg_new (); + + item->shading = G_DESKTOP_BACKGROUND_SHADING_SOLID; + item->placement = G_DESKTOP_BACKGROUND_STYLE_SCALED; + item->primary_color = g_strdup ("#000000000000"); + item->secondary_color = g_strdup ("#000000000000"); + item->needs_download = TRUE; + item->flags = 0; + item->modified = 0; +} + +static void +cc_background_item_finalize (GObject *object) +{ + CcBackgroundItem *item; + + g_return_if_fail (object != NULL); + g_return_if_fail (CC_IS_BACKGROUND_ITEM (object)); + + item = CC_BACKGROUND_ITEM (object); + + g_return_if_fail (item != NULL); + + g_clear_object (&item->cached_thumbnail.thumbnail); + g_clear_object (&item->cached_thumbnail_dark.thumbnail); + g_free (item->name); + g_free (item->uri); + g_free (item->primary_color); + g_free (item->secondary_color); + g_free (item->mime_type); + g_free (item->size); + g_free (item->source_url); + g_free (item->source_xml); + + g_clear_object (&item->bg); + g_clear_object (&item->bg_dark); + + G_OBJECT_CLASS (cc_background_item_parent_class)->finalize (object); +} + +CcBackgroundItem * +cc_background_item_new (const char *uri) +{ + GObject *object; + + object = g_object_new (CC_TYPE_BACKGROUND_ITEM, + "uri", uri, + NULL); + + return CC_BACKGROUND_ITEM (object); +} + +CcBackgroundItem * +cc_background_item_copy (CcBackgroundItem *item) +{ + CcBackgroundItem *ret; + + ret = cc_background_item_new (item->uri); + ret->name = g_strdup (item->name); + ret->size = g_strdup (item->size); + ret->placement = item->placement; + ret->shading = item->shading; + ret->primary_color = g_strdup (item->primary_color); + ret->secondary_color = g_strdup (item->secondary_color); + ret->source_url = g_strdup (item->source_url); + ret->source_xml = g_strdup (item->source_xml); + ret->is_deleted = item->is_deleted; + ret->needs_download = item->needs_download; + ret->flags = item->flags; + + return ret; +} + +static const char * +flags_to_str (CcBackgroundItemFlags flag) +{ + GFlagsClass *fclass; + GFlagsValue *value; + + fclass = G_FLAGS_CLASS (g_type_class_peek (G_DESKTOP_TYPE_BACKGROUND_ITEM_FLAGS)); + value = g_flags_get_first_value (fclass, flag); + + g_assert (value); + + return value->value_nick; +} + +static const char * +enum_to_str (GType type, + int v) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (type)); + value = g_enum_get_value (eclass, v); + + g_assert (value); + + return value->value_nick; +} + +void +cc_background_item_dump (CcBackgroundItem *item) +{ + g_autoptr(GString) flags = NULL; + int i; + + g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); + + g_debug ("name:\t\t\t%s", item->name); + g_debug ("URI:\t\t\t%s", item->uri ? item->uri : "NULL"); + if (item->size) + g_debug ("size:\t\t\t'%s'", item->size); + flags = g_string_new (NULL); + for (i = 0; i < 5; i++) { + if (item->flags & (1 << i)) { + g_string_append (flags, flags_to_str (1 << i)); + g_string_append_c (flags, ' '); + } + } + if (flags->len == 0) + g_string_append (flags, "-none-"); + g_debug ("flags:\t\t\t%s", flags->str); + if (item->primary_color) + g_debug ("pcolor:\t\t\t%s", item->primary_color); + if (item->secondary_color) + g_debug ("scolor:\t\t\t%s", item->secondary_color); + g_debug ("placement:\t\t%s", enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, item->placement)); + g_debug ("shading:\t\t%s", enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, item->shading)); + if (item->source_url) + g_debug ("source URL:\t\t%s", item->source_url); + if (item->source_xml) + g_debug ("source XML:\t\t%s", item->source_xml); + g_debug ("deleted:\t\t%s", item->is_deleted ? "yes" : "no"); + if (item->mime_type) + g_debug ("mime-type:\t\t%s", item->mime_type); + g_debug ("dimensions:\t\t%d x %d", item->width, item->height); + g_debug ("modified: %"G_GUINT64_FORMAT, item->modified); + g_debug (" "); +} + +static gboolean +files_equal (const char *a, + const char *b) +{ + g_autoptr(GFile) file1 = NULL; + g_autoptr(GFile) file2 = NULL; + gboolean retval; + + if (a == NULL && + b == NULL) + return TRUE; + + if (a == NULL || + b == NULL) + return FALSE; + + file1 = g_file_new_for_commandline_arg (a); + file2 = g_file_new_for_commandline_arg (b); + if (g_file_equal (file1, file2) == FALSE) + retval = FALSE; + else + retval = TRUE; + + return retval; +} + +static gboolean +colors_equal (const char *a, + const char *b) +{ + GdkRGBA color1, color2; + + gdk_rgba_parse (&color1, a); + gdk_rgba_parse (&color2, b); + + return gdk_rgba_equal (&color1, &color2); +} + +gboolean +cc_background_item_compare (CcBackgroundItem *saved, + CcBackgroundItem *configured) +{ + CcBackgroundItemFlags flags; + + flags = saved->flags; + if (flags == 0) + return FALSE; + + if (flags & CC_BACKGROUND_ITEM_HAS_URI) { + if (files_equal (saved->uri, configured->uri) == FALSE) + return FALSE; + } + if (flags & CC_BACKGROUND_ITEM_HAS_URI_DARK) { + if (files_equal (saved->uri_dark, configured->uri_dark) == FALSE) + return FALSE; + } + if (flags & CC_BACKGROUND_ITEM_HAS_SHADING) { + if (saved->shading != configured->shading) + return FALSE; + } + if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT) { + if (saved->placement != configured->placement) + return FALSE; + } + if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR) { + if (colors_equal (saved->primary_color, + configured->primary_color) == FALSE) { + return FALSE; + } + } + if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR) { + if (colors_equal (saved->secondary_color, + configured->secondary_color) == FALSE) { + return FALSE; + } + } + + return TRUE; +} diff --git a/panels/background/cc-background-item.h b/panels/background/cc-background-item.h new file mode 100644 index 0000000..80fd5a2 --- /dev/null +++ b/panels/background/cc-background-item.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010-2011 Red Hat, Inc. + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <glib-object.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <libgnome-desktop/gnome-desktop-thumbnail.h> +#include <gdesktop-enums.h> +#include <gnome-bg/gnome-bg.h> + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_ITEM (cc_background_item_get_type ()) +G_DECLARE_FINAL_TYPE (CcBackgroundItem, cc_background_item, CC, BACKGROUND_ITEM, GObject) + +typedef enum { + CC_BACKGROUND_ITEM_HAS_SHADING = 1 << 0, + CC_BACKGROUND_ITEM_HAS_PLACEMENT = 1 << 1, + CC_BACKGROUND_ITEM_HAS_PCOLOR = 1 << 2, + CC_BACKGROUND_ITEM_HAS_SCOLOR = 1 << 3, + CC_BACKGROUND_ITEM_HAS_URI = 1 << 4, + CC_BACKGROUND_ITEM_HAS_URI_DARK = 1 << 5 +} CcBackgroundItemFlags; + +#define CC_BACKGROUND_ITEM_HAS_ALL (CC_BACKGROUND_ITEM_HAS_SHADING & \ + CC_BACKGROUND_ITEM_HAS_PLACEMENT & \ + CC_BACKGROUND_ITEM_HAS_PCOLOR & \ + CC_BACKGROUND_ITEM_HAS_SCOLOR & \ + CC_BACKGROUND_ITEM_HAS_FNAME) + +CcBackgroundItem * cc_background_item_new (const char *uri); +CcBackgroundItem * cc_background_item_copy (CcBackgroundItem *item); +gboolean cc_background_item_load (CcBackgroundItem *item, + GFileInfo *info); +gboolean cc_background_item_changes_with_time (CcBackgroundItem *item); +gboolean cc_background_item_has_dark_version (CcBackgroundItem *item); + +GdkPixbuf * cc_background_item_get_thumbnail (CcBackgroundItem *item, + GnomeDesktopThumbnailFactory *thumbs, + int width, + int height, + int scale_factor, + gboolean dark); +GdkPixbuf * cc_background_item_get_frame_thumbnail (CcBackgroundItem *item, + GnomeDesktopThumbnailFactory *thumbs, + int width, + int height, + int scale_factor, + int frame, + gboolean force_size, + gboolean dark); + +GDesktopBackgroundStyle cc_background_item_get_placement (CcBackgroundItem *item); +GDesktopBackgroundShading cc_background_item_get_shading (CcBackgroundItem *item); +const char * cc_background_item_get_uri (CcBackgroundItem *item); +const char * cc_background_item_get_uri_dark (CcBackgroundItem *item); +const char * cc_background_item_get_source_url (CcBackgroundItem *item); +const char * cc_background_item_get_source_xml (CcBackgroundItem *item); +CcBackgroundItemFlags cc_background_item_get_flags (CcBackgroundItem *item); +const char * cc_background_item_get_pcolor (CcBackgroundItem *item); +const char * cc_background_item_get_scolor (CcBackgroundItem *item); +const char * cc_background_item_get_name (CcBackgroundItem *item); +const char * cc_background_item_get_size (CcBackgroundItem *item); +gboolean cc_background_item_get_needs_download (CcBackgroundItem *item); +guint64 cc_background_item_get_modified (CcBackgroundItem *item); + +gboolean cc_background_item_compare (CcBackgroundItem *saved, + CcBackgroundItem *configured); +void cc_background_item_dump (CcBackgroundItem *item); + +G_END_DECLS diff --git a/panels/background/cc-background-paintable.c b/panels/background/cc-background-paintable.c new file mode 100644 index 0000000..e510736 --- /dev/null +++ b/panels/background/cc-background-paintable.c @@ -0,0 +1,314 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2021 Alexander Mikhaylenko <alexm@gnome.org> + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include "cc-background-paintable.h" + +struct _CcBackgroundPaintable +{ + GObject parent_instance; + + BgSource *source; + CcBackgroundItem *item; + int scale_factor; + GtkTextDirection text_direction; + + GdkPaintable *texture; + GdkPaintable *dark_texture; +}; + +enum +{ + PROP_0, + PROP_SOURCE, + PROP_ITEM, + PROP_SCALE_FACTOR, + PROP_TEXT_DIRECTION, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void cc_background_paintable_paintable_init (GdkPaintableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CcBackgroundPaintable, cc_background_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + cc_background_paintable_paintable_init)) + +static void +update_cache (CcBackgroundPaintable *self) +{ + g_autoptr (GdkPixbuf) pixbuf = NULL; + GnomeDesktopThumbnailFactory *factory; + int width, height; + + g_clear_object (&self->texture); + g_clear_object (&self->dark_texture); + + factory = bg_source_get_thumbnail_factory (self->source); + width = bg_source_get_thumbnail_width (self->source); + height = bg_source_get_thumbnail_height (self->source); + + pixbuf = cc_background_item_get_thumbnail (self->item, + factory, + width, + height, + self->scale_factor, + FALSE); + + self->texture = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf)); + + if (cc_background_item_has_dark_version (self->item)) + { + g_autoptr (GdkPixbuf) dark_pixbuf = NULL; + + dark_pixbuf = cc_background_item_get_thumbnail (self->item, + factory, + width, + height, + self->scale_factor, + TRUE); + self->dark_texture = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (dark_pixbuf)); + } + + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); +} + +static void +cc_background_paintable_dispose (GObject *object) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object); + + g_clear_object (&self->item); + g_clear_object (&self->source); + g_clear_object (&self->texture); + g_clear_object (&self->dark_texture); + + G_OBJECT_CLASS (cc_background_paintable_parent_class)->dispose (object); +} + +static void +cc_background_paintable_constructed (GObject *object) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object); + + G_OBJECT_CLASS (cc_background_paintable_parent_class)->constructed (object); + + update_cache (self); +} + +static void +cc_background_paintable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object); + + switch (prop_id) + { + case PROP_SOURCE: + g_value_set_object (value, self->source); + break; + + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + case PROP_SCALE_FACTOR: + g_value_set_int (value, self->scale_factor); + break; + + case PROP_TEXT_DIRECTION: + g_value_set_enum (value, self->text_direction); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_background_paintable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object); + + switch (prop_id) + { + case PROP_SOURCE: + g_set_object (&self->source, g_value_get_object (value)); + break; + + case PROP_ITEM: + g_set_object (&self->item, g_value_get_object (value)); + break; + + case PROP_SCALE_FACTOR: + self->scale_factor = g_value_get_int (value); + update_cache (self); + break; + + case PROP_TEXT_DIRECTION: + self->text_direction = g_value_get_enum (value); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_background_paintable_class_init (CcBackgroundPaintableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = cc_background_paintable_dispose; + object_class->constructed = cc_background_paintable_constructed; + object_class->get_property = cc_background_paintable_get_property; + object_class->set_property = cc_background_paintable_set_property; + + properties[PROP_SOURCE] = + g_param_spec_object ("source", + "Source", + "Source", + BG_TYPE_SOURCE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ITEM] = + g_param_spec_object ("item", + "Item", + "Item", + CC_TYPE_BACKGROUND_ITEM, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_SCALE_FACTOR] = + g_param_spec_int ("scale-factor", + "Scale Factor", + "Scale Factor", + 1, G_MAXINT, 1, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_TEXT_DIRECTION] = + g_param_spec_enum ("text-direction", + "Text Direction", + "Text Direction", + GTK_TYPE_TEXT_DIRECTION, + GTK_TEXT_DIR_LTR, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +cc_background_paintable_init (CcBackgroundPaintable *self) +{ + self->scale_factor = 1; + self->text_direction = GTK_TEXT_DIR_LTR; +} + +static void +cc_background_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable); + gboolean is_rtl; + + if (!self->dark_texture) + { + gdk_paintable_snapshot (self->texture, snapshot, width, height); + return; + } + + is_rtl = self->text_direction == GTK_TEXT_DIR_RTL; + + gtk_snapshot_push_clip (GTK_SNAPSHOT (snapshot), + &GRAPHENE_RECT_INIT (is_rtl ? width / 2.0f : 0.0f, + 0.0f, + width / 2.0f, + height)); + gdk_paintable_snapshot (self->texture, snapshot, width, height); + gtk_snapshot_pop (GTK_SNAPSHOT (snapshot)); + + gtk_snapshot_push_clip (GTK_SNAPSHOT (snapshot), + &GRAPHENE_RECT_INIT (is_rtl ? 0.0f : width / 2.0f, + 0.0f, + width / 2.0f, + height)); + gdk_paintable_snapshot (self->dark_texture, snapshot, width, height); + gtk_snapshot_pop (GTK_SNAPSHOT (snapshot)); +} + +static int +cc_background_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable); + + return gdk_paintable_get_intrinsic_width (self->texture) / self->scale_factor; +} + +static int +cc_background_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable); + + return gdk_paintable_get_intrinsic_height (self->texture) / self->scale_factor; +} + +static double +cc_background_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) +{ + CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable); + + return gdk_paintable_get_intrinsic_aspect_ratio (self->texture); +} + +static void +cc_background_paintable_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = cc_background_paintable_snapshot; + iface->get_intrinsic_width = cc_background_paintable_get_intrinsic_width; + iface->get_intrinsic_height = cc_background_paintable_get_intrinsic_height; + iface->get_intrinsic_aspect_ratio = cc_background_paintable_get_intrinsic_aspect_ratio; +} + +CcBackgroundPaintable * +cc_background_paintable_new (BgSource *source, + CcBackgroundItem *item) +{ + g_return_val_if_fail (BG_IS_SOURCE (source), NULL); + g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL); + + return g_object_new (CC_TYPE_BACKGROUND_PAINTABLE, + "source", source, + "item", item, + NULL); +} diff --git a/panels/background/cc-background-paintable.h b/panels/background/cc-background-paintable.h new file mode 100644 index 0000000..75360dc --- /dev/null +++ b/panels/background/cc-background-paintable.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2021 Alexander Mikhaylenko <alexm@gnome.org> + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <gtk/gtk.h> + +#include "bg-source.h" +#include "cc-background-item.h" + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_PAINTABLE (cc_background_paintable_get_type ()) +G_DECLARE_FINAL_TYPE (CcBackgroundPaintable, cc_background_paintable, CC, BACKGROUND_PAINTABLE, GObject) + +CcBackgroundPaintable * cc_background_paintable_new (BgSource *source, + CcBackgroundItem *item); + +G_END_DECLS diff --git a/panels/background/cc-background-panel.c b/panels/background/cc-background-panel.c new file mode 100644 index 0000000..f748e9a --- /dev/null +++ b/panels/background/cc-background-panel.c @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#include <config.h> + +#include <string.h> +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <glib/gstdio.h> + +#include <gdesktop-enums.h> + +#include "cc-background-panel.h" + +#include "cc-background-chooser.h" +#include "cc-background-item.h" +#include "cc-background-preview.h" +#include "cc-background-resources.h" +#include "cc-background-xml.h" + +#define WP_PATH_ID "org.gnome.desktop.background" +#define WP_LOCK_PATH_ID "org.gnome.desktop.screensaver" +#define WP_URI_KEY "picture-uri" +#define WP_URI_DARK_KEY "picture-uri-dark" +#define WP_OPTIONS_KEY "picture-options" +#define WP_SHADING_KEY "color-shading-type" +#define WP_PCOLOR_KEY "primary-color" +#define WP_SCOLOR_KEY "secondary-color" + +#define INTERFACE_PATH_ID "org.gnome.desktop.interface" +#define INTERFACE_COLOR_SCHEME_KEY "color-scheme" + +struct _CcBackgroundPanel +{ + CcPanel parent_instance; + + GDBusConnection *connection; + + GSettings *settings; + GSettings *lock_settings; + GSettings *interface_settings; + + GnomeDesktopThumbnailFactory *thumb_factory; + GDBusProxy *proxy; + + CcBackgroundItem *current_background; + + CcBackgroundChooser *background_chooser; + CcBackgroundPreview *default_preview; + CcBackgroundPreview *dark_preview; + GtkToggleButton *default_toggle; + GtkToggleButton *dark_toggle; +}; + +CC_PANEL_REGISTER (CcBackgroundPanel, cc_background_panel) + +static void +load_custom_css (CcBackgroundPanel *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/background/preview.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +static void +reload_color_scheme_toggles (CcBackgroundPanel *self) +{ + GDesktopColorScheme scheme; + + scheme = g_settings_get_enum (self->interface_settings, INTERFACE_COLOR_SCHEME_KEY); + + if (scheme == G_DESKTOP_COLOR_SCHEME_DEFAULT) + { + gtk_toggle_button_set_active (self->default_toggle, TRUE); + } + else if (scheme == G_DESKTOP_COLOR_SCHEME_PREFER_DARK) + { + gtk_toggle_button_set_active (self->dark_toggle, TRUE); + } + else + { + gtk_toggle_button_set_active (self->default_toggle, FALSE); + gtk_toggle_button_set_active (self->dark_toggle, FALSE); + } +} + +static void +transition_screen (CcBackgroundPanel *self) +{ + g_autoptr (GError) error = NULL; + + if (!self->proxy) + return; + + g_dbus_proxy_call_sync (self->proxy, + "ScreenTransition", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error) + g_warning ("Couldn't transition screen: %s", error->message); +} + +static void +set_color_scheme (CcBackgroundPanel *self, + GDesktopColorScheme color_scheme) +{ + GDesktopColorScheme scheme; + + scheme = g_settings_get_enum (self->interface_settings, + INTERFACE_COLOR_SCHEME_KEY); + + /* We have to check the equality manually to avoid starting an unnecessary + * screen transition */ + if (color_scheme == scheme) + return; + + transition_screen (self); + + g_settings_set_enum (self->interface_settings, + INTERFACE_COLOR_SCHEME_KEY, + color_scheme); +} + +/* Color schemes */ + +static void +on_color_scheme_toggle_active_cb (CcBackgroundPanel *self) +{ + if (gtk_toggle_button_get_active (self->default_toggle)) + set_color_scheme (self, G_DESKTOP_COLOR_SCHEME_DEFAULT); + else if (gtk_toggle_button_get_active (self->dark_toggle)) + set_color_scheme (self, G_DESKTOP_COLOR_SCHEME_PREFER_DARK); +} + +static void +got_transition_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(GError) error = NULL; + CcBackgroundPanel *self = data; + + self->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (self->proxy == NULL) + { + g_warning ("Error creating proxy: %s", error->message); + return; + } +} + +/* Background */ + +static void +update_preview (CcBackgroundPanel *panel) +{ + CcBackgroundItem *current_background; + + current_background = panel->current_background; + cc_background_preview_set_item (panel->default_preview, current_background); + cc_background_preview_set_item (panel->dark_preview, current_background); +} + +static gchar * +get_save_path (void) +{ + return g_build_filename (g_get_user_config_dir (), + "gnome-control-center", + "backgrounds", + "last-edited.xml", + NULL); +} + +static void +reload_current_bg (CcBackgroundPanel *panel) +{ + g_autoptr(CcBackgroundItem) saved = NULL; + CcBackgroundItem *configured; + GSettings *settings = NULL; + g_autofree gchar *uri = NULL; + g_autofree gchar *dark_uri = NULL; + g_autofree gchar *pcolor = NULL; + g_autofree gchar *scolor = NULL; + + /* Load the saved configuration */ + uri = get_save_path (); + saved = cc_background_xml_get_item (uri); + + /* initalise the current background information from settings */ + settings = panel->settings; + uri = g_settings_get_string (settings, WP_URI_KEY); + if (uri && *uri == '\0') + g_clear_pointer (&uri, g_free); + + + configured = cc_background_item_new (uri); + + dark_uri = g_settings_get_string (settings, WP_URI_DARK_KEY); + pcolor = g_settings_get_string (settings, WP_PCOLOR_KEY); + scolor = g_settings_get_string (settings, WP_SCOLOR_KEY); + g_object_set (G_OBJECT (configured), + "name", _("Current background"), + "uri-dark", dark_uri, + "placement", g_settings_get_enum (settings, WP_OPTIONS_KEY), + "shading", g_settings_get_enum (settings, WP_SHADING_KEY), + "primary-color", pcolor, + "secondary-color", scolor, + NULL); + + if (saved != NULL && cc_background_item_compare (saved, configured)) + { + CcBackgroundItemFlags flags; + flags = cc_background_item_get_flags (saved); + /* Special case for colours */ + if (cc_background_item_get_placement (saved) == G_DESKTOP_BACKGROUND_STYLE_NONE) + flags &=~ (CC_BACKGROUND_ITEM_HAS_PCOLOR | CC_BACKGROUND_ITEM_HAS_SCOLOR); + g_object_set (G_OBJECT (configured), + "name", cc_background_item_get_name (saved), + "flags", flags, + "source-url", cc_background_item_get_source_url (saved), + "source-xml", cc_background_item_get_source_xml (saved), + NULL); + } + + g_clear_object (&panel->current_background); + panel->current_background = configured; + cc_background_item_load (configured, NULL); +} + +static gboolean +create_save_dir (void) +{ + g_autofree char *path = NULL; + + path = g_build_filename (g_get_user_config_dir (), + "gnome-control-center", + "backgrounds", + NULL); + if (g_mkdir_with_parents (path, USER_DIR_MODE) < 0) + { + g_warning ("Failed to create directory '%s'", path); + return FALSE; + } + return TRUE; +} + +static void +set_background (CcBackgroundPanel *panel, + GSettings *settings, + CcBackgroundItem *item, + gboolean set_dark) +{ + GDesktopBackgroundStyle style; + CcBackgroundItemFlags flags; + g_autofree gchar *filename = NULL; + const char *uri; + + if (item == NULL) + return; + + uri = cc_background_item_get_uri (item); + flags = cc_background_item_get_flags (item); + + g_settings_set_string (settings, WP_URI_KEY, uri); + + if (set_dark) + { + const char *uri_dark; + + uri_dark = cc_background_item_get_uri_dark (item); + + if (uri_dark && uri_dark[0]) + g_settings_set_string (settings, WP_URI_DARK_KEY, uri_dark); + else + g_settings_set_string (settings, WP_URI_DARK_KEY, uri); + } + + /* Also set the placement if we have a URI and the previous value was none */ + if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT) + { + g_settings_set_enum (settings, WP_OPTIONS_KEY, cc_background_item_get_placement (item)); + } + else if (uri != NULL) + { + style = g_settings_get_enum (settings, WP_OPTIONS_KEY); + if (style == G_DESKTOP_BACKGROUND_STYLE_NONE) + g_settings_set_enum (settings, WP_OPTIONS_KEY, cc_background_item_get_placement (item)); + } + + if (flags & CC_BACKGROUND_ITEM_HAS_SHADING) + g_settings_set_enum (settings, WP_SHADING_KEY, cc_background_item_get_shading (item)); + + g_settings_set_string (settings, WP_PCOLOR_KEY, cc_background_item_get_pcolor (item)); + g_settings_set_string (settings, WP_SCOLOR_KEY, cc_background_item_get_scolor (item)); + + /* Apply all changes */ + g_settings_apply (settings); + + /* Save the source XML if there is one */ + filename = get_save_path (); + if (create_save_dir ()) + cc_background_xml_save (panel->current_background, filename); +} + +static void +on_chooser_background_chosen_cb (CcBackgroundPanel *self, + CcBackgroundItem *item) +{ + set_background (self, self->settings, item, TRUE); + set_background (self, self->lock_settings, item, FALSE); +} + +static void +on_add_picture_button_clicked_cb (CcBackgroundPanel *self) +{ + cc_background_chooser_select_file (self->background_chooser); +} + +static const char * +cc_background_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/look-background"; +} + +static void +cc_background_panel_dispose (GObject *object) +{ + CcBackgroundPanel *panel = CC_BACKGROUND_PANEL (object); + + g_clear_object (&panel->settings); + g_clear_object (&panel->lock_settings); + g_clear_object (&panel->interface_settings); + g_clear_object (&panel->thumb_factory); + g_clear_object (&panel->proxy); + + G_OBJECT_CLASS (cc_background_panel_parent_class)->dispose (object); +} + +static void +cc_background_panel_finalize (GObject *object) +{ + CcBackgroundPanel *panel = CC_BACKGROUND_PANEL (object); + + g_clear_object (&panel->current_background); + + G_OBJECT_CLASS (cc_background_panel_parent_class)->finalize (object); +} + +static void +cc_background_panel_class_init (CcBackgroundPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + g_type_ensure (CC_TYPE_BACKGROUND_CHOOSER); + g_type_ensure (CC_TYPE_BACKGROUND_PREVIEW); + + panel_class->get_help_uri = cc_background_panel_get_help_uri; + + object_class->dispose = cc_background_panel_dispose; + object_class->finalize = cc_background_panel_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, background_chooser); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, default_preview); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, dark_preview); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, default_toggle); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, dark_toggle); + + gtk_widget_class_bind_template_callback (widget_class, on_color_scheme_toggle_active_cb); + gtk_widget_class_bind_template_callback (widget_class, on_chooser_background_chosen_cb); + gtk_widget_class_bind_template_callback (widget_class, on_add_picture_button_clicked_cb); +} + +static void +on_settings_changed (CcBackgroundPanel *panel) +{ + reload_current_bg (panel); + update_preview (panel); +} + +static void +cc_background_panel_init (CcBackgroundPanel *panel) +{ + g_resources_register (cc_background_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (panel)); + + panel->connection = g_application_get_dbus_connection (g_application_get_default ()); + + panel->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); + + panel->settings = g_settings_new (WP_PATH_ID); + g_settings_delay (panel->settings); + + panel->lock_settings = g_settings_new (WP_LOCK_PATH_ID); + g_settings_delay (panel->lock_settings); + + panel->interface_settings = g_settings_new (INTERFACE_PATH_ID); + + /* Load the background */ + reload_current_bg (panel); + update_preview (panel); + + /* Background settings */ + g_signal_connect_object (panel->settings, "changed", G_CALLBACK (on_settings_changed), panel, G_CONNECT_SWAPPED); + + /* Interface settings */ + reload_color_scheme_toggles (panel); + + g_signal_connect_object (panel->interface_settings, + "changed::" INTERFACE_COLOR_SCHEME_KEY, + G_CALLBACK (reload_color_scheme_toggles), + panel, + G_CONNECT_SWAPPED); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Shell", + "/org/gnome/Shell", + "org.gnome.Shell", + NULL, + got_transition_proxy_cb, + panel); + + load_custom_css (panel); +} diff --git a/panels/background/cc-background-panel.h b/panels/background/cc-background-panel.h new file mode 100644 index 0000000..abc894a --- /dev/null +++ b/panels/background/cc-background-panel.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_PANEL (cc_background_panel_get_type ()) +G_DECLARE_FINAL_TYPE (CcBackgroundPanel, cc_background_panel, CC, BACKGROUND_PANEL, CcPanel) + +G_END_DECLS diff --git a/panels/background/cc-background-panel.ui b/panels/background/cc-background-panel.ui new file mode 100644 index 0000000..33a8638 --- /dev/null +++ b/panels/background/cc-background-panel.ui @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcBackgroundPanel" parent="CcPanel"> + <child type="content"> + <object class="AdwPreferencesPage"> + + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Style</property> + + <child> + <object class="AdwPreferencesRow"> + <property name="activatable">False</property> + <property name="focusable">False</property> + <child> + <object class="AdwClamp"> + <property name="maximum_size">400</property> + <property name="tightening_threshold">300</property> + <child> + <object class="GtkGrid"> + <property name="column-homogeneous">True</property> + <property name="column-spacing">24</property> + <property name="row-spacing">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">18</property> + <property name="margin-bottom">12</property> + <property name="hexpand">True</property> + <child> + <object class="GtkToggleButton" id="default_toggle"> + <accessibility> + <relation name="labelled-by">default_label</relation> + </accessibility> + <signal name="notify::active" handler="on_color_scheme_toggle_active_cb" swapped="true"/> + <child> + <object class="CcBackgroundPreview" id="default_preview"/> + </child> + <style> + <class name="background-preview-button"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="default_label"> + <property name="label" translatable="yes">Default</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkToggleButton" id="dark_toggle"> + <property name="group">default_toggle</property> + <accessibility> + <relation name="labelled-by">dark_label</relation> + </accessibility> + <signal name="notify::active" handler="on_color_scheme_toggle_active_cb" swapped="true"/> + <child> + <object class="CcBackgroundPreview" id="dark_preview"> + <property name="is-dark">True</property> + </object> + </child> + <style> + <class name="background-preview-button"/> + </style> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="dark_label"> + <property name="label" translatable="yes">Dark</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + + </object> + </child> + + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Background</property> + <property name="header-suffix"> + <object class="GtkButton"> + <child> + <object class="AdwButtonContent"> + <property name="icon-name">list-add-symbolic</property> + <property name="label" translatable="yes">Add Picture…</property> + </object> + </child> + <signal name="clicked" handler="on_add_picture_button_clicked_cb" object="CcBackgroundPanel" swapped="yes" /> + <style> + <class name="flat"/> + </style> + </object> + </property> + + <child> + <object class="AdwBin"> + <style> + <class name="card"/> + </style> + <child> + <object class="CcBackgroundChooser" id="background_chooser"> + <property name="hexpand">True</property> + <signal name="background-chosen" handler="on_chooser_background_chosen_cb" object="CcBackgroundPanel" swapped="yes" /> + </object> + </child> + </object> + </child> + + </object> + </child> + + </object> + </child> + </template> +</interface> diff --git a/panels/background/cc-background-preview.c b/panels/background/cc-background-preview.c new file mode 100644 index 0000000..428c44f --- /dev/null +++ b/panels/background/cc-background-preview.c @@ -0,0 +1,351 @@ +/* cc-background-preview.c + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libgnome-desktop/gnome-desktop-thumbnail.h> + +#include "cc-background-preview.h" + +struct _CcBackgroundPreview +{ + GtkWidget parent; + + GtkWidget *drawing_area; + GtkWidget *light_dark_window; + GtkWidget *dark_window; + + GnomeDesktopThumbnailFactory *thumbnail_factory; + + gboolean is_dark; + CcBackgroundItem *item; +}; + +G_DEFINE_TYPE (CcBackgroundPreview, cc_background_preview, GTK_TYPE_WIDGET) + +enum +{ + PROP_0, + PROP_IS_DARK, + PROP_ITEM, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +/* Callbacks */ + +static void +draw_preview_func (GtkDrawingArea *drawing_area, + cairo_t *cr, + gint width, + gint height, + gpointer user_data) +{ + CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (user_data); + g_autoptr(GdkPixbuf) pixbuf = NULL; + gint scale_factor; + + if (!self->item) + return; + + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (drawing_area)); + pixbuf = cc_background_item_get_frame_thumbnail (self->item, + self->thumbnail_factory, + width, + height, + scale_factor, + 0, + TRUE, + self->is_dark && + cc_background_item_has_dark_version (self->item)); + + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); +} + +/* GObject overrides */ + +static void +cc_background_preview_dispose (GObject *object) +{ + CcBackgroundPreview *self = (CcBackgroundPreview *)object; + + g_clear_pointer (&self->drawing_area, gtk_widget_unparent); + g_clear_pointer (&self->light_dark_window, gtk_widget_unparent); + g_clear_pointer (&self->dark_window, gtk_widget_unparent); + + G_OBJECT_CLASS (cc_background_preview_parent_class)->dispose (object); +} + +static void +cc_background_preview_finalize (GObject *object) +{ + CcBackgroundPreview *self = (CcBackgroundPreview *)object; + + g_clear_object (&self->item); + g_clear_object (&self->thumbnail_factory); + + G_OBJECT_CLASS (cc_background_preview_parent_class)->finalize (object); +} + +static void +set_is_dark (CcBackgroundPreview *self, + gboolean is_dark) +{ + self->is_dark = is_dark; + + if (self->is_dark) + { + gtk_widget_add_css_class (self->light_dark_window, "dark"); + gtk_widget_remove_css_class (self->light_dark_window, "light"); + } + else + { + gtk_widget_add_css_class (self->light_dark_window, "light"); + gtk_widget_remove_css_class (self->light_dark_window, "dark"); + } +} + +static void +cc_background_preview_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (object); + + switch (prop_id) + { + case PROP_IS_DARK: + g_value_set_boolean (value, self->is_dark); + break; + + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_background_preview_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (object); + + switch (prop_id) + { + case PROP_IS_DARK: + set_is_dark (self, g_value_get_boolean (value)); + break; + + case PROP_ITEM: + cc_background_preview_set_item (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GtkSizeRequestMode +cc_background_preview_get_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +get_primary_monitor_geometry (int *width, int *height) +{ + GdkDisplay *display; + GListModel *monitors; + + display = gdk_display_get_default (); + + monitors = gdk_display_get_monitors (display); + if (monitors) + { + g_autoptr(GdkMonitor) primary_monitor = NULL; + GdkRectangle monitor_layout; + + primary_monitor = g_list_model_get_item (monitors, 0); + gdk_monitor_get_geometry (primary_monitor, &monitor_layout); + if (width) + *width = monitor_layout.width; + if (height) + *height = monitor_layout.height; + + return; + } + + if (width) + *width = 1920; + if (height) + *height = 1080; +} + +static void +cc_background_preview_measure (GtkWidget *widget, + GtkOrientation orientation, + gint for_size, + gint *minimum, + gint *natural, + gint *minimum_baseline, + gint *natural_baseline) +{ + GtkWidget *child; + int width; + + get_primary_monitor_geometry (&width, NULL); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + *natural = width; + else if (for_size < 0) + *natural = 0; + else + *natural = floor ((double) for_size * 0.75); /* 4:3 aspect ratio */ + + if (orientation == GTK_ORIENTATION_VERTICAL) + *minimum = *natural; + else + *minimum = 0; + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + int child_min, child_nat; + + gtk_widget_measure (child, orientation, for_size, + &child_min, &child_nat, NULL, NULL); + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + } +} + +static void +cc_background_preview_size_allocate (GtkWidget *widget, + gint width, + gint height, + gint baseline) +{ + CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (widget); + int window_width, window_height, margin_x, margin_y; + int opposite_margin_x, opposite_margin_y; + GskTransform *front_transform, *back_transform; + gboolean is_rtl; + + window_width = ceil (width * 0.5); + window_height = ceil (height * 0.5); + margin_x = floor (width * 0.15); + margin_y = floor (height * 0.15); + opposite_margin_x = width - window_width - margin_x; + opposite_margin_y = height - window_height - margin_y; + is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; + + front_transform = + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (is_rtl ? opposite_margin_x : margin_x, + opposite_margin_y)); + back_transform = + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (is_rtl ? margin_x : opposite_margin_x, + margin_y)); + + gtk_widget_allocate (self->drawing_area, width, height, baseline, NULL); + gtk_widget_allocate (self->dark_window, window_width, window_height, + baseline, back_transform); + gtk_widget_allocate (self->light_dark_window, window_width, window_height, + baseline, front_transform); +} + +static void +cc_background_preview_class_init (CcBackgroundPreviewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_background_preview_dispose; + object_class->finalize = cc_background_preview_finalize; + object_class->get_property = cc_background_preview_get_property; + object_class->set_property = cc_background_preview_set_property; + + widget_class->get_request_mode = cc_background_preview_get_request_mode; + widget_class->measure = cc_background_preview_measure; + widget_class->size_allocate = cc_background_preview_size_allocate; + + properties[PROP_IS_DARK] = g_param_spec_boolean ("is-dark", + "Is dark", + "Whether the preview is dark", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ITEM] = g_param_spec_object ("item", + "Item", + "Background item", + CC_TYPE_BACKGROUND_ITEM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-preview.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, drawing_area); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, light_dark_window); + gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, dark_window); + + gtk_widget_class_set_css_name (widget_class, "background-preview"); +} + +static void +cc_background_preview_init (CcBackgroundPreview *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); +} + +CcBackgroundItem* +cc_background_preview_get_item (CcBackgroundPreview *self) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_PREVIEW (self), NULL); + + return self->item; +} + +void +cc_background_preview_set_item (CcBackgroundPreview *self, + CcBackgroundItem *item) +{ + g_return_if_fail (CC_IS_BACKGROUND_PREVIEW (self)); + g_return_if_fail (CC_IS_BACKGROUND_ITEM (item)); + + if (!g_set_object (&self->item, item)) + return; + + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self->drawing_area), + draw_preview_func, self, NULL); + gtk_widget_queue_draw (self->drawing_area); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} diff --git a/panels/background/cc-background-preview.h b/panels/background/cc-background-preview.h new file mode 100644 index 0000000..40393c0 --- /dev/null +++ b/panels/background/cc-background-preview.h @@ -0,0 +1,36 @@ +/* cc-background-preview.h + * + * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 + * (at your option) 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +#include "cc-background-item.h" + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_PREVIEW (cc_background_preview_get_type()) +G_DECLARE_FINAL_TYPE (CcBackgroundPreview, cc_background_preview, CC, BACKGROUND_PREVIEW, GtkWidget) + +CcBackgroundItem* cc_background_preview_get_item (CcBackgroundPreview *self); +void cc_background_preview_set_item (CcBackgroundPreview *self, + CcBackgroundItem *item); + +G_END_DECLS diff --git a/panels/background/cc-background-preview.ui b/panels/background/cc-background-preview.ui new file mode 100644 index 0000000..4c4f573 --- /dev/null +++ b/panels/background/cc-background-preview.ui @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CcBackgroundPreview" parent="GtkWidget"> + <property name="overflow">hidden</property> + <property name="width-request">2</property> + <property name="height-request">2</property> + + <!-- Wallpaper --> + <child> + <object class="GtkDrawingArea" id="drawing_area"/> + </child> + + <!-- Always dark window --> + <child> + <object class="AdwBin" id="dark_window"> + <property name="overflow">hidden</property> + <style> + <class name="window"/> + <class name="back"/> + <class name="dark"/> + </style> + <child> + <object class="AdwBin"> + <style> + <class name="header-bar"/> + </style> + <property name="valign">start</property> + </object> + </child> + </object> + </child> + + <!-- Light/dark window --> + <child> + <object class="AdwBin" id="light_dark_window"> + <property name="overflow">hidden</property> + <style> + <class name="window"/> + <class name="front"/> + <class name="light"/> + </style> + <child> + <object class="AdwBin"> + <style> + <class name="header-bar"/> + </style> + <property name="valign">start</property> + </object> + </child> + </object> + </child> + + </template> +</interface> diff --git a/panels/background/cc-background-xml.c b/panels/background/cc-background-xml.c new file mode 100644 index 0000000..84f0a04 --- /dev/null +++ b/panels/background/cc-background-xml.c @@ -0,0 +1,669 @@ +/* + * Authors: Rodney Dawes <dobey@ximian.com> + * Bastien Nocera <hadess@hadess.net> + * + * Copyright 2003-2006 Novell, Inc. (www.novell.com) + * Copyright 2011 Red Hat Inc. + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + */ + +#include <gio/gio.h> +#include <string.h> +#include <libxml/parser.h> +#include <gdesktop-enums.h> + +#include "gdesktop-enums-types.h" +#include "cc-background-item.h" +#include "cc-background-xml.h" + +/* The number of items we signal as "added" before + * returning to the main loop */ +#define NUM_ITEMS_PER_BATCH 1 + +struct _CcBackgroundXml +{ + GObject parent_instance; + + GHashTable *wp_hash; + GAsyncQueue *item_added_queue; + guint item_added_id; + GSList *monitors; /* GSList of GFileMonitor */ +}; + +enum { + ADDED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (CcBackgroundXml, cc_background_xml, G_TYPE_OBJECT) + +static gboolean +cc_background_xml_get_bool (const xmlNode *parent, + const gchar *prop_name) +{ + xmlChar *prop; + gboolean ret_val = FALSE; + + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (prop_name != NULL, FALSE); + + prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name); + if (prop != NULL) { + if (!g_ascii_strcasecmp ((gchar *)prop, "true") || !g_ascii_strcasecmp ((gchar *)prop, "1")) { + ret_val = TRUE; + } else { + ret_val = FALSE; + } + xmlFree (prop); + } + + return ret_val; +} + +static struct { + int value; + const char *string; +} lookups[] = { + { G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL, "horizontal-gradient" }, + { G_DESKTOP_BACKGROUND_SHADING_VERTICAL, "vertical-gradient" }, +}; + +static int +enum_string_to_value (GType type, + const char *string) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (type)); + value = g_enum_get_value_by_nick (eclass, string); + + /* Here's a bit of hand-made parsing, bad bad */ + if (value == NULL) { + guint i; + for (i = 0; i < G_N_ELEMENTS (lookups); i++) { + if (g_str_equal (lookups[i].string, string)) + return lookups[i].value; + } + g_warning ("Unhandled value '%s' for enum '%s'", + string, G_FLAGS_CLASS_TYPE_NAME (eclass)); + return 0; + } + + return value->value; +} + +static gboolean +idle_emit (CcBackgroundXml *xml) +{ + gint i; + + g_async_queue_lock (xml->item_added_queue); + + for (i = 0; i < NUM_ITEMS_PER_BATCH; i++) { + g_autoptr(GObject) item = NULL; + + item = g_async_queue_try_pop_unlocked (xml->item_added_queue); + if (item == NULL) + break; + g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item); + } + + g_async_queue_unlock (xml->item_added_queue); + + if (g_async_queue_length (xml->item_added_queue) > 0) { + return TRUE; + } else { + xml->item_added_id = 0; + return FALSE; + } +} + +static void +emit_added_in_idle (CcBackgroundXml *xml, + GObject *object) +{ + g_async_queue_lock (xml->item_added_queue); + g_async_queue_push_unlocked (xml->item_added_queue, object); + if (xml->item_added_id == 0) + xml->item_added_id = g_idle_add ((GSourceFunc) idle_emit, xml); + g_async_queue_unlock (xml->item_added_queue); +} + +#define NONE "(none)" +#define UNSET_FLAG(flag) G_STMT_START{ (flags&=~(flag)); }G_STMT_END +#define SET_FLAG(flag) G_STMT_START{ (flags|=flag); }G_STMT_END + +static gboolean +cc_background_xml_load_xml_internal (CcBackgroundXml *xml, + const gchar *filename, + gboolean in_thread) +{ + xmlDoc * wplist; + xmlNode * root, * list, * wpa; + xmlChar * nodelang; + const gchar * const * syslangs; + gint i; + gboolean retval; + + wplist = xmlParseFile (filename); + retval = FALSE; + + if (!wplist) + return retval; + + syslangs = g_get_language_names (); + + root = xmlDocGetRootElement (wplist); + + for (list = root->children; list != NULL; list = list->next) { + if (!strcmp ((gchar *)list->name, "wallpaper")) { + g_autoptr(CcBackgroundItem) item = NULL; + CcBackgroundItemFlags flags; + g_autofree gchar *uri = NULL; + g_autofree gchar *cname = NULL; + g_autofree gchar *id = NULL; + + flags = 0; + item = cc_background_item_new (NULL); + + g_object_set (G_OBJECT (item), + "is-deleted", cc_background_xml_get_bool (list, "deleted"), + "source-xml", filename, + NULL); + + for (wpa = list->children; wpa != NULL; wpa = wpa->next) { + if (wpa->type == XML_COMMENT_NODE) { + continue; + } else if (!strcmp ((gchar *)wpa->name, "filename")) { + if (wpa->last != NULL && wpa->last->content != NULL) { + gchar *content = g_strstrip ((gchar *)wpa->last->content); + g_autofree gchar *bg_uri = NULL; + + /* FIXME same rubbish as in other parts of the code */ + if (strcmp (content, NONE) == 0) { + bg_uri = NULL; + } else { + g_autoptr(GFile) file = NULL; + g_autofree gchar *dirname = NULL; + + dirname = g_path_get_dirname (filename); + file = g_file_new_for_commandline_arg_and_cwd (content, dirname); + bg_uri = g_file_get_uri (file); + } + SET_FLAG(CC_BACKGROUND_ITEM_HAS_URI); + g_object_set (G_OBJECT (item), "uri", bg_uri, NULL); + } else { + break; + } + } else if (!strcmp ((gchar *)wpa->name, "filename-dark")) { + if (wpa->last != NULL && wpa->last->content != NULL) { + gchar *content = g_strstrip ((gchar *)wpa->last->content); + g_autofree gchar *bg_uri = NULL; + + /* FIXME same rubbish as in other parts of the code */ + if (strcmp (content, NONE) == 0) { + bg_uri = NULL; + } else { + g_autoptr(GFile) file = NULL; + g_autofree gchar *dirname = NULL; + + dirname = g_path_get_dirname (filename); + file = g_file_new_for_commandline_arg_and_cwd (content, dirname); + bg_uri = g_file_get_uri (file); + } + SET_FLAG(CC_BACKGROUND_ITEM_HAS_URI_DARK); + g_object_set (G_OBJECT (item), "uri-dark", bg_uri, NULL); + } else { + break; + } + } else if (!strcmp ((gchar *)wpa->name, "name")) { + if (wpa->last != NULL && wpa->last->content != NULL) { + g_autofree gchar *name = NULL; + nodelang = xmlNodeGetLang (wpa->last); + + g_object_get (G_OBJECT (item), "name", &name, NULL); + + if (name == NULL && nodelang == NULL) { + g_free (cname); + cname = g_strdup (g_strstrip ((gchar *)wpa->last->content)); + g_object_set (G_OBJECT (item), "name", cname, NULL); + } else { + for (i = 0; syslangs[i] != NULL; i++) { + if (!strcmp (syslangs[i], (gchar *)nodelang)) { + g_object_set (G_OBJECT (item), "name", + g_strstrip ((gchar *)wpa->last->content), NULL); + break; + } + } + } + + xmlFree (nodelang); + } else { + break; + } + } else if (!strcmp ((gchar *)wpa->name, "options")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "placement", + enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, + g_strstrip ((gchar *)wpa->last->content)), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_PLACEMENT); + } + } else if (!strcmp ((gchar *)wpa->name, "shade_type")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "shading", + enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, + g_strstrip ((gchar *)wpa->last->content)), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_SHADING); + } + } else if (!strcmp ((gchar *)wpa->name, "pcolor")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "primary-color", + g_strstrip ((gchar *)wpa->last->content), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_PCOLOR); + } + } else if (!strcmp ((gchar *)wpa->name, "scolor")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "secondary-color", + g_strstrip ((gchar *)wpa->last->content), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_SCOLOR); + } + } else if (!strcmp ((gchar *)wpa->name, "source_url")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), + "source-url", g_strstrip ((gchar *)wpa->last->content), + "needs-download", FALSE, + NULL); + } + } else if (!strcmp ((gchar *)wpa->name, "text")) { + /* Do nothing here, libxml2 is being weird */ + } else { + g_debug ("Unknown Tag in %s: %s", filename, wpa->name); + } + } + + /* Check whether the target file exists */ + { + const char *uri; + + uri = cc_background_item_get_uri (item); + if (uri != NULL) + { + g_autoptr(GFile) file = NULL; + + file = g_file_new_for_uri (uri); + if (g_file_query_exists (file, NULL) == FALSE) + { + g_clear_pointer (&cname, g_free); + g_clear_object (&item); + continue; + } + } + } + + /* FIXME, this is a broken way of doing, + * need to use proper code here */ + uri = g_filename_to_uri (filename, NULL, NULL); + id = g_strdup_printf ("%s#%s", uri, cname); + + /* Make sure we don't already have this one and that filename exists */ + if (g_hash_table_lookup (xml->wp_hash, id) != NULL) { + continue; + } + + g_object_set (G_OBJECT (item), "flags", flags, NULL); + g_hash_table_insert (xml->wp_hash, + g_strdup (id), + g_object_ref (item)); + if (in_thread) + emit_added_in_idle (xml, g_object_ref (G_OBJECT (item))); + else + g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item); + retval = TRUE; + } + } + xmlFreeDoc (wplist); + + return retval; +} + +static void +gnome_wp_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + CcBackgroundXml *data) +{ + g_autofree gchar *filename = NULL; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + filename = g_file_get_path (file); + cc_background_xml_load_xml_internal (data, filename, FALSE); + break; + default: + break; + } +} + +static void +cc_background_xml_add_monitor (GFile *directory, + CcBackgroundXml *data) +{ + GFileMonitor *monitor; + g_autoptr(GError) error = NULL; + + monitor = g_file_monitor_directory (directory, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (error != NULL) { + g_autofree gchar *path = NULL; + + path = g_file_get_parse_name (directory); + g_warning ("Unable to monitor directory %s: %s", + path, error->message); + return; + } + + g_signal_connect (monitor, "changed", + G_CALLBACK (gnome_wp_file_changed), + data); + + data->monitors = g_slist_prepend (data->monitors, monitor); +} + +static void +cc_background_xml_load_from_dir (const gchar *path, + CcBackgroundXml *data, + gboolean in_thread) +{ + g_autoptr(GFile) directory = NULL; + g_autoptr(GFileEnumerator) enumerator = NULL; + g_autoptr(GError) error = NULL; + + if (!g_file_test (path, G_FILE_TEST_IS_DIR)) { + return; + } + + directory = g_file_new_for_path (path); + enumerator = g_file_enumerate_children (directory, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + if (error != NULL) { + g_warning ("Unable to check directory %s: %s", path, error->message); + return; + } + + while (TRUE) { + g_autoptr(GFileInfo) info = NULL; + const gchar *filename; + g_autofree gchar *fullpath = NULL; + + info = g_file_enumerator_next_file (enumerator, NULL, NULL); + if (info == NULL) { + g_file_enumerator_close (enumerator, NULL, NULL); + cc_background_xml_add_monitor (directory, data); + return; + } + + filename = g_file_info_get_name (info); + fullpath = g_build_filename (path, filename, NULL); + + cc_background_xml_load_xml_internal (data, fullpath, in_thread); + } +} + +static void +cc_background_xml_load_list (CcBackgroundXml *data, + gboolean in_thread) +{ + const char * const *system_data_dirs; + g_autofree gchar *datadir = NULL; + gint i; + + datadir = g_build_filename (g_get_user_data_dir (), + "gnome-background-properties", + NULL); + cc_background_xml_load_from_dir (datadir, data, in_thread); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_autofree gchar *sdatadir = NULL; + sdatadir = g_build_filename (system_data_dirs[i], + "gnome-background-properties", + NULL); + cc_background_xml_load_from_dir (sdatadir, data, in_thread); + } +} + +gboolean +cc_background_xml_load_list_finish (CcBackgroundXml *xml, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, xml), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +load_list_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CcBackgroundXml *xml = CC_BACKGROUND_XML (source_object); + cc_background_xml_load_list (xml, TRUE); + g_task_return_boolean (task, TRUE); +} + +void +cc_background_xml_load_list_async (CcBackgroundXml *xml, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_BACKGROUND_XML (xml)); + + task = g_task_new (xml, cancellable, callback, user_data); + g_task_run_in_thread (task, load_list_thread); +} + +gboolean +cc_background_xml_load_xml (CcBackgroundXml *xml, + const gchar *filename) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_XML (xml), FALSE); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE) + return FALSE; + + return cc_background_xml_load_xml_internal (xml, filename, FALSE); +} + +static void +single_xml_added (CcBackgroundXml *xml, + CcBackgroundItem *item, + CcBackgroundItem **ret) +{ + g_assert (*ret == NULL); + *ret = g_object_ref (item); +} + +CcBackgroundItem * +cc_background_xml_get_item (const char *filename) +{ + g_autoptr(CcBackgroundXml) xml = NULL; + CcBackgroundItem *item = NULL; + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE) + return NULL; + + xml = cc_background_xml_new (); + g_signal_connect (G_OBJECT (xml), "added", + G_CALLBACK (single_xml_added), &item); + if (cc_background_xml_load_xml (xml, filename) == FALSE) + return NULL; + + return item; +} + +static const char * +enum_to_str (GType type, + int v) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (type)); + value = g_enum_get_value (eclass, v); + + g_assert (value); + + return value->value_nick; +} + +void +cc_background_xml_save (CcBackgroundItem *item, + const char *filename) +{ + xmlDoc *wp; + xmlNode *root, *wallpaper; + xmlNode *xml_item G_GNUC_UNUSED; + const char * none = "(none)"; + const char *placement_str, *shading_str; + g_autofree gchar *name = NULL; + g_autofree gchar *pcolor = NULL; + g_autofree gchar *scolor = NULL; + g_autofree gchar *uri = NULL; + g_autofree gchar *source_url = NULL; + CcBackgroundItemFlags flags; + GDesktopBackgroundStyle placement; + GDesktopBackgroundShading shading; + + xmlKeepBlanksDefault (0); + + wp = xmlNewDoc ((xmlChar *)"1.0"); + xmlCreateIntSubset (wp, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd"); + root = xmlNewNode (NULL, (xmlChar *)"wallpapers"); + xmlDocSetRootElement (wp, root); + + g_object_get (G_OBJECT (item), + "name", &name, + "uri", &uri, + "shading", &shading, + "placement", &placement, + "primary-color", &pcolor, + "secondary-color", &scolor, + "source-url", &source_url, + "flags", &flags, + NULL); + + placement_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, placement); + shading_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, shading); + + wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL); + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name); + if (flags & CC_BACKGROUND_ITEM_HAS_URI && + uri != NULL) + { + g_autoptr(GFile) file = NULL; + g_autofree gchar *fname = NULL; + + file = g_file_new_for_commandline_arg (uri); + fname = g_file_get_path (file); + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)fname); + } + else if (flags & CC_BACKGROUND_ITEM_HAS_URI) + { + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)none); + } + + if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)placement_str); + if (flags & CC_BACKGROUND_ITEM_HAS_SHADING) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shading_str); + if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor); + if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor); + if (source_url != NULL) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"source_url", (xmlChar *)source_url); + + xmlSaveFormatFile (filename, wp, 1); + xmlFreeDoc (wp); +} + +static void +cc_background_xml_finalize (GObject *object) +{ + CcBackgroundXml *xml; + + g_return_if_fail (object != NULL); + g_return_if_fail (CC_IS_BACKGROUND_XML (object)); + + xml = CC_BACKGROUND_XML (object); + + g_slist_free_full (xml->monitors, g_object_unref); + + g_clear_pointer (&xml->wp_hash, g_hash_table_destroy); + if (xml->item_added_id != 0) { + g_source_remove (xml->item_added_id); + xml->item_added_id = 0; + } + g_clear_pointer (&xml->item_added_queue, g_async_queue_unref); + + G_OBJECT_CLASS (cc_background_xml_parent_class)->finalize (object); +} + +static void +cc_background_xml_class_init (CcBackgroundXmlClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_background_xml_finalize; + + signals[ADDED] = g_signal_new ("added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, CC_TYPE_BACKGROUND_ITEM); +} + +static void +cc_background_xml_init (CcBackgroundXml *xml) +{ + xml->wp_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + xml->item_added_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref); +} + +CcBackgroundXml * +cc_background_xml_new (void) +{ + return CC_BACKGROUND_XML (g_object_new (CC_TYPE_BACKGROUND_XML, NULL)); +} diff --git a/panels/background/cc-background-xml.h b/panels/background/cc-background-xml.h new file mode 100644 index 0000000..3d2038c --- /dev/null +++ b/panels/background/cc-background-xml.h @@ -0,0 +1,46 @@ +/* + * Authors: Rodney Dawes <dobey@ximian.com> + * + * Copyright 2003-2012 Novell, Inc. (www.novell.com) + * + * 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 2 of the License, or + * (at your option) 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define CC_TYPE_BACKGROUND_XML (cc_background_xml_get_type ()) +G_DECLARE_FINAL_TYPE (CcBackgroundXml, cc_background_xml, CC, BACKGROUND_XML, GObject) + +CcBackgroundXml *cc_background_xml_new (void); + +void cc_background_xml_save (CcBackgroundItem *item, + const char *filename); + +CcBackgroundItem *cc_background_xml_get_item (const char *filename); +gboolean cc_background_xml_load_xml (CcBackgroundXml *data, + const char *filename); +void cc_background_xml_load_list_async (CcBackgroundXml *xml, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_background_xml_load_list_finish (CcBackgroundXml *xml, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/panels/background/gnome-background-panel.desktop.in.in b/panels/background/gnome-background-panel.desktop.in.in new file mode 100644 index 0000000..a6a044c --- /dev/null +++ b/panels/background/gnome-background-panel.desktop.in.in @@ -0,0 +1,14 @@ +[Desktop Entry] +Name=Appearance +Comment=Change your background image or the UI colors +Exec=gnome-control-center background +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=org.gnome.Settings-appearance-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-PersonalizationSettings; +OnlyShowIn=GNOME; +# Translators: Search terms to find the Appearance panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Background;Wallpaper;Screen;Desktop;Style;Light;Dark;Appearance; diff --git a/panels/background/icons/meson.build b/panels/background/icons/meson.build new file mode 100644 index 0000000..53082c1 --- /dev/null +++ b/panels/background/icons/meson.build @@ -0,0 +1,4 @@ +install_data( + 'scalable/org.gnome.Settings-appearance-symbolic.svg', + install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps') +) diff --git a/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg b/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg new file mode 100644 index 0000000..7190bbd --- /dev/null +++ b/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <g fill="#2e3436"> + <path d="m 3.011719 1 c -1.644531 0 -3.0000002 1.355469 -3.0000002 3 v 6 c 0 1.644531 1.3554692 3 3.0000002 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -6 c 0 -0.570312 -0.167969 -1.101562 -0.449219 -1.558594 l -1.550781 1.554688 v 6.003906 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570313 0 -1 -0.429688 -1 -1 v -6 c 0 -0.570312 0.429687 -1 1 -1 h 5.96875 l 2.007812 -2 z m 0 0"/> + <path d="m 11.011719 7 c 0 1.65625 -1.339844 3.007812 -3 3 h -3 v -3 c 0 -1.660156 1.34375 -3 3 -3 c 1.660156 0 3 1.339844 3 3 z m 0 0"/> + <path d="m 13.410156 0 l -3.46875 3.457031 c 0.683594 0.355469 1.234375 0.910157 1.589844 1.589844 l 0.171875 -0.171875 l 0.007813 0.007812 l 4.300781 -4.300781 v -0.582031 z m 0 0"/> + <path d="m 5.011719 14 c -1.105469 0 -2 0.894531 -2 2 h 10 c 0 -1.105469 -0.894531 -2 -2 -2 z m 0 0"/> + </g> +</svg> diff --git a/panels/background/meson.build b/panels/background/meson.build new file mode 100644 index 0000000..3634c47 --- /dev/null +++ b/panels/background/meson.build @@ -0,0 +1,99 @@ +panels_list += cappletname +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + +desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf +) + +i18n.merge_file( + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) + +install_data( + 'noise-texture-light.png', + install_dir: join_paths(control_center_pkgdatadir, 'pixmaps') +) + +common_sources = [] + +enums = 'gdesktop-enums-types' +enums_header = files( + gsettings_desktop_dep.get_pkgconfig_variable('prefix') + '/include/gsettings-desktop-schemas/gdesktop-enums.h', + 'cc-background-item.h' +) + +common_sources += gnome.mkenums( + enums + '.h', + sources: enums_header, + fhead: '#pragma once\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n', + fprod: '/* enumerations from "@filename@" */\n', + vhead: 'GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define G_DESKTOP_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n', + ftail: 'G_END_DECLS\n' +) + +common_sources += gnome.mkenums( + enums + '.c', + sources: enums_header, + fhead: '#include <gdesktop-enums.h>\n#include "gdesktop-enums-types.h"\n#include "cc-background-item.h"', + fprod: '\n/* enumerations from "@filename@" */', + vhead: 'GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {', + vprod: ' { @VALUENAME@, "@VALUENAME@", "@valuenick@" },', + vtail: ' { 0, NULL, NULL }\n };\n etype = g_@type@_register_static ("@EnumName@", values);\n }\n return etype;\n}\n' +) + +resource_data = files( + 'cc-background-chooser.ui', + 'cc-background-panel.ui', + 'cc-background-preview.ui', + 'preview.css', +) + +common_sources += gnome.compile_resources( + 'cc-@0@-resources'.format(cappletname), + cappletname + '.gresource.xml', + c_name: 'cc_' + cappletname, + dependencies: resource_data, + export: true +) + +sources = common_sources + files( + 'bg-colors-source.c', + 'bg-recent-source.c', + 'bg-source.c', + 'bg-wallpapers-source.c', + 'cc-background-chooser.c', + 'cc-background-item.c', + 'cc-background-paintable.c', + 'cc-background-panel.c', + 'cc-background-preview.c', + 'cc-background-xml.c', +) + +deps = common_deps + [ + gdk_pixbuf_dep, + gnome_bg_dep, + libxml_dep, + dependency('cairo-gobject'), +] + +cflags += [ + '-DDATADIR="@0@"'.format(control_center_datadir), + '-DGNOME_DESKTOP_USE_UNSTABLE_API' +] + +panels_libs += static_library( + cappletname, + sources: sources, + include_directories: top_inc, + dependencies: deps, + c_args: cflags, +) + +subdir('icons') diff --git a/panels/background/noise-texture-light.png b/panels/background/noise-texture-light.png Binary files differnew file mode 100644 index 0000000..f73293e --- /dev/null +++ b/panels/background/noise-texture-light.png diff --git a/panels/background/preview.css b/panels/background/preview.css new file mode 100644 index 0000000..e949734 --- /dev/null +++ b/panels/background/preview.css @@ -0,0 +1,96 @@ +background-preview { + border-radius: 6px; +} + +background-preview .window { + border-radius: 6px; + box-shadow: 0 1px 4px 1px alpha(black, 0.13), + 0 1px 10px 5px alpha(black, 0.09), + 0 3px 16px 8px alpha(black, 0.04), + 0 0 0 1px alpha(black, .05); +} + +background-preview .window .header-bar { + min-height: 15px; +} + +background-preview .window.light { + background-color: #fafafa; + color: alpha(black, .8); +} + +background-preview .window.light .header-bar { + box-shadow: inset 0 -1px alpha(black, .07); +} + +background-preview .window.front.light .header-bar { + background-color: #ebebeb; +} + +background-preview .window.dark { + background-color: #242424; + color: white; +} + +background-preview .window.dark .header-bar { + box-shadow: inset 0 -1px alpha(black, .36); +} + +background-preview .window.front.dark .header-bar { + background-color: #303030; +} + +.background-preview-button { + background: none; + border-radius: 9px; + padding: 3px; + box-shadow: none; + outline: none; +} + +.background-preview-button:checked { + box-shadow: 0 0 0 3px @accent_color; +} + +.background-preview-button:focus:focus-visible { + box-shadow: 0 0 0 3px alpha(@accent_color, .3); +} + +.background-preview-button:checked:focus:focus-visible { + box-shadow: 0 0 0 3px @accent_color, 0 0 0 6px alpha(@accent_color, .3); +} + +.background-flowbox > flowboxchild { + background: none; + border-radius: 9px; +} + +.background-thumbnail { + border-radius: 6px; +} + +.slideshow-icon { + color: white; + -gtk-icon-shadow: 0 1px 2px rgba(0, 0, 0, 0.33); + margin: 8px; +} + +.selected-check { + color: @accent_fg_color; + background: @accent_bg_color; + border-radius: 100px; + padding: 2px; + opacity: 0; + margin: 6px; +} + +flowboxchild:selected .selected-check { + opacity: 1; +} + +.remove-button { + padding: 2px; + min-width: 0; + min-height: 0; + margin: 6px; +} diff --git a/panels/background/slideshow-symbolic.svg b/panels/background/slideshow-symbolic.svg new file mode 100644 index 0000000..77350c3 --- /dev/null +++ b/panels/background/slideshow-symbolic.svg @@ -0,0 +1 @@ +<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M7.486.02A7.492 7.492 0 0 0 0 7.508a7.492 7.492 0 0 0 7.486 7.484 7.492 7.492 0 0 0 7.487-7.484A7.492 7.492 0 0 0 7.486.02zm0 1.972A5.508 5.508 0 0 1 13 7.508a5.508 5.508 0 0 1-5.514 5.512 5.508 5.508 0 0 1-5.513-5.512 5.508 5.508 0 0 1 5.513-5.516zm3.01 2.01a.5.5 0 0 0-.103.006.5.5 0 0 0-.25.154L7.486 6.818 5.83 5.162a.5.5 0 1 0-.687.688l2 2a.5.5 0 0 0 .687 0l3-3a.5.5 0 0 0-.334-.848z" style="fill:#000"/></svg>
\ No newline at end of file |