/*
* Copyright (C) 2001 Ximian, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Authors:
* Naba Kumar <naba@gnome.org>
*/
#include <config.h>
/**
* SECTION:glade-app
* @Short_Description: The central control point of the Glade core.
*
* This main control object is where we try to draw the line between
* what is the Glade core and what is the main application. The main
* application must derive from the GladeApp object and create an instance
* to initialize the Glade core.
*/
#include "glade.h"
#include "glade-debug.h"
#include "glade-cursor.h"
#include "glade-catalog.h"
#include "glade-design-view.h"
#include "glade-design-layout.h"
#include "glade-marshallers.h"
#include "glade-accumulators.h"
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#ifdef MAC_INTEGRATION
# include <gtkosxapplication.h>
#endif
#define GLADE_CONFIG_FILENAME "glade.conf"
enum
{
DOC_SEARCH,
SIGNAL_EDITOR_CREATED,
WIDGET_ADAPTOR_REGISTERED,
LAST_SIGNAL
};
struct _GladeAppPrivate
{
GtkWidget *window;
GladeClipboard *clipboard; /* See glade-clipboard */
GList *catalogs; /* See glade-catalog */
GList *projects; /* The list of Projects */
GKeyFile *config; /* The configuration file */
GtkAccelGroup *accel_group; /* Default acceleration group for this app */
};
static guint glade_app_signals[LAST_SIGNAL] = { 0 };
/* installation paths */
static gchar *catalogs_dir = NULL;
static gchar *modules_dir = NULL;
static gchar *pixmaps_dir = NULL;
static gchar *locale_dir = NULL;
static gchar *bin_dir = NULL;
static gchar *lib_dir = NULL;
static GladeApp *singleton_app = NULL;
static gboolean check_initialised = FALSE;
G_DEFINE_TYPE_WITH_PRIVATE (GladeApp, glade_app, G_TYPE_OBJECT);
/*****************************************************************
* GObjectClass *
*****************************************************************/
static GObject *
glade_app_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GObject *object;
/* singleton */
if (!singleton_app)
{
object = G_OBJECT_CLASS (glade_app_parent_class)->constructor (type,
n_construct_properties,
construct_properties);
singleton_app = GLADE_APP (object);
}
else
{
g_object_ref (singleton_app);
}
return G_OBJECT (singleton_app);
}
static void
glade_app_dispose (GObject *app)
{
GladeAppPrivate *priv = GLADE_APP (app)->priv;
if (priv->clipboard)
{
g_object_unref (priv->clipboard);
priv->clipboard = NULL;
}
/* FIXME: Remove projects */
if (priv->config)
{
g_key_file_free (priv->config);
priv->config = NULL;
}
G_OBJECT_CLASS (glade_app_parent_class)->dispose (app);
}
static void
glade_app_finalize (GObject *app)
{
g_free (catalogs_dir);
g_free (modules_dir);
g_free (pixmaps_dir);
g_free (locale_dir);
g_free (bin_dir);
g_free (lib_dir);
singleton_app = NULL;
check_initialised = FALSE;
G_OBJECT_CLASS (glade_app_parent_class)->finalize (app);
}
/* build package paths at runtime */
static void
build_package_paths (void)
{
const gchar *path;
path = g_getenv (GLADE_ENV_PIXMAP_DIR);
if (path)
pixmaps_dir = g_strdup (path);
#if defined (G_OS_WIN32) || (defined (MAC_INTEGRATION) && defined (MAC_BUNDLE))
gchar *prefix;
# ifdef G_OS_WIN32
prefix = g_win32_get_package_installation_directory_of_module (NULL);
# else // defined (MAC_INTEGRATION) && defined (MAC_BUNDLE)
prefix = quartz_application_get_resource_path ();
# endif
if (!pixmaps_dir)
pixmaps_dir = g_build_filename (prefix, "share", PACKAGE, "pixmaps", NULL);
catalogs_dir = g_build_filename (prefix, "share", PACKAGE, "catalogs", NULL);
modules_dir = g_build_filename (prefix, "lib", PACKAGE, "modules", NULL);
locale_dir = g_build_filename (prefix, "share", "locale", NULL);
bin_dir = g_build_filename (prefix, "bin", NULL);
lib_dir = g_build_filename (prefix, "lib", NULL);
g_free (prefix);
#else
catalogs_dir = g_strdup (GLADE_CATALOGSDIR);
modules_dir = g_strdup (GLADE_MODULESDIR);
if (!pixmaps_dir)
pixmaps_dir = g_strdup (GLADE_PIXMAPSDIR);
locale_dir = g_strdup (GLADE_LOCALEDIR);
bin_dir = g_strdup (GLADE_BINDIR);
lib_dir = g_strdup (GLADE_LIBDIR);
#endif
}
/* initialization function for libgladeui (not GladeApp) */
static void
glade_init_check (void)
{
if (check_initialised)
return;
glade_init_debug_flags ();
/* Make sure path accessors work on osx */
build_package_paths ();
bindtextdomain (GETTEXT_PACKAGE, locale_dir);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
check_initialised = TRUE;
}
/*****************************************************************
* GladeAppClass *
*****************************************************************/
const gchar *
glade_app_get_catalogs_dir (void)
{
glade_init_check ();
return catalogs_dir;
}
const gchar *
glade_app_get_modules_dir (void)
{
glade_init_check ();
return modules_dir;
}
const gchar *
glade_app_get_pixmaps_dir (void)
{
glade_init_check ();
return pixmaps_dir;
}
const gchar *
glade_app_get_locale_dir (void)
{
glade_init_check ();
return locale_dir;
}
const gchar *
glade_app_get_bin_dir (void)
{
glade_init_check ();
return bin_dir;
}
const gchar *
glade_app_get_lib_dir (void)
{
glade_init_check ();
return lib_dir;
}
static void
pointer_mode_register_icon (const gchar *icon_name,
gint real_size,
GladePointerMode mode,
GtkIconSize size)
{
GdkPixbuf *pixbuf;
if ((pixbuf = glade_utils_pointer_mode_render_icon (mode, size)))
{
gtk_icon_theme_add_builtin_icon (icon_name, real_size, pixbuf);
g_object_unref (pixbuf);
}
}
static void
register_icon (const gchar *new_icon_name,
gint size,
const gchar *icon_name,
const gchar *file_name)
{
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
GdkPixbuf *pixbuf;
GtkIconInfo *info;
if ((info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0)))
{
pixbuf = gtk_icon_info_load_icon (info, NULL);
}
else
{
gchar *path = g_build_filename (glade_app_get_pixmaps_dir (), file_name, NULL);
pixbuf = gdk_pixbuf_new_from_file (path, NULL);
g_free (path);
}
if (pixbuf)
{
gtk_icon_theme_add_builtin_icon (new_icon_name, size, pixbuf);
g_object_unref (pixbuf);
}
}
/*
* glade_app_register_icon_names:
* @size: icon size
*
* Register a new icon name for most of GladePointerMode.
* After calling this function "glade-selector", "glade-drag-resize",
* "glade-margin-edit" and "glade-align-edit" icon names will be available.
*/
static void
glade_app_register_icon_names (GtkIconSize size)
{
gint w, h, real_size;
if (gtk_icon_size_lookup (size, &w, &h) == FALSE)
return;
real_size = MIN (w, h);
pointer_mode_register_icon ("glade-selector", real_size, GLADE_POINTER_SELECT, size);
pointer_mode_register_icon ("glade-drag-resize", real_size, GLADE_POINTER_DRAG_RESIZE, size);
pointer_mode_register_icon ("glade-margin-edit", real_size, GLADE_POINTER_MARGIN_EDIT, size);
pointer_mode_register_icon ("glade-align-edit", real_size, GLADE_POINTER_ALIGN_EDIT, size);
register_icon ("glade-devhelp", real_size,
GLADE_DEVHELP_ICON_NAME,
GLADE_DEVHELP_FALLBACK_ICON_FILE);
}
/**
* glade_init:
*
* Initialization function for libgladeui (not #GladeApp)
* It builds paths, bind text domain, and register icons
*/
void
glade_init (void)
{
static gboolean init = FALSE;
if (init) return;
glade_init_check ();
/* Register icons needed by the UI */
glade_app_register_icon_names (GTK_ICON_SIZE_LARGE_TOOLBAR);
init = TRUE;
}
static void
glade_app_init (GladeApp *app)
{
static gboolean initialized = FALSE;
GladeAppPrivate *priv = app->priv = glade_app_get_instance_private (app);
singleton_app = app;
glade_init ();
if (!initialized)
{
GtkIconTheme *default_icon_theme = gtk_icon_theme_get_default ();
const gchar *path;
gtk_icon_theme_append_search_path (default_icon_theme, pixmaps_dir);
/* Handle extra icon theme paths. Needed for tests to work */
if ((path = g_getenv (GLADE_ENV_ICON_THEME_PATH)))
{
gchar **tokens = g_strsplit (path, ":", -1);
gint i;
for (i = 0; tokens[i]; i++)
gtk_icon_theme_append_search_path (default_icon_theme, tokens[i]);
g_strfreev (tokens);
}
glade_cursor_init ();
initialized = TRUE;
}
priv->accel_group = NULL;
/* Initialize app objects */
priv->catalogs = (GList *) glade_catalog_load_all ();
/* Create clipboard */
priv->clipboard = glade_clipboard_new ();
/* Load the configuration file */
priv->config = g_key_file_ref (glade_app_get_config ());
}
static void
glade_app_event_handler (GdkEvent *event, gpointer data)
{
if (glade_app_do_event (event)) return;
gtk_main_do_event (event);
}
static void
glade_app_class_init (GladeAppClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->constructor = glade_app_constructor;
object_class->dispose = glade_app_dispose;
object_class->finalize = glade_app_finalize;
/**
* GladeApp::doc-search:
* @gladeeditor: the #GladeEditor which received the signal.
* @arg1: the (#gchar *) book to search or %NULL
* @arg2: the (#gchar *) page to search or %NULL
* @arg3: the (#gchar *) search string or %NULL
*
* Emitted when the glade core requests that a doc-search be performed.
*/
glade_app_signals[DOC_SEARCH] =
g_signal_new ("doc-search",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_glade_marshal_VOID__STRING_STRING_STRING,
G_TYPE_NONE, 3,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
/**
* GladeApp::signal-editor-created:
* @gladeapp: the #GladeApp which received the signal.
* @signal_editor: the new #GladeSignalEditor.
*
* Emitted when a new signal editor created.
* A tree view is created in the default handler.
* Connect your handler before the default handler for setting a custom column or renderer
* and after it for connecting to the tree view signals
*/
glade_app_signals[SIGNAL_EDITOR_CREATED] =
g_signal_new ("signal-editor-created",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
_glade_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
/**
* GladeApp::widget-adaptor-registered:
* @gladeapp: the #GladeApp which received the signal.
* @adaptor: the newlly registered #GladeWidgetAdaptor.
*
* Emitted when a new widget adaptor is registered.
*/
glade_app_signals[WIDGET_ADAPTOR_REGISTERED] =
g_signal_new ("widget-adaptor-registered",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
_glade_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
gdk_event_handler_set (glade_app_event_handler, NULL, NULL);
}
/*****************************************************************
* Public API *
*****************************************************************/
/**
* glade_app_do_event:
* @event: the event to process.
*
* This function has to be called in an event handler for widget selection to work.
* See gdk_event_handler_set()
*
* Returns: true if the event was handled.
*/
gboolean
glade_app_do_event (GdkEvent *event)
{
GdkWindow *window = event->any.window;
GtkWidget *layout;
gpointer widget;
if (window == NULL) return FALSE;
gdk_window_get_user_data (window, &widget);
/* As a slight optimization we could replace gtk_widget_get_ancestor()
* with a custom function that only iterates trought parents with windows.
*/
if (widget && IS_GLADE_WIDGET_EVENT (event->type) &&
(layout = gtk_widget_get_ancestor (widget, GLADE_TYPE_DESIGN_LAYOUT)))
return _glade_design_layout_do_event (GLADE_DESIGN_LAYOUT (layout), event);
return FALSE;
}
/**
* glade_app_config_save
*
* Saves the GKeyFile to "g_get_user_config_dir()/GLADE_CONFIG_FILENAME"
*
* Return 0 on success.
*/
gint
glade_app_config_save ()
{
GIOChannel *channel;
GIOStatus status;
gchar *data = NULL, *filename;
const gchar *config_dir = g_get_user_config_dir ();
GError *error = NULL;
gsize size, written, bytes_written = 0;
static gboolean error_shown = FALSE;
GladeApp *app;
/* If we had any errors; wait untill next session to retry.
*/
if (error_shown)
return -1;
app = glade_app_get ();
/* Just in case... try to create the config directory */
if (g_file_test (config_dir, G_FILE_TEST_IS_DIR) == FALSE)
{
if (g_file_test (config_dir, G_FILE_TEST_EXISTS))
{
/* Config dir exists but is not a directory */
glade_util_ui_message
(glade_app_get_window (),
GLADE_UI_ERROR, NULL,
_("Trying to save private data to %s directory "
"but it is a regular file.\n"
"No private data will be saved in this session"), config_dir);
error_shown = TRUE;
return -1;
}
else if (g_mkdir (config_dir, S_IRWXU) != 0)
{
/* Doesnt exist; failed to create */
glade_util_ui_message
(glade_app_get_window (),
GLADE_UI_ERROR, NULL,
_("Failed to create directory %s to save private data.\n"
"No private data will be saved in this session"), config_dir);
error_shown = TRUE;
return -1;
}
}
filename = g_build_filename (config_dir, GLADE_CONFIG_FILENAME, NULL);
if ((channel = g_io_channel_new_file (filename, "w", &error)) != NULL)
{
if ((data =
g_key_file_to_data (app->priv->config, &size, &error)) != NULL)
{
/* Implement loop here */
while ((status = g_io_channel_write_chars (channel, data + bytes_written, /* Offset of write */
size - bytes_written, /* Size left to write */
&written,
&error)) !=
G_IO_STATUS_ERROR && (bytes_written + written) < size)
bytes_written += written;
if (status == G_IO_STATUS_ERROR)
{
glade_util_ui_message
(glade_app_get_window (),
GLADE_UI_ERROR, NULL,
_("Error writing private data to %s (%s).\n"
"No private data will be saved in this session"),
filename, error->message);
error_shown = TRUE;
}
g_free (data);
}
else
{
glade_util_ui_message
(glade_app_get_window (),
GLADE_UI_ERROR, NULL,
_("Error serializing configuration data to save (%s).\n"
"No private data will be saved in this session"),
error->message);
error_shown = TRUE;
}
g_io_channel_shutdown (channel, TRUE, NULL);
g_io_channel_unref (channel);
}
else
{
glade_util_ui_message
(glade_app_get_window (),
GLADE_UI_ERROR, NULL,
_("Error opening %s to write private data (%s).\n"
"No private data will be saved in this session"),
filename, error->message);
error_shown = TRUE;
}
g_free (filename);
if (error)
{
g_error_free (error);
return -1;
}
return 0;
}
GladeApp *
glade_app_get (void)
{
if (!singleton_app)
{
singleton_app = glade_app_new ();
}
return singleton_app;
}
void
glade_app_set_window (GtkWidget *window)
{
GladeApp *app = glade_app_get ();
app->priv->window = window;
}
GladeCatalog *
glade_app_get_catalog (const gchar *name)
{
GladeApp *app = glade_app_get ();
GList *list;
GladeCatalog *catalog;
g_return_val_if_fail (name && name[0], NULL);
for (list = app->priv->catalogs; list; list = list->next)
{
catalog = list->data;
if (!strcmp (glade_catalog_get_name (catalog), name))
return catalog;
}
return NULL;
}
gboolean
glade_app_get_catalog_version (const gchar *name, gint *major, gint *minor)
{
GladeCatalog *catalog = glade_app_get_catalog (name);
g_return_val_if_fail (catalog != NULL, FALSE);
if (major)
*major = glade_catalog_get_major_version (catalog);
if (minor)
*minor = glade_catalog_get_minor_version (catalog);
return TRUE;
}
GList *
glade_app_get_catalogs (void)
{
GladeApp *app = glade_app_get ();
return app->priv->catalogs;
}
GtkWidget *
glade_app_get_window (void)
{
GladeApp *app = glade_app_get ();
return app->priv->window;
}
GladeClipboard *
glade_app_get_clipboard (void)
{
GladeApp *app = glade_app_get ();
return app->priv->clipboard;
}
/**
* glade_app_get_catalogs:
*
* Return value: (element-type GladeCatalog): catalogs
*/
GList *
glade_app_get_projects (void)
{
GladeApp *app = glade_app_get ();
return app->priv->projects;
}
GKeyFile *
glade_app_get_config (void)
{
static GKeyFile *config = NULL;
if (config == NULL)
{
gchar *filename = g_build_filename (g_get_user_config_dir (),
GLADE_CONFIG_FILENAME, NULL);
config = g_key_file_new ();
g_key_file_load_from_file (config, filename, G_KEY_FILE_NONE, NULL);
g_free (filename);
}
return config;
}
gboolean
glade_app_is_project_loaded (const gchar *project_path)
{
GladeApp *app;
GList *list;
gboolean loaded = FALSE;
if (project_path == NULL)
return FALSE;
app = glade_app_get ();
for (list = app->priv->projects; list; list = list->next)
{
GladeProject *cur_project = GLADE_PROJECT (list->data);
if ((loaded = glade_project_get_path (cur_project) &&
(strcmp (glade_project_get_path (cur_project), project_path) == 0)))
break;
}
return loaded;
}
/**
* glade_app_get_project_by_path:
* @project_path: The path of an open project
*
* Finds an open project with @path
*
* Returns: A #GladeProject, or NULL if no such open project was found
*/
GladeProject *
glade_app_get_project_by_path (const gchar *project_path)
{
GladeApp *app;
GList *l;
gchar *canonical_path;
if (project_path == NULL)
return NULL;
app = glade_app_get ();
canonical_path = glade_util_canonical_path (project_path);
for (l = app->priv->projects; l; l = l->next)
{
GladeProject *project = (GladeProject *) l->data;
if (glade_project_get_path (project) &&
strcmp (canonical_path, glade_project_get_path (project)) == 0)
{
g_free (canonical_path);
return project;
}
}
g_free (canonical_path);
return NULL;
}
void
glade_app_add_project (GladeProject *project)
{
GladeApp *app;
g_return_if_fail (GLADE_IS_PROJECT (project));
app = glade_app_get ();
/* If the project was previously loaded, don't re-load */
if (g_list_find (app->priv->projects, project) != NULL)
return;
/* Take a reference for GladeApp here... */
app->priv->projects = g_list_append (app->priv->projects, g_object_ref (project));
}
void
glade_app_remove_project (GladeProject *project)
{
GladeApp *app;
g_return_if_fail (GLADE_IS_PROJECT (project));
app = glade_app_get ();
app->priv->projects = g_list_remove (app->priv->projects, project);
/* Its safe to just release the project as the project emits a
* "close" signal and everyone is responsable for cleaning up at
* that point.
*/
g_object_unref (project);
}
/*
* glade_app_set_accel_group:
*
* Sets @accel_group to app.
* The acceleration group will made available for editor dialog windows
* from the plugin backend.
*/
void
glade_app_set_accel_group (GtkAccelGroup *accel_group)
{
GladeApp *app;
g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
app = glade_app_get ();
app->priv->accel_group = accel_group;
}
GtkAccelGroup *
glade_app_get_accel_group (void)
{
return glade_app_get ()->priv->accel_group;
}
GladeApp *
glade_app_new (void)
{
return g_object_new (GLADE_TYPE_APP, NULL);
}
void
glade_app_search_docs (const gchar *book,
const gchar *page,
const gchar *search)
{
GladeApp *app;
app = glade_app_get ();
g_signal_emit (G_OBJECT (app), glade_app_signals[DOC_SEARCH], 0,
book, page, search);
}