Commit 0b6f713f authored by Tristan Van Berkom's avatar Tristan Van Berkom

2010-10-12 Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com>

	Implemented preview feature for Glade.

	* gladeui/Makefile.am, gladeui/glade-previewer.c: Added program to preview
	  glade files.

	* gladeui/glade-project.[ch]: Implemented glade_project_preview() to spawn a
	  child preview process, reap it when it dies and close all previews when
	  project closes.

	* src/glade-window.c: Added toolbar button to spawn the preview.

	* gladeui/glade-xml-utils.[ch]: Added glade_xml_dump_from_context().

	* plugins/gtk+/glade-gtk.c, plugins/gtk+/gtk+.xml.in: Added actions to preview a widget.

	* gladeui/glade-app.[ch]: Added glade_app_get_bin_dir() for resolving of previewer path.
parent 649c798e
2010-10-12 Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com>
Implemented preview feature for Glade.
* gladeui/Makefile.am, gladeui/glade-previewer.c: Added program to preview
glade files.
* gladeui/glade-project.[ch]: Implemented glade_project_preview() to spawn a
child preview process, reap it when it dies and close all previews when
project closes.
* src/glade-window.c: Added toolbar button to spawn the preview.
* gladeui/glade-xml-utils.[ch]: Added glade_xml_dump_from_context().
* plugins/gtk+/glade-gtk.c, plugins/gtk+/gtk+.xml.in: Added actions to preview a widget.
* gladeui/glade-app.[ch]: Added glade_app_get_bin_dir() for resolving of previewer path.
2010-10-11 Tristan Van Berkom <tristanvb@openismus.com>
* gladeui/glade-inspector.c: Fixed project leakage at dispose time.
......
/glade-marshallers.c
/glade-marshallers.h
/gladeui.rc
/glade-previewer
## Previewer
bin_PROGRAMS = glade-previewer
glade_previewer_CPPFLAGS = \
-I$(top_srcdir) \
-I$(top_builddir) \
-DGLADE_GNOMEHELPDIR="\"$(HELP_DIR)\""
$(AM_CPPFLAGS)
glade_previewer_CFLAGS = \
$(GTK_CFLAGS) \
$(IGE_MAC_CFLAGS) \
$(WARN_CFLAGS) \
$(AM_CFLAGS)
glade_previewer_LDFLAGS = $(AM_LDFLAGS)
glade_previewer_LDADD = $(top_builddir)/gladeui/libgladeui-2.la $(IGE_MAC_LIBS)
glade_previewer_SOURCES = \
glade-previewer.c
if NATIVE_WIN32
glade_previewer_LDADD += glade-win32-res.o
if !GLADE_UNSTABLE
glade_previewer_LDFLAGS += -mwindows
endif
endif
glade-win32-res.o: glade-previewer.rc
$(WINDRES) $< $@
## Rest of the UI ;)
common_defines = \
-DG_LOG_DOMAIN=\"GladeUI\" \
-DGLADE_CATALOGSDIR="\"$(pkgdatadir)/catalogs\""\
-DGLADE_MODULESDIR="\"$(pkglibdir)/modules\"" \
-DGLADE_PIXMAPSDIR="\"$(pkgdatadir)/pixmaps\"" \
-DGLADE_LOCALEDIR="\"$(datadir)/locale\""
-DGLADE_LOCALEDIR="\"$(datadir)/locale\""\
-DGLADE_BINDIR="\"$(bindir)\""
lib_LTLIBRARIES = libgladeui-2.la
BUILT_SOURCES = glade-marshallers.c glade-marshallers.h
EXTRA_DIST = glade-marshallers.list gladeui.rc.in icon-naming-spec.c
EXTRA_DIST = glade-marshallers.list gladeui.rc.in icon-naming-spec.c glade-previewer.rc.in
# The glade-3 core library
libgladeui_2_la_SOURCES = \
......
......@@ -103,6 +103,7 @@ static gchar *catalogs_dir = NULL;
static gchar *modules_dir = NULL;
static gchar *pixmaps_dir = NULL;
static gchar *locale_dir = NULL;
static gchar *bin_dir = NULL;
static GladeApp *singleton_app = NULL;
static gboolean check_initialised = FALSE;
......@@ -199,6 +200,7 @@ glade_app_finalize (GObject *app)
g_free (modules_dir);
g_free (pixmaps_dir);
g_free (locale_dir);
g_free (bin_dir);
singleton_app = NULL;
check_initialised = FALSE;
......@@ -354,6 +356,15 @@ glade_app_get_locale_dir (void)
return locale_dir;
}
const gchar *
glade_app_get_bin_dir (void)
{
glade_init_check ();
return bin_dir;
}
/* build package paths at runtime */
static void
build_package_paths (void)
......@@ -374,6 +385,7 @@ build_package_paths (void)
catalogs_dir = g_build_filename (prefix, "share", PACKAGE, "catalogs", NULL);
modules_dir = g_build_filename (prefix, "lib", PACKAGE, "modules", NULL);
locale_dir = g_build_filename (prefix, "share", "locale", NULL);
bin_dir = g_build_filename (prefix, "bin", NULL);
g_free (prefix);
#else
......@@ -381,6 +393,7 @@ build_package_paths (void)
modules_dir = g_strdup (GLADE_MODULESDIR);
pixmaps_dir = g_strdup (GLADE_PIXMAPSDIR);
locale_dir = g_strdup (GLADE_LOCALEDIR);
bin_dir = g_strdup (GLADE_BINDIR);
#endif
}
......
......@@ -208,6 +208,8 @@ const gchar *glade_app_get_pixmaps_dir (void) G_GNUC_CONST;
const gchar *glade_app_get_locale_dir (void) G_GNUC_CONST;
const gchar *glade_app_get_bin_dir (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GLADE_APP_H__ */
......@@ -74,7 +74,8 @@ enum
PROP_HAS_SELECTION,
PROP_PATH,
PROP_READ_ONLY,
PROP_FORMAT
PROP_FORMAT,
PROP_PREVIEWABLE
};
struct _GladeProjectPrivate
......@@ -95,6 +96,8 @@ struct _GladeProjectPrivate
* requested
*/
gboolean previewable;
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 */
......@@ -155,6 +158,9 @@ struct _GladeProjectPrivate
GtkWidget *resource_fullpath_radio;
GtkWidget *relative_path_entry;
GtkWidget *full_path_button;
/* Store preview processes, so we can kill them on close */
GHashTable *preview_channels;
};
typedef struct {
......@@ -167,6 +173,11 @@ typedef struct {
gchar *filename;
} StockFilePair;
typedef struct {
GIOChannel *channel;
guint watch;
} ChannelWatchPair;
static void glade_project_set_target_version (GladeProject *project,
const gchar *catalog,
......@@ -362,6 +373,9 @@ glade_project_get_property (GObject *object,
case PROP_FORMAT:
g_value_set_int (value, project->priv->format);
break;
case PROP_PREVIEWABLE:
g_value_set_boolean (value, project->priv->previewable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
......@@ -569,6 +583,70 @@ glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd)
0, cmd, TRUE);
}
static void
glade_project_preview_exits (GPid pid, gint status, gpointer data)
{
GladeProject *project = (GladeProject *)data;
ChannelWatchPair *channel_watch;
GIOChannel *channel;
gchar *pidstr = g_strdup_printf ("%d", pid);
channel_watch = g_hash_table_lookup (project->priv->preview_channels, pidstr);
channel = channel_watch->channel;
g_io_channel_unref (channel);
g_hash_table_remove (project->priv->preview_channels, pidstr);
g_free (pidstr);
g_free (channel_watch);
}
static void
glade_project_kill_previews (gpointer key,
gpointer value,
gpointer user_data)
{
const gchar *quit = "<quit>";
GIOChannel *channel;
ChannelWatchPair *channel_watch = (ChannelWatchPair *) value;
GError *error = NULL;
gsize size;
channel = channel_watch->channel;
/* Removing watch, since the child will commit suicide */
g_source_remove (channel_watch->watch);
g_io_channel_write_chars (channel, quit, strlen (quit), &size, &error);
if (size != strlen (quit) && error != NULL)
{
g_printerr ("Error passing quit signal trough pipe: %s", error->message);
g_error_free (error);
}
g_io_channel_flush (channel, &error);
if (error != NULL)
{
g_printerr ("Error flushing channel: %s", error->message);
g_error_free (error);
}
g_io_channel_shutdown (channel, TRUE, &error);
if (error != NULL)
{
g_printerr ("Error shutting down channel: %s", error->message);
g_error_free (error);
}
g_io_channel_unref (channel);
g_free (channel_watch);
}
static void
glade_project_close_impl (GladeProject *project)
{
g_hash_table_foreach (project->priv->preview_channels, glade_project_kill_previews, project);
g_hash_table_unref (project->priv->preview_channels);
}
static void
glade_project_changed_impl (GladeProject *project,
GladeCommand *command,
......@@ -612,6 +690,8 @@ glade_project_init (GladeProject *project)
priv->first_modification = NULL;
priv->first_modification_is_na = FALSE;
priv->preview_channels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->previewable = FALSE;
priv->toplevel_names = glade_name_context_new ();
priv->naming_policy = GLADE_POLICY_PROJECT_WIDE;
......@@ -672,9 +752,9 @@ glade_project_class_init (GladeProjectClass *klass)
klass->widget_name_changed = NULL;
klass->selection_changed = NULL;
klass->close = NULL;
klass->close = glade_project_close_impl;
klass->changed = glade_project_changed_impl;
/**
* GladeProject::add-widget:
* @gladeproject: the #GladeProject which received the signal.
......@@ -879,6 +959,16 @@ glade_project_class_init (GladeProjectClass *klass)
GLADE_PROJECT_FORMAT_GTKBUILDER,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_PREVIEWABLE,
g_param_spec_boolean ("previewable",
_("Previewable"),
_("Wether the project can be previewed"),
FALSE,
G_PARAM_READABLE));
g_type_class_add_private (klass, sizeof (GladeProjectPrivate));
}
......@@ -1721,6 +1811,118 @@ glade_project_save (GladeProject *project, const gchar *path, GError **error)
return ret > 0;
}
static GPid
glade_project_launch_preview (GladeProject *project, gchar *buffer, GtkWidget *widget)
{
GPid pid;
GError *error = NULL;
gchar *argv[4];
gint child_stdin;
GIOChannel *output;
guint watch;
ChannelWatchPair *channel_watch;
GladeWidget *glade_widget;
#ifdef WINDOWS
argv[0] = g_build_filename (glade_app_get_bin_dir(), "glade-previewer.exe", NULL);
#else
argv[0] = g_build_filename (glade_app_get_bin_dir(), "glade-previewer", NULL);
#endif
argv[1] = "--listen";
if (widget != NULL)
{
glade_widget = glade_widget_get_from_gobject (G_OBJECT (widget));
argv[2] = g_strdup_printf ("--toplevel=%s", glade_widget->name);
argv[3] = NULL;
}
if (g_spawn_async_with_pipes (NULL,
argv,
NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
&pid, &child_stdin, NULL, NULL, &error) == FALSE)
{
g_printerr (_("Error launching previewer: %s\n"), error->message);
glade_util_ui_message (glade_app_get_window(),
GLADE_UI_ERROR, NULL,
_("Failed to launch preview: %s.\n"),
error->message);
g_error_free (error);
pid = 0;
goto end;
}
/* Store watch so we can remove it later */
watch = g_child_watch_add (pid, glade_project_preview_exits, project);
#ifdef WINDOWS
output = g_io_channel_win32_new_fd (child_stdin);
#else
output = g_io_channel_unix_new (child_stdin);
#endif
gsize bytes_written;
g_io_channel_write_chars (output, buffer, strlen (buffer), &bytes_written, &error);
if (bytes_written != strlen (buffer) && error != NULL)
{
g_printerr ("Error passing UI trough pipe: %s", error->message);
g_error_free (error);
}
g_io_channel_flush (output, &error);
if (error != NULL)
{
g_printerr ("Error flushing UI trough pipe: %s", error->message);
g_error_free (error);
}
if (widget != NULL) g_free (argv[2]);
/* Adding channel to list of channels */
channel_watch = g_new (ChannelWatchPair, 1);
channel_watch->channel = output;
channel_watch->watch = watch;
g_hash_table_insert (project->priv->preview_channels, g_strdup_printf("%d", pid),
channel_watch);
end:
g_free (argv[0]);
return pid;
}
/**
* 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;
GtkWidget *widget = GTK_WIDGET (gwidget->object);
g_return_if_fail (GTK_WIDGET (widget));
g_return_if_fail (GLADE_IS_PROJECT (project));
context = glade_project_write (project);
text = glade_xml_dump_from_context (context);
glade_project_launch_preview (project, text, widget);
g_free(text);
}
/*******************************************************************
Verify code here (versioning, incompatability checks)
*******************************************************************/
......@@ -2648,6 +2850,34 @@ sort_project_dependancies (GObject *a, GObject *b)
return 1;
}
static gboolean
glade_project_has_widget (GladeProject *project)
{
GtkWidget *widget = NULL;
const GList *objects;
objects = glade_project_get_objects (project);
while (objects != NULL)
{
if (GTK_IS_WIDGET (objects->data))
{
widget = GTK_WIDGET(objects->data);
break;
}
objects = objects->next;
}
return widget != NULL;
}
static void
glade_project_update_previewable (GladeProject *project)
{
project->priv->previewable = glade_project_has_widget (project);
g_object_notify (G_OBJECT (project), "previewable");
}
/**
* glade_project_add_object:
* @project: the #GladeProject the widget is added to
......@@ -2742,8 +2972,9 @@ glade_project_add_object (GladeProject *project,
g_list_free (children);
}
/* Update user visible compatability info */
/* Update user visible compatibility info */
glade_project_verify_properties (gwidget);
glade_project_update_previewable (project);
g_signal_emit (G_OBJECT (project),
glade_project_signals [ADD_WIDGET],
......@@ -2833,6 +3064,8 @@ glade_project_remove_object (GladeProject *project, GObject *object)
gwidget);
gtk_tree_iter_free (iter);
}
glade_project_update_previewable (project);
}
static void
......@@ -3609,6 +3842,12 @@ glade_project_set_naming_policy (GladeProject *project,
}
gboolean
glade_project_get_previewable (GladeProject *project)
{
return project->priv->previewable;
}
GladeNamingPolicy
glade_project_get_naming_policy (GladeProject *project)
{
......
......@@ -95,8 +95,10 @@ GladeProject *glade_project_load (const gchar *path);
gboolean glade_project_save (GladeProject *project,
const gchar *path,
GError **error);
const gchar *glade_project_get_path (GladeProject *project);
void glade_project_preview (GladeProject *project, GladeWidget *gwidget);
const gchar *glade_project_get_path (GladeProject *project);
gchar *glade_project_get_name (GladeProject *project);
......@@ -186,6 +188,8 @@ void glade_project_set_instance_count (GladeProject *project, guint
gboolean glade_project_get_modified (GladeProject *project);
gboolean glade_project_get_previewable (GladeProject *project);
void glade_project_set_format (GladeProject *project, GladeProjectFormat format);
GladeProjectFormat glade_project_get_format (GladeProject *project);
......
......@@ -758,6 +758,22 @@ glade_xml_context_get_doc (GladeXmlContext *context)
return context->doc;
}
gchar *
glade_xml_dump_from_context (GladeXmlContext *context)
{
GladeXmlDoc *doc;
xmlChar *string = NULL;
gchar *text;
int size;
doc = glade_xml_context_get_doc (context);
xmlDocDumpFormatMemory(&(doc->doc), &string, &size, 1);
text = claim_string (string);
return text;
}
gboolean
glade_xml_node_is_comment (GladeXmlNode *node_in)
{
......
......@@ -281,6 +281,9 @@ GladeXmlContext * glade_xml_context_new_from_path (const gchar *full_path,
const gchar *root_name);
GladeXmlDoc * glade_xml_context_get_doc (GladeXmlContext *context);
/* Dumps an xml string from a context */
gchar * glade_xml_dump_from_context (GladeXmlContext *context);
gboolean glade_xml_load_sym_from_node (GladeXmlNode *node_in,
GModule *module,
gchar *tagname,
......
......@@ -1029,13 +1029,21 @@ glade_gtk_widget_action_activate (GladeWidgetAdaptor *adaptor,
GladeWidget *gwidget = glade_widget_get_from_gobject (object), *gparent;
GList this_widget = { 0, }, that_widget = { 0, };
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (object));
GladeProject *project;
if (parent)
gparent = glade_widget_get_from_gobject (parent);
else
gparent = NULL;
if (strcmp (action_path, "edit_separate") == 0)
if (strcmp (action_path, "preview") == 0)
{
project = glade_widget_get_project (gwidget);
glade_project_preview (project,
glade_widget_get_from_gobject((gpointer)object)
);
}
else if (strcmp (action_path, "edit_separate") == 0)
{
GtkWidget *dialog =
glade_editor_dialog_for_widget (gwidget);
......
......@@ -30,6 +30,7 @@
</signals>
<actions>
<action id="preview" _name="Preview snapshot"/>
<action id="edit_separate" _name="Edit Separately" stock="gtk-edit"/>
<action id="remove_parent" _name="Remove Parent" stock="gtk-remove"/>
<action id="add_parent" _name="Add Parent" stock="gtk-add">
......
......@@ -33,6 +33,8 @@
#include <gladeui/glade-popup.h>
#include <gladeui/glade-inspector.h>
#include <gladeui/glade-project.h>
#include <string.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
......@@ -117,6 +119,7 @@ struct _GladeWindowPrivate
gchar *default_path; /* the default path for open/save operations */
GtkToggleToolButton *selector_button; /* the widget selector button (replaces the one in the palette) */
GtkToolButton *preview_button; /* the project preview button (replaces the one in the palette) */
GtkToggleToolButton *drag_resize_button; /* sets the pointer to drag/resize mode */
gboolean setting_pointer_mode; /* avoid feedback signal loops */
......@@ -781,6 +784,50 @@ on_selector_button_toggled (GtkToggleToolButton *button, GladeWindow *window)
gtk_toggle_tool_button_set_active (window->priv->selector_button, TRUE);
}
static void
on_preview_button_clicked (GtkToggleToolButton *button, GladeWindow *window)
{
GladeProject *project;
const GList *objects;
GtkWidget *widget = NULL;
GtkWidget *window_to_preview = NULL;
GladeWidget *glade_widget = NULL;
project = glade_design_view_get_project (window->priv->active_view);
if (project == NULL)
return;
objects = glade_project_get_objects (project);
while (objects != NULL)
{
if (GTK_IS_WIDGET (objects->data))
{
widget = GTK_WIDGET(objects->data);
if (GTK_IS_WINDOW (widget))
{
window_to_preview = widget;
break;
}
}
objects = objects->next;
}
if (widget != NULL)
{
glade_widget = glade_widget_get_from_gobject (G_OBJECT (widget));
}
if (window_to_preview != NULL) widget = window_to_preview;
glade_project_preview (project,
glade_widget_get_from_gobject((gpointer)widget)
);
}
static void
on_drag_resize_button_toggled (GtkToggleToolButton *button, GladeWindow *window)
{
......@@ -2354,6 +2401,24 @@ create_selector_tool_button (GtkToolbar *toolbar)
return GTK_WIDGET (button);
}
static GtkWidget *
create_preview_tool_button (GtkToolbar *toolbar)
{
GtkToolItem *button;
button = gtk_tool_button_new_from_stock (GTK_STOCK_EXECUTE);
gtk_tool_button_set_label (GTK_TOOL_BUTTON(button), _("Preview snapshot"));
gtk_tool_item_set_tooltip (GTK_TOOL_ITEM (button),
toolbar->tooltips,
_("Previews snapshot of project"),
NULL);
gtk_widget_show (GTK_WIDGET (button));
return GTK_WIDGET (button);
}
static GtkWidget *
create_drag_resize_tool_button (GtkToolbar *toolbar)
{
......@@ -2466,6 +2531,10 @@ add_project (GladeWindow *window, GladeProject *project)
/* Kick the inspector in the balls here... */
glade_project_selection_changed (project);
/* Update preview button */
gtk_widget_set_sensitive ( GTK_WIDGET (window->priv->preview_button),
glade_project_get_previewable (project));
}
void
......@@ -2704,9 +2773,15 @@ refresh_undo_redo (GladeWindow *window)
static void
update_ui (GladeApp *app, GladeWindow *window)
{
{
GladeProject *project;
if (window->priv->active_view)
{
project = glade_design_view_get_project (window->priv->active_view);
gtk_widget_set_sensitive ( GTK_WIDGET (window->priv->preview_button),
glade_project_get_previewable (project));
gtk_widget_queue_draw (GTK_WIDGET (window->priv->active_view));
}
refresh_undo_redo (window);
......@@ -3267,10 +3342,16 @@ glade_window_init (GladeWindow *window)
gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), GTK_TOOL_ITEM (sep), -1);
priv->selector_button =
GTK_TOGGLE_TOOL_BUTTON (create_selector_tool_button (GTK_TOOLBAR (priv->toolbar)));
GTK_TOGGLE_TOOL_BUTTON (create_selector_tool_button (GTK_TOOLBAR (priv->toolbar)));
gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar),
GTK_TOOL_ITEM (priv->selector_button), -1);
priv->preview_button =
GTK_TOOL_BUTTON (create_preview_tool_button (GTK_TOOLBAR (priv->toolbar)));
gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar),
GTK_TOOL_ITEM (priv->preview_button), -1);
gtk_widget_set_sensitive (GTK_WIDGET (priv->preview_button), FALSE);
priv->drag_resize_button =
GTK_TOGGLE_TOOL_BUTTON (create_drag_resize_tool_button
(GTK_TOOLBAR (priv->toolbar)));
......@@ -3282,6 +3363,8 @@ glade_window_init (GladeWindow *window)
g_signal_connect (G_OBJECT (priv->selector_button), "toggled",
G_CALLBACK (on_selector_button_toggled), window);
g_signal_connect (G_OBJECT (priv->preview_button), "clicked",
G_CALLBACK (on_preview_button_clicked), window);
g_signal_connect (G_OBJECT (priv->drag_resize_button), "toggled",
G_CALLBACK (on_drag_resize_button_toggled), window);
g_signal_connect (G_OBJECT (glade_app_get()), "notify::pointer-mode",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment