/* -*- 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 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Authors: * Chema Celorio */ #include /** * 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. */ #include #include #include #include #include #include "glade.h" #include "glade-widget.h" #include "glade-id-allocator.h" #include "glade-app.h" #include "glade-marshallers.h" #include "glade-catalog.h" #include "glade-project.h" #define GLADE_PROJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GLADE_TYPE_PROJECT, GladeProjectPrivate)) enum { ADD_WIDGET, REMOVE_WIDGET, WIDGET_NAME_CHANGED, SELECTION_CHANGED, CLOSE, RESOURCE_ADDED, RESOURCE_REMOVED, CHANGED, PARSE_FINISHED, LAST_SIGNAL }; enum { PROP_0, PROP_MODIFIED, PROP_HAS_SELECTION, PROP_PATH, PROP_READ_ONLY }; struct _GladeProjectPrivate { gchar *path; /* The full canonical path of the glade file for this project */ guint instance_count; /* How many projects with this name */ gint unsaved_number; /* A unique number for this project if it is untitled */ gboolean readonly; /* A flag that is set if the project is readonly */ gboolean loading; /* A flags that is set when the project is loading */ 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 */ 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. */ gboolean has_selection; /* Whether the project has a selection */ 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 */ GtkTooltips *tooltips; GladeCommand *first_modification; /* we record the first modification, so that we * can set "modification" to FALSE when we * undo this modification */ GtkAccelGroup *accel_group; GHashTable *resources; /* resource filenames & thier associated properties */ gchar *comment; /* XML comment, Glade will preserve whatever comment was * in file, so users can delete or change it. */ time_t mtime; /* last UTC modification time of file, or 0 if it could not be read */ }; static guint glade_project_signals[LAST_SIGNAL] = {0}; static GladeIDAllocator *unsaved_number_allocator = NULL; static gboolean glade_project_load_from_interface (GladeProject *project, GladeInterface *interface, const gchar *path); G_DEFINE_TYPE (GladeProject, glade_project, G_TYPE_OBJECT) /******************************************************************* GObjectClass *******************************************************************/ static GladeIDAllocator * get_unsaved_number_allocator (void) { if (unsaved_number_allocator == NULL) unsaved_number_allocator = glade_id_allocator_new (); return unsaved_number_allocator; } 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); glade_project_list_unref (project->priv->undo_stack); project->priv->undo_stack = NULL; /* Unparent all widgets in the heirarchy first * (Since we are bookkeeping exact reference counts, we * dont want the hierarchy to just get destroyed) */ for (list = project->priv->objects; list; list = list->next) { gwidget = glade_widget_get_from_gobject (list->data); if (gwidget->parent && gwidget->internal == NULL && glade_widget_adaptor_has_child (gwidget->parent->adaptor, gwidget->parent->object, gwidget->object)) glade_widget_remove_child (gwidget->parent, gwidget); } /* Remove objects from the project */ for (list = project->priv->objects; list; list = list->next) { gwidget = glade_widget_get_from_gobject (list->data); g_object_unref (G_OBJECT (list->data)); /* Remove the GladeProject reference */ g_object_unref (G_OBJECT (gwidget)); /* Remove the overall "Glade" reference */ } project->priv->objects = NULL; gtk_object_destroy (GTK_OBJECT (project->priv->tooltips)); project->priv->tooltips = NULL; G_OBJECT_CLASS (glade_project_parent_class)->dispose (object); } static void glade_project_finalize (GObject *object) { GladeProject *project = GLADE_PROJECT (object); g_free (project->priv->path); g_free (project->priv->comment); if (project->priv->unsaved_number > 0) glade_id_allocator_release (get_unsaved_number_allocator (), project->priv->unsaved_number); 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); G_OBJECT_CLASS (glade_project_parent_class)->finalize (object); } static void glade_project_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GladeProject *project = GLADE_PROJECT (object); switch (prop_id) { case PROP_MODIFIED: g_value_set_boolean (value, project->priv->modified); break; case PROP_HAS_SELECTION: g_value_set_boolean (value, project->priv->has_selection); break; case PROP_READ_ONLY: g_value_set_boolean (value, project->priv->readonly); break; case PROP_PATH: g_value_set_string (value, project->priv->path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * 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. * */ static void glade_project_set_modified (GladeProject *project, gboolean modified, GladeCommand *modification) { GladeProjectPrivate *priv = project->priv; if (priv->modified != modified) { priv->modified = !priv->modified; if (priv->modified) { g_assert (priv->first_modification == NULL); g_assert (modification != NULL); priv->first_modification = modification; } else { g_assert (priv->first_modification != NULL); priv->first_modification = NULL; } g_object_notify (G_OBJECT (project), "modified"); } } /******************************************************************* GladeProjectClass *******************************************************************/ static void glade_project_walk_back (GladeProject *project) { if (project->priv->prev_redo_item) project->priv->prev_redo_item = project->priv->prev_redo_item->prev; } static void glade_project_walk_forward (GladeProject *project) { if (project->priv->prev_redo_item) project->priv->prev_redo_item = project->priv->prev_redo_item->next; else project->priv->prev_redo_item = project->priv->undo_stack; } static void glade_project_undo_impl (GladeProject *project) { GladeCommand *cmd, *next_cmd; while ((cmd = glade_project_next_undo_item (project)) != NULL) { glade_command_undo (cmd); 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; } } 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; if ((l = project->priv->prev_redo_item) == NULL) return NULL; return GLADE_COMMAND (l->data); } static GladeCommand * glade_project_next_redo_item_impl (GladeProject *project) { GList *l; if ((l = project->priv->prev_redo_item) == NULL) return project->priv->undo_stack ? GLADE_COMMAND (project->priv->undo_stack->data) : NULL; else return l->next ? GLADE_COMMAND (l->next->data) : NULL; } static void glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd) { GladeProjectPrivate *priv = project->priv; GList *tmp_redo_item; /* If there are no "redo" items, and the last "undo" item unifies with us, then we collapse the two items in one and we're done */ if (priv->prev_redo_item != NULL && priv->prev_redo_item->next == NULL) { GladeCommand *cmd1 = priv->prev_redo_item->data; if (glade_command_unifies (cmd1, cmd)) { glade_command_collapse (cmd1, cmd); g_object_unref (cmd); g_signal_emit (G_OBJECT (project), glade_project_signals [CHANGED], 0, cmd1, TRUE); return; } } /* We should now free all the "redo" items */ tmp_redo_item = g_list_next (priv->prev_redo_item); while (tmp_redo_item) { g_assert (tmp_redo_item->data); /* just for safety, we might not need this */ if (GLADE_COMMAND (tmp_redo_item->data) == priv->first_modification) priv->first_modification = NULL; g_object_unref (G_OBJECT (tmp_redo_item->data)); tmp_redo_item = g_list_next (tmp_redo_item); } if (priv->prev_redo_item) { g_list_free (g_list_next (priv->prev_redo_item)); priv->prev_redo_item->next = NULL; } else { g_list_free (priv->undo_stack); priv->undo_stack = NULL; } /* and then push the new undo item */ priv->undo_stack = g_list_append (priv->undo_stack, cmd); if (project->priv->prev_redo_item == NULL) priv->prev_redo_item = priv->undo_stack; else priv->prev_redo_item = g_list_next (priv->prev_redo_item); 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) { if (!project->priv->loading) { /* if this command is the first modification to cause the project * to have unsaved changes, then we can now flag the project as unmodified */ if (command == project->priv->first_modification) glade_project_set_modified (project, FALSE, NULL); else glade_project_set_modified (project, TRUE, command); } glade_app_update_ui (); } /******************************************************************* Initializers *******************************************************************/ static void glade_project_init (GladeProject *project) { GladeProjectPrivate *priv; 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; priv->first_modification = NULL; 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->tooltips = gtk_tooltips_new (); priv->accel_group = NULL; priv->resources = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); priv->unsaved_number = glade_id_allocator_allocate (get_unsaved_number_allocator ()); } static void glade_project_class_init (GladeProjectClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->get_property = glade_project_get_property; object_class->finalize = glade_project_finalize; object_class->dispose = glade_project_dispose; 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; /** * 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. */ glade_project_signals[ADD_WIDGET] = g_signal_new ("add_widget", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeProjectClass, add_object), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); /** * 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. */ glade_project_signals[REMOVE_WIDGET] = g_signal_new ("remove_widget", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeProjectClass, remove_object), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); /** * GladeProject::widget-name-changed: * @gladeproject: the #GladeProject which received the signal. * @arg1: the #GladeWidget who's name changed. * * Emitted when @gwidget's name changes. */ glade_project_signals[WIDGET_NAME_CHANGED] = 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, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); /** * GladeProject::selection-changed: * @gladeproject: the #GladeProject which received the signal. * * Emitted when @gladeproject selection list changes. */ glade_project_signals[SELECTION_CHANGED] = 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); /** * GladeProject::close: * @gladeproject: the #GladeProject which received the signal. * * Emitted when a project is closing (a good time to clean up * any associated resources). */ 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); /** * 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 */ glade_project_signals[RESOURCE_ADDED] = g_signal_new ("resource-added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeProjectClass, resource_added), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * GladeProject::resource-removed: * @gladeproject: the #GladeProject which received the signal. * @arg1: the file's basename * * Emitted when a resource file is removed from @gladeproject */ 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); /** * 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); /** * 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, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_MODIFIED, g_param_spec_boolean ("modified", _(""), _("Whether project has been modified since it was last saved"), FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_HAS_SELECTION, g_param_spec_boolean ("has-selection", _("Has Selection"), _("Whether project has a selection"), FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_READ_ONLY, g_param_spec_boolean ("read-only", _("Read Only"), _("Whether project is read only or not"), FALSE, G_PARAM_READABLE)); 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)); } /******************************************************************* API *******************************************************************/ static void glade_project_set_readonly (GladeProject *project, gboolean readonly) { g_assert (GLADE_IS_PROJECT (project)); if (project->priv->readonly != readonly) { project->priv->readonly = readonly; 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) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->readonly; } /** * glade_project_new: * * Creates a new #GladeProject. * * Returns: a new #GladeProject */ GladeProject * glade_project_new (void) { return g_object_new (GLADE_TYPE_PROJECT, NULL); } gboolean glade_project_load_from_file (GladeProject *project, const gchar *path) { GladeInterface *interface; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); g_return_val_if_fail (path != NULL, FALSE); interface = glade_parser_interface_new_from_file (path, NULL); if (interface != NULL) { if (!glade_project_load_from_interface (project, interface, path)) { glade_parser_interface_destroy (interface); return FALSE; } glade_parser_interface_destroy (interface); } else { return FALSE; } if (glade_util_file_is_writeable (project->priv->path) == FALSE) glade_project_set_readonly (project, TRUE); project->priv->modified = FALSE; project->priv->mtime = glade_util_get_file_mtime (project->priv->path, NULL); return TRUE; } /** * glade_project_selection_changed: * @project: a #GladeProject * * Causes @project to emit a "selection_changed" signal. */ void glade_project_selection_changed (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_signal_emit (G_OBJECT (project), glade_project_signals [SELECTION_CHANGED], 0); } 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) { const char *old_name = g_hash_table_lookup (project->priv->widget_old_names, widget); glade_project_widget_name_changed (project, widget, old_name); g_hash_table_insert (project->priv->widget_old_names, widget, g_strdup (glade_widget_get_name (widget))); } case 'p': if (strcmp (arg->name, "project") == 0) glade_project_remove_object (project, glade_widget_get_object (widget)); } } static void gp_sync_resources (GladeProject *project, GladeProject *prev_project, GladeWidget *gwidget, gboolean remove) { 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; if (property->klass->resource) { GValue value = { 0, }; if (remove) { glade_project_set_resource (project, property, NULL); continue; } glade_property_get_value (property, &value); if ((resource = glade_property_class_make_string_from_gvalue (property->klass, &value)) != NULL) { full_resource = glade_project_resource_fullpath (prev_project ? prev_project : project, resource); /* 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); } g_value_unset (&value); } } g_list_free (prop_list); } static void glade_project_sync_resources_for_widget (GladeProject *project, GladeProject *prev_project, GladeWidget *gwidget, gboolean remove) { GList *children, *l; GladeWidget *gchild; children = glade_widget_adaptor_get_children (gwidget->adaptor, gwidget->object); for (l = children; l; l = l->next) if ((gchild = glade_widget_get_from_gobject (l->data)) != NULL) glade_project_sync_resources_for_widget (project, prev_project, gchild, remove); if (children) g_list_free (children); gp_sync_resources (project, prev_project, gwidget, remove); } /** * glade_project_add_object: * @project: the #GladeProject the widget is added to * @old_project: the #GladeProject the widget was previously in * (or %NULL for the clipboard) * @object: the #GObject to add * * Adds an object to the project. */ void glade_project_add_object (GladeProject *project, GladeProject *old_project, GObject *object) { GladeWidget *gwidget; GList *list, *children; GtkWindow *transient_parent; static gint reentrancy_count = 0; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (G_IS_OBJECT (object)); /* We don't list placeholders */ if (GLADE_IS_PLACEHOLDER (object)) return; /* Only widgets accounted for in the catalog or widgets declared * in the plugin with glade_widget_new_for_internal_child () are * usefull in the project. */ if ((gwidget = glade_widget_get_from_gobject (object)) == NULL) return; /* Dont add widgets that are already in the project */ if (glade_project_has_object (project, object)) return; /* Code body starts here */ reentrancy_count++; if ((children = glade_widget_adaptor_get_children (gwidget->adaptor, gwidget->object)) != NULL) { for (list = children; list && list->data; list = list->next) glade_project_add_object (project, old_project, G_OBJECT (list->data)); g_list_free (children); } glade_widget_set_project (gwidget, (gpointer)project); g_hash_table_insert (project->priv->widget_old_names, gwidget, g_strdup (glade_widget_get_name (gwidget))); g_signal_connect (G_OBJECT (gwidget), "notify", (GCallback) glade_project_on_widget_notify, project); project->priv->objects = g_list_prepend (project->priv->objects, g_object_ref (object)); g_signal_emit (G_OBJECT (project), glade_project_signals [ADD_WIDGET], 0, gwidget); if (GTK_IS_WINDOW (object) && (transient_parent = glade_app_get_transient_parent ()) != NULL) gtk_window_set_transient_for (GTK_WINDOW (object), transient_parent); /* Notify widget it was added to the project */ glade_widget_project_notify (gwidget, project); /* Call this once at the end for every recursive call */ if (--reentrancy_count == 0) glade_project_sync_resources_for_widget (project, old_project, gwidget, FALSE); } /** * glade_project_has_object: * @project: the #GladeProject the widget is added to * @object: the #GObject to search * * Returns whether this object is in this project. */ gboolean glade_project_has_object (GladeProject *project, GObject *object) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); g_return_val_if_fail (G_IS_OBJECT (object), FALSE); return (g_list_find (project->priv->objects, object)) != NULL; } /** * glade_project_release_widget_name: * @project: a #GladeProject * @glade_widget: * @widget_name: * * TODO: Write me */ static void glade_project_release_widget_name (GladeProject *project, GladeWidget *glade_widget, const char *widget_name) { const char *first_number = widget_name; char *end_number; char *base_widget_name; GladeIDAllocator *id_allocator; gunichar ch; int id; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_WIDGET (glade_widget)); do { ch = g_utf8_get_char (first_number); if (ch == 0 || g_unichar_isdigit (ch)) break; first_number = g_utf8_next_char (first_number); } while (TRUE); if (ch == 0) return; base_widget_name = g_strdup (widget_name); *(base_widget_name + (first_number - widget_name)) = 0; id_allocator = g_hash_table_lookup (project->priv->widget_names_allocator, base_widget_name); if (id_allocator == NULL) goto lblend; id = (int) strtol (first_number, &end_number, 10); if (*end_number != 0) goto lblend; glade_id_allocator_release (id_allocator, id); lblend: g_hash_table_remove (project->priv->widget_old_names, glade_widget); g_free (base_widget_name); } /** * glade_project_remove_object: * @project: a #GladeProject * @object: the #GObject to remove * * Removes @object from @project. * * Note that when removing the #GObject from the project we * don't change ->project in the associated #GladeWidget; this * way UNDO can work. */ void glade_project_remove_object (GladeProject *project, GObject *object) { GladeWidget *gwidget; GList *link, *list, *children; static gint reentrancy_count = 0; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (G_IS_OBJECT (object)); if (GLADE_IS_PLACEHOLDER (object)) return; if ((gwidget = glade_widget_get_from_gobject (object)) == NULL) return; /* Code body starts here */ reentrancy_count++; /* Notify widget is being removed from the project */ glade_widget_project_notify (gwidget, NULL); if ((children = glade_widget_adaptor_get_children (gwidget->adaptor, gwidget->object)) != NULL) { for (list = children; list && list->data; list = list->next) glade_project_remove_object (project, G_OBJECT (list->data)); g_list_free (children); } glade_project_selection_remove (project, object, TRUE); if ((link = g_list_find (project->priv->objects, object)) != NULL) { g_object_unref (object); glade_project_release_widget_name (project, gwidget, glade_widget_get_name (gwidget)); project->priv->objects = g_list_delete_link (project->priv->objects, link); } g_signal_emit (G_OBJECT (project), glade_project_signals [REMOVE_WIDGET], 0, gwidget); /* Call this once at the end for every recursive call */ if (--reentrancy_count == 0) glade_project_sync_resources_for_widget (project, NULL, gwidget, TRUE); } /** * glade_project_widget_name_changed: * @project: a #GladeProject * @widget: a #GladeWidget * @old_name: * * TODO: write me */ void glade_project_widget_name_changed (GladeProject *project, GladeWidget *widget, const char *old_name) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_WIDGET (widget)); glade_project_release_widget_name (project, widget, old_name); g_signal_emit (G_OBJECT (project), glade_project_signals [WIDGET_NAME_CHANGED], 0, widget); } /** * glade_project_get_widget_by_name: * @project: a #GladeProject * @name: The user visible name of the widget we are looking for * * Searches through @project looking for a #GladeWidget named @name. * * Returns: a pointer to the widget, %NULL if the widget does not exist */ GladeWidget * glade_project_get_widget_by_name (GladeProject *project, const gchar *name) { GList *list; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); g_return_val_if_fail (name != NULL, NULL); for (list = project->priv->objects; list; list = list->next) { GladeWidget *widget; widget = glade_widget_get_from_gobject (list->data); g_return_val_if_fail (widget->name != NULL, NULL); if (strcmp (widget->name, name) == 0) return widget; } return NULL; } /** * glade_project_new_widget_name: * @project: a #GladeProject * @base_name: base name of the widget to create * * Creates a new name for a widget that doesn't collide with any of the names * already in @project. This name will start with @base_name. * * Returns: a string containing the new name, %NULL if there is not enough * memory for this string */ char * glade_project_new_widget_name (GladeProject *project, const char *base_name) { GladeIDAllocator *id_allocator; const gchar *number; gchar *name = NULL, *freeme = NULL; guint i = 1; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); number = base_name + strlen (base_name); while (number > base_name && g_ascii_isdigit (number[-1])) --number; if (*number) { freeme = g_strndup (base_name, number - base_name); base_name = freeme; } id_allocator = g_hash_table_lookup (project->priv->widget_names_allocator, base_name); if (id_allocator == NULL) { id_allocator = glade_id_allocator_new (); g_hash_table_insert (project->priv->widget_names_allocator, g_strdup (base_name), id_allocator); } while (TRUE) { i = glade_id_allocator_allocate (id_allocator); name = g_strdup_printf ("%s%u", base_name, i); /* ok, there is no widget with this name, so return the name */ if (glade_project_get_widget_by_name (project, name) == NULL) return name; /* we already have a widget with this name, so free the name and * try again with another number */ g_free (name); i++; } g_free (freeme); return name; } static void glade_project_set_has_selection (GladeProject *project, gboolean has_selection) { g_assert (GLADE_IS_PROJECT (project)); if (project->priv->has_selection != has_selection) { project->priv->has_selection = has_selection; g_object_notify (G_OBJECT (project), "has-selection"); } } /** * glade_project_get_has_selection: * @project: a #GladeProject * * Returns: whether @project currently has a selection */ gboolean glade_project_get_has_selection (GladeProject *project) { g_assert (GLADE_IS_PROJECT (project)); return project->priv->has_selection; } /** * glade_project_is_selected: * @project: a #GladeProject * @object: a #GObject * * Returns: whether @object is in @project selection */ gboolean glade_project_is_selected (GladeProject *project, GObject *object) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return (g_list_find (project->priv->selection, object)) != NULL; } /** * glade_project_selection_clear: * @project: a #GladeProject * @emit_signal: whether or not to emit a signal indication a selection change * * Clears @project's selection chain * * If @emit_signal is %TRUE, calls glade_project_selection_changed(). */ void glade_project_selection_clear (GladeProject *project, gboolean emit_signal) { g_return_if_fail (GLADE_IS_PROJECT (project)); if (project->priv->selection == NULL) return; glade_util_clear_selection (); g_list_free (project->priv->selection); project->priv->selection = NULL; glade_project_set_has_selection (project, FALSE); if (emit_signal) glade_project_selection_changed (project); } /** * glade_project_selection_remove: * @project: a #GladeProject * @object: a #GObject in @project * @emit_signal: whether or not to emit a signal * indicating a selection change * * Removes @object from the selection chain of @project * * If @emit_signal is %TRUE, calls glade_project_selection_changed(). */ void glade_project_selection_remove (GladeProject *project, GObject *object, gboolean emit_signal) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (G_IS_OBJECT (object)); if (glade_project_is_selected (project, object)) { if (GTK_IS_WIDGET (object)) glade_util_remove_selection (GTK_WIDGET (object)); project->priv->selection = g_list_remove (project->priv->selection, object); if (project->priv->selection == NULL) glade_project_set_has_selection (project, FALSE); if (emit_signal) glade_project_selection_changed (project); } } /** * glade_project_selection_add: * @project: a #GladeProject * @object: a #GObject in @project * @emit_signal: whether or not to emit a signal indicating * a selection change * * Adds @object to the selection chain of @project * * If @emit_signal is %TRUE, calls glade_project_selection_changed(). */ void glade_project_selection_add (GladeProject *project, GObject *object, gboolean emit_signal) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (g_list_find (project->priv->objects, object) != NULL); if (glade_project_is_selected (project, object) == FALSE) { if (GTK_IS_WIDGET (object)) glade_util_add_selection (GTK_WIDGET (object)); if (project->priv->selection == NULL) glade_project_set_has_selection (project, TRUE); project->priv->selection = g_list_prepend (project->priv->selection, object); if (emit_signal) glade_project_selection_changed (project); } } /** * glade_project_selection_set: * @project: a #GladeProject * @object: a #GObject in @project * @emit_signal: whether or not to emit a signal * indicating a selection change * * Set the selection in @project to @object * * If @emit_signal is %TRUE, calls glade_project_selection_changed(). */ void glade_project_selection_set (GladeProject *project, GObject *object, gboolean emit_signal) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (G_IS_OBJECT (object)); if (g_list_find (project->priv->objects, object) == NULL) return; if (project->priv->selection == NULL) glade_project_set_has_selection (project, TRUE); if (glade_project_is_selected (project, object) == FALSE || g_list_length (project->priv->selection) != 1) { glade_project_selection_clear (project, FALSE); glade_project_selection_add (project, object, emit_signal); } } /** * glade_project_selection_get: * @project: a #GladeProject * * Returns: a #GList containing the #GtkWidget items currently selected in * @project */ GList * glade_project_selection_get (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->selection; } static GList * glade_project_required_libs (GladeProject *project) { GList *required = NULL, *l, *ll; GladeWidget *gwidget; gboolean listed; for (l = project->priv->objects; l; l = l->next) { gchar *catalog = NULL; gwidget = glade_widget_get_from_gobject (l->data); g_assert (gwidget); g_object_get (gwidget->adaptor, "catalog", &catalog, NULL); if (catalog) { listed = FALSE; for (ll = required; ll; ll = ll->next) if (!strcmp ((gchar *)ll->data, catalog)) { listed = TRUE; break; } if (!listed) required = g_list_prepend (required, catalog); } } return required; } #define GLADE_XML_COMMENT "Generated with "PACKAGE_NAME static gchar * glade_project_make_comment () { time_t now = time (NULL); gchar *comment; comment = g_strdup_printf (GLADE_XML_COMMENT" "PACKAGE_VERSION" on %s", ctime (&now)); glade_util_replace (comment, '\n', ' '); return comment; } static void glade_project_update_comment (GladeProject *project) { gchar **lines, **l, *comment = NULL; /* If project has no comment -> add the new one */ if (project->priv->comment == NULL) { project->priv->comment = glade_project_make_comment (); return; } for (lines = l = g_strsplit (project->priv->comment, "\n", 0); *l; l++) { gchar *start; /* Ignore leading spaces */ for (start = *l; *start && g_ascii_isspace (*start); start++); if (g_str_has_prefix (start, GLADE_XML_COMMENT)) { /* This line was generated by glade -> updating... */ g_free (*l); *l = comment = glade_project_make_comment (); } } if (comment) { g_free (project->priv->comment); project->priv->comment = g_strjoinv ("\n", lines); } g_strfreev (lines); } /** * glade_project_write: * @project: a #GladeProject * * Returns: a libglade's GladeInterface representation of the * project and its contents */ static GladeInterface * glade_project_write (GladeProject *project) { GladeInterface *interface; GList *required, *list, *tops = NULL; gchar **strv = NULL; guint i; interface = glade_parser_interface_new (); if ((required = glade_project_required_libs (project)) != NULL) { strv = g_malloc0 (g_list_length (required) * sizeof (char *)); for (i = 0, list = required; list; i++, list = list->next) strv[i] = list->data; /* list->data is allocated for us */ interface->n_requires = g_list_length (required); interface->requires = strv; g_list_free (required); } for (i = 0, list = project->priv->objects; list; list = list->next) { GladeWidget *widget; GladeWidgetInfo *info; widget = glade_widget_get_from_gobject (list->data); /* * Append toplevel widgets. Each widget then takes * care of appending its children. */ if (widget->parent == NULL) { info = glade_widget_write (widget, interface); if (!info) return NULL; tops = g_list_prepend (tops, info); ++i; } } interface->n_toplevels = i; interface->toplevels = (GladeWidgetInfo **) g_new (GladeWidgetInfo *, i); for (i = 0, list = tops; list; list = list->next, ++i) interface->toplevels[i] = list->data; g_list_free (tops); glade_project_update_comment (project); interface->comment = g_strdup (project->priv->comment); return interface; } static gboolean loadable_interface (GladeInterface *interface, const gchar *path) { GString *string = g_string_new (NULL); gboolean loadable = TRUE; guint i; /* Check for required plugins here */ for (i = 0; i < interface->n_requires; i++) if (!glade_catalog_is_loaded (interface->requires[i])) { g_string_append (string, interface->requires[i]); loadable = FALSE; } if (loadable == FALSE) glade_util_ui_message (glade_app_get_window(), GLADE_UI_ERROR, _("Failed to load %s.\n" "The following required catalogs are unavailable: %s"), path, string->str); g_string_free (string, TRUE); return loadable; } 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); } } } } static gboolean glade_project_load_from_interface (GladeProject *project, GladeInterface *interface, const gchar *path) { GladeWidget *widget; guint i; g_return_val_if_fail (project != NULL, FALSE); g_return_val_if_fail (interface != NULL, FALSE); g_return_val_if_fail (path != NULL, FALSE); if (loadable_interface (interface, path) == FALSE) return FALSE; project->priv->path = glade_util_canonical_path (path); project->priv->selection = NULL; project->priv->objects = NULL; project->priv->loading = TRUE; /* keep a comment */ if (interface->comment) project->priv->comment = g_strdup (interface->comment); for (i = 0; i < interface->n_toplevels; ++i) { widget = glade_widget_read ((gpointer) project, interface->toplevels[i]); if (!widget) { g_warning ("Failed to read a tag"); continue; } glade_project_add_object (project, NULL, widget->object); } /* 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); /* 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); return TRUE; } /** * glade_project_load: * @path: * * Opens a project at the given path. * * Returns: a new #GladeProject for the opened project on success, %NULL on * failure */ GladeProject * glade_project_load (const gchar *path) { GladeProject *project; gboolean retval; g_return_val_if_fail (path != NULL, NULL); project = glade_project_new (); retval = glade_project_load_from_file (project, path); if (retval) { return project; } else { g_object_unref (project); return NULL; } } static void glade_project_move_resources (GladeProject *project, const gchar *old_dir, const gchar *new_dir) { GList *list, *resources; gchar *old_name, *new_name; if (old_dir == NULL || /* <-- Cant help you :( */ new_dir == NULL) /* <-- Unlikely */ return; if ((resources = /* Nothing to do here */ glade_project_list_resources (project)) == NULL) return; for (list = resources; list; list = list->next) { old_name = g_build_filename (old_dir, (gchar *)list->data, NULL); new_name = g_build_filename (new_dir, (gchar *)list->data, NULL); glade_util_copy_file (old_name, new_name); g_free (old_name); g_free (new_name); } g_list_free (resources); } /** * glade_project_save: * @project: a #GladeProject * @path: location to save glade file * @error: an error from the G_FILE_ERROR domain. * * Saves @project to the given path. * * Returns: %TRUE on success, %FALSE on failure */ gboolean glade_project_save (GladeProject *project, const gchar *path, GError **error) { GladeInterface *interface; gboolean ret; gchar *canonical_path; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); g_return_val_if_fail (path != NULL, FALSE); interface = glade_project_write (project); if (!interface) { g_warning ("Could not write glade document\n"); return FALSE; } ret = glade_parser_interface_dump (interface, path, error); glade_parser_interface_destroy (interface); canonical_path = glade_util_canonical_path (path); g_assert (canonical_path); if (project->priv->path == NULL || strcmp (canonical_path, project->priv->path)) { gchar *old_dir, *new_dir; if (project->priv->path) { old_dir = g_path_get_dirname (project->priv->path); new_dir = g_path_get_dirname (canonical_path); glade_project_move_resources (project, old_dir, new_dir); g_free (old_dir); g_free (new_dir); } project->priv->path = (g_free (project->priv->path), g_strdup (canonical_path)); } glade_project_set_readonly (project, !glade_util_file_is_writeable (project->priv->path)); project->priv->mtime = glade_util_get_file_mtime (project->priv->path, NULL); glade_project_set_modified (project, FALSE, NULL); if (project->priv->unsaved_number > 0) { glade_id_allocator_release (get_unsaved_number_allocator (), project->priv->unsaved_number); project->priv->unsaved_number = 0; } g_free (canonical_path); return ret; } /** * glade_project_undo: * @project: a #GladeProject * * Undoes a #GladeCommand in this project. */ void glade_project_undo (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); GLADE_PROJECT_GET_CLASS (project)->undo (project); } /** * glade_project_undo: * @project: a #GladeProject * * Redoes a #GladeCommand in this project. */ void glade_project_redo (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); GLADE_PROJECT_GET_CLASS (project)->redo (project); } /** * glade_project_next_undo_item: * @project: a #GladeProject * * Gets the next undo item on @project's command stack. * * Returns: the #GladeCommand */ GladeCommand * glade_project_next_undo_item (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return GLADE_PROJECT_GET_CLASS (project)->next_undo_item (project); } /** * glade_project_next_redo_item: * @project: a #GladeProject * * Gets the next redo item on @project's command stack. * * Returns: the #GladeCommand */ GladeCommand * glade_project_next_redo_item (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return GLADE_PROJECT_GET_CLASS (project)->next_redo_item (project); } /** * glade_project_push_undo: * @project: a #GladeProject * @cmd: the #GladeCommand * * Pushes a newly created #GladeCommand onto @projects stack. */ void glade_project_push_undo (GladeProject *project, GladeCommand *cmd) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_COMMAND (cmd)); GLADE_PROJECT_GET_CLASS (project)->push_undo (project, cmd); } static GList * walk_command (GList *list, gboolean forward) { GladeCommand *cmd = list->data; GladeCommand *next_cmd; if (forward) list = list->next; else list = list->prev; next_cmd = list ? list->data : NULL; while (list && next_cmd->group_id != 0 && next_cmd->group_id == cmd->group_id) { if (forward) list = list->next; else list = list->prev; if (list) next_cmd = list->data; } return list; } static void undo_item_activated (GtkMenuItem *item, GladeProject *project) { gint index, next_index; GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data"); GladeCommand *next_cmd; index = g_list_index (project->priv->undo_stack, cmd); do { next_cmd = glade_project_next_undo_item (project); next_index = g_list_index (project->priv->undo_stack, next_cmd); glade_project_undo (project); } while (next_index > index); } static void redo_item_activated (GtkMenuItem *item, GladeProject *project) { gint index, next_index; GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data"); GladeCommand *next_cmd; index = g_list_index (project->priv->undo_stack, cmd); do { next_cmd = glade_project_next_redo_item (project); next_index = g_list_index (project->priv->undo_stack, next_cmd); glade_project_redo (project); } while (next_index < index); } /** * glade_project_undo_items: * @project: A #GladeProject * * Creates a menu of the undo items in the project stack * * Returns: A newly created menu */ GtkWidget * glade_project_undo_items (GladeProject *project) { GtkWidget *menu = NULL; GtkWidget *item; GladeCommand *cmd; GList *l; for (l = project->priv->prev_redo_item; l; l = walk_command (l, FALSE)) { cmd = l->data; if (!menu) menu = gtk_menu_new (); item = gtk_menu_item_new_with_label (cmd->description); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item)); g_object_set_data (G_OBJECT (item), "command-data", cmd); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (undo_item_activated), project); } return menu; } /** * glade_project_redo_items: * @project: A #GladeProject * * Creates a menu of the undo items in the project stack * * Returns: A newly created menu */ GtkWidget * glade_project_redo_items (GladeProject *project) { GtkWidget *menu = NULL; GtkWidget *item; GladeCommand *cmd; GList *l; for (l = project->priv->prev_redo_item ? project->priv->prev_redo_item->next : project->priv->undo_stack; l; l = walk_command (l, TRUE)) { cmd = l->data; if (!menu) menu = gtk_menu_new (); item = gtk_menu_item_new_with_label (cmd->description); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item)); g_object_set_data (G_OBJECT (item), "command-data", cmd); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (redo_item_activated), project); } return menu; } void glade_project_reset_path (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); project->priv->path = (g_free (project->priv->path), NULL); } /** * glade_project_get_tooltips: * @project: a #GladeProject * * Returns: a #GtkTooltips object containing all tooltips for @project */ GtkTooltips * glade_project_get_tooltips (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->tooltips; } /** * glade_project_set_accel_group: * @project: A #GladeProject * @accel_group: The @GtkAccelGroup * * Set @accel_group to every top level widget in @project. */ void glade_project_set_accel_group (GladeProject *project, GtkAccelGroup *accel_group) { GList *objects; g_return_if_fail (GLADE_IS_PROJECT (project) && GTK_IS_ACCEL_GROUP (accel_group)); objects = project->priv->objects; while (objects) { if(GTK_IS_WINDOW (objects->data)) { if (project->priv->accel_group) gtk_window_remove_accel_group (GTK_WINDOW (objects->data), project->priv->accel_group); gtk_window_add_accel_group (GTK_WINDOW (objects->data), accel_group); } objects = objects->next; } project->priv->accel_group = accel_group; } /** * glade_project_resource_fullpath: * @project: The #GladeProject. * @resource: The resource basename * * Returns: A newly allocated string holding the * full path the the project resource. */ gchar * glade_project_resource_fullpath (GladeProject *project, const gchar *resource) { gchar *fullpath, *project_dir; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); if (project->priv->path == NULL) return g_strdup (resource); project_dir = g_path_get_dirname (project->priv->path); fullpath = g_build_filename (project_dir, resource, NULL); g_free (project_dir); return fullpath; } static gboolean find_resource_by_resource (GladeProperty *key, const gchar *resource, const gchar *resource_cmp) { g_assert (resource); g_assert (resource_cmp); return (!strcmp (resource, resource_cmp)); } /** * glade_project_set_resource: * @project: A #GladeProject * @property: The #GladeProperty this resource is required by * @resource: The resource file basename to be found in the same * directory as the glade file. * * Adds/Modifies/Removes a resource from a project; any project resources * will be copied when using "Save As...", when moving widgets across projects * and will be copied into the project's directory when selected. */ void glade_project_set_resource (GladeProject *project, GladeProperty *property, const gchar *resource) { gchar *last_resource, *last_resource_dup = NULL, *base_resource = NULL; gchar *fullpath, *dirname; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_PROPERTY (property)); if ((last_resource = g_hash_table_lookup (project->priv->resources, property)) != NULL) last_resource_dup = g_strdup (last_resource); /* Get dependable input */ if (resource && resource[0] != '\0' && strcmp (resource, ".")) base_resource = g_path_get_basename (resource); /* If the resource has been removed or the base name has changed * then remove from hash and emit removed. */ if (last_resource_dup && (base_resource == NULL || strcmp (last_resource_dup, base_resource))) { g_hash_table_remove (project->priv->resources, property); if (g_hash_table_find (project->priv->resources, (GHRFunc)find_resource_by_resource, last_resource_dup) == NULL) g_signal_emit (G_OBJECT (project), glade_project_signals [RESOURCE_REMOVED], 0, last_resource_dup); } /* Copy files when importing widgets with resources. */ if (project->priv->path) { dirname = g_path_get_dirname (project->priv->path); fullpath = g_build_filename (dirname, base_resource, NULL); if (resource && project->priv->path && g_file_test (resource, G_FILE_TEST_IS_REGULAR) && strcmp (fullpath, resource)) { /* FIXME: In the case of copy/pasting widgets * across projects we should ask the user about * copying any resources. */ glade_util_copy_file (resource, fullpath); } g_free (fullpath); g_free (dirname); } if (base_resource) { /* If the resource has been added or the base name has * changed then emit added. */ if ((last_resource_dup == NULL || strcmp (last_resource_dup, base_resource)) && g_hash_table_find (project->priv->resources, (GHRFunc)find_resource_by_resource, base_resource) == NULL) g_signal_emit (G_OBJECT (project), glade_project_signals [RESOURCE_ADDED], 0, base_resource); g_hash_table_insert (project->priv->resources, property, base_resource); } g_free (last_resource_dup); } static void list_resources_accum (GladeProperty *key, gchar *value, GList **list) { *list = g_list_prepend (*list, value); } /** * glade_project_list_resources: * @project: A #GladeProject * * Returns: A newly allocated #GList of file basenames * of resources in this project, note that the * strings are not allocated and are unsafe to * use once the projects state changes. * The returned list should be freed with g_list_free. */ GList * glade_project_list_resources (GladeProject *project) { GList *list = NULL; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); g_hash_table_foreach (project->priv->resources, (GHFunc)list_resources_accum, &list); return list; } const gchar * glade_project_get_path (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->path; } gchar * glade_project_get_name (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); if (project->priv->path) return g_filename_display_basename (project->priv->path); else return g_strdup_printf (_("Unsaved %i"), project->priv->unsaved_number); } /** * glade_project_is_loading: * @project: A #GladeProject * * Returns: Whether the project is being loaded or not * */ gboolean glade_project_is_loading (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->loading; } time_t glade_project_get_file_mtime (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), 0); return project->priv->mtime; } const GList * glade_project_get_objects (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->objects; } guint glade_project_get_instance_count (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), 0); return project->priv->instance_count; } void glade_project_set_instance_count (GladeProject *project, guint instance_count) { g_return_if_fail (GLADE_IS_PROJECT (project)); project->priv->instance_count = instance_count; } /** * glade_project_get_modified: * @project: a #GladeProject * * Get's whether the project has been modified since it was last saved. * * Returns: #TRUE if the project has been modified since it was last saved */ gboolean glade_project_get_modified (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->modified; }