glade-project.c 89 KB
Newer Older
Jose Maria Celorio's avatar
Jose Maria Celorio committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2001 Ximian, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Jose Maria Celorio's avatar
Jose Maria Celorio committed
18 19 20 21 22
 *
 * Authors:
 *   Chema Celorio <chema@celorio.com>
 */

23 24
#include <config.h>

25 26 27 28 29 30 31 32 33
/**
 * SECTION:glade-project
 * @Short_Description: The Glade document hub and Load/Save interface.
 *
 * This object owns all project objects and is responsable for loading and
 * saving the glade document, you can monitor the project state via this
 * object and its signals.
 */

34
#include <string.h>
35
#include <stdlib.h>
36
#include <glib.h>
37
#include <glib/gi18n-lib.h>
38
#include <glib/gstdio.h>
Jose Maria Celorio's avatar
Jose Maria Celorio committed
39 40

#include "glade.h"
Chema Celorio's avatar
Chema Celorio committed
41
#include "glade-widget.h"
42
#include "glade-id-allocator.h"
43
#include "glade-app.h"
44
#include "glade-marshallers.h"
45 46 47
#include "glade-catalog.h"

#include "glade-project.h"
Jose Maria Celorio's avatar
Jose Maria Celorio committed
48

49 50
#define GLADE_PROJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GLADE_TYPE_PROJECT, GladeProjectPrivate))

Jose Maria Celorio's avatar
Jose Maria Celorio committed
51 52 53 54 55
enum
{
	ADD_WIDGET,
	REMOVE_WIDGET,
	WIDGET_NAME_CHANGED,
56
	SELECTION_CHANGED,
57
	CLOSE,
58
	RESOURCE_ADDED,
59
	RESOURCE_REMOVED,
60
	CHANGED,
61
	PARSE_FINISHED,
Jose Maria Celorio's avatar
Jose Maria Celorio committed
62 63 64
	LAST_SIGNAL
};

65 66 67
enum
{
	PROP_0,
68
	PROP_MODIFIED,
69
	PROP_HAS_SELECTION,
70
	PROP_PATH,
71
	PROP_READ_ONLY
72 73
};

74 75
struct _GladeProjectPrivate
{
76
	gchar *path;            /* The full canonical path of the glade file for this project */
77 78 79

	guint   instance_count; /* How many projects with this name */

80
	gint   unsaved_number;  /* A unique number for this project if it is untitled */
81

82
	gboolean readonly;      /* A flag that is set if the project is readonly */
83

84
	gboolean loading;       /* A flags that is set when the project is loading */
85
	
86 87 88 89 90
	gboolean modified;    /* A flag that is set when a project has unsaved modifications
			       * if this flag is not set we don't have to query
			       * for confirmation after a close or exit is
			       * requested
			       */
91 92 93 94 95 96 97 98 99 100 101 102

	GList *objects; /* A list of #GObjects that make up this project.
			 * The objects are stored in no particular order.
			 */

	GList *selection; /* We need to keep the selection in the project
			   * because we have multiple projects and when the
			   * user switchs between them, he will probably
			   * not want to loose the selection. This is a list
			   * of #GtkWidget items.
			   */

103
	gboolean     has_selection;           /* Whether the project has a selection */
104

105 106 107 108
	GList       *undo_stack;              /* A stack with the last executed commands */
	GList       *prev_redo_item;          /* Points to the item previous to the redo items */
	GHashTable  *widget_names_allocator;  /* hash table with the used widget names */
	GHashTable  *widget_old_names;        /* widget -> old name of the widget */
109
	
110 111 112
	gboolean first_modification_is_na; /* the flag indicates that  the first_modification item has been lost */
	
	GList *first_modification; /* we record the first modification, so that we
113
	                                   * can set "modification" to FALSE when we
114 115 116
	                                   * undo this modification
	                                   */
	
117 118 119 120
	GtkAccelGroup *accel_group;

	GHashTable *resources; /* resource filenames & thier associated properties */
	
121 122 123
	gchar *comment;        /* XML comment, Glade will preserve whatever comment was
			        * in file, so users can delete or change it.
			        */
124
			 
125
	time_t  mtime;         /* last UTC modification time of file, or 0 if it could not be read */
Johan Dahlin's avatar
Johan Dahlin committed
126 127

	GladeProjectFormat format; /* file format */
128 129 130

	GHashTable *target_versions_major; /* target versions by catalog */
	GHashTable *target_versions_minor; /* target versions by catalog */
131 132

	GList *loaded_factory_files;
133 134
};

135 136 137 138 139
typedef struct {
	gchar *stock;
	gchar *filename;
} StockFilePair;

140

141 142 143 144
static guint              glade_project_signals[LAST_SIGNAL] = {0};

static GladeIDAllocator  *unsaved_number_allocator = NULL;

145

146 147
G_DEFINE_TYPE (GladeProject, glade_project, G_TYPE_OBJECT)

148

149 150 151 152 153

#define GLADE_GENERATED_ICON_FACTORY_NAME   "glade-generated-icon-factory"
#define GLADE_ICON_FACTORY_CLASS_NAME       "GtkIconFactory"


154 155 156
/*******************************************************************
                            GObjectClass
 *******************************************************************/
157 158 159 160 161 162 163 164 165 166

static GladeIDAllocator *
get_unsaved_number_allocator (void)
{
	if (unsaved_number_allocator == NULL)
		unsaved_number_allocator = glade_id_allocator_new ();
		
	return unsaved_number_allocator;
}

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
static void
glade_project_list_unref (GList *original_list)
{
	GList *l;
	for (l = original_list; l; l = l->next)
		g_object_unref (G_OBJECT (l->data));

	if (original_list != NULL)
		g_list_free (original_list);
}

static void
glade_project_dispose (GObject *object)
{
	GladeProject *project = GLADE_PROJECT (object);
	GList        *list;
	GladeWidget  *gwidget;
	
	/* Emit close signal */
	g_signal_emit (object, glade_project_signals [CLOSE], 0);
	
	glade_project_selection_clear (project, TRUE);

190 191
	glade_project_list_unref (project->priv->undo_stack);
	project->priv->undo_stack = NULL;
192

193 194 195 196
	/* Unparent all widgets in the heirarchy first 
	 * (Since we are bookkeeping exact reference counts, we 
	 * dont want the hierarchy to just get destroyed)
	 */
197
	for (list = project->priv->objects; list; list = list->next)
198 199 200 201
	{
		gwidget = glade_widget_get_from_gobject (list->data);

		if (gwidget->parent &&
202
		    gwidget->internal == NULL &&
203 204 205
		    glade_widget_adaptor_has_child (gwidget->parent->adaptor,
						    gwidget->parent->object,
						    gwidget->object))
206 207 208
			glade_widget_remove_child (gwidget->parent, gwidget);
	}

209
	/* Remove objects from the project */
210
	for (list = project->priv->objects; list; list = list->next)
211 212 213
	{
		gwidget = glade_widget_get_from_gobject (list->data);

214 215
		g_object_unref (G_OBJECT (list->data)); /* Remove the GladeProject reference */
		g_object_unref (G_OBJECT (gwidget));  /* Remove the overall "Glade" reference */
216
	}
217
	project->priv->objects = NULL;
218

219
	G_OBJECT_CLASS (glade_project_parent_class)->dispose (object);
220 221 222 223 224 225 226
}

