Merge branch 'gbsneto/gtk4' into 'main'

GTK4

See merge request pwithnall/malcontent!141
This commit is contained in:
Georges Basile Stavracas Neto 2022-07-20 22:27:51 +00:00
commit 2da5d5597e
26 changed files with 637 additions and 1223 deletions

View File

@ -7,7 +7,7 @@ cache:
- _ccache/ - _ccache/
variables: variables:
DEBIAN_IMAGE: "registry.freedesktop.org/pwithnall/malcontent/debian-unstable:v2" DEBIAN_IMAGE: "registry.freedesktop.org/pwithnall/malcontent/debian-unstable:v3"
MESON_TEST_TIMEOUT_MULTIPLIER: 2 MESON_TEST_TIMEOUT_MULTIPLIER: 2
G_MESSAGES_DEBUG: all G_MESSAGES_DEBUG: all
MESON_COMMON_OPTIONS: "--buildtype debug --wrap-mode=nodownload" MESON_COMMON_OPTIONS: "--buildtype debug --wrap-mode=nodownload"

View File

@ -5,13 +5,14 @@ RUN apt-get update -qq && apt-get install --no-install-recommends -qq -y \
git \ git \
gtk-doc-tools \ gtk-doc-tools \
lcov \ lcov \
libadwaita-1-dev \
libaccountsservice-dev \ libaccountsservice-dev \
libappstream-dev \ libappstream-dev \
libflatpak-dev \ libflatpak-dev \
libgirepository1.0-dev \ libgirepository1.0-dev \
libglib2.0-dev \ libglib2.0-dev \
libglib-testing-0-dev \ libglib-testing-0-dev \
libgtk-3-dev \ libgtk-4-dev \
libpam0g-dev \ libpam0g-dev \
libpolkit-gobject-1-dev \ libpolkit-gobject-1-dev \
libxml2-utils \ libxml2-utils \

View File

@ -122,7 +122,8 @@ Dependencies
* gio-unix-2.0 ≥ 2.60 * gio-unix-2.0 ≥ 2.60
* glib-2.0 ≥ 2.60 * glib-2.0 ≥ 2.60
* gobject-2.0 ≥ 2.60 * gobject-2.0 ≥ 2.60
* gtk+-3.0 * gtk4
* libadwaita
* polkit-gobject-1 * polkit-gobject-1
Licensing Licensing

View File

@ -28,7 +28,8 @@ libmalcontent_ui_public_deps = [
dependency('gio-2.0', version: '>= 2.44'), dependency('gio-2.0', version: '>= 2.44'),
dependency('glib-2.0', version: '>= 2.54.2'), dependency('glib-2.0', version: '>= 2.54.2'),
dependency('gobject-2.0', version: '>= 2.54'), dependency('gobject-2.0', version: '>= 2.54'),
dependency('gtk+-3.0', version: '>= 3.24'), dependency('gtk4', version: '>= 4.6'),
libadwaita_dep,
libmalcontent_dep, libmalcontent_dep,
] ]
libmalcontent_ui_private_deps = [ libmalcontent_ui_private_deps = [
@ -86,7 +87,7 @@ if xmllint.found()
'validate-ui', xmllint, 'validate-ui', xmllint,
args: [ args: [
'--nonet', '--noblanks', '--noout', '--nonet', '--noblanks', '--noout',
'--relaxng', join_paths(gtk_prefix, 'share', 'gtk-3.0', 'gtkbuilder.rng'), '--relaxng', join_paths(gtk_prefix, 'share', 'gtk-4.0', 'gtk4builder.rng'),
files( files(
'restrict-applications-dialog.ui', 'restrict-applications-dialog.ui',
'restrict-applications-selector.ui', 'restrict-applications-selector.ui',

View File

@ -26,6 +26,7 @@
#include <glib-object.h> #include <glib-object.h>
#include <glib/gi18n-lib.h> #include <glib/gi18n-lib.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <adwaita.h>
#include "restrict-applications-dialog.h" #include "restrict-applications-dialog.h"
#include "restrict-applications-selector.h" #include "restrict-applications-selector.h"
@ -53,13 +54,13 @@ struct _MctRestrictApplicationsDialog
GtkDialog parent_instance; GtkDialog parent_instance;
MctRestrictApplicationsSelector *selector; MctRestrictApplicationsSelector *selector;
GtkLabel *description; AdwPreferencesGroup *group;
MctAppFilter *app_filter; /* (owned) (not nullable) */ MctAppFilter *app_filter; /* (owned) (not nullable) */
gchar *user_display_name; /* (owned) (nullable) */ gchar *user_display_name; /* (owned) (nullable) */
}; };
G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, GTK_TYPE_DIALOG) G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, ADW_TYPE_PREFERENCES_WINDOW)
typedef enum typedef enum
{ {
@ -195,7 +196,7 @@ mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-dialog.ui"); gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, selector); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, selector);
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, description); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, group);
} }
static void static void
@ -214,15 +215,14 @@ update_description (MctRestrictApplicationsDialog *self)
if (self->user_display_name == NULL) if (self->user_display_name == NULL)
{ {
gtk_widget_hide (GTK_WIDGET (self->description)); adw_preferences_group_set_description (self->group, NULL);
return; return;
} }
/* Translators: the placeholder is a users full name */ /* Translators: the placeholder is a users full name */
description = g_strdup_printf (_("Restrict %s from using the following installed applications."), description = g_strdup_printf (_("Restrict %s from using the following installed applications."),
self->user_display_name); self->user_display_name);
gtk_label_set_text (self->description, description); adw_preferences_group_set_description (self->group, description);
gtk_widget_show (GTK_WIDGET (self->description));
} }
/** /**

View File

@ -24,13 +24,14 @@
#include <glib.h> #include <glib.h>
#include <glib-object.h> #include <glib-object.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <adwaita.h>
#include <libmalcontent/manager.h> #include <libmalcontent/manager.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG (mct_restrict_applications_dialog_get_type ()) #define MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG (mct_restrict_applications_dialog_get_type ())
G_DECLARE_FINAL_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, MCT, RESTRICT_APPLICATIONS_DIALOG, GtkDialog) G_DECLARE_FINAL_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, MCT, RESTRICT_APPLICATIONS_DIALOG, AdwPreferencesWindow)
MctRestrictApplicationsDialog *mct_restrict_applications_dialog_new (MctAppFilter *app_filter, MctRestrictApplicationsDialog *mct_restrict_applications_dialog_new (MctAppFilter *app_filter,
const gchar *user_display_name); const gchar *user_display_name);

View File

@ -2,48 +2,22 @@
<!-- Copyright © 2020 Endless, Inc. --> <!-- Copyright © 2020 Endless, Inc. -->
<interface domain="malcontent"> <interface domain="malcontent">
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<template class="MctRestrictApplicationsDialog" parent="GtkDialog"> <template class="MctRestrictApplicationsDialog" parent="AdwPreferencesWindow">
<property name="title" translatable="yes">Restrict Applications</property> <property name="title" translatable="yes">Restrict Applications</property>
<property name="skip-taskbar-hint">True</property> <property name="default-width">500</property>
<property name="default-width">300</property>
<property name="default-height">500</property> <property name="default-height">500</property>
<child internal-child="headerbar"> <property name="search-enabled">False</property>
<object class="GtkHeaderBar"> <child>
<property name="title" translatable="yes">Restrict Applications</property> <object class="AdwPreferencesPage">
<property name="show-close-button">True</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="visible">True</property>
<property name="margin">18</property>
<property name="spacing">12</property>
<child> <child>
<object class="GtkLabel" id="description"> <object class="AdwPreferencesGroup" id="group">
<!-- Translated dynamically: --> <!-- Translated dynamically: -->
<property name="label">Restrict {username} from using the following installed applications.</property> <property name="description">Restrict {username} from using the following installed applications.</property>
<property name="visible">False</property> <child>
<property name="ellipsize">none</property> <object class="MctRestrictApplicationsSelector" id="selector" />
<property name="wrap">True</property>
<property name="halign">start</property>
<property name="xalign">0</property>
<property name="hexpand">True</property>
<child internal-child="accessible">
<object class="AtkObject">
<property name="accessible-role">static</property>
</object>
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="MctRestrictApplicationsSelector" id="selector">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
</object> </object>
</child> </child>
</template> </template>

View File

@ -28,6 +28,7 @@
#include <glib-object.h> #include <glib-object.h>
#include <glib/gi18n-lib.h> #include <glib/gi18n-lib.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <adwaita.h>
#include <libmalcontent/app-filter.h> #include <libmalcontent/app-filter.h>
#include "restrict-applications-selector.h" #include "restrict-applications-selector.h"
@ -60,6 +61,7 @@ struct _MctRestrictApplicationsSelector
GtkBox parent_instance; GtkBox parent_instance;
GtkListBox *listbox; GtkListBox *listbox;
GtkLabel *placeholder;
GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */ GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */
GListStore *apps; /* (owned) */ GListStore *apps; /* (owned) */
@ -222,11 +224,14 @@ mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorCl
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-selector.ui"); gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-selector.ui");
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox);
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, placeholder);
} }
static void static void
mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self) mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
{ {
guint n_apps;
gtk_widget_init_template (GTK_WIDGET (self)); gtk_widget_init_template (GTK_WIDGET (self));
self->apps = g_list_store_new (G_TYPE_APP_INFO); self->apps = g_list_store_new (G_TYPE_APP_INFO);
@ -243,6 +248,10 @@ mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
self, self,
NULL); NULL);
/* Hide placeholder if not empty */
n_apps = g_list_model_get_n_items (G_LIST_MODEL (self->apps));
gtk_widget_set_visible (GTK_WIDGET (self->placeholder), n_apps != 0);
self->blocklisted_apps = g_hash_table_new_full (g_direct_hash, self->blocklisted_apps = g_hash_table_new_full (g_direct_hash,
g_direct_equal, g_direct_equal,
g_object_unref, g_object_unref,
@ -312,9 +321,8 @@ create_row_for_app_cb (gpointer item,
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (user_data); MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (user_data);
GAppInfo *app = G_APP_INFO (item); GAppInfo *app = G_APP_INFO (item);
g_autoptr(GIcon) icon = NULL; g_autoptr(GIcon) icon = NULL;
GtkWidget *box, *w; GtkWidget *row, *w;
const gchar *app_name; const gchar *app_name;
gint size;
GtkStyleContext *context; GtkStyleContext *context;
app_name = g_app_info_get_name (app); app_name = g_app_info_get_name (app);
@ -327,23 +335,15 @@ create_row_for_app_cb (gpointer item,
else else
g_object_ref (icon); g_object_ref (icon);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); row = adw_action_row_new ();
gtk_container_set_border_width (GTK_CONTAINER (box), 12);
gtk_widget_set_margin_end (box, 12);
/* Icon */ /* Icon */
w = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG); w = gtk_image_new_from_gicon (icon);
gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &size, NULL); gtk_image_set_icon_size (GTK_IMAGE (w), GTK_ICON_SIZE_LARGE);
gtk_image_set_pixel_size (GTK_IMAGE (w), size); adw_action_row_add_prefix (ADW_ACTION_ROW (row), w);
gtk_container_add (GTK_CONTAINER (box), w);
/* App name label */ /* App name label */
w = g_object_new (GTK_TYPE_LABEL, adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), app_name);
"label", app_name,
"hexpand", TRUE,
"xalign", 0.0,
NULL);
gtk_container_add (GTK_CONTAINER (box), w);
/* Switch */ /* Switch */
w = g_object_new (GTK_TYPE_SWITCH, w = g_object_new (GTK_TYPE_SWITCH,
@ -354,16 +354,16 @@ create_row_for_app_cb (gpointer item,
gtk_style_context_add_provider (context, gtk_style_context_add_provider (context,
GTK_STYLE_PROVIDER (self->css_provider), GTK_STYLE_PROVIDER (self->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
gtk_container_add (GTK_CONTAINER (box), w); adw_action_row_add_suffix (ADW_ACTION_ROW (row), w);
adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), w);
gtk_widget_show_all (box);
/* Fetch status from AccountService */ /* Fetch status from AccountService */
g_object_set_data (G_OBJECT (row), "GtkSwitch", w);
g_object_set_data_full (G_OBJECT (w), "GAppInfo", g_object_ref (app), g_object_unref); g_object_set_data_full (G_OBJECT (w), "GAppInfo", g_object_ref (app), g_object_unref);
update_listbox_row_switch (self, GTK_SWITCH (w)); update_listbox_row_switch (self, GTK_SWITCH (w));
g_signal_connect (w, "notify::active", G_CALLBACK (on_switch_active_changed_cb), self); g_signal_connect (w, "notify::active", G_CALLBACK (on_switch_active_changed_cb), self);
return box; return row;
} }
static gint static gint
@ -779,20 +779,13 @@ mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelect
for (guint i = 0; i < n_apps; i++) for (guint i = 0; i < n_apps; i++)
{ {
GtkListBoxRow *row; GtkListBoxRow *row;
GtkWidget *box, *w; GtkWidget *w;
g_autoptr(GList) children = NULL; /* (element-type GtkWidget) */
/* Navigate the widget hierarchy set up in create_row_for_app_cb(). */ /* Navigate the widget hierarchy set up in create_row_for_app_cb(). */
row = gtk_list_box_get_row_at_index (self->listbox, i); row = gtk_list_box_get_row_at_index (self->listbox, i);
g_assert (row != NULL && GTK_IS_LIST_BOX_ROW (row)); g_assert (row != NULL && GTK_IS_LIST_BOX_ROW (row));
box = gtk_bin_get_child (GTK_BIN (row)); w = g_object_get_data (G_OBJECT (row), "GtkSwitch");
g_assert (box != NULL && GTK_IS_BOX (box));
children = gtk_container_get_children (GTK_CONTAINER (box));
g_assert (children != NULL);
w = g_list_nth_data (children, 2);
g_assert (w != NULL && GTK_IS_SWITCH (w)); g_assert (w != NULL && GTK_IS_SWITCH (w));
update_listbox_row_switch (self, GTK_SWITCH (w)); update_listbox_row_switch (self, GTK_SWITCH (w));

View File

@ -4,28 +4,19 @@
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<template class="MctRestrictApplicationsSelector" parent="GtkBox"> <template class="MctRestrictApplicationsSelector" parent="GtkBox">
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkListBox" id="listbox">
<property name="visible">True</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="hscrollbar-policy">never</property> <property name="selection-mode">none</property>
<property name="min-content-height">100</property>
<property name="max-content-height">400</property>
<property name="propagate-natural-height">True</property>
<property name="shadow-type">etched-in</property>
<child> <child type="placeholder">
<object class="GtkListBox" id="listbox"> <object class="GtkLabel" id="placeholder">
<property name="visible">True</property> <property name="label" translatable="yes">No applications found to restrict.</property>
<property name="selection-mode">none</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">No applications found to restrict.</property>
</object>
</child>
</object> </object>
</child> </child>
<style>
<class name="content"/>
</style>
</object> </object>
</child> </child>
</template> </template>

View File

@ -1,11 +1,11 @@
/* FIXME: This negative variant of a GtkSwitch should probably be /* FIXME: This negative variant of a GtkSwitch should probably be
* upstreamed to GTK. See https://gitlab.gnome.org/GNOME/gtk/issues/2470 */ * upstreamed to GTK. See https://gitlab.gnome.org/GNOME/gtk/issues/2470 */
switch:checked.restricts { switch:checked.restricts {
background-color: #fffd33; background-color: @yellow_5;
} }
switch:checked.restricts, switch:checked.restricts slider { switch:checked.restricts, switch:checked.restricts slider {
border-color: #ffd52b; border-color: @yellow_3;
} }
switch:disabled.restricts { switch:disabled.restricts {
@ -14,5 +14,5 @@ switch:disabled.restricts {
} }
switch:disabled.restricts slider { switch:disabled.restricts slider {
border-color: #bfb8b1; filter: grayscale(100%);
} }

View File

@ -73,22 +73,19 @@ static const guint32 oars_disabled_age = (guint32) -1;
*/ */
struct _MctUserControls struct _MctUserControls
{ {
GtkGrid parent_instance; AdwBin parent_instance;
GtkLabel *description_label;
GMenu *age_menu; GMenu *age_menu;
GtkSwitch *restrict_software_installation_switch; GtkSwitch *restrict_software_installation_switch;
GtkLabel *restrict_software_installation_description; AdwActionRow *restrict_software_installation_row;
GtkSwitch *restrict_web_browsers_switch; GtkSwitch *restrict_web_browsers_switch;
GtkLabel *restrict_web_browsers_description; AdwActionRow *restrict_web_browsers_row;
GtkButton *oars_button; GtkMenuButton *oars_button;
GtkLabel *oars_button_label; GtkPopoverMenu *oars_popover;
GtkPopover *oars_popover;
MctRestrictApplicationsDialog *restrict_applications_dialog; MctRestrictApplicationsDialog *restrict_applications_dialog;
GtkLabel *restrict_applications_description; GtkLabel *restrict_applications_description;
GtkListBoxRow *restrict_applications_row; AdwActionRow *restrict_applications_row;
GtkListBox *application_usage_permissions_listbox;
GtkListBox *software_installation_permissions_listbox;
GSimpleActionGroup *action_group; /* (owned) */ GSimpleActionGroup *action_group; /* (owned) */
@ -111,6 +108,7 @@ struct _MctUserControls
ActUserAccountType user_account_type; ActUserAccountType user_account_type;
gchar *user_locale; /* (nullable) (owned) */ gchar *user_locale; /* (nullable) (owned) */
gchar *user_display_name; /* (nullable) (owned) */ gchar *user_display_name; /* (nullable) (owned) */
gchar *description; /* (nullable) (owned) */
}; };
static gboolean blocklist_apps_cb (gpointer data); static gboolean blocklist_apps_cb (gpointer data);
@ -123,20 +121,12 @@ static void on_restrict_web_browsers_switch_active_changed_cb (GtkSwitch
GParamSpec *pspec, GParamSpec *pspec,
MctUserControls *self); MctUserControls *self);
static void on_restrict_applications_button_clicked_cb (GtkButton *button, static void on_restrict_applications_action_activated (GSimpleAction *action,
gpointer user_data); GVariant *param,
gpointer user_data);
static gboolean on_restrict_applications_dialog_delete_event_cb (GtkWidget *widget, static gboolean on_restrict_applications_dialog_close_request_cb (GtkWidget *widget,
GdkEvent *event, gpointer user_data);
gpointer user_data);
static void on_restrict_applications_dialog_response_cb (GtkDialog *dialog,
gint response_id,
gpointer user_data);
static void on_application_usage_permissions_listbox_activated_cb (GtkListBox *list_box,
GtkListBoxRow *row,
gpointer user_data);
static void on_set_age_action_activated (GSimpleAction *action, static void on_set_age_action_activated (GSimpleAction *action,
GVariant *param, GVariant *param,
@ -146,7 +136,7 @@ static void on_permission_allowed_cb (GObject *obj,
GParamSpec *pspec, GParamSpec *pspec,
gpointer user_data); gpointer user_data);
G_DEFINE_TYPE (MctUserControls, mct_user_controls, GTK_TYPE_GRID) G_DEFINE_TYPE (MctUserControls, mct_user_controls, ADW_TYPE_BIN)
typedef enum typedef enum
{ {
@ -157,12 +147,14 @@ typedef enum
PROP_USER_LOCALE, PROP_USER_LOCALE,
PROP_USER_DISPLAY_NAME, PROP_USER_DISPLAY_NAME,
PROP_DBUS_CONNECTION, PROP_DBUS_CONNECTION,
PROP_DESCRIPTION,
} MctUserControlsProperty; } MctUserControlsProperty;
static GParamSpec *properties[PROP_DBUS_CONNECTION + 1]; static GParamSpec *properties[PROP_DESCRIPTION + 1];
static const GActionEntry actions[] = { static const GActionEntry actions[] = {
{ "set-age", on_set_age_action_activated, "u", NULL, NULL, { 0, }} { "set-age", on_set_age_action_activated, "u", NULL, NULL, { 0, }},
{ "restrict-applications", on_restrict_applications_action_activated, NULL, NULL, NULL, { 0, }}
}; };
/* Auxiliary methods */ /* Auxiliary methods */
@ -401,7 +393,7 @@ update_oars_level (MctUserControls *self)
selected_age = maximum_age; selected_age = maximum_age;
} }
gtk_label_set_label (self->oars_button_label, rating_age_category); gtk_menu_button_set_label (self->oars_button, rating_age_category);
self->selected_age = selected_age; self->selected_age = selected_age;
} }
@ -472,19 +464,21 @@ update_labels_from_name (MctUserControls *self)
{ {
g_autofree gchar *l = NULL; g_autofree gchar *l = NULL;
gtk_label_set_markup (self->description_label, self->description);
/* Translators: The placeholder is a users display name. */ /* Translators: The placeholder is a users display name. */
l = g_strdup_printf (_("Prevents %s from running web browsers. Limited web content may still be available in other applications."), self->user_display_name); l = g_strdup_printf (_("Prevents %s from running web browsers. Limited web content may still be available in other applications."), self->user_display_name);
gtk_label_set_label (self->restrict_web_browsers_description, l); adw_action_row_set_subtitle (self->restrict_web_browsers_row, l);
g_clear_pointer (&l, g_free); g_clear_pointer (&l, g_free);
/* Translators: The placeholder is a users display name. */ /* Translators: The placeholder is a users display name. */
l = g_strdup_printf (_("Prevents specified applications from being used by %s."), self->user_display_name); l = g_strdup_printf (_("Prevents specified applications from being used by %s."), self->user_display_name);
gtk_label_set_label (self->restrict_applications_description, l); adw_action_row_set_subtitle (self->restrict_applications_row, l);
g_clear_pointer (&l, g_free); g_clear_pointer (&l, g_free);
/* Translators: The placeholder is a users display name. */ /* Translators: The placeholder is a users display name. */
l = g_strdup_printf (_("Prevents %s from installing applications."), self->user_display_name); l = g_strdup_printf (_("Prevents %s from installing applications."), self->user_display_name);
gtk_label_set_label (self->restrict_software_installation_description, l); adw_action_row_set_subtitle (self->restrict_software_installation_row, l);
g_clear_pointer (&l, g_free); g_clear_pointer (&l, g_free);
} }
@ -585,29 +579,29 @@ on_restrict_web_browsers_switch_active_changed_cb (GtkSwitch *s,
} }
static void static void
on_restrict_applications_button_clicked_cb (GtkButton *button, on_restrict_applications_action_activated (GSimpleAction *action,
gpointer user_data) GVariant *param,
gpointer user_data)
{ {
MctUserControls *self = MCT_USER_CONTROLS (user_data); MctUserControls *self = MCT_USER_CONTROLS (user_data);
GtkWidget *toplevel; GtkRoot *root;
/* Show the restrict applications dialogue modally, making sure to update its /* Show the restrict applications dialogue modally, making sure to update its
* state first. */ * state first. */
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); root = gtk_widget_get_root (GTK_WIDGET (self));
if (GTK_IS_WINDOW (toplevel)) if (GTK_IS_WINDOW (root))
gtk_window_set_transient_for (GTK_WINDOW (self->restrict_applications_dialog), gtk_window_set_transient_for (GTK_WINDOW (self->restrict_applications_dialog),
GTK_WINDOW (toplevel)); GTK_WINDOW (root));
mct_restrict_applications_dialog_set_user_display_name (self->restrict_applications_dialog, self->user_display_name); mct_restrict_applications_dialog_set_user_display_name (self->restrict_applications_dialog, self->user_display_name);
mct_restrict_applications_dialog_set_app_filter (self->restrict_applications_dialog, self->filter); mct_restrict_applications_dialog_set_app_filter (self->restrict_applications_dialog, self->filter);
gtk_widget_show (GTK_WIDGET (self->restrict_applications_dialog)); gtk_window_present (GTK_WINDOW (self->restrict_applications_dialog));
} }
static gboolean static gboolean
on_restrict_applications_dialog_delete_event_cb (GtkWidget *widget, on_restrict_applications_dialog_close_request_cb (GtkWidget *widget,
GdkEvent *event, gpointer user_data)
gpointer user_data)
{ {
MctUserControls *self = MCT_USER_CONTROLS (user_data); MctUserControls *self = MCT_USER_CONTROLS (user_data);
@ -622,27 +616,6 @@ on_restrict_applications_dialog_delete_event_cb (GtkWidget *widget,
return TRUE; return TRUE;
} }
static void
on_restrict_applications_dialog_response_cb (GtkDialog *dialog,
gint response_id,
gpointer user_data)
{
MctUserControls *self = MCT_USER_CONTROLS (user_data);
on_restrict_applications_dialog_delete_event_cb (GTK_WIDGET (dialog), NULL, self);
}
static void
on_application_usage_permissions_listbox_activated_cb (GtkListBox *list_box,
GtkListBoxRow *row,
gpointer user_data)
{
MctUserControls *self = MCT_USER_CONTROLS (user_data);
if (row == self->restrict_applications_row)
on_restrict_applications_button_clicked_cb (NULL, self);
}
static void static void
on_set_age_action_activated (GSimpleAction *action, on_set_age_action_activated (GSimpleAction *action,
GVariant *param, GVariant *param,
@ -665,13 +638,13 @@ on_set_age_action_activated (GSimpleAction *action,
/* Update the button */ /* Update the button */
if (age == oars_disabled_age) if (age == oars_disabled_age)
gtk_label_set_label (self->oars_button_label, _("All Ages")); gtk_menu_button_set_label (self->oars_button, _("All Ages"));
for (i = 0; age != oars_disabled_age && entries[i] != NULL; i++) for (i = 0; age != oars_disabled_age && entries[i] != NULL; i++)
{ {
if (ages[i] == age) if (ages[i] == age)
{ {
gtk_label_set_label (self->oars_button_label, entries[i]); gtk_menu_button_set_label (self->oars_button, entries[i]);
break; break;
} }
} }
@ -688,53 +661,6 @@ on_set_age_action_activated (GSimpleAction *action,
schedule_update_blocklisted_apps (self); schedule_update_blocklisted_apps (self);
} }
static void
list_box_header_func (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data)
{
GtkWidget *current;
if (before == NULL)
{
gtk_list_box_row_set_header (row, NULL);
return;
}
current = gtk_list_box_row_get_header (row);
if (current == NULL)
{
current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_show (current);
gtk_list_box_row_set_header (row, current);
}
}
static gboolean
on_keynav_failed (GtkWidget *listbox,
GtkDirectionType direction,
gpointer user_data)
{
MctUserControls *self = MCT_USER_CONTROLS (user_data);
GtkWidget *new_widget = NULL;
/* There are currently two listboxes, so dont over-complicate this function. */
if (listbox == GTK_WIDGET (self->application_usage_permissions_listbox) &&
direction == GTK_DIR_DOWN)
new_widget = GTK_WIDGET (self->software_installation_permissions_listbox);
else if (listbox == GTK_WIDGET (self->software_installation_permissions_listbox) &&
direction == GTK_DIR_UP)
new_widget = GTK_WIDGET (self->application_usage_permissions_listbox);
if (new_widget != NULL)
{
gtk_widget_child_focus (new_widget, direction);
return TRUE;
}
return FALSE;
}
/* GObject overrides */ /* GObject overrides */
static void static void
@ -850,6 +776,10 @@ mct_user_controls_get_property (GObject *object,
g_value_set_object (value, self->dbus_connection); g_value_set_object (value, self->dbus_connection);
break; break;
case PROP_DESCRIPTION:
g_value_set_string (value, self->description);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
} }
@ -895,6 +825,10 @@ mct_user_controls_set_property (GObject *object,
self->dbus_connection = g_value_dup_object (value); self->dbus_connection = g_value_dup_object (value);
break; break;
case PROP_DESCRIPTION:
mct_user_controls_set_description (self, g_value_get_string (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
} }
@ -1013,6 +947,25 @@ mct_user_controls_class_init (MctUserControlsClass *klass)
G_PARAM_STATIC_STRINGS | G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY); G_PARAM_EXPLICIT_NOTIFY);
/**
* MctUserControls:description: (nullable)
*
* The description for the currently selected user account, or %NULL if no
* user is selected.
*
* If set, it must be valid UTF-8 and non-empty.
*
* Since: 0.11.0
*/
properties[PROP_DESCRIPTION] =
g_param_spec_string ("description",
"Description",
"The description for the currently selected user account, or %NULL if no user is selected.",
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/** /**
* MctUserControls:dbus-connection: (not nullable) * MctUserControls:dbus-connection: (not nullable)
* *
@ -1036,32 +989,24 @@ mct_user_controls_class_init (MctUserControlsClass *klass)
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/user-controls.ui"); gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/user-controls.ui");
gtk_widget_class_bind_template_child (widget_class, MctUserControls, age_menu); gtk_widget_class_bind_template_child (widget_class, MctUserControls, age_menu);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, description_label);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_software_installation_switch); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_software_installation_switch);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_software_installation_description); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_software_installation_row);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_web_browsers_switch); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_web_browsers_switch);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_web_browsers_description); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_web_browsers_row);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, oars_button); gtk_widget_class_bind_template_child (widget_class, MctUserControls, oars_button);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, oars_button_label);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, oars_popover); gtk_widget_class_bind_template_child (widget_class, MctUserControls, oars_popover);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_dialog); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_dialog);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_description);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_row); gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_row);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, application_usage_permissions_listbox);
gtk_widget_class_bind_template_child (widget_class, MctUserControls, software_installation_permissions_listbox);
gtk_widget_class_bind_template_callback (widget_class, on_restrict_installation_switch_active_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_restrict_installation_switch_active_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_restrict_web_browsers_switch_active_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_restrict_web_browsers_switch_active_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_dialog_close_request_cb);
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_dialog_delete_event_cb);
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_dialog_response_cb);
gtk_widget_class_bind_template_callback (widget_class, on_application_usage_permissions_listbox_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, on_keynav_failed);
} }
static void static void
mct_user_controls_init (MctUserControls *self) mct_user_controls_init (MctUserControls *self)
{ {
g_autoptr(GError) error = NULL;
g_autoptr(GtkCssProvider) provider = NULL; g_autoptr(GtkCssProvider) provider = NULL;
/* Ensure the types used in the UI are registered. */ /* Ensure the types used in the UI are registered. */
@ -1072,9 +1017,9 @@ mct_user_controls_init (MctUserControls *self)
provider = gtk_css_provider_new (); provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider, gtk_css_provider_load_from_resource (provider,
"/org/freedesktop/MalcontentUi/ui/restricts-switch.css"); "/org/freedesktop/MalcontentUi/ui/restricts-switch.css");
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
self->selected_age = (guint) -1; self->selected_age = (guint) -1;
@ -1090,13 +1035,7 @@ mct_user_controls_init (MctUserControls *self)
"permissions", "permissions",
G_ACTION_GROUP (self->action_group)); G_ACTION_GROUP (self->action_group));
gtk_popover_bind_model (self->oars_popover, G_MENU_MODEL (self->age_menu), NULL); gtk_popover_menu_set_menu_model (self->oars_popover, G_MENU_MODEL (self->age_menu));
/* Automatically add separators between rows. */
gtk_list_box_set_header_func (self->application_usage_permissions_listbox,
list_box_header_func, NULL, NULL);
gtk_list_box_set_header_func (self->software_installation_permissions_listbox,
list_box_header_func, NULL, NULL);
} }
/** /**
@ -1461,6 +1400,38 @@ mct_user_controls_set_user_display_name (MctUserControls *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_DISPLAY_NAME]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_DISPLAY_NAME]);
} }
/**
* mct_user_controls_set_description:
* @self: an #MctUserControls
* @description: (nullable) (transfer none): the description shown
* above the controls, or %NULL if none.
*
* Set the value of #MctUserControls:description.
*
* Since: 0.11.0
*/
void
mct_user_controls_set_description (MctUserControls *self,
const gchar *description)
{
g_return_if_fail (MCT_IS_USER_CONTROLS (self));
g_return_if_fail (description != NULL);
/* If we have pending unsaved changes from the previous user, force them to be
* saved first. */
flush_update_blocklisted_apps (self);
if (g_strcmp0 (self->description, description) == 0)
return;
g_clear_pointer (&self->description, g_free);
self->description = g_strdup (description);
setup_parental_control_settings (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DESCRIPTION]);
}
/** /**
* mct_user_controls_build_app_filter: * mct_user_controls_build_app_filter:
* @self: an #MctUserControls * @self: an #MctUserControls

View File

@ -27,13 +27,14 @@
#include <glib.h> #include <glib.h>
#include <glib-object.h> #include <glib-object.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <adwaita.h>
#include <libmalcontent/malcontent.h> #include <libmalcontent/malcontent.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define MCT_TYPE_USER_CONTROLS (mct_user_controls_get_type()) #define MCT_TYPE_USER_CONTROLS (mct_user_controls_get_type())
G_DECLARE_FINAL_TYPE (MctUserControls, mct_user_controls, MCT, USER_CONTROLS, GtkGrid) G_DECLARE_FINAL_TYPE (MctUserControls, mct_user_controls, MCT, USER_CONTROLS, AdwBin)
ActUser *mct_user_controls_get_user (MctUserControls *self); ActUser *mct_user_controls_get_user (MctUserControls *self);
void mct_user_controls_set_user (MctUserControls *self, void mct_user_controls_set_user (MctUserControls *self,
@ -59,6 +60,9 @@ const gchar *mct_user_controls_get_user_display_name (MctUserControls *self);
void mct_user_controls_set_user_display_name (MctUserControls *self, void mct_user_controls_set_user_display_name (MctUserControls *self,
const gchar *user_display_name); const gchar *user_display_name);
void mct_user_controls_set_description (MctUserControls *self,
const gchar *description);
void mct_user_controls_build_app_filter (MctUserControls *self, void mct_user_controls_build_app_filter (MctUserControls *self,
MctAppFilterBuilder *builder); MctAppFilterBuilder *builder);

View File

@ -2,491 +2,130 @@
<!-- Copyright © 2018, 2019, 2020 Endless, Inc. --> <!-- Copyright © 2018, 2019, 2020 Endless, Inc. -->
<interface domain="malcontent"> <interface domain="malcontent">
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<template class="MctUserControls" parent="GtkGrid"> <template class="MctUserControls" parent="AdwBin">
<property name="visible">True</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<property name="valign">start</property>
<!-- Application Usage Restrictions -->
<child> <child>
<object class="GtkLabel"> <object class="GtkBox">
<property name="visible">True</property> <property name="spacing">24</property>
<property name="xalign">0.0</property> <property name="orientation">vertical</property>
<property name="label" translatable="yes">Application Usage Restrictions</property>
<attributes>
<attribute name="weight" value="bold" />
</attributes>
</object>
<packing>
<property name="top-attach">0</property>
<property name="left-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child> <child>
<object class="GtkListBox" id="application_usage_permissions_listbox"> <object class="AdwPreferencesGroup">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="selection_mode">none</property>
<property name="activate-on-single-click">True</property>
<signal name="row-activated" handler="on_application_usage_permissions_listbox_activated_cb" object="MctUserControls" swapped="no" />
<signal name="keynav-failed" handler="on_keynav_failed" object="MctUserControls" swapped="no" />
<child> <child>
<object class="GtkListBoxRow"> <object class="GtkLabel" id="description_label">
<property name="visible">True</property> <property name="wrap">True</property>
<property name="can_focus">True</property> <property name="xalign">0.0</property>
<property name="activatable">False</property> <property name="yalign">0.0</property>
<property name="selectable">False</property> </object>
</child>
</object>
</child>
<!-- Application Usage Restrictions -->
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Application Usage Restrictions</property>
<child>
<object class="AdwActionRow" id="restrict_web_browsers_row">
<property name="title" translatable="yes">Restrict _Web Browsers</property>
<property name="use_underline">True</property>
<property name="activatable_widget">restrict_web_browsers_switch</property>
<!-- Set dynamically from user-controls.c: -->
<property name="subtitle">Prevents {username} from running web browsers. Limited web content may still be available in other applications.</property>
<property name="subtitle_lines">0</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkSwitch" id="restrict_web_browsers_switch">
<property name="visible">True</property> <property name="halign">end</property>
<property name="can_focus">False</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="margin-left">12</property> <signal name="notify::active" handler="on_restrict_web_browsers_switch_active_changed_cb" object="MctUserControls" swapped="no" />
<property name="margin-right">12</property> <style>
<property name="margin-top">8</property> <class name="restricts" />
<property name="margin-bottom">8</property> </style>
<property name="row-spacing">4</property>
<property name="column-spacing">4</property>
<child>
<object class="GtkLabel" id="restrict_web_browsers_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Restrict _Web Browsers</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">restrict_web_browsers_switch</property>
<accessibility>
<relation target="restrict_web_browsers_switch" type="label-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="restrict_web_browsers_description">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">none</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<!-- Set dynamically from user-controls.c: -->
<property name="label">Prevents {username} from running web browsers. Limited web content may still be available in other applications.</property>
<attributes>
<attribute name="scale" value="0.88"/>
</attributes>
<style>
<class name="dim-label" />
</style>
<accessibility>
<relation target="restrict_web_browsers_switch" type="description-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="restrict_web_browsers_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<signal name="notify::active" handler="on_restrict_web_browsers_switch_active_changed_cb" object="MctUserControls" swapped="no" />
<style>
<class name="restricts" />
</style>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
<property name="height">2</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkListBoxRow" id="restrict_applications_row"> <object class="AdwActionRow" id="restrict_applications_row">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="activatable">True</property> <property name="activatable">True</property>
<property name="selectable">False</property> <property name="title" translatable="yes">_Restrict Applications</property>
<property name="use_underline">True</property>
<!-- Set dynamically from user-controls.c: -->
<property name="subtitle">Prevents specified applications from being used by {username}.</property>
<property name="subtitle_lines">0</property>
<property name="action-name">permissions.restrict-applications</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkImage">
<property name="visible">True</property> <property name="icon-name">go-next-symbolic</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin-left">12</property>
<property name="margin-right">12</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="row-spacing">4</property>
<property name="column-spacing">4</property>
<child>
<object class="GtkLabel" id="restrict_applications_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Restrict Applications</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">restrict_applications_button</property>
<accessibility>
<relation target="restrict_applications_button" type="label-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="restrict_applications_description">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">none</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<!-- Set dynamically from user-controls.c: -->
<property name="label">Prevents specified applications from being used by {username}.</property>
<attributes>
<attribute name="scale" value="0.88"/>
</attributes>
<style>
<class name="dim-label" />
</style>
<accessibility>
<relation target="restrict_applications_button" type="description-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="restrict_applications_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="relief">none</property>
<signal name="clicked" handler="on_restrict_applications_button_clicked_cb" object="MctUserControls" swapped="no" />
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon-name">go-next-symbolic</property>
<property name="icon-size">4</property><!-- GTK_ICON_SIZE_BUTTON -->
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
<property name="height">2</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<style>
<class name="content"/>
</style>
</object> </object>
</child> </child>
</object>
<packing>
<property name="top-attach">1</property>
<property name="left-attach">0</property>
</packing>
</child>
<!-- Software Installation Restrictions --> <!-- Software Installation Restrictions -->
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="margin-top">12</property>
<property name="xalign">0.0</property>
<property name="label" translatable="yes">Software Installation Restrictions</property>
<attributes>
<attribute name="weight" value="bold" />
</attributes>
</object>
<packing>
<property name="top-attach">2</property>
<property name="left-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child> <child>
<object class="GtkListBox" id="software_installation_permissions_listbox"> <object class="AdwPreferencesGroup">
<property name="visible">True</property> <property name="title" translatable="yes">Software Installation Restrictions</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="selection_mode">none</property>
<property name="activate_on_single_click">False</property>
<signal name="keynav-failed" handler="on_keynav_failed" object="MctUserControls" swapped="no" />
<child> <child>
<object class="GtkListBoxRow"> <object class="AdwActionRow" id="restrict_software_installation_row">
<property name="visible" bind-source="restrict_software_installation_switch" bind-property="visible" bind-flags="default|sync-create" /> <property name="visible" bind-source="restrict_software_installation_switch" bind-property="visible" bind-flags="default|sync-create" />
<property name="can_focus">True</property> <property name="title" translatable="yes">Restrict Application _Installation</property>
<property name="activatable">False</property> <property name="use_underline">True</property>
<property name="selectable">False</property> <property name="activatable_widget">restrict_software_installation_switch</property>
<!-- Set dynamically from user-controls.c: -->
<property name="subtitle">Prevents {username} from installing applications.</property>
<property name="subtitle_lines">0</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkSwitch" id="restrict_software_installation_switch">
<property name="visible">True</property> <property name="halign">end</property>
<property name="can_focus">False</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="margin-left">12</property> <signal name="notify::active" handler="on_restrict_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
<property name="margin-right">12</property> <style>
<property name="margin-top">8</property> <class name="restricts" />
<property name="margin-bottom">8</property> </style>
<property name="row-spacing">4</property>
<property name="column-spacing">4</property>
<child>
<object class="GtkLabel" id="restrict_software_installation_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Restrict Application _Installation</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">restrict_software_installation_switch</property>
<accessibility>
<relation target="restrict_software_installation_switch" type="label-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="restrict_software_installation_description">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">none</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<!-- Set dynamically from user-controls.c: -->
<property name="label">Prevents {username} from installing applications.</property>
<attributes>
<attribute name="scale" value="0.88"/>
</attributes>
<style>
<class name="dim-label" />
</style>
<accessibility>
<relation target="restrict_software_installation_switch" type="description-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="restrict_software_installation_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<signal name="notify::active" handler="on_restrict_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
<style>
<class name="restricts" />
</style>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
<property name="height">2</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkListBoxRow"> <object class="AdwActionRow">
<property name="visible">True</property> <property name="title" translatable="yes">Application _Suitability</property>
<property name="can_focus">True</property> <property name="use_underline">True</property>
<property name="activatable">False</property> <property name="activatable_widget">oars_button</property>
<property name="selectable">False</property> <property name="subtitle" translatable="yes">Restricts browsing or installation of applications to applications suitable for certain ages or above.</property>
<property name="subtitle_lines">0</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkMenuButton" id="oars_button">
<property name="visible">True</property> <property name="halign">end</property>
<property name="can_focus">False</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="margin-left">12</property> <property name="popover">oars_popover</property>
<property name="margin-right">12</property> <property name="always-show-arrow">True</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="row-spacing">4</property>
<property name="column-spacing">4</property>
<child>
<object class="GtkLabel" id="oars_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Application _Suitability</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">oars_button</property>
<accessibility>
<relation target="oars_button" type="label-for"/>
<relation target="oars_button" type="flows-to"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="oars_description">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="ellipsize">none</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Restricts browsing or installation of applications to applications suitable for certain ages or above.</property>
<attributes>
<attribute name="scale" value="0.88"/>
</attributes>
<style>
<class name="dim-label" />
</style>
<accessibility>
<relation target="oars_button" type="description-for"/>
</accessibility>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="oars_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="direction">right</property>
<property name="popover">oars_popover</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkLabel" id="oars_button_label">
<property name="visible">True</property>
<property name="label"></property>
<property name="expand">True</property>
<property name="halign">start</property>
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon-name">pan-down-symbolic</property>
<property name="icon-size">4</property><!-- GTK_ICON_SIZE_BUTTON -->
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
<property name="height">2</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<style>
<class name="content"/>
</style>
</object> </object>
</child> </child>
</object> </object>
<packing>
<property name="top-attach">3</property>
<property name="left-attach">0</property>
</packing>
</child> </child>
</template> </template>
<object class="GtkPopoverMenu" id="oars_popover"> <object class="GtkPopoverMenu" id="oars_popover" />
<accessibility>
<relation target="oars_button" type="popup-for"/>
</accessibility>
</object>
<menu id="age_menu" /> <menu id="age_menu" />
<object class="GtkSizeGroup">
<property name="mode">horizontal</property>
<widgets>
<widget name="oars_button" />
<widget name="oars_popover" />
</widgets>
</object>
<object class="GtkSizeGroup">
<property name="mode">horizontal</property>
<widgets>
<widget name="restrict_web_browsers_label" />
<widget name="restrict_web_browsers_description" />
<widget name="restrict_applications_label" />
<widget name="restrict_applications_description" />
<widget name="oars_label" />
<widget name="restrict_software_installation_label" />
</widgets>
</object>
<object class="MctRestrictApplicationsDialog" id="restrict_applications_dialog"> <object class="MctRestrictApplicationsDialog" id="restrict_applications_dialog">
<property name="visible">False</property> <property name="visible">False</property>
<property name="modal">True</property> <property name="modal">True</property>
<property name="destroy-with-parent">False</property> <property name="destroy-with-parent">False</property>
<property name="use-header-bar">1</property> <signal name="close-request" handler="on_restrict_applications_dialog_close_request_cb" />
<signal name="delete-event" handler="on_restrict_applications_dialog_delete_event_cb" />
<signal name="response" handler="on_restrict_applications_dialog_response_cb" />
</object> </object>
</interface> </interface>

View File

@ -27,6 +27,7 @@
#include <glib/gi18n-lib.h> #include <glib/gi18n-lib.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <adwaita.h>
#include <libmalcontent-ui/malcontent-ui.h> #include <libmalcontent-ui/malcontent-ui.h>
#include <polkit/polkit.h> #include <polkit/polkit.h>
@ -76,8 +77,7 @@ struct _MctApplication
MctUserSelector *user_selector; MctUserSelector *user_selector;
MctUserControls *user_controls; MctUserControls *user_controls;
GtkStack *main_stack; GtkStack *main_stack;
GtkLabel *error_title; AdwStatusPage *error_page;
GtkLabel *error_message;
GtkLockButton *lock_button; GtkLockButton *lock_button;
GtkButton *user_accounts_panel_button; GtkButton *user_accounts_panel_button;
GtkLabel *help_label; GtkLabel *help_label;
@ -209,11 +209,9 @@ mct_application_activate (GApplication *application)
self->main_stack = GTK_STACK (gtk_builder_get_object (builder, "main_stack")); self->main_stack = GTK_STACK (gtk_builder_get_object (builder, "main_stack"));
self->user_selector = MCT_USER_SELECTOR (gtk_builder_get_object (builder, "user_selector")); self->user_selector = MCT_USER_SELECTOR (gtk_builder_get_object (builder, "user_selector"));
self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls")); self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls"));
self->error_title = GTK_LABEL (gtk_builder_get_object (builder, "error_title")); self->error_page = ADW_STATUS_PAGE (gtk_builder_get_object (builder, "error_page"));
self->error_message = GTK_LABEL (gtk_builder_get_object (builder, "error_message"));
self->lock_button = GTK_LOCK_BUTTON (gtk_builder_get_object (builder, "lock_button")); self->lock_button = GTK_LOCK_BUTTON (gtk_builder_get_object (builder, "lock_button"));
self->user_accounts_panel_button = GTK_BUTTON (gtk_builder_get_object (builder, "user_accounts_panel_button")); self->user_accounts_panel_button = GTK_BUTTON (gtk_builder_get_object (builder, "user_accounts_panel_button"));
self->help_label = GTK_LABEL (gtk_builder_get_object (builder, "help_label"));
/* Connect signals. */ /* Connect signals. */
g_signal_connect_object (self->user_selector, "notify::user", g_signal_connect_object (self->user_selector, "notify::user",
@ -230,7 +228,7 @@ mct_application_activate (GApplication *application)
user_selector_notify_user_cb (G_OBJECT (self->user_selector), NULL, self); user_selector_notify_user_cb (G_OBJECT (self->user_selector), NULL, self);
user_manager_notify_is_loaded_cb (G_OBJECT (self->user_manager), NULL, self); user_manager_notify_is_loaded_cb (G_OBJECT (self->user_manager), NULL, self);
gtk_widget_show (GTK_WIDGET (window)); gtk_window_present (GTK_WINDOW (window));
} }
/* Bring the window to the front. */ /* Bring the window to the front. */
@ -250,6 +248,8 @@ mct_application_startup (GApplication *application)
/* Chain up. */ /* Chain up. */
G_APPLICATION_CLASS (mct_application_parent_class)->startup (application); G_APPLICATION_CLASS (mct_application_parent_class)->startup (application);
adw_init ();
g_action_map_add_action_entries (G_ACTION_MAP (application), app_entries, g_action_map_add_action_entries (G_ACTION_MAP (application), app_entries,
G_N_ELEMENTS (app_entries), application); G_N_ELEMENTS (app_entries), application);
@ -323,13 +323,16 @@ about_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data
} }
static void static void
help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data) on_malcontent_help_shown_finished_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{ {
MctApplication *self = MCT_APPLICATION (user_data); MctApplication *self = MCT_APPLICATION (user_data);
g_autoptr(GError) local_error = NULL; g_autoptr(GError) local_error = NULL;
if (!gtk_show_uri_on_window (mct_application_get_main_window (self), "help:malcontent", if (!gtk_show_uri_full_finish (mct_application_get_main_window (self),
gtk_get_current_event_time (), &local_error)) result,
&local_error))
{ {
GtkWidget *dialog = gtk_message_dialog_new (mct_application_get_main_window (self), GtkWidget *dialog = gtk_message_dialog_new (mct_application_get_main_window (self),
GTK_DIALOG_MODAL, GTK_DIALOG_MODAL,
@ -337,13 +340,23 @@ help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
GTK_BUTTONS_OK, GTK_BUTTONS_OK,
_("The help contents could not be displayed")); _("The help contents could not be displayed"));
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", local_error->message); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", local_error->message);
gtk_window_present (GTK_WINDOW (dialog));
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
} }
} }
static void
help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
{
MctApplication *self = MCT_APPLICATION (user_data);
gtk_show_uri_full (mct_application_get_main_window (self),
"help:malcontent",
GDK_CURRENT_TIME,
NULL,
on_malcontent_help_shown_finished_cb,
self);
}
static void static void
quit_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data) quit_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
{ {
@ -371,10 +384,10 @@ update_main_stack (MctApplication *self)
if ((is_user_manager_loaded && act_user_manager_no_service (self->user_manager)) || if ((is_user_manager_loaded && act_user_manager_no_service (self->user_manager)) ||
self->permission_error != NULL) self->permission_error != NULL)
{ {
gtk_label_set_label (self->error_title, adw_status_page_set_title (self->error_page,
_("Failed to load user data from the system")); _("Failed to load user data from the system"));
gtk_label_set_label (self->error_message, adw_status_page_set_description (self->error_page,
_("Please make sure that the AccountsService is installed and enabled.")); _("Please make sure that the AccountsService is installed and enabled."));
new_page_name = "error"; new_page_name = "error";
new_focus_widget = NULL; new_focus_widget = NULL;
@ -410,7 +423,7 @@ update_main_stack (MctApplication *self)
"with %s. <a href='https://www.commonsensemedia.org/privacy-and-internet-safety'>" "with %s. <a href='https://www.commonsensemedia.org/privacy-and-internet-safety'>"
"Read guidance</a> on what to consider."), "Read guidance</a> on what to consider."),
act_user_get_real_name (selected_user)); act_user_get_real_name (selected_user));
gtk_label_set_markup (self->help_label, help_label); mct_user_controls_set_description (self->user_controls, help_label);
mct_user_controls_set_user (self->user_controls, selected_user); mct_user_controls_set_user (self->user_controls, selected_user);
@ -427,12 +440,7 @@ update_main_stack (MctApplication *self)
gtk_stack_set_visible_child_name (self->main_stack, new_page_name); gtk_stack_set_visible_child_name (self->main_stack, new_page_name);
if (new_focus_widget != NULL && !g_str_equal (old_page_name, new_page_name)) if (new_focus_widget != NULL && !g_str_equal (old_page_name, new_page_name))
{ gtk_widget_grab_focus (new_focus_widget);
if (gtk_widget_get_can_focus (new_focus_widget))
gtk_widget_grab_focus (new_focus_widget);
else
gtk_widget_child_focus (new_focus_widget, GTK_DIR_TAB_FORWARD);
}
} }
static void static void

