/* dzl-util.c
*
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "dzl-util"
#include "config.h"
#include <string.h>
#include "dzl-util-private.h"
static void
dzl_gtk_border_sum (GtkBorder *one,
const GtkBorder *two)
{
one->top += two->top;
one->right += two->right;
one->bottom += two->bottom;
one->left += two->left;
}
void
dzl_gtk_style_context_get_borders (GtkStyleContext *style_context,
GtkBorder *borders)
{
GtkBorder border = { 0 };
GtkBorder padding = { 0 };
GtkBorder margin = { 0 };
GtkStateFlags state;
g_return_if_fail (GTK_IS_STYLE_CONTEXT (style_context));
g_return_if_fail (borders != NULL);
memset (borders, 0, sizeof *borders);
state = gtk_style_context_get_state (style_context);
gtk_style_context_get_border (style_context, state, &border);
gtk_style_context_get_padding (style_context, state, &padding);
gtk_style_context_get_margin (style_context, state, &margin);
dzl_gtk_border_sum (borders, &padding);
dzl_gtk_border_sum (borders, &border);
dzl_gtk_border_sum (borders, &margin);
}
static void
split_action_name (const gchar *action_name,
gchar **prefix,
gchar **name)
{
const gchar *dot;
g_assert (prefix != NULL);
g_assert (name != NULL);
*prefix = NULL;
*name = NULL;
if (action_name == NULL)
return;
dot = strchr (action_name, '.');
if (dot == NULL)
*name = g_strdup (action_name);
else
{
*prefix = g_strndup (action_name, dot - action_name);
*name = g_strdup (dot + 1);
}
}
gboolean
dzl_gtk_widget_activate_action (GtkWidget *widget,
const gchar *full_action_name,
GVariant *parameter)
{
GtkWidget *toplevel;
GApplication *app;
GActionGroup *group = NULL;
gchar *prefix = NULL;
gchar *action_name = NULL;
const gchar *dot;
gboolean ret = FALSE;
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
g_return_val_if_fail (full_action_name, FALSE);
dot = strchr (full_action_name, '.');
if (dot == NULL)
{
prefix = NULL;
action_name = g_strdup (full_action_name);
}
else
{
prefix = g_strndup (full_action_name, dot - full_action_name);
action_name = g_strdup (dot + 1);
}
/*
* TODO: Support non-grouped names. We need to walk
* through all the groups at each level to do this.
*/
if (prefix == NULL)
prefix = g_strdup ("win");
app = g_application_get_default ();
toplevel = gtk_widget_get_toplevel (widget);
while ((group == NULL) && (widget != NULL))
{
group = gtk_widget_get_action_group (widget, prefix);
if G_UNLIKELY (GTK_IS_POPOVER (widget))
{
GtkWidget *relative_to;
relative_to = gtk_popover_get_relative_to (GTK_POPOVER (widget));
if (relative_to != NULL)
widget = relative_to;
else
widget = gtk_widget_get_parent (widget);
}
else
{
widget = gtk_widget_get_parent (widget);
}
}
if (!group && g_str_equal (prefix, "win") && G_IS_ACTION_GROUP (toplevel))
group = G_ACTION_GROUP (toplevel);
if (!group && g_str_equal (prefix, "app") && G_IS_ACTION_GROUP (app))
group = G_ACTION_GROUP (app);
if (group && g_action_group_has_action (group, action_name))
{
g_action_group_activate_action (group, action_name, parameter);
ret = TRUE;
goto cleanup;
}
if (parameter && g_variant_is_floating (parameter))
{
parameter = g_variant_ref_sink (parameter);
g_variant_unref (parameter);
}
g_warning ("Failed to locate action %s.%s", prefix, action_name);
cleanup:
g_free (prefix);
g_free (action_name);
return ret;
}
static GActionGroup *
find_group_with_action (GtkWidget *widget,
const gchar *prefix,
const gchar *name)
{
GActionGroup *group;
g_assert (GTK_IS_WIDGET (widget));
g_assert (name != NULL);
/*
* GtkWidget does not provide a way to get group names,
* so there is nothing more we can do if prefix is NULL.
*/
if (prefix == NULL)
return NULL;
if (g_str_equal (prefix, "app"))
group = G_ACTION_GROUP (g_application_get_default ());
else
group = gtk_widget_get_action_group (widget, prefix);
if (group != NULL && g_action_group_has_action (group, name))
return group;
widget = gtk_widget_get_parent (widget);
if (widget != NULL)
return find_group_with_action (widget, prefix, name);
return NULL;
}
GVariant *
dzl_gtk_widget_get_action_state (GtkWidget *widget,
const gchar *action_name)
{
GActionGroup *group;
gchar *prefix = NULL;
gchar *name = NULL;
GVariant *ret = NULL;
split_action_name (action_name, &prefix, &name);
if (name == NULL || prefix == NULL)
goto cleanup;
group = find_group_with_action (widget, prefix, name);
if (group == NULL)
goto cleanup;
ret = g_action_group_get_action_state (group, name);
cleanup:
g_free (name);
g_free (prefix);
return ret;
}
GActionGroup *
dzl_gtk_widget_find_group_for_action (GtkWidget *widget,
const gchar *action_name)
{
GActionGroup *group = NULL;
gchar *prefix = NULL;
gchar *name = NULL;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
if (action_name == NULL)
return NULL;
split_action_name (action_name, &prefix, &name);
if (name == NULL || prefix == NULL)
goto cleanup;
group = find_group_with_action (widget, prefix, name);
cleanup:
g_free (name);
g_free (prefix);
return group;
}
void
dzl_g_action_name_parse (const gchar *action_name,
gchar **prefix,
gchar **name)
{
split_action_name (action_name, prefix, name);
}
gboolean
dzl_g_action_name_parse_full (const gchar *detailed_action_name,
gchar **prefix,
gchar **name,
GVariant **target)
{
g_autofree gchar *full_name = NULL;
g_autoptr(GVariant) target_value = NULL;
const gchar *dot;
if (detailed_action_name == NULL)
return FALSE;
if (!g_action_parse_detailed_name (detailed_action_name, &full_name, &target_value, NULL))
return FALSE;
if (target_value != NULL)
g_variant_take_ref (target_value);
dot = strchr (full_name, '.');
if (dot != NULL)
{
if (prefix != NULL)
*prefix = g_strndup (full_name, dot - full_name);
if (name != NULL)
*name = g_strdup (dot + 1);
}
else
{
*prefix = NULL;
*name = g_steal_pointer (&full_name);
}
if (target != NULL)
*target = g_steal_pointer (&target_value);
return TRUE;
}
void
dzl_gtk_allocation_subtract_border (GtkAllocation *alloc,
GtkBorder *border)
{
g_return_if_fail (alloc != NULL);
g_return_if_fail (border != NULL);
alloc->x += border->left;
alloc->y += border->top;
alloc->width -= (border->left + border->right);
alloc->height -= (border->top + border->bottom);
}
void
dzl_gtk_widget_add_class (GtkWidget *widget,
const gchar *class_name)
{
gtk_style_context_add_class (gtk_widget_get_style_context (widget), class_name);
}
void
dzl_gtk_widget_class_add_css_resource (GtkWidgetClass *widget_class,
const gchar *resource)
{
GdkScreen *screen = gdk_screen_get_default ();
g_return_if_fail (widget_class != NULL);
g_return_if_fail (resource != NULL);
if (screen != NULL)
{
g_autoptr(GtkCssProvider) provider = NULL;
/*
* It would be nice if our theme data could be more theme friendly.
* However, we need to be higher than SETTINGS so that some of our
* stuff takes effect, but that is already higher than theming.
*
* So really the only proper answer is to get themes to style us and
* eventually drop all CSS. Given that is impossible... I'm not sure
* what other options we really have. Some themes will just need to
* !important or whatever when it matters.
*/
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider, resource);
gtk_style_context_add_provider_for_screen (screen,
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_SETTINGS + 50);
}
}