static void
glade_project_finalize (GObject *object)
{
	GladeProject *project = GLADE_PROJECT (object);

227 228
	g_free (project->priv->path);
	g_free (project->priv->comment);
229

230 231
	if (project->priv->unsaved_number > 0)
		glade_id_allocator_release (get_unsaved_number_allocator (), project->priv->unsaved_number);
232

233 234 235
	g_hash_table_destroy (project->priv->widget_names_allocator);
	g_hash_table_destroy (project->priv->widget_old_names);
	g_hash_table_destroy (project->priv->resources);
236 237
	g_hash_table_destroy (project->priv->target_versions_major);
	g_hash_table_destroy (project->priv->target_versions_minor);
238

239
	G_OBJECT_CLASS (glade_project_parent_class)->finalize (object);
240
}
Jose Maria Celorio's avatar
Jose Maria Celorio committed
241

242 243 244 245 246 247 248 249 250 251
static void
glade_project_get_property (GObject    *object,
			    guint       prop_id,
			    GValue     *value,
			    GParamSpec *pspec)
{
	GladeProject *project = GLADE_PROJECT (object);

	switch (prop_id)
	{
252 253
		case PROP_MODIFIED:
			g_value_set_boolean (value, project->priv->modified);
254 255
			break;
		case PROP_HAS_SELECTION:
256
			g_value_set_boolean (value, project->priv->has_selection);
257
			break;			
258
		case PROP_READ_ONLY:
259 260 261 262 263
			g_value_set_boolean (value, project->priv->readonly);
			break;
		case PROP_PATH:
			g_value_set_string (value, project->priv->path);
			break;				
264 265 266 267 268 269
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;			
	}
}

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
/**
 * glade_project_set_modified:
 * @project: a #GladeProject
 * @modified: Whether the project should be set as modified or not
 * @modification: The first #GladeCommand which caused the project to have unsaved changes
 *
 * Set's whether a #GladeProject should be flagged as modified or not. This is useful
 * for indicating that a project has unsaved changes. If @modified is #TRUE, then
 * @modification will be recorded as the first change which caused the project to 
 * have unsaved changes. @modified is #FALSE then @modification will be ignored.
 *
 * If @project is already flagged as modified, then calling this method with
 * @modified as #TRUE, will have no effect. Likewise, if @project is unmodified
 * then calling this method with @modified as #FALSE, will have no effect.
 *
 */
286
static void
287
glade_project_set_modified (GladeProject *project,
288
			    gboolean      modified)
289 290 291 292 293 294
{
	GladeProjectPrivate *priv = project->priv;

	if (priv->modified != modified)
	{
		priv->modified = !priv->modified;
295
		
296
		if (!priv->modified)
297
		{
298 299
			priv->first_modification = project->priv->prev_redo_item;
			priv->first_modification_is_na = FALSE;
300
		}
301
		
302 303 304
		g_object_notify (G_OBJECT (project), "modified");
	}
}
305 306 307 308 309 310

/*******************************************************************
                          GladeProjectClass
 *******************************************************************/
static void
glade_project_walk_back (GladeProject *project)
Jose Maria Celorio's avatar
Jose Maria Celorio committed
311
{
312 313
	if (project->priv->prev_redo_item)
		project->priv->prev_redo_item = project->priv->prev_redo_item->prev;
314
}
315

316 317 318
static void
glade_project_walk_forward (GladeProject *project)
{
319 320
	if (project->priv->prev_redo_item)
		project->priv->prev_redo_item = project->priv->prev_redo_item->next;
321
	else
322
		project->priv->prev_redo_item = project->priv->undo_stack;
323 324 325 326 327 328 329 330
}

static void
glade_project_undo_impl (GladeProject *project)
{
	GladeCommand *cmd, *next_cmd;

	while ((cmd = glade_project_next_undo_item (project)) != NULL)
331
	{	
332
		glade_command_undo (cmd);
333

334 335 336 337 338 339 340 341 342
		glade_project_walk_back (project);

		g_signal_emit (G_OBJECT (project),
			       glade_project_signals [CHANGED], 
			       0, cmd, FALSE);

		if ((next_cmd = glade_project_next_undo_item (project)) != NULL &&
		    (next_cmd->group_id == 0 || next_cmd->group_id != cmd->group_id))
			break;
Jose Maria Celorio's avatar
Jose Maria Celorio committed
343
	}
344
}
345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
static void
glade_project_redo_impl (GladeProject *project)
{
	GladeCommand *cmd, *next_cmd;
	
	while ((cmd = glade_project_next_redo_item (project)) != NULL)
	{
		glade_command_execute (cmd);

		glade_project_walk_forward (project);

		g_signal_emit (G_OBJECT (project),
			       glade_project_signals [CHANGED],
			       0, cmd, TRUE);

		if ((next_cmd = glade_project_next_redo_item (project)) != NULL &&
		    (next_cmd->group_id == 0 || next_cmd->group_id != cmd->group_id))
			break;
	}
}

static GladeCommand *
glade_project_next_undo_item_impl (GladeProject *project)
{
	GList *l;

372
	if ((l = project->priv->prev_redo_item) == NULL)
373 374 375 376 377 378 379 380 381 382
		return NULL;

	return GLADE_COMMAND (l->data);
}

static GladeCommand *
glade_project_next_redo_item_impl (GladeProject *project)
{
	GList *l;

383 384 385
	if ((l = project->priv->prev_redo_item) == NULL)
		return project->priv->undo_stack ? 
			GLADE_COMMAND (project->priv->undo_stack->data) : NULL;
386 387 388 389
	else
		return l->next ? GLADE_COMMAND (l->next->data) : NULL;
}

390 391 392 393 394 395 396 397 398 399 400 401 402
static GList *
glade_project_free_undo_item (GladeProject *project, GList *item)
{
	g_assert (item->data);

	if (item == project->priv->first_modification)
		project->priv->first_modification_is_na = TRUE;

	g_object_unref (G_OBJECT (item->data));

	return g_list_next (item);
}

403 404 405
static void
glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd)
{
406
	GladeProjectPrivate *priv = project->priv;
407
	GList *tmp_redo_item;
408 409

	/* We should now free all the "redo" items */
410
	tmp_redo_item = g_list_next (priv->prev_redo_item);
411
	while (tmp_redo_item)
412
		tmp_redo_item = glade_project_free_undo_item (project, tmp_redo_item);
413

414
	if (priv->prev_redo_item)
415
	{
416 417
		g_list_free (g_list_next (priv->prev_redo_item));
		priv->prev_redo_item->next = NULL;
418 419 420
	}
	else
	{
421 422
		g_list_free (priv->undo_stack);
		priv->undo_stack = NULL;
423 424
	}

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
	/* Try to unify only if group depth is 0 */
	if (glade_command_get_group_depth() == 0 &&
	    priv->prev_redo_item != NULL)
	{
		GladeCommand *cmd1 = priv->prev_redo_item->data;
		gboolean is_atomic = FALSE;

		/* Cannot unify with a part of a command group.
		 * Unify atomic commands only
		 */
		if (cmd1->group_id == 0 || cmd->group_id == 0 ||
		    cmd->group_id != cmd1->group_id)
			is_atomic = TRUE;

		if (is_atomic && glade_command_unifies (cmd1, cmd))
		{
			glade_command_collapse (cmd1, cmd);
			g_object_unref (cmd);

			if (glade_command_unifies (cmd1, NULL))
			{
				tmp_redo_item = priv->prev_redo_item;
				glade_project_walk_back (project);
				glade_project_free_undo_item (project, tmp_redo_item);
				priv->undo_stack = g_list_delete_link (priv->undo_stack, tmp_redo_item);
			}

			g_signal_emit (G_OBJECT (project),
				       glade_project_signals [CHANGED],
				       0, NULL, TRUE);
			return;
		}
	}

459
	/* and then push the new undo item */
460
	priv->undo_stack = g_list_append (priv->undo_stack, cmd);
461

462
	if (project->priv->prev_redo_item == NULL)
463
		priv->prev_redo_item = priv->undo_stack;
464
	else
465
		priv->prev_redo_item = g_list_next (priv->prev_redo_item);
466 467 468 469 470 471 472 473 474 475 476 477


	g_signal_emit (G_OBJECT (project),
		       glade_project_signals [CHANGED],
		       0, cmd, TRUE);
}

static void
glade_project_changed_impl (GladeProject *project, 
			    GladeCommand *command,
			    gboolean      forward)
{
478
	if (!project->priv->loading)
479
	{
480 481 482
		/* if this command is the first modification to cause the project
		 * to have unsaved changes, then we can now flag the project as unmodified
		 */
483 484
		if (!project->priv->first_modification_is_na && project->priv->prev_redo_item == project->priv->first_modification)
			glade_project_set_modified (project, FALSE);
485
		else
486
			glade_project_set_modified (project, TRUE);
487 488 489 490 491 492 493
	}
	glade_app_update_ui ();
}

/*******************************************************************
                            Initializers
 *******************************************************************/
494 495 496

static void
glade_project_get_target_version (GladeProject *project,
497
				  const gchar  *catalog,
498 499 500 501 502 503 504 505 506 507 508
				  gint         *major,
				  gint         *minor)
{
	*major = GPOINTER_TO_INT 
		(g_hash_table_lookup (project->priv->target_versions_major,
				      catalog));
	*minor = GPOINTER_TO_INT 
		(g_hash_table_lookup (project->priv->target_versions_minor,
				      catalog));
}

509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
static void
glade_project_target_version_for_adaptor (GladeProject        *project, 
					  GladeWidgetAdaptor  *adaptor,
					  gint                *major,
					  gint                *minor)
{
	gchar   *catalog = NULL;

	g_object_get (adaptor, "catalog", &catalog, NULL);
	glade_project_get_target_version (project, catalog, major, minor);
	g_free (catalog);
}

static void
glade_project_verify_adaptor (GladeProject       *project,
			      GladeWidgetAdaptor *adaptor,
			      const gchar        *path_name,
			      GString            *string,
			      gboolean            saving,
528 529
			      gboolean            forwidget,
			      GladeSupportMask   *mask)
530
{
531
	GladeSupportMask    support_mask = GLADE_SUPPORT_OK;
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
	GladeWidgetAdaptor *adaptor_iter;
	gint                target_major, target_minor;
	gchar              *catalog = NULL;

	for (adaptor_iter = adaptor; adaptor_iter;
	     adaptor_iter = glade_widget_adaptor_get_parent_adaptor (adaptor_iter))
	{

		g_object_get (adaptor_iter, "catalog", &catalog, NULL);
		glade_project_target_version_for_adaptor (project, adaptor_iter, 
							  &target_major,
							  &target_minor);

		if (target_major < GWA_VERSION_SINCE_MAJOR (adaptor_iter) ||
		    (target_major == GWA_VERSION_SINCE_MAJOR (adaptor_iter) &&
		     target_minor < GWA_VERSION_SINCE_MINOR (adaptor_iter)))
		{
			if (forwidget)
			{
551 552 553
				/* translators: reffers to a widget
				 * introduced in toolkit version '%s %d.%d',
				 * and a project targeting toolkit verion '%s %d.%d' */
554 555 556 557 558 559 560 561 562 563
				g_string_append_printf
					(string, 
					 _("This widget was introduced in %s %d.%d "
					   "project targets %s %d.%d"),
					 catalog,
					 GWA_VERSION_SINCE_MAJOR (adaptor_iter),
					 GWA_VERSION_SINCE_MINOR (adaptor_iter),
					 catalog, target_major, target_minor);
			}
			else
564 565
				/* translators: reffers to a widget '[%s]'  
				 * introduced in toolkit version '%s %d.%d' */
566 567 568 569 570 571
				g_string_append_printf
					(string, 
					 _("[%s] Object class '%s' was introduced in %s %d.%d\n"),
					 path_name, adaptor_iter->title, catalog,
					 GWA_VERSION_SINCE_MAJOR (adaptor_iter),
					 GWA_VERSION_SINCE_MINOR (adaptor_iter));
572 573

			support_mask |= GLADE_SUPPORT_MISMATCH;
574 575 576 577 578 579 580 581 582 583 584 585 586 587
		}

		if (project->priv->format == GLADE_PROJECT_FORMAT_GTKBUILDER &&
		    GWA_BUILDER_UNSUPPORTED (adaptor_iter))
		{
			if (forwidget)
			{
				if (string->len)
					g_string_append (string, "\n");
				g_string_append_printf
					(string,
					 _("This widget is not supported by GtkBuilder"));
			}
			else
588 589
				/* translators: reffers to a widget '[%s]'  
				 * loaded from toolkit version '%s %d.%d' */
590 591 592 593 594 595
				g_string_append_printf
					(string,
					 _("[%s] Object class '%s' from %s %d.%d "
					   "is not supported by GtkBuilder\n"),
					 path_name, adaptor_iter->title, catalog,
					 target_major, target_minor);
596 597

			support_mask |= GLADE_SUPPORT_BUILDER_UNSUPPORTED;
598 599 600 601 602 603 604 605 606 607 608 609
		}

		if (!saving && GWA_DEPRECATED (adaptor_iter))
		{
			if (forwidget)
			{
				if (string->len)
					g_string_append (string, "\n");
				g_string_append_printf
					(string, _("This widget is deprecated"));
			}
			else
610 611
				/* translators: reffers to a widget '[%s]'  
				 * loaded from toolkit version '%s %d.%d' */
612 613 614 615 616 617
				g_string_append_printf
					(string, 
					 _("[%s] Object class '%s' from %s %d.%d "
					   "is deprecated\n"),
					 path_name, adaptor_iter->title, catalog,
					 target_major, target_minor);
618 619

			support_mask |= GLADE_SUPPORT_DEPRECATED;
620 621 622
		}
		g_free (catalog);
	}
623 624
	if (mask)
		*mask = support_mask;
625 626
}

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
/**
 * glade_project_verify_widget_adaptor:
 * @project: A #GladeProject
 * @adaptor: the #GladeWidgetAdaptor to verify
 * @mask: a return location for a #GladeSupportMask
 * 
 * Checks the supported state of this widget adaptor
 * and generates a string to show in the UI describing why.
 *
 * Returns: A newly allocated string 
 */
gchar *
glade_project_verify_widget_adaptor (GladeProject       *project,
				     GladeWidgetAdaptor *adaptor,
				     GladeSupportMask   *mask)
642 643
{
	GString *string = g_string_new (NULL);
644
	gchar   *ret = NULL;
645

646 647
	glade_project_verify_adaptor (project, adaptor, NULL,
				      string, FALSE, TRUE, mask);
648

649
	/* there was a '\0' byte... */
650
	if (string->len > 0)
651 652 653 654
	{
		ret = string->str;
		g_string_free (string, FALSE);
	}
655
	else
656
		g_string_free (string, TRUE);
657

658 659

	return ret;
660 661 662 663 664 665 666
}

static void
glade_project_verify_project_for_ui (GladeProject *project)
{
	GList *list;
	GladeWidget *widget;
667
	gchar *warning;
668 669 670 671 672 673

	/* Sync displayable info here */
	for (list = project->priv->objects; list; list = list->next)
	{
		widget = glade_widget_get_from_gobject (list->data);

674 675 676 677 678
		warning = glade_project_verify_widget_adaptor (project, widget->adaptor, NULL);
		glade_widget_set_support_warning (widget, warning);

		if (warning)
			g_free (warning);
679

680
		glade_project_verify_properties (widget);
681
	}
682 683 684 685

	/* refresh palette if this is the active project */
	if (project == glade_app_get_project ())
		glade_palette_refresh (glade_app_get_palette ());
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
}

static void
glade_project_set_target_version (GladeProject *project,
				  const gchar  *catalog,
				  gint          major,
				  gint          minor)
{

	g_hash_table_insert (project->priv->target_versions_major,
			     g_strdup (catalog),
			     GINT_TO_POINTER (major));
	g_hash_table_insert (project->priv->target_versions_minor,
			     g_strdup (catalog),
			     GINT_TO_POINTER (minor));

	glade_project_verify_project_for_ui (project);

}

706 707 708
static void
glade_project_init (GladeProject *project)
{
709
	GladeProjectPrivate *priv;
710 711
	GList *list;

712 713 714 715 716 717 718 719 720 721
	project->priv = priv = GLADE_PROJECT_GET_PRIVATE (project);	

	priv->path = NULL;
	priv->instance_count = 0;
	priv->readonly = FALSE;
	priv->objects = NULL;
	priv->selection = NULL;
	priv->has_selection = FALSE;
	priv->undo_stack = NULL;
	priv->prev_redo_item = NULL;
722
	priv->first_modification = NULL;
723
	priv->first_modification_is_na = FALSE;
724

725 726 727 728 729 730 731 732 733 734 735 736 737
	priv->widget_names_allocator = g_hash_table_new_full (g_str_hash,
							      g_str_equal,
							      g_free, 
				       			      (GDestroyNotify) glade_id_allocator_destroy);
				       
	priv->widget_old_names = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_free);

	priv->accel_group = NULL;

	priv->resources = g_hash_table_new_full (g_direct_hash, 
						 g_direct_equal, 
						 NULL, g_free);

Johan Dahlin's avatar
Johan Dahlin committed
738 739
	priv->unsaved_number = glade_id_allocator_allocate (get_unsaved_number_allocator ());

740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
	priv->format = GLADE_PROJECT_FORMAT_GTKBUILDER;


	priv->target_versions_major = g_hash_table_new_full (g_str_hash,
							     g_str_equal,
							     g_free,
							     NULL);
	priv->target_versions_minor = g_hash_table_new_full (g_str_hash,
							     g_str_equal,
							     g_free,
							     NULL);

	for (list = glade_app_get_catalogs(); list; list = list->next)
	{
		GladeCatalog *catalog = list->data;

		/* Set default target to catalog version */
		glade_project_set_target_version (project,
						  glade_catalog_get_name (catalog),
						  glade_catalog_get_major_version (catalog),
						  glade_catalog_get_minor_version (catalog));
	}
Jose Maria Celorio's avatar
Jose Maria Celorio committed
762 763 764
}

static void
765
glade_project_class_init (GladeProjectClass *klass)
Jose Maria Celorio's avatar
Jose Maria Celorio committed
766
{
767
	GObjectClass *object_class;
Jose Maria Celorio's avatar
Jose Maria Celorio committed
768

769
	object_class = G_OBJECT_CLASS (klass);
Jose Maria Celorio's avatar
Jose Maria Celorio committed
770

771
	object_class->get_property = glade_project_get_property;
772 773
	object_class->finalize     = glade_project_finalize;
	object_class->dispose      = glade_project_dispose;
774
	
775 776 777 778 779 780 781 782 783 784 785 786 787 788
	klass->add_object          = NULL;
	klass->remove_object       = NULL;
	klass->undo                = glade_project_undo_impl;
	klass->redo                = glade_project_redo_impl;
	klass->next_undo_item      = glade_project_next_undo_item_impl;
	klass->next_redo_item      = glade_project_next_redo_item_impl;
	klass->push_undo           = glade_project_push_undo_impl;

	klass->widget_name_changed = NULL;
	klass->selection_changed   = NULL;
	klass->close               = NULL;
	klass->resource_added      = NULL;
	klass->resource_removed    = NULL;
	klass->changed             = glade_project_changed_impl;
789
	
790 791 792 793 794 795 796
	/**
	 * GladeProject::add-widget:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the #GladeWidget that was added to @gladeproject.
	 *
	 * Emitted when a widget is added to a project.
	 */
Jose Maria Celorio's avatar
Jose Maria Celorio committed
797
	glade_project_signals[ADD_WIDGET] =
798 799 800
		g_signal_new ("add_widget",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
801
			      G_STRUCT_OFFSET (GladeProjectClass, add_object),
802
			      NULL, NULL,
803
			      g_cclosure_marshal_VOID__OBJECT,
804 805
			      G_TYPE_NONE,
			      1,
806
			      GLADE_TYPE_WIDGET);
807

808 809 810 811 812 813 814
	/**
	 * GladeProject::remove-widget:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the #GladeWidget that was removed from @gladeproject.
	 * 
	 * Emitted when a widget is removed from a project.
	 */
Jose Maria Celorio's avatar
Jose Maria Celorio committed
815
	glade_project_signals[REMOVE_WIDGET] =
816 817 818
		g_signal_new ("remove_widget",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
819
			      G_STRUCT_OFFSET (GladeProjectClass, remove_object),
820
			      NULL, NULL,
821
			      g_cclosure_marshal_VOID__OBJECT,
822 823
			      G_TYPE_NONE,
			      1,
824
			      GLADE_TYPE_WIDGET);
825

826 827 828 829 830 831 832 833

	/**
	 * GladeProject::widget-name-changed:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the #GladeWidget who's name changed.
	 *
	 * Emitted when @gwidget's name changes.
	 */
Jose Maria Celorio's avatar
Jose Maria Celorio committed
834
	glade_project_signals[WIDGET_NAME_CHANGED] =
835 836 837 838 839
		g_signal_new ("widget_name_changed",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (GladeProjectClass, widget_name_changed),
			      NULL, NULL,
840
			      g_cclosure_marshal_VOID__OBJECT,
841 842
			      G_TYPE_NONE,
			      1,
843
			      GLADE_TYPE_WIDGET);
844

845 846 847 848 849 850 851

	/** 
	 * GladeProject::selection-changed:
	 * @gladeproject: the #GladeProject which received the signal.
	 *
	 * Emitted when @gladeproject selection list changes.
	 */
852
	glade_project_signals[SELECTION_CHANGED] =
853 854 855 856 857 858 859 860 861
		g_signal_new ("selection_changed",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (GladeProjectClass, selection_changed),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE,
			      0);

862 863 864 865 866 867 868 869

	/**
	 * GladeProject::close:
	 * @gladeproject: the #GladeProject which received the signal.
	 *
	 * Emitted when a project is closing (a good time to clean up
	 * any associated resources).
	 */
870 871 872 873 874 875 876 877 878 879
	glade_project_signals[CLOSE] =
		g_signal_new ("close",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (GladeProjectClass, close),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE,
			      0);

880 881 882 883 884 885 886 887 888

	/** 
	 * GladeProject::resource-added:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the file's basename (in the project path).
	 *
	 * Emitted when a resource file required by a #GladeProperty is
	 * added to @gladeproject
	 */
889 890
	glade_project_signals[RESOURCE_ADDED] =
		g_signal_new ("resource-added",
891 892
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
893
			      G_STRUCT_OFFSET (GladeProjectClass, resource_added),
894 895 896 897 898 899
			      NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE,
			      1,
			      G_TYPE_STRING);

900 901 902 903 904 905 906
	/**
	 * GladeProject::resource-removed:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the file's basename
	 *
	 * Emitted when a resource file is removed from @gladeproject
	 */
907 908 909 910 911 912 913 914 915 916 917
	glade_project_signals[RESOURCE_REMOVED] =
		g_signal_new ("resource-removed",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (GladeProjectClass, resource_removed),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE,
			      1,
			      G_TYPE_STRING);

918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937

	/**
	 * GladeProject::changed:
	 * @gladeproject: the #GladeProject which received the signal.
	 * @arg1: the #GladeCommand that was executed
	 * @arg2: whether the command was executed or undone.
	 *
	 * Emitted when a @gladeproject's state changes via a #GladeCommand.
	 */
	glade_project_signals[CHANGED] =
		g_signal_new ("changed",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_FIRST,
			      G_STRUCT_OFFSET (GladeProjectClass, changed),
			      NULL, NULL,
			      glade_marshal_VOID__OBJECT_BOOLEAN,
			      G_TYPE_NONE,
			      2,
			      GLADE_TYPE_COMMAND, G_TYPE_BOOLEAN);

938 939 940 941 942 943 944 945 946 947 948 949
	/**
	 * GladeProject::parse-finished:
	 * @gladeproject: the #GladeProject which received the signal.
	 *
	 * Emitted when @gladeproject parsing has finished.
	 */
	glade_project_signals[PARSE_FINISHED] =
		g_signal_new ("parse-finished",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_FIRST,
			      G_STRUCT_OFFSET (GladeProjectClass, parse_finished),
			      NULL, NULL,
Tristan Van Berkom's avatar
Tristan Van Berkom committed
950
			      g_cclosure_marshal_VOID__VOID,
951 952
			      G_TYPE_NONE,
			      0);
953

954
	g_object_class_install_property (object_class,
955 956
					 PROP_MODIFIED,
					 g_param_spec_boolean ("modified",
957
							       "Modified",
958
							       _("Whether project has been modified since it was last saved"),
959 960 961
							       FALSE,
							       G_PARAM_READABLE));

962 963 964
	g_object_class_install_property (object_class,
					 PROP_HAS_SELECTION,
					 g_param_spec_boolean ("has-selection",
965 966
							       _("Has Selection"),
							       _("Whether project has a selection"),
967 968
							       FALSE,
							       G_PARAM_READABLE));
969

970 971 972
	g_object_class_install_property (object_class,
					 PROP_READ_ONLY,
					 g_param_spec_boolean ("read-only",
973 974
							       _("Read Only"),
							       _("Whether project is read only or not"),
975 976
							       FALSE,
							       G_PARAM_READABLE));
977 978 979 980 981 982 983 984 985 986
							       
	g_object_class_install_property (object_class,
					 PROP_PATH,
					 g_param_spec_string ("path",
							      _("Path"),
							      _("The filesystem path of the project"),
							      NULL,
							      G_PARAM_READABLE));
							       
	g_type_class_add_private (klass, sizeof (GladeProjectPrivate));
Jose Maria Celorio's avatar
Jose Maria Celorio committed
987 988
}

