/* dzl-application.c
*
* Copyright (C) 2014-2017 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-application"
#include "config.h"
#include "app/dzl-application.h"
/**
* SECTION:dzl-application
* @title: DzlApplication
* @short_description: Application base class with goodies
*
* #DzlApplication is an extension of #GtkApplication with extra features to
* integrate various libdazzle subsystems with your application. We suggest
* subclassing #DzlApplication.
*
* The #DzlApplication class provides:
*
* - Automatic menu merging including the "app-menu".
* - Automatic Icon loading based on resources-base-path.
* - Automatic theme tracking to load CSS variants based on user themes.
*
* The #DzlApplication class automatically manages loading alternate CSS based
* on the active theme by tracking #GtkSettings:gtk-theme-name. Additionally,
* it supports menu merging including the base "app-menu" as loaded by automatic
* #GResources in #GApplication:resource-base-path. It will autom
*/
typedef struct
{
/*
* The theme manager is used to load CSS resources based on the
* GtkSettings:gtk-theme-name property. We add plugin resource
* paths to this to ensure that we load plugin CSS files too.
*/
DzlThemeManager *theme_manager;
/*
* The menu manager deals with merging menu elements from multiple
* GtkBuilder files containing <menu> elements. We use the resource
* path to map to the merge_id in the hashtable.
*/
DzlMenuManager *menu_manager;
GHashTable *menu_merge_ids;
/*
* The shortcut manager can be used to autoload keyboard themes from
* plugins or the application resources.
*/
DzlShortcutManager *shortcut_manager;
/*
* Deferred resource loading. This can be used to call
* dzl_application_add_resources() before ::startup() has been called. Upon
* ::startup(), we'll apply these. If this is set to NULL, ::startup() has
* already been called.
*/
GPtrArray *deferred_resources;
} DzlApplicationPrivate;
enum {
PROP_0,
PROP_MENU_MANAGER,
PROP_SHORTCUT_MANAGER,
PROP_THEME_MANAGER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
G_DEFINE_TYPE_WITH_PRIVATE (DzlApplication, dzl_application, GTK_TYPE_APPLICATION)
static void
dzl_application_real_add_resources (DzlApplication *self,
const gchar *resource_path)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_autoptr(GError) error = NULL;
g_autofree gchar *menu_path = NULL;
g_autofree gchar *keythemes_path = NULL;
guint merge_id;
g_assert (DZL_IS_APPLICATION (self));
g_assert (resource_path != NULL);
/* We use interned strings for hash table keys */
resource_path = g_intern_string (resource_path);
/*
* Allow the theme manager to monitor the css/Adwaita.css or other themes
* based on gtk-theme-name. The theme manager also loads icons.
*/
dzl_theme_manager_add_resources (priv->theme_manager, resource_path);
/*
* If the resource path has a gtk/menus.ui file, we want to auto-load and
* merge the menus.
*/
menu_path = g_build_filename (resource_path, "gtk", "menus.ui", NULL);
if (g_str_has_prefix (menu_path, "resource://"))
merge_id = dzl_menu_manager_add_resource (priv->menu_manager, menu_path, &error);
else
merge_id = dzl_menu_manager_add_filename (priv->menu_manager, menu_path, &error);
if (merge_id != 0)
g_hash_table_insert (priv->menu_merge_ids, (gchar *)resource_path, GUINT_TO_POINTER (merge_id));
if (error != NULL &&
!(g_error_matches (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND) ||
g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)))
g_warning ("%s", error->message);
/*
* Load any shortcut theme information from the plugin or application
* resources. We always append so that the application resource dir is
* loaded before any plugin paths.
*/
keythemes_path = g_build_filename (resource_path, "shortcuts", NULL);
dzl_shortcut_manager_append_search_path (priv->shortcut_manager, keythemes_path);
}
static void
dzl_application_real_remove_resources (DzlApplication *self,
const gchar *resource_path)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_autofree gchar *keythemes_path = NULL;
guint merge_id;
g_assert (DZL_IS_APPLICATION (self));
g_assert (resource_path != NULL);
/* We use interned strings for hash table lookups */
resource_path = g_intern_string (resource_path);
/* Remove any loaded CSS providers for @resource_path/css/. */
dzl_theme_manager_remove_resources (priv->theme_manager, resource_path);
/* Remove any merged menus from the @resource_path/gtk/menus.ui */
merge_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->menu_merge_ids, resource_path));
if (merge_id != 0)
{
if (g_hash_table_contains (priv->menu_merge_ids, resource_path))
g_hash_table_remove (priv->menu_merge_ids, resource_path);
dzl_menu_manager_remove (priv->menu_manager, merge_id);
}
/* Remove keythemes path from the shortcuts manager */
keythemes_path = g_strjoin (NULL, "resource://", resource_path, "/shortcuts", NULL);
dzl_shortcut_manager_remove_search_path (priv->shortcut_manager, keythemes_path);
}
static void
dzl_application_startup (GApplication *app)
{
DzlApplication *self = (DzlApplication *)app;
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
const gchar *resource_base_path;
GMenu *app_menu;
g_assert (DZL_IS_APPLICATION (self));
G_APPLICATION_CLASS (dzl_application_parent_class)->startup (app);
/*
* We cannot register resources before chaining startup because
* the GtkSettings and other plumbing will not yet be initialized.
*/
/* Register our resources that are part of libdazzle. */
DZL_APPLICATION_GET_CLASS (self)->add_resources (self, "resource:///org/gnome/dazzle/");
/* Now register the application resources */
if (NULL != (resource_base_path = g_application_get_resource_base_path (app)))
{
g_autofree gchar *resource_path = NULL;
resource_path = g_strdup_printf ("resource://%s", resource_base_path);
DZL_APPLICATION_GET_CLASS (self)->add_resources (self, resource_path);
}
/* If the application has "app-menu" defined in menus.ui, we want to
* assign it to the application. If we used the base GtkApplication for
* menus, this would be done for us. But since we are doing menu merging,
* we need to do it manually.
*/
app_menu = dzl_menu_manager_get_menu_by_id (priv->menu_manager, "app-menu");
gtk_application_set_app_menu (GTK_APPLICATION (self), G_MENU_MODEL (app_menu));
/*
* Now apply our deferred resources.
*/
for (guint i = 0; i < priv->deferred_resources->len; i++)
{
const gchar *path = g_ptr_array_index (priv->deferred_resources, i);
DZL_APPLICATION_GET_CLASS (self)->add_resources (self, path);
}
g_clear_pointer (&priv->deferred_resources, g_ptr_array_unref);
/*
* Now force reload the keyboard shortcuts without defering to the main
* loop or anything.
*/
dzl_shortcut_manager_reload (priv->shortcut_manager, NULL);
}
static void
dzl_application_finalize (GObject *object)
{
DzlApplication *self = (DzlApplication *)object;
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_clear_pointer (&priv->deferred_resources, g_ptr_array_unref);
g_clear_pointer (&priv->menu_merge_ids, g_hash_table_unref);
g_clear_object (&priv->theme_manager);
g_clear_object (&priv->menu_manager);
g_clear_object (&priv->shortcut_manager);
G_OBJECT_CLASS (dzl_application_parent_class)->finalize (object);
}
static void
dzl_application_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
DzlApplication *self = DZL_APPLICATION (object);
switch (prop_id)
{
case PROP_MENU_MANAGER:
g_value_set_object (value, dzl_application_get_menu_manager (self));
break;
case PROP_SHORTCUT_MANAGER:
g_value_set_object (value, dzl_application_get_shortcut_manager (self));
break;
case PROP_THEME_MANAGER:
g_value_set_object (value, dzl_application_get_theme_manager (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
dzl_application_class_init (DzlApplicationClass *klass)
{
GApplicationClass *g_app_class = G_APPLICATION_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = dzl_application_finalize;
object_class->get_property = dzl_application_get_property;
g_app_class->startup = dzl_application_startup;
klass->add_resources = dzl_application_real_add_resources;
klass->remove_resources = dzl_application_real_remove_resources;
properties [PROP_MENU_MANAGER] =
g_param_spec_object ("menu-manager", NULL, NULL,
DZL_TYPE_MENU_MANAGER,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_SHORTCUT_MANAGER] =
g_param_spec_object ("shortcut-manager", NULL, NULL,
DZL_TYPE_SHORTCUT_MANAGER,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_THEME_MANAGER] =
g_param_spec_object ("theme-manager", NULL, NULL,
DZL_TYPE_THEME_MANAGER,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
dzl_application_init (DzlApplication *self)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_autoptr(GPropertyAction) shortcut_theme_action = NULL;
g_application_set_default (G_APPLICATION (self));
priv->deferred_resources = g_ptr_array_new ();
priv->theme_manager = dzl_theme_manager_new ();
priv->menu_manager = dzl_menu_manager_new ();
priv->menu_merge_ids = g_hash_table_new (NULL, NULL);
priv->shortcut_manager = g_object_ref (dzl_shortcut_manager_get_default ());
shortcut_theme_action = g_property_action_new ("shortcut-theme", priv->shortcut_manager, "theme-name");
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (shortcut_theme_action));
}
/**
* dzl_application_get_menu_manager:
* @self: a #DzlApplication
*
* Gets the menu manager for the application.
*
* Returns: (transfer none): A #DzlMenuManager
*/
DzlMenuManager *
dzl_application_get_menu_manager (DzlApplication *self)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_val_if_fail (DZL_IS_APPLICATION (self), NULL);
return priv->menu_manager;
}
/**
* dzl_application_get_theme_manager:
* @self: a #DzlApplication
*
* Get the theme manager for the application.
*
* Returns: (transfer none): A #DzlThemeManager
*/
DzlThemeManager *
dzl_application_get_theme_manager (DzlApplication *self)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_val_if_fail (DZL_IS_APPLICATION (self), NULL);
return priv->theme_manager;
}
/**
* dzl_application_get_menu_by_id:
* @self: a #DzlApplication
* @menu_id: the id of the menu to locate
*
* Similar to gtk_application_get_menu_by_id() but takes into account
* menu merging which could have occurred upon loading plugins.
*
* Returns: (transfer none): A #GMenu
*/
GMenu *
dzl_application_get_menu_by_id (DzlApplication *self,
const gchar *menu_id)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_val_if_fail (DZL_IS_APPLICATION (self), NULL);
g_return_val_if_fail (menu_id != NULL, NULL);
return dzl_menu_manager_get_menu_by_id (priv->menu_manager, menu_id);
}
/**
* dzl_application_add_resources:
* @self: a #DzlApplication
* @resource_path: the location of the resources.
*
* This adds @resource_path to the list of "automatic resources".
*
* If @resource_path starts with "resource://", then the corresponding
* #GResources path will be searched for resources. Otherwise, @resource_path
* should be a path to a location on disk.
*
* The #DzlApplication will locate resources such as CSS themes, icons, and
* keyboard shortcuts using @resource_path.
*/
void
dzl_application_add_resources (DzlApplication *self,
const gchar *resource_path)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_if_fail (DZL_IS_APPLICATION (self));
g_return_if_fail (resource_path != NULL);
if (priv->deferred_resources != NULL)
{
g_ptr_array_add (priv->deferred_resources, (gpointer)g_intern_string (resource_path));
return;
}
DZL_APPLICATION_GET_CLASS (self)->add_resources (self, resource_path);
}
/**
* dzl_application_remove_resources:
* @self: a #DzlApplication
* @resource_path: the location of the resources.
*
* This attempts to undo as many side-effects as possible from a call to
* dzl_application_add_resources().
*/
void
dzl_application_remove_resources (DzlApplication *self,
const gchar *resource_path)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_if_fail (DZL_IS_APPLICATION (self));
g_return_if_fail (resource_path != NULL);
if (priv->deferred_resources != NULL)
{
g_ptr_array_remove (priv->deferred_resources, (gpointer)g_intern_string (resource_path));
return;
}
DZL_APPLICATION_GET_CLASS (self)->remove_resources (self, resource_path);
}
/**
* dzl_application_get_shortcut_manager:
* @self: a #DzlApplication
*
* Gets the #DzlShortcutManager for the application.
*
* Returns: (transfer none): A #DzlShortcutManager
*/
DzlShortcutManager *
dzl_application_get_shortcut_manager (DzlApplication *self)
{
DzlApplicationPrivate *priv = dzl_application_get_instance_private (self);
g_return_val_if_fail (DZL_IS_APPLICATION (self), NULL);
return priv->shortcut_manager;
}