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 ©right,
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