989 990 991
/*******************************************************************
                                  API
 *******************************************************************/
992

993 994 995 996 997
static void
glade_project_set_readonly (GladeProject *project, gboolean readonly)
{
	g_assert (GLADE_IS_PROJECT (project));
	
998
	if (project->priv->readonly != readonly)
999
	{
1000
		project->priv->readonly = readonly;
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
		g_object_notify (G_OBJECT (project), "read-only");
	}
}                                                                                               

/**
 * glade_project_get_readonly:
 * @project: a #GladeProject
 *
 * Gets whether the project is read only or not
 *
 * Returns: TRUE if project is read only
 */
gboolean
glade_project_get_readonly (GladeProject *project)
{
1016
	g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
1017

1018
	return project->priv->readonly;
1019 1020
}

1021 1022 1023
/**
 * glade_project_new:
 *
1024
 * Creates a new #GladeProject.
1025 1026 1027
 *
 * Returns: a new #GladeProject
 */
Jose Maria Celorio's avatar
Jose Maria Celorio committed
1028
GladeProject *
1029
glade_project_new (void)
Jose Maria Celorio's avatar
Jose Maria Celorio committed
1030
{
1031 1032 1033
	return g_object_new (GLADE_TYPE_PROJECT, NULL);
}

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073

static void 
glade_project_fix_object_props (GladeProject *project)
{
	GList         *l, *ll;
	GValue        *value;
	GladeWidget   *gwidget;
	GladeProperty *property;
	gchar         *txt;

	for (l = project->priv->objects; l; l = l->next)
	{
		gwidget = glade_widget_get_from_gobject (l->data);

		for (ll = gwidget->properties; ll; ll = ll->next)
		{
			property = GLADE_PROPERTY (ll->data);

			if (glade_property_class_is_object (property->klass) &&
			    (txt = g_object_get_data (G_OBJECT (property), 
						      "glade-loaded-object")) != NULL)
			{
				/* Parse the object list and set the property to it
				 * (this magicly works for both objects & object lists)
				 */
				value = glade_property_class_make_gvalue_from_string
					(property->klass, txt, project);
				
				glade_property_set_value (property, value);
				
				g_value_unset (value);
				g_free (value);
				
				g_object_set_data (G_OBJECT (property), 
						   "glade-loaded-object", NULL);
			}
		}
	}
}

1074
static gboolean
1075 1076 1077
glade_project_read_requires (GladeProject *project,
			     GladeXmlNode *root_node, 
			     const gchar  *path)