View File

@ -18,6 +18,7 @@
* *
* Authors: * Authors:
* - Felipe Borges <felipeborges@gnome.org> * - Felipe Borges <felipeborges@gnome.org>
* - Georges Basile Stavracas Neto <georges@endlessos.org>
* - Philip Withnall <withnall@endlessm.com> * - Philip Withnall <withnall@endlessm.com>
*/ */
@ -29,13 +30,16 @@
#define ARROW_SIZE 20 #define ARROW_SIZE 20
#define MCT_TYPE_CAROUSEL_LAYOUT (mct_carousel_layout_get_type ())
G_DECLARE_FINAL_TYPE (MctCarouselLayout, mct_carousel_layout, MCT, CAROUSEL_LAYOUT, GtkLayoutManager)
struct _MctCarouselItem { struct _MctCarouselItem {
GtkRadioButton parent; GtkButton parent;
gint page; gint page;
}; };
G_DEFINE_TYPE (MctCarouselItem, mct_carousel_item, GTK_TYPE_RADIO_BUTTON) G_DEFINE_TYPE (MctCarouselItem, mct_carousel_item, GTK_TYPE_BUTTON)
GtkWidget * GtkWidget *
mct_carousel_item_new (void) mct_carousel_item_new (void)
@ -43,28 +47,36 @@ mct_carousel_item_new (void)
return g_object_new (MCT_TYPE_CAROUSEL_ITEM, NULL); return g_object_new (MCT_TYPE_CAROUSEL_ITEM, NULL);
} }
void
mct_carousel_item_set_child (MctCarouselItem *self,
GtkWidget *child)
{
g_return_if_fail (MCT_IS_CAROUSEL_ITEM (self));
gtk_button_set_child (GTK_BUTTON (self), child);
}
static void static void
mct_carousel_item_class_init (MctCarouselItemClass *klass) mct_carousel_item_class_init (MctCarouselItemClass *klass)
{ {
gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "carousel-item");
} }
static void static void
mct_carousel_item_init (MctCarouselItem *self) mct_carousel_item_init (MctCarouselItem *self)
{ {
gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (self), FALSE);
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)),
"carousel-item");
} }
struct _MctCarousel { struct _MctCarousel {
GtkRevealer parent; AdwBin parent;
GtkRevealer *revealer;
GList *children; GList *children;
gint visible_page; gint visible_page;
MctCarouselItem *selected_item; MctCarouselItem *selected_item;
GtkWidget *last_box; GtkWidget *last_box;
GtkWidget *arrow; GtkWidget *arrow;
gint arrow_start_x;
/* Widgets */ /* Widgets */
GtkStack *stack; GtkStack *stack;
@ -74,7 +86,7 @@ struct _MctCarousel {
GtkStyleProvider *provider; GtkStyleProvider *provider;
}; };
G_DEFINE_TYPE (MctCarousel, mct_carousel, GTK_TYPE_REVEALER) G_DEFINE_TYPE (MctCarousel, mct_carousel, ADW_TYPE_BIN)
enum { enum {
ITEM_ACTIVATED, ITEM_ACTIVATED,
@ -91,9 +103,9 @@ mct_carousel_item_get_x (MctCarouselItem *item,
{ {
GtkWidget *widget, *parent; GtkWidget *widget, *parent;
gint width; gint width;
gint dest_x; gdouble dest_x;
parent = GTK_WIDGET (carousel->stack); parent = GTK_WIDGET (carousel->revealer);
widget = GTK_WIDGET (item); widget = GTK_WIDGET (item);
width = gtk_widget_get_allocated_width (widget); width = gtk_widget_get_allocated_width (widget);
@ -116,8 +128,6 @@ mct_carousel_move_arrow (MctCarousel *self)
GtkStyleContext *context; GtkStyleContext *context;
gchar *css; gchar *css;
gint end_x; gint end_x;
GtkSettings *settings;
gboolean animations;
if (!self->selected_item) if (!self->selected_item)
return; return;
@ -129,31 +139,9 @@ mct_carousel_move_arrow (MctCarousel *self)
gtk_style_context_remove_provider (context, self->provider); gtk_style_context_remove_provider (context, self->provider);
g_clear_object (&self->provider); g_clear_object (&self->provider);
settings = gtk_widget_get_settings (GTK_WIDGET (self)); css = g_strdup_printf ("* { margin-left: %dpx; }", end_x);
g_object_get (settings, "gtk-enable-animations", &animations, NULL);
/* Animate the arrow movement if animations are enabled. Otherwise,
* jump the arrow to the right location instantly. */
if (animations)
{
css = g_strdup_printf ("@keyframes arrow_keyframes-%d-%d {\n"
" from { margin-left: %dpx; }\n"
" to { margin-left: %dpx; }\n"
"}\n"
"* {\n"
" animation-name: arrow_keyframes-%d-%d;\n"
"}\n",
self->arrow_start_x, end_x,
self->arrow_start_x, end_x,
self->arrow_start_x, end_x);
}
else
{
css = g_strdup_printf ("* { margin-left: %dpx }", end_x);
}
self->provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); self->provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (self->provider), css, -1, NULL); gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (self->provider), css, -1);
gtk_style_context_add_provider (context, self->provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); gtk_style_context_add_provider (context, self->provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_free (css); g_free (css);
@ -234,10 +222,7 @@ mct_carousel_select_item (MctCarousel *self,
} }
if (self->selected_item != NULL) if (self->selected_item != NULL)
{ page_changed = (self->selected_item->page != item->page);
page_changed = (self->selected_item->page != item->page);
self->arrow_start_x = mct_carousel_item_get_x (self->selected_item, self);
}
self->selected_item = item; self->selected_item = item;
self->visible_page = item->page; self->visible_page = item->page;
@ -300,59 +285,44 @@ mct_carousel_goto_next_page (GtkWidget *button,
mct_carousel_select_item_at_index (self, self->visible_page * ITEMS_PER_PAGE); mct_carousel_select_item_at_index (self, self->visible_page * ITEMS_PER_PAGE);
} }
static void void
mct_carousel_add (GtkContainer *container, mct_carousel_add (MctCarousel *self,
GtkWidget *widget) MctCarouselItem *item)
{ {
MctCarousel *self = MCT_CAROUSEL (container);
gboolean last_box_is_full; gboolean last_box_is_full;
if (!MCT_IS_CAROUSEL_ITEM (widget)) g_return_if_fail (MCT_IS_CAROUSEL (self));
{ g_return_if_fail (MCT_IS_CAROUSEL_ITEM (item));
GTK_CONTAINER_CLASS (mct_carousel_parent_class)->add (container, widget);
return;
}
gtk_style_context_add_class (gtk_widget_get_style_context (widget), "menu"); self->children = g_list_append (self->children, item);
gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE); item->page = get_last_page_number (self);
g_signal_connect (item, "clicked", G_CALLBACK (on_item_toggled), self);
self->children = g_list_append (self->children, widget);
MCT_CAROUSEL_ITEM (widget)->page = get_last_page_number (self);
if (self->selected_item != NULL)
gtk_radio_button_join_group (GTK_RADIO_BUTTON (widget), GTK_RADIO_BUTTON (self->selected_item));
g_signal_connect (widget, "button-press-event", G_CALLBACK (on_item_toggled), self);
last_box_is_full = ((g_list_length (self->children) - 1) % ITEMS_PER_PAGE == 0); last_box_is_full = ((g_list_length (self->children) - 1) % ITEMS_PER_PAGE == 0);
if (last_box_is_full) if (last_box_is_full)
{ {
g_autofree gchar *page = NULL; g_autofree gchar *page = NULL;
page = g_strdup_printf ("%d", MCT_CAROUSEL_ITEM (widget)->page); page = g_strdup_printf ("%d", item->page);
self->last_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); self->last_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 18);
gtk_widget_show (self->last_box); gtk_widget_set_hexpand (self->last_box, TRUE);
gtk_widget_set_valign (self->last_box, GTK_ALIGN_CENTER); gtk_widget_set_valign (self->last_box, GTK_ALIGN_CENTER);
gtk_box_set_homogeneous (GTK_BOX (self->last_box), TRUE);
gtk_stack_add_named (self->stack, self->last_box, page); gtk_stack_add_named (self->stack, self->last_box, page);
} }
gtk_widget_show_all (widget); gtk_box_append (GTK_BOX (self->last_box), GTK_WIDGET (item));
gtk_box_pack_start (GTK_BOX (self->last_box), widget, TRUE, FALSE, 10);
update_buttons_visibility (self); update_buttons_visibility (self);
} }
static void
destroy_widget_cb (GtkWidget *widget,
gpointer user_data)
{
gtk_widget_destroy (widget);
}
void void
mct_carousel_purge_items (MctCarousel *self) mct_carousel_purge_items (MctCarousel *self)
{ {
gtk_container_forall (GTK_CONTAINER (self->stack), GtkWidget *child;
destroy_widget_cb,
NULL); while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->stack))) != NULL)
gtk_stack_remove (self->stack, child);
g_list_free (self->children); g_list_free (self->children);
self->children = NULL; self->children = NULL;
@ -386,7 +356,6 @@ mct_carousel_class_init (MctCarouselClass *klass)
{ {
GObjectClass *object_class = G_OBJECT_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
gtk_widget_class_set_template_from_resource (wclass, gtk_widget_class_set_template_from_resource (wclass,
"/org/freedesktop/MalcontentControl/ui/carousel.ui"); "/org/freedesktop/MalcontentControl/ui/carousel.ui");
@ -395,13 +364,14 @@ mct_carousel_class_init (MctCarouselClass *klass)
gtk_widget_class_bind_template_child (wclass, MctCarousel, go_back_button); gtk_widget_class_bind_template_child (wclass, MctCarousel, go_back_button);
gtk_widget_class_bind_template_child (wclass, MctCarousel, go_next_button); gtk_widget_class_bind_template_child (wclass, MctCarousel, go_next_button);
gtk_widget_class_bind_template_child (wclass, MctCarousel, arrow); gtk_widget_class_bind_template_child (wclass, MctCarousel, arrow);
gtk_widget_class_bind_template_child (wclass, MctCarousel, revealer);
gtk_widget_class_bind_template_callback (wclass, mct_carousel_goto_previous_page); gtk_widget_class_bind_template_callback (wclass, mct_carousel_goto_previous_page);
gtk_widget_class_bind_template_callback (wclass, mct_carousel_goto_next_page); gtk_widget_class_bind_template_callback (wclass, mct_carousel_goto_next_page);
object_class->dispose = mct_carousel_dispose; gtk_widget_class_set_layout_manager_type (wclass, MCT_TYPE_CAROUSEL_LAYOUT);
container_class->add = mct_carousel_add; object_class->dispose = mct_carousel_dispose;
signals[ITEM_ACTIVATED] = signals[ITEM_ACTIVATED] =
g_signal_new ("item-activated", g_signal_new ("item-activated",
@ -414,19 +384,6 @@ mct_carousel_class_init (MctCarouselClass *klass)
MCT_TYPE_CAROUSEL_ITEM); MCT_TYPE_CAROUSEL_ITEM);
} }
static void
on_size_allocate (MctCarousel *self)
{
if (self->selected_item == NULL)
return;
if (gtk_stack_get_transition_running (self->stack))
return;
self->arrow_start_x = mct_carousel_item_get_x (self->selected_item, self);
mct_carousel_move_arrow (self);
}
static void static void
on_transition_running (MctCarousel *self) on_transition_running (MctCarousel *self)
{ {
@ -445,13 +402,12 @@ mct_carousel_init (MctCarousel *self)
gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider), gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider),
"/org/freedesktop/MalcontentControl/ui/carousel.css"); "/org/freedesktop/MalcontentControl/ui/carousel.css");
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), gtk_style_context_add_provider_for_display (gdk_display_get_default (),
provider, provider,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
g_object_unref (provider); g_object_unref (provider);
g_signal_connect_swapped (self->stack, "size-allocate", G_CALLBACK (on_size_allocate), self);
g_signal_connect_swapped (self->stack, "notify::transition-running", G_CALLBACK (on_transition_running), self); g_signal_connect_swapped (self->stack, "notify::transition-running", G_CALLBACK (on_transition_running), self);
} }
@ -460,3 +416,77 @@ mct_carousel_get_item_count (MctCarousel *self)
{ {
return g_list_length (self->children); return g_list_length (self->children);
} }
void
mct_carousel_set_revealed (MctCarousel *self,
gboolean revealed)
{
g_return_if_fail (MCT_IS_CAROUSEL (self));
gtk_revealer_set_reveal_child (self->revealer, revealed);
}
struct _MctCarouselLayout {
GtkLayoutManager parent;
};
G_DEFINE_FINAL_TYPE (MctCarouselLayout, mct_carousel_layout, GTK_TYPE_LAYOUT_MANAGER)
static void
mct_carousel_layout_measure (GtkLayoutManager *layout_manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
MctCarousel *carousel;
g_assert (MCT_IS_CAROUSEL (widget));
carousel = MCT_CAROUSEL (widget);
gtk_widget_measure (GTK_WIDGET (carousel->revealer),
orientation, for_size,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
mct_carousel_layout_allocate (GtkLayoutManager *layout_manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
MctCarousel *carousel;
g_assert (MCT_IS_CAROUSEL (widget));
carousel = MCT_CAROUSEL (widget);
gtk_widget_allocate (GTK_WIDGET (carousel->revealer), width, height, baseline, NULL);
if (carousel->selected_item == NULL)
return;
if (gtk_stack_get_transition_running (carousel->stack))
return;
mct_carousel_move_arrow (carousel);
}
static void
mct_carousel_layout_class_init (MctCarouselLayoutClass *klass)
{
GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
layout_manager_class->measure = mct_carousel_layout_measure;
layout_manager_class->allocate = mct_carousel_layout_allocate;
}
static void
mct_carousel_layout_init (MctCarouselLayout *self)
{
}

View File

@ -22,7 +22,7 @@
margin-bottom: -2px; margin-bottom: -2px;
} }
.carousel-item { carousel-item {
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
border: none; border: none;

View File

@ -18,24 +18,28 @@
* *
* Authors: * Authors:
* - Felipe Borges <felipeborges@gnome.org> * - Felipe Borges <felipeborges@gnome.org>
* - Georges Basile Stavracas Neto <georges@endlessos.org>
* - Philip Withnall <withnall@endlessm.com> * - Philip Withnall <withnall@endlessm.com>
*/ */
#pragma once #pragma once
#include <gtk/gtk.h> #include <adwaita.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define MCT_TYPE_CAROUSEL_ITEM (mct_carousel_item_get_type ()) #define MCT_TYPE_CAROUSEL_ITEM (mct_carousel_item_get_type ())
G_DECLARE_FINAL_TYPE (MctCarouselItem, mct_carousel_item, MCT, CAROUSEL_ITEM, GtkRadioButton) G_DECLARE_FINAL_TYPE (MctCarouselItem, mct_carousel_item, MCT, CAROUSEL_ITEM, GtkButton)
#define MCT_TYPE_CAROUSEL (mct_carousel_get_type ()) #define MCT_TYPE_CAROUSEL (mct_carousel_get_type ())
G_DECLARE_FINAL_TYPE (MctCarousel, mct_carousel, MCT, CAROUSEL, GtkRevealer) G_DECLARE_FINAL_TYPE (MctCarousel, mct_carousel, MCT, CAROUSEL, AdwBin)
GtkWidget *mct_carousel_item_new (void); GtkWidget *mct_carousel_item_new (void);
void mct_carousel_item_set_child (MctCarouselItem *self,
GtkWidget *child);
MctCarousel *mct_carousel_new (void); MctCarousel *mct_carousel_new (void);
void mct_carousel_purge_items (MctCarousel *self); void mct_carousel_purge_items (MctCarousel *self);
@ -49,4 +53,10 @@ void mct_carousel_select_item (MctCarousel *self,
guint mct_carousel_get_item_count (MctCarousel *self); guint mct_carousel_get_item_count (MctCarousel *self);
void mct_carousel_add (MctCarousel *self,
MctCarouselItem *item);
void mct_carousel_set_revealed (MctCarousel *self,
gboolean revealed);
G_END_DECLS G_END_DECLS

View File

@ -3,100 +3,76 @@
<!-- Copyright © 2020 Endless, Inc. --> <!-- Copyright © 2020 Endless, Inc. -->
<interface> <interface>
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<template class="MctCarousel" parent="GtkRevealer"> <template class="MctCarousel" parent="AdwBin">
<property name="transition_duration">400</property>
<property name="reveal-child">True</property>
<child> <child>
<object class="GtkOverlay"> <object class="GtkRevealer" id="revealer">
<property name="visible">True</property> <property name="transition_duration">400</property>
<property name="hexpand">True</property> <property name="reveal-child">True</property>
<property name="border_width">16</property>
<child> <child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="transition_duration">400</property>
<property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
<style>
<class name="location-bar"/>
</style>
</object>
</child>
<child type="overlay">
<object class="GtkOverlay"> <object class="GtkOverlay">
<property name="visible">True</property> <property name="hexpand">True</property>
<child> <child>
<object class="GtkBox"> <object class="GtkStack" id="stack">
<property name="visible">True</property> <property name="margin-top">16</property>
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="margin-bottom">16</property>
<property name="border_width">12</property> <property name="margin-start">16</property>
<child> <property name="margin-end">16</property>
<object class="GtkButton" id="go_back_button"> <property name="transition_duration">400</property>
<property name="visible">False</property> <property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
<property name="can_focus">True</property> <style>
<property name="valign">center</property> <class name="location-bar"/>
<style> </style>
<class name="circular"/> </object>
</style> </child>
<child> <child type="overlay">
<object class="GtkImage"> <object class="GtkButton" id="go_back_button">
<property name="visible">True</property> <property name="visible">False</property>
<property name="can_focus">False</property> <property name="halign">start</property>
<property name="icon-size">4</property> <property name="valign">center</property>
<property name="icon_name">go-previous-symbolic</property> <property name="margin-top">12</property>
<child internal-child="accessible"> <property name="margin-bottom">12</property>
<object class="AtkObject"> <property name="margin-start">12</property>
<property name="accessible-name" translatable="yes">Previous Page</property> <property name="margin-end">12</property>
</object> <property name="icon_name">go-previous-symbolic</property>
</child> <accessibility>
</object> <property name="label" translatable="yes">Previous Page</property>
</child> </accessibility>
<signal name="clicked" handler="mct_carousel_goto_previous_page" object="MctCarousel" swapped="no"/> <style>
</object> <class name="circular"/>
<packing> </style>
<property name="pack_type">GTK_PACK_START</property> <signal name="clicked" handler="mct_carousel_goto_previous_page" object="MctCarousel" swapped="no"/>
</packing> </object>
</child> </child>
<child> <child type="overlay">
<object class="GtkButton" id="go_next_button"> <object class="GtkButton" id="go_next_button">
<property name="can_focus">True</property> <property name="valign">center</property>
<property name="valign">center</property> <property name="halign">end</property>
<style> <property name="margin-top">12</property>
<class name="circular"/> <property name="margin-bottom">12</property>
</style> <property name="margin-start">12</property>
<child> <property name="margin-end">12</property>
<object class="GtkImage"> <property name="hexpand">True</property>
<property name="visible">True</property> <property name="icon_name">go-next-symbolic</property>
<property name="can_focus">False</property> <accessibility>
<property name="icon-size">4</property> <property name="label" translatable="yes">Next Page</property>
<property name="icon_name">go-next-symbolic</property> </accessibility>
<child internal-child="accessible"> <style>
<object class="AtkObject"> <class name="circular"/>
<property name="accessible-name" translatable="yes">Next Page</property> </style>
</object> <signal name="clicked" handler="mct_carousel_goto_next_page" object="MctCarousel" swapped="no"/>
</child>
</object>
</child>
<signal name="clicked" handler="mct_carousel_goto_next_page" object="MctCarousel" swapped="no"/>
</object>
<packing>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</object> </object>
</child> </child>
<child type="overlay"> <child type="overlay">
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="can-focus">False</property>
<property name="valign">GTK_ALIGN_END</property> <property name="valign">GTK_ALIGN_END</property>
<style> <style>
<class name="carousel-arrow-container"/> <class name="carousel-arrow-container"/>
</style> </style>
<child> <child>
<object class="GtkOverlay"> <object class="GtkOverlay">
<property name="visible">True</property>
<child> <child>
<object class="GtkBox" id="arrow"> <object class="GtkBox" id="arrow">
<property name="visible">True</property>
<property name="halign">GTK_ALIGN_END</property> <property name="halign">GTK_ALIGN_END</property>
<style> <style>
<class name="carousel-arrow"/> <class name="carousel-arrow"/>
@ -105,7 +81,6 @@
</child> </child>
<child type="overlay"> <child type="overlay">
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property>
<property name="halign">GTK_ALIGN_END</property> <property name="halign">GTK_ALIGN_END</property>
<style> <style>
<class name="carousel-inner-arrow"/> <class name="carousel-inner-arrow"/>
@ -115,14 +90,8 @@
</object> </object>
</child> </child>
</object> </object>
<packing>
<property name="pass-through">True</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="pass-through">True</property>
</packing>
</child> </child>
</object> </object>
</child> </child>

View File

@ -2,297 +2,163 @@
<!-- Copyright © 2019, 2020 Endless Mobile, Inc. --> <!-- Copyright © 2019, 2020 Endless Mobile, Inc. -->
<interface> <interface>
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<object class="GtkApplicationWindow" id="main_window"> <object class="AdwApplicationWindow" id="main_window">
<property name="default-width">540</property> <property name="default-width">540</property>
<property name="default-height">580</property> <property name="default-height">580</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<property name="show-close-button">True</property>
<!-- Translators: This is the title of the main window -->
<property name="title" translatable="yes">Parental Controls</property>
<property name="visible">True</property>
<child>
<object class="GtkMenuButton" id="primary_menu">
<property name="visible">True</property>
<property name="direction">none</property>
<property name="can-focus">True</property>
<property name="use-popover">True</property>
<property name="menu-model">primary-menu</property>
<accelerator key="F10" signal="activate"/>
<style>
<class name="image-button"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
<child> <child>
<object class="GtkStack" id="main_stack"> <object class="GtkBox">
<property name="visible">True</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox"> <object class="GtkHeaderBar" id="header">
<property name="visible">True</property> <property name="show-title-buttons">True</property>
<property name="orientation">vertical</property> <property name="title-widget">
<property name="border_width">0</property> <object class="AdwWindowTitle">
<child> <!-- Translators: This is the title of the main window -->
<object class="MctUserSelector" id="user_selector"> <property name="title" translatable="yes">Parental Controls</property>
<property name="visible">True</property>
<property name="user-manager">user_manager</property>
<property name="show-administrators">False</property>
<accessibility>
<relation target="user_controls" type="controller-for"/>
</accessibility>
</object> </object>
<packing> </property>
<property name="fill">False</property> <child type="end">
<property name="expand">False</property> <object class="GtkMenuButton" id="primary_menu">
</packing> <property name="direction">none</property>
</child> <property name="menu-model">primary-menu</property>
<child> <style>
<object class="GtkScrolledWindow"> <class name="image-button"/>
<property name="visible">True</property> </style>
<property name="hscrollbar-policy">never</property>
<property name="min-content-height">370</property>
<child> <child>
<object class="GtkBox"> <object class="GtkShortcutController">
<property name="margin">18</property> <property name="scope">global</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="visible">True</property>
<child> <child>
<object class="GtkLabel" id="help_label"> <object class="GtkShortcut">
<!-- Content is set in code; this string is just a placeholder --> <property name="trigger">F10</property>
<property name="label">Its recommended that restrictions are set as part of an ongoing conversation with $name. Read guidance on what to consider.</property> <property name="action">activate</property>
<property name="visible">True</property> </object>
<property name="wrap">True</property> </child>
<property name="xalign">0.0</property> </object>
<property name="yalign">0.0</property> </child>
<child internal-child="accessible"> </object>
<object class="AtkObject"> </child>
<property name="AtkObject::accessible-role">static</property> </object>
</child>
<child>
<object class="GtkStack" id="main_stack">
<child>
<object class="GtkStackPage">
<property name="name">controls</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="MctUserSelector" id="user_selector">
<property name="user-manager">user_manager</property>
<property name="show-administrators">False</property>
<accessibility>
<relation name="controls">user_controls</relation>
</accessibility>
</object>
</child>
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="MctUserControls" id="user_controls">
<!-- Content is set in code; this string is just a placeholder -->
<property name="description">Its recommended that restrictions are set as part of an ongoing conversation with $name. Read guidance on what to consider.</property>
<property name="height-request">270</property>
<property name="dbus-connection">dbus_connection</property>
<property name="vexpand">True</property>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">unlock</property>
<property name="child">
<object class="AdwStatusPage">
<property name="title" translatable="yes">Permission Required</property>
<property name="description" translatable="yes">Permission is required to view and change user parental controls settings.</property>
<property name="icon-name">org.freedesktop.MalcontentControl</property>
<child> <child>
<object class="MctUserControls" id="user_controls"> <object class="GtkLockButton" id="lock_button">
<property name="halign">center</property> <property name="halign">center</property>
<property name="visible">True</property> <style>
<property name="dbus-connection">dbus_connection</property> <class name="suggested-action" />
<class name="pill" />
</style>
</object> </object>
</child> </child>
</object> </object>
</child> </property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
</object>
<packing>
<property name="name">controls</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="margin">18</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Permission Required</property>
<attributes>
<attribute name="scale" value="1.4"/>
</attributes>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Permission is required to view and change user parental controls settings.</property>
<property name="wrap">True</property>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLockButton" id="lock_button">
<property name="visible">True</property>
<property name="halign">center</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="can-focus">True</property>
<!-- Give 18px spacing between the label and the button -->
<property name="margin-top">6</property>
</object>
</child>
</object> </object>
</child> </child>
</object>
<packing>
<property name="name">unlock</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkStackPage">
<property name="visible">True</property> <property name="name">no-other-users</property>
<property name="orientation">vertical</property> <property name="child">
<property name="hexpand">True</property> <object class="AdwStatusPage">
<property name="vexpand">True</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="margin">18</property>
<child>
<object class="GtkImage">
<property name="icon-name">system-users-symbolic</property> <property name="icon-name">system-users-symbolic</property>
<property name="pixel-size">96</property> <property name="title" translatable="yes">No Standard User Accounts</property>
<property name="visible">True</property> <property name="description" translatable="yes">Parental controls can only be applied to standard user
<child internal-child="accessible"> accounts. These can be created in the user settings.</property>
<object class="AtkObject"> <child>
<property name="AtkObject::accessible-role">static</property> <object class="GtkButton" id="user_accounts_panel_button">
<property name="label" translatable="yes">_User Settings</property>
<property name="halign">center</property>
<property name="use-underline">True</property>
<style>
<class name="suggested-action"/>
</style>
</object> </object>
</child> </child>
</object> </object>
</child> </property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">No Standard User Accounts</property>
<attributes>
<attribute name="scale" value="1.4"/>
</attributes>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Parental controls can only be applied to standard user
accounts. These can be created in the user settings.</property>
<property name="wrap">True</property>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="user_accounts_panel_button">
<property name="visible">True</property>
<property name="label" translatable="yes">_User Settings</property>
<property name="halign">center</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="use-underline">True</property>
<property name="can-focus">True</property>
<!-- Give 18px spacing between the label and the button -->
<property name="margin-top">6</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object> </object>
</child> </child>
</object>
<packing>
<property name="name">no-other-users</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkStackPage">
<property name="visible">True</property> <property name="name">loading</property>
<property name="orientation">vertical</property> <property name="child">
<property name="hexpand">True</property> <object class="GtkBox">
<property name="vexpand">True</property> <property name="orientation">vertical</property>
<child type="center"> <property name="hexpand">True</property>
<object class="GtkLabel"> <property name="vexpand">True</property>
<property name="visible">True</property> <child type="center">
<property name="label" translatable="yes">Loading…</property> <object class="GtkLabel">
<attributes> <property name="label" translatable="yes">Loading…</property>
<attribute name="scale" value="1.4"/> <property name="vexpand">True</property>
</attributes> <style>
<child internal-child="accessible"> <class name="title-1" />
<object class="AtkObject"> </style>
<property name="AtkObject::accessible-role">static</property> </object>
</child>
</object> </object>
</child> </property>
</object> </object>
</child> </child>
</object>
<packing>
<property name="name">loading</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkStackPage">
<property name="visible">True</property> <property name="name">error</property>
<property name="orientation">vertical</property> <property name="child">
<property name="hexpand">True</property> <object class="AdwStatusPage" id="error_page">
<property name="vexpand">True</property> <property name="icon-name">dialog-error-symbolic</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="error_title">
<property name="visible">True</property>
<property name="label"></property>
<attributes>
<attribute name="scale" value="1.4"/>
</attributes>
</object> </object>
</child> </property>
<child>
<object class="GtkLabel" id="error_message">
<property name="visible">True</property>
<property name="label"></property>
</object>
</child>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">alert</property>
</object> </object>
</child> </child>
</object> </object>
<packing>
<property name="name">error</property>
</packing>
</child> </child>
</object> </object>
</child> </child>

View File

@ -27,8 +27,9 @@ malcontent_control = executable('malcontent-control',
dependency('gio-2.0', version: '>= 2.44'), dependency('gio-2.0', version: '>= 2.44'),
dependency('glib-2.0', version: '>= 2.54.2'), dependency('glib-2.0', version: '>= 2.54.2'),
dependency('gobject-2.0', version: '>= 2.54'), dependency('gobject-2.0', version: '>= 2.54'),
dependency('gtk+-3.0'), dependency('gtk4', version: '>= 4.6'),
dependency('polkit-gobject-1'), dependency('polkit-gobject-1'),
libadwaita_dep,
libmalcontent_dep, libmalcontent_dep,
libmalcontent_ui_dep, libmalcontent_ui_dep,
], ],
@ -83,7 +84,7 @@ if xmllint.found()
'validate-ui', xmllint, 'validate-ui', xmllint,
args: [ args: [
'--nonet', '--noblanks', '--noout', '--nonet', '--noblanks', '--noout',
'--relaxng', join_paths(gtk_prefix, 'share', 'gtk-3.0', 'gtkbuilder.rng'), '--relaxng', join_paths(gtk_prefix, 'share', 'gtk-4.0', 'gtk4builder.rng'),
files( files(
'carousel.ui', 'carousel.ui',
'main.ui', 'main.ui',

View File

@ -1,5 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
* *
* Copyright © 2022 Endless Mobile, Inc.
* Copyright © 2015 Red Hat, Inc. * Copyright © 2015 Red Hat, Inc.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -16,10 +17,11 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>. * along with this program; if not, see <http://www.gnu.org/licenses/>.
* *
* Authors: * Authors:
* - Georges Basile Stavracas Neto <georges@endlessos.org>
* - Ondrej Holy <oholy@redhat.com> * - Ondrej Holy <oholy@redhat.com>
*/ */
#include <gtk/gtk.h> #include <adwaita.h>
#include <act/act.h> #include <act/act.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -28,108 +30,49 @@
struct _MctUserImage struct _MctUserImage
{ {
GtkImage parent_instance; AdwBin parent_instance;
AdwAvatar *avatar;
ActUser *user; ActUser *user;
}; };
G_DEFINE_TYPE (MctUserImage, mct_user_image, GTK_TYPE_IMAGE) G_DEFINE_TYPE (MctUserImage, mct_user_image, ADW_TYPE_BIN)
static GdkPixbuf * static GdkTexture *
round_image (GdkPixbuf *pixbuf) render_user_icon_texture (ActUser *user)
{ {
GdkPixbuf *dest = NULL; g_autoptr(GdkTexture) texture = NULL;
cairo_surface_t *surface; g_autoptr(GError) error = NULL;
cairo_t *cr;
gint size;
size = gdk_pixbuf_get_width (pixbuf);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
cr = cairo_create (surface);
/* Clip a circle */
cairo_arc (cr, size / 2, size / 2, size / 2, 0, 2 * G_PI);
cairo_clip (cr);
cairo_new_path (cr);
gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
cairo_paint (cr);
dest = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
cairo_surface_destroy (surface);
cairo_destroy (cr);
return dest;
}
static cairo_surface_t *
render_user_icon (ActUser *user,
gint icon_size,
gint scale)
{
g_autoptr(GdkPixbuf) source_pixbuf = NULL;
GdkPixbuf *pixbuf = NULL;
GError *error;
const gchar *icon_file; const gchar *icon_file;
cairo_surface_t *surface = NULL;
g_return_val_if_fail (ACT_IS_USER (user), NULL); g_return_val_if_fail (ACT_IS_USER (user), NULL);
g_return_val_if_fail (icon_size > 12, NULL);
icon_file = act_user_get_icon_file (user); icon_file = act_user_get_icon_file (user);
pixbuf = NULL; if (icon_file == NULL)
if (icon_file) return NULL;
texture = gdk_texture_new_from_filename (icon_file, &error);
if (error != NULL)
{ {
source_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_file, g_warning ("Error loading user icon: %s", error->message);
icon_size * scale, return NULL;
icon_size * scale,
NULL);
if (source_pixbuf)
pixbuf = round_image (source_pixbuf);
} }
if (pixbuf != NULL) return g_steal_pointer (&texture);
goto out;
error = NULL;
pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
"avatar-default",
icon_size * scale,
GTK_ICON_LOOKUP_FORCE_SIZE,
&error);
if (error)
{
g_warning ("%s", error->message);
g_error_free (error);
}
out:
if (pixbuf != NULL)
{
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
g_object_unref (pixbuf);
}
return surface;
} }
static void static void
render_image (MctUserImage *image) render_image (MctUserImage *image)
{ {
cairo_surface_t *surface; g_autoptr(GdkTexture) texture = NULL;
gint scale, pixel_size;
if (image->user == NULL) if (image->user == NULL)
return; return;
pixel_size = gtk_image_get_pixel_size (GTK_IMAGE (image)); texture = render_user_icon_texture (image->user);
scale = gtk_widget_get_scale_factor (GTK_WIDGET (image)); adw_avatar_set_custom_image (image->avatar, GDK_PAINTABLE (texture));
surface = render_user_icon (image->user, adw_avatar_set_text (image->avatar, act_user_get_real_name (image->user));
pixel_size > 0 ? pixel_size : 48,
scale);
gtk_image_set_from_surface (GTK_IMAGE (image), surface);
cairo_surface_destroy (surface);
} }
void void
@ -163,8 +106,8 @@ mct_user_image_class_init (MctUserImageClass *class)
static void static void
mct_user_image_init (MctUserImage *image) mct_user_image_init (MctUserImage *image)
{ {
g_signal_connect_swapped (image, "notify::scale-factor", G_CALLBACK (render_image), image); image->avatar = ADW_AVATAR (adw_avatar_new (48, NULL, TRUE));
g_signal_connect_swapped (image, "notify::pixel-size", G_CALLBACK (render_image), image); adw_bin_set_child (ADW_BIN (image), GTK_WIDGET (image->avatar));
} }
GtkWidget * GtkWidget *

View File

@ -1,5 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
* *
* Copyright © 2022 Endless Mobile, Inc.
* Copyright © 2015 Red Hat, Inc. * Copyright © 2015 Red Hat, Inc.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -16,19 +17,20 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>. * along with this program; if not, see <http://www.gnu.org/licenses/>.
* *
* Authors: * Authors:
* - Georges Basile Stavracas Neto <georges@endlessos.org>
* - Ondrej Holy <oholy@redhat.com> * - Ondrej Holy <oholy@redhat.com>
*/ */
#pragma once #pragma once
#include <gtk/gtk.h> #include <adwaita.h>
#include <act/act.h> #include <act/act.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define MCT_TYPE_USER_IMAGE (mct_user_image_get_type ()) #define MCT_TYPE_USER_IMAGE (mct_user_image_get_type ())
G_DECLARE_FINAL_TYPE (MctUserImage, mct_user_image, MCT, USER_IMAGE, GtkImage) G_DECLARE_FINAL_TYPE (MctUserImage, mct_user_image, MCT, USER_IMAGE, AdwBin)
GtkWidget *mct_user_image_new (void); GtkWidget *mct_user_image_new (void);
void mct_user_image_set_user (MctUserImage *image, void mct_user_image_set_user (MctUserImage *image,

View File

@ -370,11 +370,11 @@ reload_users (MctUserSelector *self,
g_object_get (settings, "gtk-enable-animations", &animations, NULL); g_object_get (settings, "gtk-enable-animations", &animations, NULL);
g_object_set (settings, "gtk-enable-animations", FALSE, NULL); g_object_set (settings, "gtk-enable-animations", FALSE, NULL);
mct_carousel_purge_items (self->carousel);
list = act_user_manager_list_users (self->user_manager); list = act_user_manager_list_users (self->user_manager);
g_debug ("Got %u users", g_slist_length (list)); g_debug ("Got %u users", g_slist_length (list));
mct_carousel_purge_items (self->carousel);
list = g_slist_sort (list, (GCompareFunc) sort_users); list = g_slist_sort (list, (GCompareFunc) sort_users);
for (l = list; l; l = l->next) for (l = list; l; l = l->next)
{ {
@ -397,7 +397,7 @@ reload_users (MctUserSelector *self,
g_object_set (settings, "gtk-enable-animations", animations, NULL); g_object_set (settings, "gtk-enable-animations", animations, NULL);
gtk_revealer_set_reveal_child (GTK_REVEALER (self->carousel), TRUE); mct_carousel_set_revealed (self->carousel, TRUE);
} }
static GtkWidget * static GtkWidget *
@ -411,7 +411,7 @@ create_carousel_entry (MctUserSelector *self,
widget = mct_user_image_new (); widget = mct_user_image_new ();
mct_user_image_set_user (MCT_USER_IMAGE (widget), user); mct_user_image_set_user (MCT_USER_IMAGE (widget), user);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); gtk_box_append (GTK_BOX (box), widget);
label = g_strdup_printf ("<b>%s</b>", label = g_strdup_printf ("<b>%s</b>",
get_real_or_user_name (user)); get_real_or_user_name (user));
@ -419,7 +419,7 @@ create_carousel_entry (MctUserSelector *self,
gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
gtk_widget_set_margin_top (widget, 5); gtk_widget_set_margin_top (widget, 5);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); gtk_box_append (GTK_BOX (box), widget);
g_free (label); g_free (label);
if (act_user_get_uid (user) == getuid ()) if (act_user_get_uid (user) == getuid ())
@ -431,7 +431,7 @@ create_carousel_entry (MctUserSelector *self,
gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
g_free (label); g_free (label);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); gtk_box_append (GTK_BOX (box), widget);
gtk_style_context_add_class (gtk_widget_get_style_context (widget), gtk_style_context_add_class (gtk_widget_get_style_context (widget),
"dim-label"); "dim-label");
@ -455,10 +455,10 @@ user_added_cb (ActUserManager *user_manager,
widget = create_carousel_entry (self, user); widget = create_carousel_entry (self, user);
item = mct_carousel_item_new (); item = mct_carousel_item_new ();
gtk_container_add (GTK_CONTAINER (item), widget); mct_carousel_item_set_child (MCT_CAROUSEL_ITEM (item), widget);
g_object_set_data (G_OBJECT (item), "uid", GINT_TO_POINTER (act_user_get_uid (user))); g_object_set_data (G_OBJECT (item), "uid", GINT_TO_POINTER (act_user_get_uid (user)));
gtk_container_add (GTK_CONTAINER (self->carousel), item); mct_carousel_add (self->carousel, MCT_CAROUSEL_ITEM (item));
} }
static void static void

View File

@ -5,7 +5,6 @@
<template class="MctUserSelector" parent="GtkBox"> <template class="MctUserSelector" parent="GtkBox">
<child> <child>
<object class="MctCarousel" id="carousel"> <object class="MctCarousel" id="carousel">
<property name="visible">True</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<signal name="item-activated" handler="carousel_item_activated"/> <signal name="item-activated" handler="carousel_item_activated"/>
</object> </object>

View File

@ -134,6 +134,12 @@ else
'Malcontent-' + libmalcontent_api_version + '.typelib'] 'Malcontent-' + libmalcontent_api_version + '.typelib']
endif endif
if get_option('ui').enabled() if get_option('ui').enabled()
libadwaita_dep = dependency(
'libadwaita-1',
version: '>= 1.1',
fallback: ['libadwaita', 'libadwaita_dep'],
default_options: ['examples=false', 'introspection=disabled', 'tests=false', 'vapi=false'],
)
subdir('libmalcontent-ui') subdir('libmalcontent-ui')
endif endif
subdir('malcontent-client') subdir('malcontent-client')

View File

@ -0,0 +1,4 @@
[wrap-git]
url = https://gitlab.gnome.org/GNOME/libadwaita.git
revision = 1.1.0
depth = 1