mirror of
https://gitlab.gnome.org/GNOME/glade.git
synced 2025-11-27 00:06:20 -05:00
4922 lines
147 KiB
C
4922 lines
147 KiB
C
/*
|
|
* 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 <chema@celorio.com>
|
|
* Tristan Van Berkom <tvb@gnome.org>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
/**
|
|
* 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 <string.h>
|
|
#include <stdlib.h>
|
|
#include <glib.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "glade.h"
|
|
#include "glade-widget.h"
|
|
#include "glade-id-allocator.h"
|
|
#include "glade-app.h"
|
|
#include "glade-marshallers.h"
|
|
#include "glade-catalog.h"
|
|
|
|
#include "glade-preview.h"
|
|
|
|
#include "glade-project.h"
|
|
#include "glade-command.h"
|
|
#include "glade-name-context.h"
|
|
#include "glade-object-stub.h"
|
|
|
|
#define VALID_ITER(project, iter) ((iter)!= NULL && G_IS_OBJECT ((iter)->user_data) && ((GladeProject*)(project))->priv->stamp == (iter)->stamp)
|
|
|
|
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,
|
|
N_PROPERTIES
|
|
};
|
|
|
|
static GParamSpec *properties[N_PROPERTIES];
|
|
|
|
struct _GladeProjectPrivate
|
|
{
|
|
gchar *path; /* The full canonical path of the glade file for this project */
|
|
|
|
gint unsaved_number; /* A unique number for this project if it is untitled */
|
|
|
|
GladeWidgetAdaptor *add_item; /* The next item to add to the project. */
|
|
|
|
gint stamp; /* A a random int per instance of project used to stamp/check the
|
|
* GtkTreeIter->stamps */
|
|
GList *tree; /* List of toplevel Objects in this projects */
|
|
GList *objects; /* List of all objects in this project */
|
|
|
|
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
|
|
*/
|
|
|
|
GtkAccelGroup *accel_group;
|
|
|
|
gchar *comment; /* XML comment, Glade will preserve whatever comment was
|
|
* in file, so users can delete or change it.
|
|
*/
|
|
|
|
time_t mtime; /* last UTC modification time of file, or 0 if it could not be read */
|
|
|
|
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).
|
|
*/
|
|
|
|
GList *unknown_catalogs; /* List of CatalogInfo catalogs */
|
|
|
|
/* Control on the properties dialog to update buttons etc when properties change */
|
|
GtkWidget *prefs_dialog;
|
|
GtkWidget *project_wide_radio;
|
|
GtkWidget *toplevel_contextual_radio;
|
|
GHashTable *target_radios;
|
|
|
|
GtkWidget *resource_default_radio;
|
|
GtkWidget *resource_relative_radio;
|
|
GtkWidget *resource_fullpath_radio;
|
|
GtkWidget *relative_path_entry;
|
|
GtkWidget *full_path_button;
|
|
|
|
/* 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 pointer_mode : 2; /* The currently effective GladePointerMode */
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gchar *catalog;
|
|
gint position;
|
|
} CatalogInfo;
|
|
|
|
GType
|
|
glade_pointer_mode_get_type (void)
|
|
{
|
|
static GType etype = 0;
|
|
|
|
if (etype == 0)
|
|
{
|
|
static const GEnumValue values[] = {
|
|
{GLADE_POINTER_SELECT, "select", "Select-widgets"},
|
|
{GLADE_POINTER_ADD_WIDGET, "add", "Add-widgets"},
|
|
{GLADE_POINTER_DRAG_RESIZE, "drag-resize", "Drag-and-resize-widgets"},
|
|
{0, NULL, NULL}
|
|
};
|
|
etype = g_enum_register_static ("GladePointerMode", values);
|
|
}
|
|
return etype;
|
|
}
|
|
|
|
static void glade_project_set_target_version (GladeProject *project,
|
|
const gchar *catalog,
|
|
guint16 major, guint16 minor);
|
|
|
|
static void glade_project_target_version_for_adaptor (GladeProject *project,
|
|
GladeWidgetAdaptor *adaptor,
|
|
gint *major,
|
|
gint *minor);
|
|
|
|
static void glade_project_set_readonly (GladeProject *project,
|
|
gboolean readonly);
|
|
|
|
|
|
static gboolean glade_project_verify (GladeProject *project, gboolean saving);
|
|
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,
|
|
gboolean saving,
|
|
gboolean forwidget,
|
|
GladeSupportMask *mask);
|
|
|
|
static GtkWidget *glade_project_build_prefs_dialog (GladeProject *project);
|
|
|
|
static void target_button_clicked (GtkWidget *widget, GladeProject *project);
|
|
static void update_prefs_for_resource_path (GladeProject *project);
|
|
|
|
static void gtk_tree_model_iface_init (GtkTreeModelIface *iface);
|
|
|
|
static void glade_project_model_get_iter_for_object (GladeProject *project,
|
|
GObject *object,
|
|
GtkTreeIter *iter);
|
|
|
|
static gint glade_project_count_children (GladeProject *project,
|
|
GladeWidget *parent);
|
|
|
|
static guint glade_project_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static GladeIDAllocator *unsaved_number_allocator = NULL;
|
|
|
|
#define GLADE_PROJECT_LARGE_PROJECT 40
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GladeProject, glade_project, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
|
|
gtk_tree_model_iface_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);
|
|
|
|
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_CLASS (glade_project_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
glade_project_finalize (GObject *object)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (object);
|
|
|
|
gtk_widget_destroy (project->priv->prefs_dialog);
|
|
|
|
g_free (project->priv->path);
|
|
g_free (project->priv->comment);
|
|
|
|
if (project->priv->unsaved_number > 0)
|
|
glade_id_allocator_release (get_unsaved_number_allocator (),
|
|
project->priv->unsaved_number);
|
|
|
|
g_hash_table_destroy (project->priv->target_versions_major);
|
|
g_hash_table_destroy (project->priv->target_versions_minor);
|
|
g_hash_table_destroy (project->priv->target_radios);
|
|
|
|
glade_name_context_destroy (project->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;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* glade_project_set_modified:
|
|
* @project: a #GladeProject
|
|
* @modified: Whether the project should be set as modified or not
|
|
* @modification: The first #GladeCommand which caused the project to have unsaved changes
|
|
*
|
|
* Set's whether a #GladeProject should be flagged as modified or not. This is useful
|
|
* for indicating that a project has unsaved changes. If @modified is #TRUE, then
|
|
* @modification will be recorded as the first change which caused the project to
|
|
* have unsaved changes. @modified is #FALSE then @modification will be ignored.
|
|
*
|
|
* If @project is already flagged as modified, then calling this method with
|
|
* @modified as #TRUE, will have no effect. Likewise, if @project is unmodified
|
|
* then calling this method with @modified as #FALSE, will have no effect.
|
|
*
|
|
*/
|
|
static void
|
|
glade_project_set_modified (GladeProject *project, gboolean modified)
|
|
{
|
|
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), properties[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), properties[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_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), properties[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;
|
|
}
|
|
|
|
/*******************************************************************
|
|
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 void
|
|
glade_project_preview_exits (GladePreview *preview, GladeProject *project)
|
|
{
|
|
gchar *pidstr;
|
|
|
|
pidstr = g_strdup_printf ("%d", glade_preview_get_pid (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);
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
Class Initializers
|
|
*******************************************************************/
|
|
static void
|
|
glade_project_init (GladeProject *project)
|
|
{
|
|
GladeProjectPrivate *priv;
|
|
GList *list;
|
|
|
|
project->priv = priv =
|
|
G_TYPE_INSTANCE_GET_PRIVATE ((project), GLADE_TYPE_PROJECT,
|
|
GladeProjectPrivate);
|
|
|
|
priv->path = NULL;
|
|
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->accel_group = NULL;
|
|
|
|
priv->unsaved_number =
|
|
glade_id_allocator_allocate (get_unsaved_number_allocator ());
|
|
|
|
do
|
|
{ /* Get a random non-zero TreeIter stamper */
|
|
priv->stamp = g_random_int ();
|
|
}
|
|
while (priv->stamp == 0);
|
|
|
|
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->target_radios = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, NULL);
|
|
priv->prefs_dialog = glade_project_build_prefs_dialog (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->finalize = glade_project_finalize;
|
|
object_class->dispose = glade_project_dispose;
|
|
|
|
klass->add_object = NULL;
|
|
klass->remove_object = NULL;
|
|
klass->undo = glade_project_undo_impl;
|
|
klass->redo = glade_project_redo_impl;
|
|
klass->next_undo_item = glade_project_next_undo_item_impl;
|
|
klass->next_redo_item = glade_project_next_redo_item_impl;
|
|
klass->push_undo = glade_project_push_undo_impl;
|
|
|
|
klass->widget_name_changed = NULL;
|
|
klass->selection_changed = NULL;
|
|
klass->close = NULL;
|
|
klass->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);
|
|
|
|
properties[PROP_MODIFIED] =
|
|
g_param_spec_boolean ("modified",
|
|
"Modified",
|
|
_("Whether project has been modified since it was last saved"),
|
|
FALSE,
|
|
G_PARAM_READABLE);
|
|
|
|
properties[PROP_HAS_SELECTION] =
|
|
g_param_spec_boolean ("has-selection",
|
|
_("Has Selection"),
|
|
_("Whether project has a selection"),
|
|
FALSE,
|
|
G_PARAM_READABLE);
|
|
|
|
properties[PROP_PATH] =
|
|
g_param_spec_string ("path",
|
|
_("Path"),
|
|
_("The filesystem path of the project"),
|
|
NULL,
|
|
G_PARAM_READABLE);
|
|
|
|
properties[PROP_READ_ONLY] =
|
|
g_param_spec_boolean ("read-only",
|
|
_("Read Only"),
|
|
_("Whether project is read-only"),
|
|
FALSE,
|
|
G_PARAM_READABLE);
|
|
|
|
properties[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);
|
|
|
|
properties[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);
|
|
|
|
/* Install all properties */
|
|
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
|
|
|
|
g_type_class_add_private (klass, sizeof (GladeProjectPrivate));
|
|
}
|
|
|
|
/*******************************************************************
|
|
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 gchar *
|
|
glade_project_read_requires_from_comment (GladeXmlNode *comment,
|
|
guint16 *major, guint16 *minor)
|
|
{
|
|
gint maj, min;
|
|
gchar *value, buffer[256];
|
|
gchar *required_lib = NULL;
|
|
|
|
if (!glade_xml_node_is_comment (comment))
|
|
return NULL;
|
|
|
|
value = glade_xml_get_content (comment);
|
|
|
|
if (value &&
|
|
!strncmp (" interface-requires", value, strlen (" interface-requires")))
|
|
{
|
|
if (sscanf (value, " interface-requires %s %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;
|
|
gchar *required_lib = NULL;
|
|
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))
|
|
{
|
|
/* 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 gchar *
|
|
glade_project_read_resource_path_from_comment (GladeXmlNode *comment)
|
|
{
|
|
gchar *value, buffer[FILENAME_MAX], *path = NULL;
|
|
|
|
if (!glade_xml_node_is_comment (comment))
|
|
return FALSE;
|
|
|
|
value = glade_xml_get_content (comment);
|
|
if (value &&
|
|
!strncmp (" interface-local-resource-path", value,
|
|
strlen (" interface-local-resource-path")))
|
|
{
|
|
if (sscanf (value, " interface-local-resource-path %s", buffer) == 1)
|
|
path = g_strdup (buffer);
|
|
}
|
|
g_free (value);
|
|
|
|
return path;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* This function assumes ownership of 'path'. */
|
|
static void
|
|
glade_project_set_resource_path (GladeProject *project, gchar *path)
|
|
{
|
|
g_free (project->priv->resource_path);
|
|
project->priv->resource_path = path;
|
|
|
|
update_project_for_resource_path (project);
|
|
update_prefs_for_resource_path (project);
|
|
}
|
|
|
|
static void
|
|
glade_project_read_resource_path (GladeProject *project,
|
|
GladeXmlNode *root_node)
|
|
{
|
|
GladeXmlNode *node;
|
|
gchar *path = NULL;
|
|
|
|
for (node = glade_xml_node_get_children_with_comments (root_node);
|
|
node; node = glade_xml_node_next_with_comments (node))
|
|
{
|
|
/* Skip non "requires" tags */
|
|
if ((path = glade_project_read_resource_path_from_comment (node)) != NULL)
|
|
break;
|
|
}
|
|
|
|
glade_project_set_resource_path (project, path);
|
|
}
|
|
|
|
|
|
static void
|
|
glade_project_read_comment (GladeProject *project, GladeXmlDoc *doc)
|
|
{
|
|
/* TODO Write me !! Find out how to extract root level comments
|
|
* with libxml2 !!!
|
|
*/
|
|
}
|
|
|
|
|
|
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))
|
|
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: reffers 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 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;
|
|
gint count;
|
|
|
|
priv->selection = NULL;
|
|
priv->objects = NULL;
|
|
priv->loading = TRUE;
|
|
|
|
/* get the context & root node of the catalog file */
|
|
if (!(context =
|
|
glade_xml_context_new_from_path (priv->path, NULL, NULL)))
|
|
{
|
|
g_warning ("Couldn't open glade file [%s].", priv->path);
|
|
priv->loading = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
priv->mtime = glade_util_get_file_mtime (priv->path, NULL);
|
|
|
|
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",
|
|
priv->path);
|
|
glade_xml_context_free (context);
|
|
priv->loading = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
/* Emit "parse-began" signal */
|
|
g_signal_emit (project, glade_project_signals[PARSE_BEGAN], 0);
|
|
|
|
/* XXX Need to load project->priv->comment ! */
|
|
glade_project_read_comment (project, doc);
|
|
|
|
/* 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, priv->path, &has_gtk_dep);
|
|
|
|
glade_project_read_resource_path (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))
|
|
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;
|
|
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);
|
|
|
|
/* Reset project status here too so that you get a clean
|
|
* slate after calling glade_project_open().
|
|
*/
|
|
priv->modified = FALSE;
|
|
priv->loading = FALSE;
|
|
|
|
/* Emit "parse-finished" signal */
|
|
g_signal_emit (project, glade_project_signals[PARSE_FINISHED], 0);
|
|
|
|
/* Update ui with versioning info
|
|
*/
|
|
glade_project_verify_project_for_ui (project);
|
|
|
|
glade_project_check_target_version (project);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
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), properties[PROP_PATH]);
|
|
|
|
retval = glade_project_load_internal (project);
|
|
|
|
if (retval)
|
|
{
|
|
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);
|
|
}
|
|
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;
|
|
gboolean retval;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
project = g_object_new (GLADE_TYPE_PROJECT, NULL);
|
|
|
|
project->priv->path = glade_util_canonical_path (path);
|
|
|
|
retval = glade_project_load_internal (project);
|
|
|
|
if (retval)
|
|
{
|
|
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);
|
|
|
|
return project;
|
|
}
|
|
else
|
|
{
|
|
g_object_unref (project);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*******************************************************************
|
|
Writing project code here
|
|
*******************************************************************/
|
|
|
|
#define GLADE_XML_COMMENT "Generated with "PACKAGE_NAME
|
|
|
|
static gchar *
|
|
glade_project_make_comment ()
|
|
{
|
|
time_t now = time (NULL);
|
|
gchar *comment;
|
|
comment = g_strdup_printf (GLADE_XML_COMMENT " " PACKAGE_VERSION " on %s",
|
|
ctime (&now));
|
|
glade_util_replace (comment, '\n', ' ');
|
|
|
|
return comment;
|
|
}
|
|
|
|
static void
|
|
glade_project_update_comment (GladeProject *project)
|
|
{
|
|
gchar **lines, **l, *comment = NULL;
|
|
|
|
/* If project has no comment -> add the new one */
|
|
if (project->priv->comment == NULL)
|
|
{
|
|
project->priv->comment = glade_project_make_comment ();
|
|
return;
|
|
}
|
|
|
|
for (lines = l = g_strsplit (project->priv->comment, "\n", 0); *l; l++)
|
|
{
|
|
gchar *start;
|
|
|
|
/* Ignore leading spaces */
|
|
for (start = *l; *start && g_ascii_isspace (*start); start++);
|
|
|
|
if (g_str_has_prefix (start, GLADE_XML_COMMENT))
|
|
{
|
|
/* This line was generated by glade -> updating... */
|
|
g_free (*l);
|
|
*l = comment = glade_project_make_comment ();
|
|
}
|
|
}
|
|
|
|
if (comment)
|
|
{
|
|
g_free (project->priv->comment);
|
|
project->priv->comment = g_strjoinv ("\n", lines);
|
|
}
|
|
|
|
g_strfreev (lines);
|
|
}
|
|
|
|
static void
|
|
glade_project_write_required_libs (GladeProject *project,
|
|
GladeXmlContext *context,
|
|
GladeXmlNode *root)
|
|
{
|
|
GladeXmlNode *req_node;
|
|
GList *required, *list;
|
|
gint major, minor;
|
|
gchar *version;
|
|
|
|
if ((required = glade_project_required_libs (project)) != NULL)
|
|
{
|
|
for (list = required; list; list = list->next)
|
|
{
|
|
glade_project_get_target_version (project, (gchar *) list->data,
|
|
&major, &minor);
|
|
|
|
version = g_strdup_printf ("%d.%d", major, minor);
|
|
|
|
/* Write the standard requires tag */
|
|
if (GLADE_GTKBUILDER_HAS_VERSIONING (major, minor))
|
|
{
|
|
req_node = glade_xml_node_new (context, GLADE_XML_TAG_REQUIRES);
|
|
glade_xml_node_append_child (root, req_node);
|
|
glade_xml_node_set_property_string (req_node,
|
|
GLADE_XML_TAG_LIB,
|
|
(gchar *) list->data);
|
|
}
|
|
else
|
|
{
|
|
gchar *comment = g_strdup_printf (" interface-requires %s %s ",
|
|
(gchar *) list->data, version);
|
|
req_node = glade_xml_node_new_comment (context, comment);
|
|
glade_xml_node_append_child (root, req_node);
|
|
g_free (comment);
|
|
}
|
|
|
|
glade_xml_node_set_property_string (req_node, GLADE_XML_TAG_VERSION,
|
|
version);
|
|
g_free (version);
|
|
|
|
}
|
|
g_list_foreach (required, (GFunc) g_free, NULL);
|
|
g_list_free (required);
|
|
}
|
|
}
|
|
|
|
static void
|
|
glade_project_write_resource_path (GladeProject *project,
|
|
GladeXmlContext *context,
|
|
GladeXmlNode *root)
|
|
{
|
|
GladeXmlNode *path_node;
|
|
if (project->priv->resource_path)
|
|
{
|
|
gchar *comment = g_strdup_printf (" interface-local-resource-path %s ",
|
|
project->priv->resource_path);
|
|
path_node = glade_xml_node_new_comment (context, comment);
|
|
glade_xml_node_append_child (root, path_node);
|
|
g_free (comment);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
sort_project_dependancies (GObject *a, GObject *b)
|
|
{
|
|
GladeWidget *ga, *gb;
|
|
|
|
ga = glade_widget_get_from_gobject (a);
|
|
gb = glade_widget_get_from_gobject (b);
|
|
|
|
if (glade_widget_adaptor_depends (glade_widget_get_adaptor (ga), ga, gb))
|
|
return 1;
|
|
else if (glade_widget_adaptor_depends (glade_widget_get_adaptor (gb), gb, ga))
|
|
return -1;
|
|
else
|
|
return strcmp (glade_widget_get_name (ga), glade_widget_get_name (gb));
|
|
}
|
|
|
|
static GladeXmlContext *
|
|
glade_project_write (GladeProject *project)
|
|
{
|
|
GladeXmlContext *context;
|
|
GladeXmlDoc *doc;
|
|
GladeXmlNode *root; /* *comment_node; */
|
|
GList *list;
|
|
|
|
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);
|
|
|
|
glade_project_update_comment (project);
|
|
/* comment_node = glade_xml_node_new_comment (context, project->priv->comment); */
|
|
|
|
/* XXX Need to append this to the doc ! not the ROOT !
|
|
glade_xml_node_append_child (root, comment_node); */
|
|
|
|
glade_project_write_required_libs (project, context, root);
|
|
|
|
glade_project_write_resource_path (project, context, root);
|
|
|
|
/* Sort the whole thing */
|
|
project->priv->objects =
|
|
g_list_sort (project->priv->objects,
|
|
(GCompareFunc) sort_project_dependancies);
|
|
|
|
for (list = project->priv->objects; list; list = list->next)
|
|
{
|
|
GladeWidget *widget;
|
|
|
|
widget = glade_widget_get_from_gobject (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);
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
GladeXmlContext *context;
|
|
GladeXmlDoc *doc;
|
|
gchar *canonical_path;
|
|
gint ret;
|
|
|
|
g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE);
|
|
|
|
if (glade_project_is_loading (project))
|
|
return FALSE;
|
|
|
|
if (!glade_project_verify (project, TRUE))
|
|
return FALSE;
|
|
|
|
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))
|
|
{
|
|
gchar *name, *title;
|
|
|
|
project->priv->path = (g_free (project->priv->path),
|
|
g_strdup (canonical_path));
|
|
g_object_notify_by_pspec (G_OBJECT (project), properties[PROP_PATH]);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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_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));
|
|
|
|
context = glade_project_write (project);
|
|
|
|
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)
|
|
{
|
|
preview = glade_preview_launch (gwidget, text);
|
|
|
|
/* 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",
|
|
g_strdup_printf ("%d", glade_preview_get_pid (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,
|
|
g_strdup_printf("%d", glade_preview_get_pid (preview)),
|
|
preview);
|
|
}
|
|
else
|
|
{
|
|
glade_preview_update (preview, text);
|
|
}
|
|
|
|
g_free (text);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Verify code here (versioning, incompatability checks)
|
|
*******************************************************************/
|
|
|
|
/* translators: reffers 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: reffers 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: reffers 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")
|
|
|
|
|
|
/* 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: reffers 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: reffers 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: reffers 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")
|
|
|
|
/* translators: reffers 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")
|
|
|
|
/* translators: reffers 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")
|
|
|
|
|
|
static void
|
|
glade_project_verify_property_internal (GladeProject *project,
|
|
GladeProperty *property,
|
|
const gchar *path_name,
|
|
GString *string,
|
|
gboolean forwidget)
|
|
{
|
|
GladeWidgetAdaptor *adaptor, *prop_adaptor;
|
|
GladePropertyClass *pclass;
|
|
GParamSpec *pspec;
|
|
gint target_major, target_minor;
|
|
gchar *catalog, *tooltip;
|
|
|
|
if (glade_property_original_default (property) && !forwidget)
|
|
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 (!GPC_VERSION_CHECK (pclass, 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 (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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
glade_project_verify_signal_internal (GladeWidget *widget,
|
|
GladeSignal *signal,
|
|
const gchar *path_name,
|
|
GString *string,
|
|
gboolean forwidget)
|
|
{
|
|
GladeSignalClass *signal_class;
|
|
GladeWidgetAdaptor *adaptor;
|
|
gint target_major, target_minor;
|
|
gchar *catalog;
|
|
|
|
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);
|
|
|
|
g_object_get (adaptor, "catalog", &catalog, NULL);
|
|
glade_project_target_version_for_adaptor (glade_widget_get_project (widget),
|
|
adaptor,
|
|
&target_major, &target_minor);
|
|
|
|
if (!GSC_VERSION_CHECK (signal_class, 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 (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);
|
|
}
|
|
|
|
void
|
|
glade_project_verify_signal (GladeWidget *widget, GladeSignal *signal)
|
|
{
|
|
glade_project_verify_signal_internal (widget, signal, NULL, NULL, TRUE);
|
|
}
|
|
|
|
static void
|
|
glade_project_verify_signals (GladeWidget *widget,
|
|
const gchar *path_name,
|
|
GString *string,
|
|
gboolean forwidget)
|
|
{
|
|
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);
|
|
}
|
|
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_project_verify_signals (widget, NULL, NULL, TRUE);
|
|
|
|
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;
|
|
gchar *name;
|
|
gboolean ret;
|
|
|
|
swindow = gtk_scrolled_window_new (NULL, NULL);
|
|
textview = gtk_text_view_new ();
|
|
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
|
|
expander = gtk_expander_new (_("Details"));
|
|
|
|
gtk_text_buffer_set_text (buffer, string->str, -1);
|
|
|
|
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (swindow),
|
|
textview);
|
|
gtk_container_add (GTK_CONTAINER (expander), swindow);
|
|
gtk_widget_show_all (expander);
|
|
|
|
gtk_widget_set_size_request (swindow, 800, -1);
|
|
|
|
name = glade_project_get_name (project);
|
|
ret = glade_util_ui_message (glade_app_get_window (),
|
|
saving ? GLADE_UI_YES_OR_NO : GLADE_UI_INFO,
|
|
expander,
|
|
saving ?
|
|
_("Project \"%s\" has errors. Save anyway?") :
|
|
_("Project \"%s\" has deprecated widgets "
|
|
"and/or version mismatches."), name);
|
|
g_free (name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
glade_project_verify (GladeProject *project, gboolean saving)
|
|
{
|
|
GString *string = g_string_new (NULL);
|
|
GList *list;
|
|
gboolean ret = TRUE;
|
|
|
|
for (list = project->priv->objects; list; list = list->next)
|
|
{
|
|
GladeWidget *widget = glade_widget_get_from_gobject (list->data);
|
|
|
|
if (GLADE_IS_OBJECT_STUB (list->data))
|
|
{
|
|
gchar *type;
|
|
g_object_get (list->data, "object-type", &type, NULL);
|
|
|
|
/* translators: reffers to an unknow object named '%s' of type '%s' */
|
|
g_string_append_printf (string, _("Unknow object %s with 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, saving, FALSE, NULL);
|
|
glade_project_verify_properties_internal (widget, path_name, string,
|
|
FALSE);
|
|
glade_project_verify_signals (widget, path_name, string, FALSE);
|
|
|
|
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);
|
|
|
|
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,
|
|
gboolean saving,
|
|
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 (!GWA_VERSION_CHECK (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 (!saving && GWA_DEPRECATED (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, FALSE, 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;
|
|
gchar *warning;
|
|
|
|
/* Sync displayable info here */
|
|
for (list = project->priv->objects; list; list = list->next)
|
|
{
|
|
widget = glade_widget_get_from_gobject (list->data);
|
|
|
|
warning =
|
|
glade_project_verify_widget_adaptor (project, glade_widget_get_adaptor (widget), NULL);
|
|
glade_widget_set_support_warning (widget, warning);
|
|
|
|
if (warning)
|
|
g_free (warning);
|
|
|
|
glade_project_verify_properties (widget);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* glade_project_get_widget_by_name:
|
|
* @project: a #GladeProject
|
|
* @ancestor: The toplevel project object to search under
|
|
* @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
|
|
* @base_name: base name of the widget to create
|
|
*
|
|
* Creates a new name for a widget that doesn't collide with any of the names
|
|
* already in @project. This name will start with @base_name.
|
|
*
|
|
* Returns: a string containing the new name, %NULL if there is not enough
|
|
* memory for this string
|
|
*/
|
|
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 (GLADE_IS_WIDGET (widget), 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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
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_model_get_iter_for_object (project, glade_widget_get_object (widget), &iter);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
gtk_tree_model_row_changed (GTK_TREE_MODEL (project), path, &iter);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
static void
|
|
glade_project_notify_row_has_child (GladeProject *project, GladeWidget *gwidget)
|
|
{
|
|
GladeWidget *parent;
|
|
gint siblings;
|
|
|
|
parent = glade_widget_get_parent (gwidget);
|
|
|
|
if (parent)
|
|
{
|
|
siblings = glade_project_count_children (project, parent);
|
|
|
|
if (siblings == 1)
|
|
{
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
|
|
glade_project_model_get_iter_for_object (project, glade_widget_get_object (parent), &iter);
|
|
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (project), path, &iter);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
glade_project_notify_row_inserted (GladeProject *project, GladeWidget *gwidget)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
/* The state of old iters go invalid and then the new iter is valid
|
|
* until the next change */
|
|
project->priv->stamp++;
|
|
|
|
glade_project_model_get_iter_for_object (project, glade_widget_get_object (gwidget), &iter);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
gtk_tree_model_row_inserted (GTK_TREE_MODEL (project), path, &iter);
|
|
gtk_tree_path_free (path);
|
|
|
|
glade_project_notify_row_has_child (project, gwidget);
|
|
}
|
|
|
|
static void
|
|
glade_project_notify_row_deleted (GladeProject *project, GladeWidget *gwidget)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
glade_project_model_get_iter_for_object (project, glade_widget_get_object (gwidget), &iter);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
|
|
gtk_tree_model_row_deleted (GTK_TREE_MODEL (project), path);
|
|
gtk_tree_path_free (path);
|
|
|
|
project->priv->stamp++;
|
|
}
|
|
|
|
void
|
|
glade_project_check_reordered (GladeProject *project,
|
|
GladeWidget *parent,
|
|
GList *old_order)
|
|
{
|
|
GList *new_order, *l, *ll;
|
|
gint *order, n_children, i;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
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 = l->next, ll = ll->next)
|
|
{
|
|
if (l->data != ll->data)
|
|
break;
|
|
}
|
|
|
|
if (l || ll)
|
|
{
|
|
n_children = glade_project_count_children (project, parent);
|
|
order = g_new (gint, n_children);
|
|
|
|
for (i = 0, l = new_order; l; l = l->next)
|
|
{
|
|
GObject *obj = l->data;
|
|
|
|
if (glade_project_has_object (project, obj))
|
|
{
|
|
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_model_get_iter_for_object (project, glade_widget_get_object (parent), &iter);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
gtk_tree_model_rows_reordered (GTK_TREE_MODEL (project), path, &iter, order);
|
|
gtk_tree_path_free (path);
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
priv->objects = g_list_prepend (priv->objects, object);
|
|
|
|
glade_project_notify_row_inserted (project, gwidget);
|
|
|
|
/* 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)
|
|
{
|
|
GObject *object;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
g_return_if_fail (GLADE_IS_PROJECT (project));
|
|
g_return_if_fail (GLADE_IS_WIDGET (gwidget));
|
|
|
|
object = glade_widget_get_object (gwidget);
|
|
g_return_if_fail (glade_project_has_object (project, object));
|
|
|
|
glade_project_model_get_iter_for_object (project, object, &iter);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (project), &iter);
|
|
gtk_tree_model_row_changed (GTK_TREE_MODEL (project), 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;
|
|
|
|
g_return_if_fail (GLADE_IS_PROJECT (project));
|
|
g_return_if_fail (G_IS_OBJECT (object));
|
|
|
|
if (!glade_project_has_object (project, object))
|
|
return;
|
|
|
|
if (GLADE_IS_PLACEHOLDER (object))
|
|
return;
|
|
|
|
if ((gwidget = glade_widget_get_from_gobject (object)) == NULL)
|
|
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);
|
|
}
|
|
|
|
/* Notify views that the row is being deleted *before* deleting it */
|
|
glade_project_notify_row_deleted (project, gwidget);
|
|
|
|
/* 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 ((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);
|
|
|
|
glade_project_notify_row_has_child (project, gwidget);
|
|
g_object_unref (gwidget);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Remaining stubs and api
|
|
*******************************************************************/
|
|
static void
|
|
glade_project_set_target_version (GladeProject *project,
|
|
const gchar *catalog,
|
|
guint16 major,
|
|
guint16 minor)
|
|
{
|
|
GladeTargetableVersion *version;
|
|
GSList *radios, *list;
|
|
GtkWidget *radio;
|
|
|
|
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));
|
|
|
|
/* Update prefs dialog from here... */
|
|
if (project->priv->target_radios &&
|
|
(radios =
|
|
g_hash_table_lookup (project->priv->target_radios, catalog)) != NULL)
|
|
{
|
|
for (list = radios; list; list = list->next)
|
|
g_signal_handlers_block_by_func (G_OBJECT (list->data),
|
|
G_CALLBACK (target_button_clicked),
|
|
project);
|
|
|
|
for (list = radios; list; list = list->next)
|
|
{
|
|
radio = list->data;
|
|
|
|
version = g_object_get_data (G_OBJECT (radio), "version");
|
|
if (version->major == major && version->minor == minor)
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (list = radios; list; list = list->next)
|
|
g_signal_handlers_unblock_by_func (G_OBJECT (list->data),
|
|
G_CALLBACK (target_button_clicked),
|
|
project);
|
|
}
|
|
|
|
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), properties[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), properties[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 *required = NULL, *l, *ll;
|
|
GladeWidget *gwidget;
|
|
gboolean listed;
|
|
|
|
for (l = project->priv->objects; l; l = l->next)
|
|
{
|
|
gchar *catalog = NULL;
|
|
|
|
gwidget = glade_widget_get_from_gobject (l->data);
|
|
g_assert (gwidget);
|
|
|
|
g_object_get (glade_widget_get_adaptor (gwidget), "catalog", &catalog, NULL);
|
|
|
|
if (catalog)
|
|
{
|
|
listed = FALSE;
|
|
for (ll = required; ll; ll = ll->next)
|
|
if (!strcmp ((gchar *) ll->data, catalog))
|
|
{
|
|
listed = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!listed)
|
|
required = g_list_prepend (required, catalog);
|
|
}
|
|
}
|
|
|
|
/* Assume GTK+ here */
|
|
if (!required)
|
|
required = g_list_prepend (required, g_strdup ("gtk+"));
|
|
|
|
required = g_list_reverse (required);
|
|
|
|
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 may be relative or fullpaths, but glade
|
|
* always expects a copy in the glade file directory, this function
|
|
* is used to make a local path to the file.
|
|
*
|
|
* Returns: A newly allocated string holding the
|
|
* local path the the project resource.
|
|
*/
|
|
gchar *
|
|
glade_project_resource_fullpath (GladeProject *project, const gchar *resource)
|
|
{
|
|
gchar *fullpath, *project_dir = NULL, *basename;
|
|
|
|
g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
|
|
|
|
basename = g_path_get_basename (resource);
|
|
|
|
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, basename, NULL);
|
|
else
|
|
fullpath =
|
|
g_build_filename (project_dir, project->priv->resource_path,
|
|
basename, NULL);
|
|
}
|
|
else
|
|
fullpath = g_build_filename (project_dir, basename, NULL);
|
|
|
|
g_free (project_dir);
|
|
g_free (basename);
|
|
|
|
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;
|
|
}
|
|
|
|
static void
|
|
target_button_clicked (GtkWidget *widget, GladeProject *project)
|
|
{
|
|
GladeTargetableVersion *version =
|
|
g_object_get_data (G_OBJECT (widget), "version");
|
|
gchar *catalog = g_object_get_data (G_OBJECT (widget), "catalog");
|
|
|
|
glade_project_set_target_version (project,
|
|
catalog, version->major, version->minor);
|
|
}
|
|
|
|
static void
|
|
verify_clicked (GtkWidget *button, GladeProject *project)
|
|
{
|
|
if (glade_project_verify (project, FALSE))
|
|
{
|
|
gchar *name = glade_project_get_name (project);
|
|
glade_util_ui_message (glade_app_get_window (),
|
|
GLADE_UI_INFO, NULL,
|
|
_("Project %s has no deprecated widgets "
|
|
"or version mismatches."), name);
|
|
g_free (name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
resource_default_toggled (GtkWidget *widget, GladeProject *project)
|
|
{
|
|
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
|
|
return;
|
|
|
|
glade_project_set_resource_path (project, NULL);
|
|
gtk_widget_set_sensitive (project->priv->relative_path_entry, FALSE);
|
|
gtk_widget_set_sensitive (project->priv->full_path_button, FALSE);
|
|
}
|
|
|
|
|
|
static void
|
|
resource_relative_toggled (GtkWidget *widget, GladeProject *project)
|
|
{
|
|
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
|
|
return;
|
|
|
|
gtk_widget_set_sensitive (project->priv->relative_path_entry, TRUE);
|
|
gtk_widget_set_sensitive (project->priv->full_path_button, FALSE);
|
|
}
|
|
|
|
static void
|
|
resource_fullpath_toggled (GtkWidget *widget, GladeProject *project)
|
|
{
|
|
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
|
|
return;
|
|
|
|
gtk_widget_set_sensitive (project->priv->relative_path_entry, FALSE);
|
|
gtk_widget_set_sensitive (project->priv->full_path_button, TRUE);
|
|
}
|
|
|
|
static void
|
|
resource_path_activated (GtkEntry *entry, GladeProject *project)
|
|
{
|
|
const gchar *text = gtk_entry_get_text (entry);
|
|
|
|
glade_project_set_resource_path (project, text ? g_strdup (text) : NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
resource_full_path_set (GtkFileChooserButton *button, GladeProject *project)
|
|
{
|
|
gchar *text = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (button));
|
|
|
|
glade_project_set_resource_path (project, text);
|
|
}
|
|
|
|
static void
|
|
update_prefs_for_resource_path (GladeProject *project)
|
|
{
|
|
gtk_widget_set_sensitive (project->priv->full_path_button, FALSE);
|
|
gtk_widget_set_sensitive (project->priv->relative_path_entry, FALSE);
|
|
|
|
|
|
g_signal_handlers_block_by_func (project->priv->resource_default_radio,
|
|
G_CALLBACK (resource_default_toggled),
|
|
project);
|
|
g_signal_handlers_block_by_func (project->priv->resource_relative_radio,
|
|
G_CALLBACK (resource_relative_toggled),
|
|
project);
|
|
g_signal_handlers_block_by_func (project->priv->resource_fullpath_radio,
|
|
G_CALLBACK (resource_fullpath_toggled),
|
|
project);
|
|
g_signal_handlers_block_by_func (project->priv->relative_path_entry,
|
|
G_CALLBACK (resource_path_activated),
|
|
project);
|
|
|
|
if (project->priv->resource_path == NULL)
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
|
|
(project->priv->resource_default_radio),
|
|
TRUE);
|
|
else if (g_path_is_absolute (project->priv->resource_path))
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
|
|
(project->priv->resource_fullpath_radio),
|
|
TRUE);
|
|
gtk_widget_set_sensitive (project->priv->full_path_button, TRUE);
|
|
}
|
|
else
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
|
|
(project->priv->resource_relative_radio),
|
|
TRUE);
|
|
gtk_widget_set_sensitive (project->priv->relative_path_entry, TRUE);
|
|
}
|
|
|
|
gtk_entry_set_text (GTK_ENTRY (project->priv->relative_path_entry),
|
|
project->priv->resource_path ? project->priv->
|
|
resource_path : "");
|
|
|
|
g_signal_handlers_unblock_by_func (project->priv->resource_default_radio,
|
|
G_CALLBACK (resource_default_toggled),
|
|
project);
|
|
g_signal_handlers_unblock_by_func (project->priv->resource_relative_radio,
|
|
G_CALLBACK (resource_relative_toggled),
|
|
project);
|
|
g_signal_handlers_unblock_by_func (project->priv->resource_fullpath_radio,
|
|
G_CALLBACK (resource_fullpath_toggled),
|
|
project);
|
|
g_signal_handlers_unblock_by_func (project->priv->relative_path_entry,
|
|
G_CALLBACK (resource_path_activated),
|
|
project);
|
|
}
|
|
|
|
|
|
static GtkWidget *
|
|
glade_project_build_prefs_box (GladeProject *project)
|
|
{
|
|
GtkWidget *main_box, *button;
|
|
GtkWidget *vbox, *hbox, *frame;
|
|
GtkWidget *target_radio, *active_radio;
|
|
GtkWidget *label, *alignment;
|
|
GList *list, *targets;
|
|
gchar *string;
|
|
GtkWidget *main_frame, *main_alignment;
|
|
GtkSizeGroup *sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
|
|
main_frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (main_frame), GTK_SHADOW_NONE);
|
|
main_alignment = gtk_alignment_new (0.5F, 0.5F, 0.8F, 0.8F);
|
|
main_box = gtk_vbox_new (FALSE, 0);
|
|
|
|
gtk_alignment_set_padding (GTK_ALIGNMENT (main_alignment), 0, 0, 4, 0);
|
|
|
|
gtk_container_add (GTK_CONTAINER (main_alignment), main_box);
|
|
gtk_container_add (GTK_CONTAINER (main_frame), main_alignment);
|
|
|
|
/* Resource path */
|
|
string =
|
|
g_strdup_printf ("<b>%s</b>", _("Image resources are loaded locally:"));
|
|
frame = gtk_frame_new (NULL);
|
|
vbox = gtk_vbox_new (FALSE, 0);
|
|
alignment = gtk_alignment_new (0.5F, 0.5F, 0.8F, 0.8F);
|
|
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 8, 0, 12, 0);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
|
|
label = gtk_label_new (string);
|
|
g_free (string);
|
|
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_box), frame, TRUE, TRUE, 2);
|
|
gtk_frame_set_label_widget (GTK_FRAME (frame), label);
|
|
gtk_container_add (GTK_CONTAINER (frame), alignment);
|
|
gtk_container_add (GTK_CONTAINER (alignment), vbox);
|
|
|
|
/* Project directory... */
|
|
project->priv->resource_default_radio =
|
|
gtk_radio_button_new_with_label (NULL, _("From the project directory"));
|
|
gtk_box_pack_start (GTK_BOX (vbox), project->priv->resource_default_radio,
|
|
FALSE, FALSE, 0);
|
|
gtk_size_group_add_widget (sizegroup, project->priv->resource_default_radio);
|
|
|
|
/* Project relative directory... */
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
project->priv->resource_relative_radio =
|
|
gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON
|
|
(project->priv->
|
|
resource_default_radio),
|
|
_("From a project relative directory"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), project->priv->resource_relative_radio,
|
|
TRUE, TRUE, 0);
|
|
project->priv->relative_path_entry = gtk_entry_new ();
|
|
gtk_box_pack_start (GTK_BOX (hbox), project->priv->relative_path_entry, FALSE,
|
|
TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
|
|
gtk_size_group_add_widget (sizegroup, hbox);
|
|
|
|
|
|
/* fullpath directory... */
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
project->priv->resource_fullpath_radio =
|
|
gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON
|
|
(project->priv->resource_default_radio),
|
|
_("From this directory"));
|
|
gtk_box_pack_start (GTK_BOX (hbox), project->priv->resource_fullpath_radio,
|
|
TRUE, TRUE, 0);
|
|
|
|
project->priv->full_path_button =
|
|
gtk_file_chooser_button_new (_("Choose a path to load image resources"),
|
|
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
|
|
gtk_box_pack_start (GTK_BOX (hbox), project->priv->full_path_button, FALSE,
|
|
TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
|
|
gtk_size_group_add_widget (sizegroup, hbox);
|
|
|
|
/* Pass ownership to the widgets in the group */
|
|
g_object_unref (sizegroup);
|
|
|
|
update_prefs_for_resource_path (project);
|
|
|
|
g_signal_connect (G_OBJECT (project->priv->resource_default_radio), "toggled",
|
|
G_CALLBACK (resource_default_toggled), project);
|
|
g_signal_connect (G_OBJECT (project->priv->resource_relative_radio),
|
|
"toggled", G_CALLBACK (resource_relative_toggled), project);
|
|
g_signal_connect (G_OBJECT (project->priv->resource_fullpath_radio),
|
|
"toggled", G_CALLBACK (resource_fullpath_toggled), project);
|
|
|
|
g_signal_connect (G_OBJECT (project->priv->relative_path_entry), "activate",
|
|
G_CALLBACK (resource_path_activated), project);
|
|
g_signal_connect (G_OBJECT (project->priv->full_path_button), "file-set",
|
|
G_CALLBACK (resource_full_path_set), project);
|
|
|
|
/* Target versions */
|
|
string = g_strdup_printf ("<b>%s</b>", _("Toolkit versions required:"));
|
|
frame = gtk_frame_new (NULL);
|
|
vbox = gtk_vbox_new (FALSE, 0);
|
|
alignment = gtk_alignment_new (0.5F, 0.5F, 1.0F, 1.0F);
|
|
|
|
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 8, 0, 12, 0);
|
|
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
|
|
|
|
label = gtk_label_new (string);
|
|
g_free (string);
|
|
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
|
|
|
|
gtk_frame_set_label_widget (GTK_FRAME (frame), label);
|
|
gtk_container_add (GTK_CONTAINER (alignment), vbox);
|
|
gtk_container_add (GTK_CONTAINER (frame), alignment);
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_box), frame, TRUE, TRUE, 6);
|
|
|
|
/* Add stuff to vbox */
|
|
for (list = glade_app_get_catalogs (); list; list = list->next)
|
|
{
|
|
GladeCatalog *catalog = list->data;
|
|
gint minor, major;
|
|
|
|
/* Skip if theres only one option */
|
|
if (g_list_length (glade_catalog_get_targets (catalog)) <= 1)
|
|
continue;
|
|
|
|
glade_project_get_target_version (project,
|
|
glade_catalog_get_name (catalog),
|
|
&major, &minor);
|
|
|
|
/* Special case to mark GTK+ in upper case */
|
|
if (strcmp (glade_catalog_get_name (catalog), "gtk+") == 0)
|
|
label = gtk_label_new ("GTK+");
|
|
else
|
|
label = gtk_label_new (glade_catalog_get_name (catalog));
|
|
gtk_misc_set_alignment (GTK_MISC (label), 0.0F, 0.5F);
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 2);
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
|
|
active_radio = NULL;
|
|
target_radio = NULL;
|
|
|
|
for (targets = glade_catalog_get_targets (catalog);
|
|
targets; targets = targets->next)
|
|
{
|
|
GladeTargetableVersion *version = targets->data;
|
|
gchar *name = g_strdup_printf ("%d.%d",
|
|
version->major,
|
|
version->minor);
|
|
|
|
if (!target_radio)
|
|
target_radio = gtk_radio_button_new_with_label (NULL, name);
|
|
else
|
|
target_radio =
|
|
gtk_radio_button_new_with_label_from_widget
|
|
(GTK_RADIO_BUTTON (target_radio), name);
|
|
g_free (name);
|
|
|
|
g_signal_connect (G_OBJECT (target_radio), "clicked",
|
|
G_CALLBACK (target_button_clicked), project);
|
|
|
|
g_object_set_data (G_OBJECT (target_radio), "version", version);
|
|
g_object_set_data (G_OBJECT (target_radio), "catalog",
|
|
(gchar *) glade_catalog_get_name (catalog));
|
|
|
|
gtk_box_pack_end (GTK_BOX (hbox), target_radio, TRUE, TRUE, 2);
|
|
|
|
if (major == version->major && minor == version->minor)
|
|
active_radio = target_radio;
|
|
|
|
}
|
|
|
|
if (active_radio)
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (active_radio), TRUE);
|
|
g_hash_table_insert (project->priv->target_radios,
|
|
g_strdup (glade_catalog_get_name (catalog)),
|
|
gtk_radio_button_get_group (GTK_RADIO_BUTTON
|
|
(active_radio)));
|
|
}
|
|
else
|
|
g_warning ("Corrupt catalog versions");
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
|
|
}
|
|
|
|
/* Run verify */
|
|
hbox = gtk_hbox_new (FALSE, 2);
|
|
button = gtk_button_new_from_stock (GTK_STOCK_EXECUTE);
|
|
g_signal_connect (G_OBJECT (button), "clicked",
|
|
G_CALLBACK (verify_clicked), project);
|
|
|
|
label = gtk_label_new (_("Verify versions and deprecations:"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4);
|
|
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 4);
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 4);
|
|
|
|
gtk_widget_show_all (main_frame);
|
|
|
|
return main_frame;
|
|
}
|
|
|
|
static GtkWidget *
|
|
glade_project_build_prefs_dialog (GladeProject *project)
|
|
{
|
|
GtkWidget *widget, *dialog;
|
|
gchar *title, *name;
|
|
|
|
name = glade_project_get_name (project);
|
|
title = g_strdup_printf (_("%s document properties"), name);
|
|
|
|
dialog = gtk_dialog_new_with_buttons (title,
|
|
GTK_WINDOW (glade_app_get_window ()),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_STOCK_CLOSE,
|
|
GTK_RESPONSE_ACCEPT, NULL);
|
|
g_free (title);
|
|
g_free (name);
|
|
|
|
widget = glade_project_build_prefs_box (project);
|
|
gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
|
widget, TRUE, TRUE, 2);
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
|
|
gtk_box_set_spacing (GTK_BOX
|
|
(gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
|
|
|
|
/* HIG spacings */
|
|
gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
|
|
gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); /* 2 * 5 + 2 = 12 */
|
|
gtk_container_set_border_width (GTK_CONTAINER
|
|
(gtk_dialog_get_action_area
|
|
(GTK_DIALOG (dialog))), 5);
|
|
gtk_box_set_spacing (GTK_BOX
|
|
(gtk_dialog_get_action_area (GTK_DIALOG (dialog))), 6);
|
|
|
|
|
|
/* Were explicitly destroying it anyway */
|
|
g_signal_connect (G_OBJECT (dialog), "delete-event",
|
|
G_CALLBACK (gtk_widget_hide_on_delete), NULL);
|
|
|
|
/* Only one action, used to "close" the dialog */
|
|
g_signal_connect (G_OBJECT (dialog), "response",
|
|
G_CALLBACK (gtk_widget_hide), NULL);
|
|
|
|
return dialog;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/* GtkTreeModel implementation */
|
|
|
|
static GObject *
|
|
glade_project_nth_child (GladeProject *project,
|
|
GladeWidget *parent,
|
|
gint nth)
|
|
{
|
|
GList *children, *list;
|
|
GObject *child = NULL;
|
|
gint i;
|
|
|
|
children = glade_widget_get_children (parent);
|
|
|
|
for (list = children, i = 0; list; list = list->next)
|
|
{
|
|
child = list->data;
|
|
|
|
if (!glade_project_has_object (project, child))
|
|
continue;
|
|
|
|
if (i == nth)
|
|
break;
|
|
|
|
child = NULL;
|
|
i++;
|
|
}
|
|
|
|
g_list_free (children);
|
|
|
|
return child;
|
|
}
|
|
|
|
static gint
|
|
glade_project_child_position (GladeProject *project,
|
|
GladeWidget *parent,
|
|
GObject *child)
|
|
{
|
|
GList *children, *list;
|
|
GObject *iter;
|
|
gint i, position = -1;
|
|
|
|
children = glade_widget_get_children (parent);
|
|
|
|
for (list = children, i = 0; list; list = list->next)
|
|
{
|
|
iter = list->data;
|
|
|
|
if (!glade_project_has_object (project, iter))
|
|
continue;
|
|
|
|
if (iter == child)
|
|
{
|
|
position = i;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
g_list_free (children);
|
|
|
|
return position;
|
|
}
|
|
|
|
static gint
|
|
glade_project_count_children (GladeProject *project, GladeWidget *parent)
|
|
{
|
|
GList *children, *list;
|
|
GObject *iter;
|
|
gint i;
|
|
|
|
children = glade_widget_get_children (parent);
|
|
|
|
for (list = children, i = 0; list; list = list->next)
|
|
{
|
|
iter = list->data;
|
|
|
|
if (!glade_project_has_object (project, iter))
|
|
continue;
|
|
i++;
|
|
}
|
|
|
|
g_list_free (children);
|
|
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
glade_project_model_get_iter_for_object (GladeProject *project,
|
|
GObject *object,
|
|
GtkTreeIter *iter)
|
|
{
|
|
g_assert (object);
|
|
|
|
iter->stamp = project->priv->stamp;
|
|
iter->user_data = object;
|
|
}
|
|
|
|
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;
|
|
default:
|
|
g_assert_not_reached ();
|
|
return G_TYPE_NONE;
|
|
}
|
|
}
|
|
|
|
GladeWidget *debug_widget;
|
|
|
|
static gboolean
|
|
glade_project_model_get_iter (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GtkTreePath *path)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
gint *indices = gtk_tree_path_get_indices (path);
|
|
gint depth = gtk_tree_path_get_depth (path);
|
|
GladeWidget *widget;
|
|
GObject *object;
|
|
gint i;
|
|
GList *parent;
|
|
|
|
if ((parent = g_list_nth (project->priv->tree, indices[0])) != NULL)
|
|
{
|
|
object = parent->data;
|
|
widget = glade_widget_get_from_gobject (object);
|
|
}
|
|
else
|
|
{
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
debug_widget = widget;
|
|
for (i = 1; i < depth; i++)
|
|
{
|
|
object = glade_project_nth_child (project, widget, indices[i]);
|
|
|
|
if (!object)
|
|
{
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
widget = glade_widget_get_from_gobject (object);
|
|
debug_widget = widget;
|
|
}
|
|
|
|
if (object)
|
|
{
|
|
glade_project_model_get_iter_for_object (project, object, iter);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GtkTreePath *
|
|
glade_project_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GtkTreePath *path;
|
|
GObject *object;
|
|
GladeWidget *widget;
|
|
GladeWidget *toplevel;
|
|
GladeWidget *parent;
|
|
GList *top;
|
|
|
|
g_return_val_if_fail (VALID_ITER (project, iter), NULL);
|
|
|
|
object = iter->user_data;
|
|
widget = glade_widget_get_from_gobject (object);
|
|
toplevel = glade_widget_get_toplevel (widget);
|
|
parent = widget;
|
|
|
|
path = gtk_tree_path_new ();
|
|
|
|
while ((parent = glade_widget_get_parent (widget)) != NULL)
|
|
{
|
|
gint position;
|
|
|
|
if ((position = glade_project_child_position (project, parent,
|
|
glade_widget_get_object (widget))) < 0)
|
|
gtk_tree_path_prepend_index (path, 0);
|
|
else
|
|
gtk_tree_path_prepend_index (path, position);
|
|
|
|
widget = parent;
|
|
}
|
|
|
|
/* Get the index for the top-level list */
|
|
top = g_list_find (project->priv->tree, glade_widget_get_object (toplevel));
|
|
|
|
/* While the project is disposing widgets are unparented and sometimes no longer in the tree */
|
|
if (top)
|
|
gtk_tree_path_prepend_index (path, g_list_position (project->priv->tree, top));
|
|
else
|
|
gtk_tree_path_prepend_index (path, 0);
|
|
|
|
return path;
|
|
}
|
|
|
|
static void
|
|
glade_project_model_get_value (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gint column,
|
|
GValue *value)
|
|
{
|
|
GObject *object;
|
|
GladeWidget *widget;
|
|
GladeProperty *ref_prop;
|
|
gchar *str = NULL, *child_type;
|
|
|
|
g_return_if_fail (VALID_ITER (model, iter));
|
|
|
|
object = iter->user_data;
|
|
widget = glade_widget_get_from_gobject (object);
|
|
|
|
value = g_value_init (value,
|
|
glade_project_model_get_column_type (model, column));
|
|
|
|
switch (column)
|
|
{
|
|
case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME:
|
|
g_object_get (glade_widget_get_adaptor (widget), "icon-name", &str, NULL);
|
|
g_value_take_string (value, str);
|
|
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:
|
|
g_value_set_static_string (value, G_OBJECT_TYPE_NAME (object));
|
|
break;
|
|
case GLADE_PROJECT_MODEL_COLUMN_OBJECT:
|
|
g_value_set_object (value, object);
|
|
break;
|
|
case GLADE_PROJECT_MODEL_COLUMN_MISC:
|
|
/* 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 ((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: reffers 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;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
glade_project_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GObject *object = iter->user_data;
|
|
GladeWidget *widget;
|
|
GladeWidget *parent;
|
|
GList *children;
|
|
GList *child;
|
|
GList *next;
|
|
gboolean retval = FALSE;
|
|
|
|
g_return_val_if_fail (VALID_ITER (project, iter), FALSE);
|
|
|
|
widget = glade_widget_get_from_gobject (object);
|
|
parent = glade_widget_get_parent (widget);
|
|
|
|
if (parent)
|
|
{
|
|
children = glade_widget_get_children (parent);
|
|
}
|
|
else
|
|
{
|
|
children = project->priv->tree;
|
|
}
|
|
|
|
child = g_list_find (children, object);
|
|
if (child)
|
|
{
|
|
/* Get the next child that is actually in the project */
|
|
for (next = child->next; next; next = next->next)
|
|
{
|
|
GObject *object = next->data;
|
|
|
|
if (glade_project_has_object (project, object))
|
|
break;
|
|
}
|
|
|
|
if (next)
|
|
{
|
|
glade_project_model_get_iter_for_object (project, next->data, iter);
|
|
retval = TRUE;
|
|
}
|
|
}
|
|
if (children != project->priv->tree)
|
|
g_list_free (children);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
glade_project_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GladeWidget *widget;
|
|
|
|
g_return_val_if_fail (VALID_ITER (model, iter), FALSE);
|
|
|
|
widget = glade_widget_get_from_gobject (iter->user_data);
|
|
|
|
if (glade_project_count_children (project, widget) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
glade_project_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
|
|
g_return_val_if_fail (iter == NULL || VALID_ITER (project, iter), 0);
|
|
|
|
if (iter)
|
|
{
|
|
GladeWidget *widget = glade_widget_get_from_gobject (iter->user_data);
|
|
|
|
return glade_project_count_children (project, widget);
|
|
}
|
|
|
|
return g_list_length (project->priv->tree);
|
|
}
|
|
|
|
static gboolean
|
|
glade_project_model_iter_nth_child (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent,
|
|
gint nth)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GObject *object = NULL;
|
|
|
|
g_return_val_if_fail (parent == NULL || VALID_ITER (project, parent), FALSE);
|
|
|
|
if (parent != NULL)
|
|
{
|
|
GObject *obj = parent->user_data;
|
|
GladeWidget *widget = glade_widget_get_from_gobject (obj);
|
|
|
|
object = glade_project_nth_child (project, widget, nth);
|
|
}
|
|
else if (project->priv->tree)
|
|
{
|
|
GList *child = g_list_nth (project->priv->tree, nth);
|
|
|
|
if (child)
|
|
object = child->data;
|
|
}
|
|
|
|
if (object)
|
|
{
|
|
glade_project_model_get_iter_for_object (project, object, iter);
|
|
return TRUE;
|
|
}
|
|
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
glade_project_model_iter_children (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GObject *object = NULL;
|
|
|
|
g_return_val_if_fail (parent == NULL || VALID_ITER (project, parent), FALSE);
|
|
|
|
if (parent)
|
|
{
|
|
GladeWidget *widget = glade_widget_get_from_gobject (parent->user_data);
|
|
|
|
object = glade_project_nth_child (project, widget, 0);
|
|
}
|
|
else if (project->priv->tree)
|
|
object = project->priv->tree->data;
|
|
|
|
if (object)
|
|
{
|
|
glade_project_model_get_iter_for_object (project, object, iter);
|
|
return TRUE;
|
|
}
|
|
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
glade_project_model_iter_parent (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *child)
|
|
{
|
|
GladeProject *project = GLADE_PROJECT (model);
|
|
GladeWidget *widget;
|
|
GladeWidget *parent;
|
|
|
|
g_return_val_if_fail (VALID_ITER (project, child), FALSE);
|
|
|
|
widget = glade_widget_get_from_gobject (child->user_data);
|
|
parent = glade_widget_get_parent (widget);
|
|
|
|
if (parent &&
|
|
glade_project_has_object (project, glade_widget_get_object (parent)))
|
|
{
|
|
glade_project_model_get_iter_for_object (project,
|
|
glade_widget_get_object (parent),
|
|
iter);
|
|
return TRUE;
|
|
}
|
|
|
|
iter->stamp = 0;
|
|
iter->user_data = NULL;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_tree_model_iface_init (GtkTreeModelIface *iface)
|
|
{
|
|
iface->get_flags = glade_project_model_get_flags;
|
|
iface->get_column_type = glade_project_model_get_column_type;
|
|
iface->get_n_columns = glade_project_model_get_n_columns;
|
|
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;
|
|
}
|
|
|
|
|
|
/*************************************************
|
|
* Command Central *
|
|
*************************************************/
|
|
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)
|
|
{
|
|
if (GLADE_IS_OBJECT_STUB (list->data))
|
|
has_unknown = TRUE;
|
|
else
|
|
{
|
|
GladeWidget *widget = glade_widget_get_from_gobject (list->data);
|
|
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, _("Unknown widgets ignored."));
|
|
|
|
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)
|
|
{
|
|
if (GLADE_IS_OBJECT_STUB (list->data))
|
|
has_unknown = TRUE;
|
|
else
|
|
{
|
|
GladeWidget *widget = glade_widget_get_from_gobject (list->data);
|
|
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, _("Unknown widgets ignored."));
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Abort operation when adding a non scrollable widget to any kind of GtkScrolledWindow. */
|
|
if (parent && widget &&
|
|
glade_util_check_and_warn_scrollable (parent, glade_widget_get_adaptor (widget),
|
|
glade_app_get_window ()))
|
|
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);
|
|
}
|