1078 1079 1080 1081 1082 1083
{

	GString      *string = g_string_new (NULL);
	GladeXmlNode *node;
	gchar        *required_lib;
	gboolean      loadable = TRUE;
1084
	gint          major, minor;
1085 1086 1087 1088

	for (node = glade_xml_node_get_children (root_node); 
	     node; node = glade_xml_node_next (node))
	{
1089
		/* Skip non "requires" tags */
1090 1091 1092 1093 1094 1095 1096
		if (!glade_xml_node_verify_silent (node, GLADE_XML_TAG_REQUIRES))
			continue;

		if ((required_lib = 
		     glade_xml_get_property_string_required (node, GLADE_XML_TAG_LIB, 
							     NULL)) != NULL)
		{
1097 1098 1099
			/* Dont mention gtk+ as a required lib in 
			 * the generated glade file
			 */
1100 1101 1102 1103 1104 1105 1106 1107
			if (!glade_catalog_is_loaded (required_lib))
			{
				if (!loadable)
					g_string_append (string, ", ");

				g_string_append (string, required_lib);
				loadable = FALSE;
			}
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
			else if (glade_xml_get_property_version (node, 
								 GLADE_XML_TAG_VERSION, 
								 &major, &minor))
				glade_project_set_target_version
					(project, required_lib, major, minor);

			g_free (required_lib);
		}
	}


	/* We use a different tag to save target version metadata in libglade files */
	for (node = glade_xml_node_get_children (root_node); 
	     node; node = glade_xml_node_next (node))
	{
		/* Skip non "requires" tags */
		if (!glade_xml_node_verify_silent (node, GLADE_XML_TAG_REQUIRES_LIBGLADE_EXTRA))
			continue;

		if ((required_lib = 
		     glade_xml_get_property_string_required (node, GLADE_XML_TAG_LIB, 
							     NULL)) != NULL)
		{
			if (glade_xml_get_property_version (node, 
							    GLADE_XML_TAG_VERSION, 
							    &major, &minor))
				glade_project_set_target_version
					(project, required_lib, major, minor);


			g_free (required_lib);
1139 1140 1141
		}
	}

1142

1143 1144
	if (!loadable)
		glade_util_ui_message (glade_app_get_window(),
1145
				       GLADE_UI_ERROR, NULL,
1146 1147 1148 1149 1150 1151 1152
				       _("Failed to load %s.\n"
					 "The following required catalogs are unavailable: %s"),
				       path, string->str);
	g_string_free (string, TRUE);
	return loadable;
}

1153 1154 1155 1156 1157 1158 1159 1160
static void
glade_project_read_comment (GladeProject *project, GladeXmlDoc *doc)
{
	/* TODO Write me !! Find out how to extract root level comments 
	 * with libxml2 !!! 
	 */
}

1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361
static GList *
glade_project_get_factory_stock_id_props (GladeProject *project)
{
	GladeWidget *widget;
	GladeProperty *property;
	GList *list, *l;
	GList *properties = NULL;

	for (list = project->priv->objects; list; list = list->next)
	{
		widget = glade_widget_get_from_gobject (list->data);

		for (l = widget->properties; l; l = l->next)
		{
			property = l->data;
			if (property->klass->factory_stock_id &&
			    property->enabled &&
			    !glade_property_default (property))
				properties = g_list_prepend (properties, property);
		}

		for (l = widget->packing_properties; l; l = l->next)
		{
			property = l->data;
			if (property->klass->factory_stock_id &&
			    property->enabled &&
			    !glade_property_default (property))
				properties = g_list_prepend (properties, property);
		}

	}

	return properties;
}

static void
glade_project_generate_nodes (GladeProject    *project, 
			      GladeXmlContext *context,
			      GladeXmlNode    *node)

{
	GladeProperty *property;
	GladeXmlNode  *widget_node;
	GladeXmlNode  *source_node;
	GladeXmlNode  *sources_node;
	GList         *properties, *list;
	gchar         *icon_name, *filename;

	if (project->priv->format == GLADE_PROJECT_FORMAT_GTKBUILDER &&
	    (properties = glade_project_get_factory_stock_id_props (project)))
	{

		widget_node = glade_xml_node_new
			(context, GLADE_XML_TAG_WIDGET (project->priv->format));
		glade_xml_node_append_child (node, widget_node);

		/* Set class and id */
		glade_xml_node_set_property_string (widget_node, 
						    GLADE_XML_TAG_CLASS, 
						    GLADE_ICON_FACTORY_CLASS_NAME);
		glade_xml_node_set_property_string (widget_node, 
						    GLADE_XML_TAG_ID, 
						    GLADE_GENERATED_ICON_FACTORY_NAME);
		

		sources_node = glade_xml_node_new (context, GLADE_XML_TAG_SOURCES);
		glade_xml_node_append_child (widget_node, sources_node);

		for (list = properties; list; list = list->next)
		{
			property = list->data;

			source_node = glade_xml_node_new (context, GLADE_XML_TAG_SOURCE);
			glade_xml_node_append_child (sources_node, source_node);

			if ((filename = glade_widget_adaptor_string_from_value
			     (GLADE_WIDGET_ADAPTOR (property->klass->handle),
			      property->klass, property->value)) != NULL)
			{
				icon_name = glade_util_filename_to_icon_name (filename);

				/* Set stock-id and filename */
				glade_xml_node_set_property_string
					(source_node, 
					 GLADE_XML_TAG_STOCK_ID, 
					 icon_name);
				glade_xml_node_set_property_string (source_node, 
								    GLADE_XML_TAG_FILENAME, 
								    filename);
				g_free (icon_name);
				g_free (filename);
			}
		}
		g_list_free (properties);
	}
}

gboolean
glade_project_is_loaded_factory_file (GladeProject *project, 
				      const gchar  *stock_id)
{
	GList *list;
	StockFilePair *pair;

	for (list = project->priv->loaded_factory_files;
	     list; list = list->next)
	{
		pair = list->data;
		if (!strcmp (stock_id, pair->stock))
			return TRUE;
	}
	return FALSE;
}

static void
glade_project_free_loaded_factory_files (GladeProject *project)
{
	GList *list;
	StockFilePair *pair;

	for (list = project->priv->loaded_factory_files;
	     list; list = list->next)
	{
		pair = list->data;
		g_free (pair->stock);
		g_free (pair->filename);
		g_free (pair);
	}
	g_list_free (project->priv->loaded_factory_files);
	project->priv->loaded_factory_files = NULL;
}

static void
glade_project_read_factory_files (GladeProject *project, 
				  GladeXmlNode *node)
{
	GladeXmlNode  *source;
	StockFilePair *pair;

	if ((source = 
	     glade_xml_search_child_required (node, GLADE_XML_TAG_SOURCES)))
	{
		for (source = glade_xml_node_get_children (source);
		     source; source = glade_xml_node_next (source))
		{
			if (!glade_xml_node_verify (source, GLADE_XML_TAG_SOURCE))
				continue;

			pair = g_new (StockFilePair, 1);
			
			pair->stock = glade_xml_get_property_string_required 
				(source, GLADE_XML_TAG_STOCK_ID, NULL);
			pair->filename = glade_xml_get_property_string_required 
				(source, GLADE_XML_TAG_FILENAME, NULL);

			if (!pair->stock || !pair->filename)
			{
				g_free (pair->stock);
				g_free (pair->filename);
				g_free (pair);
				continue;
			}
			
			project->priv->loaded_factory_files =
				g_list_prepend (project->priv->loaded_factory_files, pair);
		}
	}
}

static gboolean
glade_project_is_generated_node (GladeProject *project, 
				 GladeXmlNode *node)
{
	gboolean  generated = FALSE;
	gchar    *klass, *id;

	if (!glade_xml_node_verify
	    (node, GLADE_XML_TAG_WIDGET (project->priv->format)))
		return FALSE;

	if ((klass = 
	     glade_xml_get_property_string_required
	     (node, GLADE_XML_TAG_CLASS, NULL)) != NULL)
	{
		if ((id = 
		     glade_xml_get_property_string_required
		     (node, GLADE_XML_TAG_ID, NULL)) != NULL)
		{
			if (!strcmp (klass, GLADE_ICON_FACTORY_CLASS_NAME) &&
			    !strcmp (id, GLADE_GENERATED_ICON_FACTORY_NAME))
			{
				/* Read in the generated stock names and files */
				glade_project_read_factory_files (project, node);
				generated = TRUE;
			}
			g_free (id);
		}
		g_free (klass);
	}
	return generated;
}
1362

