1 /*
2  * Copyright (C) 2001 Ximian, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Authors:
19  *   Naba Kumar <naba@gnome.org>
20  */
21 
22 #include <config.h>
23 
24 /**
25  * SECTION:glade-app
26  * @Short_Description: The central control point of the Glade core.
27  *
28  * This main control object is where we try to draw the line between
29  * what is the Glade core and what is the main application. The main
30  * application must derive from the GladeApp object and create an instance
31  * to initialize the Glade core.
32  */
33 
34 #include "glade.h"
35 #include "glade-debug.h"
36 #include "glade-cursor.h"
37 #include "glade-catalog.h"
38 #include "glade-design-view.h"
39 #include "glade-design-layout.h"
40 #include "glade-marshallers.h"
41 #include "glade-accumulators.h"
42 
43 #include <string.h>
44 #include <glib.h>
45 #include <glib/gstdio.h>
46 #include <glib/gi18n-lib.h>
47 #include <gdk/gdkkeysyms.h>
48 #include <gtk/gtk.h>
49 
50 #ifdef MAC_INTEGRATION
51 #  include <gtkosxapplication.h>
52 #endif
53 
54 #define GLADE_CONFIG_FILENAME "glade.conf"
55 
56 enum
57 {
58   DOC_SEARCH,
59   SIGNAL_EDITOR_CREATED,
60   WIDGET_ADAPTOR_REGISTERED,
61   LAST_SIGNAL
62 };
63 
64 struct _GladeAppPrivate
65 {
66   GtkWidget *window;
67 
68   GladeClipboard *clipboard;    /* See glade-clipboard */
69   GList *catalogs;              /* See glade-catalog */
70 
71   GList *projects;              /* The list of Projects */
72 
73   GKeyFile *config;             /* The configuration file */
74 
75   GtkAccelGroup *accel_group;   /* Default acceleration group for this app */
76 };
77 
78 static guint glade_app_signals[LAST_SIGNAL] = { 0 };
79 
80 /* installation paths */
81 static gchar *catalogs_dir = NULL;
82 static gchar *modules_dir = NULL;
83 static gchar *pixmaps_dir = NULL;
84 static gchar *locale_dir = NULL;
85 static gchar *bin_dir = NULL;
86 static gchar *lib_dir = NULL;
87 
88 static GladeApp *singleton_app = NULL;
89 static gboolean check_initialised = FALSE;
90 
91 G_DEFINE_TYPE_WITH_PRIVATE (GladeApp, glade_app, G_TYPE_OBJECT);
92 
93 /*****************************************************************
94  *                    GObjectClass                               *
95  *****************************************************************/
96 static GObject *
97 glade_app_constructor (GType                  type,
98                        guint                  n_construct_properties,
99                        GObjectConstructParam *construct_properties)
100 {
101   GObject *object;
102 
103   /* singleton */
104   if (!singleton_app)
105     {
106       object = G_OBJECT_CLASS (glade_app_parent_class)->constructor (type,
107                                                                      n_construct_properties,
108                                                                      construct_properties);
109       singleton_app = GLADE_APP (object);
110     }
111   else
112     {
113       g_object_ref (singleton_app);
114     }
115 
116   return G_OBJECT (singleton_app);
117 }
118 
119 
120 
121 static void
122 glade_app_dispose (GObject *app)
123 {
124   GladeAppPrivate *priv = GLADE_APP (app)->priv;
125 
126   if (priv->clipboard)
127     {
128       g_object_unref (priv->clipboard);
129       priv->clipboard = NULL;
130     }
131   /* FIXME: Remove projects */
132 
133   if (priv->config)
134     {
135       g_key_file_free (priv->config);
136       priv->config = NULL;
137     }
138 
139   G_OBJECT_CLASS (glade_app_parent_class)->dispose (app);
140 }
141 
142 static void
143 glade_app_finalize (GObject *app)
144 {
145   g_free (catalogs_dir);
146   g_free (modules_dir);
147   g_free (pixmaps_dir);
148   g_free (locale_dir);
149   g_free (bin_dir);
150   g_free (lib_dir);
151 
152   singleton_app = NULL;
153   check_initialised = FALSE;
154 
155   G_OBJECT_CLASS (glade_app_parent_class)->finalize (app);
156 }
157 
158 /* build package paths at runtime */
159 static void
160 build_package_paths (void)
161 {
162   const gchar *path;
163 
164   path = g_getenv (GLADE_ENV_PIXMAP_DIR);
165   if (path)
166     pixmaps_dir = g_strdup (path);
167 
168 #if defined (G_OS_WIN32) || (defined (MAC_INTEGRATION) && defined (MAC_BUNDLE))
169   gchar *prefix;
170 
171 # ifdef G_OS_WIN32
172   prefix = g_win32_get_package_installation_directory_of_module (NULL);
173 
174 # else // defined (MAC_INTEGRATION) && defined (MAC_BUNDLE)
175   prefix = quartz_application_get_resource_path ();
176 
177 # endif
178 
179   if (!pixmaps_dir)
180     pixmaps_dir = g_build_filename (prefix, "share", PACKAGE, "pixmaps", NULL);
181 
182   catalogs_dir = g_build_filename (prefix, "share", PACKAGE, "catalogs", NULL);
183   modules_dir = g_build_filename (prefix, "lib", PACKAGE, "modules", NULL);
184   locale_dir = g_build_filename (prefix, "share", "locale", NULL);
185   bin_dir = g_build_filename (prefix, "bin", NULL);
186   lib_dir = g_build_filename (prefix, "lib", NULL);
187 
188   g_free (prefix);
189 #else
190   catalogs_dir = g_strdup (GLADE_CATALOGSDIR);
191   modules_dir = g_strdup (GLADE_MODULESDIR);
192 
193   if (!pixmaps_dir)
194     pixmaps_dir = g_strdup (GLADE_PIXMAPSDIR);
195   locale_dir = g_strdup (GLADE_LOCALEDIR);
196   bin_dir = g_strdup (GLADE_BINDIR);
197   lib_dir = g_strdup (GLADE_LIBDIR);
198 #endif
199 }
200 
201 /* initialization function for libgladeui (not GladeApp) */
202 static void
203 glade_init_check (void)
204 {
205   if (check_initialised)
206     return;
207 
208   glade_init_debug_flags ();
209 
210   /* Make sure path accessors work on osx */
211   build_package_paths ();
212 
213   bindtextdomain (GETTEXT_PACKAGE, locale_dir);
214   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
215 
216   check_initialised = TRUE;
217 }
218 
219 /*****************************************************************
220  *                    GladeAppClass                              *
221  *****************************************************************/
222 const gchar *
223 glade_app_get_catalogs_dir (void)
224 {
225   glade_init_check ();
226 
227   return catalogs_dir;
228 }
229 
230 const gchar *
231 glade_app_get_modules_dir (void)
232 {
233   glade_init_check ();
234 
235   return modules_dir;
236 }
237 
238 const gchar *
239 glade_app_get_pixmaps_dir (void)
240 {
241   glade_init_check ();
242 
243   return pixmaps_dir;
244 }
245 
246 const gchar *
247 glade_app_get_locale_dir (void)
248 {
249   glade_init_check ();
250 
251   return locale_dir;
252 }
253 
254 const gchar *
255 glade_app_get_bin_dir (void)
256 {
257   glade_init_check ();
258 
259   return bin_dir;
260 }
261 
262 const gchar *
263 glade_app_get_lib_dir (void)
264 {
265   glade_init_check ();
266 
267   return lib_dir;
268 }
269 
270 static void
271 pointer_mode_register_icon (const gchar     *icon_name,
272                             gint             real_size,
273                             GladePointerMode mode,
274                             GtkIconSize      size)
275 {
276   GdkPixbuf *pixbuf;
277 
278   if ((pixbuf = glade_utils_pointer_mode_render_icon (mode, size)))
279     {
280       gtk_icon_theme_add_builtin_icon (icon_name, real_size, pixbuf);
281       g_object_unref (pixbuf);
282     }
283 }
284 
285 static void
286 register_icon (const gchar    *new_icon_name,
287                gint            size,
288                const gchar    *icon_name,
289                const gchar    *file_name)
290 {
291   GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
292   GdkPixbuf *pixbuf;
293   GtkIconInfo *info;
294 
295   if ((info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0)))
296     {
297       pixbuf = gtk_icon_info_load_icon (info, NULL);
298     }
299   else
300     {
301       gchar *path = g_build_filename (glade_app_get_pixmaps_dir (), file_name, NULL);
302       pixbuf = gdk_pixbuf_new_from_file (path, NULL);
303       g_free (path);
304     }
305 
306   if (pixbuf)
307     {
308       gtk_icon_theme_add_builtin_icon (new_icon_name, size, pixbuf);
309       g_object_unref (pixbuf);
310     }
311 }
312 
313 /*
314  * glade_app_register_icon_names:
315  * @size: icon size
316  *
317  * Register a new icon name for most of GladePointerMode.
318  * After calling this function "glade-selector", "glade-drag-resize",
319  * "glade-margin-edit" and "glade-align-edit" icon names will be available.
320  */
321 static void
322 glade_app_register_icon_names (GtkIconSize size)
323 {
324   gint w, h, real_size;
325 
326   if (gtk_icon_size_lookup (size, &w, &h) == FALSE)
327     return;
328 
329   real_size = MIN (w, h);
330 
331   pointer_mode_register_icon ("glade-selector", real_size, GLADE_POINTER_SELECT, size);
332   pointer_mode_register_icon ("glade-drag-resize", real_size, GLADE_POINTER_DRAG_RESIZE, size);
333   pointer_mode_register_icon ("glade-margin-edit", real_size, GLADE_POINTER_MARGIN_EDIT, size);
334   pointer_mode_register_icon ("glade-align-edit", real_size, GLADE_POINTER_ALIGN_EDIT, size);
335 
336   register_icon ("glade-devhelp", real_size,
337                  GLADE_DEVHELP_ICON_NAME,
338                  GLADE_DEVHELP_FALLBACK_ICON_FILE);
339 }
340 
341 /**
342  * glade_init:
343  *
344  * Initialization function for libgladeui (not #GladeApp)
345  * It builds paths, bind text domain, and register icons
346  */
347 void
348 glade_init (void)
349 {
350   static gboolean init = FALSE;
351 
352   if (init) return;
353 
354   glade_init_check ();
355 
356   /* Register icons needed by the UI */
357   glade_app_register_icon_names (GTK_ICON_SIZE_LARGE_TOOLBAR);
358 
359   init = TRUE;
360 }
361 
362 static void
363 glade_app_init (GladeApp *app)
364 {
365   static gboolean initialized = FALSE;
366   GladeAppPrivate *priv = app->priv = glade_app_get_instance_private (app);
367 
368   singleton_app = app;
369 
370   glade_init ();
371 
372   if (!initialized)
373     {
374       GtkIconTheme *default_icon_theme = gtk_icon_theme_get_default ();
375       const gchar *path;
376 
377       gtk_icon_theme_append_search_path (default_icon_theme, pixmaps_dir);
378 
379       /* Handle extra icon theme paths. Needed for tests to work */
380       if ((path = g_getenv (GLADE_ENV_ICON_THEME_PATH)))
381         {
382           gchar **tokens = g_strsplit (path, ":", -1);
383           gint i;
384 
385           for (i = 0; tokens[i]; i++)
386             gtk_icon_theme_append_search_path (default_icon_theme, tokens[i]);
387 
388           g_strfreev (tokens);
389         }
390 
391       glade_cursor_init ();
392 
393       initialized = TRUE;
394     }
395 
396   priv->accel_group = NULL;
397 
398   /* Initialize app objects */
399   priv->catalogs = (GList *) glade_catalog_load_all ();
400 
401   /* Create clipboard */
402   priv->clipboard = glade_clipboard_new ();
403 
404   /* Load the configuration file */
405   priv->config = g_key_file_ref (glade_app_get_config ());
406 }
407 
408 static void
409 glade_app_event_handler (GdkEvent *event, gpointer data)
410 {
411   if (glade_app_do_event (event)) return;
412 
413   gtk_main_do_event (event);
414 }
415 
416 static void
417 glade_app_class_init (GladeAppClass *klass)
418 {
419   GObjectClass *object_class;
420 
421   object_class = G_OBJECT_CLASS (klass);
422 
423   object_class->constructor = glade_app_constructor;
424   object_class->dispose = glade_app_dispose;
425   object_class->finalize = glade_app_finalize;
426 
427   /**
428    * GladeApp::doc-search:
429    * @gladeeditor: the #GladeEditor which received the signal.
430    * @arg1: the (#gchar *) book to search or %NULL
431    * @arg2: the (#gchar *) page to search or %NULL
432    * @arg3: the (#gchar *) search string or %NULL
433    *
434    * Emitted when the glade core requests that a doc-search be performed.
435    */
436   glade_app_signals[DOC_SEARCH] =
437       g_signal_new ("doc-search",
438                     G_TYPE_FROM_CLASS (object_class),
439                     G_SIGNAL_RUN_LAST, 0, NULL, NULL,
440                     _glade_marshal_VOID__STRING_STRING_STRING,
441                     G_TYPE_NONE, 3,
442                     G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
443 
444   /**
445    * GladeApp::signal-editor-created:
446    * @gladeapp: the #GladeApp which received the signal.
447    * @signal_editor: the new #GladeSignalEditor.
448    *
449    * Emitted when a new signal editor created.
450    * A tree view is created in the default handler.
451    * Connect your handler before the default handler for setting a custom column or renderer
452    * and after it for connecting to the tree view signals
453    */
454   glade_app_signals[SIGNAL_EDITOR_CREATED] =
455     g_signal_new ("signal-editor-created",
456                   G_TYPE_FROM_CLASS (object_class),
457                   G_SIGNAL_RUN_LAST,
458                   0, NULL, NULL,
459                   _glade_marshal_VOID__OBJECT,
460                   G_TYPE_NONE, 1, G_TYPE_OBJECT);
461 
462   /**
463    * GladeApp::widget-adaptor-registered:
464    * @gladeapp: the #GladeApp which received the signal.
465    * @adaptor: the newlly registered #GladeWidgetAdaptor.
466    *
467    * Emitted when a new widget adaptor is registered.
468    */
469   glade_app_signals[WIDGET_ADAPTOR_REGISTERED] =
470     g_signal_new ("widget-adaptor-registered",
471                   G_TYPE_FROM_CLASS (object_class),
472                   G_SIGNAL_RUN_LAST,
473                   0, NULL, NULL,
474                   _glade_marshal_VOID__OBJECT,
475                   G_TYPE_NONE, 1, G_TYPE_OBJECT);
476 
477   gdk_event_handler_set (glade_app_event_handler, NULL, NULL);
478 }
479 
480 /*****************************************************************
481  *                       Public API                              *
482  *****************************************************************/
483 
484 /**
485  * glade_app_do_event:
486  * @event: the event to process.
487  *
488  * This function has to be called in an event handler for widget selection to work.
489  * See gdk_event_handler_set()
490  *
491  * Returns: true if the event was handled.
492  */
493 gboolean
494 glade_app_do_event (GdkEvent *event)
495 {
496   GdkWindow *window = event->any.window;
497   GtkWidget *layout;
498   gpointer widget;
499 
500   if (window == NULL) return FALSE;
501 
502   gdk_window_get_user_data (window, &widget);
503 
504   /* As a slight optimization we could replace gtk_widget_get_ancestor()
505    * with a custom function that only iterates trought parents with windows.
506    */
507   if (widget && IS_GLADE_WIDGET_EVENT (event->type) &&
508       (layout = gtk_widget_get_ancestor (widget, GLADE_TYPE_DESIGN_LAYOUT)))
509     return _glade_design_layout_do_event (GLADE_DESIGN_LAYOUT (layout), event);
510 
511   return FALSE;
512 }
513 
514 /**
515  * glade_app_config_save
516  *
517  * Saves the GKeyFile to "g_get_user_config_dir()/GLADE_CONFIG_FILENAME"
518  *
519  * Return 0 on success.
520  */
521 gint
522 glade_app_config_save ()
523 {
524   GIOChannel *channel;
525   GIOStatus status;
526   gchar *data = NULL, *filename;
527   const gchar *config_dir = g_get_user_config_dir ();
528   GError *error = NULL;
529   gsize size, written, bytes_written = 0;
530   static gboolean error_shown = FALSE;
531   GladeApp *app;
532 
533   /* If we had any errors; wait untill next session to retry.
534    */
535   if (error_shown)
536     return -1;
537 
538   app = glade_app_get ();
539 
540   /* Just in case... try to create the config directory */
541   if (g_file_test (config_dir, G_FILE_TEST_IS_DIR) == FALSE)
542     {
543       if (g_file_test (config_dir, G_FILE_TEST_EXISTS))
544         {
545           /* Config dir exists but is not a directory */
546           glade_util_ui_message
547               (glade_app_get_window (),
548                GLADE_UI_ERROR, NULL,
549                _("Trying to save private data to %s directory "
550                  "but it is a regular file.\n"
551                  "No private data will be saved in this session"), config_dir);
552           error_shown = TRUE;
553           return -1;
554         }
555       else if (g_mkdir (config_dir, S_IRWXU) != 0)
556         {
557           /* Doesnt exist; failed to create */
558           glade_util_ui_message
559               (glade_app_get_window (),
560                GLADE_UI_ERROR, NULL,
561                _("Failed to create directory %s to save private data.\n"
562                  "No private data will be saved in this session"), config_dir);
563           error_shown = TRUE;
564           return -1;
565         }
566     }
567 
568   filename = g_build_filename (config_dir, GLADE_CONFIG_FILENAME, NULL);
569 
570   if ((channel = g_io_channel_new_file (filename, "w", &error)) != NULL)
571     {
572       if ((data =
573            g_key_file_to_data (app->priv->config, &size, &error)) != NULL)
574         {
575 
576           /* Implement loop here */
577           while ((status = g_io_channel_write_chars (channel, data + bytes_written,     /* Offset of write */
578                                                      size - bytes_written,      /* Size left to write */
579                                                      &written,
580                                                      &error)) !=
581                  G_IO_STATUS_ERROR && (bytes_written + written) < size)
582             bytes_written += written;
583 
584           if (status == G_IO_STATUS_ERROR)
585             {
586               glade_util_ui_message
587                   (glade_app_get_window (),
588                    GLADE_UI_ERROR, NULL,
589                    _("Error writing private data to %s (%s).\n"
590                      "No private data will be saved in this session"),
591                    filename, error->message);
592               error_shown = TRUE;
593             }
594           g_free (data);
595         }
596       else
597         {
598           glade_util_ui_message
599               (glade_app_get_window (),
600                GLADE_UI_ERROR, NULL,
601                _("Error serializing configuration data to save (%s).\n"
602                  "No private data will be saved in this session"),
603                error->message);
604           error_shown = TRUE;
605         }
606       g_io_channel_shutdown (channel, TRUE, NULL);
607       g_io_channel_unref (channel);
608     }
609   else
610     {
611       glade_util_ui_message
612           (glade_app_get_window (),
613            GLADE_UI_ERROR, NULL,
614            _("Error opening %s to write private data (%s).\n"
615              "No private data will be saved in this session"),
616            filename, error->message);
617       error_shown = TRUE;
618     }
619   g_free (filename);
620 
621   if (error)
622     {
623       g_error_free (error);
624       return -1;
625     }
626   return 0;
627 }
628 
629 GladeApp *
630 glade_app_get (void)
631 {
632   if (!singleton_app)
633     {
634       singleton_app = glade_app_new ();
635     }
636 
637   return singleton_app;
638 }
639 
640 void
641 glade_app_set_window (GtkWidget *window)
642 {
643   GladeApp *app = glade_app_get ();
644 
645   app->priv->window = window;
646 }
647 
648 GladeCatalog *
649 glade_app_get_catalog (const gchar *name)
650 {
651   GladeApp *app = glade_app_get ();
652   GList *list;
653   GladeCatalog *catalog;
654 
655   g_return_val_if_fail (name && name[0], NULL);
656 
657   for (list = app->priv->catalogs; list; list = list->next)
658     {
659       catalog = list->data;
660       if (!strcmp (glade_catalog_get_name (catalog), name))
661         return catalog;
662     }
663   return NULL;
664 }
665 
666 gboolean
667 glade_app_get_catalog_version (const gchar *name, gint *major, gint *minor)
668 {
669   GladeCatalog *catalog = glade_app_get_catalog (name);
670 
671   g_return_val_if_fail (catalog != NULL, FALSE);
672 
673   if (major)
674     *major = glade_catalog_get_major_version (catalog);
675   if (minor)
676     *minor = glade_catalog_get_minor_version (catalog);
677 
678   return TRUE;
679 }
680 
681 GList *
682 glade_app_get_catalogs (void)
683 {
684   GladeApp *app = glade_app_get ();
685 
686   return app->priv->catalogs;
687 }
688 
689 
690 GtkWidget *
691 glade_app_get_window (void)
692 {
693   GladeApp *app = glade_app_get ();
694   return app->priv->window;
695 }
696 
697 GladeClipboard *
698 glade_app_get_clipboard (void)
699 {
700   GladeApp *app = glade_app_get ();
701   return app->priv->clipboard;
702 }
703 /**
704  * glade_app_get_catalogs:
705  *
706  * Return value: (element-type GladeCatalog): catalogs
707  */
708 GList *
709 glade_app_get_projects (void)
710 {
711   GladeApp *app = glade_app_get ();
712   return app->priv->projects;
713 }
714 
715 GKeyFile *
716 glade_app_get_config (void)
717 {
718   static GKeyFile *config = NULL;
719 
720   if (config == NULL)
721     {
722       gchar *filename = g_build_filename (g_get_user_config_dir (),
723                                           GLADE_CONFIG_FILENAME, NULL);
724       config = g_key_file_new ();
725       g_key_file_load_from_file (config, filename, G_KEY_FILE_NONE, NULL);
726       g_free (filename);
727     }
728 
729   return config;
730 }
731 
732 gboolean
733 glade_app_is_project_loaded (const gchar *project_path)
734 {
735   GladeApp *app;
736   GList *list;
737   gboolean loaded = FALSE;
738 
739   if (project_path == NULL)
740     return FALSE;
741 
742   app = glade_app_get ();
743 
744   for (list = app->priv->projects; list; list = list->next)
745     {
746       GladeProject *cur_project = GLADE_PROJECT (list->data);
747 
748       if ((loaded = glade_project_get_path (cur_project) &&
749            (strcmp (glade_project_get_path (cur_project), project_path) == 0)))
750         break;
751     }
752 
753   return loaded;
754 }
755 
756 /**
757  * glade_app_get_project_by_path:
758  * @project_path: The path of an open project
759  *
760  * Finds an open project with @path
761  *
762  * Returns: A #GladeProject, or NULL if no such open project was found
763  */
764 GladeProject *
765 glade_app_get_project_by_path (const gchar *project_path)
766 {
767   GladeApp *app;
768   GList *l;
769   gchar *canonical_path;
770 
771   if (project_path == NULL)
772     return NULL;
773 
774   app = glade_app_get ();
775 
776   canonical_path = glade_util_canonical_path (project_path);
777 
778   for (l = app->priv->projects; l; l = l->next)
779     {
780       GladeProject *project = (GladeProject *) l->data;
781 
782       if (glade_project_get_path (project) &&
783           strcmp (canonical_path, glade_project_get_path (project)) == 0)
784         {
785           g_free (canonical_path);
786           return project;
787         }
788     }
789 
790   g_free (canonical_path);
791 
792   return NULL;
793 }
794 
795 void
796 glade_app_add_project (GladeProject *project)
797 {
798   GladeApp *app;
799 
800   g_return_if_fail (GLADE_IS_PROJECT (project));
801 
802   app = glade_app_get ();
803 
804   /* If the project was previously loaded, don't re-load */
805   if (g_list_find (app->priv->projects, project) != NULL)
806     return;
807 
808   /* Take a reference for GladeApp here... */
809   app->priv->projects = g_list_append (app->priv->projects, g_object_ref (project));
810 }
811 
812 void
813 glade_app_remove_project (GladeProject *project)
814 {
815   GladeApp *app;
816   g_return_if_fail (GLADE_IS_PROJECT (project));
817 
818   app = glade_app_get ();
819 
820   app->priv->projects = g_list_remove (app->priv->projects, project);
821 
822   /* Its safe to just release the project as the project emits a
823    * "close" signal and everyone is responsable for cleaning up at
824    * that point.
825    */
826   g_object_unref (project);
827 }
828 
829 /*
830  * glade_app_set_accel_group:
831  *
832  * Sets @accel_group to app.
833  * The acceleration group will made available for editor dialog windows
834  * from the plugin backend.
835  */
836 void
837 glade_app_set_accel_group (GtkAccelGroup *accel_group)
838 {
839   GladeApp *app;
840   g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
841 
842   app = glade_app_get ();
843 
844   app->priv->accel_group = accel_group;
845 }
846 
847 GtkAccelGroup *
848 glade_app_get_accel_group (void)
849 {
850   return glade_app_get ()->priv->accel_group;
851 }
852 
853 GladeApp *
854 glade_app_new (void)
855 {
856   return g_object_new (GLADE_TYPE_APP, NULL);
857 }
858 
859 void
860 glade_app_search_docs (const gchar *book,
861 		       const gchar *page,
862 		       const gchar *search)
863 {
864   GladeApp *app;
865 
866   app = glade_app_get ();
867 
868   g_signal_emit (G_OBJECT (app), glade_app_signals[DOC_SEARCH], 0,
869 		 book, page, search);
870 }
871