diff options
Diffstat (limited to 'panels/common')
33 files changed, 4829 insertions, 0 deletions
diff --git a/panels/common/cc-common-language.c b/panels/common/cc-common-language.c new file mode 100644 index 0000000..9357c3e --- /dev/null +++ b/panels/common/cc-common-language.c @@ -0,0 +1,304 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 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/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include <fontconfig/fontconfig.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +#include "cc-common-language.h" +#include "shell/cc-object-storage.h" + +static char *get_lang_for_user_object_path (const char *path); + +static gboolean +iter_for_language (GtkTreeModel *model, + const gchar *lang, + GtkTreeIter *iter, + gboolean region) +{ + g_autofree gchar *name = NULL; + + g_assert (gtk_tree_model_get_iter_first (model, iter)); + do { + g_autofree gchar *l = NULL; + gtk_tree_model_get (model, iter, LOCALE_COL, &l, -1); + if (g_strcmp0 (l, lang) == 0) + return TRUE; + } while (gtk_tree_model_iter_next (model, iter)); + + name = gnome_normalize_locale (lang); + if (name != NULL) { + g_autofree gchar *language = NULL; + + if (region) { + language = gnome_get_country_from_locale (name, NULL); + } + else { + language = gnome_get_language_from_locale (name, NULL); + } + + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), + iter, + -1, + LOCALE_COL, name, + DISPLAY_LOCALE_COL, language, + -1); + return TRUE; + } + + return FALSE; +} + +gboolean +cc_common_language_get_iter_for_language (GtkTreeModel *model, + const gchar *lang, + GtkTreeIter *iter) +{ + return iter_for_language (model, lang, iter, FALSE); +} + +gboolean +cc_common_language_has_font (const gchar *locale) +{ + const FcCharSet *charset; + FcPattern *pattern; + FcObjectSet *object_set; + FcFontSet *font_set; + g_autofree gchar *language_code = NULL; + gboolean is_displayable; + + is_displayable = FALSE; + pattern = NULL; + object_set = NULL; + font_set = NULL; + + if (!gnome_parse_locale (locale, &language_code, NULL, NULL, NULL)) + return FALSE; + + charset = FcLangGetCharSet ((FcChar8 *) language_code); + if (!charset) { + /* fontconfig does not know about this language */ + is_displayable = TRUE; + } + else { + /* see if any fonts support rendering it */ + pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL); + + if (pattern == NULL) + goto done; + + object_set = FcObjectSetCreate (); + + if (object_set == NULL) + goto done; + + font_set = FcFontList (NULL, pattern, object_set); + + if (font_set == NULL) + goto done; + + is_displayable = (font_set->nfont > 0); + } + + done: + if (font_set != NULL) + FcFontSetDestroy (font_set); + + if (object_set != NULL) + FcObjectSetDestroy (object_set); + + if (pattern != NULL) + FcPatternDestroy (pattern); + + return is_displayable; +} + +gchar * +cc_common_language_get_current_language (void) +{ + gchar *language; + g_autofree gchar *path = NULL; + const gchar *locale; + + path = g_strdup_printf ("/org/freedesktop/Accounts/User%d", getuid ()); + language = get_lang_for_user_object_path (path); + if (language != NULL && *language != '\0') + return language; + + locale = (const gchar *) setlocale (LC_MESSAGES, NULL); + if (locale) + language = gnome_normalize_locale (locale); + else + language = NULL; + + return language; +} + +static char * +get_lang_for_user_object_path (const char *path) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GDBusProxy) user = NULL; + g_autoptr(GVariant) props = NULL; + char *lang; + + user = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.Accounts", + path, + "org.freedesktop.Accounts.User", + NULL, + &error); + if (user == NULL) { + g_warning ("Failed to get proxy for user '%s': %s", + path, error->message); + return NULL; + } + + props = g_dbus_proxy_get_cached_property (user, "Language"); + if (props == NULL) + return NULL; + lang = g_variant_dup_string (props, NULL); + + return lang; +} + +/* + * Note that @lang needs to be formatted like the locale strings + * returned by gnome_get_all_locales(). + */ +static void +insert_language (GHashTable *ht, + const char *lang) +{ + g_autofree gchar *label_own_lang = NULL; + g_autofree gchar *label_current_lang = NULL; + g_autofree gchar *label_untranslated = NULL; + + label_own_lang = gnome_get_language_from_locale (lang, lang); + label_current_lang = gnome_get_language_from_locale (lang, NULL); + label_untranslated = gnome_get_language_from_locale (lang, "C"); + + /* We don't have a translation for the label in + * its own language? */ + if (g_strcmp0 (label_own_lang, label_untranslated) == 0) { + if (g_strcmp0 (label_current_lang, label_untranslated) == 0) + g_hash_table_insert (ht, g_strdup (lang), g_strdup (label_untranslated)); + else + g_hash_table_insert (ht, g_strdup (lang), g_strdup (label_current_lang)); + } else { + g_hash_table_insert (ht, g_strdup (lang), g_strdup (label_own_lang)); + } +} + +GHashTable * +cc_common_language_get_initial_languages (void) +{ + GHashTable *ht; + + ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + insert_language (ht, "en_US.UTF-8"); + insert_language (ht, "en_GB.UTF-8"); + insert_language (ht, "de_DE.UTF-8"); + insert_language (ht, "fr_FR.UTF-8"); + insert_language (ht, "es_ES.UTF-8"); + insert_language (ht, "zh_CN.UTF-8"); + insert_language (ht, "ja_JP.UTF-8"); + insert_language (ht, "ru_RU.UTF-8"); + insert_language (ht, "ar_EG.UTF-8"); + + return ht; +} + +static void +foreach_user_lang_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + GtkListStore *store = (GtkListStore *) user_data; + const char *locale = (const char *) key; + const char *display_locale = (const char *) value; + GtkTreeIter iter; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + LOCALE_COL, locale, + DISPLAY_LOCALE_COL, display_locale, + -1); +} + +void +cc_common_language_add_user_languages (GtkTreeModel *model) +{ + g_autofree gchar *name = NULL; + GtkTreeIter iter; + GtkListStore *store = GTK_LIST_STORE (model); + GHashTable *user_langs; + const char *display; + + gtk_list_store_clear (store); + + user_langs = cc_common_language_get_initial_languages (); + + /* Add the current locale first */ + name = cc_common_language_get_current_language (); + display = g_hash_table_lookup (user_langs, name); + if (!display) { + g_autofree gchar *language = NULL; + g_autofree gchar *country = NULL; + g_autofree gchar *codeset = NULL; + + gnome_parse_locale (name, &language, &country, &codeset, NULL); + + if (!codeset || !g_str_equal (codeset, "UTF-8")) + g_warning ("Current user locale codeset isn't UTF-8"); + + g_free (name); + name = g_strdup_printf ("%s_%s.UTF-8", language, country); + + insert_language (user_langs, name); + display = g_hash_table_lookup (user_langs, name); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, display, -1); + g_hash_table_remove (user_langs, name); + + /* The rest of the languages */ + g_hash_table_foreach (user_langs, (GHFunc) foreach_user_lang_cb, store); + + /* And now the "Other…" selection */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, LOCALE_COL, NULL, DISPLAY_LOCALE_COL, _("Other…"), -1); + + g_hash_table_destroy (user_langs); +} + diff --git a/panels/common/cc-common-language.h b/panels/common/cc-common-language.h new file mode 100644 index 0000000..1f578b7 --- /dev/null +++ b/panels/common/cc-common-language.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 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/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +enum { + LOCALE_COL, + DISPLAY_LOCALE_COL, + SEPARATOR_COL, + USER_LANGUAGE, + NUM_COLS +}; + +gboolean cc_common_language_get_iter_for_language (GtkTreeModel *model, + const gchar *lang, + GtkTreeIter *iter); +gboolean cc_common_language_get_iter_for_region (GtkTreeModel *model, + const gchar *lang, + GtkTreeIter *iter); +guint cc_common_language_add_available_languages (GtkListStore *store, + gboolean regions, + GHashTable *user_langs); +gboolean cc_common_language_has_font (const gchar *locale); +gchar *cc_common_language_get_current_language (void); + +GHashTable *cc_common_language_get_initial_languages (void); +GHashTable *cc_common_language_get_user_languages (void); +GHashTable *cc_common_language_get_initial_regions (const gchar *lang); + +void cc_common_language_setup_list (GtkWidget *treeview, + GHashTable *users, + GHashTable *initial); +void cc_common_language_select_current_language (GtkTreeView *treeview); + +void cc_common_language_add_user_languages (GtkTreeModel *model); + +G_END_DECLS diff --git a/panels/common/cc-hostname-entry.c b/panels/common/cc-hostname-entry.c new file mode 100644 index 0000000..8a79b03 --- /dev/null +++ b/panels/common/cc-hostname-entry.c @@ -0,0 +1,265 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2013 Intel, Inc + * Copyright (C) 2011,2012 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 "cc-common-resources.h" +#include "cc-hostname-entry.h" +#include "hostname-helper.h" + +#include <polkit/polkit.h> + +struct _CcHostnameEntry +{ + GtkEntry parent; + + GDBusProxy *hostnamed_proxy; + guint set_hostname_timeout_source_id; +}; + +G_DEFINE_TYPE (CcHostnameEntry, cc_hostname_entry, GTK_TYPE_ENTRY) + +#define SET_HOSTNAME_TIMEOUT 1 + +static void +cc_hostname_entry_set_hostname (CcHostnameEntry *self) +{ + g_autofree gchar *hostname = NULL; + g_autoptr(GVariant) pretty_result = NULL; + g_autoptr(GVariant) static_result = NULL; + g_autoptr(GError) pretty_error = NULL; + g_autoptr(GError) static_error = NULL; + const gchar *text; + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + + g_debug ("Setting PrettyHostname to '%s'", text); + pretty_result = g_dbus_proxy_call_sync (self->hostnamed_proxy, + "SetPrettyHostname", + g_variant_new ("(sb)", text, FALSE), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &pretty_error); + if (pretty_result == NULL) + g_warning ("Could not set PrettyHostname: %s", pretty_error->message); + + /* Set the static hostname */ + hostname = pretty_hostname_to_static (text, FALSE); + g_assert (hostname); + + g_debug ("Setting StaticHostname to '%s'", hostname); + static_result = g_dbus_proxy_call_sync (self->hostnamed_proxy, + "SetStaticHostname", + g_variant_new ("(sb)", hostname, FALSE), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &static_error); + if (static_result == NULL) + g_warning ("Could not set StaticHostname: %s", static_error->message); +} + +static char * +get_hostname_property (CcHostnameEntry *self, + const char *property) +{ + g_autoptr(GVariant) variant = NULL; + + if (!self->hostnamed_proxy) + return g_strdup (""); + + variant = g_dbus_proxy_get_cached_property (self->hostnamed_proxy, + property); + if (!variant) + { + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) inner = NULL; + + /* Work around systemd-hostname not sending us back + * the property value when changing values */ + variant = g_dbus_proxy_call_sync (self->hostnamed_proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new ("(ss)", "org.freedesktop.hostname1", property), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (variant == NULL) + { + g_warning ("Failed to get property '%s': %s", property, error->message); + return NULL; + } + + g_variant_get (variant, "(v)", &inner); + return g_variant_dup_string (inner, NULL); + } + else + { + return g_variant_dup_string (variant, NULL); + } +} + +static char * +cc_hostname_entry_get_display_hostname (CcHostnameEntry *self) +{ + g_autofree gchar *str = NULL; + + str = get_hostname_property (self, "PrettyHostname"); + + /* Empty strings means that we need to fallback */ + if (str != NULL && + *str == '\0') + return get_hostname_property (self, "Hostname"); + + return g_steal_pointer (&str); +} + +static gboolean +set_hostname_timeout (CcHostnameEntry *self) +{ + self->set_hostname_timeout_source_id = 0; + + cc_hostname_entry_set_hostname (self); + + return FALSE; +} + +static void +remove_hostname_timeout (CcHostnameEntry *self) +{ + if (self->set_hostname_timeout_source_id) + g_source_remove (self->set_hostname_timeout_source_id); + + self->set_hostname_timeout_source_id = 0; +} + +static void +reset_hostname_timeout (CcHostnameEntry *self) +{ + remove_hostname_timeout (self); + + self->set_hostname_timeout_source_id = g_timeout_add_seconds (SET_HOSTNAME_TIMEOUT, + (GSourceFunc) set_hostname_timeout, + self); +} + +static void +text_changed_cb (CcHostnameEntry *entry) +{ + reset_hostname_timeout (entry); +} + +static void +cc_hostname_entry_dispose (GObject *object) +{ + CcHostnameEntry *self = CC_HOSTNAME_ENTRY (object); + + if (self->set_hostname_timeout_source_id) + { + remove_hostname_timeout (self); + set_hostname_timeout (self); + } + + g_clear_object (&self->hostnamed_proxy); + + G_OBJECT_CLASS (cc_hostname_entry_parent_class)->dispose (object); +} + +static void +cc_hostname_entry_constructed (GObject *object) +{ + CcHostnameEntry *self = CC_HOSTNAME_ENTRY (object); + GPermission *permission; + g_autoptr(GError) error = NULL; + g_autofree gchar *str = NULL; + + permission = polkit_permission_new_sync ("org.freedesktop.hostname1.set-static-hostname", + NULL, NULL, NULL); + + /* Is hostnamed installed? */ + if (permission == NULL) + { + g_debug ("Will not show hostname, hostnamed not installed"); + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + return; + } + + if (g_permission_get_allowed (permission)) + gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); + else + { + g_debug ("Not allowed to change the hostname"); + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + } + + gtk_widget_set_sensitive (GTK_WIDGET (self), + g_permission_get_allowed (permission)); + + self->hostnamed_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + NULL, + &error); + + /* This could only happen if the policy file was installed + * but not hostnamed, which points to a system bug */ + if (self->hostnamed_proxy == NULL) + { + g_debug ("Couldn't get hostnamed to start, bailing: %s", error->message); + return; + } + + str = cc_hostname_entry_get_display_hostname (CC_HOSTNAME_ENTRY (self)); + + if (str != NULL) + gtk_editable_set_text (GTK_EDITABLE (self), str); + else + gtk_editable_set_text (GTK_EDITABLE (self), ""); + + g_signal_connect (self, "changed", G_CALLBACK (text_changed_cb), NULL); +} + +static void +cc_hostname_entry_class_init (CcHostnameEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = cc_hostname_entry_constructed; + object_class->dispose = cc_hostname_entry_dispose; +} + +static void +cc_hostname_entry_init (CcHostnameEntry *self) +{ + g_resources_register (cc_common_get_resource ()); +} + +CcHostnameEntry * +cc_hostname_entry_new (void) +{ + return g_object_new (CC_TYPE_HOSTNAME_ENTRY, NULL); +} + +gchar* +cc_hostname_entry_get_hostname (CcHostnameEntry *entry) +{ + return get_hostname_property (entry, "Hostname"); +} diff --git a/panels/common/cc-hostname-entry.h b/panels/common/cc-hostname-entry.h new file mode 100644 index 0000000..a481779 --- /dev/null +++ b/panels/common/cc-hostname-entry.h @@ -0,0 +1,33 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2013 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/>. + * + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_HOSTNAME_ENTRY (cc_hostname_entry_get_type()) + +G_DECLARE_FINAL_TYPE (CcHostnameEntry, cc_hostname_entry, CC, HOSTNAME_ENTRY, GtkEntry) + +CcHostnameEntry *cc_hostname_entry_new (void); +gchar* cc_hostname_entry_get_hostname (CcHostnameEntry *entry); + +G_END_DECLS diff --git a/panels/common/cc-language-chooser.c b/panels/common/cc-language-chooser.c new file mode 100644 index 0000000..1aa2ec4 --- /dev/null +++ b/panels/common/cc-language-chooser.c @@ -0,0 +1,343 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 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/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#define _GNU_SOURCE +#include <config.h> +#include "cc-language-chooser.h" +#include "cc-language-row.h" +#include "cc-common-resources.h" + +#include <locale.h> +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include "cc-common-language.h" +#include "cc-util.h" + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +struct _CcLanguageChooser { + GtkDialog parent_instance; + + GtkSearchEntry *language_filter_entry; + GtkListBox *language_listbox; + GtkListBoxRow *more_row; + GtkSearchBar *search_bar; + GtkButton *select_button; + + gboolean showing_extra; + gchar *language; + gchar **filter_words; +}; + +G_DEFINE_TYPE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_DIALOG) + +static void +add_all_languages (CcLanguageChooser *self) +{ + g_auto(GStrv) locale_ids = NULL; + g_autoptr(GHashTable) initial = NULL; + + locale_ids = gnome_get_all_locales (); + initial = cc_common_language_get_initial_languages (); + for (int i = 0; locale_ids[i] != NULL; i++) { + CcLanguageRow *row; + gboolean is_initial; + + if (!cc_common_language_has_font (locale_ids[i])) + continue; + + row = cc_language_row_new (locale_ids[i]); + gtk_widget_show (GTK_WIDGET (row)); + is_initial = (g_hash_table_lookup (initial, locale_ids[i]) != NULL); + cc_language_row_set_is_extra (row, !is_initial); + gtk_list_box_prepend (self->language_listbox, GTK_WIDGET (row)); + } +} + +static gboolean +match_all (gchar **words, + const gchar *str) +{ + gchar **w; + + if (str == NULL) + return FALSE; + + for (w = words; *w; ++w) + if (!strstr (str, *w)) + return FALSE; + + return TRUE; +} + +static gboolean +language_visible (GtkListBoxRow *row, + gpointer user_data) +{ + CcLanguageChooser *self = user_data; + g_autofree gchar *language = NULL; + g_autofree gchar *country = NULL; + g_autofree gchar *language_local = NULL; + g_autofree gchar *country_local = NULL; + gboolean visible; + + if (row == self->more_row) + return !self->showing_extra; + + if (!CC_IS_LANGUAGE_ROW (row)) + return TRUE; + + if (!self->showing_extra && cc_language_row_get_is_extra (CC_LANGUAGE_ROW (row))) + return FALSE; + + if (!self->filter_words) + return TRUE; + + language = + cc_util_normalize_casefold_and_unaccent (cc_language_row_get_language (CC_LANGUAGE_ROW (row))); + visible = match_all (self->filter_words, language); + if (visible) + return TRUE; + + country = + cc_util_normalize_casefold_and_unaccent (cc_language_row_get_country (CC_LANGUAGE_ROW (row))); + visible = match_all (self->filter_words, country); + if (visible) + return TRUE; + + language_local = + cc_util_normalize_casefold_and_unaccent (cc_language_row_get_language_local (CC_LANGUAGE_ROW (row))); + visible = match_all (self->filter_words, language_local); + if (visible) + return TRUE; + + country_local = + cc_util_normalize_casefold_and_unaccent (cc_language_row_get_country_local (CC_LANGUAGE_ROW (row))); + return match_all (self->filter_words, country_local); +} + +static gint +sort_languages (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer data) +{ + int d; + + if (!CC_IS_LANGUAGE_ROW (a)) + return 1; + if (!CC_IS_LANGUAGE_ROW (b)) + return -1; + + d = g_strcmp0 (cc_language_row_get_language (CC_LANGUAGE_ROW (a)), cc_language_row_get_language (CC_LANGUAGE_ROW (b))); + if (d != 0) + return d; + + return g_strcmp0 (cc_language_row_get_country (CC_LANGUAGE_ROW (a)), cc_language_row_get_country (CC_LANGUAGE_ROW (b))); +} + +static void +language_filter_entry_search_changed_cb (CcLanguageChooser *self) +{ + g_autofree gchar *filter_contents = NULL; + + g_clear_pointer (&self->filter_words, g_strfreev); + + filter_contents = + cc_util_normalize_casefold_and_unaccent (gtk_editable_get_text (GTK_EDITABLE (self->language_filter_entry))); + if (!filter_contents) { + gtk_list_box_invalidate_filter (self->language_listbox); + return; + } + self->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0); + gtk_list_box_invalidate_filter (self->language_listbox); +} + +static void +show_more (CcLanguageChooser *self, gboolean visible) +{ + gint width, height; + + gtk_window_get_default_size (GTK_WINDOW (self), &width, &height); + gtk_widget_set_size_request (GTK_WIDGET (self), width, height); + + gtk_search_bar_set_search_mode (self->search_bar, visible); + gtk_widget_grab_focus (visible ? GTK_WIDGET (self->language_filter_entry) : GTK_WIDGET (self->language_listbox)); + + self->showing_extra = visible; + + gtk_list_box_invalidate_filter (self->language_listbox); +} + +static void +set_locale_id (CcLanguageChooser *self, + const gchar *locale_id) +{ + GtkWidget *child; + + gtk_widget_set_sensitive (GTK_WIDGET (self->select_button), FALSE); + + for (child = gtk_widget_get_first_child (GTK_WIDGET (self->language_listbox)); + child; + child = gtk_widget_get_next_sibling (child)) { + CcLanguageRow *row; + + if (!CC_IS_LANGUAGE_ROW (child)) + continue; + + row = CC_LANGUAGE_ROW (child); + if (g_strcmp0 (locale_id, cc_language_row_get_locale_id (row)) == 0) { + cc_language_row_set_checked (row, TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->select_button), TRUE); + + /* make sure the selected language is shown */ + if (!self->showing_extra && cc_language_row_get_is_extra (row)) { + cc_language_row_set_is_extra (row, FALSE); + gtk_list_box_invalidate_filter (self->language_listbox); + } + } else { + cc_language_row_set_checked (row, FALSE); + } + } + + g_free (self->language); + self->language = g_strdup (locale_id); +} + +static void +language_listbox_row_activated_cb (CcLanguageChooser *self, GtkListBoxRow *row) +{ + const gchar *new_locale_id; + + if (row == self->more_row) { + show_more (self, TRUE); + return; + } + + if (!CC_IS_LANGUAGE_ROW (row)) + return; + + new_locale_id = cc_language_row_get_locale_id (CC_LANGUAGE_ROW (row)); + if (g_strcmp0 (new_locale_id, self->language) == 0) { + gtk_dialog_response (GTK_DIALOG (self), + gtk_dialog_get_response_for_widget (GTK_DIALOG (self), + GTK_WIDGET (self->select_button))); + } else { + set_locale_id (self, new_locale_id); + } +} + +static void +activate_default_cb (CcLanguageChooser *self) +{ + GtkWidget *focus; + + focus = gtk_window_get_focus (GTK_WINDOW (self)); + if (!focus || !CC_IS_LANGUAGE_ROW (focus)) + return; + + if (g_strcmp0 (cc_language_row_get_locale_id (CC_LANGUAGE_ROW (focus)), self->language) == 0) + return; + + g_signal_stop_emission_by_name (GTK_WINDOW (self), "activate-default"); + gtk_widget_activate (focus); +} + +void +cc_language_chooser_init (CcLanguageChooser *self) +{ + g_resources_register (cc_common_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_list_box_set_sort_func (self->language_listbox, + sort_languages, self, NULL); + gtk_list_box_set_filter_func (self->language_listbox, + language_visible, self, NULL); + add_all_languages (self); + + gtk_list_box_invalidate_filter (self->language_listbox); +} + +static void +cc_language_chooser_dispose (GObject *object) +{ + CcLanguageChooser *self = CC_LANGUAGE_CHOOSER (object); + + g_clear_pointer (&self->filter_words, g_strfreev); + g_clear_pointer (&self->language, g_free); + + G_OBJECT_CLASS (cc_language_chooser_parent_class)->dispose (object); +} + +void +cc_language_chooser_class_init (CcLanguageChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_language_chooser_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/common/cc-language-chooser.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, language_filter_entry); + gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, language_listbox); + gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, more_row); + gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, search_bar); + gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, select_button); + + gtk_widget_class_bind_template_callback (widget_class, activate_default_cb); + gtk_widget_class_bind_template_callback (widget_class, language_filter_entry_search_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, language_listbox_row_activated_cb); +} + +CcLanguageChooser * +cc_language_chooser_new (void) +{ + return CC_LANGUAGE_CHOOSER (g_object_new (CC_TYPE_LANGUAGE_CHOOSER, + "use-header-bar", 1, + NULL)); +} + +void +cc_language_chooser_clear_filter (CcLanguageChooser *self) +{ + g_return_if_fail (CC_IS_LANGUAGE_CHOOSER (self)); + gtk_editable_set_text (GTK_EDITABLE (self->language_filter_entry), ""); + show_more (self, FALSE); +} + +const gchar * +cc_language_chooser_get_language (CcLanguageChooser *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_CHOOSER (self), NULL); + return self->language; +} + +void +cc_language_chooser_set_language (CcLanguageChooser *self, + const gchar *language) +{ + g_return_if_fail (CC_IS_LANGUAGE_CHOOSER (self)); + set_locale_id (self, language); +} diff --git a/panels/common/cc-language-chooser.h b/panels/common/cc-language-chooser.h new file mode 100644 index 0000000..8e050ee --- /dev/null +++ b/panels/common/cc-language-chooser.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 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/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define CC_TYPE_LANGUAGE_CHOOSER (cc_language_chooser_get_type ()) +G_DECLARE_FINAL_TYPE (CcLanguageChooser, cc_language_chooser, CC, LANGUAGE_CHOOSER, GtkDialog) + +CcLanguageChooser *cc_language_chooser_new (void); +void cc_language_chooser_clear_filter (CcLanguageChooser *chooser); +const gchar *cc_language_chooser_get_language (CcLanguageChooser *chooser); +void cc_language_chooser_set_language (CcLanguageChooser *chooser, + const gchar *language); + +G_END_DECLS diff --git a/panels/common/cc-language-chooser.ui b/panels/common/cc-language-chooser.ui new file mode 100644 index 0000000..4efc9a8 --- /dev/null +++ b/panels/common/cc-language-chooser.ui @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CcLanguageChooser" parent="GtkDialog"> + <property name="title" translatable="yes">Select Language</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <property name="default_width">400</property> + <property name="default_height">475</property> + <signal name="activate-default" handler="activate_default_cb"/> + <child type="action"> + <object class="GtkButton" id="select_button"> + <property name="label" translatable="yes">_Select</property> + <property name="sensitive">False</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">0</property> + <child> + <object class="GtkSearchBar" id="search_bar"> + <property name="hexpand">True</property> + <child> + <object class="GtkSearchEntry" id="language_filter_entry"> + <property name="width_chars">30</property> + <signal name="search-changed" handler="language_filter_entry_search_changed_cb" object="CcLanguageChooser" swapped="yes"/> + </object> + </child> + </object> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">automatic</property> + <property name="propagate-natural-height">True</property> + <property name="min-content-height">200</property> + <child> + <object class="GtkListBox" id="language_listbox"> + <property name="can-focus">True</property> + <property name="vexpand">True</property> + <property name="halign">fill</property> + <property name="valign">fill</property> + <property name="selection-mode">none</property> + <property name="show-separators">True</property> + <signal name="row-activated" handler="language_listbox_row_activated_cb" object="CcLanguageChooser" swapped="yes"/> + <child type="placeholder"> + <object class="GtkLabel"> + <property name="label" translatable="yes">No languages found</property> + <property name="sensitive">False</property> + </object> + </child> + + <!-- "More" row --> + <child> + <object class="GtkListBoxRow" id="more_row"> + <child> + <object class="GtkBox"> + <property name="spacing">10</property> + <property name="tooltip_markup" translatable="yes">More…</property> + <child> + <object class="GtkImage"> + <property name="hexpand">True</property> + <property name="halign">center</property> + <property name="icon-name">view-more-symbolic</property> + <property name="icon-size">1</property> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + + </object> + </child> + </object> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5" default="true">select_button</action-widget> + <action-widget response="-6">cancel_button</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/common/cc-language-row.c b/panels/common/cc-language-row.c new file mode 100644 index 0000000..a2832df --- /dev/null +++ b/panels/common/cc-language-row.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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 "cc-language-row.h" +#include "cc-common-resources.h" + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +struct _CcLanguageRow { + GtkListBoxRow parent_instance; + + GtkImage *check_image; + GtkLabel *country_label; + GtkLabel *language_label; + + gchar *locale_id; + gchar *language; + gchar *language_local; + gchar *country; + gchar *country_local; + + gboolean is_extra; +}; + +G_DEFINE_TYPE (CcLanguageRow, cc_language_row, GTK_TYPE_LIST_BOX_ROW) + +static gchar * +get_language_label (const gchar *language_code, + const gchar *modifier, + const gchar *locale_id) +{ + g_autofree gchar *language = NULL; + + language = gnome_get_language_from_code (language_code, locale_id); + + if (modifier == NULL) + return g_steal_pointer (&language); + else + { + g_autofree gchar *t_mod = gnome_get_translated_modifier (modifier, locale_id); + return g_strdup_printf ("%s — %s", language, t_mod); + } +} + +static void +cc_language_row_dispose (GObject *object) +{ + CcLanguageRow *self = CC_LANGUAGE_ROW (object); + + g_clear_pointer (&self->locale_id, g_free); + g_clear_pointer (&self->country, g_free); + g_clear_pointer (&self->country_local, g_free); + g_clear_pointer (&self->language, g_free); + g_clear_pointer (&self->language_local, g_free); + + G_OBJECT_CLASS (cc_language_row_parent_class)->dispose (object); +} + +void +cc_language_row_class_init (CcLanguageRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_language_row_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/common/cc-language-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcLanguageRow, check_image); + gtk_widget_class_bind_template_child (widget_class, CcLanguageRow, country_label); + gtk_widget_class_bind_template_child (widget_class, CcLanguageRow, language_label); +} + +void +cc_language_row_init (CcLanguageRow *self) +{ + g_resources_register (cc_common_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcLanguageRow * +cc_language_row_new (const gchar *locale_id) +{ + CcLanguageRow *self; + g_autofree gchar *language_code = NULL; + g_autofree gchar *country_code = NULL; + g_autofree gchar *modifier = NULL; + + self = CC_LANGUAGE_ROW (g_object_new (CC_TYPE_LANGUAGE_ROW, NULL)); + self->locale_id = g_strdup (locale_id); + + gnome_parse_locale (locale_id, &language_code, &country_code, NULL, &modifier); + + self->language = get_language_label (language_code, modifier, locale_id); + self->language_local = get_language_label (language_code, modifier, NULL); + gtk_label_set_label (self->language_label, self->language); + + if (country_code == NULL) + { + self->country = NULL; + self->country_local = NULL; + } + else + { + self->country = gnome_get_country_from_code (country_code, locale_id); + self->country_local = gnome_get_country_from_code (country_code, NULL); + gtk_label_set_label (self->country_label, self->country); + } + + return self; +} + +const gchar * +cc_language_row_get_locale_id (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), NULL); + return self->locale_id; +} + +const gchar * +cc_language_row_get_language (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), NULL); + return self->language; +} + +const gchar * +cc_language_row_get_language_local (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), NULL); + return self->language_local; +} + +const gchar * +cc_language_row_get_country (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), NULL); + return self->country; +} + +const gchar * +cc_language_row_get_country_local (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), NULL); + return self->country_local; +} + +void +cc_language_row_set_checked (CcLanguageRow *self, gboolean checked) +{ + g_return_if_fail (CC_IS_LANGUAGE_ROW (self)); + gtk_widget_set_visible (GTK_WIDGET (self->check_image), checked); +} + +void +cc_language_row_set_is_extra (CcLanguageRow *self, gboolean is_extra) +{ + g_return_if_fail (CC_IS_LANGUAGE_ROW (self)); + self->is_extra = is_extra; +} + +gboolean +cc_language_row_get_is_extra (CcLanguageRow *self) +{ + g_return_val_if_fail (CC_IS_LANGUAGE_ROW (self), FALSE); + return self->is_extra; +} diff --git a/panels/common/cc-language-row.h b/panels/common/cc-language-row.h new file mode 100644 index 0000000..2e3e8cd --- /dev/null +++ b/panels/common/cc-language-row.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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> + +G_BEGIN_DECLS + +#define CC_TYPE_LANGUAGE_ROW (cc_language_row_get_type ()) +G_DECLARE_FINAL_TYPE (CcLanguageRow, cc_language_row, CC, LANGUAGE_ROW, GtkListBoxRow) + +CcLanguageRow *cc_language_row_new (const gchar *locale_id); + +const gchar *cc_language_row_get_locale_id (CcLanguageRow *row); + +const gchar *cc_language_row_get_language (CcLanguageRow *row); + +const gchar *cc_language_row_get_language_local (CcLanguageRow *row); + +const gchar *cc_language_row_get_country (CcLanguageRow *row); + +const gchar *cc_language_row_get_country_local (CcLanguageRow *row); + +void cc_language_row_set_checked (CcLanguageRow *row, gboolean checked); + +void cc_language_row_set_is_extra (CcLanguageRow *row, gboolean is_extra); + +gboolean cc_language_row_get_is_extra (CcLanguageRow *row); + +G_END_DECLS diff --git a/panels/common/cc-language-row.ui b/panels/common/cc-language-row.ui new file mode 100644 index 0000000..957df54 --- /dev/null +++ b/panels/common/cc-language-row.ui @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CcLanguageRow" parent="GtkListBoxRow"> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="spacing">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <child> + <object class="GtkLabel" id="language_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ellipsize">end</property> + </object> + </child> + <child> + <object class="GtkImage" id="check_image"> + <property name="visible">False</property> + <property name="icon-name">object-select-symbolic</property> + <property name="icon-size">1</property> + </object> + </child> + <child> + <object class="GtkLabel" id="country_label"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="ellipsize">end</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/common/cc-list-row.c b/panels/common/cc-list-row.c new file mode 100644 index 0000000..6dc4388 --- /dev/null +++ b/panels/common/cc-list-row.c @@ -0,0 +1,283 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-list-row.c + * + * Copyright 2019 Purism SPC + * + * 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-list-row" + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "cc-common-resources.h" +#include "cc-list-row.h" + +struct _CcListRow +{ + AdwActionRow parent_instance; + + GtkLabel *secondary_label; + + GtkImage *arrow; + gboolean show_arrow; + + GtkSwitch *enable_switch; + gboolean show_switch; + + gboolean switch_active; +}; + +G_DEFINE_TYPE (CcListRow, cc_list_row, ADW_TYPE_ACTION_ROW) + + +enum { + PROP_0, + PROP_SECONDARY_LABEL, + PROP_SHOW_ARROW, + PROP_SHOW_SWITCH, + PROP_ACTIVE, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +cc_list_row_switch_active_cb (CcListRow *self) +{ + gboolean switch_active; + + g_assert (CC_IS_LIST_ROW (self)); + g_assert (self->show_switch); + + switch_active = gtk_switch_get_active (self->enable_switch); + + if (switch_active == self->switch_active) + return; + + self->switch_active = switch_active; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]); +} + +static void +cc_list_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcListRow *self = (CcListRow *)object; + + switch (prop_id) + { + case PROP_SECONDARY_LABEL: + g_value_set_string (value, gtk_label_get_label (self->secondary_label)); + break; + + case PROP_SHOW_ARROW: + g_value_set_boolean (value, self->show_arrow); + break; + + case PROP_ACTIVE: + g_value_set_boolean (value, self->switch_active); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_list_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcListRow *self = (CcListRow *)object; + + switch (prop_id) + { + case PROP_SECONDARY_LABEL: + gtk_label_set_label (self->secondary_label, g_value_get_string (value)); + break; + + case PROP_SHOW_ARROW: + cc_list_row_set_show_arrow (self, g_value_get_boolean (value)); + break; + + case PROP_SHOW_SWITCH: + cc_list_row_set_show_switch (self, g_value_get_boolean (value)); + break; + + case PROP_ACTIVE: + g_signal_handlers_block_by_func (self->enable_switch, + cc_list_row_switch_active_cb, self); + gtk_switch_set_active (self->enable_switch, + g_value_get_boolean (value)); + self->switch_active = g_value_get_boolean (value); + g_signal_handlers_unblock_by_func (self->enable_switch, + cc_list_row_switch_active_cb, self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_list_row_class_init (CcListRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_list_row_get_property; + object_class->set_property = cc_list_row_set_property; + + properties[PROP_SECONDARY_LABEL] = + g_param_spec_string ("secondary-label", + "Secondary Label", + "Set Secondary Label", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SHOW_ARROW] = + g_param_spec_boolean ("show-arrow", + "Show Arrow", + "Whether to show an arrow at the end of the row", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SHOW_SWITCH] = + g_param_spec_boolean ("show-switch", + "Show Switch", + "Whether to show a switch at the end of row", + FALSE, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", + "Active", + "The active state of the switch", + FALSE, + 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/" + "common/cc-list-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcListRow, secondary_label); + gtk_widget_class_bind_template_child (widget_class, CcListRow, arrow); + gtk_widget_class_bind_template_child (widget_class, CcListRow, enable_switch); + + gtk_widget_class_bind_template_callback (widget_class, cc_list_row_switch_active_cb); +} + +static void +cc_list_row_init (CcListRow *self) +{ + g_resources_register (cc_common_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +cc_list_row_set_show_arrow (CcListRow *self, + gboolean show_arrow) +{ + g_return_if_fail (CC_IS_LIST_ROW (self)); + g_return_if_fail (!self->show_switch); + + if (self->show_arrow == show_arrow) + return; + + self->show_arrow = show_arrow; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_ARROW]); +} + +void +cc_list_row_set_show_switch (CcListRow *self, + gboolean show_switch) +{ + g_return_if_fail (CC_IS_LIST_ROW (self)); + + self->show_switch = !!show_switch; + + gtk_widget_set_visible (GTK_WIDGET (self->enable_switch), self->show_switch); + gtk_widget_set_visible (GTK_WIDGET (self->arrow), !self->show_switch); + gtk_widget_set_visible (GTK_WIDGET (self->secondary_label), !self->show_switch); + + adw_action_row_set_activatable_widget (ADW_ACTION_ROW (self), + self->show_switch ? GTK_WIDGET (self->enable_switch) : NULL); +} + +gboolean +cc_list_row_get_active (CcListRow *self) +{ + g_return_val_if_fail (CC_IS_LIST_ROW (self), FALSE); + g_return_val_if_fail (self->show_switch, FALSE); + + return self->switch_active; +} + +void +cc_list_row_set_secondary_label (CcListRow *self, + const gchar *label) +{ + g_return_if_fail (CC_IS_LIST_ROW (self)); + g_return_if_fail (!self->show_switch); + + if (!label) + label = ""; + + if (g_str_equal (label, gtk_label_get_label (self->secondary_label))) + return; + + gtk_label_set_text (self->secondary_label, label); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECONDARY_LABEL]); +} + +void +cc_list_row_set_secondary_markup (CcListRow *self, + const gchar *markup) +{ + g_return_if_fail (CC_IS_LIST_ROW (self)); + g_return_if_fail (!self->show_switch); + + if (!markup) + markup = ""; + + if (g_str_equal (markup, gtk_label_get_label (self->secondary_label))) + return; + + gtk_label_set_markup (self->secondary_label, markup); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECONDARY_LABEL]); +} + +void +cc_list_row_set_switch_sensitive (CcListRow *self, + gboolean sensitive) +{ + g_return_if_fail (CC_IS_LIST_ROW (self)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->enable_switch), sensitive); +} diff --git a/panels/common/cc-list-row.h b/panels/common/cc-list-row.h new file mode 100644 index 0000000..6e4c62e --- /dev/null +++ b/panels/common/cc-list-row.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-list-row.h + * + * Copyright 2019 Purism SPC + * + * 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_LIST_ROW (cc_list_row_get_type()) +G_DECLARE_FINAL_TYPE (CcListRow, cc_list_row, CC, LIST_ROW, AdwActionRow) + +void cc_list_row_set_show_arrow (CcListRow *self, + gboolean show_arrow); +void cc_list_row_set_show_switch (CcListRow *self, + gboolean show_switch); +gboolean cc_list_row_get_active (CcListRow *self); +void cc_list_row_set_secondary_label (CcListRow *self, + const gchar *label); +void cc_list_row_set_secondary_markup (CcListRow *self, + const gchar *markup); +void cc_list_row_set_switch_sensitive (CcListRow *self, + gboolean sensitive); + +G_END_DECLS diff --git a/panels/common/cc-list-row.ui b/panels/common/cc-list-row.ui new file mode 100644 index 0000000..f668a10 --- /dev/null +++ b/panels/common/cc-list-row.ui @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcListRow" parent="AdwActionRow"> + <property name="activatable">True</property> + + <!-- Secondary Label --> + <child type="suffix"> + <object class="GtkLabel" id="secondary_label"> + <property name="valign">center</property> + <property name="ellipsize">end</property> + <property name="selectable" bind-source="CcListRow" bind-property="activatable" bind-flags="sync-create|invert-boolean" /> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + + <!-- Switch --> + <child type="suffix"> + <object class="GtkSwitch" id="enable_switch"> + <property name="visible">False</property> + <property name="valign">center</property> + <signal name="notify::active" handler="cc_list_row_switch_active_cb" swapped="yes"/> + </object> + </child> + + <!-- Arrow --> + <child type="suffix"> + <object class="GtkImage" id="arrow"> + <property name="visible" bind-source="CcListRow" bind-property="show-arrow" bind-flags="sync-create"/> + <property name="valign">center</property> + <property name="icon-name">go-next-symbolic</property> + </object> + </child> + + </template> +</interface> diff --git a/panels/common/cc-permission-infobar.c b/panels/common/cc-permission-infobar.c new file mode 100644 index 0000000..78e2b07 --- /dev/null +++ b/panels/common/cc-permission-infobar.c @@ -0,0 +1,112 @@ +/* cc-permission-infobar.c + * + * Copyright (C) 2020 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 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/>. + * + * Author(s): + * Felipe Borges <felipeborges@gnome.org> + * + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-permission-infobar" + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <glib/gi18n.h> + +#include "cc-permission-infobar.h" + +struct _CcPermissionInfobar +{ + AdwBin parent_instance; + + GtkRevealer *revealer; + GtkLabel *title; + GtkLockButton *lock_button; +}; + +G_DEFINE_TYPE (CcPermissionInfobar, cc_permission_infobar, ADW_TYPE_BIN) + +static void +on_permission_changed (CcPermissionInfobar *self) +{ + GPermission *permission; + gboolean is_authorized; + + permission = gtk_lock_button_get_permission (self->lock_button); + is_authorized = g_permission_get_allowed (permission); + + gtk_revealer_set_reveal_child (self->revealer, !is_authorized); +} + +static void +cc_permission_infobar_class_init (CcPermissionInfobarClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/control-center/" + "common/cc-permission-infobar.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcPermissionInfobar, revealer); + gtk_widget_class_bind_template_child (widget_class, CcPermissionInfobar, title); + gtk_widget_class_bind_template_child (widget_class, CcPermissionInfobar, lock_button); +} + +static void +cc_permission_infobar_init (CcPermissionInfobar *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + /* Set the default title. */ + cc_permission_infobar_set_title (self, NULL); +} + +void +cc_permission_infobar_set_permission (CcPermissionInfobar *self, + GPermission *permission) +{ + g_return_if_fail (CC_IS_PERMISSION_INFOBAR (self)); + + gtk_lock_button_set_permission (self->lock_button, permission); + + g_signal_connect_object (permission, "notify", + G_CALLBACK (on_permission_changed), + self, + G_CONNECT_SWAPPED); + on_permission_changed (self); +} + +/** + * cc_permission_infobar_set_title: + * @self: a #CcPermissionInfobar + * @title: (nullable): title to display in the infobar, or %NULL for the default + * + * Set the title text to display in the infobar. + */ +void +cc_permission_infobar_set_title (CcPermissionInfobar *self, + const gchar *title) +{ + g_return_if_fail (CC_IS_PERMISSION_INFOBAR (self)); + + if (title == NULL) + title = _("Unlock to Change Settings"); + + gtk_label_set_text (self->title, title); +} diff --git a/panels/common/cc-permission-infobar.h b/panels/common/cc-permission-infobar.h new file mode 100644 index 0000000..4d7064d --- /dev/null +++ b/panels/common/cc-permission-infobar.h @@ -0,0 +1,37 @@ +/* cc-permission-infobar.h + * + * Copyright (C) 2020 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 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/>. + * + * Author(s): + * Felipe Borges <felipeborges@gnome.org> + * + */ +#pragma once + +#include <adwaita.h> + +G_BEGIN_DECLS + +#define CC_TYPE_PERMISSION_INFOBAR (cc_permission_infobar_get_type()) +G_DECLARE_FINAL_TYPE (CcPermissionInfobar, cc_permission_infobar, CC, PERMISSION_INFOBAR, AdwBin) + +void cc_permission_infobar_set_permission (CcPermissionInfobar *self, + GPermission *permission); + +void cc_permission_infobar_set_title (CcPermissionInfobar *self, + const gchar *title); + +G_END_DECLS diff --git a/panels/common/cc-permission-infobar.ui b/panels/common/cc-permission-infobar.ui new file mode 100644 index 0000000..a2830b9 --- /dev/null +++ b/panels/common/cc-permission-infobar.ui @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcPermissionInfobar" parent="AdwBin"> + <property name="valign">start</property> + <child> + <object class="GtkRevealer" id="revealer"> + <property name="hexpand">True</property> + <property name="reveal-child">True</property> + <child> + <object class="GtkInfoBar"> + <child> + <object class="GtkBox"> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <property name="margin-start">10</property> + <property name="margin-end">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkImage"> + <property name="icon-name">system-lock-screen-symbolic</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="title"> + <property name="halign">start</property> + <property name="wrap">True</property> + <!-- Actual string set in code --> + <property name="label"></property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="halign">start</property> + <property name="wrap">True</property> + <property name="label" translatable="yes">Some settings must be unlocked before they can be changed.</property> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkCenterBox"> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <property name="margin-start">10</property> + <property name="margin-end">10</property> + <child type="end"> + <object class="GtkLockButton" id="lock_button"> + <property name="receives-default">True</property> + <property name="label" translatable="yes">Unlock…</property> + <property name="valign">GTK_ALIGN_CENTER</property> + </object> + </child> + </object> + </child> + + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/common/cc-time-editor.c b/panels/common/cc-time-editor.c new file mode 100644 index 0000000..572fc85 --- /dev/null +++ b/panels/common/cc-time-editor.c @@ -0,0 +1,372 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-time-editor.c + * + * Copyright 2020 Purism SPC + * + * 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(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-time-editor" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-wall-clock.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "cc-time-entry.h" +#include "cc-time-editor.h" + + +#define TIMEOUT_INITIAL 500 +#define TIMEOUT_REPEAT 50 + +#define FILECHOOSER_SCHEMA "org.gtk.Settings.FileChooser" +#define CLOCK_SCHEMA "org.gnome.desktop.interface" +#define CLOCK_FORMAT_KEY "clock-format" +#define SECONDS_PER_MINUTE (60) +#define SECONDS_PER_HOUR (60 * 60) +#define SECONDS_PER_DAY (60 * 60 * 24) + + +struct _CcTimeEditor +{ + AdwBin parent_instance; + + GtkButton *am_pm_button; + GtkStack *am_pm_stack; + GtkLabel *am_label; + GtkLabel *pm_label; + GtkButton *hour_up_button; + GtkButton *hour_down_button; + GtkButton *minute_up_button; + GtkButton *minute_down_button; + CcTimeEntry *time_entry; + + GtkButton *clicked_button; /* The button currently being clicked */ + GSettings *clock_settings; + GSettings *filechooser_settings; + + guint timer_id; +}; + +G_DEFINE_TYPE (CcTimeEditor, cc_time_editor, ADW_TYPE_BIN) + + +enum { + TIME_CHANGED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +static void +time_editor_clock_changed_cb (CcTimeEditor *self) +{ + GDesktopClockFormat value; + gboolean is_am_pm; + + g_assert (CC_IS_TIME_EDITOR (self)); + + value = g_settings_get_enum (self->clock_settings, CLOCK_FORMAT_KEY); + + is_am_pm = value == G_DESKTOP_CLOCK_FORMAT_12H; + cc_time_entry_set_am_pm (self->time_entry, is_am_pm); + gtk_widget_set_visible (GTK_WIDGET (self->am_pm_button), is_am_pm); + + if (is_am_pm) + { + if (cc_time_entry_get_is_am (self->time_entry)) + gtk_stack_set_visible_child (self->am_pm_stack, GTK_WIDGET (self->am_label)); + else + gtk_stack_set_visible_child (self->am_pm_stack, GTK_WIDGET (self->pm_label)); + } +} + +static void +time_editor_time_changed_cb (CcTimeEditor *self) +{ + g_assert (CC_IS_TIME_EDITOR (self)); + + time_editor_clock_changed_cb (self); + g_signal_emit (self, signals[TIME_CHANGED], 0); +} + +static void +editor_change_time_clicked_cb (CcTimeEditor *self, + GtkButton *button) +{ + g_assert (CC_IS_TIME_EDITOR (self)); + + if (button == NULL) + return; + + if (button == self->hour_up_button) + { + gtk_editable_set_position (GTK_EDITABLE (self->time_entry), 0); + g_signal_emit_by_name (self->time_entry, "change-value", GTK_SCROLL_STEP_UP); + } + else if (button == self->hour_down_button) + { + gtk_editable_set_position (GTK_EDITABLE (self->time_entry), 0); + g_signal_emit_by_name (self->time_entry, "change-value", GTK_SCROLL_STEP_DOWN); + } + else if (button == self->minute_up_button) + { + gtk_editable_set_position (GTK_EDITABLE (self->time_entry), 3); + g_signal_emit_by_name (self->time_entry, "change-value", GTK_SCROLL_STEP_UP); + } + else if (button == self->minute_down_button) + { + gtk_editable_set_position (GTK_EDITABLE (self->time_entry), 3); + g_signal_emit_by_name (self->time_entry, "change-value", GTK_SCROLL_STEP_DOWN); + } +} + +static gboolean +editor_change_time_repeat (CcTimeEditor *self) +{ + if (self->clicked_button == NULL) + { + self->timer_id = 0; + + return G_SOURCE_REMOVE; + } + + editor_change_time_clicked_cb (self, self->clicked_button); + + return G_SOURCE_CONTINUE; +} + +static gboolean +editor_change_time_cb (CcTimeEditor *self) +{ + g_assert (CC_IS_TIME_EDITOR (self)); + g_clear_handle_id (&self->timer_id, g_source_remove); + + editor_change_time_clicked_cb (self, self->clicked_button); + self->timer_id = g_timeout_add (TIMEOUT_REPEAT, + (GSourceFunc)editor_change_time_repeat, + self); + return G_SOURCE_REMOVE; +} + +static gboolean +editor_change_time_pressed_cb (CcTimeEditor *self, + gint n_press, + gdouble x, + gdouble y, + GtkGestureClick *click_gesture) +{ + GtkWidget *button; + + g_assert (CC_IS_TIME_EDITOR (self)); + + button = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (click_gesture)); + + self->clicked_button = GTK_BUTTON (button); + /* Keep changing time until the press is released */ + self->timer_id = g_timeout_add (TIMEOUT_INITIAL, + (GSourceFunc)editor_change_time_cb, + self); + editor_change_time_clicked_cb (self, GTK_BUTTON (button)); + return FALSE; +} + +static gboolean +editor_change_time_released_cb (CcTimeEditor *self) +{ + self->clicked_button = NULL; + g_clear_handle_id (&self->timer_id, g_source_remove); + + return FALSE; +} + +static void +editor_am_pm_button_clicked_cb (CcTimeEditor *self) +{ + gboolean is_am; + + g_assert (CC_IS_TIME_EDITOR (self)); + g_assert (cc_time_entry_get_am_pm (self->time_entry)); + + is_am = cc_time_entry_get_is_am (self->time_entry); + /* Toggle AM PM */ + cc_time_entry_set_is_am (self->time_entry, !is_am); + time_editor_clock_changed_cb (self); +} + +static void +editor_am_pm_stack_changed_cb (CcTimeEditor *self) +{ + GtkWidget *label; + const gchar *text; + + g_assert (CC_IS_TIME_EDITOR (self)); + + label = gtk_stack_get_visible_child (self->am_pm_stack); + text = gtk_label_get_text (GTK_LABEL (label)); + gtk_accessible_update_property (GTK_ACCESSIBLE (self->am_pm_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, text, + -1); +} + +static void +cc_time_editor_constructed (GObject *object) +{ + CcTimeEditor *self = (CcTimeEditor *)object; + GDateTime *date; + char *label; + + G_OBJECT_CLASS (cc_time_editor_parent_class)->constructed (object); + + /* Set localized identifier for AM */ + date = g_date_time_new_utc (1, 1, 1, 0, 0, 0); + label = g_date_time_format (date, "%p"); + gtk_label_set_label (self->am_label, label); + g_date_time_unref (date); + g_free (label); + + /* Set localized identifier for PM */ + date = g_date_time_new_utc (1, 1, 1, 12, 0, 0); + label = g_date_time_format (date, "%p"); + gtk_label_set_label (self->pm_label, label); + g_date_time_unref (date); + g_free (label); +} + +static void +cc_time_editor_finalize (GObject *object) +{ + CcTimeEditor *self = (CcTimeEditor *)object; + + g_clear_handle_id (&self->timer_id, g_source_remove); + g_clear_object (&self->clock_settings); + g_clear_object (&self->filechooser_settings); + + G_OBJECT_CLASS (cc_time_editor_parent_class)->finalize (object); +} + +static void +cc_time_editor_class_init (CcTimeEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = cc_time_editor_constructed; + object_class->finalize = cc_time_editor_finalize; + + signals[TIME_CHANGED] = + g_signal_new ("time-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/control-center/" + "common/cc-time-editor.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, am_pm_button); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, am_pm_stack); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, am_label); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, pm_label); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, hour_up_button); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, hour_down_button); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, minute_up_button); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, minute_down_button); + gtk_widget_class_bind_template_child (widget_class, CcTimeEditor, time_entry); + + gtk_widget_class_bind_template_callback (widget_class, editor_change_time_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, editor_change_time_released_cb); + gtk_widget_class_bind_template_callback (widget_class, editor_am_pm_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, editor_am_pm_stack_changed_cb); + + g_type_ensure (CC_TYPE_TIME_ENTRY); +} + +static void +cc_time_editor_init (CcTimeEditor *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->clock_settings = g_settings_new (CLOCK_SCHEMA); + self->filechooser_settings = g_settings_new (FILECHOOSER_SCHEMA); + + g_signal_connect_object (self->clock_settings, "changed::" CLOCK_FORMAT_KEY, + G_CALLBACK (time_editor_clock_changed_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_swapped (self->time_entry, "time-changed", + G_CALLBACK (time_editor_time_changed_cb), self); + time_editor_clock_changed_cb (self); +} + +CcTimeEditor * +cc_time_editor_new (void) +{ + return g_object_new (CC_TYPE_TIME_EDITOR, NULL); +} + +void +cc_time_editor_set_time (CcTimeEditor *self, + guint hour, + guint minute) +{ + g_return_if_fail (CC_IS_TIME_EDITOR (self)); + + cc_time_entry_set_time (self->time_entry, hour, minute); +} + +guint +cc_time_editor_get_hour (CcTimeEditor *self) +{ + g_return_val_if_fail (CC_IS_TIME_EDITOR (self), 0); + + return cc_time_entry_get_hour (self->time_entry); +} + +guint +cc_time_editor_get_minute (CcTimeEditor *self) +{ + g_return_val_if_fail (CC_IS_TIME_EDITOR (self), 0); + + return cc_time_entry_get_minute (self->time_entry); +} + +gboolean +cc_time_editor_get_am_pm (CcTimeEditor *self) +{ + g_return_val_if_fail (CC_IS_TIME_EDITOR (self), TRUE); + + return TRUE; +} + +void +cc_time_editor_set_am_pm (CcTimeEditor *self, + gboolean is_am_pm) +{ + g_return_if_fail (CC_IS_TIME_EDITOR (self)); + + cc_time_entry_set_am_pm (self->time_entry, is_am_pm); +} diff --git a/panels/common/cc-time-editor.h b/panels/common/cc-time-editor.h new file mode 100644 index 0000000..f33b10a --- /dev/null +++ b/panels/common/cc-time-editor.h @@ -0,0 +1,45 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-time-editor.h + * + * Copyright 2020 Purism SPC + * + * 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(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> + +G_BEGIN_DECLS + +#define CC_TYPE_TIME_EDITOR (cc_time_editor_get_type ()) + +G_DECLARE_FINAL_TYPE (CcTimeEditor, cc_time_editor, CC, TIME_EDITOR, AdwBin) + +CcTimeEditor *cc_time_editor_new (void); +void cc_time_editor_set_time (CcTimeEditor *self, + guint hour, + guint minute); +guint cc_time_editor_get_hour (CcTimeEditor *self); +guint cc_time_editor_get_minute (CcTimeEditor *self); +gboolean cc_time_editor_get_am_pm (CcTimeEditor *self); +void cc_time_editor_set_am_pm (CcTimeEditor *self, + gboolean is_am_pm); + +G_END_DECLS diff --git a/panels/common/cc-time-editor.ui b/panels/common/cc-time-editor.ui new file mode 100644 index 0000000..155d830 --- /dev/null +++ b/panels/common/cc-time-editor.ui @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcTimeEditor" parent="AdwBin"> + <child> + <object class="GtkGrid"> + <property name="row-spacing">6</property> + <property name="column-spacing">6</property> + + <!-- Increment Hour Button --> + <child> + <object class="GtkButton" id="hour_up_button"> + <property name="valign">center</property> + <property name="halign">center</property> + <property name="icon-name">go-up-symbolic</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + <style> + <class name="titlebutton"/> + <class name="circular"/> + <class name="flat"/> + </style> + <accessibility> + <property name="label" translatable="yes">Increment Hour</property> + </accessibility> + <child> + <object class="GtkGestureClick"> + <property name="propagation-phase">capture</property> + <signal name="pressed" handler="editor_change_time_pressed_cb" swapped="yes"/> + <signal name="released" handler="editor_change_time_released_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <!-- Increment Minute Button --> + <child> + <object class="GtkButton" id="minute_up_button"> + <property name="valign">center</property> + <property name="halign">center</property> + <property name="icon-name">go-up-symbolic</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + <style> + <class name="titlebutton"/> + <class name="circular"/> + <class name="flat"/> + </style> + <accessibility> + <property name="label" translatable="yes">Increment Minute</property> + </accessibility> + <child> + <object class="GtkGestureClick"> + <property name="propagation-phase">capture</property> + <signal name="pressed" handler="editor_change_time_pressed_cb" swapped="yes"/> + <signal name="released" handler="editor_change_time_released_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <child> + <object class="CcTimeEntry" id="time_entry"> + <layout> + <property name="column">0</property> + <property name="row">1</property> + <property name="column-span">2</property> + </layout> + <accessibility> + <property name="label" translatable="yes">Time</property> + </accessibility> + </object> + </child> + + <!-- Decrement Hour Button --> + <child> + <object class="GtkButton" id="hour_down_button"> + <property name="valign">center</property> + <property name="halign">center</property> + <property name="icon-name">go-down-symbolic</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + <style> + <class name="titlebutton"/> + <class name="circular"/> + <class name="flat"/> + </style> + <accessibility> + <property name="label" translatable="yes">Decrement Hour</property> + </accessibility> + <child> + <object class="GtkGestureClick"> + <property name="propagation-phase">capture</property> + <signal name="pressed" handler="editor_change_time_pressed_cb" swapped="yes"/> + <signal name="released" handler="editor_change_time_released_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <!-- Decrement Minute Button --> + <child> + <object class="GtkButton" id="minute_down_button"> + <property name="valign">center</property> + <property name="halign">center</property> + <property name="icon-name">go-down-symbolic</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <style> + <class name="titlebutton"/> + <class name="circular"/> + <class name="flat"/> + </style> + <accessibility> + <property name="label" translatable="yes">Decrement Minute</property> + </accessibility> + <child> + <object class="GtkGestureClick"> + <property name="propagation-phase">capture</property> + <signal name="pressed" handler="editor_change_time_pressed_cb" swapped="yes"/> + <signal name="released" handler="editor_change_time_released_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <!-- AM/PM Button --> + <child> + <object class="GtkButton" id="am_pm_button"> + <property name="valign">center</property> + <signal name="clicked" handler="editor_am_pm_button_clicked_cb" swapped="yes"/> + <layout> + <property name="column">2</property> + <property name="row">1</property> + </layout> + <child> + <object class="GtkStack" id="am_pm_stack"> + <signal name="notify::visible-child" handler="editor_am_pm_stack_changed_cb" swapped="yes"/> + <child> + <object class="GtkLabel" id="am_label"> + <attributes> + <attribute name="scale" value="1.4"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="pm_label"> + <attributes> + <attribute name="scale" value="1.4"/> + </attributes> + </object> + </child> + </object> + </child> + </object> + </child> + + </object> + </child> + </template> +</interface> diff --git a/panels/common/cc-time-entry.c b/panels/common/cc-time-entry.c new file mode 100644 index 0000000..421a98b --- /dev/null +++ b/panels/common/cc-time-entry.c @@ -0,0 +1,660 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-time-entry.c + * + * Copyright 2020 Purism SPC + * + * 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(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-time-entry" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "cc-time-entry.h" + +#define SEPARATOR_INDEX 2 +#define END_INDEX 4 +#define EMIT_CHANGED_TIMEOUT 100 + + +struct _CcTimeEntry +{ + GtkWidget parent_instance; + + GtkWidget *text; + + guint insert_text_id; + guint time_changed_id; + int hour; /* Range: 0-23 in 24H and 1-12 in 12H with is_am set/unset */ + int minute; + gboolean is_am_pm; + gboolean is_am; /* AM if TRUE. PM if FALSE. valid iff is_am_pm set */ +}; + + +static void editable_insert_text_cb (GtkText *text, + char *new_text, + gint new_text_length, + gint *position, + CcTimeEntry *self); + +static void gtk_editable_interface_init (GtkEditableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CcTimeEntry, cc_time_entry, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_editable_interface_init)); + +enum { + CHANGE_VALUE, + TIME_CHANGED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +static gboolean +emit_time_changed (CcTimeEntry *self) +{ + self->time_changed_id = 0; + + g_signal_emit (self, signals[TIME_CHANGED], 0); + + return G_SOURCE_REMOVE; +} + +static void +time_entry_fill_time (CcTimeEntry *self) +{ + g_autofree gchar *str = NULL; + + g_assert (CC_IS_TIME_ENTRY (self)); + + str = g_strdup_printf ("%02d∶%02d", self->hour, self->minute); + + g_signal_handlers_block_by_func (self->text, editable_insert_text_cb, self); + gtk_editable_set_text (GTK_EDITABLE (self->text), str); + g_signal_handlers_unblock_by_func (self->text, editable_insert_text_cb, self); +} + +static void +cursor_position_changed_cb (CcTimeEntry *self) +{ + int current_pos; + + g_assert (CC_IS_TIME_ENTRY (self)); + + current_pos = gtk_editable_get_position (GTK_EDITABLE (self)); + + g_signal_handlers_block_by_func (self->text, cursor_position_changed_cb, self); + + /* If cursor is on ‘:’ move to the next field */ + if (current_pos == SEPARATOR_INDEX) + gtk_editable_set_position (GTK_EDITABLE (self->text), current_pos + 1); + + /* If cursor is after the last digit and without selection, move to last digit */ + if (current_pos > END_INDEX && + !gtk_editable_get_selection_bounds (GTK_EDITABLE (self->text), NULL, NULL)) + gtk_editable_set_position (GTK_EDITABLE (self->text), END_INDEX); + + g_signal_handlers_unblock_by_func (self->text, cursor_position_changed_cb, self); +} + +static void +entry_selection_changed_cb (CcTimeEntry *self) +{ + GtkEditable *editable; + + g_assert (CC_IS_TIME_ENTRY (self)); + + editable = GTK_EDITABLE (self->text); + + g_signal_handlers_block_by_func (self->text, cursor_position_changed_cb, self); + + /* If cursor is after the last digit and without selection, move to last digit */ + if (gtk_editable_get_position (editable) > END_INDEX && + !gtk_editable_get_selection_bounds (editable, NULL, NULL)) + gtk_editable_set_position (editable, END_INDEX); + + g_signal_handlers_unblock_by_func (self->text, cursor_position_changed_cb, self); +} + +static void +editable_insert_text_cb (GtkText *text, + char *new_text, + gint new_text_length, + gint *position, + CcTimeEntry *self) +{ + g_assert (CC_IS_TIME_ENTRY (self)); + + if (new_text_length == -1) + new_text_length = strlen (new_text); + + if (new_text_length == 5) + { + const gchar *text = gtk_editable_get_text (GTK_EDITABLE (self)); + guint16 text_length; + + text_length = g_utf8_strlen (text, -1); + + /* Return if the text matches XX:XX template (where X is a number) */ + if (text_length == 0 && + strstr (new_text, "0123456789:") == new_text + new_text_length && + strchr (new_text, ':') == strrchr (new_text, ':')) + return; + } + + /* Insert text if single digit number */ + if (new_text_length == 1 && + strspn (new_text, "0123456789")) + { + int pos, number; + + pos = *position; + number = *new_text - '0'; + + if (pos == 0) + self->hour = self->hour % 10 + number * 10; + else if (pos == 1) + self->hour = self->hour / 10 * 10 + number; + else if (pos == 3) + self->minute = self->minute % 10 + number * 10; + else if (pos == 4) + self->minute = self->minute / 10 * 10 + number; + + if (self->is_am_pm) + self->hour = CLAMP (self->hour, 1, 12); + else + self->hour = CLAMP (self->hour, 0, 23); + + self->minute = CLAMP (self->minute, 0, 59); + + g_signal_stop_emission_by_name (text, "insert-text"); + time_entry_fill_time (self); + *position = pos + 1; + + g_clear_handle_id (&self->time_changed_id, g_source_remove); + self->time_changed_id = g_timeout_add (EMIT_CHANGED_TIMEOUT, + (GSourceFunc)emit_time_changed, self); + return; + } + + /* Warn otherwise */ + g_signal_stop_emission_by_name (text, "insert-text"); + gtk_widget_error_bell (GTK_WIDGET (self)); +} + + +static gboolean +change_value_cb (GtkWidget *widget, + GVariant *arguments, + gpointer user_data) +{ + CcTimeEntry *self = CC_TIME_ENTRY (widget); + GtkScrollType type; + int position; + + g_assert (CC_IS_TIME_ENTRY (self)); + + type = g_variant_get_int32 (arguments); + position = gtk_editable_get_position (GTK_EDITABLE (self)); + + if (position > SEPARATOR_INDEX) + { + if (type == GTK_SCROLL_STEP_UP) + self->minute++; + else + self->minute--; + + if (self->minute >= 60) + self->minute = 0; + else if (self->minute <= -1) + self->minute = 59; + } + else + { + if (type == GTK_SCROLL_STEP_UP) + self->hour++; + else + self->hour--; + + if (self->is_am_pm) + { + if (self->hour > 12) + self->hour = 1; + else if (self->hour < 1) + self->hour = 12; + } + else + { + if (self->hour >= 24) + self->hour = 0; + else if (self->hour <= -1) + self->hour = 23; + } + } + + time_entry_fill_time (self); + gtk_editable_set_position (GTK_EDITABLE (self), position); + + g_clear_handle_id (&self->time_changed_id, g_source_remove); + self->time_changed_id = g_timeout_add (EMIT_CHANGED_TIMEOUT, + (GSourceFunc)emit_time_changed, self); + + return GDK_EVENT_STOP; +} + +static void +value_changed_cb (CcTimeEntry *self, + GtkScrollType type) +{ + g_autoptr(GVariant) value; + + g_assert (CC_IS_TIME_ENTRY (self)); + + value = g_variant_new_int32 (type); + + change_value_cb (GTK_WIDGET (self), value, NULL); +} + +static void +on_text_cut_clipboard_cb (GtkText *text, + CcTimeEntry *self) +{ + gtk_widget_error_bell (GTK_WIDGET (self)); + g_signal_stop_emission_by_name (text, "cut-clipboard"); +} + +static void +on_text_delete_from_cursor_cb (GtkText *text, + GtkDeleteType *type, + gint count, + CcTimeEntry *self) +{ + gtk_widget_error_bell (GTK_WIDGET (self)); + g_signal_stop_emission_by_name (text, "delete-from-cursor"); +} + +static void +on_text_move_cursor_cb (GtkText *text, + GtkMovementStep step, + gint count, + gboolean extend, + CcTimeEntry *self) +{ + int current_pos; + + current_pos = gtk_editable_get_position (GTK_EDITABLE (self)); + + /* If cursor is on ‘:’ move backward/forward depending on the current movement */ + if ((step == GTK_MOVEMENT_LOGICAL_POSITIONS || + step == GTK_MOVEMENT_VISUAL_POSITIONS) && + current_pos + count == SEPARATOR_INDEX) + count > 0 ? count++ : count--; + + g_signal_handlers_block_by_func (text, on_text_move_cursor_cb, self); + gtk_editable_set_position (GTK_EDITABLE (text), current_pos + count); + g_signal_handlers_unblock_by_func (text, on_text_move_cursor_cb, self); + + g_signal_stop_emission_by_name (text, "move-cursor"); +} + +static void +on_text_paste_clipboard_cb (GtkText *text, + CcTimeEntry *self) +{ + gtk_widget_error_bell (GTK_WIDGET (self)); + g_signal_stop_emission_by_name (text, "paste-clipboard"); +} + +static void +on_text_toggle_overwrite_cb (GtkText *text, + CcTimeEntry *self) +{ + gtk_widget_error_bell (GTK_WIDGET (self)); + g_signal_stop_emission_by_name (text, "toggle-overwrite"); +} + +static gboolean +on_key_pressed_cb (GtkEventControllerKey *key_controller, + guint keyval, + guint keycode, + GdkModifierType state, + CcTimeEntry *self) +{ + /* Allow entering numbers */ + if (!(state & GDK_SHIFT_MASK) && + ((keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9) || + (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9))) + return GDK_EVENT_PROPAGATE; + + /* Allow navigation keys */ + if ((keyval >= GDK_KEY_Left && keyval <= GDK_KEY_Down) || + (keyval >= GDK_KEY_KP_Left && keyval <= GDK_KEY_KP_Down) || + keyval == GDK_KEY_Home || + keyval == GDK_KEY_End || + keyval == GDK_KEY_Menu) + return GDK_EVENT_PROPAGATE; + + if (state & (GDK_CONTROL_MASK | GDK_ALT_MASK)) + return GDK_EVENT_PROPAGATE; + + if (keyval == GDK_KEY_Tab) + { + /* If focus is on Hour field skip to minute field */ + if (gtk_editable_get_position (GTK_EDITABLE (self)) <= 1) + { + gtk_editable_set_position (GTK_EDITABLE (self), SEPARATOR_INDEX + 1); + + return GDK_EVENT_STOP; + } + + return GDK_EVENT_PROPAGATE; + } + + /* Shift-Tab */ + if (keyval == GDK_KEY_ISO_Left_Tab) + { + /* If focus is on Minute field skip back to Hour field */ + if (gtk_editable_get_position (GTK_EDITABLE (self)) >= 2) + { + gtk_editable_set_position (GTK_EDITABLE (self), 0); + + return GDK_EVENT_STOP; + } + + return GDK_EVENT_PROPAGATE; + } + + return GDK_EVENT_STOP; +} + +static GtkEditable * +cc_time_entry_get_delegate (GtkEditable *editable) +{ + CcTimeEntry *self = CC_TIME_ENTRY (editable); + return GTK_EDITABLE (self->text); +} + +static void +gtk_editable_interface_init (GtkEditableInterface *iface) +{ + iface->get_delegate = cc_time_entry_get_delegate; +} + +static void +cc_time_entry_constructed (GObject *object) +{ + CcTimeEntry *self = CC_TIME_ENTRY (object); + PangoAttrList *list; + PangoAttribute *attribute; + + G_OBJECT_CLASS (cc_time_entry_parent_class)->constructed (object); + + time_entry_fill_time (CC_TIME_ENTRY (object)); + + list = pango_attr_list_new (); + + attribute = pango_attr_size_new (PANGO_SCALE * 32); + pango_attr_list_insert (list, attribute); + + attribute = pango_attr_weight_new (PANGO_WEIGHT_LIGHT); + pango_attr_list_insert (list, attribute); + + /* Use tabular(monospace) letters */ + attribute = pango_attr_font_features_new ("tnum"); + pango_attr_list_insert (list, attribute); + + gtk_text_set_attributes (GTK_TEXT (self->text), list); + + pango_attr_list_unref (list); +} + +static void +cc_time_entry_dispose (GObject *object) +{ + CcTimeEntry *self = CC_TIME_ENTRY (object); + + gtk_editable_finish_delegate (GTK_EDITABLE (self)); + g_clear_pointer (&self->text, gtk_widget_unparent); + + G_OBJECT_CLASS (cc_time_entry_parent_class)->dispose (object); +} + +static void +cc_time_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + if (gtk_editable_delegate_get_property (object, property_id, value, pspec)) + return; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cc_time_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + if (gtk_editable_delegate_set_property (object, property_id, value, pspec)) + return; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cc_time_entry_class_init (CcTimeEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = cc_time_entry_constructed; + object_class->dispose = cc_time_entry_dispose; + object_class->get_property = cc_time_entry_get_property; + object_class->set_property = cc_time_entry_set_property; + + signals[CHANGE_VALUE] = + g_signal_new ("change-value", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_ACTION, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, 1, + GTK_TYPE_SCROLL_TYPE); + + signals[TIME_CHANGED] = + g_signal_new ("time-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_editable_install_properties (object_class, 1); + + gtk_widget_class_set_css_name (widget_class, "entry"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + + gtk_widget_class_add_binding (widget_class, GDK_KEY_Up, 0, + change_value_cb, "i", GTK_SCROLL_STEP_UP); + gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Up, 0, + change_value_cb, "i", GTK_SCROLL_STEP_UP); + gtk_widget_class_add_binding (widget_class, GDK_KEY_Down, 0, + change_value_cb, "i", GTK_SCROLL_STEP_DOWN); + gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Down, 0, + change_value_cb, "i", GTK_SCROLL_STEP_DOWN); +} + +static void +cc_time_entry_init (CcTimeEntry *self) +{ + GtkEventController *key_controller; + + key_controller = gtk_event_controller_key_new (); + gtk_event_controller_set_propagation_phase (key_controller, GTK_PHASE_CAPTURE); + g_signal_connect (key_controller, "key-pressed", G_CALLBACK (on_key_pressed_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), key_controller); + + self->text = g_object_new (GTK_TYPE_TEXT, + "input-purpose", GTK_INPUT_PURPOSE_DIGITS, + "input-hints", GTK_INPUT_HINT_NO_EMOJI, + "overwrite-mode", TRUE, + "xalign", 0.5, + "max-length", 5, + NULL); + gtk_widget_set_parent (self->text, GTK_WIDGET (self)); + gtk_editable_init_delegate (GTK_EDITABLE (self)); + g_object_connect (self->text, + "signal::cut-clipboard", on_text_cut_clipboard_cb, self, + "signal::delete-from-cursor", on_text_delete_from_cursor_cb, self, + "signal::insert-text", editable_insert_text_cb, self, + "signal::move-cursor", on_text_move_cursor_cb, self, + "swapped-signal::notify::cursor-position", cursor_position_changed_cb, self, + "swapped-signal::notify::selection-bound", entry_selection_changed_cb, self, + "signal::paste-clipboard", on_text_paste_clipboard_cb, self, + "signal::toggle-overwrite", on_text_toggle_overwrite_cb, self, + NULL); + g_signal_connect (self, "change-value", + G_CALLBACK (value_changed_cb), self); +} + +GtkWidget * +cc_time_entry_new (void) +{ + return g_object_new (CC_TYPE_TIME_ENTRY, NULL); +} + +void +cc_time_entry_set_time (CcTimeEntry *self, + guint hour, + guint minute) +{ + gboolean is_am_pm; + + g_return_if_fail (CC_IS_TIME_ENTRY (self)); + + if (cc_time_entry_get_hour (self) == hour && + cc_time_entry_get_minute (self) == minute) + return; + + is_am_pm = cc_time_entry_get_am_pm (self); + cc_time_entry_set_am_pm (self, FALSE); + + self->hour = CLAMP (hour, 0, 23); + self->minute = CLAMP (minute, 0, 59); + + cc_time_entry_set_am_pm (self, is_am_pm); + + g_signal_emit (self, signals[TIME_CHANGED], 0); + time_entry_fill_time (self); +} + +guint +cc_time_entry_get_hour (CcTimeEntry *self) +{ + g_return_val_if_fail (CC_IS_TIME_ENTRY (self), 0); + + if (!self->is_am_pm) + return self->hour; + + if (self->is_am && self->hour == 12) + return 0; + else if (self->is_am || self->hour == 12) + return self->hour; + else + return self->hour + 12; +} + +guint +cc_time_entry_get_minute (CcTimeEntry *self) +{ + g_return_val_if_fail (CC_IS_TIME_ENTRY (self), 0); + + return self->minute; +} + +gboolean +cc_time_entry_get_is_am (CcTimeEntry *self) +{ + g_return_val_if_fail (CC_IS_TIME_ENTRY (self), FALSE); + + if (self->is_am_pm) + return self->is_am; + + return self->hour < 12; +} + +void +cc_time_entry_set_is_am (CcTimeEntry *self, + gboolean is_am) +{ + g_return_if_fail (CC_IS_TIME_ENTRY (self)); + + self->is_am = !!is_am; + g_signal_emit (self, signals[TIME_CHANGED], 0); +} + +gboolean +cc_time_entry_get_am_pm (CcTimeEntry *self) +{ + g_return_val_if_fail (CC_IS_TIME_ENTRY (self), FALSE); + + return self->is_am_pm; +} + +void +cc_time_entry_set_am_pm (CcTimeEntry *self, + gboolean is_am_pm) +{ + g_return_if_fail (CC_IS_TIME_ENTRY (self)); + + if (self->is_am_pm == !!is_am_pm) + return; + + if (self->hour < 12) + self->is_am = TRUE; + else + self->is_am = FALSE; + + if (is_am_pm) + { + if (self->hour == 0) + self->hour = 12; + else if (self->hour > 12) + self->hour = self->hour - 12; + } + else + { + if (self->hour == 12 && self->is_am) + self->hour = 0; + else if (!self->is_am) + self->hour = self->hour + 12; + } + + self->is_am_pm = !!is_am_pm; + time_entry_fill_time (self); +} diff --git a/panels/common/cc-time-entry.h b/panels/common/cc-time-entry.h new file mode 100644 index 0000000..f416143 --- /dev/null +++ b/panels/common/cc-time-entry.h @@ -0,0 +1,47 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-time-entry.h + * + * Copyright 2020 Purism SPC + * + * 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(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_TIME_ENTRY (cc_time_entry_get_type ()) +G_DECLARE_FINAL_TYPE (CcTimeEntry, cc_time_entry, CC, TIME_ENTRY, GtkWidget) + +GtkWidget *cc_time_entry_new (void); +void cc_time_entry_set_time (CcTimeEntry *self, + guint hour, + guint minute); +guint cc_time_entry_get_minute (CcTimeEntry *self); +guint cc_time_entry_get_hour (CcTimeEntry *self); +gboolean cc_time_entry_get_is_am (CcTimeEntry *self); +void cc_time_entry_set_is_am (CcTimeEntry *self, + gboolean is_am); +gboolean cc_time_entry_get_am_pm (CcTimeEntry *self); +void cc_time_entry_set_am_pm (CcTimeEntry *self, + gboolean is_am_pm); + +G_END_DECLS diff --git a/panels/common/cc-util.c b/panels/common/cc-util.c new file mode 100644 index 0000000..9418d56 --- /dev/null +++ b/panels/common/cc-util.c @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * The Control Center 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. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n.h> + + +#include "cc-util.h" + +/* Combining diacritical mark? + * Basic range: [0x0300,0x036F] + * Supplement: [0x1DC0,0x1DFF] + * For Symbols: [0x20D0,0x20FF] + * Half marks: [0xFE20,0xFE2F] + */ +#define IS_CDM_UCS4(c) (((c) >= 0x0300 && (c) <= 0x036F) || \ + ((c) >= 0x1DC0 && (c) <= 0x1DFF) || \ + ((c) >= 0x20D0 && (c) <= 0x20FF) || \ + ((c) >= 0xFE20 && (c) <= 0xFE2F)) + +#define IS_SOFT_HYPHEN(c) ((c) == 0x00AD) + +/* Copied from tracker/src/libtracker-fts/tracker-parser-glib.c under the GPL + * And then from gnome-shell/src/shell-util.c + * + * Originally written by Aleksander Morgado <aleksander@gnu.org> + */ +char * +cc_util_normalize_casefold_and_unaccent (const char *str) +{ + g_autofree gchar *normalized = NULL; + gchar *tmp; + int i = 0, j = 0, ilen; + + if (str == NULL) + return NULL; + + normalized = g_utf8_normalize (str, -1, G_NORMALIZE_NFKD); + tmp = g_utf8_casefold (normalized, -1); + + ilen = strlen (tmp); + + while (i < ilen) + { + gunichar unichar; + gchar *next_utf8; + gint utf8_len; + + /* Get next character of the word as UCS4 */ + unichar = g_utf8_get_char_validated (&tmp[i], -1); + + /* Invalid UTF-8 character or end of original string. */ + if (unichar == (gunichar) -1 || + unichar == (gunichar) -2) + { + break; + } + + /* Find next UTF-8 character */ + next_utf8 = g_utf8_next_char (&tmp[i]); + utf8_len = next_utf8 - &tmp[i]; + + if (IS_CDM_UCS4 (unichar) || IS_SOFT_HYPHEN (unichar)) + { + /* If the given unichar is a combining diacritical mark, + * just update the original index, not the output one */ + i += utf8_len; + continue; + } + + /* If already found a previous combining + * diacritical mark, indexes are different so + * need to copy characters. As output and input + * buffers may overlap, need to use memmove + * instead of memcpy */ + if (i != j) + { + memmove (&tmp[j], &tmp[i], utf8_len); + } + + /* Update both indexes */ + i += utf8_len; + j += utf8_len; + } + + /* Force proper string end */ + tmp[j] = '\0'; + + return tmp; +} + +char * +cc_util_get_smart_date (GDateTime *date) +{ + g_autoptr(GDateTime) today = NULL; + g_autoptr(GDateTime) local = NULL; + GTimeSpan span; + + /* Set today date */ + local = g_date_time_new_now_local (); + today = g_date_time_new_local (g_date_time_get_year (local), + g_date_time_get_month (local), + g_date_time_get_day_of_month (local), + 0, 0, 0); + + span = g_date_time_difference (today, date); + if (span <= 0) + { + return g_strdup (_("Today")); + } + else if (span <= G_TIME_SPAN_DAY) + { + return g_strdup (_("Yesterday")); + } + else + { + if (g_date_time_get_year (date) == g_date_time_get_year (today)) + { + /* Translators: This is a date format string in the style of "Feb 24". */ + return g_date_time_format (date, _("%b %e")); + } + else + { + /* Translators: This is a date format string in the style of "Feb 24, 2013". */ + return g_date_time_format (date, _("%b %e, %Y")); + } + } +} + +/* Copied from src/plugins/properties/bacon-video-widget-properties.c + * in totem */ +char * +cc_util_time_to_string_text (gint64 msecs) +{ + g_autofree gchar *hours = NULL; + g_autofree gchar *mins = NULL; + g_autofree gchar *secs = NULL; + gint sec, min, hour, _time; + + _time = (int) (msecs / 1000); + sec = _time % 60; + _time = _time - sec; + min = (_time % (60*60)) / 60; + _time = _time - (min * 60); + hour = _time / (60*60); + + hours = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d hour", "%d hours", hour), hour); + mins = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", min), min); + secs = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d second", "%d seconds", sec), sec); + + if (hour > 0) + { + if (min > 0 && sec > 0) + { + /* 5 hours 2 minutes 12 seconds */ + return g_strdup_printf (C_("hours minutes seconds", "%s %s %s"), hours, mins, secs); + } + else if (min > 0) + { + /* 5 hours 2 minutes */ + return g_strdup_printf (C_("hours minutes", "%s %s"), hours, mins); + } + else + { + /* 5 hours */ + return g_strdup_printf (C_("hours", "%s"), hours); + } + } + else if (min > 0) + { + if (sec > 0) + { + /* 2 minutes 12 seconds */ + return g_strdup_printf (C_("minutes seconds", "%s %s"), mins, secs); + } + else + { + /* 2 minutes */ + return g_strdup_printf (C_("minutes", "%s"), mins); + } + } + else if (sec > 0) + { + /* 10 seconds */ + return g_strdup (secs); + } + else + { + /* 0 seconds */ + return g_strdup (_("0 seconds")); + } +} diff --git a/panels/common/cc-util.h b/panels/common/cc-util.h new file mode 100644 index 0000000..131c2af --- /dev/null +++ b/panels/common/cc-util.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * The Control Center 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. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#pragma once + +#include <glib.h> + +char * cc_util_normalize_casefold_and_unaccent (const char *str); +char * cc_util_get_smart_date (GDateTime *date); +char * cc_util_time_to_string_text (gint64 msecs); diff --git a/panels/common/common.gresource.xml b/panels/common/common.gresource.xml new file mode 100644 index 0000000..b8c2946 --- /dev/null +++ b/panels/common/common.gresource.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/common"> + <file preprocess="xml-stripblanks">cc-language-chooser.ui</file> + <file preprocess="xml-stripblanks">cc-language-row.ui</file> + <file preprocess="xml-stripblanks">cc-list-row.ui</file> + <file preprocess="xml-stripblanks">cc-time-editor.ui</file> + <file preprocess="xml-stripblanks">cc-permission-infobar.ui</file> + </gresource> +</gresources> diff --git a/panels/common/gnome-control-center.rules.in b/panels/common/gnome-control-center.rules.in new file mode 100644 index 0000000..22cf785 --- /dev/null +++ b/panels/common/gnome-control-center.rules.in @@ -0,0 +1,13 @@ +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.locale1.set-locale" || + action.id == "org.freedesktop.locale1.set-keyboard" || + action.id == "org.freedesktop.ModemManager1.Device.Control" || + action.id == "org.freedesktop.hostname1.set-static-hostname" || + action.id == "org.freedesktop.hostname1.set-hostname" || + action.id == "org.gnome.controlcenter.datetime.configure") && + subject.local && + subject.active && + subject.isInGroup ("@PRIVILEGED_GROUP@")) { + return polkit.Result.YES; + } +}); diff --git a/panels/common/gnome-settings-bus.h b/panels/common/gnome-settings-bus.h new file mode 100644 index 0000000..122289f --- /dev/null +++ b/panels/common/gnome-settings-bus.h @@ -0,0 +1,14 @@ +/* Stub to replace gnome-settings-daemon's + * gnome-settings-bus.h helpers */ + +#include <gdk/x11/gdkx.h> + +#ifdef GDK_WINDOWING_WAYLAND + +static inline gboolean +gnome_settings_is_wayland (void) +{ + return !GDK_IS_X11_DISPLAY (gdk_display_get_default ()); +} + +#endif /* GDK_WINDOWING_WAYLAND */ diff --git a/panels/common/gsd-device-manager.c b/panels/common/gsd-device-manager.c new file mode 100644 index 0000000..6283ccb --- /dev/null +++ b/panels/common/gsd-device-manager.c @@ -0,0 +1,688 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2014 Red Hat + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#include "config.h" + +#include <string.h> +#include <gudev/gudev.h> + +#include "gsd-device-manager.h" +#include "gsd-common-enums.h" +#include "gnome-settings-bus.h" +#include "gsd-input-helper.h" + +#ifdef GDK_WINDOWING_X11 +#include <gdk/x11/gdkx.h> +#endif +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/wayland/gdkwayland.h> +#endif + +typedef struct +{ + gchar *name; + gchar *device_file; + gchar *vendor_id; + gchar *product_id; + gchar *group; + GsdDeviceType type; + guint width; + guint height; +} GsdDevicePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GsdDevice, gsd_device, G_TYPE_OBJECT) + +typedef struct +{ + GObject parent_instance; + GHashTable *devices; + GUdevClient *udev_client; +} GsdDeviceManagerPrivate; + +enum { + PROP_NAME = 1, + PROP_DEVICE_FILE, + PROP_VENDOR_ID, + PROP_PRODUCT_ID, + PROP_TYPE, + PROP_WIDTH, + PROP_HEIGHT, + PROP_GROUP +}; + +enum { + DEVICE_ADDED, + DEVICE_REMOVED, + DEVICE_CHANGED, + N_SIGNALS +}; + +/* Index matches GsdDeviceType */ +const gchar *udev_ids[] = { + "ID_INPUT_MOUSE", + "ID_INPUT_KEYBOARD", + "ID_INPUT_TOUCHPAD", + "ID_INPUT_TABLET", + "ID_INPUT_TOUCHSCREEN", + "ID_INPUT_TABLET_PAD", +}; + +static guint signals[N_SIGNALS] = { 0 }; + +G_DEFINE_TYPE_WITH_PRIVATE (GsdDeviceManager, gsd_device_manager, G_TYPE_OBJECT) + +static void +gsd_device_init (GsdDevice *device) +{ +} + +static void +gsd_device_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsdDevicePrivate *priv; + + priv = gsd_device_get_instance_private (GSD_DEVICE (object)); + + switch (prop_id) { + case PROP_NAME: + priv->name = g_value_dup_string (value); + break; + case PROP_DEVICE_FILE: + priv->device_file = g_value_dup_string (value); + break; + case PROP_VENDOR_ID: + priv->vendor_id = g_value_dup_string (value); + break; + case PROP_PRODUCT_ID: + priv->product_id = g_value_dup_string (value); + break; + case PROP_TYPE: + priv->type = g_value_get_flags (value); + break; + case PROP_WIDTH: + priv->width = g_value_get_uint (value); + break; + case PROP_HEIGHT: + priv->height = g_value_get_uint (value); + break; + case PROP_GROUP: + priv->group = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsd_device_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsdDevicePrivate *priv; + + priv = gsd_device_get_instance_private (GSD_DEVICE (object)); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_DEVICE_FILE: + g_value_set_string (value, priv->device_file); + break; + case PROP_VENDOR_ID: + g_value_set_string (value, priv->vendor_id); + break; + case PROP_PRODUCT_ID: + g_value_set_string (value, priv->product_id); + break; + case PROP_TYPE: + g_value_set_flags (value, priv->type); + break; + case PROP_WIDTH: + g_value_set_uint (value, priv->width); + break; + case PROP_HEIGHT: + g_value_set_uint (value, priv->height); + break; + case PROP_GROUP: + g_value_set_string (value, priv->group); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsd_device_finalize (GObject *object) +{ + GsdDevicePrivate *priv; + + priv = gsd_device_get_instance_private (GSD_DEVICE (object)); + + g_free (priv->name); + g_free (priv->vendor_id); + g_free (priv->product_id); + g_free (priv->device_file); + g_free (priv->group); + + G_OBJECT_CLASS (gsd_device_parent_class)->finalize (object); +} + +static void +gsd_device_class_init (GsdDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gsd_device_set_property; + object_class->get_property = gsd_device_get_property; + object_class->finalize = gsd_device_finalize; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_DEVICE_FILE, + g_param_spec_string ("device-file", + "Device file", + "Device file", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_VENDOR_ID, + g_param_spec_string ("vendor-id", + "Vendor ID", + "Vendor ID", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_PRODUCT_ID, + g_param_spec_string ("product-id", + "Product ID", + "Product ID", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_TYPE, + g_param_spec_flags ("type", + "Device type", + "Device type", + GSD_TYPE_DEVICE_TYPE, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_WIDTH, + g_param_spec_uint ("width", + "Width", + "Width", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_HEIGHT, + g_param_spec_uint ("height", + "Height", + "Height", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_GROUP, + g_param_spec_string ("group", + "Group", + "Group", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gsd_device_manager_finalize (GObject *object) +{ + GsdDeviceManager *manager = GSD_DEVICE_MANAGER (object); + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + + g_hash_table_destroy (priv->devices); + g_object_unref (priv->udev_client); + + G_OBJECT_CLASS (gsd_device_manager_parent_class)->finalize (object); +} + +static GList * +gsd_device_manager_real_list_devices (GsdDeviceManager *manager, + GsdDeviceType type) +{ + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + GsdDeviceType device_type; + GList *devices = NULL; + GHashTableIter iter; + GsdDevice *device; + + g_hash_table_iter_init (&iter, priv->devices); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &device)) { + device_type = gsd_device_get_device_type (device); + + if ((device_type & type) == type) + devices = g_list_prepend (devices, device); + } + + return devices; +} + +static GsdDevice * +gsd_device_manager_real_lookup_device (GsdDeviceManager *manager, + GdkDevice *gdk_device) +{ + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + GdkDisplay *display = gdk_device_get_display (gdk_device); + const gchar *node_path = NULL; + GHashTableIter iter; + GsdDevice *device; + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (display)) + node_path = xdevice_get_device_node (gdk_x11_device_get_id (gdk_device)); +#endif +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (display)) + node_path = g_strdup (gdk_wayland_device_get_node_path (gdk_device)); +#endif + if (!node_path) + return NULL; + + g_hash_table_iter_init (&iter, priv->devices); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &device)) { + if (g_strcmp0 (node_path, + gsd_device_get_device_file (device)) == 0) { + return device; + } + } + + return NULL; +} + +static void +gsd_device_manager_class_init (GsdDeviceManagerClass *klass) +{ + GsdDeviceManagerClass *manager_class = GSD_DEVICE_MANAGER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_device_manager_finalize; + manager_class->list_devices = gsd_device_manager_real_list_devices; + manager_class->lookup_device = gsd_device_manager_real_lookup_device; + + signals[DEVICE_ADDED] = + g_signal_new ("device-added", + GSD_TYPE_DEVICE_MANAGER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdDeviceManagerClass, device_added), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE); + + signals[DEVICE_REMOVED] = + g_signal_new ("device-removed", + GSD_TYPE_DEVICE_MANAGER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdDeviceManagerClass, device_removed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE); + + signals[DEVICE_CHANGED] = + g_signal_new ("device-changed", + GSD_TYPE_DEVICE_MANAGER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdDeviceManagerClass, device_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE); +} + +static GsdDeviceType +udev_device_get_device_type (GUdevDevice *device) +{ + GsdDeviceType type = 0; + gint i; + + for (i = 0; i < G_N_ELEMENTS (udev_ids); i++) { + if (g_udev_device_get_property_as_boolean (device, udev_ids[i])) + type |= (1 << i); + } + + return type; +} + +static gboolean +device_is_evdev (GUdevDevice *device) +{ + const gchar *device_file; + + device_file = g_udev_device_get_device_file (device); + + if (!device_file || strstr (device_file, "/event") == NULL) + return FALSE; + + return g_udev_device_get_property_as_boolean (device, "ID_INPUT"); +} + +static GsdDevice * +create_device (GUdevDevice *udev_device, GUdevDevice *parent) +{ + const gchar *vendor, *product, *name, *group; + guint width, height; + + name = g_udev_device_get_sysfs_attr (parent, "name"); + vendor = g_udev_device_get_property (udev_device, "ID_VENDOR_ID"); + product = g_udev_device_get_property (udev_device, "ID_MODEL_ID"); + + if (!vendor || !product) { + vendor = g_udev_device_get_sysfs_attr (udev_device, "device/id/vendor"); + product = g_udev_device_get_sysfs_attr (udev_device, "device/id/product"); + } + + width = g_udev_device_get_property_as_int (udev_device, "ID_INPUT_WIDTH_MM"); + height = g_udev_device_get_property_as_int (udev_device, "ID_INPUT_HEIGHT_MM"); + + group = g_udev_device_get_property (udev_device, "LIBINPUT_DEVICE_GROUP"); + + return g_object_new (GSD_TYPE_DEVICE, + "name", name, + "device-file", g_udev_device_get_device_file (udev_device), + "type", udev_device_get_device_type (udev_device), + "vendor-id", vendor, + "product-id", product, + "width", width, + "height", height, + "group", group, + NULL); +} + +static void +add_device (GsdDeviceManager *manager, + GUdevDevice *udev_device) +{ + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + g_autoptr(GUdevDevice) parent = NULL; + GsdDevice *device; + const gchar *syspath; + + parent = g_udev_device_get_parent (udev_device); + + if (!parent) + return; + + device = create_device (udev_device, parent); + syspath = g_udev_device_get_sysfs_path (udev_device); + g_hash_table_insert (priv->devices, g_strdup (syspath), device); + g_signal_emit_by_name (manager, "device-added", device); +} + +static void +remove_device (GsdDeviceManager *manager, + GUdevDevice *udev_device) +{ + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + GsdDevice *device; + const gchar *syspath; + + syspath = g_udev_device_get_sysfs_path (udev_device); + device = g_hash_table_lookup (priv->devices, syspath); + + if (!device) + return; + + g_hash_table_steal (priv->devices, syspath); + g_signal_emit_by_name (manager, "device-removed", device); + + g_object_unref (device); +} + +static void +udev_event_cb (GUdevClient *client, + gchar *action, + GUdevDevice *device, + GsdDeviceManager *manager) +{ + if (!device_is_evdev (device)) + return; + + if (g_strcmp0 (action, "add") == 0) { + add_device (manager, device); + } else if (g_strcmp0 (action, "remove") == 0) { + remove_device (manager, device); + } +} + +static void +gsd_device_manager_init (GsdDeviceManager *manager) +{ + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + const gchar *subsystems[] = { "input", NULL }; + g_autoptr(GList) devices = NULL; + GList *l; + + priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + priv->udev_client = g_udev_client_new (subsystems); + g_signal_connect (priv->udev_client, "uevent", + G_CALLBACK (udev_event_cb), manager); + + devices = g_udev_client_query_by_subsystem (priv->udev_client, + subsystems[0]); + + for (l = devices; l; l = l->next) { + g_autoptr(GUdevDevice) device = l->data; + + if (device_is_evdev (device)) + add_device (manager, device); + } +} + +GsdDeviceManager * +gsd_device_manager_get (void) +{ + GsdDeviceManager *manager; + GdkDisplay *display; + + display = gdk_display_get_default (); + g_return_val_if_fail (display != NULL, NULL); + + manager = g_object_get_data (G_OBJECT (display), "gsd-device-manager-data"); + + if (!manager) { + manager = g_object_new (GSD_TYPE_DEVICE_MANAGER, + NULL); + + g_object_set_data_full (G_OBJECT (display), "gsd-device-manager-data", + manager, (GDestroyNotify) g_object_unref); + } + + return manager; +} + +GList * +gsd_device_manager_list_devices (GsdDeviceManager *manager, + GsdDeviceType type) +{ + g_return_val_if_fail (GSD_IS_DEVICE_MANAGER (manager), NULL); + + return GSD_DEVICE_MANAGER_GET_CLASS (manager)->list_devices (manager, type); +} + +GsdDeviceType +gsd_device_get_device_type (GsdDevice *device) +{ + GsdDevicePrivate *priv; + + g_return_val_if_fail (GSD_IS_DEVICE (device), 0); + + priv = gsd_device_get_instance_private (device); + + return priv->type; +} + +void +gsd_device_get_device_ids (GsdDevice *device, + const gchar **vendor, + const gchar **product) +{ + GsdDevicePrivate *priv; + + g_return_if_fail (GSD_IS_DEVICE (device)); + + priv = gsd_device_get_instance_private (device); + + if (vendor) + *vendor = priv->vendor_id; + if (product) + *product = priv->product_id; +} + +GSettings * +gsd_device_get_settings (GsdDevice *device) +{ + const gchar *schema = NULL, *vendor, *product; + GsdDeviceType type; + g_autofree gchar *path = NULL; + + g_return_val_if_fail (GSD_IS_DEVICE (device), NULL); + + type = gsd_device_get_device_type (device); + + if (type & (GSD_DEVICE_TYPE_TOUCHSCREEN | GSD_DEVICE_TYPE_TABLET)) { + gsd_device_get_device_ids (device, &vendor, &product); + + if (type & GSD_DEVICE_TYPE_TOUCHSCREEN) { + schema = "org.gnome.desktop.peripherals.touchscreen"; + path = g_strdup_printf ("/org/gnome/desktop/peripherals/touchscreens/%s:%s/", + vendor, product); + } else if (type & GSD_DEVICE_TYPE_TABLET) { + schema = "org.gnome.desktop.peripherals.tablet"; + path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/", + vendor, product); + } + } else if (type & (GSD_DEVICE_TYPE_MOUSE | GSD_DEVICE_TYPE_TOUCHPAD)) { + schema = "org.gnome.desktop.peripherals.mouse"; + } else if (type & GSD_DEVICE_TYPE_KEYBOARD) { + schema = "org.gnome.desktop.peripherals.keyboard"; + } else { + return NULL; + } + + if (path) { + return g_settings_new_with_path (schema, path); + } else { + return g_settings_new (schema); + } +} + +const gchar * +gsd_device_get_name (GsdDevice *device) +{ + GsdDevicePrivate *priv; + + g_return_val_if_fail (GSD_IS_DEVICE (device), NULL); + + priv = gsd_device_get_instance_private (device); + + return priv->name; +} + +const gchar * +gsd_device_get_device_file (GsdDevice *device) +{ + GsdDevicePrivate *priv; + + g_return_val_if_fail (GSD_IS_DEVICE (device), NULL); + + priv = gsd_device_get_instance_private (device); + + return priv->device_file; +} + +gboolean +gsd_device_get_dimensions (GsdDevice *device, + guint *width, + guint *height) +{ + GsdDevicePrivate *priv; + + g_return_val_if_fail (GSD_IS_DEVICE (device), FALSE); + + priv = gsd_device_get_instance_private (device); + + if (width) + *width = priv->width; + if (height) + *height = priv->height; + + return priv->width > 0 && priv->height > 0; +} + +GsdDevice * +gsd_device_manager_lookup_gdk_device (GsdDeviceManager *manager, + GdkDevice *gdk_device) +{ + GsdDeviceManagerClass *klass; + + g_return_val_if_fail (GSD_IS_DEVICE_MANAGER (manager), NULL); + g_return_val_if_fail (GDK_IS_DEVICE (gdk_device), NULL); + + klass = GSD_DEVICE_MANAGER_GET_CLASS (manager); + if (!klass->lookup_device) + return NULL; + + return klass->lookup_device (manager, gdk_device); +} + +gboolean +gsd_device_shares_group (GsdDevice *device1, + GsdDevice *device2) +{ + GsdDevicePrivate *priv1, *priv2; + + priv1 = gsd_device_get_instance_private (GSD_DEVICE (device1)); + priv2 = gsd_device_get_instance_private (GSD_DEVICE (device2)); + + /* Don't group NULLs together */ + if (!priv1->group && !priv2->group) + return FALSE; + + return g_strcmp0 (priv1->group, priv2->group) == 0; +} diff --git a/panels/common/gsd-device-manager.h b/panels/common/gsd-device-manager.h new file mode 100644 index 0000000..f48b5ad --- /dev/null +++ b/panels/common/gsd-device-manager.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2014 Carlos Garnacho <carlosg@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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include <gdk/gdk.h> +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GSD_TYPE_DEVICE (gsd_device_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsdDevice, gsd_device, GSD, DEVICE, GObject) + +#define GSD_TYPE_DEVICE_MANAGER (gsd_device_manager_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsdDeviceManager, gsd_device_manager, GSD, DEVICE_MANAGER, GObject) + +typedef enum { + GSD_DEVICE_TYPE_MOUSE = 1 << 0, + GSD_DEVICE_TYPE_KEYBOARD = 1 << 1, + GSD_DEVICE_TYPE_TOUCHPAD = 1 << 2, + GSD_DEVICE_TYPE_TABLET = 1 << 3, + GSD_DEVICE_TYPE_TOUCHSCREEN = 1 << 4, + GSD_DEVICE_TYPE_PAD = 1 << 5 +} GsdDeviceType; + +struct _GsdDeviceClass { + GObjectClass parent_class; +}; + +struct _GsdDeviceManagerClass +{ + GObjectClass parent_class; + + GList * (* list_devices) (GsdDeviceManager *manager, + GsdDeviceType type); + + void (* device_added) (GsdDeviceManager *manager, + GsdDevice *device); + void (* device_removed) (GsdDeviceManager *manager, + GsdDevice *device); + void (* device_changed) (GsdDeviceManager *manager, + GsdDevice *device); + + GsdDevice * (* lookup_device) (GsdDeviceManager *manager, + GdkDevice *gdk_device); +}; + +GsdDeviceManager * gsd_device_manager_get (void); +GList * gsd_device_manager_list_devices (GsdDeviceManager *manager, + GsdDeviceType type); + +const gchar * gsd_device_get_name (GsdDevice *device); +GsdDeviceType gsd_device_get_device_type (GsdDevice *device); +void gsd_device_get_device_ids (GsdDevice *device, + const gchar **vendor, + const gchar **product); +GSettings * gsd_device_get_settings (GsdDevice *device); + +const gchar * gsd_device_get_device_file (GsdDevice *device); +gboolean gsd_device_get_dimensions (GsdDevice *device, + guint *width, + guint *height); + +GsdDevice * gsd_device_manager_lookup_gdk_device (GsdDeviceManager *manager, + GdkDevice *gdk_device); +gboolean gsd_device_shares_group (GsdDevice *device1, + GsdDevice *device2); + +G_END_DECLS diff --git a/panels/common/gsd-input-helper.c b/panels/common/gsd-input-helper.c new file mode 100644 index 0000000..025070e --- /dev/null +++ b/panels/common/gsd-input-helper.c @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Bastien Nocera <hadess@hadess.net> + * + * 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 <string.h> + +#include <gdk/gdk.h> +#include <gdk/x11/gdkx.h> + +#include <sys/types.h> +#include <X11/Xatom.h> +#include <X11/extensions/XInput2.h> + +#include "gsd-input-helper.h" +#include "gsd-device-manager.h" + +static gboolean +device_type_is_present (GsdDeviceType type) +{ + g_autoptr(GList) l = gsd_device_manager_list_devices (gsd_device_manager_get (), + type); + return l != NULL; +} + +gboolean +touchscreen_is_present (void) +{ + return device_type_is_present (GSD_DEVICE_TYPE_TOUCHSCREEN); +} + +gboolean +touchpad_is_present (void) +{ + return device_type_is_present (GSD_DEVICE_TYPE_TOUCHPAD); +} + +gboolean +mouse_is_present (void) +{ + return device_type_is_present (GSD_DEVICE_TYPE_MOUSE); +} + +char * +xdevice_get_device_node (int deviceid) +{ + GdkDisplay *display; + Atom prop; + Atom act_type; + int act_format; + unsigned long nitems, bytes_after; + unsigned char *data; + char *ret; + + display = gdk_display_get_default (); + gdk_display_sync (display); + + prop = XInternAtom (GDK_DISPLAY_XDISPLAY (display), "Device Node", False); + if (!prop) + return NULL; + + gdk_x11_display_error_trap_push (display); + + if (!XIGetProperty (GDK_DISPLAY_XDISPLAY (display), + deviceid, prop, 0, 1000, False, + AnyPropertyType, &act_type, &act_format, + &nitems, &bytes_after, &data) == Success) { + gdk_x11_display_error_trap_pop_ignored (display); + return NULL; + } + if (gdk_x11_display_error_trap_pop (display)) + goto out; + + if (nitems == 0) + goto out; + + if (act_type != XA_STRING) + goto out; + + /* Unknown string format */ + if (act_format != 8) + goto out; + + ret = g_strdup ((char *) data); + + XFree (data); + return ret; + +out: + XFree (data); + return NULL; +} diff --git a/panels/common/gsd-input-helper.h b/panels/common/gsd-input-helper.h new file mode 100644 index 0000000..8fda1e6 --- /dev/null +++ b/panels/common/gsd-input-helper.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Bastien Nocera <hadess@hadess.net> + * + * 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 + +G_BEGIN_DECLS + +#include <glib.h> + +gboolean touchpad_is_present (void); +gboolean touchscreen_is_present (void); +gboolean mouse_is_present (void); + +char * xdevice_get_device_node (int deviceid); + +G_END_DECLS diff --git a/panels/common/hostname-helper.c b/panels/common/hostname-helper.c new file mode 100644 index 0000000..a8f7c41 --- /dev/null +++ b/panels/common/hostname-helper.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 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 <glib.h> +#include <glib/gi18n.h> +#include <string.h> + +#include "hostname-helper.h" + +static char * +allowed_chars (void) +{ + GString *s; + char i; + + s = g_string_new (NULL); + for (i = 'a'; i <= 'z'; i++) + g_string_append_c (s, i); + for (i = 'A'; i <= 'Z'; i++) + g_string_append_c (s, i); + for (i = '0'; i <= '9'; i++) + g_string_append_c (s, i); + g_string_append_c (s, '-'); + + return g_string_free (s, FALSE); +} + +static char * +remove_leading_dashes (char *input) +{ + char *start; + + for (start = input; *start && (*start == '-'); start++) + ; + + memmove (input, start, strlen (start) + 1); + + return input; +} + +static gboolean +is_empty (const char *input) +{ + if (input == NULL || + *input == '\0') + return TRUE; + return FALSE; +} + +static char * +remove_trailing_dashes (char *input) +{ + int len; + + len = strlen (input); + while (len--) { + if (input[len] == '-') + input[len] = '\0'; + else + break; + } + return input; +} + +static char * +remove_apostrophes (char *input) +{ + char *apo; + + while ((apo = strchr (input, '\'')) != NULL) + memmove (apo, apo + 1, strlen (apo)); + return input; +} + +static char * +remove_duplicate_dashes (char *input) +{ + char *dashes; + + while ((dashes = strstr (input, "--")) != NULL) + memmove (dashes, dashes + 1, strlen (dashes)); + return input; +} + +#define CHECK if (is_empty (result)) return g_strdup ("localhost") + +char * +pretty_hostname_to_static (const char *pretty, + gboolean for_display) +{ + g_autofree gchar *result = NULL; + g_autofree gchar *valid_chars = NULL; + g_autofree gchar *composed = NULL; + + g_return_val_if_fail (pretty != NULL, NULL); + g_return_val_if_fail (g_utf8_validate (pretty, -1, NULL), NULL); + + g_debug ("Input: '%s'", pretty); + + composed = g_utf8_normalize (pretty, -1, G_NORMALIZE_ALL_COMPOSE); + g_debug ("\tcomposed: '%s'", composed); + /* Transform the pretty hostname to ASCII */ + result = g_str_to_ascii (composed, NULL); + g_debug ("\ttranslit: '%s'", result); + + CHECK; + + /* Remove apostrophes */ + remove_apostrophes (result); + g_debug ("\tapostrophes: '%s'", result); + + CHECK; + + /* Remove all the not-allowed chars */ + valid_chars = allowed_chars (); + g_strcanon (result, valid_chars, '-'); + g_debug ("\tcanon: '%s'", result); + + CHECK; + + /* Remove the leading dashes */ + remove_leading_dashes (result); + g_debug ("\tleading: '%s'", result); + + CHECK; + + /* Remove trailing dashes */ + remove_trailing_dashes (result); + g_debug ("\ttrailing: '%s'", result); + + CHECK; + + /* Remove duplicate dashes */ + remove_duplicate_dashes (result); + g_debug ("\tduplicate: '%s'", result); + + CHECK; + + /* Lower case */ + if (!for_display) + return g_ascii_strdown (result, -1); + + return g_steal_pointer (&result); +} +#undef CHECK + +/* Max length of an SSID in bytes */ +#define SSID_MAX_LEN 32 +char * +pretty_hostname_to_ssid (const char *pretty) +{ + const char *p, *prev; + + if (pretty == NULL || *pretty == '\0') { + pretty = g_get_host_name (); + if (g_strcmp0 (pretty, "localhost") == 0) + pretty = NULL; + } + + if (pretty == NULL) { + /* translators: This is the default hotspot name, need to be less than 32-bytes */ + gchar *ret = g_strdup (C_("hotspot", "Hotspot")); + g_assert (strlen (ret) <= SSID_MAX_LEN); + return ret; + } + + g_return_val_if_fail (g_utf8_validate (pretty, -1, NULL), NULL); + + p = pretty; + prev = NULL; + while ((p = g_utf8_find_next_char (p, NULL)) != NULL) { + if (p == prev) + break; + + if (p - pretty > SSID_MAX_LEN) { + return g_strndup (pretty, prev - pretty); + } + if (p - pretty == SSID_MAX_LEN) { + return g_strndup (pretty, p - pretty); + } + + if (*p == '\0') + break; + + prev = p; + } + + return g_strdup (pretty); +} diff --git a/panels/common/hostname-helper.h b/panels/common/hostname-helper.h new file mode 100644 index 0000000..3d2a143 --- /dev/null +++ b/panels/common/hostname-helper.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 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 + +char *pretty_hostname_to_static (const char *pretty, + gboolean for_display); +char *pretty_hostname_to_ssid (const char *pretty); diff --git a/panels/common/meson.build b/panels/common/meson.build new file mode 100644 index 0000000..13557c1 --- /dev/null +++ b/panels/common/meson.build @@ -0,0 +1,126 @@ +common_inc = include_directories('.') + +common_sources = [] + +enums = 'gsd-common-enums' +enums_header = files('gsd-device-manager.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 GSD_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n', + ftail: 'G_END_DECLS\n' +) + +common_sources += gnome.mkenums( + enums + '.c', + sources: enums_header, + fhead: '#include "gsd-device-manager.h"\n#include "gsd-common-enums.h"\n', + 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-language-chooser.ui', + 'cc-language-row.ui', + 'cc-list-row.ui', + 'cc-time-editor.ui', + 'cc-permission-infobar.ui', +) + +common_sources += gnome.compile_resources( + 'cc-common-resources', + 'common.gresource.xml', + c_name: 'cc_common', + dependencies: resource_data, + export: true +) + +generates_sources_dep = declare_dependency( + sources: common_sources, +) + +sources = files( + 'cc-hostname-entry.c', + 'cc-time-entry.c', + 'hostname-helper.c', +) + +libwidgets = static_library( + 'widgets', + sources: sources, + include_directories: top_inc, + dependencies: common_deps + [ generates_sources_dep, polkit_gobject_dep ] +) +libwidgets_dep = declare_dependency( + include_directories: common_inc, + link_with: libwidgets +) + +sources = common_sources + files( + 'cc-common-language.c', + 'cc-language-chooser.c', + 'cc-language-row.c', + 'cc-list-row.c', + 'cc-time-editor.c', + 'cc-permission-infobar.c', + 'cc-util.c' +) + +deps = common_deps + [ + generates_sources_dep, + gnome_desktop_dep, + dependency('fontconfig') +] + +liblanguage = static_library( + 'language', + sources: sources, + include_directories: top_inc, + dependencies: deps +) + +liblanguage_dep = declare_dependency( + include_directories: common_inc, + link_with: liblanguage +) + +gsd_headers = [ + 'gsd-device-manager.h', + 'gsd-input-helper.h' +] + +gsd_sources = [ + 'gsd-device-manager.c', + 'gsd-input-helper.c' +] + +sources = common_sources + files(gsd_sources) + +deps = common_deps + [ gudev_dep ] + +libdevice = static_library( + 'device', + sources: sources, + include_directories: top_inc, + dependencies: deps +) + +libdevice_dep = declare_dependency( + include_directories: common_inc, + link_with: libdevice +) + +polkit_conf = configuration_data() +polkit_conf.set('PRIVILEGED_GROUP', get_option('privileged_group')) +configure_file( + input: 'gnome-control-center.rules.in', + output: 'gnome-control-center.rules', + configuration: polkit_conf, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'rules.d') +) + |