1363 1364 1365
gboolean
glade_project_load_from_file (GladeProject *project, const gchar *path)
{
1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
	GladeXmlContext *context;
	GladeXmlDoc     *doc;
	GladeXmlNode    *root;
	GladeXmlNode    *node;
	GladeWidget     *widget;

	project->priv->selection = NULL;
	project->priv->objects = NULL;
	project->priv->loading = TRUE;

1376
	project->priv->path = glade_util_canonical_path (path);	
1377 1378 1379 1380 1381

	/* get the context & root node of the catalog file */
	if (!(context = 
	      glade_xml_context_new_from_path (path,
					       NULL, 
1382
					       NULL)))
1383 1384 1385 1386 1387 1388 1389 1390
	{
		g_warning ("Couldn't open glade file [%s].", path);
		return FALSE;
	}

	doc  = glade_xml_context_get_doc (context);
	root = glade_xml_doc_get_root (doc);

1391 1392 1393 1394 1395
	if (glade_xml_node_verify_silent (root, GLADE_XML_TAG_LIBGLADE_PROJECT))
		project->priv->format = GLADE_PROJECT_FORMAT_LIBGLADE;
	else if (glade_xml_node_verify_silent (root, GLADE_XML_TAG_BUILDER_PROJECT))
		project->priv->format = GLADE_PROJECT_FORMAT_GTKBUILDER;
	else
1396
	{
1397
		g_warning ("Couldnt determine project format, skipping %s", path);
1398 1399 1400 1401
		glade_xml_context_free (context);
		return FALSE;
	}

1402 1403 1404
	/* XXX Need to load project->priv->comment ! */
	glade_project_read_comment (project, doc);

1405
	if (glade_project_read_requires (project, root, path) == FALSE)
1406 1407 1408 1409 1410 1411 1412 1413
	{
		glade_xml_context_free (context);
		return FALSE;
	}

	for (node = glade_xml_node_get_children (root); 
	     node; node = glade_xml_node_next (node))
	{
1414
		/* Skip "requires" tags */
1415 1416
		if (!glade_xml_node_verify_silent
		    (node, GLADE_XML_TAG_WIDGET (project->priv->format)))
1417 1418
			continue;

1419 1420 1421 1422
		/* Skip toplevel glade generated nodes */
		if (glade_project_is_generated_node (project, node))
			continue;

1423 1424 1425 1426 1427 1428 1429
		if ((widget = glade_widget_read (project, NULL, node, NULL)) != NULL)
			glade_project_add_object (project, NULL, widget->object);
	}

	if (glade_util_file_is_writeable (project->priv->path) == FALSE)
		glade_project_set_readonly (project, TRUE);

1430 1431
	/* Finished with the xml context */
	glade_xml_context_free (context);
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442

	project->priv->mtime = glade_util_get_file_mtime (project->priv->path, NULL);

	/* Reset project status here too so that you get a clean
	 * slate after calling glade_project_open().
	 */
	project->priv->modified = FALSE;
	project->priv->loading = FALSE;

	/* Emit "parse-finished" signal */
	g_signal_emit (project, glade_project_signals [PARSE_FINISHED], 0);
1443 1444 1445

	/* Free up some load time metadata */
	glade_project_free_loaded_factory_files (project);
1446 1447 1448 1449 1450 1451
	
	/* Now we have to loop over all the object properties
	 * and fix'em all ('cause they probably weren't found)
	 */
	glade_project_fix_object_props (project);

1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487
	/* Update ui with versioning info
	 */
	glade_project_verify_project_for_ui (project);

	return TRUE;

}



static void
glade_project_verify_property (GladeProperty  *property, 
			       const gchar    *path_name,
			       GString        *string,
			       gboolean        forwidget)
{
	GladeWidgetAdaptor *adaptor;
	gint target_major, target_minor;
	gchar *catalog, *tooltip;

	if (glade_property_original_default (property) && !forwidget)
		return;

	adaptor = GLADE_WIDGET_ADAPTOR (property->klass->origin_handle);
	
	g_object_get (adaptor, "catalog", &catalog, NULL);
	glade_project_target_version_for_adaptor (property->widget->project, adaptor, 
						  &target_major,
						  &target_minor);
	
	if (target_major < property->klass->version_since_major ||
	    (target_major == property->klass->version_since_major &&
	     target_minor < property->klass->version_since_minor))
	{
		if (forwidget)
		{
1488 1489 1490
			/* translators: reffers to a property introduced in toolkit
			 * version '%s %d.%d' and a project targeting toolkit
			 * version '%s %d.%d' */
1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503
			tooltip = g_strdup_printf
				(_("This property was introduced in %s %d.%d, "
				   "project targets %s %d.%d"),
				 catalog,
				 property->klass->version_since_major,
				 property->klass->version_since_minor,
				 catalog,
				 target_major, target_minor);

			glade_property_set_support_warning (property, tooltip);
			g_free (tooltip);
		}
		else
1504 1505
			/* translators: reffers to a property '%s' of widget '[%s]' 
			 * introduced in toolkit version '%s %d.%d' */
1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579
			g_string_append_printf
				(string,
				 property->klass->packing ?
				 _("[%s] Packing property '%s' of object class '%s' was "
				   "introduced in %s %d.%d\n") :
				 _("[%s] Property '%s' of object class '%s' was "
				   "introduced in %s %d.%d\n"),
				 path_name,
				 property->klass->name, 
				 adaptor->title, catalog,
				 property->klass->version_since_major,
				 property->klass->version_since_minor);
	} 
	else if (forwidget)
		glade_property_set_support_warning (property, NULL);

	g_free (catalog);
		
}


static void
glade_project_verify_properties_internal (GladeWidget  *widget, 
					  const gchar  *path_name,
					  GString      *string,
					  gboolean      forwidget)
{
	GList *list;
	GladeProperty *property;

	for (list = widget->properties; list; list = list->next)
	{
		property = list->data;
		glade_project_verify_property (property, path_name, string, forwidget);
	}

	for (list = widget->packing_properties; list; list = list->next)
	{
		property = list->data;

		g_assert (widget->parent);
		property = list->data;
		glade_project_verify_property (property, path_name, string, forwidget);
	}
}


/**
 * glade_project_verify_properties:
 * @widget: A #GladeWidget
 *
 * Synchonizes @widget with user visible information
 * about version compatability
 */
void
glade_project_verify_properties (GladeWidget *widget)
{
	g_return_if_fail (GLADE_IS_WIDGET (widget));
	glade_project_verify_properties_internal (widget, NULL, NULL, TRUE);
}

static void
glade_project_verify_signal (GladeWidget  *widget,
			     GladeSignal  *signal,
			     const gchar  *path_name,
			     GString      *string)
{
	GladeSignalClass *signal_class;
	gint target_major, target_minor;
	gchar *catalog;

	signal_class = 
		glade_widget_adaptor_get_signal_class (widget->adaptor,
						       signal->name);
1580 1581 1582
	//* Cannot verify unknown signal */
	if (!signal_class)
		return;
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
	g_assert (signal_class->adaptor);
	
	g_object_get (signal_class->adaptor, "catalog", &catalog, NULL);
	glade_project_target_version_for_adaptor (widget->project, 
						  signal_class->adaptor, 
						  &target_major,
						  &target_minor);

	if (target_major < signal_class->version_since_major ||
	    (target_major == signal_class->version_since_major &&
	     target_minor < signal_class->version_since_minor))
1594 1595
		/* translators: reffers to a signal '%s' of widget '[%s]' 
		 * introduced in toolkit version '%s %d.%d' */
1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686
		g_string_append_printf
			(string, 
			 _("[%s] Signal '%s' of object class '%s' was "
			   "introduced in %s %d.%d\n"),
			 path_name,
			 signal->name,
			 signal_class->adaptor->title, 
			 catalog,
			 signal_class->version_since_major,
			 signal_class->version_since_minor);

	g_free (catalog);
}


