/* * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2008 Tristan Van Berkom * * 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 * Tristan Van Berkom */ #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 "gladeui-enum-types.h" #include "glade-widget.h" #include "glade-id-allocator.h" #include "glade-app.h" #include "glade-marshallers.h" #include "glade-catalog.h" #include "glade-preview.h" #include "glade-project.h" #include "glade-command.h" #include "glade-name-context.h" #include "glade-object-stub.h" #include "glade-project-properties.h" #include "glade-dnd.h" #include "glade-private.h" #include "glade-tsort.h" static void glade_project_target_version_for_adaptor (GladeProject *project, GladeWidgetAdaptor *adaptor, gint *major, gint *minor); static void glade_project_verify_properties (GladeWidget *widget); static void glade_project_verify_project_for_ui (GladeProject *project); static void glade_project_verify_adaptor (GladeProject *project, GladeWidgetAdaptor *adaptor, const gchar *path_name, GString *string, GladeVerifyFlags flags, gboolean forwidget, GladeSupportMask *mask); static void glade_project_set_readonly (GladeProject *project, gboolean readonly); static void glade_project_set_modified (GladeProject *project, gboolean modified); static void glade_project_model_iface_init (GtkTreeModelIface *iface); static void glade_project_drag_source_init (GtkTreeDragSourceIface *iface); struct _GladeProjectPrivate { gchar *path; /* The full canonical path of the glade file for this project */ gchar *translation_domain; /* The project translation domain */ gint unsaved_number; /* A unique number for this project if it is untitled */ GladeWidgetAdaptor *add_item; /* The next item to add to the project. */ GList *tree; /* List of toplevel Objects in this projects */ GList *objects; /* List of all objects in this project */ GtkTreeModel *model; /* GtkTreeStore used as proxy model */ 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. */ guint selection_changed_id; GladeNameContext *widget_names; /* Context for uniqueness of names */ GList *undo_stack; /* A stack with the last executed commands */ GList *prev_redo_item; /* Points to the item previous to the redo items */ GList *first_modification; /* we record the first modification, so that we * can set "modification" to FALSE when we * undo this modification */ GladeWidget *template; /* The template widget */ gchar *license; /* License for this project (will be saved as a comment) */ GList *comments; /* XML comments, Glade will preserve whatever comment was * in file before the root element, so users can delete or change it. */ time_t mtime; /* last UTC modification time of file, or 0 if it could not be read */ GHashTable *target_versions_major; /* target versions by catalog */ GHashTable *target_versions_minor; /* target versions by catalog */ gchar *resource_path; /* Indicates where to load resources from for this project * (full or relative path, null means project directory). */ gchar *css_provider_path; /* The custom css to use for this project */ GtkCssProvider *css_provider; GFileMonitor *css_monitor; GList *unknown_catalogs; /* List of CatalogInfo catalogs */ GtkWidget *prefs_dialog; /* Store previews, so we can kill them on close */ GHashTable *previews; /* For the loading progress bars ("load-progress" signal) */ gint progress_step; gint progress_full; /* Flags */ guint load_cancel : 1; guint first_modification_is_na : 1; /* indicates that the first_modification item has been lost */ guint has_selection : 1; /* Whether the project has a selection */ guint readonly : 1; /* A flag that is set if the project is readonly */ guint loading : 1; /* A flags that is set when the project is loading */ guint modified : 1; /* 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 */ guint writing_preview : 1; /* During serialization, if we are serializing for a preview */ guint pointer_mode : 3; /* The currently effective GladePointerMode */ }; typedef struct { gchar *catalog; gint position; } CatalogInfo; enum { ADD_WIDGET, REMOVE_WIDGET, WIDGET_NAME_CHANGED, SELECTION_CHANGED, CLOSE, CHANGED, PARSE_BEGAN, PARSE_FINISHED, TARGETS_CHANGED, LOAD_PROGRESS, WIDGET_VISIBILITY_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_MODIFIED, PROP_HAS_SELECTION, PROP_PATH, PROP_READ_ONLY, PROP_ADD_ITEM, PROP_POINTER_MODE, PROP_TRANSLATION_DOMAIN, PROP_TEMPLATE, PROP_RESOURCE_PATH, PROP_LICENSE, PROP_CSS_PROVIDER_PATH, N_PROPERTIES }; static GParamSpec *glade_project_props[N_PROPERTIES]; static guint glade_project_signals[LAST_SIGNAL] = { 0 }; static GladeIDAllocator *unsaved_number_allocator = NULL; #define GLADE_XML_COMMENT "Generated with "PACKAGE_NAME #define GLADE_PROJECT_LARGE_PROJECT 40 #define VALID_ITER(project, iter) \ ((iter)!= NULL && G_IS_OBJECT ((iter)->user_data) && \ ((GladeProject*)(project))->priv->stamp == (iter)->stamp) G_DEFINE_TYPE_WITH_CODE (GladeProject, glade_project, G_TYPE_OBJECT, G_ADD_PRIVATE (GladeProject) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, glade_project_model_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, glade_project_drag_source_init)) /******************************************************************* 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 unparent_objects_recurse (GladeWidget *widget) { GladeWidget *child; GList *children, *list; /* Unparent all children */ if ((children = glade_widget_get_children (widget)) != NULL) { for (list = children; list; list = list->next) { child = glade_widget_get_from_gobject (list->data); unparent_objects_recurse (child); if (!glade_widget_get_internal (child)) glade_widget_remove_child (widget, child); } g_list_free (children); } } static void glade_project_dispose (GObject *object) { GladeProject *project = GLADE_PROJECT (object); GladeProjectPrivate *priv = project->priv; GList *list, *tree; /* Emit close signal */ g_signal_emit (object, glade_project_signals[CLOSE], 0); /* Destroy running previews */ if (priv->previews) { g_hash_table_destroy (priv->previews); priv->previews = NULL; } if (priv->selection_changed_id > 0) priv->selection_changed_id = (g_source_remove (priv->selection_changed_id), 0); glade_project_selection_clear (project, TRUE); g_clear_object (&priv->css_provider); g_clear_object (&priv->css_monitor); glade_project_list_unref (priv->undo_stack); priv->undo_stack = NULL; /* Remove objects from the project */ tree = g_list_copy (priv->tree); for (list = tree; list; list = list->next) { GladeWidget *gwidget = glade_widget_get_from_gobject (list->data); unparent_objects_recurse (gwidget); } g_list_free (tree); while (priv->tree) glade_project_remove_object (project, priv->tree->data); while (priv->objects) glade_project_remove_object (project, priv->objects->data); g_assert (priv->tree == NULL); g_assert (priv->objects == NULL); if (priv->unknown_catalogs) { GList *l; for (l = priv->unknown_catalogs; l; l = g_list_next (l)) { CatalogInfo *data = l->data; g_free (data->catalog); g_free (data); } g_list_free (priv->unknown_catalogs); priv->unknown_catalogs = NULL; } g_object_unref (priv->model); G_OBJECT_CLASS (glade_project_parent_class)->dispose (object); } static void glade_project_finalize (GObject *object) { GladeProject *project = GLADE_PROJECT (object); GladeProjectPrivate *priv = project->priv; gtk_widget_destroy (priv->prefs_dialog); g_free (priv->path); g_free (priv->license); g_free (priv->css_provider_path); if (priv->comments) { g_list_foreach (priv->comments, (GFunc) g_free, NULL); g_list_free (priv->comments); } if (priv->unsaved_number > 0) glade_id_allocator_release (get_unsaved_number_allocator (), priv->unsaved_number); g_hash_table_destroy (priv->target_versions_major); g_hash_table_destroy (priv->target_versions_minor); glade_name_context_destroy (priv->widget_names); 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_PATH: g_value_set_string (value, project->priv->path); break; case PROP_READ_ONLY: g_value_set_boolean (value, project->priv->readonly); break; case PROP_ADD_ITEM: g_value_set_object (value, project->priv->add_item); break; case PROP_POINTER_MODE: g_value_set_enum (value, project->priv->pointer_mode); break; case PROP_TRANSLATION_DOMAIN: g_value_set_string (value, project->priv->translation_domain); break; case PROP_TEMPLATE: g_value_set_object (value, project->priv->template); break; case PROP_RESOURCE_PATH: g_value_set_string (value, project->priv->resource_path); break; case PROP_LICENSE: g_value_set_string (value, project->priv->license); break; case PROP_CSS_PROVIDER_PATH: g_value_set_string (value, project->priv->css_provider_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void glade_project_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_TRANSLATION_DOMAIN: glade_project_set_translation_domain (GLADE_PROJECT (object), g_value_get_string (value)); break; case PROP_TEMPLATE: glade_project_set_template (GLADE_PROJECT (object), g_value_get_object (value)); break; case PROP_RESOURCE_PATH: glade_project_set_resource_path (GLADE_PROJECT (object), g_value_get_string (value)); break; case PROP_LICENSE: glade_project_set_license (GLADE_PROJECT (object), g_value_get_string (value)); break; case PROP_CSS_PROVIDER_PATH: glade_project_set_css_provider_path (GLADE_PROJECT (object), g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /******************************************************************* 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 && (glade_command_group_id (next_cmd) == 0 || glade_command_group_id (next_cmd) != glade_command_group_id (cmd))) 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 && (glade_command_group_id (next_cmd) == 0 || glade_command_group_id (next_cmd) != glade_command_group_id (cmd))) 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 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); } static void glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd) { GladeProjectPrivate *priv = project->priv; GList *tmp_redo_item; /* We should now free all the "redo" items */ tmp_redo_item = g_list_next (priv->prev_redo_item); while (tmp_redo_item) tmp_redo_item = glade_project_free_undo_item (project, 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; } /* Try to unify only if group depth is 0 and the project has not been recently saved */ if (glade_command_get_group_depth () == 0 && priv->prev_redo_item != NULL && project->priv->prev_redo_item != project->priv->first_modification) { GladeCommand *cmd1 = priv->prev_redo_item->data; if (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); cmd1 = NULL; } g_signal_emit (G_OBJECT (project), glade_project_signals[CHANGED], 0, cmd1, TRUE); return; } } /* 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 inline gchar * glade_preview_get_pid_as_str (GladePreview *preview) { #ifdef G_OS_WIN32 return g_strdup_printf ("%p", glade_preview_get_pid (preview)); #else return g_strdup_printf ("%d", glade_preview_get_pid (preview)); #endif } static void glade_project_preview_exits (GladePreview *preview, GladeProject *project) { gchar *pidstr; pidstr = glade_preview_get_pid_as_str (preview); preview = g_hash_table_lookup (project->priv->previews, pidstr); if (preview) g_hash_table_remove (project->priv->previews, pidstr); g_free (pidstr); } static void glade_project_destroy_preview (gpointer data) { GladePreview *preview = GLADE_PREVIEW (data); GladeWidget *gwidget; gwidget = glade_preview_get_widget (preview); g_object_set_data (G_OBJECT (gwidget), "preview", NULL); g_signal_handlers_disconnect_by_func (preview, G_CALLBACK (glade_project_preview_exits), g_object_get_data (G_OBJECT (preview), "project")); g_object_unref (preview); } 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 (!project->priv->first_modification_is_na && project->priv->prev_redo_item == project->priv->first_modification) glade_project_set_modified (project, FALSE); else glade_project_set_modified (project, TRUE); } } static void glade_project_set_css_provider_forall (GtkWidget *widget, gpointer data) { if (GLADE_IS_PLACEHOLDER (widget) || GLADE_IS_OBJECT_STUB (widget)) return; gtk_style_context_add_provider (gtk_widget_get_style_context (widget), GTK_STYLE_PROVIDER (data), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); if (GTK_IS_CONTAINER (widget)) gtk_container_forall (GTK_CONTAINER (widget), glade_project_set_css_provider_forall, data); } static void glade_project_add_object_impl (GladeProject *project, GladeWidget *gwidget) { GladeProjectPrivate *priv = project->priv; GObject *widget = glade_widget_get_object (gwidget); if (!priv->css_provider || !GTK_IS_WIDGET (widget)) return; glade_project_set_css_provider_forall (GTK_WIDGET (widget), priv->css_provider); } /******************************************************************* Class Initializers *******************************************************************/ static void glade_project_init (GladeProject *project) { GladeProjectPrivate *priv; GList *list; project->priv = priv = glade_project_get_instance_private (project); priv->path = NULL; priv->model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_OBJECT)); g_signal_connect_swapped (priv->model, "row-changed", G_CALLBACK (gtk_tree_model_row_changed), project); g_signal_connect_swapped (priv->model, "row-inserted", G_CALLBACK (gtk_tree_model_row_inserted), project); g_signal_connect_swapped (priv->model, "row-has-child-toggled", G_CALLBACK (gtk_tree_model_row_has_child_toggled), project); g_signal_connect_swapped (priv->model, "row-deleted", G_CALLBACK (gtk_tree_model_row_deleted), project); g_signal_connect_swapped (priv->model, "rows-reordered", G_CALLBACK (gtk_tree_model_rows_reordered), project); priv->readonly = FALSE; priv->tree = NULL; priv->selection = NULL; priv->has_selection = FALSE; priv->undo_stack = NULL; priv->prev_redo_item = NULL; priv->first_modification = NULL; priv->first_modification_is_na = FALSE; priv->unknown_catalogs = NULL; priv->previews = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, glade_project_destroy_preview); priv->widget_names = glade_name_context_new (); priv->unsaved_number = glade_id_allocator_allocate (get_unsaved_number_allocator ()); 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)); } priv->prefs_dialog = glade_project_properties_new (project); } 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->set_property = glade_project_set_property; object_class->finalize = glade_project_finalize; object_class->dispose = glade_project_dispose; klass->add_object = glade_project_add_object_impl; 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->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::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-began: * @gladeproject: the #GladeProject which received the signal. * * Emitted when @gladeproject parsing starts. */ glade_project_signals[PARSE_BEGAN] = g_signal_new ("parse-began", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * 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); /** * GladeProject::targets-changed: * @gladeproject: the #GladeProject which received the signal. * * Emitted when @gladeproject target versions change. */ glade_project_signals[TARGETS_CHANGED] = g_signal_new ("targets-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GladeProject::load-progress: * @gladeproject: the #GladeProject which received the signal. * @objects_total: the total amount of objects to load * @objects_loaded: the current amount of loaded objects * * Emitted while @project is loading. */ glade_project_signals[LOAD_PROGRESS] = g_signal_new ("load-progress", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, _glade_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); /** * GladeProject::widget-visibility-changed: * @gladeproject: the #GladeProject which received the signal. * @widget: the widget that its visibity changed * @visible: the current visiblity of the widget * * Emitted when the visivility of a widget changed */ glade_project_signals[WIDGET_VISIBILITY_CHANGED] = g_signal_new ("widget-visibility-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, _glade_marshal_VOID__OBJECT_BOOLEAN, G_TYPE_NONE, 2, GLADE_TYPE_WIDGET, G_TYPE_BOOLEAN); glade_project_props[PROP_MODIFIED] = g_param_spec_boolean ("modified", "Modified", _("Whether project has been modified since it was last saved"), FALSE, G_PARAM_READABLE); glade_project_props[PROP_HAS_SELECTION] = g_param_spec_boolean ("has-selection", _("Has Selection"), _("Whether project has a selection"), FALSE, G_PARAM_READABLE); glade_project_props[PROP_PATH] = g_param_spec_string ("path", _("Path"), _("The filesystem path of the project"), NULL, G_PARAM_READABLE); glade_project_props[PROP_READ_ONLY] = g_param_spec_boolean ("read-only", _("Read Only"), _("Whether project is read-only"), FALSE, G_PARAM_READABLE); glade_project_props[PROP_ADD_ITEM] = g_param_spec_object ("add-item", _("Add Item"), _("The current item to add to the project"), GLADE_TYPE_WIDGET_ADAPTOR, G_PARAM_READABLE); glade_project_props[PROP_POINTER_MODE] = g_param_spec_enum ("pointer-mode", _("Pointer Mode"), _("The currently effective GladePointerMode"), GLADE_TYPE_POINTER_MODE, GLADE_POINTER_SELECT, G_PARAM_READABLE); glade_project_props[PROP_TRANSLATION_DOMAIN] = g_param_spec_string ("translation-domain", _("Translation Domain"), _("The project translation domain"), NULL, G_PARAM_READWRITE); glade_project_props[PROP_TEMPLATE] = g_param_spec_object ("template", _("Template"), _("The project's template widget, if any"), GLADE_TYPE_WIDGET, G_PARAM_READWRITE); glade_project_props[PROP_RESOURCE_PATH] = g_param_spec_string ("resource-path", _("Resource Path"), _("Path to load images and resources in Glade's runtime"), NULL, G_PARAM_READWRITE); glade_project_props[PROP_LICENSE] = g_param_spec_string ("license", _("License"), _("License for this project, it will be added as a document level comment."), NULL, G_PARAM_READWRITE); glade_project_props[PROP_CSS_PROVIDER_PATH] = g_param_spec_string ("css-provider-path", _("CSS Provider Path"), _("Path to use as the custom CSS provider for this project."), NULL, G_PARAM_READWRITE); /* Install all properties */ g_object_class_install_properties (object_class, N_PROPERTIES, glade_project_props); } /****************************************************************** * GtkTreeModelIface * ******************************************************************/ static GtkTreeModelFlags glade_project_model_get_flags (GtkTreeModel *model) { return 0; } static gint glade_project_model_get_n_columns (GtkTreeModel *model) { return GLADE_PROJECT_MODEL_N_COLUMNS; } static GType glade_project_model_get_column_type (GtkTreeModel *model, gint column) { switch (column) { case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME: return G_TYPE_STRING; case GLADE_PROJECT_MODEL_COLUMN_NAME: return G_TYPE_STRING; case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME: return G_TYPE_STRING; case GLADE_PROJECT_MODEL_COLUMN_OBJECT: return G_TYPE_OBJECT; case GLADE_PROJECT_MODEL_COLUMN_MISC: return G_TYPE_STRING; case GLADE_PROJECT_MODEL_COLUMN_WARNING: return G_TYPE_STRING; default: g_assert_not_reached (); return G_TYPE_NONE; } } static gboolean glade_project_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) { return gtk_tree_model_get_iter (GLADE_PROJECT (model)->priv->model, iter, path); } static GtkTreePath * glade_project_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) { return gtk_tree_model_get_path (GLADE_PROJECT (model)->priv->model, iter); } static void glade_project_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, gint column, GValue *value) { GladeWidget *widget; gtk_tree_model_get (GLADE_PROJECT (model)->priv->model, iter, 0, &widget, -1); value = g_value_init (value, glade_project_model_get_column_type (model, column)); switch (column) { case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME: g_value_set_string (value, glade_widget_adaptor_get_icon_name (glade_widget_get_adaptor (widget))); break; case GLADE_PROJECT_MODEL_COLUMN_NAME: g_value_set_string (value, glade_widget_get_name (widget)); break; case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME: { GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (widget); g_value_set_static_string (value, glade_widget_adaptor_get_name (adaptor)); break; } case GLADE_PROJECT_MODEL_COLUMN_OBJECT: g_value_set_object (value, glade_widget_get_object (widget)); break; case GLADE_PROJECT_MODEL_COLUMN_MISC: { gchar *str = NULL, *child_type; GladeProperty *ref_prop; /* special child type / internal child */ if (glade_widget_get_internal (widget) != NULL) str = g_strdup_printf (_("(internal %s)"), glade_widget_get_internal (widget)); else if ((child_type = g_object_get_data (glade_widget_get_object (widget), "special-child-type")) != NULL) str = g_strdup_printf (_("(%s child)"), child_type); else if (glade_widget_get_is_composite (widget)) str = g_strdup_printf (_("(template)")); else if ((ref_prop = glade_widget_get_parentless_widget_ref (widget)) != NULL) { GladePropertyClass *pclass = glade_property_get_class (ref_prop); GladeWidget *ref_widget = glade_property_get_widget (ref_prop); /* translators: refers to a property named '%s' of widget '%s' */ str = g_strdup_printf (_("(%s of %s)"), glade_property_class_get_name (pclass), glade_widget_get_name (ref_widget)); } g_value_take_string (value, str); } break; case GLADE_PROJECT_MODEL_COLUMN_WARNING: g_value_set_string (value, glade_widget_support_warning (widget)); break; default: g_assert_not_reached (); } } static gboolean glade_project_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) { return gtk_tree_model_iter_next (GLADE_PROJECT (model)->priv->model, iter); } static gboolean glade_project_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) { return gtk_tree_model_iter_has_child (GLADE_PROJECT (model)->priv->model, iter); } static gint glade_project_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) { return gtk_tree_model_iter_n_children (GLADE_PROJECT (model)->priv->model, iter); } static gboolean glade_project_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, gint nth) { return gtk_tree_model_iter_nth_child (GLADE_PROJECT (model)->priv->model, iter, parent, nth); } static gboolean glade_project_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent) { return gtk_tree_model_iter_children (GLADE_PROJECT (model)->priv->model, iter, parent); } static gboolean glade_project_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child) { return gtk_tree_model_iter_parent (GLADE_PROJECT (model)->priv->model, iter, child); } static void glade_project_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) { gtk_tree_model_ref_node (GLADE_PROJECT (model)->priv->model, iter); } static void glade_project_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) { gtk_tree_model_unref_node (GLADE_PROJECT (model)->priv->model, iter); } static void glade_project_model_iface_init (GtkTreeModelIface *iface) { iface->get_flags = glade_project_model_get_flags; iface->get_n_columns = glade_project_model_get_n_columns; iface->get_column_type = glade_project_model_get_column_type; iface->get_iter = glade_project_model_get_iter; iface->get_path = glade_project_model_get_path; iface->get_value = glade_project_model_get_value; iface->iter_next = glade_project_model_iter_next; iface->iter_children = glade_project_model_iter_children; iface->iter_has_child = glade_project_model_iter_has_child; iface->iter_n_children = glade_project_model_iter_n_children; iface->iter_nth_child = glade_project_model_iter_nth_child; iface->iter_parent = glade_project_model_iter_parent; iface->ref_node = glade_project_model_ref_node; iface->unref_node = glade_project_model_unref_node; } static gboolean glade_project_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path) { return TRUE; } static gboolean glade_project_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path) { return FALSE; } static gboolean glade_project_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data) { GtkTreeIter iter; if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path)) { GObject *object; gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1); _glade_dnd_set_data (selection_data, object); return TRUE; } return FALSE; } static void glade_project_drag_source_init (GtkTreeDragSourceIface *iface) { iface->row_draggable = glade_project_row_draggable; iface->drag_data_delete = glade_project_drag_data_delete; iface->drag_data_get = glade_project_drag_data_get; } /******************************************************************* Loading project code here *******************************************************************/ /** * glade_project_new: * * Creates a new #GladeProject. * * Returns: a new #GladeProject */ GladeProject * glade_project_new (void) { GladeProject *project = g_object_new (GLADE_TYPE_PROJECT, NULL); return project; } /* Called when finishing loading a glade file to resolve object type properties */ static void glade_project_fix_object_props (GladeProject *project) { GList *l, *ll, *objects; GValue *value; GladeWidget *gwidget; GladeProperty *property; gchar *txt; objects = g_list_copy (project->priv->objects); for (l = objects; l; l = l->next) { gwidget = glade_widget_get_from_gobject (l->data); for (ll = glade_widget_get_properties (gwidget); ll; ll = ll->next) { GladePropertyClass *klass; property = GLADE_PROPERTY (ll->data); klass = glade_property_get_class (property); if (glade_property_class_is_object (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 (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); } } } g_list_free (objects); } static void glade_project_fix_template (GladeProject *project) { GList *l; gboolean composite = FALSE; for (l = project->priv->tree; l; l = l->next) { GObject *obj = l->data; GladeWidget *gwidget; gwidget = glade_widget_get_from_gobject (obj); composite = glade_widget_get_is_composite (gwidget); if (composite) { glade_project_set_template (project, gwidget); break; } } } static gchar * gp_comment_strip_property (gchar *value, gchar *property) { if (g_str_has_prefix (value, property)) { gchar *start = value + strlen (property); if (*start == ' ') start++; memmove (value, start, strlen (start) + 1); return value; } return NULL; } static gchar * gp_comment_get_content (GladeXmlNode *comment) { gchar *value; if (glade_xml_node_is_comment (comment) && (value = glade_xml_get_content (comment))) { gchar *compressed; /* Replace NON-BREAKING HYPHEN with regular HYPHEN */ value = _glade_util_strreplace (g_strstrip (value), TRUE, "\\‑\\‑", "--"); compressed = g_strcompress (value); g_free (value); return compressed; } return NULL; } static gchar * glade_project_read_requires_from_comment (GladeXmlNode *comment, guint16 *major, guint16 *minor) { gchar *value, *requires, *required_lib = NULL; if ((value = gp_comment_get_content (comment)) && (requires = gp_comment_strip_property (value, "interface-requires"))) { gchar buffer[128]; gint maj, min; if (sscanf (requires, "%128s %d.%d", buffer, &maj, &min) == 3) { if (major) *major = maj; if (minor) *minor = min; required_lib = g_strdup (buffer); } } g_free (value); return required_lib; } static gboolean glade_project_read_requires (GladeProject *project, GladeXmlNode *root_node, const gchar *path, gboolean *has_gtk_dep) { GString *string = g_string_new (NULL); GladeXmlNode *node; gboolean loadable = TRUE; guint16 major, minor; gint position = 0; for (node = glade_xml_node_get_children_with_comments (root_node); node; node = glade_xml_node_next_with_comments (node)) { gchar *required_lib = NULL; /* Skip non "requires" tags */ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_REQUIRES) || (required_lib = glade_project_read_requires_from_comment (node, &major, &minor)))) continue; if (!required_lib) { required_lib = glade_xml_get_property_string_required (node, GLADE_XML_TAG_LIB, NULL); glade_xml_get_property_version (node, GLADE_XML_TAG_VERSION, &major, &minor); } if (!required_lib) continue; /* Dont mention gtk+ as a required lib in * the generated glade file */ if (!glade_catalog_is_loaded (required_lib)) { CatalogInfo *data = g_new0(CatalogInfo, 1); data->catalog = required_lib; data->position = position; /* Keep a list of unknown catalogs to avoid loosing the requirement */ project->priv->unknown_catalogs = g_list_append (project->priv->unknown_catalogs, data); /* Also keep the version */ glade_project_set_target_version (project, required_lib, major, minor); if (!loadable) g_string_append (string, ", "); g_string_append (string, required_lib); loadable = FALSE; } else { if (has_gtk_dep && strcmp (required_lib, "gtk+") == 0) *has_gtk_dep = TRUE; glade_project_set_target_version (project, required_lib, major, minor); g_free (required_lib); } position++; } if (!loadable) glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, _("Failed to load %s.\n" "The following required catalogs are unavailable: %s"), path, string->str); g_string_free (string, TRUE); return loadable; } static void update_project_for_resource_path (GladeProject *project) { GladeWidget *widget; GladeProperty *property; GList *l, *list; for (l = project->priv->objects; l; l = l->next) { widget = glade_widget_get_from_gobject (l->data); for (list = glade_widget_get_properties (widget); list; list = list->next) { GladePropertyClass *klass; GParamSpec *pspec; property = list->data; klass = glade_property_get_class (property); pspec = glade_property_class_get_pspec (klass); /* XXX We should have a "resource" flag on properties that need * to be loaded from the resource path, but that would require * that they can serialize both ways (custom properties are only * required to generate unique strings for value comparisons). */ if (pspec->value_type == GDK_TYPE_PIXBUF) { GValue *value; gchar *string; string = glade_property_make_string (property); value = glade_property_class_make_gvalue_from_string (klass, string, project); glade_property_set_value (property, value); g_value_unset (value); g_free (value); g_free (string); } } } } void glade_project_set_resource_path (GladeProject *project, const gchar *path) { g_return_if_fail (GLADE_IS_PROJECT (project)); if (g_strcmp0 (project->priv->resource_path, path) != 0) { g_free (project->priv->resource_path); project->priv->resource_path = g_strdup (path); update_project_for_resource_path (project); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_RESOURCE_PATH]); } } const gchar * glade_project_get_resource_path (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->resource_path; } void glade_project_set_license (GladeProject *project, const gchar *license) { GladeProjectPrivate *priv; g_return_if_fail (GLADE_IS_PROJECT (project)); priv = project->priv; if ((!license && priv->license) || (license && g_strcmp0 (priv->license, license) != 0)) { g_free (priv->license); priv->license = g_strdup (license); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_LICENSE]); } } const gchar * glade_project_get_license (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->license; } static void glade_project_read_comment_properties (GladeProject *project, GladeXmlNode *root_node) { GladeProjectPrivate *priv = project->priv; gchar *license, *name, *description, *copyright, *authors; GladeXmlNode *node; license = name = description = copyright = authors = NULL; for (node = glade_xml_node_get_children_with_comments (root_node); node; node = glade_xml_node_next_with_comments (node)) { gchar *val; if (!(val = gp_comment_get_content (node))) continue; if (gp_comment_strip_property (val, "interface-local-resource-path")) glade_project_set_resource_path (project, val); else if (gp_comment_strip_property (val, "interface-css-provider-path")) { if (g_path_is_absolute (val)) glade_project_set_css_provider_path (project, val); else { gchar *dirname = g_path_get_dirname (priv->path); gchar *full_path = g_build_filename (dirname, val, NULL); glade_project_set_css_provider_path (project, full_path); g_free (dirname); g_free (full_path); } } else if (!license && (license = gp_comment_strip_property (val, "interface-license-type"))) continue; else if (!name && (name = gp_comment_strip_property (val, "interface-name"))) continue; else if (!description && (description = gp_comment_strip_property (val, "interface-description"))) continue; else if (!copyright && (copyright = gp_comment_strip_property (val, "interface-copyright"))) continue; else if (!authors && (authors = gp_comment_strip_property (val, "interface-authors"))) continue; g_free (val); } _glade_project_properties_set_license_data (GLADE_PROJECT_PROPERTIES (priv->prefs_dialog), license, name, description, copyright, authors); g_free (license); g_free (name); g_free (description); g_free (copyright); g_free (authors); } static inline void glade_project_read_comments (GladeProject *project, GladeXmlNode *root) { GladeProjectPrivate *priv = project->priv; GladeXmlNode *node; /* We only support comments before the root element */ for (node = glade_xml_node_prev_with_comments (root); node; node = glade_xml_node_prev_with_comments (node)) { if (glade_xml_node_is_comment (node)) { gchar *start, *comment = glade_xml_get_content (node); /* Ignore leading spaces */ for (start = comment; *start && g_ascii_isspace (*start); start++); /* Do not load generated with glade comment! */ if (g_str_has_prefix (start, GLADE_XML_COMMENT)) { gchar *new_line = g_strstr_len (start, -1, "\n"); if (new_line) glade_project_set_license (project, g_strstrip (new_line)); g_free (comment); continue; } /* Since we are reading in backwards order, * prepending gives us the right order */ priv->comments = g_list_prepend (priv->comments, comment); } } } typedef struct { GladeWidget *widget; gint major; gint minor; } VersionData; static void glade_project_introspect_signal_versions (GladeSignal *signal, VersionData *data) { GladeSignalClass *signal_class; GladeWidgetAdaptor *adaptor; gchar *catalog = NULL; gboolean is_gtk_adaptor = FALSE; signal_class = glade_widget_adaptor_get_signal_class (glade_widget_get_adaptor (data->widget), glade_signal_get_name (signal)); /* unknown signal... can it happen ? */ if (!signal_class) return; adaptor = glade_signal_class_get_adaptor (signal_class); /* Check if the signal comes from a GTK+ widget class */ g_object_get (adaptor, "catalog", &catalog, NULL); if (strcmp (catalog, "gtk+") == 0) is_gtk_adaptor = TRUE; g_free (catalog); if (is_gtk_adaptor && !GSC_VERSION_CHECK (signal_class, data->major, data->minor)) { data->major = glade_signal_class_since_major (signal_class); data->minor = glade_signal_class_since_minor (signal_class); } } static void glade_project_introspect_gtk_version (GladeProject *project) { GladeWidget *widget; GList *list, *l; gint target_major = 2, target_minor = 12; for (list = project->priv->objects; list; list = list->next) { gboolean is_gtk_adaptor = FALSE; gchar *catalog = NULL; VersionData data = { 0, }; GList *signals; widget = glade_widget_get_from_gobject (list->data); /* Check if its a GTK+ widget class */ g_object_get (glade_widget_get_adaptor (widget), "catalog", &catalog, NULL); if (strcmp (catalog, "gtk+") == 0) is_gtk_adaptor = TRUE; g_free (catalog); /* Check widget class version */ if (is_gtk_adaptor && !GWA_VERSION_CHECK (glade_widget_get_adaptor (widget), target_major, target_minor)) { target_major = GWA_VERSION_SINCE_MAJOR (glade_widget_get_adaptor (widget)); target_minor = GWA_VERSION_SINCE_MINOR (glade_widget_get_adaptor (widget)); } /* Check all properties */ for (l = glade_widget_get_properties (widget); l; l = l->next) { GladeProperty *property = l->data; GladePropertyClass *pclass = glade_property_get_class (property); GladeWidgetAdaptor *prop_adaptor, *adaptor; GParamSpec *pspec; /* Unset properties ofcourse dont count... */ if (glade_property_original_default (property)) continue; /* Check if this property originates from a GTK+ widget class */ pspec = glade_property_class_get_pspec (pclass); prop_adaptor = glade_property_class_get_adaptor (pclass); adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); catalog = NULL; is_gtk_adaptor = FALSE; g_object_get (adaptor, "catalog", &catalog, NULL); if (strcmp (catalog, "gtk+") == 0) is_gtk_adaptor = TRUE; g_free (catalog); /* Check GTK+ property class versions */ if (is_gtk_adaptor && !GPC_VERSION_CHECK (pclass, target_major, target_minor)) { target_major = glade_property_class_since_major (pclass); target_minor = glade_property_class_since_minor (pclass); } } /* Check all signal versions here */ data.widget = widget; data.major = target_major; data.minor = target_minor; signals = glade_widget_get_signal_list (widget); g_list_foreach (signals, (GFunc)glade_project_introspect_signal_versions, &data); g_list_free (signals); if (target_major < data.major) target_major = data.major; if (target_minor < data.minor) target_minor = data.minor; } glade_project_set_target_version (project, "gtk+", target_major, target_minor); } static gint glade_project_count_xml_objects (GladeProject *project, GladeXmlNode *root, gint count) { GladeXmlNode *node; for (node = glade_xml_node_get_children (root); node; node = glade_xml_node_next (node)) { if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)) count = glade_project_count_xml_objects (project, node, ++count); else if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_CHILD)) count = glade_project_count_xml_objects (project, node, count); } return count; } void glade_project_cancel_load (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); project->priv->load_cancel = TRUE; } gboolean glade_project_load_cancelled (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->load_cancel; } void glade_project_push_progress (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); project->priv->progress_step++; g_signal_emit (project, glade_project_signals[LOAD_PROGRESS], 0, project->priv->progress_full, project->priv->progress_step); } /* translators: refers to project name '%s' that targets gtk version '%d.%d' */ #define PROJECT_TARGET_DIALOG_TITLE_FMT _("%s targets Gtk+ %d.%d") static void glade_project_check_target_version (GladeProject *project) { GladeProjectPrivate *priv; GHashTable *unknown_classes; gint unknown_objects; gint major, minor; GtkWidget *dialog; GString *text; GList *l; glade_project_get_target_version (project, "gtk+", &major, &minor); /* Glade >= 3.10 only targets Gtk 3 */ if (major >= 3) return; priv = project->priv; unknown_classes = g_hash_table_new (g_str_hash, g_str_equal); unknown_objects = 0; for (l = priv->objects; l; l = g_list_next (l)) { if (GLADE_IS_OBJECT_STUB (l->data)) { gchar *type; g_object_get (l->data, "object-type", &type, NULL); g_hash_table_insert (unknown_classes, type, NULL); unknown_objects++; } } if (unknown_objects) { GList *classes = g_hash_table_get_keys (unknown_classes); if (unknown_objects == 1) { text = g_string_new (_("Specially because there is an object that can not be build with type ")); } else { text = g_string_new (""); g_string_printf (text, _("Specially because there are %d objects that can not be build with types "), unknown_objects); } for (l = classes; l; l = g_list_next (l)) { if (g_list_previous (l)) g_string_append (text, (g_list_next (l)) ? ", " : _(" and ")); g_string_append (text, l->data); } g_list_free (classes); } else text = NULL; dialog = gtk_message_dialog_new (GTK_WINDOW (glade_app_get_window ()), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, PROJECT_TARGET_DIALOG_TITLE_FMT, glade_project_get_name (project), major, minor); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("But this version of Glade is for GTK+ 3 only.\n" "Make sure you can run this project with Glade 3.8 with no deprecated widgets first.\n" "%s"), (text) ? text->str : ""); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); glade_project_set_target_version (project, "gtk+", 3, 0); g_hash_table_destroy (unknown_classes); if (text) g_string_free (text, TRUE); } static gchar * glade_project_autosave_name (const gchar *path) { gchar *basename, *dirname, *autoname; gchar *autosave_name; basename = g_path_get_basename (path); dirname = g_path_get_dirname (path); autoname = g_strdup_printf ("#%s#", basename); autosave_name = g_build_filename (dirname, autoname, NULL); g_free (basename); g_free (dirname); g_free (autoname); return autosave_name; } static gboolean glade_project_load_internal (GladeProject *project) { GladeProjectPrivate *priv = project->priv; GladeXmlContext *context; GladeXmlDoc *doc; GladeXmlNode *root; GladeXmlNode *node; GladeWidget *widget; gboolean has_gtk_dep = FALSE; gchar *domain; gint count; gchar *autosave_path; time_t mtime, autosave_mtime; gchar *load_path = NULL; /* Check if an autosave is more recent then the specified file */ autosave_path = glade_project_autosave_name (priv->path); autosave_mtime = glade_util_get_file_mtime (autosave_path, NULL); mtime = glade_util_get_file_mtime (priv->path, NULL); if (autosave_mtime > mtime) { gchar *display_name; display_name = glade_project_get_name (project); if (glade_util_ui_message (glade_app_get_window (), GLADE_UI_YES_OR_NO, NULL, _("An automatically saved version of `%s' is more recent.\n\n" "Would you like to load the autosave version instead?"), display_name)) { mtime = autosave_mtime; load_path = g_strdup (autosave_path); } g_free (display_name); } g_free (autosave_path); priv->selection = NULL; priv->objects = NULL; priv->loading = TRUE; _glade_xml_error_reset_last (); /* get the context & root node of the catalog file */ if (!(context = glade_xml_context_new_from_path (load_path ? load_path : priv->path, NULL, NULL))) { gchar *message = _glade_xml_error_get_last_message (); if (message) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, "%s", message); g_free (message); } else glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, "Couldn't open glade file [%s].", load_path ? load_path : priv->path); g_free (load_path); priv->loading = FALSE; return FALSE; } priv->mtime = mtime; doc = glade_xml_context_get_doc (context); root = glade_xml_doc_get_root (doc); if (!glade_xml_node_verify_silent (root, GLADE_XML_TAG_PROJECT)) { g_warning ("Couldnt recognize GtkBuilder xml, skipping %s", load_path ? load_path : priv->path); glade_xml_context_free (context); g_free (load_path); priv->loading = FALSE; return FALSE; } /* Emit "parse-began" signal */ g_signal_emit (project, glade_project_signals[PARSE_BEGAN], 0); if ((domain = glade_xml_get_property_string (root, GLADE_TAG_DOMAIN))) glade_project_set_translation_domain (project, domain); glade_project_read_comments (project, root); /* Read requieres, and do not abort load if there are missing catalog since * GladeObjectStub is created to keep the original xml for unknown object classes */ glade_project_read_requires (project, root, load_path ? load_path : priv->path, &has_gtk_dep); /* Read the rest of properties saved as comments */ glade_project_read_comment_properties (project, root); /* Launch a dialog if it's going to take enough time to be * worth showing at all */ count = glade_project_count_xml_objects (project, root, 0); priv->progress_full = count; priv->progress_step = 0; for (node = glade_xml_node_get_children (root); node; node = glade_xml_node_next (node)) { /* Skip "requires" tags */ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) continue; if ((widget = glade_widget_read (project, NULL, node, NULL)) != NULL) glade_project_add_object (project, glade_widget_get_object (widget)); if (priv->load_cancel) break; } /* Finished with the xml context */ glade_xml_context_free (context); if (priv->load_cancel) { priv->loading = FALSE; g_free (load_path); return FALSE; } if (!has_gtk_dep) glade_project_introspect_gtk_version (project); if (glade_util_file_is_writeable (priv->path) == FALSE) glade_project_set_readonly (project, TRUE); /* 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); glade_project_fix_template (project); /* Emit "parse-finished" signal */ g_signal_emit (project, glade_project_signals[PARSE_FINISHED], 0); /* Reset project status here too so that you get a clean * slate after calling glade_project_open(). */ priv->modified = FALSE; priv->loading = FALSE; /* Update ui with versioning info */ glade_project_verify_project_for_ui (project); glade_project_check_target_version (project); return TRUE; } static void glade_project_update_properties_title (GladeProject *project) { gchar *name, *title; /* Update prefs dialogs here... */ name = glade_project_get_name (project); title = g_strdup_printf (_("%s document properties"), name); gtk_window_set_title (GTK_WINDOW (project->priv->prefs_dialog), title); g_free (title); g_free (name); } gboolean glade_project_load_from_file (GladeProject *project, const gchar *path) { gboolean retval; g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); project->priv->path = glade_util_canonical_path (path); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]); if ((retval = glade_project_load_internal (project))) glade_project_update_properties_title (project); return retval; } /** * 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; g_return_val_if_fail (path != NULL, NULL); project = g_object_new (GLADE_TYPE_PROJECT, NULL); project->priv->path = glade_util_canonical_path (path); if (glade_project_load_internal (project)) { glade_project_update_properties_title (project); return project; } else { g_object_unref (project); return NULL; } } /******************************************************************* Writing project code here *******************************************************************/ #define GLADE_PROJECT_COMMENT " "GLADE_XML_COMMENT" "PACKAGE_VERSION" " static void glade_project_write_comment_property (GladeProject *project, GladeXmlContext *context, GladeXmlNode *root, const gchar *property, gchar *value) { gchar *comment, *escaped; GladeXmlNode *path_node; if (!value || *value == '\0') return; /* The string "--" (double hyphen) is not allowed in xml comments, so we replace * the regular HYPHEN with a NON-BREAKING HYPHEN which look the same but have * a different encoding. */ escaped = _glade_util_strreplace (g_strescape (value, "‑"), TRUE, "--", "\\‑\\‑"); comment = g_strconcat (" ", property, " ", escaped, " ", NULL); path_node = glade_xml_node_new_comment (context, comment); glade_xml_node_append_child (root, path_node); g_free (escaped); g_free (comment); } static void glade_project_write_required_libs (GladeProject *project, GladeXmlContext *context, GladeXmlNode *root) { gboolean supports_require_tag; GladeXmlNode *req_node; GList *required, *list; gint major, minor; glade_project_get_target_version (project, "gtk+", &major, &minor); supports_require_tag = GLADE_GTKBUILDER_HAS_VERSIONING (major, minor); if ((required = glade_project_required_libs (project)) != NULL) { gchar version[16]; for (list = required; list; list = list->next) { gchar *library = list->data; glade_project_get_target_version (project, library, &major, &minor); g_snprintf (version, sizeof (version), "%d.%d", major, minor); /* Write the standard requires tag */ if (supports_require_tag) { req_node = glade_xml_node_new (context, GLADE_XML_TAG_REQUIRES); glade_xml_node_set_property_string (req_node, GLADE_XML_TAG_LIB, library); glade_xml_node_set_property_string (req_node, GLADE_XML_TAG_VERSION, version); glade_xml_node_append_child (root, req_node); } else { gchar *value = g_strconcat (library, " ", version, NULL); glade_project_write_comment_property (project, context, root, "interface-requires", value); g_free (value); } } g_list_free_full (required, g_free); } } static void glade_project_write_resource_path (GladeProject *project, GladeXmlContext *context, GladeXmlNode *root) { glade_project_write_comment_property (project, context, root, "interface-local-resource-path", project->priv->resource_path); } static void glade_project_write_css_provider_path (GladeProject *project, GladeXmlContext *context, GladeXmlNode *root) { GladeProjectPrivate *priv = project->priv; gchar *dirname; if (priv->css_provider_path && priv->path && (dirname = g_path_get_dirname (priv->path))) { GFile *project_path = g_file_new_for_path (dirname); GFile *file_path = g_file_new_for_path (priv->css_provider_path); gchar *css_provider_path; css_provider_path = g_file_get_relative_path (project_path, file_path); if (css_provider_path) glade_project_write_comment_property (project, context, root, "interface-css-provider-path", css_provider_path); else g_warning ("g_file_get_relative_path () return NULL"); g_object_unref (project_path); g_object_unref (file_path); g_free (css_provider_path); g_free (dirname); } } static void glade_project_write_license_data (GladeProject *project, GladeXmlContext *context, GladeXmlNode *root) { gchar *license, *name, *description, *copyright, *authors; _glade_project_properties_get_license_data (GLADE_PROJECT_PROPERTIES (project->priv->prefs_dialog), &license, &name, &description, ©right, &authors); if (!license) return; glade_project_write_comment_property (project, context, root, "interface-license-type", license); glade_project_write_comment_property (project, context, root, "interface-name", name); glade_project_write_comment_property (project, context, root, "interface-description", description); glade_project_write_comment_property (project, context, root, "interface-copyright", copyright); glade_project_write_comment_property (project, context, root, "interface-authors", authors); g_free (license); g_free (name); g_free (description); g_free (copyright); g_free (authors); } static gint glade_widgets_name_cmp (gconstpointer ga, gconstpointer gb) { return g_strcmp0 (glade_widget_get_name ((gpointer)ga), glade_widget_get_name ((gpointer)gb)); } static gint glade_node_edge_name_cmp (gconstpointer ga, gconstpointer gb) { _NodeEdge *na = (gpointer)ga, *nb = (gpointer)gb; return g_strcmp0 (glade_widget_get_name (nb->successor), glade_widget_get_name (na->successor)); } static inline gboolean glade_project_widget_hard_depends (GladeWidget *widget, GladeWidget *another) { GList *l; for (l = _glade_widget_peek_prop_refs (another); l; l = g_list_next (l)) { GladePropertyClass *klass; /* If one of the properties that reference @another is * owned by @widget then @widget depends on @another */ if (glade_property_get_widget (l->data) == widget && (klass = glade_property_get_class (l->data)) && glade_property_class_get_construct_only (klass)) return TRUE; } return FALSE; } static GList * glade_project_get_graph_deps (GladeProject *project) { GladeProjectPrivate *priv = project->priv; GList *l, *edges = NULL; /* Create edges of the directed graph */ for (l = priv->objects; l; l = g_list_next (l)) { GladeWidget *predecessor = glade_widget_get_from_gobject (l->data); GladeWidget *predecessor_top; GList *ll; predecessor_top = glade_widget_get_toplevel (predecessor); /* Adds dependencies based on properties refs */ for (ll = _glade_widget_peek_prop_refs (predecessor); ll; ll = g_list_next (ll)) { GladeWidget *successor = glade_property_get_widget (ll->data); GladeWidget *successor_top; /* Ignore widgets that are not part of this project. (ie removed ones) */ if (glade_widget_get_project (successor) != project) continue; successor_top = glade_widget_get_toplevel (successor); /* Ignore objects within the same toplevel */ if (predecessor_top != successor_top) edges = _node_edge_prepend (edges, predecessor_top, successor_top); } } return edges; } static GList * glade_project_get_nodes_from_edges (GladeProject *project, GList *edges) { GList *l, *hard_edges = NULL; GList *cycles = NULL; /* Collect widgets with circular dependencies */ for (l = edges; l; l = g_list_next (l)) { _NodeEdge *edge = l->data; if (glade_widget_get_parent (edge->successor) == edge->predecessor || glade_project_widget_hard_depends (edge->predecessor, edge->successor)) hard_edges = _node_edge_prepend (hard_edges, edge->predecessor, edge->successor); /* Just toplevels */ if (glade_widget_get_parent (edge->successor)) continue; if (!g_list_find (cycles, edge->successor)) cycles = g_list_prepend (cycles, edge->successor); } /* Sort them alphabetically */ cycles = g_list_sort (cycles, glade_widgets_name_cmp); if (!hard_edges) return cycles; /* Sort them by hard deps */ cycles = _glade_tsort (&cycles, &hard_edges); if (hard_edges) { GList *l, *hard_cycles = NULL; /* Collect widgets with hard circular dependencies */ for (l = hard_edges; l; l = g_list_next (l)) { _NodeEdge *edge = l->data; /* Just toplevels */ if (glade_widget_get_parent (edge->successor)) continue; if (!g_list_find (hard_cycles, edge->successor)) hard_cycles = g_list_prepend (hard_cycles, edge->successor); } /* And append to the end of the cycles list */ cycles = g_list_concat (cycles, g_list_sort (hard_cycles, glade_widgets_name_cmp)); /* Opps! there is at least one hard circular dependency, * GtkBuilder will fail to set one of the properties that create the cycle */ _node_edge_list_free (hard_edges); } return cycles; } static GList * glade_project_add_hardcoded_dependencies (GList *edges, GladeProject *project) { GList *l, *toplevels = project->priv->tree; /* Iterate over every toplevel */ for (l = toplevels; l; l = g_list_next (l)) { GObject *predecessor = l->data; /* Looking for a GtkIconFactory */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS if (GTK_IS_ICON_FACTORY (predecessor)) { GladeWidget *predecessor_top = glade_widget_get_from_gobject (predecessor); GList *ll; /* add dependency on the GtkIconFactory on every toplevel */ for (ll = toplevels; ll; ll = g_list_next (ll)) { GObject *successor = ll->data; /* except for GtkIconFactory */ if (!GTK_IS_ICON_FACTORY (successor)) edges = _node_edge_prepend (edges, predecessor_top, glade_widget_get_from_gobject (successor)); } } G_GNUC_END_IGNORE_DEPRECATIONS } return edges; } static GList * glade_project_get_ordered_toplevels (GladeProject *project) { GladeProjectPrivate *priv = project->priv; GList *l, *sorted_tree, *tree = NULL; GList *edges; /* Create list of toplevels GladeWidgets */ for (l = priv->tree; l; l = g_list_next (l)) tree = g_list_prepend (tree, glade_widget_get_from_gobject (l->data)); /* Get dependency graph */ edges = glade_project_get_graph_deps (project); /* Added hardcoded dependencies */ edges = glade_project_add_hardcoded_dependencies (edges, project); /* Sort toplevels alphabetically */ tree = g_list_sort (tree, glade_widgets_name_cmp); /* Sort dep graph alphabetically using successor name. * _glade_tsort() is a stable sort algorithm so, output of nodes without * dependency depends on the input order */ edges = g_list_sort (edges, glade_node_edge_name_cmp); /* Sort tree */ sorted_tree = _glade_tsort (&tree, &edges); if (edges) { GList *cycles = glade_project_get_nodes_from_edges (project, edges); /* And append to the end of the sorted list */ sorted_tree = g_list_concat (sorted_tree, cycles); _node_edge_list_free (edges); } /* No need to free tree as tsort will consume the list */ return sorted_tree; } static inline void glade_project_write_comments (GladeProject *project, GladeXmlDoc *doc, GladeXmlNode *root) { GladeProjectPrivate *priv = project->priv; GladeXmlNode *comment_node, *node; GList *l; if (priv->license) { /* Replace regular HYPHEN with NON-BREAKING HYPHEN */ gchar *license = _glade_util_strreplace (priv->license, FALSE, "--", "‑‑"); gchar *comment = g_strdup_printf (GLADE_PROJECT_COMMENT"\n\n%s\n\n", license); comment_node = glade_xml_doc_new_comment (doc, comment); g_free (comment); } else comment_node = glade_xml_doc_new_comment (doc, GLADE_PROJECT_COMMENT); comment_node = glade_xml_node_add_prev_sibling (root, comment_node); for (l = priv->comments; l; l = g_list_next (l)) { node = glade_xml_doc_new_comment (doc, l->data); comment_node = glade_xml_node_add_next_sibling (comment_node, node); } } static GladeXmlContext * glade_project_write (GladeProject *project) { GladeProjectPrivate *priv = project->priv; GladeXmlContext *context; GladeXmlDoc *doc; GladeXmlNode *root; GList *list; GList *toplevels; doc = glade_xml_doc_new (); context = glade_xml_context_new (doc, NULL); root = glade_xml_node_new (context, GLADE_XML_TAG_PROJECT); glade_xml_doc_set_root (doc, root); if (priv->translation_domain) glade_xml_node_set_property_string (root, GLADE_TAG_DOMAIN, priv->translation_domain); glade_project_write_comments (project, doc, root); glade_project_write_required_libs (project, context, root); glade_project_write_resource_path (project, context, root); glade_project_write_css_provider_path (project, context, root); glade_project_write_license_data (project, context, root); /* Get sorted toplevels */ toplevels = glade_project_get_ordered_toplevels (project); for (list = toplevels; list; list = g_list_next (list)) { GladeWidget *widget = list->data; /* * Append toplevel widgets. Each widget then takes * care of appending its children. */ if (!glade_widget_get_parent (widget)) glade_widget_write (widget, context, root); else g_warning ("Tried to save a non toplevel object '%s' at xml root", glade_widget_get_name (widget)); } g_list_free (toplevels); return context; } /** * glade_project_backup: * @project: a #GladeProject * @path: location to save glade file * @error: an error from the G_FILE_ERROR domain. * * Backup the last file which @project has saved to * or was loaded from. * * If @path is not the same as the current project * path, then the current project path will be * backed up under the new location. * * If this the first save, and no persisted file * exists, then %TRUE is returned and no backup is made. * * Returns: %TRUE on success, %FALSE on failure */ gboolean glade_project_backup (GladeProject *project, const gchar *path, GError **error) { gchar *canonical_path; gchar *destination_path; gchar *content = NULL; gsize length = 0; gboolean success; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); if (project->priv->path == NULL) return TRUE; canonical_path = glade_util_canonical_path (path); destination_path = g_strconcat (canonical_path, "~", NULL); g_free (canonical_path); success = g_file_get_contents (project->priv->path, &content, &length, error); if (success) success = g_file_set_contents (destination_path, content, length, error); g_free (destination_path); return success; } /** * glade_project_autosave: * @project: a #GladeProject * @error: an error from the G_FILE_ERROR domain. * * Saves an autosave snapshot of @project to it's currently set path * * If the project was never saved, nothing is done and %TRUE is returned. * * Returns: %TRUE on success, %FALSE on failure */ gboolean glade_project_autosave (GladeProject *project, GError **error) { GladeXmlContext *context; GladeXmlDoc *doc; gchar *autosave_path; gint ret; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); if (project->priv->path == NULL) return TRUE; autosave_path = glade_project_autosave_name (project->priv->path); context = glade_project_write (project); doc = glade_xml_context_get_doc (context); ret = glade_xml_doc_save (doc, autosave_path); glade_xml_context_destroy (context); g_free (autosave_path); return ret > 0; } static inline void update_project_resource_path (GladeProject *project, gchar *path) { GFile *new_resource_path; GList *l; new_resource_path = g_file_new_for_path (path); for (l = project->priv->objects; l; l = l->next) { GladeWidget *widget = glade_widget_get_from_gobject (l->data); GList *list; for (list = glade_widget_get_properties (widget); list; list = list->next) { GladeProperty *property = list->data; GladePropertyClass *klass = glade_property_get_class (property); GParamSpec *pspec = glade_property_class_get_pspec (klass); if (pspec->value_type == GDK_TYPE_PIXBUF) { gchar *fullpath, *relpath; const gchar *filename; GFile *fullpath_file; GObject *pixbuf; glade_property_get (property, &pixbuf); if (pixbuf == NULL) continue; filename = g_object_get_data (pixbuf, "GladeFileName"); fullpath = glade_project_resource_fullpath (project, filename); fullpath_file = g_file_new_for_path (fullpath); relpath = _glade_util_file_get_relative_path (new_resource_path, fullpath_file); g_object_set_data_full (pixbuf, "GladeFileName", relpath, g_free); g_object_unref (fullpath_file); g_free (fullpath); } } } g_object_unref (new_resource_path); } static inline void sync_project_resource_path (GladeProject *project) { GList *l; for (l = glade_project_selection_get (project); l; l = l->next) { GladeWidget *widget = glade_widget_get_from_gobject (l->data); GList *list; for (list = glade_widget_get_properties (widget); list; list = list->next) { GladeProperty *property = list->data; GladePropertyClass *klass = glade_property_get_class (property); GParamSpec *pspec = glade_property_class_get_pspec (klass); if (pspec->value_type == GDK_TYPE_PIXBUF) { const gchar *filename; GObject *pixbuf; GValue *value; glade_property_get (property, &pixbuf); if (pixbuf == NULL) continue; filename = g_object_get_data (pixbuf, "GladeFileName"); value = glade_property_class_make_gvalue_from_string (klass, filename, project); glade_property_set_value (property, value); g_value_unset (value); g_free (value); } } } } /** * glade_project_save: * @project: a #GladeProject * @path: location to save glade file * @flags: the #GladeVerifyFlags to warn about * @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_verify (GladeProject *project, const gchar *path, GladeVerifyFlags flags, GError **error) { GladeXmlContext *context; GladeXmlDoc *doc; gchar *canonical_path; gint ret; gchar *autosave_path; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); if (glade_project_is_loading (project)) return FALSE; if (!glade_project_verify (project, TRUE, flags)) return FALSE; /* Delete any autosaves at this point, if they exist */ if (project->priv->path) { autosave_path = glade_project_autosave_name (project->priv->path); g_unlink (autosave_path); g_free (autosave_path); } if (!project->priv->resource_path) { /* Fix pixbuf paths: Since there is no resource_path, images are relative * to path or CWD so they need to be updated to be relative to @path */ gchar *dirname = g_path_get_dirname (path); update_project_resource_path (project, dirname); g_free (dirname); } /* Save the project */ context = glade_project_write (project); doc = glade_xml_context_get_doc (context); ret = glade_xml_doc_save (doc, path); glade_xml_context_destroy (context); canonical_path = glade_util_canonical_path (path); g_assert (canonical_path); if (project->priv->path == NULL || strcmp (canonical_path, project->priv->path)) { project->priv->path = (g_free (project->priv->path), g_strdup (canonical_path)); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]); glade_project_update_properties_title (project); /* Sync selected objects pixbuf properties */ sync_project_resource_path (project); } 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); 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 > 0; } /** * 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) { return glade_project_save_verify (project, path, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_UNRECOGNIZED, error); } /** * glade_project_preview: * @project: a #GladeProject * @gwidget: a #GladeWidget * * Creates and displays a preview window holding a snapshot of @gwidget's * toplevel window in @project. Note that the preview window is only a snapshot * of the current state of the project, there is no limit on how many preview * snapshots can be taken. */ void glade_project_preview (GladeProject *project, GladeWidget *gwidget) { GladeXmlContext *context; gchar *text, *pidstr; GladePreview *preview = NULL; g_return_if_fail (GLADE_IS_PROJECT (project)); project->priv->writing_preview = TRUE; context = glade_project_write (project); project->priv->writing_preview = FALSE; text = glade_xml_dump_from_context (context); gwidget = glade_widget_get_toplevel (gwidget); if (!GTK_IS_WIDGET (glade_widget_get_object (gwidget))) return; if ((pidstr = g_object_get_data (G_OBJECT (gwidget), "preview")) != NULL) preview = g_hash_table_lookup (project->priv->previews, pidstr); if (!preview) { /* If the previewer program is somehow missing, this can return NULL */ preview = glade_preview_launch (gwidget, text); g_return_if_fail (GLADE_IS_PREVIEW (preview)); /* Leave project data on the preview */ g_object_set_data (G_OBJECT (preview), "project", project); /* Leave preview data on the widget */ g_object_set_data_full (G_OBJECT (gwidget), "preview", glade_preview_get_pid_as_str (preview), g_free); g_signal_connect (preview, "exits", G_CALLBACK (glade_project_preview_exits), project); /* Add preview to list of previews */ g_hash_table_insert (project->priv->previews, glade_preview_get_pid_as_str (preview), preview); } else { glade_preview_update (preview, text); } g_free (text); } gboolean glade_project_writing_preview (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->writing_preview; } /******************************************************************* Verify code here (versioning, incompatability checks) *******************************************************************/ /* Defined here for pretty translator comments (bug in intl tools, for some reason * you can only comment about the line directly following, forcing you to write * ugly messy code with comments in line breaks inside function calls). */ /* translators: refers to a widget in toolkit version '%s %d.%d' and a project targeting toolkit version '%s %d.%d' */ #define WIDGET_VERSION_CONFLICT_MSGFMT _("This widget was introduced in %s %d.%d " \ "while project targets %s %d.%d") /* translators: refers to a widget '[%s]' introduced in toolkit version '%s %d.%d' */ #define WIDGET_VERSION_CONFLICT_FMT _("[%s] Object class '%s' was introduced in %s %d.%d\n") #define WIDGET_DEPRECATED_MSG _("This widget is deprecated") /* translators: refers to a widget '[%s]' loaded from toolkit version '%s %d.%d' */ #define WIDGET_DEPRECATED_FMT _("[%s] Object class '%s' from %s %d.%d is deprecated\n") /* translators: refers to a property in toolkit version '%s %d.%d' * and a project targeting toolkit version '%s %d.%d' */ #define PROP_VERSION_CONFLICT_MSGFMT _("This property was introduced in %s %d.%d " \ "while project targets %s %d.%d") /* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ #define PROP_VERSION_CONFLICT_FMT _("[%s] Property '%s' of object class '%s' " \ "was introduced in %s %d.%d\n") /* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ #define PACK_PROP_VERSION_CONFLICT_FMT _("[%s] Packing property '%s' of object class '%s' " \ "was introduced in %s %d.%d\n") #define PROP_DEPRECATED_MSG _("This property is deprecated") /* translators: refers to a property '%s' of widget '[%s]' */ #define PROP_DEPRECATED_FMT _("[%s] Property '%s' of object class '%s' is deprecated\n") /* translators: refers to a signal in toolkit version '%s %d.%d' * and a project targeting toolkit version '%s %d.%d' */ #define SIGNAL_VERSION_CONFLICT_MSGFMT _("This signal was introduced in %s %d.%d " \ "while project targets %s %d.%d") /* translators: refers to a signal '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ #define SIGNAL_VERSION_CONFLICT_FMT _("[%s] Signal '%s' of object class '%s' " \ "was introduced in %s %d.%d\n") #define SIGNAL_DEPRECATED_MSG _("This signal is deprecated") /* translators: refers to a signal '%s' of widget '[%s]' */ #define SIGNAL_DEPRECATED_FMT _("[%s] Signal '%s' of object class '%s' is deprecated\n") static void glade_project_verify_property_internal (GladeProject *project, GladeProperty *property, const gchar *path_name, GString *string, gboolean forwidget, GladeVerifyFlags flags) { GladeWidgetAdaptor *adaptor, *prop_adaptor; GladePropertyClass *pclass; GParamSpec *pspec; gint target_major, target_minor; gchar *catalog, *tooltip; /* For verification lists, we're only interested in verifying the 'used' state of properties. * * For the UI on the other hand, we want to show warnings on unset properties until they * are set. */ if (!forwidget && (glade_property_get_state (property) & GLADE_STATE_CHANGED) == 0) return; pclass = glade_property_get_class (property); pspec = glade_property_class_get_pspec (pclass); prop_adaptor = glade_property_class_get_adaptor (pclass); adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); g_object_get (adaptor, "catalog", &catalog, NULL); glade_project_target_version_for_adaptor (project, adaptor, &target_major, &target_minor); if ((flags & GLADE_VERIFY_VERSIONS) != 0 && !GPC_VERSION_CHECK (pclass, target_major, target_minor)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s not available in version %d.%d\n", glade_property_class_id (pclass), glade_widget_adaptor_get_name (adaptor), target_major, target_minor)); if (forwidget) { tooltip = g_strdup_printf (PROP_VERSION_CONFLICT_MSGFMT, catalog, glade_property_class_since_major (pclass), glade_property_class_since_minor (pclass), catalog, target_major, target_minor); glade_property_set_support_warning (property, FALSE, tooltip); g_free (tooltip); } else g_string_append_printf (string, glade_property_class_get_is_packing (pclass) ? PACK_PROP_VERSION_CONFLICT_FMT : PROP_VERSION_CONFLICT_FMT, path_name, glade_property_class_get_name (pclass), glade_widget_adaptor_get_title (adaptor), catalog, glade_property_class_since_major (pclass), glade_property_class_since_minor (pclass)); } else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && glade_property_class_deprecated (pclass)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s is deprecated\n", glade_property_class_id (pclass), glade_widget_adaptor_get_name (adaptor))); if (forwidget) glade_property_set_support_warning (property, FALSE, PROP_DEPRECATED_MSG); else g_string_append_printf (string, PROP_DEPRECATED_FMT, path_name, glade_property_class_get_name (pclass), glade_widget_adaptor_get_title (adaptor)); } else if (forwidget) glade_property_set_support_warning (property, FALSE, NULL); g_free (catalog); } static void glade_project_verify_properties_internal (GladeWidget *widget, const gchar *path_name, GString *string, gboolean forwidget, GladeVerifyFlags flags) { GList *list; GladeProperty *property; for (list = glade_widget_get_properties (widget); list; list = list->next) { property = list->data; glade_project_verify_property_internal (glade_widget_get_project (widget), property, path_name, string, forwidget, flags); } /* Sometimes widgets on the clipboard have packing props with no parent */ if (glade_widget_get_parent (widget)) { for (list = glade_widget_get_packing_properties (widget); list; list = list->next) { property = list->data; glade_project_verify_property_internal (glade_widget_get_project (widget), property, path_name, string, forwidget, flags); } } } static void glade_project_verify_signal_internal (GladeWidget *widget, GladeSignal *signal, const gchar *path_name, GString *string, gboolean forwidget, GladeVerifyFlags flags) { GladeSignalClass *signal_class; GladeWidgetAdaptor *adaptor; gint target_major, target_minor; gchar *catalog; GladeProject *project; signal_class = glade_widget_adaptor_get_signal_class (glade_widget_get_adaptor (widget), glade_signal_get_name (signal)); if (!signal_class) return; adaptor = glade_signal_class_get_adaptor (signal_class); project = glade_widget_get_project (widget); if (!project) return; g_object_get (adaptor, "catalog", &catalog, NULL); glade_project_target_version_for_adaptor (project, adaptor, &target_major, &target_minor); if ((flags & GLADE_VERIFY_VERSIONS) != 0 && !GSC_VERSION_CHECK (signal_class, target_major, target_minor)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s not avalable in version %d.%d\n", glade_signal_get_name (signal), glade_widget_adaptor_get_name (adaptor), target_major, target_minor)); if (forwidget) { gchar *warning; warning = g_strdup_printf (SIGNAL_VERSION_CONFLICT_MSGFMT, catalog, glade_signal_class_since_major (signal_class), glade_signal_class_since_minor (signal_class), catalog, target_major, target_minor); glade_signal_set_support_warning (signal, warning); g_free (warning); } else g_string_append_printf (string, SIGNAL_VERSION_CONFLICT_FMT, path_name, glade_signal_get_name (signal), glade_widget_adaptor_get_title (adaptor), catalog, glade_signal_class_since_major (signal_class), glade_signal_class_since_minor (signal_class)); } else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && glade_signal_class_deprecated (signal_class)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s is deprecated\n", glade_signal_get_name (signal), glade_widget_adaptor_get_name (adaptor))); if (forwidget) glade_signal_set_support_warning (signal, SIGNAL_DEPRECATED_MSG); else g_string_append_printf (string, SIGNAL_DEPRECATED_FMT, path_name, glade_signal_get_name (signal), glade_widget_adaptor_get_title (adaptor)); } else if (forwidget) glade_signal_set_support_warning (signal, NULL); g_free (catalog); } void glade_project_verify_property (GladeProperty *property) { GladeWidget *widget; GladeProject *project; g_return_if_fail (GLADE_IS_PROPERTY (property)); widget = glade_property_get_widget (property); project = glade_widget_get_project (widget); if (project) glade_project_verify_property_internal (project, property, NULL, NULL, TRUE, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_DEPRECATIONS | GLADE_VERIFY_UNRECOGNIZED); } void glade_project_verify_signal (GladeWidget *widget, GladeSignal *signal) { glade_project_verify_signal_internal (widget, signal, NULL, NULL, TRUE, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_DEPRECATIONS | GLADE_VERIFY_UNRECOGNIZED); } static void glade_project_verify_signals (GladeWidget *widget, const gchar *path_name, GString *string, gboolean forwidget, GladeVerifyFlags flags) { 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_internal (widget, signal, path_name, string, forwidget, flags); } g_list_free (signals); } } /** * glade_project_verify_properties: * @widget: A #GladeWidget * * Synchonizes @widget with user visible information * about version compatability and notifies the UI * it should update. */ static void glade_project_verify_properties (GladeWidget *widget) { GladeProject *project; g_return_if_fail (GLADE_IS_WIDGET (widget)); project = glade_widget_get_project (widget); if (!project || project->priv->loading) return; glade_project_verify_properties_internal (widget, NULL, NULL, TRUE, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_DEPRECATIONS | GLADE_VERIFY_UNRECOGNIZED); glade_project_verify_signals (widget, NULL, NULL, TRUE, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_DEPRECATIONS | GLADE_VERIFY_UNRECOGNIZED); glade_widget_support_changed (widget); } static gboolean glade_project_verify_dialog (GladeProject *project, GString *string, gboolean saving) { GtkWidget *swindow; GtkWidget *textview; GtkWidget *expander; GtkTextBuffer *buffer; GtkTextIter iter; 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_get_start_iter (buffer, &iter); gtk_text_buffer_insert_markup (buffer, &iter, string->str, -1); gtk_widget_set_vexpand (swindow, TRUE); gtk_container_add (GTK_CONTAINER (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; } gboolean glade_project_verify (GladeProject *project, gboolean saving, GladeVerifyFlags flags) { GString *string = g_string_new (NULL); GList *list; gboolean ret = TRUE; GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() start\n")); if (project->priv->template) { gint major, minor; glade_project_get_target_version (project, "gtk+", &major, &minor); if (major == 3 && minor < 10) g_string_append_printf (string, _("Object %s is a class template but this is not supported in gtk+ %d.%d"), glade_widget_get_name (project->priv->template), major, minor); } for (list = project->priv->objects; list; list = list->next) { GladeWidget *widget = glade_widget_get_from_gobject (list->data); if ((flags & GLADE_VERIFY_UNRECOGNIZED) != 0 && GLADE_IS_OBJECT_STUB (list->data)) { gchar *type; g_object_get (list->data, "object-type", &type, NULL); g_string_append_printf (string, _("Object %s has unrecognized type %s\n"), glade_widget_get_name (widget), type); g_free (type); } else { gchar *path_name = glade_widget_generate_path_name (widget); glade_project_verify_adaptor (project, glade_widget_get_adaptor (widget), path_name, string, flags, FALSE, NULL); glade_project_verify_properties_internal (widget, path_name, string, FALSE, flags); glade_project_verify_signals (widget, path_name, string, FALSE, flags); 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); GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() end\n")); return ret; } 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, GladeVerifyFlags flags, gboolean forwidget, GladeSupportMask *mask) { GladeSupportMask support_mask = GLADE_SUPPORT_OK; GladeWidgetAdaptor *adaptor_iter; gint target_major, target_minor; gchar *catalog = NULL; for (adaptor_iter = adaptor; adaptor_iter && support_mask == GLADE_SUPPORT_OK; 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); /* Only one versioning message (builder or otherwise)... */ if ((flags & GLADE_VERIFY_VERSIONS) != 0 && !GWA_VERSION_CHECK (adaptor_iter, target_major, target_minor)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' not available in version %d.%d\n", glade_widget_adaptor_get_name (adaptor_iter), target_major, target_minor)); if (forwidget) g_string_append_printf (string, WIDGET_VERSION_CONFLICT_MSGFMT, catalog, GWA_VERSION_SINCE_MAJOR (adaptor_iter), GWA_VERSION_SINCE_MINOR (adaptor_iter), catalog, target_major, target_minor); else g_string_append_printf (string, WIDGET_VERSION_CONFLICT_FMT, path_name, glade_widget_adaptor_get_title (adaptor_iter), catalog, GWA_VERSION_SINCE_MAJOR (adaptor_iter), GWA_VERSION_SINCE_MINOR (adaptor_iter)); support_mask |= GLADE_SUPPORT_MISMATCH; } if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && GWA_DEPRECATED (adaptor_iter)) { GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' is deprecated\n", glade_widget_adaptor_get_name (adaptor_iter))); if (forwidget) { if (string->len) g_string_append (string, "\n"); g_string_append_printf (string, WIDGET_DEPRECATED_MSG); } else g_string_append_printf (string, WIDGET_DEPRECATED_FMT, path_name, glade_widget_adaptor_get_title (adaptor_iter), catalog, target_major, target_minor); support_mask |= GLADE_SUPPORT_DEPRECATED; } g_free (catalog); } if (mask) *mask = support_mask; } /** * 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) { GString *string = g_string_new (NULL); gchar *ret = NULL; glade_project_verify_adaptor (project, adaptor, NULL, string, GLADE_VERIFY_VERSIONS | GLADE_VERIFY_DEPRECATIONS | GLADE_VERIFY_UNRECOGNIZED, TRUE, mask); /* there was a '\0' byte... */ if (string->len > 0) { ret = string->str; g_string_free (string, FALSE); } else g_string_free (string, TRUE); return ret; } /** * glade_project_verify_project_for_ui: * @project: A #GladeProject * * Checks the project and updates warning strings in the UI */ static void glade_project_verify_project_for_ui (GladeProject *project) { GList *list; GladeWidget *widget; /* Sync displayable info here */ for (list = project->priv->objects; list; list = list->next) { widget = glade_widget_get_from_gobject (list->data); /* Update the support warnings for widget's properties */ glade_project_verify_properties (widget); /* Update the support warning for widget */ glade_widget_verify (widget); } } /** * glade_project_get_widget_by_name: * @project: a #GladeProject * @name: The user visible name of the widget we are looking for * * Searches under @ancestor in @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); if (strcmp (glade_widget_get_name (widget), name) == 0) return widget; } return NULL; } static void glade_project_release_widget_name (GladeProject *project, GladeWidget *gwidget, const char *widget_name) { glade_name_context_release_name (project->priv->widget_names, widget_name); } /** * glade_project_available_widget_name: * @project: a #GladeProject * @widget: the #GladeWidget intended to recieve a new name * @name: base name of the widget to create * * Checks whether @name is an appropriate name for @widget. * * Returns: whether the name is available or not. */ gboolean glade_project_available_widget_name (GladeProject *project, GladeWidget *widget, const gchar *name) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE); if (!name || !name[0]) return FALSE; return !glade_name_context_has_name (project->priv->widget_names, name); } static void glade_project_reserve_widget_name (GladeProject *project, GladeWidget *gwidget, const char *widget_name) { if (!glade_project_available_widget_name (project, gwidget, widget_name)) { g_warning ("BUG: widget '%s' attempting to reserve an unavailable widget name '%s' !", glade_widget_get_name (gwidget), widget_name); return; } /* Add to name context */ glade_name_context_add_name (project->priv->widget_names, widget_name); } /** * glade_project_new_widget_name: * @project: a #GladeProject * @widget: the #GladeWidget intended to recieve a new name, or %NULL * @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. * * Note the @widget parameter is ignored and preserved only for historical reasons. * * Returns: a string containing the new name, %NULL if there is not enough * memory for this string */ gchar * glade_project_new_widget_name (GladeProject *project, GladeWidget *widget, const gchar *base_name) { gchar *name; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); g_return_val_if_fail (base_name && base_name[0], NULL); name = glade_name_context_new_name (project->priv->widget_names, base_name); return name; } static gboolean glade_project_get_iter_for_object (GladeProject *project, GladeWidget *widget, GtkTreeIter *iter) { GtkTreeModel *model = project->priv->model; GladeWidget *widget_iter = widget; GList *parent_node, *hierarchy = NULL; gboolean iter_valid; g_return_val_if_fail (widget, FALSE); g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE); if (!(iter_valid = gtk_tree_model_get_iter_first (model, iter))) return FALSE; /* Generate widget hierarchy list */ while ((widget_iter = glade_widget_get_parent (widget_iter))) hierarchy = g_list_prepend (hierarchy, widget_iter); parent_node = hierarchy; do { gtk_tree_model_get (model, iter, 0, &widget_iter, -1); if (widget_iter == widget) { /* Object found */ g_list_free (hierarchy); return TRUE; } else if (parent_node && widget_iter == parent_node->data) { GtkTreeIter child_iter; if (gtk_tree_model_iter_children (model, &child_iter, iter)) { /* Parent found, adn child iter updated, continue looking */ parent_node = g_list_next (parent_node); *iter = child_iter; continue; } else { /* Parent with no children? */ g_warning ("Discrepancy found in TreeModel data proxy. " "Can not get children iter for widget %s", glade_widget_get_name (widget_iter)); break; } } iter_valid = gtk_tree_model_iter_next (model, iter); } while (iter_valid); g_list_free (hierarchy); return FALSE; } /** * glade_project_set_widget_name: * @project: a #GladeProject * @widget: the #GladeWidget to set a name on * @name: the name to set. * * Sets @name on @widget in @project, if @name is not * available then a new name will be used. */ void glade_project_set_widget_name (GladeProject *project, GladeWidget *widget, const gchar *name) { gchar *new_name; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_WIDGET (widget)); g_return_if_fail (name && name[0]); if (strcmp (name, glade_widget_get_name (widget)) == 0) return; /* Police the widget name */ if (!glade_project_available_widget_name (project, widget, name)) new_name = glade_project_new_widget_name (project, widget, name); else new_name = g_strdup (name); glade_project_reserve_widget_name (project, widget, new_name); /* Release old name and set new widget name */ glade_project_release_widget_name (project, widget, glade_widget_get_name (widget)); glade_widget_set_name (widget, new_name); g_signal_emit (G_OBJECT (project), glade_project_signals[WIDGET_NAME_CHANGED], 0, widget); g_free (new_name); /* Notify views about the iter change */ glade_project_widget_changed (project, widget); } void glade_project_check_reordered (GladeProject *project, GladeWidget *parent, GList *old_order) { GList *new_order, *l, *ll; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_WIDGET (parent)); g_return_if_fail (glade_project_has_object (project, glade_widget_get_object (parent))); new_order = glade_widget_get_children (parent); /* Check if the list changed */ for (l = old_order, ll = new_order; l && ll; l = g_list_next (l), ll = g_list_next (ll)) { if (l->data != ll->data) break; } if (l || ll) { gint *order = g_new0 (gint, g_list_length (new_order)); GtkTreeIter iter; gint i; for (i = 0, l = new_order; l; l = g_list_next (l)) { GObject *obj = l->data; GList *node = g_list_find (old_order, obj); g_assert (node); order[i] = g_list_position (old_order, node); i++; } /* Signal that the rows were reordered */ glade_project_get_iter_for_object (project, parent, &iter); gtk_tree_store_reorder (GTK_TREE_STORE (project->priv->model), &iter, order); g_free (order); } g_list_free (new_order); } static inline gboolean glade_project_has_gwidget (GladeProject *project, GladeWidget *gwidget) { return (glade_widget_get_project (gwidget) == project && glade_widget_in_project (gwidget)); } /** * glade_project_add_object: * @project: the #GladeProject the widget is added to * @object: the #GObject to add * * Adds an object to the project. */ void glade_project_add_object (GladeProject *project, GObject *object) { GladeProjectPrivate *priv; GladeWidget *gwidget; GList *list, *children; const gchar *name; GtkTreeIter iter, *parent = NULL; 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; if (glade_project_has_gwidget (project, gwidget)) { /* FIXME: It's possible we need to notify the model iface if this * happens to make sure the hierarchy is the same, I dont know, this * happens when message dialogs with children are rebuilt but the * hierarchy still looks good afterwards. */ return; } priv = project->priv; name = glade_widget_get_name (gwidget); /* Make sure we have an exclusive name first... */ if (!glade_project_available_widget_name (project, gwidget, name)) { gchar *new_name = glade_project_new_widget_name (project, gwidget, name); /* XXX Collect these errors and make a report at startup time */ if (priv->loading) g_warning ("Loading object '%s' with name conflict, renaming to '%s'", name, new_name); glade_widget_set_name (gwidget, new_name); name = glade_widget_get_name (gwidget); g_free (new_name); } glade_project_reserve_widget_name (project, gwidget, name); glade_widget_set_project (gwidget, (gpointer) project); glade_widget_set_in_project (gwidget, TRUE); g_object_ref_sink (gwidget); /* Be sure to update the lists before emitting signals */ if (glade_widget_get_parent (gwidget) == NULL) priv->tree = g_list_append (priv->tree, object); else if (glade_project_get_iter_for_object (project, glade_widget_get_parent (gwidget), &iter)) { parent = &iter; } priv->objects = g_list_prepend (priv->objects, object); gtk_tree_store_insert_with_values (GTK_TREE_STORE (priv->model), NULL, parent, -1, 0, gwidget, -1); /* NOTE: Sensitive ordering here, we need to recurse after updating * the tree model listeners (and update those listeners after our * internal lists have been resolved), otherwise children are added * before the parents (and the views dont like that). */ if ((children = glade_widget_get_children (gwidget)) != NULL) { for (list = children; list && list->data; list = list->next) glade_project_add_object (project, G_OBJECT (list->data)); g_list_free (children); } /* Update user visible compatibility info */ glade_project_verify_properties (gwidget); g_signal_emit (G_OBJECT (project), glade_project_signals[ADD_WIDGET], 0, gwidget); } /** * 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) { GladeWidget *gwidget; g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); g_return_val_if_fail (G_IS_OBJECT (object), FALSE); gwidget = glade_widget_get_from_gobject (object); g_return_val_if_fail (GLADE_IS_WIDGET (gwidget), FALSE); return glade_project_has_gwidget (project, gwidget); } void glade_project_widget_changed (GladeProject *project, GladeWidget *gwidget) { GtkTreeIter iter; GtkTreePath *path; g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (GLADE_IS_WIDGET (gwidget)); g_return_if_fail (glade_project_has_gwidget (project, gwidget)); glade_project_get_iter_for_object (project, gwidget, &iter); path = gtk_tree_model_get_path (project->priv->model, &iter); gtk_tree_model_row_changed (project->priv->model, path, &iter); gtk_tree_path_free (path); } /** * 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 *list, *children; gchar *preview_pid; GtkTreeIter iter; 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) { if (g_list_find (project->priv->objects, object)) { project->priv->tree = g_list_remove_all (project->priv->tree, object); project->priv->objects = g_list_remove_all (project->priv->objects, object); project->priv->selection = g_list_remove_all (project->priv->selection, object); g_warning ("Internal data model error, removing object %p %s without a GladeWidget wrapper", object, G_OBJECT_TYPE_NAME (object)); } return; } if (!glade_project_has_object (project, object)) return; /* Recurse and remove deepest children first */ if ((children = glade_widget_get_children (gwidget)) != NULL) { for (list = children; list && list->data; list = list->next) glade_project_remove_object (project, G_OBJECT (list->data)); g_list_free (children); } /* Remove selection and release name from the name context */ glade_project_selection_remove (project, object, TRUE); glade_project_release_widget_name (project, gwidget, glade_widget_get_name (gwidget)); g_signal_emit (G_OBJECT (project), glade_project_signals[REMOVE_WIDGET], 0, gwidget); /* Update internal data structure (remove from lists) */ project->priv->tree = g_list_remove (project->priv->tree, object); project->priv->objects = g_list_remove (project->priv->objects, object); if (glade_project_get_iter_for_object (project, gwidget, &iter)) gtk_tree_store_remove (GTK_TREE_STORE (project->priv->model), &iter); else g_warning ("Internal data model error, object %p %s not found in tree model", object, G_OBJECT_TYPE_NAME (object)); if ((preview_pid = g_object_get_data (G_OBJECT (gwidget), "preview"))) g_hash_table_remove (project->priv->previews, preview_pid); /* Unset the project pointer on the GladeWidget */ glade_widget_set_project (gwidget, NULL); glade_widget_set_in_project (gwidget, FALSE); g_object_unref (gwidget); } /******************************************************************* * Other API * *******************************************************************/ /** * 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) { GladeProjectPrivate *priv; g_return_if_fail (GLADE_IS_PROJECT (project)); priv = project->priv; if (priv->modified != modified) { priv->modified = !priv->modified; if (!priv->modified) { priv->first_modification = project->priv->prev_redo_item; priv->first_modification_is_na = FALSE; } g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_MODIFIED]); } } /** * 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; } void glade_project_set_pointer_mode (GladeProject *project, GladePointerMode mode) { g_return_if_fail (GLADE_IS_PROJECT (project)); if (project->priv->pointer_mode != mode) { project->priv->pointer_mode = mode; g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_POINTER_MODE]); } } GladePointerMode glade_project_get_pointer_mode (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->pointer_mode; } void glade_project_set_template (GladeProject *project, GladeWidget *widget) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget)); if (widget) { GObject *object = glade_widget_get_object (widget); g_return_if_fail (GTK_IS_WIDGET (object)); g_return_if_fail (glade_widget_get_parent (widget) == NULL); g_return_if_fail (glade_widget_get_project (widget) == project); } /* Let's not add any strong reference here, we already own the widget */ if (project->priv->template != widget) { if (project->priv->template) glade_widget_set_is_composite (project->priv->template, FALSE); project->priv->template = widget; if (project->priv->template) glade_widget_set_is_composite (project->priv->template, TRUE); glade_project_verify_project_for_ui (project); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_TEMPLATE]); } } GladeWidget * glade_project_get_template (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); return project->priv->template; } void glade_project_set_add_item (GladeProject *project, GladeWidgetAdaptor *adaptor) { GladeProjectPrivate *priv; g_return_if_fail (GLADE_IS_PROJECT (project)); priv = project->priv; if (priv->add_item != adaptor) { priv->add_item = adaptor; g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_ADD_ITEM]); } } GladeWidgetAdaptor * glade_project_get_add_item (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->add_item; } void glade_project_set_target_version (GladeProject *project, const gchar *catalog, gint major, gint minor) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (catalog && catalog[0]); g_return_if_fail (major >= 0); g_return_if_fail (minor >= 0); g_hash_table_insert (project->priv->target_versions_major, g_strdup (catalog), GINT_TO_POINTER ((int) major)); g_hash_table_insert (project->priv->target_versions_minor, g_strdup (catalog), GINT_TO_POINTER ((int) minor)); glade_project_verify_project_for_ui (project); g_signal_emit (project, glade_project_signals[TARGETS_CHANGED], 0); } 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_by_pspec (G_OBJECT (project), glade_project_props[PROP_READ_ONLY]); } } /** * glade_project_get_target_version: * @project: a #GladeProject * @catalog: the name of the catalog @project includes * @major: the return location for the target major version * @minor: the return location for the target minor version * * Fetches the target version of the @project for @catalog. * */ void glade_project_get_target_version (GladeProject *project, const gchar *catalog, gint *major, gint *minor) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (catalog && catalog[0]); g_return_if_fail (major && 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)); } /** * 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_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); /* Cancel any idle we have */ if (project->priv->selection_changed_id > 0) project->priv->selection_changed_id = (g_source_remove (project->priv->selection_changed_id), 0); } static gboolean selection_change_idle (GladeProject *project) { project->priv->selection_changed_id = 0; glade_project_selection_changed (project); return FALSE; } void glade_project_queue_selection_changed (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); if (project->priv->selection_changed_id == 0) project->priv->selection_changed_id = g_idle_add ((GSourceFunc) selection_change_idle, project); } 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_by_pspec (G_OBJECT (project), glade_project_props[PROP_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) { GList *l; g_return_if_fail (GLADE_IS_PROJECT (project)); if (project->priv->selection == NULL) return; for (l = project->priv->selection; l; l = l->next) { if (GTK_IS_WIDGET (l->data)) gtk_widget_queue_draw (GTK_WIDGET (l->data)); } 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)) { 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 (glade_project_has_object (project, object)); if (glade_project_is_selected (project, object) == FALSE) { gboolean toggle_has_selection = (project->priv->selection == NULL); if (GTK_IS_WIDGET (object)) gtk_widget_queue_draw (GTK_WIDGET (object)); project->priv->selection = g_list_prepend (project->priv->selection, object); if (toggle_has_selection) glade_project_set_has_selection (project, TRUE); 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)); g_return_if_fail (glade_project_has_object (project, object)); 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; } /** * glade_project_required_libs: * @project: a #GladeProject * * Returns: a #GList of allocated strings which are the names * of the required catalogs for this project */ GList * glade_project_required_libs (GladeProject *project) { GList *l, *required = NULL; /* Assume GTK+ catalog here */ required = g_list_prepend (required, _glade_catalog_get_catalog ("gtk+")); for (l = project->priv->objects; l; l = g_list_next (l)) { GladeWidget *gwidget = glade_widget_get_from_gobject (l->data); GladeCatalog *catalog; gchar *name = NULL; g_assert (gwidget); g_object_get (glade_widget_get_adaptor (gwidget), "catalog", &name, NULL); if ((catalog = _glade_catalog_get_catalog (name))) { if (!g_list_find (required, catalog)) required = g_list_prepend (required, catalog); } g_free (name); } /* Sort by dependency */ required = _glade_catalog_tsort (required); /* Convert list of GladeCatalog to list of names */ for (l = required; l; l = g_list_next (l)) l->data = g_strdup (glade_catalog_get_name (l->data)); for (l = project->priv->unknown_catalogs; l; l = g_list_next (l)) { CatalogInfo *data = l->data; /* Keep position to make sure we do not create a diff when saving */ required = g_list_insert (required, g_strdup (data->catalog), data->position); } return required; } /** * 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 && glade_command_group_id (next_cmd) != 0 && glade_command_group_id (next_cmd) == glade_command_group_id (cmd)) { 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; g_return_val_if_fail (project != NULL, NULL); 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 (glade_command_description (cmd)); 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; g_return_val_if_fail (project != NULL, NULL); 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 (glade_command_description (cmd)); 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_resource_fullpath: * @project: The #GladeProject. * @resource: The resource basename * * Project resource strings are always relative, this function tranforms a * path relative to project to a full path. * * Returns: A newly allocated string holding the * full path to the resource. */ gchar * glade_project_resource_fullpath (GladeProject *project, const gchar *resource) { gchar *fullpath, *project_dir = NULL; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); if (project->priv->path == NULL) project_dir = g_get_current_dir (); else project_dir = g_path_get_dirname (project->priv->path); if (project->priv->resource_path) { if (g_path_is_absolute (project->priv->resource_path)) fullpath = g_build_filename (project->priv->resource_path, resource, NULL); else fullpath = g_build_filename (project_dir, project->priv->resource_path, resource, NULL); } else fullpath = g_build_filename (project_dir, resource, NULL); g_free (project_dir); return fullpath; } /** * glade_project_widget_visibility_changed: * @project: The #GladeProject. * @widget: The widget which visibility changed * @visible: widget visibility value * * Emmits GladeProject::widget-visibility-changed signal * */ void glade_project_widget_visibility_changed (GladeProject *project, GladeWidget *widget, gboolean visible) { g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (project == glade_widget_get_project (widget)); g_signal_emit (project, glade_project_signals[WIDGET_VISIBILITY_CHANGED], 0, widget, visible); } 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; } /** * glade_projects_get_objects: * @project: a GladeProject * * Returns: List of all objects in this project */ const GList * glade_project_get_objects (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->objects; } /** * glade_project_properties: * @project: A #GladeProject * * Runs a document properties dialog for @project. */ void glade_project_properties (GladeProject *project) { g_return_if_fail (GLADE_IS_PROJECT (project)); gtk_window_present (GTK_WINDOW (project->priv->prefs_dialog)); } gchar * glade_project_display_dependencies (GladeProject *project) { GList *catalogs, *l; GString *string; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); string = g_string_new (""); catalogs = glade_project_required_libs (project); for (l = catalogs; l; l = l->next) { gchar *catalog = l->data; gint major = 0, minor = 0; glade_project_get_target_version (project, catalog, &major, &minor); if (l != catalogs) g_string_append (string, ", "); /* Capitalize GTK+ */ if (strcmp (catalog, "gtk+") == 0) g_string_append_printf (string, "GTK+ >= %d.%d", major, minor); else if (major && minor) g_string_append_printf (string, "%s >= %d.%d", catalog, major, minor); else g_string_append_printf (string, "%s", catalog); g_free (catalog); } g_list_free (catalogs); return g_string_free (string, FALSE); } /** * glade_project_toplevels: * @project: a #GladeProject * * Returns: a #GList containing the #GtkWidget toplevel items in @project */ GList * glade_project_toplevels (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->tree; } /** * glade_project_set_translation_domain: * @project: a #GladeProject * @domain: the translation domain * * Set the project translation domain. */ void glade_project_set_translation_domain (GladeProject *project, const gchar *domain) { GladeProjectPrivate *priv; g_return_if_fail (GLADE_IS_PROJECT (project)); priv = project->priv; if (g_strcmp0 (priv->translation_domain, domain)) { g_free (priv->translation_domain); priv->translation_domain = g_strdup (domain); g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_TRANSLATION_DOMAIN]); } } /** * glade_project_get_translation_domain: * @project: a #GladeProject * * Returns: the translation domain */ const gchar * glade_project_get_translation_domain (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->translation_domain; } static void glade_project_css_provider_remove_forall (GtkWidget *widget, gpointer data) { gtk_style_context_remove_provider (gtk_widget_get_style_context (widget), GTK_STYLE_PROVIDER (data)); if (GTK_IS_CONTAINER (widget)) gtk_container_forall (GTK_CONTAINER (widget), glade_project_css_provider_remove_forall, data); } static inline void glade_project_css_provider_refresh (GladeProject *project, gboolean remove) { GladeProjectPrivate *priv = project->priv; GtkCssProvider *provider = priv->css_provider; const GList *l; for (l = priv->tree; l; l = g_list_next (l)) { GObject *object = l->data; if (!GTK_IS_WIDGET (object) || GLADE_IS_OBJECT_STUB (object)) continue; if (remove) glade_project_css_provider_remove_forall (GTK_WIDGET (object), provider); else glade_project_set_css_provider_forall (GTK_WIDGET (object), provider); } } static void on_css_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GladeProject *project) { GError *error = NULL; gtk_css_provider_load_from_file (project->priv->css_provider, file, &error); if (error) { g_message ("CSS parsing failed: %s", error->message); g_error_free (error); } } /** * glade_project_set_css_provider_path: * @project: a #GladeProject * @path: a CSS file path * * Set the custom CSS provider path to use in @project */ void glade_project_set_css_provider_path (GladeProject *project, const gchar *path) { GladeProjectPrivate *priv; g_return_if_fail (GLADE_IS_PROJECT (project)); priv = project->priv; if (g_strcmp0 (priv->css_provider_path, path) != 0) { g_free (priv->css_provider_path); priv->css_provider_path = g_strdup (path); g_clear_object (&priv->css_monitor); if (priv->css_provider) { glade_project_css_provider_refresh (project, TRUE); g_clear_object (&priv->css_provider); } if (priv->css_provider_path && g_file_test (priv->css_provider_path, G_FILE_TEST_IS_REGULAR)) { GFile *file = g_file_new_for_path (priv->css_provider_path); priv->css_provider = GTK_CSS_PROVIDER (gtk_css_provider_new ()); g_object_ref_sink (priv->css_provider); gtk_css_provider_load_from_file (priv->css_provider, file, NULL); g_clear_object (&priv->css_monitor); priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_ref_sink (priv->css_monitor); g_signal_connect_object (priv->css_monitor, "changed", G_CALLBACK (on_css_monitor_changed), project, 0); glade_project_css_provider_refresh (project, FALSE); g_object_unref (file); } g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_CSS_PROVIDER_PATH]); } } /** * glade_project_get_css_provider_path: * @project: a #GladeProject * * Returns: the CSS path of the custom provider used for @project */ const gchar * glade_project_get_css_provider_path (GladeProject *project) { g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); return project->priv->css_provider_path; } /************************************************* * Command Central * *************************************************/ static gboolean widget_contains_unknown_type (GladeWidget *widget) { GList *list, *l; GObject *object; gboolean has_unknown = FALSE; object = glade_widget_get_object (widget); if (GLADE_IS_OBJECT_STUB (object)) return TRUE; list = glade_widget_get_children (widget); for (l = list; l && has_unknown == FALSE; l = l->next) { GladeWidget *child = glade_widget_get_from_gobject (l->data); has_unknown = widget_contains_unknown_type (child); } g_list_free (list); return has_unknown; } void glade_project_copy_selection (GladeProject *project) { GList *widgets = NULL, *list; gboolean has_unknown = FALSE; g_return_if_fail (GLADE_IS_PROJECT (project)); if (glade_project_is_loading (project)) return; if (!project->priv->selection) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("No widget selected.")); return; } for (list = project->priv->selection; list && list->data; list = list->next) { GladeWidget *widget = glade_widget_get_from_gobject (list->data); if (widget_contains_unknown_type (widget)) has_unknown = TRUE; else widgets = g_list_prepend (widgets, glade_widget_dup (widget, FALSE)); } if (has_unknown) glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Unable to copy unrecognized widget type.")); glade_clipboard_add (glade_app_get_clipboard (), widgets); g_list_free (widgets); } void glade_project_command_cut (GladeProject *project) { GList *widgets = NULL, *list; gboolean has_unknown = FALSE; gboolean failed = FALSE; g_return_if_fail (GLADE_IS_PROJECT (project)); if (glade_project_is_loading (project)) return; for (list = project->priv->selection; list && list->data; list = list->next) { GladeWidget *widget = glade_widget_get_from_gobject (list->data); if (widget_contains_unknown_type (widget)) has_unknown = TRUE; else widgets = g_list_prepend (widgets, widget); } if (failed == FALSE && widgets != NULL) glade_command_cut (widgets); else if (has_unknown) glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Unable to cut unrecognized widget type")); else if (widgets == NULL) glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("No widget selected.")); if (widgets) g_list_free (widgets); } void glade_project_command_paste (GladeProject *project, GladePlaceholder *placeholder) { GladeClipboard *clipboard; GList *list; GladeWidget *widget = NULL, *parent; gint placeholder_relations = 0; g_return_if_fail (GLADE_IS_PROJECT (project)); if (glade_project_is_loading (project)) return; if (placeholder) { if (glade_placeholder_get_project (placeholder) == NULL || glade_project_is_loading (glade_placeholder_get_project (placeholder))) return; } list = project->priv->selection; clipboard = glade_app_get_clipboard (); /* If there is a selection, paste in to the selected widget, otherwise * paste into the placeholder's parent, or at the toplevel */ parent = list ? glade_widget_get_from_gobject (list->data) : (placeholder) ? glade_placeholder_get_parent (placeholder) : NULL; widget = glade_clipboard_widgets (clipboard) ? glade_clipboard_widgets (clipboard)->data : NULL; /* Ignore parent argument if we are pasting a toplevel */ if (g_list_length (glade_clipboard_widgets (clipboard)) == 1 && widget && GWA_IS_TOPLEVEL (glade_widget_get_adaptor (widget))) parent = NULL; /* Check if parent is actually a container of any sort */ if (parent && !glade_widget_adaptor_is_container (glade_widget_get_adaptor (parent))) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Unable to paste to the selected parent")); return; } /* Check if selection is good */ if (project->priv->selection) { if (g_list_length (project->priv->selection) != 1) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Unable to paste to multiple widgets")); return; } } /* Check if we have anything to paste */ if (g_list_length (glade_clipboard_widgets (clipboard)) == 0) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("No widget on the clipboard")); return; } /* Check that the underlying adaptor allows the paste */ if (parent) { for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next) { widget = list->data; if (!glade_widget_add_verify (parent, widget, TRUE)) return; } } /* Check that we have compatible heirarchies */ for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next) { widget = list->data; if (!GWA_IS_TOPLEVEL (glade_widget_get_adaptor (widget)) && parent) { /* Count placeholder relations */ if (glade_widget_placeholder_relation (parent, widget)) placeholder_relations++; } } g_assert (widget); /* A GladeWidget that doesnt use placeholders can only paste one * at a time * * XXX: Not sure if this has to be true. */ if (GTK_IS_WIDGET (glade_widget_get_object (widget)) && parent && !GWA_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) && g_list_length (glade_clipboard_widgets (clipboard)) != 1) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Only one widget can be pasted at a " "time to this container")); return; } /* Check that enough placeholders are available */ if (parent && GWA_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) && glade_util_count_placeholders (parent) < placeholder_relations) { glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Insufficient amount of placeholders in " "target container")); return; } glade_command_paste (glade_clipboard_widgets (clipboard), parent, placeholder, project); } void glade_project_command_delete (GladeProject *project) { GList *widgets = NULL, *list; GladeWidget *widget; gboolean failed = FALSE; g_return_if_fail (GLADE_IS_PROJECT (project)); if (glade_project_is_loading (project)) return; for (list = project->priv->selection; list && list->data; list = list->next) { widget = glade_widget_get_from_gobject (list->data); widgets = g_list_prepend (widgets, widget); } if (failed == FALSE && widgets != NULL) glade_command_delete (widgets); else if (widgets == NULL) glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("No widget selected.")); if (widgets) g_list_free (widgets); }