1 /*
2  * Copyright (C) 2001 Ximian, Inc.
3  * Copyright (C) 2008 Tristan Van Berkom
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * Authors:
20  *   Chema Celorio <chema@celorio.com>
21  *   Tristan Van Berkom <tvb@gnome.org>
22  */
23 
24 #include <config.h>
25 
26 /**
27  * SECTION:glade-project
28  * @Short_Description: The Glade document hub and Load/Save interface.
29  *
30  * This object owns all project objects and is responsable for loading and
31  * saving the glade document, you can monitor the project state via this
32  * object and its signals.
33  */
34 
35 #include <string.h>
36 #include <stdlib.h>
37 #include <glib.h>
38 #include <glib/gi18n-lib.h>
39 #include <glib/gstdio.h>
40 
41 #include "glade.h"
42 #include "gladeui-enum-types.h"
43 #include "glade-widget.h"
44 #include "glade-id-allocator.h"
45 #include "glade-app.h"
46 #include "glade-marshallers.h"
47 #include "glade-catalog.h"
48 
49 #include "glade-preview.h"
50 
51 #include "glade-project.h"
52 #include "glade-command.h"
53 #include "glade-name-context.h"
54 #include "glade-object-stub.h"
55 #include "glade-project-properties.h"
56 #include "glade-dnd.h"
57 #include "glade-private.h"
58 #include "glade-tsort.h"
59 
60 static void     glade_project_target_version_for_adaptor
61                                                     (GladeProject       *project,
62 						     GladeWidgetAdaptor *adaptor,
63 						     gint               *major,
64 						     gint               *minor);
65 static void     glade_project_verify_properties     (GladeWidget        *widget);
66 static void     glade_project_verify_project_for_ui (GladeProject       *project);
67 static void     glade_project_verify_adaptor        (GladeProject       *project,
68 						     GladeWidgetAdaptor *adaptor,
69 						     const gchar        *path_name,
70 						     GString            *string,
71 						     GladeVerifyFlags    flags,
72 						     gboolean            forwidget,
73 						     GladeSupportMask   *mask);
74 static void     glade_project_set_readonly          (GladeProject       *project,
75 						     gboolean            readonly);
76 static void     glade_project_set_modified          (GladeProject       *project,
77 						     gboolean            modified);
78 
79 static void     glade_project_model_iface_init      (GtkTreeModelIface  *iface);
80 
81 static void     glade_project_drag_source_init      (GtkTreeDragSourceIface *iface);
82 
83 struct _GladeProjectPrivate
84 {
85   gchar *path;                  /* The full canonical path of the glade file for this project */
86 
87   gchar *translation_domain;    /* The project translation domain */
88 
89   gint unsaved_number;          /* A unique number for this project if it is untitled */
90 
91   GladeWidgetAdaptor *add_item; /* The next item to add to the project. */
92 
93   GList *tree;                  /* List of toplevel Objects in this projects */
94   GList *objects;               /* List of all objects in this project */
95   GtkTreeModel *model;          /* GtkTreeStore used as proxy model */
96 
97   GList *selection;             /* We need to keep the selection in the project
98                                  * because we have multiple projects and when the
99                                  * user switchs between them, he will probably
100                                  * not want to loose the selection. This is a list
101                                  * of #GtkWidget items.
102                                  */
103   guint selection_changed_id;
104 
105   GladeNameContext *widget_names; /* Context for uniqueness of names */
106 
107 
108   GList *undo_stack;            /* A stack with the last executed commands */
109   GList *prev_redo_item;        /* Points to the item previous to the redo items */
110 
111   GList *first_modification;    /* we record the first modification, so that we
112                                  * can set "modification" to FALSE when we
113                                  * undo this modification
114                                  */
115 
116   GladeWidget *template;        /* The template widget */
117 
118   gchar *license;               /* License for this project (will be saved as a comment) */
119 
120   GList *comments;              /* XML comments, Glade will preserve whatever comment was
121                                  * in file before the root element, so users can delete or change it.
122                                  */
123 
124   time_t mtime;                 /* last UTC modification time of file, or 0 if it could not be read */
125 
126   GHashTable *target_versions_major;    /* target versions by catalog */
127   GHashTable *target_versions_minor;    /* target versions by catalog */
128 
129   gchar *resource_path;         /* Indicates where to load resources from for this project
130                                  * (full or relative path, null means project directory).
131                                  */
132 
133   gchar *css_provider_path;     /* The custom css to use for this project */
134   GtkCssProvider *css_provider;
135   GFileMonitor *css_monitor;
136 
137   GList *unknown_catalogs; /* List of CatalogInfo catalogs */
138 
139   GtkWidget *prefs_dialog;
140 
141   /* Store previews, so we can kill them on close */
142   GHashTable *previews;
143 
144   /* For the loading progress bars ("load-progress" signal) */
145   gint progress_step;
146   gint progress_full;
147 
148   /* Flags */
149   guint load_cancel : 1;
150   guint first_modification_is_na : 1;  /* indicates that the first_modification item has been lost */
151   guint has_selection : 1;       /* Whether the project has a selection */
152   guint readonly : 1;            /* A flag that is set if the project is readonly */
153   guint loading : 1;             /* A flags that is set when the project is loading */
154   guint modified : 1;            /* A flag that is set when a project has unsaved modifications
155                                   * if this flag is not set we don't have to query
156                                   * for confirmation after a close or exit is
157                                   * requested
158                                   */
159   guint writing_preview : 1;     /* During serialization, if we are serializing for a preview */
160   guint pointer_mode : 3;        /* The currently effective GladePointerMode */
161 };
162 
163 typedef struct
164 {
165   gchar *catalog;
166   gint position;
167 } CatalogInfo;
168 
169 
170 enum
171 {
172   ADD_WIDGET,
173   REMOVE_WIDGET,
174   WIDGET_NAME_CHANGED,
175   SELECTION_CHANGED,
176   CLOSE,
177   CHANGED,
178   PARSE_BEGAN,
179   PARSE_FINISHED,
180   TARGETS_CHANGED,
181   LOAD_PROGRESS,
182   WIDGET_VISIBILITY_CHANGED,
183   LAST_SIGNAL
184 };
185 
186 enum
187 {
188   PROP_0,
189   PROP_MODIFIED,
190   PROP_HAS_SELECTION,
191   PROP_PATH,
192   PROP_READ_ONLY,
193   PROP_ADD_ITEM,
194   PROP_POINTER_MODE,
195   PROP_TRANSLATION_DOMAIN,
196   PROP_TEMPLATE,
197   PROP_RESOURCE_PATH,
198   PROP_LICENSE,
199   PROP_CSS_PROVIDER_PATH,
200   N_PROPERTIES
201 };
202 
203 static GParamSpec       *glade_project_props[N_PROPERTIES];
204 static guint             glade_project_signals[LAST_SIGNAL] = { 0 };
205 static GladeIDAllocator *unsaved_number_allocator = NULL;
206 
207 
208 #define GLADE_XML_COMMENT "Generated with "PACKAGE_NAME
209 #define GLADE_PROJECT_LARGE_PROJECT 40
210 
211 #define VALID_ITER(project, iter) \
212   ((iter)!= NULL && G_IS_OBJECT ((iter)->user_data) && \
213    ((GladeProject*)(project))->priv->stamp == (iter)->stamp)
214 
G_DEFINE_TYPE_WITH_CODE(GladeProject,glade_project,G_TYPE_OBJECT,G_ADD_PRIVATE (GladeProject)G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,glade_project_model_iface_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,glade_project_drag_source_init))215 G_DEFINE_TYPE_WITH_CODE (GladeProject, glade_project, G_TYPE_OBJECT,
216                          G_ADD_PRIVATE (GladeProject)
217                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
218                                                 glade_project_model_iface_init)
219                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
220                                                 glade_project_drag_source_init))
221 
222 
223 /*******************************************************************
224                             GObjectClass
225  *******************************************************************/
226 static GladeIDAllocator *get_unsaved_number_allocator (void)
227 {
228   if (unsaved_number_allocator == NULL)
229     unsaved_number_allocator = glade_id_allocator_new ();
230 
231   return unsaved_number_allocator;
232 }
233 
234 static void
glade_project_list_unref(GList * original_list)235 glade_project_list_unref (GList *original_list)
236 {
237   GList *l;
238   for (l = original_list; l; l = l->next)
239     g_object_unref (G_OBJECT (l->data));
240 
241   if (original_list != NULL)
242     g_list_free (original_list);
243 }
244 
245 static void
unparent_objects_recurse(GladeWidget * widget)246 unparent_objects_recurse (GladeWidget *widget)
247 {
248   GladeWidget *child;
249   GList *children, *list;
250 
251   /* Unparent all children */
252   if ((children = glade_widget_get_children (widget)) != NULL)
253     {
254       for (list = children; list; list = list->next)
255         {
256           child = glade_widget_get_from_gobject (list->data);
257 
258           unparent_objects_recurse (child);
259 
260           if (!glade_widget_get_internal (child))
261             glade_widget_remove_child (widget, child);
262         }
263       g_list_free (children);
264     }
265 }
266 
267 static void
glade_project_dispose(GObject * object)268 glade_project_dispose (GObject *object)
269 {
270   GladeProject *project = GLADE_PROJECT (object);
271   GladeProjectPrivate *priv = project->priv;
272   GList *list, *tree;
273 
274   /* Emit close signal */
275   g_signal_emit (object, glade_project_signals[CLOSE], 0);
276 
277   /* Destroy running previews */
278   if (priv->previews)
279     {
280       g_hash_table_destroy (priv->previews);
281       priv->previews = NULL;
282     }
283 
284   if (priv->selection_changed_id > 0)
285     priv->selection_changed_id =
286       (g_source_remove (priv->selection_changed_id), 0);
287 
288   glade_project_selection_clear (project, TRUE);
289 
290   g_clear_object (&priv->css_provider);
291   g_clear_object (&priv->css_monitor);
292 
293   glade_project_list_unref (priv->undo_stack);
294   priv->undo_stack = NULL;
295 
296   /* Remove objects from the project */
297   tree = g_list_copy (priv->tree);
298   for (list = tree; list; list = list->next)
299     {
300       GladeWidget *gwidget = glade_widget_get_from_gobject (list->data);
301 
302       unparent_objects_recurse (gwidget);
303     }
304   g_list_free (tree);
305 
306   while (priv->tree)
307     glade_project_remove_object (project, priv->tree->data);
308 
309   while (priv->objects)
310     glade_project_remove_object (project, priv->objects->data);
311 
312   g_assert (priv->tree == NULL);
313   g_assert (priv->objects == NULL);
314 
315   if (priv->unknown_catalogs)
316     {
317       GList *l;
318 
319       for (l = priv->unknown_catalogs; l; l = g_list_next (l))
320         {
321           CatalogInfo *data = l->data;
322           g_free (data->catalog);
323           g_free (data);
324         }
325 
326       g_list_free (priv->unknown_catalogs);
327       priv->unknown_catalogs = NULL;
328     }
329 
330   g_object_unref (priv->model);
331 
332   G_OBJECT_CLASS (glade_project_parent_class)->dispose (object);
333 }
334 
335 static void
glade_project_finalize(GObject * object)336 glade_project_finalize (GObject *object)
337 {
338   GladeProject *project = GLADE_PROJECT (object);
339   GladeProjectPrivate *priv = project->priv;
340 
341   gtk_widget_destroy (priv->prefs_dialog);
342 
343   g_free (priv->path);
344   g_free (priv->license);
345   g_free (priv->css_provider_path);
346 
347   if (priv->comments)
348     {
349       g_list_foreach (priv->comments, (GFunc) g_free, NULL);
350       g_list_free (priv->comments);
351     }
352 
353   if (priv->unsaved_number > 0)
354     glade_id_allocator_release (get_unsaved_number_allocator (),
355                                 priv->unsaved_number);
356 
357   g_hash_table_destroy (priv->target_versions_major);
358   g_hash_table_destroy (priv->target_versions_minor);
359 
360   glade_name_context_destroy (priv->widget_names);
361 
362   G_OBJECT_CLASS (glade_project_parent_class)->finalize (object);
363 }
364 
365 static void
glade_project_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)366 glade_project_get_property (GObject    *object,
367                             guint       prop_id,
368                             GValue     *value,
369                             GParamSpec *pspec)
370 {
371   GladeProject *project = GLADE_PROJECT (object);
372 
373   switch (prop_id)
374     {
375     case PROP_MODIFIED:
376       g_value_set_boolean (value, project->priv->modified);
377       break;
378     case PROP_HAS_SELECTION:
379       g_value_set_boolean (value, project->priv->has_selection);
380       break;
381     case PROP_PATH:
382       g_value_set_string (value, project->priv->path);
383       break;
384     case PROP_READ_ONLY:
385       g_value_set_boolean (value, project->priv->readonly);
386       break;
387     case PROP_ADD_ITEM:
388       g_value_set_object (value, project->priv->add_item);
389       break;
390     case PROP_POINTER_MODE:
391       g_value_set_enum (value, project->priv->pointer_mode);
392       break;
393     case PROP_TRANSLATION_DOMAIN:
394       g_value_set_string (value, project->priv->translation_domain);
395       break;
396     case PROP_TEMPLATE:
397       g_value_set_object (value, project->priv->template);
398       break;
399     case PROP_RESOURCE_PATH:
400       g_value_set_string (value, project->priv->resource_path);
401       break;
402     case PROP_LICENSE:
403       g_value_set_string (value, project->priv->license);
404       break;
405     case PROP_CSS_PROVIDER_PATH:
406       g_value_set_string (value, project->priv->css_provider_path);
407       break;
408     default:
409       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
410       break;
411     }
412 }
413 
414 static void
glade_project_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)415 glade_project_set_property (GObject      *object,
416                             guint         prop_id,
417                             const GValue *value,
418                             GParamSpec   *pspec)
419 {
420   switch (prop_id)
421     {
422     case PROP_TRANSLATION_DOMAIN:
423       glade_project_set_translation_domain (GLADE_PROJECT (object),
424                                             g_value_get_string (value));
425       break;
426     case PROP_TEMPLATE:
427       glade_project_set_template (GLADE_PROJECT (object),
428 				  g_value_get_object (value));
429       break;
430     case PROP_RESOURCE_PATH:
431       glade_project_set_resource_path (GLADE_PROJECT (object),
432 				       g_value_get_string (value));
433       break;
434     case PROP_LICENSE:
435       glade_project_set_license (GLADE_PROJECT (object),
436                                  g_value_get_string (value));
437       break;
438     case PROP_CSS_PROVIDER_PATH:
439       glade_project_set_css_provider_path (GLADE_PROJECT (object),
440                                            g_value_get_string (value));
441       break;
442     default:
443       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
444       break;
445     }
446 }
447 
448 /*******************************************************************
449                           GladeProjectClass
450  *******************************************************************/
451 static void
glade_project_walk_back(GladeProject * project)452 glade_project_walk_back (GladeProject *project)
453 {
454   if (project->priv->prev_redo_item)
455     project->priv->prev_redo_item = project->priv->prev_redo_item->prev;
456 }
457 
458 static void
glade_project_walk_forward(GladeProject * project)459 glade_project_walk_forward (GladeProject *project)
460 {
461   if (project->priv->prev_redo_item)
462     project->priv->prev_redo_item = project->priv->prev_redo_item->next;
463   else
464     project->priv->prev_redo_item = project->priv->undo_stack;
465 }
466 
467 static void
glade_project_undo_impl(GladeProject * project)468 glade_project_undo_impl (GladeProject *project)
469 {
470   GladeCommand *cmd, *next_cmd;
471 
472   while ((cmd = glade_project_next_undo_item (project)) != NULL)
473     {
474       glade_command_undo (cmd);
475 
476       glade_project_walk_back (project);
477 
478       g_signal_emit (G_OBJECT (project),
479                      glade_project_signals[CHANGED], 0, cmd, FALSE);
480 
481       if ((next_cmd = glade_project_next_undo_item (project)) != NULL &&
482           (glade_command_group_id (next_cmd) == 0 ||
483            glade_command_group_id (next_cmd) != glade_command_group_id (cmd)))
484         break;
485     }
486 }
487 
488 static void
glade_project_redo_impl(GladeProject * project)489 glade_project_redo_impl (GladeProject *project)
490 {
491   GladeCommand *cmd, *next_cmd;
492 
493   while ((cmd = glade_project_next_redo_item (project)) != NULL)
494     {
495       glade_command_execute (cmd);
496 
497       glade_project_walk_forward (project);
498 
499       g_signal_emit (G_OBJECT (project),
500                      glade_project_signals[CHANGED], 0, cmd, TRUE);
501 
502       if ((next_cmd = glade_project_next_redo_item (project)) != NULL &&
503           (glade_command_group_id (next_cmd) == 0 ||
504            glade_command_group_id (next_cmd) != glade_command_group_id (cmd)))
505         break;
506     }
507 }
508 
509 static GladeCommand *
glade_project_next_undo_item_impl(GladeProject * project)510 glade_project_next_undo_item_impl (GladeProject *project)
511 {
512   GList *l;
513 
514   if ((l = project->priv->prev_redo_item) == NULL)
515     return NULL;
516 
517   return GLADE_COMMAND (l->data);
518 }
519 
520 static GladeCommand *
glade_project_next_redo_item_impl(GladeProject * project)521 glade_project_next_redo_item_impl (GladeProject *project)
522 {
523   GList *l;
524 
525   if ((l = project->priv->prev_redo_item) == NULL)
526     return project->priv->undo_stack ?
527         GLADE_COMMAND (project->priv->undo_stack->data) : NULL;
528   else
529     return l->next ? GLADE_COMMAND (l->next->data) : NULL;
530 }
531 
532 static GList *
glade_project_free_undo_item(GladeProject * project,GList * item)533 glade_project_free_undo_item (GladeProject *project, GList *item)
534 {
535   g_assert (item->data);
536 
537   if (item == project->priv->first_modification)
538     project->priv->first_modification_is_na = TRUE;
539 
540   g_object_unref (G_OBJECT (item->data));
541 
542   return g_list_next (item);
543 }
544 
545 static void
glade_project_push_undo_impl(GladeProject * project,GladeCommand * cmd)546 glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd)
547 {
548   GladeProjectPrivate *priv = project->priv;
549   GList *tmp_redo_item;
550 
551   /* We should now free all the "redo" items */
552   tmp_redo_item = g_list_next (priv->prev_redo_item);
553   while (tmp_redo_item)
554     tmp_redo_item = glade_project_free_undo_item (project, tmp_redo_item);
555 
556   if (priv->prev_redo_item)
557     {
558       g_list_free (g_list_next (priv->prev_redo_item));
559       priv->prev_redo_item->next = NULL;
560     }
561   else
562     {
563       g_list_free (priv->undo_stack);
564       priv->undo_stack = NULL;
565     }
566 
567   /* Try to unify only if group depth is 0 and the project has not been recently saved */
568   if (glade_command_get_group_depth () == 0 &&
569       priv->prev_redo_item != NULL &&
570       project->priv->prev_redo_item != project->priv->first_modification)
571     {
572       GladeCommand *cmd1 = priv->prev_redo_item->data;
573 
574       if (glade_command_unifies (cmd1, cmd))
575         {
576           glade_command_collapse (cmd1, cmd);
577           g_object_unref (cmd);
578 
579           if (glade_command_unifies (cmd1, NULL))
580             {
581               tmp_redo_item = priv->prev_redo_item;
582               glade_project_walk_back (project);
583               glade_project_free_undo_item (project, tmp_redo_item);
584               priv->undo_stack =
585                 g_list_delete_link (priv->undo_stack, tmp_redo_item);
586 
587               cmd1 = NULL;
588             }
589 
590           g_signal_emit (G_OBJECT (project),
591                          glade_project_signals[CHANGED], 0, cmd1, TRUE);
592           return;
593         }
594     }
595 
596   /* and then push the new undo item */
597   priv->undo_stack = g_list_append (priv->undo_stack, cmd);
598 
599   if (project->priv->prev_redo_item == NULL)
600     priv->prev_redo_item = priv->undo_stack;
601   else
602     priv->prev_redo_item = g_list_next (priv->prev_redo_item);
603 
604   g_signal_emit (G_OBJECT (project),
605                  glade_project_signals[CHANGED], 0, cmd, TRUE);
606 }
607 
608 static inline gchar *
glade_preview_get_pid_as_str(GladePreview * preview)609 glade_preview_get_pid_as_str (GladePreview *preview)
610 {
611 #ifdef G_OS_WIN32
612   return g_strdup_printf ("%p", glade_preview_get_pid (preview));
613 #else
614   return g_strdup_printf ("%d", glade_preview_get_pid (preview));
615 #endif
616 }
617 
618 static void
glade_project_preview_exits(GladePreview * preview,GladeProject * project)619 glade_project_preview_exits (GladePreview *preview, GladeProject *project)
620 {
621   gchar       *pidstr;
622 
623   pidstr  = glade_preview_get_pid_as_str (preview);
624   preview = g_hash_table_lookup (project->priv->previews, pidstr);
625 
626   if (preview)
627     g_hash_table_remove (project->priv->previews, pidstr);
628 
629   g_free (pidstr);
630 }
631 
632 static void
glade_project_destroy_preview(gpointer data)633 glade_project_destroy_preview (gpointer data)
634 {
635   GladePreview *preview = GLADE_PREVIEW (data);
636   GladeWidget  *gwidget;
637 
638   gwidget = glade_preview_get_widget (preview);
639   g_object_set_data (G_OBJECT (gwidget), "preview", NULL);
640 
641   g_signal_handlers_disconnect_by_func (preview,
642                                         G_CALLBACK (glade_project_preview_exits),
643                                         g_object_get_data (G_OBJECT (preview), "project"));
644   g_object_unref (preview);
645 }
646 
647 static void
glade_project_changed_impl(GladeProject * project,GladeCommand * command,gboolean forward)648 glade_project_changed_impl (GladeProject *project,
649                             GladeCommand *command,
650                             gboolean      forward)
651 {
652   if (!project->priv->loading)
653     {
654       /* if this command is the first modification to cause the project
655        * to have unsaved changes, then we can now flag the project as unmodified
656        */
657       if (!project->priv->first_modification_is_na &&
658           project->priv->prev_redo_item == project->priv->first_modification)
659         glade_project_set_modified (project, FALSE);
660       else
661         glade_project_set_modified (project, TRUE);
662     }
663 }
664 
665 static void
glade_project_set_css_provider_forall(GtkWidget * widget,gpointer data)666 glade_project_set_css_provider_forall (GtkWidget *widget, gpointer data)
667 {
668   if (GLADE_IS_PLACEHOLDER (widget) || GLADE_IS_OBJECT_STUB (widget))
669     return;
670 
671   gtk_style_context_add_provider (gtk_widget_get_style_context (widget),
672                                   GTK_STYLE_PROVIDER (data),
673                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
674 
675   if (GTK_IS_CONTAINER (widget))
676     gtk_container_forall (GTK_CONTAINER (widget), glade_project_set_css_provider_forall, data);
677 }
678 
679 static void
glade_project_add_object_impl(GladeProject * project,GladeWidget * gwidget)680 glade_project_add_object_impl (GladeProject *project, GladeWidget *gwidget)
681 {
682   GladeProjectPrivate *priv = project->priv;
683   GObject *widget = glade_widget_get_object (gwidget);
684 
685   if (!priv->css_provider || !GTK_IS_WIDGET (widget))
686     return;
687 
688   glade_project_set_css_provider_forall (GTK_WIDGET (widget), priv->css_provider);
689 }
690 
691 /*******************************************************************
692                           Class Initializers
693  *******************************************************************/
694 static void
glade_project_init(GladeProject * project)695 glade_project_init (GladeProject *project)
696 {
697   GladeProjectPrivate *priv;
698   GList *list;
699 
700   project->priv = priv = glade_project_get_instance_private (project);
701 
702   priv->path = NULL;
703   priv->model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_OBJECT));
704 
705   g_signal_connect_swapped (priv->model, "row-changed",
706                             G_CALLBACK (gtk_tree_model_row_changed),
707                             project);
708   g_signal_connect_swapped (priv->model, "row-inserted",
709                             G_CALLBACK (gtk_tree_model_row_inserted),
710                             project);
711   g_signal_connect_swapped (priv->model, "row-has-child-toggled",
712                             G_CALLBACK (gtk_tree_model_row_has_child_toggled),
713                             project);
714   g_signal_connect_swapped (priv->model, "row-deleted",
715                             G_CALLBACK (gtk_tree_model_row_deleted),
716                             project);
717   g_signal_connect_swapped (priv->model, "rows-reordered",
718                             G_CALLBACK (gtk_tree_model_rows_reordered),
719                             project);
720 
721   priv->readonly = FALSE;
722   priv->tree = NULL;
723   priv->selection = NULL;
724   priv->has_selection = FALSE;
725   priv->undo_stack = NULL;
726   priv->prev_redo_item = NULL;
727   priv->first_modification = NULL;
728   priv->first_modification_is_na = FALSE;
729   priv->unknown_catalogs = NULL;
730 
731   priv->previews = g_hash_table_new_full (g_str_hash,
732                                           g_str_equal,
733                                           g_free,
734                                           glade_project_destroy_preview);
735 
736   priv->widget_names = glade_name_context_new ();
737 
738   priv->unsaved_number =
739       glade_id_allocator_allocate (get_unsaved_number_allocator ());
740 
741   priv->target_versions_major = g_hash_table_new_full (g_str_hash,
742                                                        g_str_equal,
743                                                        g_free, NULL);
744   priv->target_versions_minor = g_hash_table_new_full (g_str_hash,
745                                                        g_str_equal,
746                                                        g_free, NULL);
747 
748   for (list = glade_app_get_catalogs (); list; list = list->next)
749     {
750       GladeCatalog *catalog = list->data;
751 
752       /* Set default target to catalog version */
753       glade_project_set_target_version (project,
754                                         glade_catalog_get_name (catalog),
755                                         glade_catalog_get_major_version
756                                         (catalog),
757                                         glade_catalog_get_minor_version
758                                         (catalog));
759     }
760 
761   priv->prefs_dialog = glade_project_properties_new (project);
762 }
763 
764 static void
glade_project_class_init(GladeProjectClass * klass)765 glade_project_class_init (GladeProjectClass *klass)
766 {
767   GObjectClass *object_class;
768 
769   object_class = G_OBJECT_CLASS (klass);
770 
771   object_class->get_property = glade_project_get_property;
772   object_class->set_property = glade_project_set_property;
773   object_class->finalize = glade_project_finalize;
774   object_class->dispose = glade_project_dispose;
775 
776   klass->add_object = glade_project_add_object_impl;
777   klass->remove_object = NULL;
778   klass->undo = glade_project_undo_impl;
779   klass->redo = glade_project_redo_impl;
780   klass->next_undo_item = glade_project_next_undo_item_impl;
781   klass->next_redo_item = glade_project_next_redo_item_impl;
782   klass->push_undo = glade_project_push_undo_impl;
783 
784   klass->widget_name_changed = NULL;
785   klass->selection_changed = NULL;
786   klass->close = NULL;
787   klass->changed = glade_project_changed_impl;
788 
789   /**
790    * GladeProject::add-widget:
791    * @gladeproject: the #GladeProject which received the signal.
792    * @arg1: the #GladeWidget that was added to @gladeproject.
793    *
794    * Emitted when a widget is added to a project.
795    */
796   glade_project_signals[ADD_WIDGET] =
797       g_signal_new ("add_widget",
798                     G_TYPE_FROM_CLASS (object_class),
799                     G_SIGNAL_RUN_LAST,
800                     G_STRUCT_OFFSET (GladeProjectClass, add_object),
801                     NULL, NULL,
802                     g_cclosure_marshal_VOID__OBJECT,
803                     G_TYPE_NONE, 1, GLADE_TYPE_WIDGET);
804 
805   /**
806    * GladeProject::remove-widget:
807    * @gladeproject: the #GladeProject which received the signal.
808    * @arg1: the #GladeWidget that was removed from @gladeproject.
809    *
810    * Emitted when a widget is removed from a project.
811    */
812   glade_project_signals[REMOVE_WIDGET] =
813       g_signal_new ("remove_widget",
814                     G_TYPE_FROM_CLASS (object_class),
815                     G_SIGNAL_RUN_LAST,
816                     G_STRUCT_OFFSET (GladeProjectClass, remove_object),
817                     NULL, NULL,
818                     g_cclosure_marshal_VOID__OBJECT,
819                     G_TYPE_NONE, 1, GLADE_TYPE_WIDGET);
820 
821 
822   /**
823    * GladeProject::widget-name-changed:
824    * @gladeproject: the #GladeProject which received the signal.
825    * @arg1: the #GladeWidget who's name changed.
826    *
827    * Emitted when @gwidget's name changes.
828    */
829   glade_project_signals[WIDGET_NAME_CHANGED] =
830       g_signal_new ("widget_name_changed",
831                     G_TYPE_FROM_CLASS (object_class),
832                     G_SIGNAL_RUN_LAST,
833                     G_STRUCT_OFFSET (GladeProjectClass, widget_name_changed),
834                     NULL, NULL,
835                     g_cclosure_marshal_VOID__OBJECT,
836                     G_TYPE_NONE, 1, GLADE_TYPE_WIDGET);
837 
838 
839   /**
840    * GladeProject::selection-changed:
841    * @gladeproject: the #GladeProject which received the signal.
842    *
843    * Emitted when @gladeproject selection list changes.
844    */
845   glade_project_signals[SELECTION_CHANGED] =
846       g_signal_new ("selection_changed",
847                     G_TYPE_FROM_CLASS (object_class),
848                     G_SIGNAL_RUN_LAST,
849                     G_STRUCT_OFFSET (GladeProjectClass, selection_changed),
850                     NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
851 
852 
853   /**
854    * GladeProject::close:
855    * @gladeproject: the #GladeProject which received the signal.
856    *
857    * Emitted when a project is closing (a good time to clean up
858    * any associated resources).
859    */
860   glade_project_signals[CLOSE] =
861       g_signal_new ("close",
862                     G_TYPE_FROM_CLASS (object_class),
863                     G_SIGNAL_RUN_LAST,
864                     G_STRUCT_OFFSET (GladeProjectClass, close),
865                     NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
866 
867   /**
868    * GladeProject::changed:
869    * @gladeproject: the #GladeProject which received the signal.
870    * @arg1: the #GladeCommand that was executed
871    * @arg2: whether the command was executed or undone.
872    *
873    * Emitted when a @gladeproject's state changes via a #GladeCommand.
874    */
875   glade_project_signals[CHANGED] =
876       g_signal_new ("changed",
877                     G_TYPE_FROM_CLASS (object_class),
878                     G_SIGNAL_RUN_FIRST,
879                     G_STRUCT_OFFSET (GladeProjectClass, changed),
880                     NULL, NULL,
881                     _glade_marshal_VOID__OBJECT_BOOLEAN,
882                     G_TYPE_NONE, 2, GLADE_TYPE_COMMAND, G_TYPE_BOOLEAN);
883 
884   /**
885    * GladeProject::parse-began:
886    * @gladeproject: the #GladeProject which received the signal.
887    *
888    * Emitted when @gladeproject parsing starts.
889    */
890   glade_project_signals[PARSE_BEGAN] =
891       g_signal_new ("parse-began",
892                     G_TYPE_FROM_CLASS (object_class),
893                     G_SIGNAL_RUN_FIRST,
894                     0, NULL, NULL,
895                     g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
896 
897   /**
898    * GladeProject::parse-finished:
899    * @gladeproject: the #GladeProject which received the signal.
900    *
901    * Emitted when @gladeproject parsing has finished.
902    */
903   glade_project_signals[PARSE_FINISHED] =
904       g_signal_new ("parse-finished",
905                     G_TYPE_FROM_CLASS (object_class),
906                     G_SIGNAL_RUN_FIRST,
907                     G_STRUCT_OFFSET (GladeProjectClass, parse_finished),
908                     NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
909 
910   /**
911    * GladeProject::targets-changed:
912    * @gladeproject: the #GladeProject which received the signal.
913    *
914    * Emitted when @gladeproject target versions change.
915    */
916   glade_project_signals[TARGETS_CHANGED] =
917       g_signal_new ("targets-changed",
918                     G_TYPE_FROM_CLASS (object_class),
919                     G_SIGNAL_RUN_FIRST,
920                     0, NULL, NULL,
921                     g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
922 
923   /**
924    * GladeProject::load-progress:
925    * @gladeproject: the #GladeProject which received the signal.
926    * @objects_total: the total amount of objects to load
927    * @objects_loaded: the current amount of loaded objects
928    *
929    * Emitted while @project is loading.
930    */
931   glade_project_signals[LOAD_PROGRESS] =
932       g_signal_new ("load-progress",
933                     G_TYPE_FROM_CLASS (object_class),
934                     G_SIGNAL_RUN_FIRST,
935                     0, NULL, NULL,
936                     _glade_marshal_VOID__INT_INT,
937                     G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
938 
939   /**
940    * GladeProject::widget-visibility-changed:
941    * @gladeproject: the #GladeProject which received the signal.
942    * @widget: the widget that its visibity changed
943    * @visible: the current visiblity of the widget
944    *
945    * Emitted when the visivility of a widget changed
946    */
947   glade_project_signals[WIDGET_VISIBILITY_CHANGED] =
948       g_signal_new ("widget-visibility-changed",
949                     G_TYPE_FROM_CLASS (object_class),
950                     G_SIGNAL_RUN_FIRST,
951                     0, NULL, NULL,
952                     _glade_marshal_VOID__OBJECT_BOOLEAN,
953                     G_TYPE_NONE, 2, GLADE_TYPE_WIDGET, G_TYPE_BOOLEAN);
954 
955   glade_project_props[PROP_MODIFIED] =
956     g_param_spec_boolean ("modified",
957                           "Modified",
958                           _("Whether project has been modified since it was last saved"),
959                           FALSE,
960                           G_PARAM_READABLE);
961 
962   glade_project_props[PROP_HAS_SELECTION] =
963     g_param_spec_boolean ("has-selection",
964                           _("Has Selection"),
965                           _("Whether project has a selection"),
966                           FALSE,
967                           G_PARAM_READABLE);
968 
969   glade_project_props[PROP_PATH] =
970     g_param_spec_string ("path",
971                          _("Path"),
972                          _("The filesystem path of the project"),
973                          NULL,
974                          G_PARAM_READABLE);
975 
976   glade_project_props[PROP_READ_ONLY] =
977     g_param_spec_boolean ("read-only",
978                           _("Read Only"),
979                           _("Whether project is read-only"),
980                           FALSE,
981                           G_PARAM_READABLE);
982 
983   glade_project_props[PROP_ADD_ITEM] =
984     g_param_spec_object ("add-item",
985                          _("Add Item"),
986                          _("The current item to add to the project"),
987                          GLADE_TYPE_WIDGET_ADAPTOR,
988                          G_PARAM_READABLE);
989 
990   glade_project_props[PROP_POINTER_MODE] =
991     g_param_spec_enum ("pointer-mode",
992                        _("Pointer Mode"),
993                        _("The currently effective GladePointerMode"),
994                        GLADE_TYPE_POINTER_MODE,
995                        GLADE_POINTER_SELECT,
996                        G_PARAM_READABLE);
997 
998   glade_project_props[PROP_TRANSLATION_DOMAIN] =
999     g_param_spec_string ("translation-domain",
1000                          _("Translation Domain"),
1001                          _("The project translation domain"),
1002                          NULL,
1003                          G_PARAM_READWRITE);
1004 
1005   glade_project_props[PROP_TEMPLATE] =
1006     g_param_spec_object ("template",
1007                          _("Template"),
1008                          _("The project's template widget, if any"),
1009                          GLADE_TYPE_WIDGET,
1010                          G_PARAM_READWRITE);
1011 
1012   glade_project_props[PROP_RESOURCE_PATH] =
1013     g_param_spec_string ("resource-path",
1014                          _("Resource Path"),
1015                          _("Path to load images and resources in Glade's runtime"),
1016                          NULL,
1017                          G_PARAM_READWRITE);
1018 
1019   glade_project_props[PROP_LICENSE] =
1020     g_param_spec_string ("license",
1021                          _("License"),
1022                          _("License for this project, it will be added as a document level comment."),
1023                          NULL,
1024                          G_PARAM_READWRITE);
1025 
1026   glade_project_props[PROP_CSS_PROVIDER_PATH] =
1027     g_param_spec_string ("css-provider-path",
1028                          _("CSS Provider Path"),
1029                          _("Path to use as the custom CSS provider for this project."),
1030                          NULL,
1031                          G_PARAM_READWRITE);
1032 
1033   /* Install all properties */
1034   g_object_class_install_properties (object_class, N_PROPERTIES, glade_project_props);
1035 }
1036 
1037 /******************************************************************
1038  *                    GtkTreeModelIface                           *
1039  ******************************************************************/
1040 static GtkTreeModelFlags
glade_project_model_get_flags(GtkTreeModel * model)1041 glade_project_model_get_flags (GtkTreeModel *model)
1042 {
1043   return 0;
1044 }
1045 
1046 static gint
glade_project_model_get_n_columns(GtkTreeModel * model)1047 glade_project_model_get_n_columns (GtkTreeModel *model)
1048 {
1049   return GLADE_PROJECT_MODEL_N_COLUMNS;
1050 }
1051 
1052 static GType
glade_project_model_get_column_type(GtkTreeModel * model,gint column)1053 glade_project_model_get_column_type (GtkTreeModel *model, gint column)
1054 {
1055   switch (column)
1056     {
1057       case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME:
1058         return G_TYPE_STRING;
1059       case GLADE_PROJECT_MODEL_COLUMN_NAME:
1060         return G_TYPE_STRING;
1061       case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME:
1062         return G_TYPE_STRING;
1063       case GLADE_PROJECT_MODEL_COLUMN_OBJECT:
1064         return G_TYPE_OBJECT;
1065       case GLADE_PROJECT_MODEL_COLUMN_MISC:
1066         return G_TYPE_STRING;
1067       case GLADE_PROJECT_MODEL_COLUMN_WARNING:
1068         return G_TYPE_STRING;
1069       default:
1070         g_assert_not_reached ();
1071         return G_TYPE_NONE;
1072     }
1073 }
1074 
1075 static gboolean
glade_project_model_get_iter(GtkTreeModel * model,GtkTreeIter * iter,GtkTreePath * path)1076 glade_project_model_get_iter (GtkTreeModel *model,
1077                               GtkTreeIter  *iter,
1078                               GtkTreePath  *path)
1079 {
1080   return gtk_tree_model_get_iter (GLADE_PROJECT (model)->priv->model, iter, path);
1081 }
1082 
1083 static GtkTreePath *
glade_project_model_get_path(GtkTreeModel * model,GtkTreeIter * iter)1084 glade_project_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
1085 {
1086   return gtk_tree_model_get_path (GLADE_PROJECT (model)->priv->model, iter);
1087 }
1088 
1089 static void
glade_project_model_get_value(GtkTreeModel * model,GtkTreeIter * iter,gint column,GValue * value)1090 glade_project_model_get_value (GtkTreeModel *model,
1091                                GtkTreeIter  *iter,
1092                                gint          column,
1093                                GValue       *value)
1094 {
1095   GladeWidget *widget;
1096 
1097   gtk_tree_model_get (GLADE_PROJECT (model)->priv->model, iter, 0, &widget, -1);
1098 
1099   value = g_value_init (value,
1100                         glade_project_model_get_column_type (model, column));
1101 
1102   switch (column)
1103     {
1104       case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME:
1105         g_value_set_string (value, glade_widget_adaptor_get_icon_name (glade_widget_get_adaptor (widget)));
1106         break;
1107       case GLADE_PROJECT_MODEL_COLUMN_NAME:
1108         g_value_set_string (value, glade_widget_get_name (widget));
1109         break;
1110       case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME:
1111         {
1112           GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (widget);
1113           g_value_set_static_string (value, glade_widget_adaptor_get_name (adaptor));
1114           break;
1115         }
1116       case GLADE_PROJECT_MODEL_COLUMN_OBJECT:
1117         g_value_set_object (value, glade_widget_get_object (widget));
1118         break;
1119       case GLADE_PROJECT_MODEL_COLUMN_MISC:
1120         {
1121           gchar *str = NULL, *child_type;
1122           GladeProperty *ref_prop;
1123 
1124           /* special child type / internal child */
1125           if (glade_widget_get_internal (widget) != NULL)
1126             str = g_strdup_printf (_("(internal %s)"),
1127                                    glade_widget_get_internal (widget));
1128           else if ((child_type =
1129                     g_object_get_data (glade_widget_get_object (widget),
1130                                        "special-child-type")) != NULL)
1131             str = g_strdup_printf (_("(%s child)"), child_type);
1132           else if (glade_widget_get_is_composite (widget))
1133             str = g_strdup_printf (_("(template)"));
1134           else if ((ref_prop =
1135                     glade_widget_get_parentless_widget_ref (widget)) != NULL)
1136             {
1137               GladePropertyClass *pclass     = glade_property_get_class (ref_prop);
1138               GladeWidget        *ref_widget = glade_property_get_widget (ref_prop);
1139 
1140               /* translators: refers to a property named '%s' of widget '%s' */
1141               str = g_strdup_printf (_("(%s of %s)"),
1142                                      glade_property_class_get_name (pclass),
1143                                      glade_widget_get_name (ref_widget));
1144             }
1145 
1146           g_value_take_string (value, str);
1147         }
1148         break;
1149       case GLADE_PROJECT_MODEL_COLUMN_WARNING:
1150         g_value_set_string (value, glade_widget_support_warning (widget));
1151 	break;
1152 
1153       default:
1154         g_assert_not_reached ();
1155     }
1156 }
1157 
1158 static gboolean
glade_project_model_iter_next(GtkTreeModel * model,GtkTreeIter * iter)1159 glade_project_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
1160 {
1161   return gtk_tree_model_iter_next (GLADE_PROJECT (model)->priv->model, iter);
1162 }
1163 
1164 static gboolean
glade_project_model_iter_has_child(GtkTreeModel * model,GtkTreeIter * iter)1165 glade_project_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
1166 {
1167   return gtk_tree_model_iter_has_child (GLADE_PROJECT (model)->priv->model, iter);
1168 }
1169 
1170 static gint
glade_project_model_iter_n_children(GtkTreeModel * model,GtkTreeIter * iter)1171 glade_project_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
1172 {
1173   return gtk_tree_model_iter_n_children (GLADE_PROJECT (model)->priv->model, iter);
1174 }
1175 
1176 static gboolean
glade_project_model_iter_nth_child(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent,gint nth)1177 glade_project_model_iter_nth_child (GtkTreeModel *model,
1178                                     GtkTreeIter  *iter,
1179                                     GtkTreeIter  *parent,
1180                                     gint          nth)
1181 {
1182   return gtk_tree_model_iter_nth_child (GLADE_PROJECT (model)->priv->model,
1183                                         iter, parent, nth);
1184 }
1185 
1186 static gboolean
glade_project_model_iter_children(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)1187 glade_project_model_iter_children (GtkTreeModel *model,
1188                                    GtkTreeIter  *iter,
1189                                    GtkTreeIter  *parent)
1190 {
1191   return gtk_tree_model_iter_children (GLADE_PROJECT (model)->priv->model,
1192                                        iter, parent);
1193 }
1194 
1195 static gboolean
glade_project_model_iter_parent(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * child)1196 glade_project_model_iter_parent (GtkTreeModel *model,
1197                                  GtkTreeIter  *iter,
1198                                  GtkTreeIter  *child)
1199 {
1200   return gtk_tree_model_iter_parent (GLADE_PROJECT (model)->priv->model,
1201                                      iter, child);
1202 }
1203 
1204 static void
glade_project_model_ref_node(GtkTreeModel * model,GtkTreeIter * iter)1205 glade_project_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter)
1206 {
1207   gtk_tree_model_ref_node (GLADE_PROJECT (model)->priv->model, iter);
1208 }
1209 
1210 static void
glade_project_model_unref_node(GtkTreeModel * model,GtkTreeIter * iter)1211 glade_project_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter)
1212 {
1213   gtk_tree_model_unref_node (GLADE_PROJECT (model)->priv->model, iter);
1214 }
1215 
1216 
1217 static void
glade_project_model_iface_init(GtkTreeModelIface * iface)1218 glade_project_model_iface_init (GtkTreeModelIface *iface)
1219 {
1220   iface->get_flags = glade_project_model_get_flags;
1221   iface->get_n_columns = glade_project_model_get_n_columns;
1222   iface->get_column_type = glade_project_model_get_column_type;
1223   iface->get_iter = glade_project_model_get_iter;
1224   iface->get_path = glade_project_model_get_path;
1225   iface->get_value = glade_project_model_get_value;
1226   iface->iter_next = glade_project_model_iter_next;
1227   iface->iter_children = glade_project_model_iter_children;
1228   iface->iter_has_child = glade_project_model_iter_has_child;
1229   iface->iter_n_children = glade_project_model_iter_n_children;
1230   iface->iter_nth_child = glade_project_model_iter_nth_child;
1231   iface->iter_parent = glade_project_model_iter_parent;
1232   iface->ref_node = glade_project_model_ref_node;
1233   iface->unref_node = glade_project_model_unref_node;
1234 }
1235 
1236 static gboolean
glade_project_row_draggable(GtkTreeDragSource * drag_source,GtkTreePath * path)1237 glade_project_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path)
1238 {
1239   return TRUE;
1240 }
1241 
1242 static gboolean
glade_project_drag_data_delete(GtkTreeDragSource * drag_source,GtkTreePath * path)1243 glade_project_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path)
1244 {
1245   return FALSE;
1246 }
1247 
1248 static gboolean
glade_project_drag_data_get(GtkTreeDragSource * drag_source,GtkTreePath * path,GtkSelectionData * selection_data)1249 glade_project_drag_data_get (GtkTreeDragSource *drag_source,
1250                              GtkTreePath       *path,
1251                              GtkSelectionData  *selection_data)
1252 {
1253   GtkTreeIter iter;
1254 
1255   if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
1256     {
1257       GObject *object;
1258 
1259       gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
1260                           GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object,
1261                           -1);
1262 
1263       _glade_dnd_set_data (selection_data, object);
1264       return TRUE;
1265     }
1266 
1267   return FALSE;
1268 }
1269 
1270 static void
glade_project_drag_source_init(GtkTreeDragSourceIface * iface)1271 glade_project_drag_source_init (GtkTreeDragSourceIface *iface)
1272 {
1273   iface->row_draggable = glade_project_row_draggable;
1274   iface->drag_data_delete = glade_project_drag_data_delete;
1275   iface->drag_data_get = glade_project_drag_data_get;
1276 }
1277 
1278 /*******************************************************************
1279                     Loading project code here
1280  *******************************************************************/
1281 
1282 /**
1283  * glade_project_new:
1284  *
1285  * Creates a new #GladeProject.
1286  *
1287  * Returns: a new #GladeProject
1288  */
1289 GladeProject *
glade_project_new(void)1290 glade_project_new (void)
1291 {
1292   GladeProject *project = g_object_new (GLADE_TYPE_PROJECT, NULL);
1293   return project;
1294 }
1295 
1296 /* Called when finishing loading a glade file to resolve object type properties
1297  */
1298 static void
glade_project_fix_object_props(GladeProject * project)1299 glade_project_fix_object_props (GladeProject *project)
1300 {
1301   GList *l, *ll, *objects;
1302   GValue *value;
1303   GladeWidget *gwidget;
1304   GladeProperty *property;
1305   gchar *txt;
1306 
1307   objects = g_list_copy (project->priv->objects);
1308   for (l = objects; l; l = l->next)
1309     {
1310       gwidget = glade_widget_get_from_gobject (l->data);
1311 
1312       for (ll = glade_widget_get_properties (gwidget); ll; ll = ll->next)
1313         {
1314           GladePropertyClass *klass;
1315 
1316           property = GLADE_PROPERTY (ll->data);
1317           klass    = glade_property_get_class (property);
1318 
1319           if (glade_property_class_is_object (klass) &&
1320               (txt = g_object_get_data (G_OBJECT (property),
1321                                         "glade-loaded-object")) != NULL)
1322             {
1323               /* Parse the object list and set the property to it
1324                * (this magicly works for both objects & object lists)
1325                */
1326               value = glade_property_class_make_gvalue_from_string (klass, txt, project);
1327 
1328               glade_property_set_value (property, value);
1329 
1330               g_value_unset (value);
1331               g_free (value);
1332 
1333               g_object_set_data (G_OBJECT (property),
1334                                  "glade-loaded-object", NULL);
1335             }
1336         }
1337     }
1338   g_list_free (objects);
1339 }
1340 
1341 static void
glade_project_fix_template(GladeProject * project)1342 glade_project_fix_template (GladeProject *project)
1343 {
1344   GList *l;
1345   gboolean composite = FALSE;
1346 
1347   for (l = project->priv->tree; l; l = l->next)
1348     {
1349       GObject     *obj = l->data;
1350       GladeWidget *gwidget;
1351 
1352       gwidget   = glade_widget_get_from_gobject (obj);
1353       composite = glade_widget_get_is_composite (gwidget);
1354 
1355       if (composite)
1356         {
1357 	  glade_project_set_template (project, gwidget);
1358 	  break;
1359         }
1360     }
1361 }
1362 
1363 static gchar *
gp_comment_strip_property(gchar * value,gchar * property)1364 gp_comment_strip_property (gchar *value, gchar *property)
1365 {
1366   if (g_str_has_prefix (value, property))
1367     {
1368       gchar *start = value + strlen (property);
1369 
1370       if (*start == ' ')
1371         start++;
1372 
1373       memmove (value, start, strlen (start) + 1);
1374       return value;
1375     }
1376 
1377   return NULL;
1378 }
1379 
1380 static gchar *
gp_comment_get_content(GladeXmlNode * comment)1381 gp_comment_get_content (GladeXmlNode *comment)
1382 {
1383   gchar *value;
1384 
1385   if (glade_xml_node_is_comment (comment) &&
1386       (value = glade_xml_get_content (comment)))
1387     {
1388       gchar *compressed;
1389 
1390       /* Replace NON-BREAKING HYPHEN with regular HYPHEN */
1391       value = _glade_util_strreplace (g_strstrip (value), TRUE, "\\‑\\‑", "--");
1392       compressed = g_strcompress (value);
1393       g_free (value);
1394       return compressed;
1395     }
1396 
1397   return NULL;
1398 }
1399 
1400 static gchar *
glade_project_read_requires_from_comment(GladeXmlNode * comment,guint16 * major,guint16 * minor)1401 glade_project_read_requires_from_comment (GladeXmlNode *comment,
1402                                           guint16      *major,
1403                                           guint16      *minor)
1404 {
1405   gchar *value, *requires, *required_lib = NULL;
1406 
1407   if ((value = gp_comment_get_content (comment)) &&
1408       (requires = gp_comment_strip_property (value, "interface-requires")))
1409     {
1410       gchar buffer[128];
1411       gint maj, min;
1412 
1413       if (sscanf (requires, "%128s %d.%d", buffer, &maj, &min) == 3)
1414         {
1415           if (major)
1416             *major = maj;
1417           if (minor)
1418             *minor = min;
1419           required_lib = g_strdup (buffer);
1420         }
1421     }
1422   g_free (value);
1423 
1424   return required_lib;
1425 }
1426 
1427 
1428 static gboolean
glade_project_read_requires(GladeProject * project,GladeXmlNode * root_node,const gchar * path,gboolean * has_gtk_dep)1429 glade_project_read_requires (GladeProject *project,
1430                              GladeXmlNode *root_node,
1431                              const gchar  *path,
1432                              gboolean     *has_gtk_dep)
1433 {
1434 
1435   GString *string = g_string_new (NULL);
1436   GladeXmlNode *node;
1437   gboolean loadable = TRUE;
1438   guint16 major, minor;
1439   gint position = 0;
1440 
1441   for (node = glade_xml_node_get_children_with_comments (root_node);
1442        node; node = glade_xml_node_next_with_comments (node))
1443     {
1444       gchar *required_lib = NULL;
1445 
1446       /* Skip non "requires" tags */
1447       if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_REQUIRES) ||
1448             (required_lib =
1449              glade_project_read_requires_from_comment (node, &major, &minor))))
1450         continue;
1451 
1452       if (!required_lib)
1453         {
1454           required_lib =
1455               glade_xml_get_property_string_required (node, GLADE_XML_TAG_LIB,
1456                                                       NULL);
1457           glade_xml_get_property_version (node, GLADE_XML_TAG_VERSION,
1458                                           &major, &minor);
1459         }
1460 
1461       if (!required_lib)
1462         continue;
1463 
1464       /* Dont mention gtk+ as a required lib in
1465        * the generated glade file
1466        */
1467       if (!glade_catalog_is_loaded (required_lib))
1468         {
1469           CatalogInfo *data = g_new0(CatalogInfo, 1);
1470 
1471           data->catalog = required_lib;
1472           data->position = position;
1473 
1474           /* Keep a list of unknown catalogs to avoid loosing the requirement */
1475           project->priv->unknown_catalogs = g_list_append (project->priv->unknown_catalogs,
1476                                                            data);
1477           /* Also keep the version */
1478           glade_project_set_target_version (project, required_lib, major, minor);
1479 
1480           if (!loadable)
1481             g_string_append (string, ", ");
1482 
1483           g_string_append (string, required_lib);
1484           loadable = FALSE;
1485         }
1486       else
1487         {
1488           if (has_gtk_dep && strcmp (required_lib, "gtk+") == 0)
1489             *has_gtk_dep = TRUE;
1490 
1491           glade_project_set_target_version
1492               (project, required_lib, major, minor);
1493           g_free (required_lib);
1494         }
1495 
1496       position++;
1497     }
1498 
1499   if (!loadable)
1500     glade_util_ui_message (glade_app_get_window (),
1501                            GLADE_UI_ERROR, NULL,
1502                            _("Failed to load %s.\n"
1503                              "The following required catalogs are unavailable: %s"),
1504                            path, string->str);
1505   g_string_free (string, TRUE);
1506   return loadable;
1507 }
1508 
1509 static void
update_project_for_resource_path(GladeProject * project)1510 update_project_for_resource_path (GladeProject *project)
1511 {
1512   GladeWidget *widget;
1513   GladeProperty *property;
1514   GList *l, *list;
1515 
1516   for (l = project->priv->objects; l; l = l->next)
1517     {
1518 
1519       widget = glade_widget_get_from_gobject (l->data);
1520 
1521       for (list = glade_widget_get_properties (widget); list; list = list->next)
1522         {
1523           GladePropertyClass *klass;
1524           GParamSpec         *pspec;
1525 
1526           property = list->data;
1527           klass    = glade_property_get_class (property);
1528           pspec    = glade_property_class_get_pspec (klass);
1529 
1530           /* XXX We should have a "resource" flag on properties that need
1531            *   to be loaded from the resource path, but that would require
1532            * that they can serialize both ways (custom properties are only
1533                                                 * required to generate unique strings for value comparisons).
1534                                                   */
1535           if (pspec->value_type == GDK_TYPE_PIXBUF)
1536             {
1537               GValue *value;
1538               gchar  *string;
1539 
1540               string = glade_property_make_string (property);
1541               value  = glade_property_class_make_gvalue_from_string (klass, string, project);
1542 
1543               glade_property_set_value (property, value);
1544 
1545               g_value_unset (value);
1546               g_free (value);
1547               g_free (string);
1548             }
1549         }
1550     }
1551 }
1552 
1553 void
glade_project_set_resource_path(GladeProject * project,const gchar * path)1554 glade_project_set_resource_path (GladeProject *project, const gchar *path)
1555 {
1556   g_return_if_fail (GLADE_IS_PROJECT (project));
1557 
1558   if (g_strcmp0 (project->priv->resource_path, path) != 0)
1559     {
1560       g_free (project->priv->resource_path);
1561       project->priv->resource_path = g_strdup (path);
1562 
1563       update_project_for_resource_path (project);
1564 
1565       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_RESOURCE_PATH]);
1566     }
1567 }
1568 
1569 const gchar *
glade_project_get_resource_path(GladeProject * project)1570 glade_project_get_resource_path (GladeProject *project)
1571 {
1572   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
1573 
1574   return project->priv->resource_path;
1575 }
1576 
1577 void
glade_project_set_license(GladeProject * project,const gchar * license)1578 glade_project_set_license (GladeProject *project, const gchar *license)
1579 {
1580   GladeProjectPrivate *priv;
1581 
1582   g_return_if_fail (GLADE_IS_PROJECT (project));
1583   priv = project->priv;
1584 
1585   if ((!license && priv->license) ||
1586       (license && g_strcmp0 (priv->license, license) != 0))
1587     {
1588       g_free (priv->license);
1589       priv->license = g_strdup (license);
1590       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_LICENSE]);
1591     }
1592 }
1593 
1594 const gchar *
glade_project_get_license(GladeProject * project)1595 glade_project_get_license (GladeProject *project)
1596 {
1597   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
1598 
1599   return project->priv->license;
1600 }
1601 
1602 static void
glade_project_read_comment_properties(GladeProject * project,GladeXmlNode * root_node)1603 glade_project_read_comment_properties (GladeProject *project,
1604                                        GladeXmlNode *root_node)
1605 {
1606   GladeProjectPrivate *priv = project->priv;
1607   gchar *license, *name, *description, *copyright, *authors;
1608   GladeXmlNode *node;
1609 
1610   license = name = description = copyright = authors = NULL;
1611 
1612   for (node = glade_xml_node_get_children_with_comments (root_node);
1613        node; node = glade_xml_node_next_with_comments (node))
1614     {
1615       gchar *val;
1616 
1617       if (!(val = gp_comment_get_content (node)))
1618         continue;
1619 
1620       if (gp_comment_strip_property (val, "interface-local-resource-path"))
1621         glade_project_set_resource_path (project, val);
1622       else if (gp_comment_strip_property (val, "interface-css-provider-path"))
1623         {
1624           if (g_path_is_absolute (val))
1625             glade_project_set_css_provider_path (project, val);
1626           else
1627             {
1628               gchar *dirname = g_path_get_dirname (priv->path);
1629               gchar *full_path = g_build_filename (dirname, val, NULL);
1630 
1631               glade_project_set_css_provider_path (project, full_path);
1632 
1633               g_free (dirname);
1634               g_free (full_path);
1635             }
1636         }
1637       else if (!license && (license = gp_comment_strip_property (val, "interface-license-type")))
1638         continue;
1639       else if (!name && (name = gp_comment_strip_property (val, "interface-name")))
1640         continue;
1641       else if (!description && (description = gp_comment_strip_property (val, "interface-description")))
1642         continue;
1643       else if (!copyright && (copyright = gp_comment_strip_property (val, "interface-copyright")))
1644         continue;
1645       else if (!authors && (authors = gp_comment_strip_property (val, "interface-authors")))
1646         continue;
1647 
1648       g_free (val);
1649     }
1650 
1651   _glade_project_properties_set_license_data (GLADE_PROJECT_PROPERTIES (priv->prefs_dialog),
1652                                               license,
1653                                               name,
1654                                               description,
1655                                               copyright,
1656                                               authors);
1657   g_free (license);
1658   g_free (name);
1659   g_free (description);
1660   g_free (copyright);
1661   g_free (authors);
1662 }
1663 
1664 static inline void
glade_project_read_comments(GladeProject * project,GladeXmlNode * root)1665 glade_project_read_comments (GladeProject *project, GladeXmlNode *root)
1666 {
1667   GladeProjectPrivate *priv = project->priv;
1668   GladeXmlNode *node;
1669 
1670   /* We only support comments before the root element */
1671   for (node = glade_xml_node_prev_with_comments (root); node;
1672        node = glade_xml_node_prev_with_comments (node))
1673     {
1674       if (glade_xml_node_is_comment (node))
1675         {
1676           gchar *start, *comment = glade_xml_get_content (node);
1677 
1678           /* Ignore leading spaces */
1679           for (start = comment; *start && g_ascii_isspace (*start); start++);
1680 
1681           /* Do not load generated with glade comment! */
1682           if (g_str_has_prefix (start, GLADE_XML_COMMENT))
1683             {
1684               gchar *new_line = g_strstr_len (start, -1, "\n");
1685 
1686               if (new_line)
1687                 glade_project_set_license (project, g_strstrip (new_line));
1688 
1689               g_free (comment);
1690               continue;
1691             }
1692 
1693           /* Since we are reading in backwards order,
1694            * prepending gives us the right order
1695            */
1696           priv->comments = g_list_prepend (priv->comments, comment);
1697         }
1698     }
1699 }
1700 
1701 typedef struct
1702 {
1703   GladeWidget *widget;
1704   gint major;
1705   gint minor;
1706 } VersionData;
1707 
1708 static void
glade_project_introspect_signal_versions(GladeSignal * signal,VersionData * data)1709 glade_project_introspect_signal_versions (GladeSignal *signal,
1710                                           VersionData *data)
1711 {
1712   GladeSignalClass   *signal_class;
1713   GladeWidgetAdaptor *adaptor;
1714   gchar              *catalog = NULL;
1715   gboolean            is_gtk_adaptor = FALSE;
1716 
1717   signal_class =
1718     glade_widget_adaptor_get_signal_class (glade_widget_get_adaptor (data->widget),
1719                                            glade_signal_get_name (signal));
1720 
1721   /*  unknown signal... can it happen ? */
1722   if (!signal_class)
1723     return;
1724 
1725   adaptor = glade_signal_class_get_adaptor (signal_class);
1726 
1727   /* Check if the signal comes from a GTK+ widget class */
1728   g_object_get (adaptor, "catalog", &catalog, NULL);
1729   if (strcmp (catalog, "gtk+") == 0)
1730     is_gtk_adaptor = TRUE;
1731   g_free (catalog);
1732 
1733   if (is_gtk_adaptor &&
1734       !GSC_VERSION_CHECK (signal_class, data->major, data->minor))
1735     {
1736       data->major = glade_signal_class_since_major (signal_class);
1737       data->minor = glade_signal_class_since_minor (signal_class);
1738     }
1739 }
1740 
1741 static void
glade_project_introspect_gtk_version(GladeProject * project)1742 glade_project_introspect_gtk_version (GladeProject *project)
1743 {
1744   GladeWidget *widget;
1745   GList *list, *l;
1746   gint target_major = 2, target_minor = 12;
1747 
1748   for (list = project->priv->objects; list; list = list->next)
1749     {
1750       gboolean is_gtk_adaptor = FALSE;
1751       gchar *catalog = NULL;
1752       VersionData data = { 0, };
1753       GList *signals;
1754 
1755       widget = glade_widget_get_from_gobject (list->data);
1756 
1757       /* Check if its a GTK+ widget class */
1758       g_object_get (glade_widget_get_adaptor (widget), "catalog", &catalog, NULL);
1759       if (strcmp (catalog, "gtk+") == 0)
1760         is_gtk_adaptor = TRUE;
1761       g_free (catalog);
1762 
1763       /* Check widget class version */
1764       if (is_gtk_adaptor &&
1765           !GWA_VERSION_CHECK (glade_widget_get_adaptor (widget), target_major, target_minor))
1766         {
1767           target_major = GWA_VERSION_SINCE_MAJOR (glade_widget_get_adaptor (widget));
1768           target_minor = GWA_VERSION_SINCE_MINOR (glade_widget_get_adaptor (widget));
1769         }
1770 
1771       /* Check all properties */
1772       for (l = glade_widget_get_properties (widget); l; l = l->next)
1773         {
1774           GladeProperty *property = l->data;
1775           GladePropertyClass *pclass = glade_property_get_class (property);
1776           GladeWidgetAdaptor *prop_adaptor, *adaptor;
1777           GParamSpec         *pspec;
1778 
1779           /* Unset properties ofcourse dont count... */
1780           if (glade_property_original_default (property))
1781             continue;
1782 
1783           /* Check if this property originates from a GTK+ widget class */
1784           pspec        = glade_property_class_get_pspec (pclass);
1785           prop_adaptor = glade_property_class_get_adaptor (pclass);
1786           adaptor      = glade_widget_adaptor_from_pspec (prop_adaptor, pspec);
1787 
1788           catalog = NULL;
1789           is_gtk_adaptor = FALSE;
1790           g_object_get (adaptor, "catalog", &catalog, NULL);
1791           if (strcmp (catalog, "gtk+") == 0)
1792             is_gtk_adaptor = TRUE;
1793           g_free (catalog);
1794 
1795           /* Check GTK+ property class versions */
1796           if (is_gtk_adaptor &&
1797               !GPC_VERSION_CHECK (pclass, target_major, target_minor))
1798             {
1799               target_major = glade_property_class_since_major (pclass);
1800               target_minor = glade_property_class_since_minor (pclass);
1801             }
1802         }
1803 
1804       /* Check all signal versions here */
1805       data.widget = widget;
1806       data.major = target_major;
1807       data.minor = target_minor;
1808 
1809       signals = glade_widget_get_signal_list (widget);
1810       g_list_foreach (signals, (GFunc)glade_project_introspect_signal_versions, &data);
1811       g_list_free (signals);
1812 
1813       if (target_major < data.major)
1814         target_major = data.major;
1815 
1816       if (target_minor < data.minor)
1817         target_minor = data.minor;
1818     }
1819 
1820   glade_project_set_target_version (project, "gtk+", target_major,
1821                                     target_minor);
1822 }
1823 
1824 
1825 static gint
glade_project_count_xml_objects(GladeProject * project,GladeXmlNode * root,gint count)1826 glade_project_count_xml_objects (GladeProject *project,
1827                                  GladeXmlNode *root,
1828                                  gint          count)
1829 {
1830   GladeXmlNode *node;
1831 
1832   for (node = glade_xml_node_get_children (root);
1833        node; node = glade_xml_node_next (node))
1834     {
1835       if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
1836 	  glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))
1837         count = glade_project_count_xml_objects (project, node, ++count);
1838       else if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_CHILD))
1839         count = glade_project_count_xml_objects (project, node, count);
1840     }
1841   return count;
1842 }
1843 
1844 void
glade_project_cancel_load(GladeProject * project)1845 glade_project_cancel_load (GladeProject *project)
1846 {
1847   g_return_if_fail (GLADE_IS_PROJECT (project));
1848 
1849   project->priv->load_cancel = TRUE;
1850 }
1851 
1852 gboolean
glade_project_load_cancelled(GladeProject * project)1853 glade_project_load_cancelled (GladeProject *project)
1854 {
1855   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
1856 
1857   return project->priv->load_cancel;
1858 }
1859 
1860 void
glade_project_push_progress(GladeProject * project)1861 glade_project_push_progress (GladeProject *project)
1862 {
1863   g_return_if_fail (GLADE_IS_PROJECT (project));
1864 
1865   project->priv->progress_step++;
1866 
1867   g_signal_emit (project, glade_project_signals[LOAD_PROGRESS], 0,
1868                  project->priv->progress_full, project->priv->progress_step);
1869 }
1870 
1871 
1872 /* translators: refers to project name '%s' that targets gtk version '%d.%d' */
1873 #define PROJECT_TARGET_DIALOG_TITLE_FMT _("%s targets Gtk+ %d.%d")
1874 
1875 static void
glade_project_check_target_version(GladeProject * project)1876 glade_project_check_target_version (GladeProject *project)
1877 {
1878   GladeProjectPrivate *priv;
1879   GHashTable *unknown_classes;
1880   gint unknown_objects;
1881   gint major, minor;
1882   GtkWidget *dialog;
1883   GString *text;
1884   GList *l;
1885 
1886   glade_project_get_target_version (project, "gtk+", &major, &minor);
1887 
1888   /* Glade >= 3.10 only targets Gtk 3 */
1889   if (major >= 3) return;
1890 
1891   priv = project->priv;
1892   unknown_classes = g_hash_table_new (g_str_hash, g_str_equal);
1893   unknown_objects = 0;
1894 
1895   for (l = priv->objects; l; l = g_list_next (l))
1896     {
1897       if (GLADE_IS_OBJECT_STUB (l->data))
1898         {
1899           gchar *type;
1900           g_object_get (l->data, "object-type", &type, NULL);
1901           g_hash_table_insert (unknown_classes, type, NULL);
1902           unknown_objects++;
1903         }
1904     }
1905 
1906   if (unknown_objects)
1907     {
1908       GList *classes = g_hash_table_get_keys (unknown_classes);
1909 
1910       if (unknown_objects == 1)
1911         {
1912           text = g_string_new (_("Specially because there is an object that can not be build with type "));
1913         }
1914       else
1915         {
1916           text = g_string_new ("");
1917           g_string_printf (text, _("Specially because there are %d objects that can not be build with types "),
1918                            unknown_objects);
1919         }
1920 
1921       for (l = classes; l; l = g_list_next (l))
1922         {
1923           if (g_list_previous (l))
1924             g_string_append (text, (g_list_next (l)) ? ", " : _(" and "));
1925 
1926           g_string_append (text, l->data);
1927         }
1928 
1929       g_list_free (classes);
1930     }
1931   else
1932     text = NULL;
1933 
1934   dialog = gtk_message_dialog_new (GTK_WINDOW (glade_app_get_window ()),
1935                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1936                                    GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
1937                                    PROJECT_TARGET_DIALOG_TITLE_FMT,
1938                                    glade_project_get_name (project),
1939                                    major, minor);
1940 
1941   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1942                                             _("But this version of Glade is for GTK+ 3 only.\n"
1943                                               "Make sure you can run this project with Glade 3.8 with no deprecated widgets first.\n"
1944                                               "%s"), (text) ? text->str : "");
1945 
1946   gtk_dialog_run (GTK_DIALOG (dialog));
1947   gtk_widget_destroy (dialog);
1948 
1949   glade_project_set_target_version (project, "gtk+", 3, 0);
1950 
1951   g_hash_table_destroy (unknown_classes);
1952   if (text) g_string_free (text, TRUE);
1953 }
1954 
1955 static gchar *
glade_project_autosave_name(const gchar * path)1956 glade_project_autosave_name (const gchar *path)
1957 {
1958   gchar *basename, *dirname, *autoname;
1959   gchar *autosave_name;
1960 
1961   basename = g_path_get_basename (path);
1962   dirname = g_path_get_dirname (path);
1963 
1964   autoname = g_strdup_printf ("#%s#", basename);
1965   autosave_name = g_build_filename (dirname, autoname, NULL);
1966 
1967   g_free (basename);
1968   g_free (dirname);
1969   g_free (autoname);
1970 
1971   return autosave_name;
1972 }
1973 
1974 static gboolean
glade_project_load_internal(GladeProject * project)1975 glade_project_load_internal (GladeProject *project)
1976 {
1977   GladeProjectPrivate *priv = project->priv;
1978   GladeXmlContext *context;
1979   GladeXmlDoc *doc;
1980   GladeXmlNode *root;
1981   GladeXmlNode *node;
1982   GladeWidget *widget;
1983   gboolean has_gtk_dep = FALSE;
1984   gchar *domain;
1985   gint count;
1986   gchar *autosave_path;
1987   time_t mtime, autosave_mtime;
1988   gchar *load_path = NULL;
1989 
1990   /* Check if an autosave is more recent then the specified file */
1991   autosave_path = glade_project_autosave_name (priv->path);
1992   autosave_mtime = glade_util_get_file_mtime (autosave_path, NULL);
1993   mtime = glade_util_get_file_mtime (priv->path, NULL);
1994 
1995   if (autosave_mtime > mtime)
1996     {
1997       gchar *display_name;
1998 
1999       display_name = glade_project_get_name (project);
2000 
2001       if (glade_util_ui_message (glade_app_get_window (),
2002 				 GLADE_UI_YES_OR_NO, NULL,
2003 				 _("An automatically saved version of `%s' is more recent.\n\n"
2004 				   "Would you like to load the autosave version instead?"),
2005 				 display_name))
2006 	{
2007 	  mtime = autosave_mtime;
2008 	  load_path = g_strdup (autosave_path);
2009 	}
2010       g_free (display_name);
2011     }
2012 
2013   g_free (autosave_path);
2014 
2015   priv->selection = NULL;
2016   priv->objects = NULL;
2017   priv->loading = TRUE;
2018 
2019   _glade_xml_error_reset_last ();
2020 
2021   /* get the context & root node of the catalog file */
2022   if (!(context =
2023         glade_xml_context_new_from_path (load_path ? load_path : priv->path, NULL, NULL)))
2024     {
2025       gchar *message = _glade_xml_error_get_last_message ();
2026 
2027       if (message)
2028         {
2029           glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, "%s", message);
2030           g_free (message);
2031         }
2032       else
2033         glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL,
2034                                "Couldn't open glade file [%s].",
2035                                load_path ? load_path : priv->path);
2036 
2037       g_free (load_path);
2038       priv->loading = FALSE;
2039       return FALSE;
2040     }
2041 
2042   priv->mtime = mtime;
2043 
2044   doc = glade_xml_context_get_doc (context);
2045   root = glade_xml_doc_get_root (doc);
2046 
2047   if (!glade_xml_node_verify_silent (root, GLADE_XML_TAG_PROJECT))
2048     {
2049       g_warning ("Couldnt recognize GtkBuilder xml, skipping %s",
2050                  load_path ? load_path : priv->path);
2051       glade_xml_context_free (context);
2052       g_free (load_path);
2053       priv->loading = FALSE;
2054       return FALSE;
2055     }
2056 
2057   /* Emit "parse-began" signal */
2058   g_signal_emit (project, glade_project_signals[PARSE_BEGAN], 0);
2059 
2060   if ((domain = glade_xml_get_property_string (root, GLADE_TAG_DOMAIN)))
2061     glade_project_set_translation_domain (project, domain);
2062 
2063   glade_project_read_comments (project, root);
2064 
2065   /* Read requieres, and do not abort load if there are missing catalog since
2066    * GladeObjectStub is created to keep the original xml for unknown object classes
2067    */
2068   glade_project_read_requires (project, root, load_path ? load_path : priv->path, &has_gtk_dep);
2069 
2070   /* Read the rest of properties saved as comments */
2071   glade_project_read_comment_properties (project, root);
2072 
2073   /* Launch a dialog if it's going to take enough time to be
2074    * worth showing at all */
2075   count = glade_project_count_xml_objects (project, root, 0);
2076   priv->progress_full = count;
2077   priv->progress_step = 0;
2078 
2079   for (node = glade_xml_node_get_children (root);
2080        node; node = glade_xml_node_next (node))
2081     {
2082       /* Skip "requires" tags */
2083       if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
2084 	    glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)))
2085         continue;
2086 
2087       if ((widget = glade_widget_read (project, NULL, node, NULL)) != NULL)
2088         glade_project_add_object (project, glade_widget_get_object (widget));
2089 
2090       if (priv->load_cancel)
2091         break;
2092     }
2093 
2094   /* Finished with the xml context */
2095   glade_xml_context_free (context);
2096 
2097   if (priv->load_cancel)
2098     {
2099       priv->loading = FALSE;
2100       g_free (load_path);
2101       return FALSE;
2102     }
2103 
2104   if (!has_gtk_dep)
2105     glade_project_introspect_gtk_version (project);
2106 
2107   if (glade_util_file_is_writeable (priv->path) == FALSE)
2108     glade_project_set_readonly (project, TRUE);
2109 
2110   /* Now we have to loop over all the object properties
2111    * and fix'em all ('cause they probably weren't found)
2112    */
2113   glade_project_fix_object_props (project);
2114 
2115   glade_project_fix_template (project);
2116 
2117   /* Emit "parse-finished" signal */
2118   g_signal_emit (project, glade_project_signals[PARSE_FINISHED], 0);
2119 
2120   /* Reset project status here too so that you get a clean
2121    * slate after calling glade_project_open().
2122    */
2123   priv->modified = FALSE;
2124   priv->loading = FALSE;
2125 
2126   /* Update ui with versioning info
2127    */
2128   glade_project_verify_project_for_ui (project);
2129 
2130   glade_project_check_target_version (project);
2131 
2132   return TRUE;
2133 
2134 }
2135 
2136 static void
glade_project_update_properties_title(GladeProject * project)2137 glade_project_update_properties_title (GladeProject *project)
2138 {
2139   gchar *name, *title;
2140 
2141   /* Update prefs dialogs here... */
2142   name = glade_project_get_name (project);
2143   title = g_strdup_printf (_("%s document properties"), name);
2144   gtk_window_set_title (GTK_WINDOW (project->priv->prefs_dialog), title);
2145   g_free (title);
2146   g_free (name);
2147 }
2148 
2149 gboolean
glade_project_load_from_file(GladeProject * project,const gchar * path)2150 glade_project_load_from_file (GladeProject *project, const gchar *path)
2151 {
2152   gboolean retval;
2153 
2154   g_return_val_if_fail (path != NULL, FALSE);
2155   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
2156 
2157   project->priv->path = glade_util_canonical_path (path);
2158   g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]);
2159 
2160   if ((retval = glade_project_load_internal (project)))
2161     glade_project_update_properties_title (project);
2162 
2163   return retval;
2164 }
2165 
2166 /**
2167  * glade_project_load:
2168  * @path:
2169  *
2170  * Opens a project at the given path.
2171  *
2172  * Returns: a new #GladeProject for the opened project on success, %NULL on
2173  *          failure
2174  */
2175 GladeProject *
glade_project_load(const gchar * path)2176 glade_project_load (const gchar *path)
2177 {
2178   GladeProject *project;
2179 
2180   g_return_val_if_fail (path != NULL, NULL);
2181 
2182   project = g_object_new (GLADE_TYPE_PROJECT, NULL);
2183 
2184   project->priv->path = glade_util_canonical_path (path);
2185 
2186   if (glade_project_load_internal (project))
2187     {
2188       glade_project_update_properties_title (project);
2189       return project;
2190     }
2191   else
2192     {
2193       g_object_unref (project);
2194       return NULL;
2195     }
2196 }
2197 
2198 /*******************************************************************
2199                     Writing project code here
2200  *******************************************************************/
2201 #define GLADE_PROJECT_COMMENT " "GLADE_XML_COMMENT" "PACKAGE_VERSION" "
2202 
2203 static void
glade_project_write_comment_property(GladeProject * project,GladeXmlContext * context,GladeXmlNode * root,const gchar * property,gchar * value)2204 glade_project_write_comment_property (GladeProject    *project,
2205                                       GladeXmlContext *context,
2206                                       GladeXmlNode    *root,
2207                                       const gchar     *property,
2208                                       gchar           *value)
2209 {
2210   gchar *comment, *escaped;
2211   GladeXmlNode *path_node;
2212 
2213   if (!value || *value == '\0')
2214     return;
2215 
2216   /* The string "--" (double hyphen) is not allowed in xml comments, so we replace
2217    * the regular HYPHEN with a NON-BREAKING HYPHEN which look the same but have
2218    * a different encoding.
2219    */
2220   escaped = _glade_util_strreplace (g_strescape (value, "‑"), TRUE, "--", "\\‑\\‑");
2221 
2222   comment = g_strconcat (" ", property, " ", escaped, " ", NULL);
2223   path_node = glade_xml_node_new_comment (context, comment);
2224   glade_xml_node_append_child (root, path_node);
2225 
2226   g_free (escaped);
2227   g_free (comment);
2228 }
2229 
2230 static void
glade_project_write_required_libs(GladeProject * project,GladeXmlContext * context,GladeXmlNode * root)2231 glade_project_write_required_libs (GladeProject    *project,
2232                                    GladeXmlContext *context,
2233                                    GladeXmlNode    *root)
2234 {
2235   gboolean supports_require_tag;
2236   GladeXmlNode *req_node;
2237   GList *required, *list;
2238   gint major, minor;
2239 
2240   glade_project_get_target_version (project, "gtk+", &major, &minor);
2241   supports_require_tag = GLADE_GTKBUILDER_HAS_VERSIONING (major, minor);
2242 
2243   if ((required = glade_project_required_libs (project)) != NULL)
2244     {
2245       gchar version[16];
2246 
2247       for (list = required; list; list = list->next)
2248         {
2249           gchar *library = list->data;
2250 
2251           glade_project_get_target_version (project, library, &major, &minor);
2252 
2253           g_snprintf (version, sizeof (version), "%d.%d", major, minor);
2254 
2255           /* Write the standard requires tag */
2256           if (supports_require_tag)
2257             {
2258               req_node = glade_xml_node_new (context, GLADE_XML_TAG_REQUIRES);
2259               glade_xml_node_set_property_string (req_node,
2260                                                   GLADE_XML_TAG_LIB,
2261                                                   library);
2262               glade_xml_node_set_property_string (req_node,
2263                                                   GLADE_XML_TAG_VERSION,
2264                                                   version);
2265               glade_xml_node_append_child (root, req_node);
2266             }
2267           else
2268             {
2269               gchar *value = g_strconcat (library, " ", version, NULL);
2270               glade_project_write_comment_property (project, context, root,
2271                                                     "interface-requires",
2272                                                     value);
2273               g_free (value);
2274             }
2275         }
2276       g_list_free_full (required, g_free);
2277     }
2278 }
2279 
2280 static void
glade_project_write_resource_path(GladeProject * project,GladeXmlContext * context,GladeXmlNode * root)2281 glade_project_write_resource_path (GladeProject    *project,
2282                                    GladeXmlContext *context,
2283                                    GladeXmlNode    *root)
2284 {
2285   glade_project_write_comment_property (project, context, root,
2286                                         "interface-local-resource-path",
2287                                         project->priv->resource_path);
2288 }
2289 
2290 static void
glade_project_write_css_provider_path(GladeProject * project,GladeXmlContext * context,GladeXmlNode * root)2291 glade_project_write_css_provider_path (GladeProject    *project,
2292                                        GladeXmlContext *context,
2293                                        GladeXmlNode    *root)
2294 {
2295   GladeProjectPrivate *priv = project->priv;
2296   gchar *dirname;
2297 
2298   if (priv->css_provider_path && priv->path &&
2299       (dirname = g_path_get_dirname (priv->path)))
2300     {
2301       GFile *project_path = g_file_new_for_path (dirname);
2302       GFile *file_path = g_file_new_for_path (priv->css_provider_path);
2303       gchar *css_provider_path;
2304 
2305       css_provider_path = g_file_get_relative_path (project_path, file_path);
2306 
2307       if (css_provider_path)
2308         glade_project_write_comment_property (project, context, root,
2309                                               "interface-css-provider-path",
2310                                               css_provider_path);
2311       else
2312         g_warning ("g_file_get_relative_path () return NULL");
2313 
2314       g_object_unref (project_path);
2315       g_object_unref (file_path);
2316       g_free (css_provider_path);
2317       g_free (dirname);
2318     }
2319 }
2320 
2321 static void
glade_project_write_license_data(GladeProject * project,GladeXmlContext * context,GladeXmlNode * root)2322 glade_project_write_license_data (GladeProject    *project,
2323                                   GladeXmlContext *context,
2324                                   GladeXmlNode    *root)
2325 {
2326   gchar *license, *name, *description, *copyright, *authors;
2327 
2328   _glade_project_properties_get_license_data (GLADE_PROJECT_PROPERTIES (project->priv->prefs_dialog),
2329                                               &license,
2330                                               &name,
2331                                               &description,
2332                                               &copyright,
2333                                               &authors);
2334 
2335   if (!license)
2336     return;
2337 
2338   glade_project_write_comment_property (project, context, root,
2339                                         "interface-license-type",
2340                                         license);
2341   glade_project_write_comment_property (project, context, root,
2342                                         "interface-name",
2343                                         name);
2344   glade_project_write_comment_property (project, context, root,
2345                                         "interface-description",
2346                                         description);
2347   glade_project_write_comment_property (project, context, root,
2348                                         "interface-copyright",
2349                                         copyright);
2350   glade_project_write_comment_property (project, context, root,
2351                                         "interface-authors",
2352                                         authors);
2353 
2354   g_free (license);
2355   g_free (name);
2356   g_free (description);
2357   g_free (copyright);
2358   g_free (authors);
2359 }
2360 
2361 static gint
glade_widgets_name_cmp(gconstpointer ga,gconstpointer gb)2362 glade_widgets_name_cmp (gconstpointer ga, gconstpointer gb)
2363 {
2364   return g_strcmp0 (glade_widget_get_name ((gpointer)ga),
2365                     glade_widget_get_name ((gpointer)gb));
2366 }
2367 
2368 static gint
glade_node_edge_name_cmp(gconstpointer ga,gconstpointer gb)2369 glade_node_edge_name_cmp (gconstpointer ga, gconstpointer gb)
2370 {
2371   _NodeEdge *na = (gpointer)ga, *nb = (gpointer)gb;
2372   return g_strcmp0 (glade_widget_get_name (nb->successor),
2373                     glade_widget_get_name (na->successor));
2374 }
2375 
2376 static inline gboolean
glade_project_widget_hard_depends(GladeWidget * widget,GladeWidget * another)2377 glade_project_widget_hard_depends (GladeWidget *widget, GladeWidget *another)
2378 {
2379   GList *l;
2380 
2381   for (l = _glade_widget_peek_prop_refs (another); l; l = g_list_next (l))
2382     {
2383       GladePropertyClass *klass;
2384 
2385       /* If one of the properties that reference @another is
2386        * owned by @widget then @widget depends on @another
2387        */
2388       if (glade_property_get_widget (l->data) == widget &&
2389           (klass = glade_property_get_class (l->data)) &&
2390           glade_property_class_get_construct_only (klass))
2391         return TRUE;
2392     }
2393 
2394   return FALSE;
2395 }
2396 
2397 static GList *
glade_project_get_graph_deps(GladeProject * project)2398 glade_project_get_graph_deps (GladeProject *project)
2399 {
2400   GladeProjectPrivate *priv = project->priv;
2401   GList *l, *edges = NULL;
2402 
2403   /* Create edges of the directed graph */
2404   for (l = priv->objects; l; l = g_list_next (l))
2405     {
2406       GladeWidget *predecessor = glade_widget_get_from_gobject (l->data);
2407       GladeWidget *predecessor_top;
2408       GList *ll;
2409 
2410       predecessor_top = glade_widget_get_toplevel (predecessor);
2411 
2412       /* Adds dependencies based on properties refs */
2413       for (ll = _glade_widget_peek_prop_refs (predecessor); ll; ll = g_list_next (ll))
2414         {
2415           GladeWidget *successor = glade_property_get_widget (ll->data);
2416           GladeWidget *successor_top;
2417 
2418           /* Ignore widgets that are not part of this project. (ie removed ones) */
2419           if (glade_widget_get_project (successor) != project)
2420             continue;
2421 
2422           successor_top = glade_widget_get_toplevel (successor);
2423 
2424           /* Ignore objects within the same toplevel */
2425           if (predecessor_top != successor_top)
2426             edges = _node_edge_prepend (edges, predecessor_top, successor_top);
2427         }
2428     }
2429 
2430   return edges;
2431 }
2432 
2433 static GList *
glade_project_get_nodes_from_edges(GladeProject * project,GList * edges)2434 glade_project_get_nodes_from_edges (GladeProject *project, GList *edges)
2435 {
2436   GList *l, *hard_edges = NULL;
2437   GList *cycles = NULL;
2438 
2439   /* Collect widgets with circular dependencies */
2440   for (l = edges; l; l = g_list_next (l))
2441     {
2442       _NodeEdge *edge = l->data;
2443 
2444       if (glade_widget_get_parent (edge->successor) == edge->predecessor ||
2445           glade_project_widget_hard_depends (edge->predecessor, edge->successor))
2446         hard_edges = _node_edge_prepend (hard_edges, edge->predecessor, edge->successor);
2447 
2448       /* Just toplevels */
2449       if (glade_widget_get_parent (edge->successor))
2450         continue;
2451 
2452       if (!g_list_find (cycles, edge->successor))
2453         cycles = g_list_prepend (cycles, edge->successor);
2454     }
2455 
2456   /* Sort them alphabetically */
2457   cycles = g_list_sort (cycles, glade_widgets_name_cmp);
2458 
2459   if (!hard_edges)
2460     return cycles;
2461 
2462   /* Sort them by hard deps */
2463   cycles = _glade_tsort (&cycles, &hard_edges);
2464 
2465   if (hard_edges)
2466     {
2467       GList *l, *hard_cycles = NULL;
2468 
2469       /* Collect widgets with hard circular dependencies */
2470       for (l = hard_edges; l; l = g_list_next (l))
2471         {
2472           _NodeEdge *edge = l->data;
2473 
2474           /* Just toplevels */
2475           if (glade_widget_get_parent (edge->successor))
2476             continue;
2477 
2478           if (!g_list_find (hard_cycles, edge->successor))
2479             hard_cycles = g_list_prepend (hard_cycles, edge->successor);
2480         }
2481 
2482       /* And append to the end of the cycles list */
2483       cycles = g_list_concat (cycles, g_list_sort (hard_cycles, glade_widgets_name_cmp));
2484 
2485       /* Opps! there is at least one hard circular dependency,
2486        * GtkBuilder will fail to set one of the properties that create the cycle
2487        */
2488 
2489       _node_edge_list_free (hard_edges);
2490     }
2491 
2492   return cycles;
2493 }
2494 
2495 static GList *
glade_project_add_hardcoded_dependencies(GList * edges,GladeProject * project)2496 glade_project_add_hardcoded_dependencies (GList *edges, GladeProject *project)
2497 {
2498   GList *l, *toplevels = project->priv->tree;
2499 
2500   /* Iterate over every toplevel */
2501   for (l = toplevels; l; l = g_list_next (l))
2502     {
2503       GObject *predecessor = l->data;
2504 
2505       /* Looking for a GtkIconFactory */
2506 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
2507       if (GTK_IS_ICON_FACTORY (predecessor))
2508         {
2509           GladeWidget *predecessor_top = glade_widget_get_from_gobject (predecessor);
2510           GList *ll;
2511 
2512           /* add dependency on the GtkIconFactory on every toplevel */
2513           for (ll = toplevels; ll; ll = g_list_next (ll))
2514             {
2515               GObject *successor = ll->data;
2516 
2517               /* except for GtkIconFactory */
2518               if (!GTK_IS_ICON_FACTORY (successor))
2519                 edges = _node_edge_prepend (edges, predecessor_top,
2520                                             glade_widget_get_from_gobject (successor));
2521             }
2522         }
2523 G_GNUC_END_IGNORE_DEPRECATIONS
2524     }
2525 
2526   return edges;
2527 }
2528 
2529 static GList *
glade_project_get_ordered_toplevels(GladeProject * project)2530 glade_project_get_ordered_toplevels (GladeProject *project)
2531 {
2532   GladeProjectPrivate *priv = project->priv;
2533   GList *l, *sorted_tree, *tree = NULL;
2534   GList *edges;
2535 
2536   /* Create list of toplevels GladeWidgets */
2537   for (l = priv->tree; l; l = g_list_next (l))
2538     tree = g_list_prepend (tree, glade_widget_get_from_gobject (l->data));
2539 
2540   /* Get dependency graph */
2541   edges = glade_project_get_graph_deps (project);
2542 
2543   /* Added hardcoded dependencies */
2544   edges = glade_project_add_hardcoded_dependencies (edges, project);
2545 
2546   /* Sort toplevels alphabetically */
2547   tree = g_list_sort (tree, glade_widgets_name_cmp);
2548 
2549   /* Sort dep graph alphabetically using successor name.
2550    * _glade_tsort() is a stable sort algorithm so, output of nodes without
2551    * dependency depends on the input order
2552    */
2553   edges = g_list_sort (edges, glade_node_edge_name_cmp);
2554 
2555   /* Sort tree */
2556   sorted_tree = _glade_tsort (&tree, &edges);
2557 
2558   if (edges)
2559     {
2560       GList *cycles = glade_project_get_nodes_from_edges (project, edges);
2561 
2562       /* And append to the end of the sorted list */
2563       sorted_tree = g_list_concat (sorted_tree, cycles);
2564 
2565       _node_edge_list_free (edges);
2566     }
2567 
2568   /* No need to free tree as tsort will consume the list */
2569   return sorted_tree;
2570 }
2571 
2572 static inline void
glade_project_write_comments(GladeProject * project,GladeXmlDoc * doc,GladeXmlNode * root)2573 glade_project_write_comments (GladeProject *project,
2574                               GladeXmlDoc  *doc,
2575                               GladeXmlNode *root)
2576 {
2577   GladeProjectPrivate *priv = project->priv;
2578   GladeXmlNode *comment_node, *node;
2579   GList *l;
2580 
2581   if (priv->license)
2582     {
2583       /* Replace regular HYPHEN with NON-BREAKING HYPHEN */
2584       gchar *license = _glade_util_strreplace (priv->license, FALSE, "--", "‑‑");
2585       gchar *comment = g_strdup_printf (GLADE_PROJECT_COMMENT"\n\n%s\n\n", license);
2586       comment_node = glade_xml_doc_new_comment (doc, comment);
2587       g_free (comment);
2588     }
2589   else
2590     comment_node = glade_xml_doc_new_comment (doc, GLADE_PROJECT_COMMENT);
2591 
2592   comment_node = glade_xml_node_add_prev_sibling (root, comment_node);
2593 
2594   for (l = priv->comments; l; l = g_list_next (l))
2595     {
2596       node = glade_xml_doc_new_comment (doc, l->data);
2597       comment_node = glade_xml_node_add_next_sibling (comment_node, node);
2598     }
2599 }
2600 
2601 static GladeXmlContext *
glade_project_write(GladeProject * project)2602 glade_project_write (GladeProject *project)
2603 {
2604   GladeProjectPrivate *priv = project->priv;
2605   GladeXmlContext *context;
2606   GladeXmlDoc *doc;
2607   GladeXmlNode *root;
2608   GList *list;
2609   GList *toplevels;
2610 
2611   doc = glade_xml_doc_new ();
2612   context = glade_xml_context_new (doc, NULL);
2613   root = glade_xml_node_new (context, GLADE_XML_TAG_PROJECT);
2614   glade_xml_doc_set_root (doc, root);
2615 
2616   if (priv->translation_domain)
2617     glade_xml_node_set_property_string (root, GLADE_TAG_DOMAIN, priv->translation_domain);
2618 
2619   glade_project_write_comments (project, doc, root);
2620 
2621   glade_project_write_required_libs (project, context, root);
2622 
2623   glade_project_write_resource_path (project, context, root);
2624 
2625   glade_project_write_css_provider_path (project, context, root);
2626 
2627   glade_project_write_license_data (project, context, root);
2628 
2629   /* Get sorted toplevels */
2630   toplevels = glade_project_get_ordered_toplevels (project);
2631 
2632   for (list = toplevels; list; list = g_list_next (list))
2633     {
2634       GladeWidget *widget = list->data;
2635 
2636       /*
2637        * Append toplevel widgets. Each widget then takes
2638        * care of appending its children.
2639        */
2640       if (!glade_widget_get_parent (widget))
2641         glade_widget_write (widget, context, root);
2642       else
2643         g_warning ("Tried to save a non toplevel object '%s' at xml root",
2644                    glade_widget_get_name (widget));
2645     }
2646 
2647   g_list_free (toplevels);
2648 
2649   return context;
2650 }
2651 
2652 /**
2653  * glade_project_backup:
2654  * @project: a #GladeProject
2655  * @path: location to save glade file
2656  * @error: an error from the G_FILE_ERROR domain.
2657  *
2658  * Backup the last file which @project has saved to
2659  * or was loaded from.
2660  *
2661  * If @path is not the same as the current project
2662  * path, then the current project path will be
2663  * backed up under the new location.
2664  *
2665  * If this the first save, and no persisted file
2666  * exists, then %TRUE is returned and no backup is made.
2667  *
2668  * Returns: %TRUE on success, %FALSE on failure
2669  */
2670 gboolean
glade_project_backup(GladeProject * project,const gchar * path,GError ** error)2671 glade_project_backup (GladeProject *project, const gchar *path, GError **error)
2672 {
2673   gchar *canonical_path;
2674   gchar *destination_path;
2675   gchar *content = NULL;
2676   gsize  length = 0;
2677   gboolean success;
2678 
2679   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
2680 
2681   if (project->priv->path == NULL)
2682     return TRUE;
2683 
2684   canonical_path = glade_util_canonical_path (path);
2685   destination_path = g_strconcat (canonical_path, "~", NULL);
2686   g_free (canonical_path);
2687 
2688   success = g_file_get_contents (project->priv->path, &content, &length, error);
2689   if (success)
2690     success = g_file_set_contents (destination_path, content, length, error);
2691 
2692   g_free (destination_path);
2693 
2694   return success;
2695 }
2696 
2697 /**
2698  * glade_project_autosave:
2699  * @project: a #GladeProject
2700  * @error: an error from the G_FILE_ERROR domain.
2701  *
2702  * Saves an autosave snapshot of @project to it's currently set path
2703  *
2704  * If the project was never saved, nothing is done and %TRUE is returned.
2705  *
2706  * Returns: %TRUE on success, %FALSE on failure
2707  */
2708 gboolean
glade_project_autosave(GladeProject * project,GError ** error)2709 glade_project_autosave (GladeProject *project, GError **error)
2710 {
2711 
2712   GladeXmlContext *context;
2713   GladeXmlDoc *doc;
2714   gchar *autosave_path;
2715   gint ret;
2716 
2717   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
2718 
2719   if (project->priv->path == NULL)
2720     return TRUE;
2721 
2722   autosave_path = glade_project_autosave_name (project->priv->path);
2723 
2724   context = glade_project_write (project);
2725   doc = glade_xml_context_get_doc (context);
2726   ret = glade_xml_doc_save (doc, autosave_path);
2727   glade_xml_context_destroy (context);
2728 
2729   g_free (autosave_path);
2730 
2731   return ret > 0;
2732 }
2733 
2734 static inline void
update_project_resource_path(GladeProject * project,gchar * path)2735 update_project_resource_path (GladeProject *project, gchar *path)
2736 {
2737   GFile *new_resource_path;
2738   GList *l;
2739 
2740   new_resource_path = g_file_new_for_path (path);
2741 
2742   for (l = project->priv->objects; l; l = l->next)
2743     {
2744       GladeWidget *widget = glade_widget_get_from_gobject (l->data);
2745       GList *list;
2746 
2747       for (list = glade_widget_get_properties (widget); list; list = list->next)
2748         {
2749           GladeProperty      *property = list->data;
2750           GladePropertyClass *klass = glade_property_get_class (property);
2751           GParamSpec         *pspec = glade_property_class_get_pspec (klass);
2752 
2753           if (pspec->value_type == GDK_TYPE_PIXBUF)
2754             {
2755               gchar *fullpath, *relpath;
2756               const gchar *filename;
2757               GFile *fullpath_file;
2758               GObject *pixbuf;
2759 
2760               glade_property_get (property, &pixbuf);
2761               if (pixbuf == NULL)
2762                 continue;
2763 
2764               filename = g_object_get_data (pixbuf, "GladeFileName");
2765               fullpath = glade_project_resource_fullpath (project, filename);
2766               fullpath_file = g_file_new_for_path (fullpath);
2767               relpath = _glade_util_file_get_relative_path (new_resource_path,
2768                                                             fullpath_file);
2769               g_object_set_data_full (pixbuf, "GladeFileName", relpath, g_free);
2770 
2771               g_object_unref (fullpath_file);
2772               g_free (fullpath);
2773             }
2774         }
2775     }
2776 
2777   g_object_unref (new_resource_path);
2778 }
2779 
2780 static inline void
sync_project_resource_path(GladeProject * project)2781 sync_project_resource_path (GladeProject *project)
2782 {
2783   GList *l;
2784 
2785   for (l = glade_project_selection_get (project); l; l = l->next)
2786     {
2787       GladeWidget *widget = glade_widget_get_from_gobject (l->data);
2788       GList *list;
2789 
2790       for (list = glade_widget_get_properties (widget); list; list = list->next)
2791         {
2792           GladeProperty      *property = list->data;
2793           GladePropertyClass *klass = glade_property_get_class (property);
2794           GParamSpec         *pspec = glade_property_class_get_pspec (klass);
2795 
2796           if (pspec->value_type == GDK_TYPE_PIXBUF)
2797             {
2798               const gchar *filename;
2799               GObject *pixbuf;
2800               GValue *value;
2801 
2802               glade_property_get (property, &pixbuf);
2803               if (pixbuf == NULL)
2804                 continue;
2805 
2806               filename = g_object_get_data (pixbuf, "GladeFileName");
2807               value = glade_property_class_make_gvalue_from_string (klass,
2808                                                                     filename,
2809                                                                     project);
2810               glade_property_set_value (property, value);
2811               g_value_unset (value);
2812               g_free (value);
2813             }
2814         }
2815     }
2816 }
2817 
2818 /**
2819  * glade_project_save:
2820  * @project: a #GladeProject
2821  * @path: location to save glade file
2822  * @flags: the #GladeVerifyFlags to warn about
2823  * @error: an error from the %G_FILE_ERROR domain.
2824  *
2825  * Saves @project to the given path.
2826  *
2827  * Returns: %TRUE on success, %FALSE on failure
2828  */
2829 gboolean
glade_project_save_verify(GladeProject * project,const gchar * path,GladeVerifyFlags flags,GError ** error)2830 glade_project_save_verify (GladeProject      *project,
2831 			   const gchar       *path,
2832 			   GladeVerifyFlags   flags,
2833 			   GError           **error)
2834 {
2835   GladeXmlContext *context;
2836   GladeXmlDoc *doc;
2837   gchar *canonical_path;
2838   gint ret;
2839   gchar *autosave_path;
2840 
2841   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
2842 
2843   if (glade_project_is_loading (project))
2844     return FALSE;
2845 
2846   if (!glade_project_verify (project, TRUE, flags))
2847     return FALSE;
2848 
2849   /* Delete any autosaves at this point, if they exist */
2850   if (project->priv->path)
2851     {
2852       autosave_path = glade_project_autosave_name (project->priv->path);
2853       g_unlink (autosave_path);
2854       g_free (autosave_path);
2855     }
2856 
2857   if (!project->priv->resource_path)
2858     {
2859       /* Fix pixbuf paths: Since there is no resource_path, images are relative
2860        * to path or CWD so they need to be updated to be relative to @path
2861        */
2862       gchar *dirname = g_path_get_dirname (path);
2863       update_project_resource_path (project, dirname);
2864       g_free (dirname);
2865     }
2866 
2867   /* Save the project */
2868   context = glade_project_write (project);
2869   doc = glade_xml_context_get_doc (context);
2870   ret = glade_xml_doc_save (doc, path);
2871   glade_xml_context_destroy (context);
2872 
2873   canonical_path = glade_util_canonical_path (path);
2874   g_assert (canonical_path);
2875 
2876   if (project->priv->path == NULL ||
2877       strcmp (canonical_path, project->priv->path))
2878     {
2879       project->priv->path = (g_free (project->priv->path),
2880                              g_strdup (canonical_path));
2881       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]);
2882 
2883       glade_project_update_properties_title (project);
2884 
2885       /* Sync selected objects pixbuf properties */
2886       sync_project_resource_path (project);
2887     }
2888 
2889   glade_project_set_readonly (project,
2890                               !glade_util_file_is_writeable (project->priv->
2891                                                              path));
2892 
2893   project->priv->mtime = glade_util_get_file_mtime (project->priv->path, NULL);
2894 
2895   glade_project_set_modified (project, FALSE);
2896 
2897   if (project->priv->unsaved_number > 0)
2898     {
2899       glade_id_allocator_release (get_unsaved_number_allocator (),
2900                                   project->priv->unsaved_number);
2901       project->priv->unsaved_number = 0;
2902     }
2903 
2904   g_free (canonical_path);
2905 
2906   return ret > 0;
2907 }
2908 
2909 /**
2910  * glade_project_save:
2911  * @project: a #GladeProject
2912  * @path: location to save glade file
2913  * @error: an error from the %G_FILE_ERROR domain.
2914  *
2915  * Saves @project to the given path.
2916  *
2917  * Returns: %TRUE on success, %FALSE on failure
2918  */
2919 gboolean
glade_project_save(GladeProject * project,const gchar * path,GError ** error)2920 glade_project_save (GladeProject *project, const gchar *path, GError **error)
2921 {
2922   return glade_project_save_verify (project, path,
2923 				    GLADE_VERIFY_VERSIONS |
2924 				    GLADE_VERIFY_UNRECOGNIZED,
2925 				    error);
2926 }
2927 
2928 /**
2929  * glade_project_preview:
2930  * @project: a #GladeProject
2931  * @gwidget: a #GladeWidget
2932  *
2933  * Creates and displays a preview window holding a snapshot of @gwidget's
2934  * toplevel window in @project. Note that the preview window is only a snapshot
2935  * of the current state of the project, there is no limit on how many preview
2936  * snapshots can be taken.
2937  */
2938 void
glade_project_preview(GladeProject * project,GladeWidget * gwidget)2939 glade_project_preview (GladeProject *project, GladeWidget *gwidget)
2940 {
2941   GladeXmlContext *context;
2942   gchar *text, *pidstr;
2943   GladePreview *preview = NULL;
2944 
2945   g_return_if_fail (GLADE_IS_PROJECT (project));
2946 
2947   project->priv->writing_preview = TRUE;
2948   context = glade_project_write (project);
2949   project->priv->writing_preview = FALSE;
2950 
2951   text = glade_xml_dump_from_context (context);
2952 
2953   gwidget = glade_widget_get_toplevel (gwidget);
2954   if (!GTK_IS_WIDGET (glade_widget_get_object (gwidget)))
2955     return;
2956 
2957   if ((pidstr = g_object_get_data (G_OBJECT (gwidget), "preview")) != NULL)
2958     preview = g_hash_table_lookup (project->priv->previews, pidstr);
2959 
2960   if (!preview)
2961     {
2962       /* If the previewer program is somehow missing, this can return NULL */
2963       preview = glade_preview_launch (gwidget, text);
2964       g_return_if_fail (GLADE_IS_PREVIEW (preview));
2965 
2966       /* Leave project data on the preview */
2967       g_object_set_data (G_OBJECT (preview), "project", project);
2968 
2969       /* Leave preview data on the widget */
2970       g_object_set_data_full (G_OBJECT (gwidget),
2971                               "preview",
2972                               glade_preview_get_pid_as_str (preview),
2973                               g_free);
2974 
2975       g_signal_connect (preview, "exits",
2976                         G_CALLBACK (glade_project_preview_exits),
2977                         project);
2978 
2979       /* Add preview to list of previews */
2980       g_hash_table_insert (project->priv->previews,
2981                            glade_preview_get_pid_as_str (preview),
2982                            preview);
2983     }
2984   else
2985     {
2986       glade_preview_update (preview, text);
2987     }
2988 
2989   g_free (text);
2990 }
2991 
2992 gboolean
glade_project_writing_preview(GladeProject * project)2993 glade_project_writing_preview (GladeProject       *project)
2994 {
2995   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
2996 
2997   return project->priv->writing_preview;
2998 }
2999 
3000 /*******************************************************************
3001      Verify code here (versioning, incompatability checks)
3002  *******************************************************************/
3003 
3004 /* Defined here for pretty translator comments (bug in intl tools, for some reason
3005  * you can only comment about the line directly following, forcing you to write
3006  * ugly messy code with comments in line breaks inside function calls).
3007  */
3008 
3009 /* translators: refers to a widget in toolkit version '%s %d.%d' and a project targeting toolkit version '%s %d.%d' */
3010 #define WIDGET_VERSION_CONFLICT_MSGFMT _("This widget was introduced in %s %d.%d " \
3011                                          "while project targets %s %d.%d")
3012 
3013 /* translators: refers to a widget '[%s]' introduced in toolkit version '%s %d.%d' */
3014 #define WIDGET_VERSION_CONFLICT_FMT    _("[%s] Object class '<b>%s</b>' was introduced in %s %d.%d\n")
3015 
3016 #define WIDGET_DEPRECATED_MSG          _("This widget is deprecated")
3017 
3018 /* translators: refers to a widget '[%s]' loaded from toolkit version '%s %d.%d' */
3019 #define WIDGET_DEPRECATED_FMT          _("[%s] Object class '<b>%s</b>' from %s %d.%d is deprecated\n")
3020 
3021 
3022 /* translators: refers to a property in toolkit version '%s %d.%d'
3023  * and a project targeting toolkit version '%s %d.%d' */
3024 #define PROP_VERSION_CONFLICT_MSGFMT   _("This property was introduced in %s %d.%d " \
3025                                          "while project targets %s %d.%d")
3026 
3027 /* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */
3028 #define PROP_VERSION_CONFLICT_FMT      _("[%s] Property '<b>%s</b>' of object class '<b>%s</b>' " \
3029                                          "was introduced in %s %d.%d\n")
3030 
3031 /* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */
3032 #define PACK_PROP_VERSION_CONFLICT_FMT _("[%s] Packing property '<b>%s</b>' of object class '<b>%s</b>' " \
3033                                          "was introduced in %s %d.%d\n")
3034 
3035 #define PROP_DEPRECATED_MSG            _("This property is deprecated")
3036 
3037 /* translators: refers to a property '%s' of widget '[%s]' */
3038 #define PROP_DEPRECATED_FMT            _("[%s] Property '<b>%s</b>' of object class '<b>%s</b>' is deprecated\n")
3039 
3040 /* translators: refers to a signal in toolkit version '%s %d.%d'
3041  * and a project targeting toolkit version '%s %d.%d' */
3042 #define SIGNAL_VERSION_CONFLICT_MSGFMT _("This signal was introduced in %s %d.%d " \
3043                                          "while project targets %s %d.%d")
3044 
3045 /* translators: refers to a signal '%s' of widget '[%s]' in toolkit version '%s %d.%d' */
3046 #define SIGNAL_VERSION_CONFLICT_FMT    _("[%s] Signal '<b>%s</b>' of object class '<b>%s</b>' " \
3047                                          "was introduced in %s %d.%d\n")
3048 
3049 #define SIGNAL_DEPRECATED_MSG          _("This signal is deprecated")
3050 
3051 /* translators: refers to a signal '%s' of widget '[%s]' */
3052 #define SIGNAL_DEPRECATED_FMT          _("[%s] Signal '<b>%s</b>' of object class '<b>%s</b>' is deprecated\n")
3053 
3054 
3055 static void
glade_project_verify_property_internal(GladeProject * project,GladeProperty * property,const gchar * path_name,GString * string,gboolean forwidget,GladeVerifyFlags flags)3056 glade_project_verify_property_internal (GladeProject    *project,
3057                                         GladeProperty   *property,
3058                                         const gchar     *path_name,
3059                                         GString         *string,
3060                                         gboolean         forwidget,
3061 					GladeVerifyFlags flags)
3062 {
3063   GladeWidgetAdaptor *adaptor, *prop_adaptor;
3064   GladePropertyClass *pclass;
3065   GParamSpec         *pspec;
3066   gint target_major, target_minor;
3067   gchar *catalog, *tooltip;
3068 
3069   /* For verification lists, we're only interested in verifying the 'used' state of properties.
3070    *
3071    * For the UI on the other hand, we want to show warnings on unset properties until they
3072    * are set.
3073    */
3074   if (!forwidget && (glade_property_get_state (property) & GLADE_STATE_CHANGED) == 0)
3075     return;
3076 
3077   pclass       = glade_property_get_class (property);
3078   pspec        = glade_property_class_get_pspec (pclass);
3079   prop_adaptor = glade_property_class_get_adaptor (pclass);
3080   adaptor      = glade_widget_adaptor_from_pspec (prop_adaptor, pspec);
3081 
3082   g_object_get (adaptor, "catalog", &catalog, NULL);
3083   glade_project_target_version_for_adaptor (project, adaptor,
3084                                             &target_major, &target_minor);
3085 
3086   if ((flags & GLADE_VERIFY_VERSIONS) != 0 &&
3087       !GPC_VERSION_CHECK (pclass, target_major, target_minor))
3088     {
3089       GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s not available in version %d.%d\n",
3090 				   glade_property_class_id (pclass),
3091 				   glade_widget_adaptor_get_name (adaptor),
3092 				   target_major, target_minor));
3093 
3094       if (forwidget)
3095         {
3096           tooltip = g_strdup_printf (PROP_VERSION_CONFLICT_MSGFMT,
3097                                      catalog,
3098                                      glade_property_class_since_major (pclass),
3099                                      glade_property_class_since_minor (pclass),
3100                                      catalog, target_major, target_minor);
3101 
3102           glade_property_set_support_warning (property, FALSE, tooltip);
3103           g_free (tooltip);
3104         }
3105       else
3106         g_string_append_printf (string,
3107                                 glade_property_class_get_is_packing (pclass) ?
3108                                 PACK_PROP_VERSION_CONFLICT_FMT :
3109                                   PROP_VERSION_CONFLICT_FMT,
3110                                   path_name,
3111                                   glade_property_class_get_name (pclass),
3112                                   glade_widget_adaptor_get_title (adaptor),
3113                                   catalog,
3114                                   glade_property_class_since_major (pclass),
3115                                   glade_property_class_since_minor (pclass));
3116     }
3117   else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 &&
3118 	   glade_property_class_deprecated (pclass))
3119     {
3120       GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s is deprecated\n",
3121 				   glade_property_class_id (pclass),
3122 				   glade_widget_adaptor_get_name (adaptor)));
3123 
3124       if (forwidget)
3125 	glade_property_set_support_warning (property, FALSE, PROP_DEPRECATED_MSG);
3126       else
3127         g_string_append_printf (string,
3128                                 PROP_DEPRECATED_FMT,
3129                                 path_name,
3130 				glade_property_class_get_name (pclass),
3131                                 glade_widget_adaptor_get_title (adaptor));
3132     }
3133   else if (forwidget)
3134     glade_property_set_support_warning (property, FALSE, NULL);
3135 
3136   g_free (catalog);
3137 }
3138 
3139 static void
glade_project_verify_properties_internal(GladeWidget * widget,const gchar * path_name,GString * string,gboolean forwidget,GladeVerifyFlags flags)3140 glade_project_verify_properties_internal (GladeWidget     *widget,
3141                                           const gchar     *path_name,
3142                                           GString         *string,
3143                                           gboolean         forwidget,
3144 					  GladeVerifyFlags flags)
3145 {
3146   GList *list;
3147   GladeProperty *property;
3148 
3149   for (list = glade_widget_get_properties (widget); list; list = list->next)
3150     {
3151       property = list->data;
3152       glade_project_verify_property_internal (glade_widget_get_project (widget),
3153                                               property, path_name,
3154                                               string, forwidget, flags);
3155     }
3156 
3157   /* Sometimes widgets on the clipboard have packing props with no parent */
3158   if (glade_widget_get_parent (widget))
3159     {
3160       for (list = glade_widget_get_packing_properties (widget); list; list = list->next)
3161         {
3162           property = list->data;
3163           glade_project_verify_property_internal (glade_widget_get_project (widget),
3164                                                   property, path_name, string, forwidget, flags);
3165         }
3166     }
3167 }
3168 
3169 static void
glade_project_verify_signal_internal(GladeWidget * widget,GladeSignal * signal,const gchar * path_name,GString * string,gboolean forwidget,GladeVerifyFlags flags)3170 glade_project_verify_signal_internal (GladeWidget     *widget,
3171                                       GladeSignal     *signal,
3172                                       const gchar     *path_name,
3173                                       GString         *string,
3174                                       gboolean         forwidget,
3175 				      GladeVerifyFlags flags)
3176 {
3177   GladeSignalClass   *signal_class;
3178   GladeWidgetAdaptor *adaptor;
3179   gint                target_major, target_minor;
3180   gchar              *catalog;
3181   GladeProject       *project;
3182 
3183   signal_class =
3184       glade_widget_adaptor_get_signal_class (glade_widget_get_adaptor (widget),
3185                                              glade_signal_get_name (signal));
3186 
3187   if (!signal_class)
3188     return;
3189 
3190   adaptor = glade_signal_class_get_adaptor (signal_class);
3191   project = glade_widget_get_project (widget);
3192 
3193   if (!project)
3194     return;
3195 
3196   g_object_get (adaptor, "catalog", &catalog, NULL);
3197   glade_project_target_version_for_adaptor (project, adaptor,
3198                                             &target_major, &target_minor);
3199 
3200   if ((flags & GLADE_VERIFY_VERSIONS) != 0 &&
3201       !GSC_VERSION_CHECK (signal_class, target_major, target_minor))
3202     {
3203       GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s not avalable in version %d.%d\n",
3204 				   glade_signal_get_name (signal),
3205 				   glade_widget_adaptor_get_name (adaptor),
3206 				   target_major, target_minor));
3207 
3208       if (forwidget)
3209         {
3210           gchar *warning;
3211 
3212           warning = g_strdup_printf (SIGNAL_VERSION_CONFLICT_MSGFMT,
3213                                      catalog,
3214                                      glade_signal_class_since_major (signal_class),
3215                                      glade_signal_class_since_minor (signal_class),
3216                                      catalog, target_major, target_minor);
3217           glade_signal_set_support_warning (signal, warning);
3218           g_free (warning);
3219         }
3220       else
3221         g_string_append_printf (string,
3222                                 SIGNAL_VERSION_CONFLICT_FMT,
3223                                 path_name,
3224                                 glade_signal_get_name (signal),
3225                                 glade_widget_adaptor_get_title (adaptor),
3226                                 catalog,
3227                                 glade_signal_class_since_major (signal_class),
3228                                 glade_signal_class_since_minor (signal_class));
3229     }
3230   else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 &&
3231 	   glade_signal_class_deprecated (signal_class))
3232     {
3233       GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s is deprecated\n",
3234 				   glade_signal_get_name (signal),
3235 				   glade_widget_adaptor_get_name (adaptor)));
3236 
3237       if (forwidget)
3238 	glade_signal_set_support_warning (signal, SIGNAL_DEPRECATED_MSG);
3239       else
3240         g_string_append_printf (string,
3241                                 SIGNAL_DEPRECATED_FMT,
3242                                 path_name,
3243                                 glade_signal_get_name (signal),
3244                                 glade_widget_adaptor_get_title (adaptor));
3245     }
3246   else if (forwidget)
3247     glade_signal_set_support_warning (signal, NULL);
3248 
3249   g_free (catalog);
3250 }
3251 
3252 void
glade_project_verify_property(GladeProperty * property)3253 glade_project_verify_property (GladeProperty *property)
3254 {
3255   GladeWidget  *widget;
3256   GladeProject *project;
3257 
3258   g_return_if_fail (GLADE_IS_PROPERTY (property));
3259 
3260   widget  = glade_property_get_widget (property);
3261   project = glade_widget_get_project (widget);
3262 
3263   if (project)
3264     glade_project_verify_property_internal (project, property, NULL, NULL, TRUE,
3265 					    GLADE_VERIFY_VERSIONS     |
3266 					    GLADE_VERIFY_DEPRECATIONS |
3267 					    GLADE_VERIFY_UNRECOGNIZED);
3268 }
3269 
3270 void
glade_project_verify_signal(GladeWidget * widget,GladeSignal * signal)3271 glade_project_verify_signal (GladeWidget *widget, GladeSignal *signal)
3272 {
3273   glade_project_verify_signal_internal (widget, signal, NULL, NULL, TRUE,
3274 					GLADE_VERIFY_VERSIONS     |
3275 					GLADE_VERIFY_DEPRECATIONS |
3276 					GLADE_VERIFY_UNRECOGNIZED);
3277 }
3278 
3279 static void
glade_project_verify_signals(GladeWidget * widget,const gchar * path_name,GString * string,gboolean forwidget,GladeVerifyFlags flags)3280 glade_project_verify_signals (GladeWidget     *widget,
3281                               const gchar     *path_name,
3282                               GString         *string,
3283                               gboolean         forwidget,
3284 			      GladeVerifyFlags flags)
3285 {
3286   GladeSignal *signal;
3287   GList *signals, *list;
3288 
3289   if ((signals = glade_widget_get_signal_list (widget)) != NULL)
3290     {
3291       for (list = signals; list; list = list->next)
3292         {
3293           signal = list->data;
3294           glade_project_verify_signal_internal (widget, signal, path_name,
3295                                                 string, forwidget, flags);
3296         }
3297       g_list_free (signals);
3298     }
3299 }
3300 
3301 
3302 /**
3303  * glade_project_verify_properties:
3304  * @widget: A #GladeWidget
3305  *
3306  * Synchonizes @widget with user visible information
3307  * about version compatability and notifies the UI
3308  * it should update.
3309  */
3310 static void
glade_project_verify_properties(GladeWidget * widget)3311 glade_project_verify_properties (GladeWidget *widget)
3312 {
3313   GladeProject *project;
3314 
3315   g_return_if_fail (GLADE_IS_WIDGET (widget));
3316 
3317   project = glade_widget_get_project (widget);
3318   if (!project || project->priv->loading)
3319     return;
3320 
3321   glade_project_verify_properties_internal (widget, NULL, NULL, TRUE,
3322 					    GLADE_VERIFY_VERSIONS     |
3323 					    GLADE_VERIFY_DEPRECATIONS |
3324 					    GLADE_VERIFY_UNRECOGNIZED);
3325   glade_project_verify_signals (widget, NULL, NULL, TRUE,
3326 				GLADE_VERIFY_VERSIONS     |
3327 				GLADE_VERIFY_DEPRECATIONS |
3328 				GLADE_VERIFY_UNRECOGNIZED);
3329 
3330   glade_widget_support_changed (widget);
3331 }
3332 
3333 static gboolean
glade_project_verify_dialog(GladeProject * project,GString * string,gboolean saving)3334 glade_project_verify_dialog (GladeProject *project,
3335                              GString      *string,
3336 			     gboolean      saving)
3337 {
3338   GtkWidget *swindow;
3339   GtkWidget *textview;
3340   GtkWidget *expander;
3341   GtkTextBuffer *buffer;
3342   GtkTextIter iter;
3343   gchar *name;
3344   gboolean ret;
3345 
3346   swindow = gtk_scrolled_window_new (NULL, NULL);
3347   textview = gtk_text_view_new ();
3348   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
3349   expander = gtk_expander_new (_("Details"));
3350 
3351   gtk_text_buffer_get_start_iter (buffer, &iter);
3352   gtk_text_buffer_insert_markup (buffer, &iter, string->str, -1);
3353   gtk_widget_set_vexpand (swindow, TRUE);
3354 
3355   gtk_container_add (GTK_CONTAINER (swindow), textview);
3356   gtk_container_add (GTK_CONTAINER (expander), swindow);
3357   gtk_widget_show_all (expander);
3358 
3359   gtk_widget_set_size_request (swindow, 800, -1);
3360 
3361   name = glade_project_get_name (project);
3362   ret = glade_util_ui_message (glade_app_get_window (),
3363                                saving ? GLADE_UI_YES_OR_NO : GLADE_UI_INFO,
3364                                expander,
3365                                saving ?
3366                                _("Project \"%s\" has errors. Save anyway?") :
3367                                _("Project \"%s\" has deprecated widgets "
3368                                  "and/or version mismatches."), name);
3369   g_free (name);
3370 
3371   return ret;
3372 }
3373 
3374 
3375 gboolean
glade_project_verify(GladeProject * project,gboolean saving,GladeVerifyFlags flags)3376 glade_project_verify (GladeProject    *project,
3377                       gboolean         saving,
3378                       GladeVerifyFlags flags)
3379 {
3380   GString *string = g_string_new (NULL);
3381   GList *list;
3382   gboolean ret = TRUE;
3383 
3384   GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() start\n"));
3385 
3386   if (project->priv->template)
3387     {
3388       gint major, minor;
3389       glade_project_get_target_version (project, "gtk+", &major, &minor);
3390 
3391       if (major == 3 && minor < 10)
3392         g_string_append_printf (string, _("Object %s is a class template but this is not supported in gtk+ %d.%d"),
3393                                 glade_widget_get_name (project->priv->template),
3394                                 major, minor);
3395     }
3396 
3397   for (list = project->priv->objects; list; list = list->next)
3398     {
3399       GladeWidget *widget = glade_widget_get_from_gobject (list->data);
3400 
3401       if ((flags & GLADE_VERIFY_UNRECOGNIZED) != 0 &&
3402 	  GLADE_IS_OBJECT_STUB (list->data))
3403         {
3404           gchar *type;
3405           g_object_get (list->data, "object-type", &type, NULL);
3406 
3407           g_string_append_printf (string, _("Object %s has unrecognized type %s\n"),
3408                                   glade_widget_get_name (widget), type);
3409           g_free (type);
3410         }
3411       else
3412         {
3413           gchar *path_name = glade_widget_generate_path_name (widget);
3414 
3415           glade_project_verify_adaptor (project, glade_widget_get_adaptor (widget),
3416                                         path_name, string, flags, FALSE, NULL);
3417           glade_project_verify_properties_internal (widget, path_name, string, FALSE, flags);
3418           glade_project_verify_signals (widget, path_name, string, FALSE, flags);
3419 
3420           g_free (path_name);
3421         }
3422     }
3423 
3424   if (string->len > 0)
3425     {
3426       ret = glade_project_verify_dialog (project, string, saving);
3427 
3428       if (!saving)
3429         ret = FALSE;
3430     }
3431 
3432   g_string_free (string, TRUE);
3433 
3434   GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() end\n"));
3435 
3436   return ret;
3437 }
3438 
3439 static void
glade_project_target_version_for_adaptor(GladeProject * project,GladeWidgetAdaptor * adaptor,gint * major,gint * minor)3440 glade_project_target_version_for_adaptor (GladeProject       *project,
3441                                           GladeWidgetAdaptor *adaptor,
3442                                           gint               *major,
3443                                           gint               *minor)
3444 {
3445   gchar *catalog = NULL;
3446 
3447   g_object_get (adaptor, "catalog", &catalog, NULL);
3448   glade_project_get_target_version (project, catalog, major, minor);
3449   g_free (catalog);
3450 }
3451 
3452 static void
glade_project_verify_adaptor(GladeProject * project,GladeWidgetAdaptor * adaptor,const gchar * path_name,GString * string,GladeVerifyFlags flags,gboolean forwidget,GladeSupportMask * mask)3453 glade_project_verify_adaptor (GladeProject       *project,
3454                               GladeWidgetAdaptor *adaptor,
3455                               const gchar        *path_name,
3456                               GString            *string,
3457                               GladeVerifyFlags   flags,
3458                               gboolean           forwidget,
3459                               GladeSupportMask   *mask)
3460 {
3461   GladeSupportMask support_mask = GLADE_SUPPORT_OK;
3462   GladeWidgetAdaptor *adaptor_iter;
3463   gint target_major, target_minor;
3464   gchar *catalog = NULL;
3465 
3466   for (adaptor_iter = adaptor; adaptor_iter && support_mask == GLADE_SUPPORT_OK;
3467        adaptor_iter = glade_widget_adaptor_get_parent_adaptor (adaptor_iter))
3468     {
3469 
3470       g_object_get (adaptor_iter, "catalog", &catalog, NULL);
3471       glade_project_target_version_for_adaptor (project, adaptor_iter,
3472                                                 &target_major, &target_minor);
3473 
3474       /* Only one versioning message (builder or otherwise)...
3475        */
3476       if ((flags & GLADE_VERIFY_VERSIONS) != 0 &&
3477 	  !GWA_VERSION_CHECK (adaptor_iter, target_major, target_minor))
3478         {
3479 	  GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' not available in version %d.%d\n",
3480 				       glade_widget_adaptor_get_name (adaptor_iter),
3481 				       target_major, target_minor));
3482 
3483           if (forwidget)
3484             g_string_append_printf (string,
3485                                     WIDGET_VERSION_CONFLICT_MSGFMT,
3486                                     catalog,
3487                                     GWA_VERSION_SINCE_MAJOR (adaptor_iter),
3488                                     GWA_VERSION_SINCE_MINOR (adaptor_iter),
3489                                     catalog, target_major, target_minor);
3490           else
3491             g_string_append_printf (string,
3492                                     WIDGET_VERSION_CONFLICT_FMT,
3493                                     path_name,
3494                                     glade_widget_adaptor_get_title (adaptor_iter),
3495                                     catalog,
3496                                     GWA_VERSION_SINCE_MAJOR (adaptor_iter),
3497                                     GWA_VERSION_SINCE_MINOR (adaptor_iter));
3498 
3499           support_mask |= GLADE_SUPPORT_MISMATCH;
3500         }
3501 
3502       if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 &&
3503 	  GWA_DEPRECATED (adaptor_iter))
3504         {
3505 	  GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' is deprecated\n",
3506 				       glade_widget_adaptor_get_name (adaptor_iter)));
3507 
3508           if (forwidget)
3509             {
3510               if (string->len)
3511                 g_string_append (string, "\n");
3512 
3513               g_string_append_printf (string, WIDGET_DEPRECATED_MSG);
3514             }
3515           else
3516             g_string_append_printf (string, WIDGET_DEPRECATED_FMT,
3517                                     path_name,
3518                                     glade_widget_adaptor_get_title (adaptor_iter),
3519                                     catalog, target_major, target_minor);
3520 
3521           support_mask |= GLADE_SUPPORT_DEPRECATED;
3522         }
3523       g_free (catalog);
3524     }
3525   if (mask)
3526     *mask = support_mask;
3527 
3528 }
3529 
3530 /**
3531  * glade_project_verify_widget_adaptor:
3532  * @project: A #GladeProject
3533  * @adaptor: the #GladeWidgetAdaptor to verify
3534  * @mask: a return location for a #GladeSupportMask
3535  *
3536  * Checks the supported state of this widget adaptor
3537  * and generates a string to show in the UI describing why.
3538  *
3539  * Returns: A newly allocated string
3540  */
3541 gchar *
glade_project_verify_widget_adaptor(GladeProject * project,GladeWidgetAdaptor * adaptor,GladeSupportMask * mask)3542 glade_project_verify_widget_adaptor (GladeProject       *project,
3543                                      GladeWidgetAdaptor *adaptor,
3544                                      GladeSupportMask   *mask)
3545 {
3546   GString *string = g_string_new (NULL);
3547   gchar *ret = NULL;
3548 
3549   glade_project_verify_adaptor (project, adaptor, NULL,
3550                                 string,
3551 				GLADE_VERIFY_VERSIONS     |
3552 				GLADE_VERIFY_DEPRECATIONS |
3553 				GLADE_VERIFY_UNRECOGNIZED,
3554 				TRUE, mask);
3555 
3556   /* there was a '\0' byte... */
3557   if (string->len > 0)
3558     {
3559       ret = string->str;
3560       g_string_free (string, FALSE);
3561     }
3562   else
3563     g_string_free (string, TRUE);
3564 
3565 
3566   return ret;
3567 }
3568 
3569 
3570 /**
3571  * glade_project_verify_project_for_ui:
3572  * @project: A #GladeProject
3573  *
3574  * Checks the project and updates warning strings in the UI
3575  */
3576 static void
glade_project_verify_project_for_ui(GladeProject * project)3577 glade_project_verify_project_for_ui (GladeProject *project)
3578 {
3579   GList *list;
3580   GladeWidget *widget;
3581 
3582   /* Sync displayable info here */
3583   for (list = project->priv->objects; list; list = list->next)
3584     {
3585       widget = glade_widget_get_from_gobject (list->data);
3586 
3587       /* Update the support warnings for widget's properties */
3588       glade_project_verify_properties (widget);
3589 
3590       /* Update the support warning for widget */
3591       glade_widget_verify (widget);
3592     }
3593 }
3594 
3595 /**
3596  * glade_project_get_widget_by_name:
3597  * @project: a #GladeProject
3598  * @name: The user visible name of the widget we are looking for
3599  *
3600  * Searches under @ancestor in @project looking for a #GladeWidget named @name.
3601  *
3602  * Returns: a pointer to the widget, %NULL if the widget does not exist
3603  */
3604 GladeWidget *
glade_project_get_widget_by_name(GladeProject * project,const gchar * name)3605 glade_project_get_widget_by_name (GladeProject *project, const gchar *name)
3606 {
3607   GList *list;
3608 
3609   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
3610   g_return_val_if_fail (name != NULL, NULL);
3611 
3612   for (list = project->priv->objects; list; list = list->next)
3613     {
3614       GladeWidget *widget;
3615 
3616       widget = glade_widget_get_from_gobject (list->data);
3617 
3618       if (strcmp (glade_widget_get_name (widget), name) == 0)
3619         return widget;
3620     }
3621 
3622   return NULL;
3623 }
3624 
3625 static void
glade_project_release_widget_name(GladeProject * project,GladeWidget * gwidget,const char * widget_name)3626 glade_project_release_widget_name (GladeProject *project,
3627                                    GladeWidget  *gwidget,
3628                                    const char   *widget_name)
3629 {
3630   glade_name_context_release_name (project->priv->widget_names, widget_name);
3631 }
3632 
3633 /**
3634  * glade_project_available_widget_name:
3635  * @project: a #GladeProject
3636  * @widget: the #GladeWidget intended to recieve a new name
3637  * @name: base name of the widget to create
3638  *
3639  * Checks whether @name is an appropriate name for @widget.
3640  *
3641  * Returns: whether the name is available or not.
3642  */
3643 gboolean
glade_project_available_widget_name(GladeProject * project,GladeWidget * widget,const gchar * name)3644 glade_project_available_widget_name (GladeProject *project,
3645                                      GladeWidget  *widget,
3646                                      const gchar  *name)
3647 {
3648   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
3649   g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE);
3650 
3651   if (!name || !name[0])
3652     return FALSE;
3653 
3654   return !glade_name_context_has_name (project->priv->widget_names, name);
3655 }
3656 
3657 static void
glade_project_reserve_widget_name(GladeProject * project,GladeWidget * gwidget,const char * widget_name)3658 glade_project_reserve_widget_name (GladeProject *project,
3659                                    GladeWidget  *gwidget,
3660                                    const char   *widget_name)
3661 {
3662   if (!glade_project_available_widget_name (project, gwidget, widget_name))
3663     {
3664       g_warning ("BUG: widget '%s' attempting to reserve an unavailable widget name '%s' !",
3665                  glade_widget_get_name (gwidget), widget_name);
3666       return;
3667     }
3668 
3669   /* Add to name context */
3670   glade_name_context_add_name (project->priv->widget_names, widget_name);
3671 }
3672 
3673 /**
3674  * glade_project_new_widget_name:
3675  * @project: a #GladeProject
3676  * @widget: the #GladeWidget intended to recieve a new name, or %NULL
3677  * @base_name: base name of the widget to create
3678  *
3679  * Creates a new name for a widget that doesn't collide with any of the names
3680  * already in @project. This name will start with @base_name.
3681  *
3682  * Note the @widget parameter is ignored and preserved only for historical reasons.
3683  *
3684  * Returns: a string containing the new name, %NULL if there is not enough
3685  *          memory for this string
3686  */
3687 gchar *
glade_project_new_widget_name(GladeProject * project,GladeWidget * widget,const gchar * base_name)3688 glade_project_new_widget_name (GladeProject *project,
3689                                GladeWidget  *widget,
3690                                const gchar  *base_name)
3691 {
3692   gchar *name;
3693 
3694   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
3695   g_return_val_if_fail (base_name && base_name[0], NULL);
3696 
3697   name = glade_name_context_new_name (project->priv->widget_names, base_name);
3698 
3699   return name;
3700 }
3701 
3702 static gboolean
glade_project_get_iter_for_object(GladeProject * project,GladeWidget * widget,GtkTreeIter * iter)3703 glade_project_get_iter_for_object (GladeProject *project,
3704                                    GladeWidget  *widget,
3705                                    GtkTreeIter  *iter)
3706 {
3707   GtkTreeModel *model = project->priv->model;
3708   GladeWidget *widget_iter = widget;
3709   GList *parent_node, *hierarchy = NULL;
3710   gboolean iter_valid;
3711 
3712   g_return_val_if_fail (widget, FALSE);
3713   g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE);
3714 
3715   if (!(iter_valid = gtk_tree_model_get_iter_first (model, iter)))
3716     return FALSE;
3717 
3718   /* Generate widget hierarchy list */
3719   while ((widget_iter = glade_widget_get_parent (widget_iter)))
3720     hierarchy = g_list_prepend (hierarchy, widget_iter);
3721 
3722   parent_node = hierarchy;
3723 
3724   do
3725     {
3726       gtk_tree_model_get (model, iter, 0, &widget_iter, -1);
3727 
3728       if (widget_iter == widget)
3729         {
3730           /* Object found */
3731           g_list_free (hierarchy);
3732           return TRUE;
3733         }
3734       else if (parent_node && widget_iter == parent_node->data)
3735         {
3736           GtkTreeIter child_iter;
3737 
3738           if (gtk_tree_model_iter_children (model, &child_iter, iter))
3739             {
3740               /* Parent found, adn child iter updated, continue looking */
3741               parent_node = g_list_next (parent_node);
3742               *iter = child_iter;
3743               continue;
3744             }
3745           else
3746             {
3747               /* Parent with no children? */
3748               g_warning ("Discrepancy found in TreeModel data proxy. "
3749                          "Can not get children iter for widget %s",
3750                          glade_widget_get_name (widget_iter));
3751               break;
3752             }
3753         }
3754 
3755       iter_valid = gtk_tree_model_iter_next (model, iter);
3756 
3757     } while (iter_valid);
3758 
3759   g_list_free (hierarchy);
3760   return FALSE;
3761 }
3762 
3763 /**
3764  * glade_project_set_widget_name:
3765  * @project: a #GladeProject
3766  * @widget: the #GladeWidget to set a name on
3767  * @name: the name to set.
3768  *
3769  * Sets @name on @widget in @project, if @name is not
3770  * available then a new name will be used.
3771  */
3772 void
glade_project_set_widget_name(GladeProject * project,GladeWidget * widget,const gchar * name)3773 glade_project_set_widget_name (GladeProject *project,
3774                                GladeWidget  *widget,
3775                                const gchar  *name)
3776 {
3777   gchar *new_name;
3778 
3779   g_return_if_fail (GLADE_IS_PROJECT (project));
3780   g_return_if_fail (GLADE_IS_WIDGET (widget));
3781   g_return_if_fail (name && name[0]);
3782 
3783   if (strcmp (name, glade_widget_get_name (widget)) == 0)
3784     return;
3785 
3786   /* Police the widget name */
3787   if (!glade_project_available_widget_name (project, widget, name))
3788     new_name = glade_project_new_widget_name (project, widget, name);
3789   else
3790     new_name = g_strdup (name);
3791 
3792   glade_project_reserve_widget_name (project, widget, new_name);
3793 
3794   /* Release old name and set new widget name */
3795   glade_project_release_widget_name (project, widget, glade_widget_get_name (widget));
3796   glade_widget_set_name (widget, new_name);
3797 
3798   g_signal_emit (G_OBJECT (project),
3799                  glade_project_signals[WIDGET_NAME_CHANGED], 0, widget);
3800 
3801   g_free (new_name);
3802 
3803   /* Notify views about the iter change */
3804   glade_project_widget_changed (project, widget);
3805 }
3806 
3807 void
glade_project_check_reordered(GladeProject * project,GladeWidget * parent,GList * old_order)3808 glade_project_check_reordered (GladeProject *project,
3809                                GladeWidget  *parent,
3810                                GList        *old_order)
3811 {
3812   GList *new_order, *l, *ll;
3813 
3814   g_return_if_fail (GLADE_IS_PROJECT (project));
3815   g_return_if_fail (GLADE_IS_WIDGET (parent));
3816   g_return_if_fail (glade_project_has_object (project,
3817                                               glade_widget_get_object (parent)));
3818 
3819   new_order = glade_widget_get_children (parent);
3820 
3821   /* Check if the list changed */
3822   for (l = old_order, ll = new_order;
3823        l && ll;
3824        l = g_list_next (l), ll = g_list_next (ll))
3825     {
3826       if (l->data != ll->data)
3827         break;
3828     }
3829 
3830   if (l || ll)
3831     {
3832       gint *order = g_new0 (gint, g_list_length (new_order));
3833       GtkTreeIter iter;
3834       gint i;
3835 
3836       for (i = 0, l = new_order; l; l = g_list_next (l))
3837         {
3838           GObject *obj = l->data;
3839           GList *node = g_list_find (old_order, obj);
3840 
3841           g_assert (node);
3842 
3843           order[i] = g_list_position (old_order, node);
3844           i++;
3845         }
3846 
3847       /* Signal that the rows were reordered */
3848       glade_project_get_iter_for_object (project, parent, &iter);
3849       gtk_tree_store_reorder (GTK_TREE_STORE (project->priv->model), &iter, order);
3850 
3851       g_free (order);
3852     }
3853 
3854   g_list_free (new_order);
3855 }
3856 
3857 static inline gboolean
glade_project_has_gwidget(GladeProject * project,GladeWidget * gwidget)3858 glade_project_has_gwidget (GladeProject *project, GladeWidget *gwidget)
3859 {
3860   return (glade_widget_get_project (gwidget) == project &&
3861            glade_widget_in_project (gwidget));
3862 }
3863 
3864 /**
3865  * glade_project_add_object:
3866  * @project: the #GladeProject the widget is added to
3867  * @object: the #GObject to add
3868  *
3869  * Adds an object to the project.
3870  */
3871 void
glade_project_add_object(GladeProject * project,GObject * object)3872 glade_project_add_object (GladeProject *project, GObject *object)
3873 {
3874   GladeProjectPrivate *priv;
3875   GladeWidget *gwidget;
3876   GList *list, *children;
3877   const gchar *name;
3878   GtkTreeIter iter, *parent = NULL;
3879 
3880   g_return_if_fail (GLADE_IS_PROJECT (project));
3881   g_return_if_fail (G_IS_OBJECT (object));
3882 
3883   /* We don't list placeholders */
3884   if (GLADE_IS_PLACEHOLDER (object))
3885     return;
3886 
3887   /* Only widgets accounted for in the catalog or widgets declared
3888    * in the plugin with glade_widget_new_for_internal_child () are
3889    * usefull in the project.
3890    */
3891   if ((gwidget = glade_widget_get_from_gobject (object)) == NULL)
3892     return;
3893 
3894   if (glade_project_has_gwidget (project, gwidget))
3895     {
3896       /* FIXME: It's possible we need to notify the model iface if this
3897        * happens to make sure the hierarchy is the same, I dont know, this
3898        * happens when message dialogs with children are rebuilt but the
3899        * hierarchy still looks good afterwards. */
3900       return;
3901     }
3902 
3903   priv = project->priv;
3904 
3905   name = glade_widget_get_name (gwidget);
3906   /* Make sure we have an exclusive name first... */
3907   if (!glade_project_available_widget_name (project, gwidget, name))
3908     {
3909       gchar *new_name = glade_project_new_widget_name (project, gwidget, name);
3910 
3911       /* XXX Collect these errors and make a report at startup time */
3912       if (priv->loading)
3913         g_warning ("Loading object '%s' with name conflict, renaming to '%s'",
3914                    name, new_name);
3915 
3916       glade_widget_set_name (gwidget, new_name);
3917       name = glade_widget_get_name (gwidget);
3918 
3919       g_free (new_name);
3920     }
3921 
3922   glade_project_reserve_widget_name (project, gwidget, name);
3923 
3924   glade_widget_set_project (gwidget, (gpointer) project);
3925   glade_widget_set_in_project (gwidget, TRUE);
3926   g_object_ref_sink (gwidget);
3927 
3928   /* Be sure to update the lists before emitting signals */
3929   if (glade_widget_get_parent (gwidget) == NULL)
3930     priv->tree = g_list_append (priv->tree, object);
3931   else if (glade_project_get_iter_for_object (project,
3932                                               glade_widget_get_parent (gwidget),
3933                                               &iter))
3934     {
3935       parent = &iter;
3936     }
3937 
3938   priv->objects = g_list_prepend (priv->objects, object);
3939   gtk_tree_store_insert_with_values (GTK_TREE_STORE (priv->model), NULL, parent, -1,
3940                                      0, gwidget, -1);
3941 
3942   /* NOTE: Sensitive ordering here, we need to recurse after updating
3943    * the tree model listeners (and update those listeners after our
3944    * internal lists have been resolved), otherwise children are added
3945    * before the parents (and the views dont like that).
3946    */
3947   if ((children = glade_widget_get_children (gwidget)) != NULL)
3948     {
3949       for (list = children; list && list->data; list = list->next)
3950         glade_project_add_object (project, G_OBJECT (list->data));
3951       g_list_free (children);
3952     }
3953 
3954   /* Update user visible compatibility info */
3955   glade_project_verify_properties (gwidget);
3956 
3957   g_signal_emit (G_OBJECT (project),
3958                  glade_project_signals[ADD_WIDGET], 0, gwidget);
3959 }
3960 
3961 /**
3962  * glade_project_has_object:
3963  * @project: the #GladeProject the widget is added to
3964  * @object: the #GObject to search
3965  *
3966  * Returns: whether this object is in this project.
3967  */
3968 gboolean
glade_project_has_object(GladeProject * project,GObject * object)3969 glade_project_has_object (GladeProject *project, GObject *object)
3970 {
3971   GladeWidget *gwidget;
3972 
3973   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
3974   g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
3975 
3976   gwidget = glade_widget_get_from_gobject (object);
3977 
3978   g_return_val_if_fail (GLADE_IS_WIDGET (gwidget), FALSE);
3979 
3980   return glade_project_has_gwidget (project, gwidget);
3981 }
3982 
3983 void
glade_project_widget_changed(GladeProject * project,GladeWidget * gwidget)3984 glade_project_widget_changed (GladeProject *project, GladeWidget *gwidget)
3985 {
3986   GtkTreeIter  iter;
3987   GtkTreePath *path;
3988 
3989   g_return_if_fail (GLADE_IS_PROJECT (project));
3990   g_return_if_fail (GLADE_IS_WIDGET (gwidget));
3991   g_return_if_fail (glade_project_has_gwidget (project, gwidget));
3992 
3993   glade_project_get_iter_for_object (project, gwidget, &iter);
3994   path = gtk_tree_model_get_path (project->priv->model, &iter);
3995   gtk_tree_model_row_changed (project->priv->model, path, &iter);
3996   gtk_tree_path_free (path);
3997 }
3998 
3999 /**
4000  * glade_project_remove_object:
4001  * @project: a #GladeProject
4002  * @object: the #GObject to remove
4003  *
4004  * Removes @object from @project.
4005  *
4006  * Note that when removing the #GObject from the project we
4007  * don't change ->project in the associated #GladeWidget; this
4008  * way UNDO can work.
4009  */
4010 void
glade_project_remove_object(GladeProject * project,GObject * object)4011 glade_project_remove_object (GladeProject *project, GObject *object)
4012 {
4013   GladeWidget *gwidget;
4014   GList *list, *children;
4015   gchar *preview_pid;
4016   GtkTreeIter iter;
4017 
4018   g_return_if_fail (GLADE_IS_PROJECT (project));
4019   g_return_if_fail (G_IS_OBJECT (object));
4020 
4021   if (GLADE_IS_PLACEHOLDER (object))
4022     return;
4023 
4024   if ((gwidget = glade_widget_get_from_gobject (object)) == NULL)
4025     {
4026       if (g_list_find (project->priv->objects, object))
4027         {
4028           project->priv->tree = g_list_remove_all (project->priv->tree, object);
4029           project->priv->objects = g_list_remove_all (project->priv->objects, object);
4030           project->priv->selection = g_list_remove_all (project->priv->selection, object);
4031           g_warning ("Internal data model error, removing object %p %s without a GladeWidget wrapper",
4032                      object, G_OBJECT_TYPE_NAME (object));
4033         }
4034       return;
4035     }
4036 
4037   if (!glade_project_has_object (project, object))
4038     return;
4039 
4040   /* Recurse and remove deepest children first */
4041   if ((children = glade_widget_get_children (gwidget)) != NULL)
4042     {
4043       for (list = children; list && list->data; list = list->next)
4044         glade_project_remove_object (project, G_OBJECT (list->data));
4045       g_list_free (children);
4046     }
4047 
4048   /* Remove selection and release name from the name context */
4049   glade_project_selection_remove (project, object, TRUE);
4050   glade_project_release_widget_name (project, gwidget,
4051                                      glade_widget_get_name (gwidget));
4052 
4053   g_signal_emit (G_OBJECT (project),
4054                  glade_project_signals[REMOVE_WIDGET], 0, gwidget);
4055 
4056   /* Update internal data structure (remove from lists) */
4057   project->priv->tree = g_list_remove (project->priv->tree, object);
4058   project->priv->objects = g_list_remove (project->priv->objects, object);
4059 
4060   if (glade_project_get_iter_for_object (project, gwidget, &iter))
4061     gtk_tree_store_remove (GTK_TREE_STORE (project->priv->model), &iter);
4062   else
4063     g_warning ("Internal data model error, object %p %s not found in tree model",
4064                object, G_OBJECT_TYPE_NAME (object));
4065 
4066   if ((preview_pid = g_object_get_data (G_OBJECT (gwidget), "preview")))
4067     g_hash_table_remove (project->priv->previews, preview_pid);
4068 
4069   /* Unset the project pointer on the GladeWidget */
4070   glade_widget_set_project (gwidget, NULL);
4071   glade_widget_set_in_project (gwidget, FALSE);
4072   g_object_unref (gwidget);
4073 }
4074 
4075 /*******************************************************************
4076  *                          Other API                              *
4077  *******************************************************************/
4078 /**
4079  * glade_project_set_modified:
4080  * @project: a #GladeProject
4081  * @modified: Whether the project should be set as modified or not
4082  * @modification: The first #GladeCommand which caused the project to have unsaved changes
4083  *
4084  * Set's whether a #GladeProject should be flagged as modified or not. This is useful
4085  * for indicating that a project has unsaved changes. If @modified is #TRUE, then
4086  * @modification will be recorded as the first change which caused the project to
4087  * have unsaved changes. @modified is #FALSE then @modification will be ignored.
4088  *
4089  * If @project is already flagged as modified, then calling this method with
4090  * @modified as #TRUE, will have no effect. Likewise, if @project is unmodified
4091  * then calling this method with @modified as #FALSE, will have no effect.
4092  *
4093  */
4094 static void
glade_project_set_modified(GladeProject * project,gboolean modified)4095 glade_project_set_modified (GladeProject *project, gboolean modified)
4096 {
4097   GladeProjectPrivate *priv;
4098 
4099   g_return_if_fail (GLADE_IS_PROJECT (project));
4100 
4101   priv = project->priv;
4102 
4103   if (priv->modified != modified)
4104     {
4105       priv->modified = !priv->modified;
4106 
4107       if (!priv->modified)
4108         {
4109           priv->first_modification = project->priv->prev_redo_item;
4110           priv->first_modification_is_na = FALSE;
4111         }
4112 
4113       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_MODIFIED]);
4114     }
4115 }
4116 
4117 /**
4118  * glade_project_get_modified:
4119  * @project: a #GladeProject
4120  *
4121  * Get's whether the project has been modified since it was last saved.
4122  *
4123  * Returns: %TRUE if the project has been modified since it was last saved
4124  */
4125 gboolean
glade_project_get_modified(GladeProject * project)4126 glade_project_get_modified (GladeProject *project)
4127 {
4128   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4129 
4130   return project->priv->modified;
4131 }
4132 
4133 void
glade_project_set_pointer_mode(GladeProject * project,GladePointerMode mode)4134 glade_project_set_pointer_mode (GladeProject *project, GladePointerMode mode)
4135 {
4136   g_return_if_fail (GLADE_IS_PROJECT (project));
4137 
4138   if (project->priv->pointer_mode != mode)
4139     {
4140       project->priv->pointer_mode = mode;
4141 
4142       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_POINTER_MODE]);
4143     }
4144 }
4145 
4146 GladePointerMode
glade_project_get_pointer_mode(GladeProject * project)4147 glade_project_get_pointer_mode (GladeProject *project)
4148 {
4149   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4150 
4151   return project->priv->pointer_mode;
4152 }
4153 
4154 void
glade_project_set_template(GladeProject * project,GladeWidget * widget)4155 glade_project_set_template (GladeProject *project, GladeWidget *widget)
4156 {
4157   g_return_if_fail (GLADE_IS_PROJECT (project));
4158   g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget));
4159 
4160   if (widget)
4161     {
4162       GObject *object = glade_widget_get_object (widget);
4163 
4164       g_return_if_fail (GTK_IS_WIDGET (object));
4165       g_return_if_fail (glade_widget_get_parent (widget) == NULL);
4166       g_return_if_fail (glade_widget_get_project (widget) == project);
4167     }
4168 
4169   /* Let's not add any strong reference here, we already own the widget */
4170   if (project->priv->template != widget)
4171     {
4172       if (project->priv->template)
4173 	glade_widget_set_is_composite (project->priv->template, FALSE);
4174 
4175       project->priv->template = widget;
4176 
4177       if (project->priv->template)
4178 	glade_widget_set_is_composite (project->priv->template, TRUE);
4179 
4180       glade_project_verify_project_for_ui (project);
4181 
4182       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_TEMPLATE]);
4183     }
4184 }
4185 
4186 GladeWidget *
glade_project_get_template(GladeProject * project)4187 glade_project_get_template (GladeProject *project)
4188 {
4189   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4190 
4191   return project->priv->template;
4192 }
4193 
4194 void
glade_project_set_add_item(GladeProject * project,GladeWidgetAdaptor * adaptor)4195 glade_project_set_add_item (GladeProject *project, GladeWidgetAdaptor *adaptor)
4196 {
4197   GladeProjectPrivate *priv;
4198 
4199   g_return_if_fail (GLADE_IS_PROJECT (project));
4200 
4201   priv = project->priv;
4202 
4203   if (priv->add_item != adaptor)
4204     {
4205       priv->add_item = adaptor;
4206 
4207       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_ADD_ITEM]);
4208     }
4209 }
4210 
4211 GladeWidgetAdaptor *
glade_project_get_add_item(GladeProject * project)4212 glade_project_get_add_item (GladeProject *project)
4213 {
4214   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4215 
4216   return project->priv->add_item;
4217 }
4218 
4219 void
glade_project_set_target_version(GladeProject * project,const gchar * catalog,gint major,gint minor)4220 glade_project_set_target_version (GladeProject *project,
4221                                   const gchar  *catalog,
4222                                   gint          major,
4223                                   gint          minor)
4224 {
4225   g_return_if_fail (GLADE_IS_PROJECT (project));
4226   g_return_if_fail (catalog && catalog[0]);
4227   g_return_if_fail (major >= 0);
4228   g_return_if_fail (minor >= 0);
4229 
4230   g_hash_table_insert (project->priv->target_versions_major,
4231                        g_strdup (catalog), GINT_TO_POINTER ((int) major));
4232   g_hash_table_insert (project->priv->target_versions_minor,
4233                        g_strdup (catalog), GINT_TO_POINTER ((int) minor));
4234 
4235   glade_project_verify_project_for_ui (project);
4236 
4237   g_signal_emit (project, glade_project_signals[TARGETS_CHANGED], 0);
4238 }
4239 
4240 static void
glade_project_set_readonly(GladeProject * project,gboolean readonly)4241 glade_project_set_readonly (GladeProject *project, gboolean readonly)
4242 {
4243   g_assert (GLADE_IS_PROJECT (project));
4244 
4245   if (project->priv->readonly != readonly)
4246     {
4247       project->priv->readonly = readonly;
4248       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_READ_ONLY]);
4249     }
4250 }
4251 
4252 /**
4253  * glade_project_get_target_version:
4254  * @project: a #GladeProject
4255  * @catalog: the name of the catalog @project includes
4256  * @major: the return location for the target major version
4257  * @minor: the return location for the target minor version
4258  *
4259  * Fetches the target version of the @project for @catalog.
4260  *
4261  */
4262 void
glade_project_get_target_version(GladeProject * project,const gchar * catalog,gint * major,gint * minor)4263 glade_project_get_target_version (GladeProject *project,
4264                                   const gchar  *catalog,
4265                                   gint         *major,
4266                                   gint         *minor)
4267 {
4268   g_return_if_fail (GLADE_IS_PROJECT (project));
4269   g_return_if_fail (catalog && catalog[0]);
4270   g_return_if_fail (major && minor);
4271 
4272   *major = GPOINTER_TO_INT
4273       (g_hash_table_lookup (project->priv->target_versions_major, catalog));
4274   *minor = GPOINTER_TO_INT
4275       (g_hash_table_lookup (project->priv->target_versions_minor, catalog));
4276 }
4277 
4278 /**
4279  * glade_project_get_readonly:
4280  * @project: a #GladeProject
4281  *
4282  * Gets whether the project is read only or not
4283  *
4284  * Returns: TRUE if project is read only
4285  */
4286 gboolean
glade_project_get_readonly(GladeProject * project)4287 glade_project_get_readonly (GladeProject *project)
4288 {
4289   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4290 
4291   return project->priv->readonly;
4292 }
4293 
4294 /**
4295  * glade_project_selection_changed:
4296  * @project: a #GladeProject
4297  *
4298  * Causes @project to emit a "selection_changed" signal.
4299  */
4300 void
glade_project_selection_changed(GladeProject * project)4301 glade_project_selection_changed (GladeProject *project)
4302 {
4303   g_return_if_fail (GLADE_IS_PROJECT (project));
4304 
4305   g_signal_emit (G_OBJECT (project),
4306                  glade_project_signals[SELECTION_CHANGED], 0);
4307 
4308   /* Cancel any idle we have */
4309   if (project->priv->selection_changed_id > 0)
4310     project->priv->selection_changed_id =
4311       (g_source_remove (project->priv->selection_changed_id), 0);
4312 }
4313 
4314 static gboolean
selection_change_idle(GladeProject * project)4315 selection_change_idle (GladeProject *project)
4316 {
4317   project->priv->selection_changed_id = 0;
4318   glade_project_selection_changed (project);
4319   return FALSE;
4320 }
4321 
4322 void
glade_project_queue_selection_changed(GladeProject * project)4323 glade_project_queue_selection_changed (GladeProject *project)
4324 {
4325   g_return_if_fail (GLADE_IS_PROJECT (project));
4326 
4327   if (project->priv->selection_changed_id == 0)
4328     project->priv->selection_changed_id =
4329       g_idle_add ((GSourceFunc) selection_change_idle, project);
4330 }
4331 
4332 static void
glade_project_set_has_selection(GladeProject * project,gboolean has_selection)4333 glade_project_set_has_selection (GladeProject *project, gboolean has_selection)
4334 {
4335   g_assert (GLADE_IS_PROJECT (project));
4336 
4337   if (project->priv->has_selection != has_selection)
4338     {
4339       project->priv->has_selection = has_selection;
4340       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_HAS_SELECTION]);
4341     }
4342 }
4343 
4344 /**
4345  * glade_project_get_has_selection:
4346  * @project: a #GladeProject
4347  *
4348  * Returns: whether @project currently has a selection
4349  */
4350 gboolean
glade_project_get_has_selection(GladeProject * project)4351 glade_project_get_has_selection (GladeProject *project)
4352 {
4353   g_assert (GLADE_IS_PROJECT (project));
4354 
4355   return project->priv->has_selection;
4356 }
4357 
4358 /**
4359  * glade_project_is_selected:
4360  * @project: a #GladeProject
4361  * @object: a #GObject
4362  *
4363  * Returns: whether @object is in @project selection
4364  */
4365 gboolean
glade_project_is_selected(GladeProject * project,GObject * object)4366 glade_project_is_selected (GladeProject *project, GObject *object)
4367 {
4368   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4369   return (g_list_find (project->priv->selection, object)) != NULL;
4370 }
4371 
4372 /**
4373  * glade_project_selection_clear:
4374  * @project: a #GladeProject
4375  * @emit_signal: whether or not to emit a signal indication a selection change
4376  *
4377  * Clears @project's selection chain
4378  *
4379  * If @emit_signal is %TRUE, calls glade_project_selection_changed().
4380  */
4381 void
glade_project_selection_clear(GladeProject * project,gboolean emit_signal)4382 glade_project_selection_clear (GladeProject *project, gboolean emit_signal)
4383 {
4384   GList *l;
4385 
4386   g_return_if_fail (GLADE_IS_PROJECT (project));
4387 
4388   if (project->priv->selection == NULL)
4389     return;
4390 
4391   for (l = project->priv->selection; l; l = l->next)
4392     {
4393       if (GTK_IS_WIDGET (l->data))
4394         gtk_widget_queue_draw (GTK_WIDGET (l->data));
4395     }
4396 
4397   g_list_free (project->priv->selection);
4398   project->priv->selection = NULL;
4399   glade_project_set_has_selection (project, FALSE);
4400 
4401   if (emit_signal)
4402     glade_project_selection_changed (project);
4403 }
4404 
4405 /**
4406  * glade_project_selection_remove:
4407  * @project: a #GladeProject
4408  * @object:  a #GObject in @project
4409  * @emit_signal: whether or not to emit a signal
4410  *               indicating a selection change
4411  *
4412  * Removes @object from the selection chain of @project
4413  *
4414  * If @emit_signal is %TRUE, calls glade_project_selection_changed().
4415  */
4416 void
glade_project_selection_remove(GladeProject * project,GObject * object,gboolean emit_signal)4417 glade_project_selection_remove (GladeProject *project,
4418                                 GObject      *object,
4419                                 gboolean      emit_signal)
4420 {
4421   g_return_if_fail (GLADE_IS_PROJECT (project));
4422   g_return_if_fail (G_IS_OBJECT (object));
4423 
4424   if (glade_project_is_selected (project, object))
4425     {
4426       project->priv->selection =
4427           g_list_remove (project->priv->selection, object);
4428       if (project->priv->selection == NULL)
4429         glade_project_set_has_selection (project, FALSE);
4430       if (emit_signal)
4431         glade_project_selection_changed (project);
4432     }
4433 }
4434 
4435 /**
4436  * glade_project_selection_add:
4437  * @project: a #GladeProject
4438  * @object:  a #GObject in @project
4439  * @emit_signal: whether or not to emit a signal indicating
4440  *               a selection change
4441  *
4442  * Adds @object to the selection chain of @project
4443  *
4444  * If @emit_signal is %TRUE, calls glade_project_selection_changed().
4445  */
4446 void
glade_project_selection_add(GladeProject * project,GObject * object,gboolean emit_signal)4447 glade_project_selection_add (GladeProject *project,
4448                              GObject      *object,
4449                              gboolean      emit_signal)
4450 {
4451   g_return_if_fail (GLADE_IS_PROJECT (project));
4452   g_return_if_fail (G_IS_OBJECT (object));
4453   g_return_if_fail (glade_project_has_object (project, object));
4454 
4455   if (glade_project_is_selected (project, object) == FALSE)
4456     {
4457       gboolean toggle_has_selection = (project->priv->selection == NULL);
4458 
4459       if (GTK_IS_WIDGET (object))
4460         gtk_widget_queue_draw (GTK_WIDGET (object));
4461 
4462       project->priv->selection =
4463         g_list_prepend (project->priv->selection, object);
4464 
4465       if (toggle_has_selection)
4466         glade_project_set_has_selection (project, TRUE);
4467 
4468       if (emit_signal)
4469         glade_project_selection_changed (project);
4470     }
4471 }
4472 
4473 /**
4474  * glade_project_selection_set:
4475  * @project: a #GladeProject
4476  * @object:  a #GObject in @project
4477  * @emit_signal: whether or not to emit a signal
4478  *               indicating a selection change
4479  *
4480  * Set the selection in @project to @object
4481  *
4482  * If @emit_signal is %TRUE, calls glade_project_selection_changed().
4483  */
4484 void
glade_project_selection_set(GladeProject * project,GObject * object,gboolean emit_signal)4485 glade_project_selection_set (GladeProject *project,
4486                              GObject      *object,
4487                              gboolean      emit_signal)
4488 {
4489   g_return_if_fail (GLADE_IS_PROJECT (project));
4490   g_return_if_fail (G_IS_OBJECT (object));
4491   g_return_if_fail (glade_project_has_object (project, object));
4492 
4493   if (glade_project_is_selected (project, object) == FALSE ||
4494       g_list_length (project->priv->selection) != 1)
4495     {
4496       glade_project_selection_clear (project, FALSE);
4497       glade_project_selection_add (project, object, emit_signal);
4498     }
4499 }
4500 
4501 /**
4502  * glade_project_selection_get:
4503  * @project: a #GladeProject
4504  *
4505  * Returns: a #GList containing the #GtkWidget items currently selected in @project
4506  */
4507 GList *
glade_project_selection_get(GladeProject * project)4508 glade_project_selection_get (GladeProject *project)
4509 {
4510   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4511 
4512   return project->priv->selection;
4513 }
4514 
4515 /**
4516  * glade_project_required_libs:
4517  * @project: a #GladeProject
4518  *
4519  * Returns: a #GList of allocated strings which are the names
4520  * of the required catalogs for this project
4521  */
4522 GList *
glade_project_required_libs(GladeProject * project)4523 glade_project_required_libs (GladeProject *project)
4524 {
4525   GList *l, *required = NULL;
4526 
4527   /* Assume GTK+ catalog here */
4528   required = g_list_prepend (required, _glade_catalog_get_catalog ("gtk+"));
4529 
4530   for (l = project->priv->objects; l; l = g_list_next (l))
4531     {
4532       GladeWidget *gwidget = glade_widget_get_from_gobject (l->data);
4533       GladeCatalog *catalog;
4534       gchar *name = NULL;
4535 
4536       g_assert (gwidget);
4537 
4538       g_object_get (glade_widget_get_adaptor (gwidget), "catalog", &name, NULL);
4539 
4540       if ((catalog = _glade_catalog_get_catalog (name)))
4541         {
4542           if (!g_list_find (required, catalog))
4543             required = g_list_prepend (required, catalog);
4544         }
4545 
4546       g_free (name);
4547     }
4548 
4549   /* Sort by dependency */
4550   required = _glade_catalog_tsort (required);
4551 
4552   /* Convert list of GladeCatalog to list of names */
4553   for (l = required; l; l = g_list_next (l))
4554     l->data = g_strdup (glade_catalog_get_name (l->data));
4555 
4556   for (l = project->priv->unknown_catalogs; l; l = g_list_next (l))
4557     {
4558       CatalogInfo *data = l->data;
4559       /* Keep position to make sure we do not create a diff when saving */
4560       required = g_list_insert (required, g_strdup (data->catalog), data->position);
4561     }
4562 
4563   return required;
4564 }
4565 
4566 /**
4567  * glade_project_undo:
4568  * @project: a #GladeProject
4569  *
4570  * Undoes a #GladeCommand in this project.
4571  */
4572 void
glade_project_undo(GladeProject * project)4573 glade_project_undo (GladeProject *project)
4574 {
4575   g_return_if_fail (GLADE_IS_PROJECT (project));
4576   GLADE_PROJECT_GET_CLASS (project)->undo (project);
4577 }
4578 
4579 /**
4580  * glade_project_undo:
4581  * @project: a #GladeProject
4582  *
4583  * Redoes a #GladeCommand in this project.
4584  */
4585 void
glade_project_redo(GladeProject * project)4586 glade_project_redo (GladeProject *project)
4587 {
4588   g_return_if_fail (GLADE_IS_PROJECT (project));
4589   GLADE_PROJECT_GET_CLASS (project)->redo (project);
4590 }
4591 
4592 /**
4593  * glade_project_next_undo_item:
4594  * @project: a #GladeProject
4595  *
4596  * Gets the next undo item on @project's command stack.
4597  *
4598  * Returns: the #GladeCommand
4599  */
4600 GladeCommand *
glade_project_next_undo_item(GladeProject * project)4601 glade_project_next_undo_item (GladeProject *project)
4602 {
4603   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4604   return GLADE_PROJECT_GET_CLASS (project)->next_undo_item (project);
4605 }
4606 
4607 /**
4608  * glade_project_next_redo_item:
4609  * @project: a #GladeProject
4610  *
4611  * Gets the next redo item on @project's command stack.
4612  *
4613  * Returns: the #GladeCommand
4614  */
4615 GladeCommand *
glade_project_next_redo_item(GladeProject * project)4616 glade_project_next_redo_item (GladeProject *project)
4617 {
4618   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4619   return GLADE_PROJECT_GET_CLASS (project)->next_redo_item (project);
4620 }
4621 
4622 /**
4623  * glade_project_push_undo:
4624  * @project: a #GladeProject
4625  * @cmd: the #GladeCommand
4626  *
4627  * Pushes a newly created #GladeCommand onto @projects stack.
4628  */
4629 void
glade_project_push_undo(GladeProject * project,GladeCommand * cmd)4630 glade_project_push_undo (GladeProject *project, GladeCommand *cmd)
4631 {
4632   g_return_if_fail (GLADE_IS_PROJECT (project));
4633   g_return_if_fail (GLADE_IS_COMMAND (cmd));
4634 
4635   GLADE_PROJECT_GET_CLASS (project)->push_undo (project, cmd);
4636 }
4637 
4638 static GList *
walk_command(GList * list,gboolean forward)4639 walk_command (GList *list, gboolean forward)
4640 {
4641   GladeCommand *cmd = list->data;
4642   GladeCommand *next_cmd;
4643 
4644   if (forward)
4645     list = list->next;
4646   else
4647     list = list->prev;
4648 
4649   next_cmd = list ? list->data : NULL;
4650 
4651   while (list &&
4652    glade_command_group_id (next_cmd) != 0 &&
4653    glade_command_group_id (next_cmd) == glade_command_group_id (cmd))
4654     {
4655       if (forward)
4656         list = list->next;
4657       else
4658         list = list->prev;
4659 
4660       if (list)
4661         next_cmd = list->data;
4662     }
4663 
4664   return list;
4665 }
4666 
4667 static void
undo_item_activated(GtkMenuItem * item,GladeProject * project)4668 undo_item_activated (GtkMenuItem *item, GladeProject *project)
4669 {
4670   gint index, next_index;
4671 
4672   GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data");
4673   GladeCommand *next_cmd;
4674 
4675   index = g_list_index (project->priv->undo_stack, cmd);
4676 
4677   do
4678     {
4679       next_cmd = glade_project_next_undo_item (project);
4680       next_index = g_list_index (project->priv->undo_stack, next_cmd);
4681 
4682       glade_project_undo (project);
4683 
4684     }
4685   while (next_index > index);
4686 }
4687 
4688 static void
redo_item_activated(GtkMenuItem * item,GladeProject * project)4689 redo_item_activated (GtkMenuItem *item, GladeProject *project)
4690 {
4691   gint index, next_index;
4692 
4693   GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data");
4694   GladeCommand *next_cmd;
4695 
4696   index = g_list_index (project->priv->undo_stack, cmd);
4697 
4698   do
4699     {
4700       next_cmd = glade_project_next_redo_item (project);
4701       next_index = g_list_index (project->priv->undo_stack, next_cmd);
4702 
4703       glade_project_redo (project);
4704 
4705     }
4706   while (next_index < index);
4707 }
4708 
4709 
4710 /**
4711  * glade_project_undo_items:
4712  * @project: A #GladeProject
4713  *
4714  * Creates a menu of the undo items in the project stack
4715  *
4716  * Returns: A newly created menu
4717  */
4718 GtkWidget *
glade_project_undo_items(GladeProject * project)4719 glade_project_undo_items (GladeProject *project)
4720 {
4721   GtkWidget *menu = NULL;
4722   GtkWidget *item;
4723   GladeCommand *cmd;
4724   GList *l;
4725 
4726   g_return_val_if_fail (project != NULL, NULL);
4727 
4728   for (l = project->priv->prev_redo_item; l; l = walk_command (l, FALSE))
4729     {
4730       cmd = l->data;
4731 
4732       if (!menu)
4733         menu = gtk_menu_new ();
4734 
4735       item = gtk_menu_item_new_with_label (glade_command_description (cmd));
4736       gtk_widget_show (item);
4737       gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item));
4738       g_object_set_data (G_OBJECT (item), "command-data", cmd);
4739 
4740       g_signal_connect (G_OBJECT (item), "activate",
4741                         G_CALLBACK (undo_item_activated), project);
4742 
4743     }
4744 
4745   return menu;
4746 }
4747 
4748 /**
4749  * glade_project_redo_items:
4750  * @project: A #GladeProject
4751  *
4752  * Creates a menu of the undo items in the project stack
4753  *
4754  * Returns: A newly created menu
4755  */
4756 GtkWidget *
glade_project_redo_items(GladeProject * project)4757 glade_project_redo_items (GladeProject *project)
4758 {
4759   GtkWidget *menu = NULL;
4760   GtkWidget *item;
4761   GladeCommand *cmd;
4762   GList *l;
4763 
4764   g_return_val_if_fail (project != NULL, NULL);
4765 
4766   for (l = project->priv->prev_redo_item ?
4767        project->priv->prev_redo_item->next :
4768        project->priv->undo_stack; l; l = walk_command (l, TRUE))
4769     {
4770       cmd = l->data;
4771 
4772       if (!menu)
4773         menu = gtk_menu_new ();
4774 
4775       item = gtk_menu_item_new_with_label (glade_command_description (cmd));
4776       gtk_widget_show (item);
4777       gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item));
4778       g_object_set_data (G_OBJECT (item), "command-data", cmd);
4779 
4780       g_signal_connect (G_OBJECT (item), "activate",
4781                         G_CALLBACK (redo_item_activated), project);
4782 
4783     }
4784 
4785   return menu;
4786 }
4787 
4788 void
glade_project_reset_path(GladeProject * project)4789 glade_project_reset_path (GladeProject *project)
4790 {
4791   g_return_if_fail (GLADE_IS_PROJECT (project));
4792   project->priv->path = (g_free (project->priv->path), NULL);
4793 }
4794 
4795 /**
4796  * glade_project_resource_fullpath:
4797  * @project: The #GladeProject.
4798  * @resource: The resource basename
4799  *
4800  * Project resource strings are always relative, this function tranforms a
4801  * path relative to project to a full path.
4802  *
4803  * Returns: A newly allocated string holding the
4804  *          full path to the resource.
4805  */
4806 gchar *
glade_project_resource_fullpath(GladeProject * project,const gchar * resource)4807 glade_project_resource_fullpath (GladeProject *project, const gchar *resource)
4808 {
4809   gchar *fullpath, *project_dir = NULL;
4810 
4811   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4812 
4813   if (project->priv->path == NULL)
4814     project_dir = g_get_current_dir ();
4815   else
4816     project_dir = g_path_get_dirname (project->priv->path);
4817 
4818   if (project->priv->resource_path)
4819     {
4820       if (g_path_is_absolute (project->priv->resource_path))
4821         fullpath =
4822             g_build_filename (project->priv->resource_path, resource, NULL);
4823       else
4824         fullpath =
4825             g_build_filename (project_dir, project->priv->resource_path,
4826                               resource, NULL);
4827     }
4828   else
4829     fullpath = g_build_filename (project_dir, resource, NULL);
4830 
4831   g_free (project_dir);
4832   return fullpath;
4833 }
4834 
4835 /**
4836  * glade_project_widget_visibility_changed:
4837  * @project: The #GladeProject.
4838  * @widget: The widget which visibility changed
4839  * @visible: widget visibility value
4840  *
4841  * Emmits  GladeProject::widget-visibility-changed signal
4842  *
4843  */
4844 void
glade_project_widget_visibility_changed(GladeProject * project,GladeWidget * widget,gboolean visible)4845 glade_project_widget_visibility_changed (GladeProject  *project,
4846                                          GladeWidget   *widget,
4847                                          gboolean       visible)
4848 {
4849   g_return_if_fail (GLADE_IS_PROJECT (project));
4850   g_return_if_fail (project == glade_widget_get_project (widget));
4851 
4852   g_signal_emit (project, glade_project_signals[WIDGET_VISIBILITY_CHANGED], 0,
4853                  widget, visible);
4854 }
4855 
4856 const gchar *
glade_project_get_path(GladeProject * project)4857 glade_project_get_path (GladeProject *project)
4858 {
4859   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4860 
4861   return project->priv->path;
4862 }
4863 
4864 gchar *
glade_project_get_name(GladeProject * project)4865 glade_project_get_name (GladeProject *project)
4866 {
4867   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4868 
4869   if (project->priv->path)
4870     return g_filename_display_basename (project->priv->path);
4871   else
4872     return g_strdup_printf (_("Unsaved %i"), project->priv->unsaved_number);
4873 }
4874 
4875 /**
4876  * glade_project_is_loading:
4877  * @project: A #GladeProject
4878  *
4879  * Returns: Whether the project is being loaded or not
4880  *
4881  */
4882 gboolean
glade_project_is_loading(GladeProject * project)4883 glade_project_is_loading (GladeProject *project)
4884 {
4885   g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
4886 
4887   return project->priv->loading;
4888 }
4889 
4890 time_t
glade_project_get_file_mtime(GladeProject * project)4891 glade_project_get_file_mtime (GladeProject *project)
4892 {
4893   g_return_val_if_fail (GLADE_IS_PROJECT (project), 0);
4894 
4895   return project->priv->mtime;
4896 }
4897 
4898 /**
4899  * glade_projects_get_objects:
4900  * @project: a GladeProject
4901  *
4902  * Returns: List of all objects in this project
4903  */
4904 const GList *
glade_project_get_objects(GladeProject * project)4905 glade_project_get_objects (GladeProject *project)
4906 {
4907   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4908 
4909   return project->priv->objects;
4910 }
4911 
4912 /**
4913  * glade_project_properties:
4914  * @project: A #GladeProject
4915  *
4916  * Runs a document properties dialog for @project.
4917  */
4918 void
glade_project_properties(GladeProject * project)4919 glade_project_properties (GladeProject *project)
4920 {
4921   g_return_if_fail (GLADE_IS_PROJECT (project));
4922 
4923   gtk_window_present (GTK_WINDOW (project->priv->prefs_dialog));
4924 }
4925 
4926 gchar *
glade_project_display_dependencies(GladeProject * project)4927 glade_project_display_dependencies (GladeProject *project)
4928 {
4929   GList *catalogs, *l;
4930   GString *string;
4931 
4932   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4933 
4934   string = g_string_new ("");
4935 
4936   catalogs = glade_project_required_libs (project);
4937   for (l = catalogs; l; l = l->next)
4938     {
4939       gchar *catalog = l->data;
4940       gint major = 0, minor = 0;
4941 
4942       glade_project_get_target_version (project, catalog, &major, &minor);
4943 
4944       if (l != catalogs)
4945         g_string_append (string, ", ");
4946 
4947       /* Capitalize GTK+ */
4948       if (strcmp (catalog, "gtk+") == 0)
4949         g_string_append_printf (string, "GTK+ >= %d.%d", major, minor);
4950       else if (major && minor)
4951         g_string_append_printf (string, "%s >= %d.%d", catalog, major, minor);
4952       else
4953         g_string_append_printf (string, "%s", catalog);
4954 
4955       g_free (catalog);
4956     }
4957   g_list_free (catalogs);
4958 
4959   return g_string_free (string, FALSE);
4960 }
4961 
4962 /**
4963  * glade_project_toplevels:
4964  * @project: a #GladeProject
4965  *
4966  * Returns: a #GList containing the #GtkWidget toplevel items in @project
4967  */
4968 GList *
glade_project_toplevels(GladeProject * project)4969 glade_project_toplevels (GladeProject *project)
4970 {
4971   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
4972 
4973   return project->priv->tree;
4974 }
4975 
4976 /**
4977  * glade_project_set_translation_domain:
4978  * @project: a #GladeProject
4979  * @domain: the translation domain
4980  *
4981  * Set the project translation domain.
4982  */
4983 void
glade_project_set_translation_domain(GladeProject * project,const gchar * domain)4984 glade_project_set_translation_domain (GladeProject *project, const gchar *domain)
4985 {
4986   GladeProjectPrivate *priv;
4987 
4988   g_return_if_fail (GLADE_IS_PROJECT (project));
4989 
4990   priv = project->priv;
4991 
4992   if (g_strcmp0 (priv->translation_domain, domain))
4993     {
4994       g_free (priv->translation_domain);
4995       priv->translation_domain = g_strdup (domain);
4996 
4997       g_object_notify_by_pspec (G_OBJECT (project),
4998                                 glade_project_props[PROP_TRANSLATION_DOMAIN]);
4999     }
5000 }
5001 
5002 /**
5003  * glade_project_get_translation_domain:
5004  * @project: a #GladeProject
5005  *
5006  * Returns: the translation domain
5007  */
5008 const gchar *
glade_project_get_translation_domain(GladeProject * project)5009 glade_project_get_translation_domain (GladeProject *project)
5010 {
5011   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
5012 
5013   return project->priv->translation_domain;
5014 }
5015 
5016 static void
glade_project_css_provider_remove_forall(GtkWidget * widget,gpointer data)5017 glade_project_css_provider_remove_forall (GtkWidget *widget, gpointer data)
5018 {
5019   gtk_style_context_remove_provider (gtk_widget_get_style_context (widget),
5020                                      GTK_STYLE_PROVIDER (data));
5021 
5022   if (GTK_IS_CONTAINER (widget))
5023     gtk_container_forall (GTK_CONTAINER (widget), glade_project_css_provider_remove_forall, data);
5024 }
5025 
5026 static inline void
glade_project_css_provider_refresh(GladeProject * project,gboolean remove)5027 glade_project_css_provider_refresh (GladeProject *project, gboolean remove)
5028 {
5029   GladeProjectPrivate *priv = project->priv;
5030   GtkCssProvider *provider = priv->css_provider;
5031   const GList *l;
5032 
5033   for (l = priv->tree; l; l = g_list_next (l))
5034     {
5035       GObject *object = l->data;
5036 
5037       if (!GTK_IS_WIDGET (object) || GLADE_IS_OBJECT_STUB (object))
5038         continue;
5039 
5040       if (remove)
5041         glade_project_css_provider_remove_forall (GTK_WIDGET (object), provider);
5042       else
5043         glade_project_set_css_provider_forall (GTK_WIDGET (object), provider);
5044     }
5045 }
5046 
5047 static void
on_css_monitor_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,GladeProject * project)5048 on_css_monitor_changed (GFileMonitor     *monitor,
5049                         GFile            *file,
5050                         GFile            *other_file,
5051                         GFileMonitorEvent event_type,
5052                         GladeProject     *project)
5053 {
5054   GError *error = NULL;
5055 
5056   gtk_css_provider_load_from_file (project->priv->css_provider, file, &error);
5057 
5058   if (error)
5059     {
5060       g_message ("CSS parsing failed: %s", error->message);
5061       g_error_free (error);
5062     }
5063 }
5064 
5065 /**
5066  * glade_project_set_css_provider_path:
5067  * @project: a #GladeProject
5068  * @path: a CSS file path
5069  *
5070  * Set the custom CSS provider path to use in @project
5071  */
5072 void
glade_project_set_css_provider_path(GladeProject * project,const gchar * path)5073 glade_project_set_css_provider_path (GladeProject *project, const gchar *path)
5074 {
5075   GladeProjectPrivate *priv;
5076 
5077   g_return_if_fail (GLADE_IS_PROJECT (project));
5078   priv = project->priv;
5079 
5080   if (g_strcmp0 (priv->css_provider_path, path) != 0)
5081     {
5082       g_free (priv->css_provider_path);
5083       priv->css_provider_path = g_strdup (path);
5084 
5085       g_clear_object (&priv->css_monitor);
5086 
5087       if (priv->css_provider)
5088         {
5089           glade_project_css_provider_refresh (project, TRUE);
5090           g_clear_object (&priv->css_provider);
5091         }
5092 
5093       if (priv->css_provider_path &&
5094           g_file_test (priv->css_provider_path, G_FILE_TEST_IS_REGULAR))
5095         {
5096           GFile *file = g_file_new_for_path (priv->css_provider_path);
5097 
5098           priv->css_provider = GTK_CSS_PROVIDER (gtk_css_provider_new ());
5099           g_object_ref_sink (priv->css_provider);
5100           gtk_css_provider_load_from_file (priv->css_provider, file, NULL);
5101 
5102           g_clear_object (&priv->css_monitor);
5103           priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
5104           g_object_ref_sink (priv->css_monitor);
5105           g_signal_connect_object (priv->css_monitor, "changed",
5106                                    G_CALLBACK (on_css_monitor_changed), project, 0);
5107 
5108           glade_project_css_provider_refresh (project, FALSE);
5109           g_object_unref (file);
5110         }
5111 
5112       g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_CSS_PROVIDER_PATH]);
5113     }
5114 }
5115 
5116 /**
5117  * glade_project_get_css_provider_path:
5118  * @project: a #GladeProject
5119  *
5120  * Returns: the CSS path of the custom provider used for @project
5121  */
5122 const gchar *
glade_project_get_css_provider_path(GladeProject * project)5123 glade_project_get_css_provider_path (GladeProject *project)
5124 {
5125   g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
5126 
5127   return project->priv->css_provider_path;
5128 }
5129 
5130 /*************************************************
5131  *                Command Central                *
5132  *************************************************/
5133 static gboolean
widget_contains_unknown_type(GladeWidget * widget)5134 widget_contains_unknown_type (GladeWidget *widget)
5135 {
5136   GList *list, *l;
5137   GObject *object;
5138   gboolean has_unknown = FALSE;
5139 
5140   object = glade_widget_get_object (widget);
5141 
5142   if (GLADE_IS_OBJECT_STUB (object))
5143     return TRUE;
5144 
5145   list = glade_widget_get_children (widget);
5146   for (l = list; l && has_unknown == FALSE; l = l->next)
5147     {
5148       GladeWidget *child = glade_widget_get_from_gobject (l->data);
5149 
5150       has_unknown = widget_contains_unknown_type (child);
5151     }
5152   g_list_free (list);
5153 
5154   return has_unknown;
5155 }
5156 
5157 void
glade_project_copy_selection(GladeProject * project)5158 glade_project_copy_selection (GladeProject *project)
5159 {
5160   GList *widgets = NULL, *list;
5161   gboolean has_unknown = FALSE;
5162 
5163   g_return_if_fail (GLADE_IS_PROJECT (project));
5164 
5165   if (glade_project_is_loading (project))
5166     return;
5167 
5168   if (!project->priv->selection)
5169     {
5170       glade_util_ui_message (glade_app_get_window (),
5171                              GLADE_UI_INFO, NULL, _("No widget selected."));
5172       return;
5173     }
5174 
5175   for (list = project->priv->selection; list && list->data; list = list->next)
5176     {
5177       GladeWidget *widget = glade_widget_get_from_gobject (list->data);
5178 
5179       if (widget_contains_unknown_type (widget))
5180         has_unknown = TRUE;
5181       else
5182 	widgets = g_list_prepend (widgets, glade_widget_dup (widget, FALSE));
5183     }
5184 
5185   if (has_unknown)
5186     glade_util_ui_message (glade_app_get_window (),
5187                            GLADE_UI_INFO, NULL, _("Unable to copy unrecognized widget type."));
5188 
5189   glade_clipboard_add (glade_app_get_clipboard (), widgets);
5190   g_list_free (widgets);
5191 }
5192 
5193 void
glade_project_command_cut(GladeProject * project)5194 glade_project_command_cut (GladeProject *project)
5195 {
5196   GList *widgets = NULL, *list;
5197   gboolean has_unknown = FALSE;
5198   gboolean failed = FALSE;
5199 
5200   g_return_if_fail (GLADE_IS_PROJECT (project));
5201 
5202   if (glade_project_is_loading (project))
5203     return;
5204 
5205   for (list = project->priv->selection; list && list->data; list = list->next)
5206     {
5207       GladeWidget *widget = glade_widget_get_from_gobject (list->data);
5208 
5209       if (widget_contains_unknown_type (widget))
5210         has_unknown = TRUE;
5211       else
5212 	widgets = g_list_prepend (widgets, widget);
5213     }
5214 
5215   if (failed == FALSE && widgets != NULL)
5216     glade_command_cut (widgets);
5217   else if (has_unknown)
5218     glade_util_ui_message (glade_app_get_window (),
5219                            GLADE_UI_INFO, NULL, _("Unable to cut unrecognized widget type"));
5220   else if (widgets == NULL)
5221     glade_util_ui_message (glade_app_get_window (),
5222                            GLADE_UI_INFO, NULL, _("No widget selected."));
5223 
5224   if (widgets)
5225     g_list_free (widgets);
5226 }
5227 
5228 void
glade_project_command_paste(GladeProject * project,GladePlaceholder * placeholder)5229 glade_project_command_paste (GladeProject     *project,
5230                              GladePlaceholder *placeholder)
5231 {
5232   GladeClipboard *clipboard;
5233   GList *list;
5234   GladeWidget *widget = NULL, *parent;
5235   gint placeholder_relations = 0;
5236 
5237   g_return_if_fail (GLADE_IS_PROJECT (project));
5238 
5239   if (glade_project_is_loading (project))
5240     return;
5241 
5242   if (placeholder)
5243     {
5244       if (glade_placeholder_get_project (placeholder) == NULL ||
5245           glade_project_is_loading (glade_placeholder_get_project (placeholder)))
5246         return;
5247     }
5248 
5249   list      = project->priv->selection;
5250   clipboard = glade_app_get_clipboard ();
5251 
5252   /* If there is a selection, paste in to the selected widget, otherwise
5253    * paste into the placeholder's parent, or at the toplevel
5254    */
5255   parent = list ? glade_widget_get_from_gobject (list->data) :
5256       (placeholder) ? glade_placeholder_get_parent (placeholder) : NULL;
5257 
5258   widget = glade_clipboard_widgets (clipboard) ? glade_clipboard_widgets (clipboard)->data : NULL;
5259 
5260   /* Ignore parent argument if we are pasting a toplevel
5261    */
5262   if (g_list_length (glade_clipboard_widgets (clipboard)) == 1 &&
5263       widget && GWA_IS_TOPLEVEL (glade_widget_get_adaptor (widget)))
5264     parent = NULL;
5265 
5266   /* Check if parent is actually a container of any sort */
5267   if (parent && !glade_widget_adaptor_is_container (glade_widget_get_adaptor (parent)))
5268     {
5269       glade_util_ui_message (glade_app_get_window (),
5270                              GLADE_UI_INFO, NULL,
5271                              _("Unable to paste to the selected parent"));
5272       return;
5273     }
5274 
5275   /* Check if selection is good */
5276   if (project->priv->selection)
5277     {
5278       if (g_list_length (project->priv->selection) != 1)
5279         {
5280           glade_util_ui_message (glade_app_get_window (),
5281                                  GLADE_UI_INFO, NULL,
5282                                  _("Unable to paste to multiple widgets"));
5283 
5284           return;
5285         }
5286     }
5287 
5288   /* Check if we have anything to paste */
5289   if (g_list_length (glade_clipboard_widgets (clipboard)) == 0)
5290     {
5291       glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL,
5292                              _("No widget on the clipboard"));
5293 
5294       return;
5295     }
5296 
5297   /* Check that the underlying adaptor allows the paste */
5298   if (parent)
5299     {
5300       for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next)
5301         {
5302           widget = list->data;
5303 
5304           if (!glade_widget_add_verify (parent, widget, TRUE))
5305             return;
5306         }
5307     }
5308 
5309 
5310   /* Check that we have compatible heirarchies */
5311   for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next)
5312     {
5313       widget = list->data;
5314 
5315       if (!GWA_IS_TOPLEVEL (glade_widget_get_adaptor (widget)) && parent)
5316         {
5317           /* Count placeholder relations
5318            */
5319           if (glade_widget_placeholder_relation (parent, widget))
5320             placeholder_relations++;
5321         }
5322     }
5323 
5324   g_assert (widget);
5325 
5326   /* A GladeWidget that doesnt use placeholders can only paste one
5327    * at a time
5328    *
5329    * XXX: Not sure if this has to be true.
5330    */
5331   if (GTK_IS_WIDGET (glade_widget_get_object (widget)) &&
5332       parent && !GWA_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) &&
5333       g_list_length (glade_clipboard_widgets (clipboard)) != 1)
5334     {
5335       glade_util_ui_message (glade_app_get_window (),
5336                              GLADE_UI_INFO, NULL,
5337                              _("Only one widget can be pasted at a "
5338                                "time to this container"));
5339       return;
5340     }
5341 
5342   /* Check that enough placeholders are available */
5343   if (parent &&
5344       GWA_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) &&
5345       glade_util_count_placeholders (parent) < placeholder_relations)
5346     {
5347       glade_util_ui_message (glade_app_get_window (),
5348                              GLADE_UI_INFO, NULL,
5349                              _("Insufficient amount of placeholders in "
5350                                "target container"));
5351       return;
5352     }
5353 
5354   glade_command_paste (glade_clipboard_widgets (clipboard), parent, placeholder, project);
5355 }
5356 
5357 void
glade_project_command_delete(GladeProject * project)5358 glade_project_command_delete (GladeProject *project)
5359 {
5360   GList *widgets = NULL, *list;
5361   GladeWidget *widget;
5362   gboolean failed = FALSE;
5363 
5364   g_return_if_fail (GLADE_IS_PROJECT (project));
5365 
5366   if (glade_project_is_loading (project))
5367     return;
5368 
5369   for (list = project->priv->selection; list && list->data; list = list->next)
5370     {
5371       widget  = glade_widget_get_from_gobject (list->data);
5372       widgets = g_list_prepend (widgets, widget);
5373     }
5374 
5375   if (failed == FALSE && widgets != NULL)
5376     glade_command_delete (widgets);
5377   else if (widgets == NULL)
5378     glade_util_ui_message (glade_app_get_window (),
5379                            GLADE_UI_INFO, NULL, _("No widget selected."));
5380 
5381   if (widgets)
5382     g_list_free (widgets);
5383 }
5384