static void
glade_project_verify_signals (GladeWidget  *widget, 
			      const gchar  *path_name,
			      GString      *string)
{
	GladeSignal      *signal;
	GList *signals, *list;
	
	if ((signals = glade_widget_get_signal_list (widget)) != NULL)
	{
		for (list = signals; list; list = list->next)
		{
			signal = list->data;
			glade_project_verify_signal (widget, signal, path_name, string);
		}
		g_list_free (signals);
	}	
}

static gboolean
glade_project_verify_dialog (GladeProject *project,
			     GString      *string,
			     gboolean      saving)
{
	GtkWidget     *swindow;
	GtkWidget     *textview;
	GtkWidget     *expander;
	GtkTextBuffer *buffer;
	gchar         *name;
	gboolean ret;

	swindow   = gtk_scrolled_window_new (NULL, NULL);
	textview  = gtk_text_view_new ();
	buffer    = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
	expander  = gtk_expander_new (_("Details"));

	gtk_text_buffer_set_text (buffer, string->str, -1);

	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (swindow),
					       textview);
	gtk_container_add (GTK_CONTAINER (expander), swindow);
	gtk_widget_show_all (expander);

	gtk_widget_set_size_request (swindow, 800, -1);
	
	name = glade_project_get_name (project);
	ret = glade_util_ui_message (glade_app_get_window (),
				     saving ? GLADE_UI_YES_OR_NO : GLADE_UI_INFO,
				     expander,
				     saving ? 
				     _("Project %s has errors, save anyway ?") :
				     _("Project %s has deprecated widgets "
				       "and/or version mismatches."), name);
	g_free (name);

	return ret;
}


static gboolean
glade_project_verify (GladeProject *project, 
		      gboolean      saving)
{
	GString     *string = g_string_new (NULL);
	GladeWidget *widget;
	GList       *list;
	gboolean     ret = TRUE;
	gchar       *path_name;

	for (list = project->priv->objects; list; list = list->next)
	{
		widget = glade_widget_get_from_gobject (list->data);

		path_name = glade_widget_generate_path_name (widget);

		glade_project_verify_adaptor (project, widget->adaptor, 
1687
					      path_name, string, saving, FALSE, NULL);
1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702
		glade_project_verify_properties_internal (widget, path_name, string, FALSE);
		glade_project_verify_signals (widget, path_name, string);

		g_free (path_name);
	}

	if (string->len > 0)
	{
		ret = glade_project_verify_dialog (project, string, saving);

		if (!saving)
			ret = FALSE;
	}

	g_string_free (string, TRUE);
1703

1704
	return ret;
Jose Maria Celorio's avatar
Jose Maria Celorio committed
1705 1706
}

1707

1708 1709 1710 1711 1712 1713
/**
 * glade_project_selection_changed:
 * @project: a #GladeProject
 *
 * Causes @project to emit a "selection_changed" signal.
 */
1714 1715 1716
void
glade_project_selection_changed (GladeProject *project)
{
1717
	g_return_if_fail (GLADE_IS_PROJECT (project));
1718 1719 1720
	g_signal_emit (G_OBJECT (project),
		       glade_project_signals [SELECTION_CHANGED],
		       0);
1721 1722
}

1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733
static void
glade_project_on_widget_notify (GladeWidget *widget, GParamSpec *arg, GladeProject *project)
{
	g_return_if_fail (GLADE_IS_WIDGET (widget));
	g_return_if_fail (GLADE_IS_PROJECT (project));

	switch (arg->name[0])
	{
	case 'n':
		if (strcmp (arg->name, "name") == 0)
		{
1734
			const char *old_name = g_hash_table_lookup (project->priv->widget_old_names, widget);
1735
			glade_project_widget_name_changed (project, widget, old_name);
1736
			g_hash_table_insert (project->priv->widget_old_names, widget, g_strdup (glade_widget_get_name (widget)));
1737 1738 1739 1740
		}

	case 'p':
		if (strcmp (arg->name, "project") == 0)
1741
			glade_project_remove_object (project, glade_widget_get_object (widget));
1742 1743 1744
	}
}

1745 1746 1747 1748

static void
gp_sync_resources (GladeProject *project, 
		   GladeProject *prev_project,
1749 1750
		   GladeWidget  *gwidget,
		   gboolean      remove)
1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762
{
	GList          *prop_list, *l;
	GladeProperty  *property;
	gchar          *resource, *full_resource;

	prop_list = g_list_copy (gwidget->properties);
	prop_list = g_list_concat
		(prop_list, g_list_copy (gwidget->packing_properties));

	for (l = prop_list; l; l = l->next)
	{
		property = l->data;
1763
		if (property->klass->resource)
1764 1765
		{
			GValue value = { 0, };
1766 1767 1768 1769 1770 1771 1772

			if (remove)
			{
				glade_project_set_resource (project, property, NULL);
				continue;
			}

1773 1774
			glade_property_get_value (property, &value);
			
1775 1776 1777
			if ((resource = glade_widget_adaptor_string_from_value
			     (GLADE_WIDGET_ADAPTOR (property->klass->handle),
			      property->klass, &value)) != NULL)
1778 1779 1780
			{
				full_resource = glade_project_resource_fullpath
					(prev_project ? prev_project : project, resource);
1781
			
1782 1783 1784 1785 1786 1787 1788 1789 1790 1791
				/* Use a full path here so that the current
				 * working directory isnt used.
				 */
				glade_project_set_resource (project, 
							    property,
							    full_resource);
				
				g_free (full_resource);
				g_free (resource);
			}
1792 1793 1794 1795 1796 1797 1798 1799 1800
			g_value_unset (&value);
		}
	}
	g_list_free (prop_list);
}

static void
glade_project_sync_resources_for_widget (GladeProject *project, 
					 GladeProject *prev_project,
1801 1802
					 GladeWidget  *gwidget,
					 gboolean      remove)
1803 1804 1805 1806
{
	GList *children, *l;
	GladeWidget *gchild;

1807 1808
	children = glade_widget_adaptor_get_children
		(gwidget->adaptor, gwidget->object);
1809 1810 1811 1812 1813

	for (l = children; l; l = l->next)
		if ((gchild = 
		     glade_widget_get_from_gobject (l->data)) != NULL)
			glade_project_sync_resources_for_widget 
1814
				(project, prev_project, gchild, remove);
1815 1816 1817
	if (children)
		g_list_free (children);

1818
	gp_sync_resources (project, prev_project, gwidget, remove);
1819 1820
}

1821
/**
1822 1823
 * glade_project_add_object:
 * @project: the #GladeProject the widget is added to
Tristan Van Berkom's avatar
Tristan Van Berkom committed
1824 1825
 * @old_project: the #GladeProject the widget was previously in
 *               (or %NULL for the clipboard)
Tristan Van Berkom's avatar