diff options
author | Juan Pablo Ugarte <juanpablougarte@gmail.com> | 2016-04-08 22:25:57 -0300 |
---|---|---|
committer | Juan Pablo Ugarte <juanpablougarte@gmail.com> | 2016-04-08 22:57:39 -0300 |
commit | 695ab6282a25ab554ae692d4d6e4f409d17411c5 (patch) | |
tree | 0e02138a6ccfa4d1ae81ea46ec61f6930342d260 /gladeui | |
parent | b540ffcc55e3d722ae01ff57667e66e52fd625ef (diff) | |
download | glade-695ab6282a25ab554ae692d4d6e4f409d17411c5.tar.gz |
glade-previewer: adapted GladePreviewWindow as GladePreviewer to not longer pack toplevels inside a window
This avoids bug #761651 "[Wayland] glade previewer is resizing windows until it crashes gnome-shell"
Diffstat (limited to 'gladeui')
-rw-r--r-- | gladeui/Makefile.am | 4 | ||||
-rw-r--r-- | gladeui/glade-preview-window.c | 794 | ||||
-rw-r--r-- | gladeui/glade-preview-window.h | 92 | ||||
-rw-r--r-- | gladeui/glade-previewer-main.c | 447 | ||||
-rw-r--r-- | gladeui/glade-previewer.c | 1140 | ||||
-rw-r--r-- | gladeui/glade-previewer.h | 97 |
6 files changed, 1267 insertions, 1307 deletions
diff --git a/gladeui/Makefile.am b/gladeui/Makefile.am index ab256c21..7510084e 100644 --- a/gladeui/Makefile.am +++ b/gladeui/Makefile.am @@ -22,8 +22,8 @@ glade_previewer_LDFLAGS = $(AM_LDFLAGS) glade_previewer_LDADD = libgladeui-2.la $(GTK_MAC_LIBS) glade_previewer_SOURCES = \ + glade-previewer-main.c \ glade-previewer.c \ - glade-preview-window.c \ glade-preview-template.c if NATIVE_WIN32 @@ -197,7 +197,7 @@ noinst_HEADERS = \ glade-popup.h \ glade-preview.h \ glade-preview-tokens.h \ - glade-preview-window.h \ + glade-previewer.h \ glade-preview-template.h \ glade-private.h \ glade-project-properties.h \ diff --git a/gladeui/glade-preview-window.c b/gladeui/glade-preview-window.c deleted file mode 100644 index dc350ed3..00000000 --- a/gladeui/glade-preview-window.c +++ /dev/null @@ -1,794 +0,0 @@ -/* - * glade-preview-window.c - * - * Copyright (C) 2013-2014 Juan Pablo Ugarte - * - * Author: Juan Pablo Ugarte <juanpablougarte@gmail.com> - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser 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. - * - */ -#include <config.h> - -#include "glade-preview-window.h" -#include <glib/gi18n-lib.h> -#include <glib/gprintf.h> -#include <cairo-pdf.h> -#include <cairo-svg.h> -#include <cairo-ps.h> - -struct _GladePreviewWindowPrivate -{ - GtkWidget *box; - GtkWidget *info; - GtkWidget *message_label; - GtkWidget *widget; - - GtkCssProvider *css_provider; - GFileMonitor *css_monitor; - gchar *css_file; - gchar *extension; - gboolean print_handlers; -}; - -G_DEFINE_TYPE_WITH_PRIVATE (GladePreviewWindow, glade_preview_window, GTK_TYPE_WINDOW); - -static void -glade_preview_window_init (GladePreviewWindow *window) -{ - GladePreviewWindowPrivate *priv = glade_preview_window_get_instance_private (window); - GtkWidget *content_area; - - window->priv = priv; - - gtk_window_set_title (GTK_WINDOW (window), _("Preview")); - gtk_widget_add_events (GTK_WIDGET (window), GDK_KEY_PRESS_MASK); - - priv->box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - priv->info = gtk_info_bar_new (); - priv->message_label = gtk_label_new (""); - gtk_label_set_line_wrap (GTK_LABEL (priv->message_label), TRUE); - gtk_label_set_selectable (GTK_LABEL (priv->message_label), TRUE); - - gtk_widget_set_valign (priv->info, GTK_ALIGN_END); - gtk_widget_set_vexpand (priv->info, FALSE); - content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->info)); - gtk_container_add (GTK_CONTAINER (content_area), priv->message_label); - - gtk_info_bar_set_show_close_button (GTK_INFO_BAR (priv->info), TRUE); - - g_signal_connect (priv->info, "response", G_CALLBACK (gtk_widget_hide), NULL); - - gtk_box_pack_start (GTK_BOX (priv->box), priv->info, FALSE, FALSE, 0); - gtk_container_add (GTK_CONTAINER (window), priv->box); - - gtk_widget_show (priv->message_label); - gtk_widget_show (priv->box); -} - -static void -glade_preview_window_dispose (GObject *object) -{ - GladePreviewWindowPrivate *priv = GLADE_PREVIEW_WINDOW (object)->priv; - - priv->info = NULL; - g_clear_object (&priv->css_provider); - g_clear_object (&priv->css_monitor); - - G_OBJECT_CLASS (glade_preview_window_parent_class)->dispose (object); -} - -static void -glade_preview_window_finalize (GObject *object) -{ - GladePreviewWindowPrivate *priv = GLADE_PREVIEW_WINDOW (object)->priv; - - g_free (priv->css_file); - g_free (priv->extension); - - G_OBJECT_CLASS (glade_preview_window_parent_class)->finalize (object); -} - -static gboolean -glade_preview_window_key_press_event (GtkWidget *widget, GdkEventKey *event) -{ - GladePreviewWindow *window = GLADE_PREVIEW_WINDOW (widget); - GladePreviewWindowPrivate *priv = window->priv; - gchar *extension; - - switch (event->keyval) - { - case GDK_KEY_F5: - extension = "svg"; - break; - case GDK_KEY_F6: - extension = "ps"; - break; - case GDK_KEY_F7: - extension = "pdf"; - break; - case GDK_KEY_F8: - extension = priv->extension ? priv->extension : "png"; - break; - case GDK_KEY_F11: - if (gdk_window_get_state (gtk_widget_get_window (widget)) & GDK_WINDOW_STATE_FULLSCREEN) - gtk_window_unfullscreen (GTK_WINDOW (widget)); - else - gtk_window_fullscreen (GTK_WINDOW (widget)); - - return TRUE; - break; - default: - return FALSE; - break; - } - - if (extension) - { - gchar *tmp_file = g_strdup_printf ("glade-screenshot-XXXXXX.%s", extension); - - g_mkstemp (tmp_file); - glade_preview_window_screenshot (window, FALSE, tmp_file); - g_free (tmp_file); - - return TRUE; - } - - return FALSE; -} - -static void -glade_preview_window_realize (GtkWidget *widget) -{ - GladePreviewWindowPrivate *priv = GLADE_PREVIEW_WINDOW (widget)->priv; - - GTK_WIDGET_CLASS (glade_preview_window_parent_class)->realize (widget); - - if (priv->widget && gtk_widget_is_toplevel (priv->widget) && - gtk_widget_get_parent (priv->widget) == NULL) - { - gtk_widget_set_parent_window (priv->widget, gtk_widget_get_window (widget)); - gtk_box_pack_start (GTK_BOX (priv->box), priv->widget, TRUE, TRUE, 0); - gtk_widget_show (priv->widget); - } -} - -static void -glade_preview_window_class_init (GladePreviewWindowClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = glade_preview_window_dispose; - object_class->finalize = glade_preview_window_finalize; - - widget_class->realize = glade_preview_window_realize; - widget_class->key_press_event = glade_preview_window_key_press_event; -} - -GtkWidget * -glade_preview_window_new (void) -{ - return GTK_WIDGET (g_object_new (GLADE_TYPE_PREVIEW_WINDOW, NULL)); -} - -static void -glade_preview_window_set_css_provider_forall (GtkWidget *widget, gpointer data) -{ - gtk_style_context_add_provider (gtk_widget_get_style_context (widget), - GTK_STYLE_PROVIDER (data), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - if (GTK_IS_CONTAINER (widget)) - gtk_container_forall (GTK_CONTAINER (widget), glade_preview_window_set_css_provider_forall, data); -} - -void -glade_preview_window_set_widget (GladePreviewWindow *window, GtkWidget *widget) -{ - GladePreviewWindowPrivate *priv; - GdkWindow *gdkwindow; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - g_return_if_fail (GTK_IS_WIDGET (widget)); - - priv = window->priv; - - if (priv->widget) - gtk_container_remove (GTK_CONTAINER (priv->box), priv->widget); - - priv->widget = widget; - - if (priv->css_provider) - glade_preview_window_set_css_provider_forall (widget, priv->css_provider); - - if (gtk_widget_is_toplevel (widget)) - { - /* Delay setting it until we have a window */ - if (!(gdkwindow = gtk_widget_get_window (priv->box))) - return; - - gtk_widget_set_parent_window (widget, gdkwindow); - } - - gtk_box_pack_start (GTK_BOX (priv->box), widget, TRUE, TRUE, 0); -} - -void -glade_preview_window_set_message (GladePreviewWindow *window, - GtkMessageType type, - const gchar *message) -{ - GladePreviewWindowPrivate *priv; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - priv = window->priv; - - if (!priv->info) - return; - - gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->info), type); - - if (message) - { - gtk_label_set_text (GTK_LABEL (priv->message_label), message); - gtk_widget_show (priv->info); - } - else - { - gtk_label_set_text (GTK_LABEL (priv->message_label), ""); - gtk_widget_hide (priv->info); - } -} - -static void -on_css_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - GladePreviewWindow *window) -{ - GladePreviewWindowPrivate *priv = window->priv; - GError *error = NULL; - - gtk_css_provider_load_from_file (priv->css_provider, file, &error); - - if (error) - { - glade_preview_window_set_message (window, GTK_MESSAGE_WARNING, error->message); - g_error_free (error); - } - else - glade_preview_window_set_message (window, GTK_MESSAGE_OTHER, NULL); -} - -void -glade_preview_window_set_css_file (GladePreviewWindow *window, - const gchar *css_file) -{ - GladePreviewWindowPrivate *priv; - GError *error = NULL; - GFile *file; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - priv = window->priv; - - g_free (priv->css_file); - g_clear_object (&priv->css_provider); - g_clear_object (&priv->css_monitor); - - priv->css_file = g_strdup (css_file); - - file = g_file_new_for_path (css_file); - priv->css_provider = gtk_css_provider_new (); - g_object_ref_sink (priv->css_provider); - - priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); - if (error) - { - g_warning ("Cant monitor CSS file %s: %s", css_file, error->message); - g_error_free (error); - } - else - { - g_object_ref_sink (priv->css_monitor); - g_signal_connect (priv->css_monitor, "changed", - G_CALLBACK (on_css_monitor_changed), window); - } - - /* load CSS */ - gtk_css_provider_load_from_file (priv->css_provider, file, &error); - if (error) - { - glade_preview_window_set_message (window, GTK_MESSAGE_INFO, error->message); - g_message ("%s CSS parsing failed: %s", css_file, error->message); - g_error_free (error); - } - - if (priv->widget) - glade_preview_window_set_css_provider_forall (priv->widget, priv->css_provider); - - g_object_unref (file); -} - -void -glade_preview_window_set_screenshot_extension (GladePreviewWindow *window, - const gchar *extension) -{ - GladePreviewWindowPrivate *priv; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - priv = window->priv; - - g_free (priv->extension); - priv->extension = g_strdup (extension); -} - -static gboolean -quit_when_idle (gpointer loop) -{ - g_main_loop_quit (loop); - - return G_SOURCE_REMOVE; -} - -static void -check_for_draw (GdkEvent *event, gpointer loop) -{ - if (event->type == GDK_EXPOSE) - { - g_idle_add (quit_when_idle, loop); - gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL); - } - - gtk_main_do_event (event); -} - -/* Taken from Gtk sources gtk-reftest.c */ -static void -glade_preview_wait_for_drawing (GdkWindow *window) -{ - GMainLoop *loop; - - loop = g_main_loop_new (NULL, FALSE); - /* We wait until the widget is drawn for the first time. - * We can not wait for a GtkWidget::draw event, because that might not - * happen if the window is fully obscured by windowed child widgets. - * Alternatively, we could wait for an expose event on widget's window. - * Both of these are rather hairy, not sure what's best. */ - gdk_event_handler_set (check_for_draw, loop, NULL); - g_main_loop_run (loop); - - /* give the WM/server some time to sync. They need it. - * Also, do use popups instead of toplevls in your tests - * whenever you can. */ - gdk_display_sync (gdk_window_get_display (window)); - g_timeout_add (500, quit_when_idle, loop); - g_main_loop_run (loop); -} - -static const gchar * -glade_preview_get_extension (const gchar *filename) -{ - gchar *extension; - - g_return_val_if_fail (filename != NULL, NULL); - - extension = g_strrstr (filename,"."); - - if (extension) - extension++; - - if (!extension) - { - g_warning ("%s has no extension!", filename); - return NULL; - } - return extension; -} - -static void -glade_preview_get_scale (GdkScreen *screen, gdouble *sx, gdouble *sy) -{ - if (sx) - *sx = 72.0 / (gdk_screen_get_width (screen) / (gdk_screen_get_width_mm (screen) * 0.03937008)); - - if (sy) - *sy = 72.0 / (gdk_screen_get_height (screen) / (gdk_screen_get_height_mm (screen) * 0.03937008)); -} - -static cairo_surface_t * -glade_preview_surface_from_file (const gchar *filename, gdouble w, gdouble h) -{ - cairo_surface_t *surface; - const gchar *extension; - - extension = glade_preview_get_extension (filename); - - if (extension == NULL) - return NULL; - - if (g_strcmp0 (extension, "svg") == 0) -#if CAIRO_HAS_SVG_SURFACE - surface = cairo_svg_surface_create (filename, w, h); -#else - g_warning ("PDF not supported by the cairo version used"); -#endif - else if (g_strcmp0 (extension, "ps") == 0) -#if CAIRO_HAS_PS_SURFACE - surface = cairo_ps_surface_create (filename, w, h); -#else - g_warning ("PS not supported by the cairo version used"); -#endif - else if (g_strcmp0 (extension, "pdf") == 0) -#if CAIRO_HAS_PDF_SURFACE - surface = cairo_pdf_surface_create (filename, w, h); -#else - g_warning ("PDF not supported by the cairo version used"); -#endif - else - return NULL; - - return surface; -} - -/** - * glade_preview_window_screenshot: - * @window: A GladePreviewWindow - * @wait: True if it should wait for widget to draw. - * @filename: a filename to save the image. - * - * Takes a screenshot of the current widget @window is showing and save it to @filename - * Supported extension are svg, ps, pdf and wahtever gdk-pixbuf supports - */ -void -glade_preview_window_screenshot (GladePreviewWindow *window, - gboolean wait, - const gchar *filename) -{ - GladePreviewWindowPrivate *priv; - cairo_surface_t *surface; - GdkWindow *gdkwindow; - GdkScreen *screen; - gdouble sx, sy; - gint w, h; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - g_return_if_fail (filename != NULL); - priv = window->priv; - - if (!priv->widget) - return; - - gdkwindow = gtk_widget_get_window (priv->widget); - screen = gdk_window_get_screen (gdkwindow); - - if (wait) - glade_preview_wait_for_drawing (gdkwindow); - - w = gtk_widget_get_allocated_width (priv->widget); - h = gtk_widget_get_allocated_height (priv->widget); - glade_preview_get_scale (screen, &sx, &sy); - - surface = glade_preview_surface_from_file (filename, w*sx, h*sy); - - if (surface) - { - cairo_t *cr = cairo_create (surface); - cairo_scale (cr, sx, sy); - gtk_widget_draw (priv->widget, cr); - cairo_destroy (cr); - cairo_surface_destroy(surface); - } - else - { - GdkPixbuf *pix = gdk_pixbuf_get_from_window (gdkwindow, 0, 0, w, h); - const gchar *ext = glade_preview_get_extension (filename); - GError *error = NULL; - - if (!gdk_pixbuf_save (pix, filename, ext ? ext : "png", &error, NULL)) - { - g_warning ("Could not save screenshot to %s because %s", filename, error->message); - g_error_free (error); - } - - g_object_unref (pix); - } -} - -/** - * glade_preview_window_slideshow_save: - * @window: A GladePreviewWindow - * @filename: a filename to save the slideshow. - * - * Takes a screenshot of every widget GtkStack children and save it to @filename - * each in a different page - */ -void -glade_preview_window_slideshow_save (GladePreviewWindow *window, - const gchar *filename) -{ - GladePreviewWindowPrivate *priv; - cairo_surface_t *surface; - GdkWindow *gdkwindow; - GtkStack *stack; - gdouble sx, sy; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - g_return_if_fail (filename != NULL); - priv = window->priv; - - g_return_if_fail (priv->widget); - g_return_if_fail (GTK_IS_STACK (priv->widget)); - stack = GTK_STACK (priv->widget); - - gdkwindow = gtk_widget_get_window (priv->widget); - glade_preview_wait_for_drawing (gdkwindow); - - glade_preview_get_scale (gtk_widget_get_screen (GTK_WIDGET (window)), &sx, &sy); - surface = glade_preview_surface_from_file (filename, - gtk_widget_get_allocated_width (GTK_WIDGET (stack))*sx, - gtk_widget_get_allocated_height (GTK_WIDGET (stack))*sy); - - if (surface) - { - GList *l, *children = gtk_container_get_children (GTK_CONTAINER (stack)); - cairo_t *cr= cairo_create (surface); - - cairo_scale (cr, sx, sy); - - for (l = children; l; l = g_list_next (l)) - { - GtkWidget *child = l->data; - gtk_stack_set_visible_child (stack, child); - glade_preview_wait_for_drawing (gdkwindow); - gtk_widget_draw (child, cr); - cairo_show_page (cr); - } - - if (children) - gtk_stack_set_visible_child (stack, children->data); - - g_list_free (children); - cairo_destroy (cr); - cairo_surface_destroy(surface); - } - else - g_warning ("Could not save slideshow to %s", filename); -} - -/** - * glade_preview_window_set_print_handlers: - * @window: A GladePreviewWindow - * @print: whether to print handlers or not - * - * Set whether to print handlers when they are activated or not. - * It only works if you use glade_preview_window_connect_function() as the - * connect funtion. - */ -void -glade_preview_window_set_print_handlers (GladePreviewWindow *window, - gboolean print) -{ - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - window->priv->print_handlers = print; -} - -typedef struct -{ - gchar *handler_name; - GObject *connect_object; - GConnectFlags flags; -} HandlerData; - -typedef struct -{ - GladePreviewWindow *window; - gint n_invocations; - - GSignalQuery query; - GObject *object; - GList *handlers; -} SignalData; - -static void -handler_data_free (gpointer udata) -{ - HandlerData *hd = udata; - g_clear_object (&hd->connect_object); - g_free (hd->handler_name); - g_free (hd); -} - -static void -signal_data_free (gpointer udata, GClosure *closure) -{ - SignalData *data = udata; - - g_list_free_full (data->handlers, handler_data_free); - data->handlers = NULL; - - g_clear_object (&data->window); - g_clear_object (&data->object); - - g_free (data); -} - -static inline const gchar * -object_get_name (GObject *object) -{ - if (GTK_IS_BUILDABLE (object)) - return gtk_buildable_get_name (GTK_BUILDABLE (object)); - else - return g_object_get_data (object, "gtk-builder-name"); -} - -static void -glade_handler_append (GString *message, - GSignalQuery *query, - const gchar *object, - GList *handlers, - gboolean after) -{ - GList *l; - - for (l = handlers; l; l = g_list_next (l)) - { - HandlerData *hd = l->data; - gboolean handler_after = (hd->flags & G_CONNECT_AFTER); - gboolean swapped = (hd->flags & G_CONNECT_SWAPPED); - GObject *obj = hd->connect_object; - gint i; - - if ((after && !handler_after) || (!after && handler_after)) - continue; - - g_string_append_printf (message, "\n\t-> %s%s %s (%s%s%s", - g_type_name (query->return_type), - g_type_is_a (query->return_type, G_TYPE_OBJECT) ? " *" : "", - hd->handler_name, - (swapped) ? ((obj) ? G_OBJECT_TYPE_NAME (obj) : "") : g_type_name (query->itype), - (swapped) ? ((obj) ? " *" : "") : " *", - (swapped) ? ((obj) ? object_get_name (obj) : _("user_data")) : object); - - for (i = 1; i < query->n_params; i++) - g_string_append_printf (message, ", %s%s", - g_type_name (query->param_types[i]), - g_type_is_a (query->param_types[i], G_TYPE_OBJECT) ? " *" : ""); - - g_string_append_printf (message, ", %s%s%s); ", - (swapped) ? g_type_name (query->itype) : ((obj) ? G_OBJECT_TYPE_NAME (obj) : ""), - (swapped) ? " *" : ((obj) ? " *" : ""), - (swapped) ? object : ((obj) ? object_get_name (obj) : _("user_data"))); - - if (swapped && after) - /* translators: GConnectFlags values */ - g_string_append (message, _("Swapped | After")); - else if (swapped) - /* translators: GConnectFlags value */ - g_string_append (message, _("Swapped")); - else if (after) - /* translators: GConnectFlags value */ - g_string_append (message, _("After")); - } -} - -static inline void -glade_handler_method_append (GString *msg, GSignalQuery *q, const gchar *flags) -{ - g_string_append_printf (msg, "\n\t%sClass->%s(); %s", g_type_name (q->itype), - q->signal_name, flags); -} - -static void -on_handler_called (SignalData *data) -{ - GSignalQuery *query = &data->query; - GObject *object = data->object; - const gchar *object_name = object_get_name (object); - GString *message = g_string_new (""); - - data->n_invocations++; - - if (data->n_invocations == 1) - /* translators: this will be shown in glade previewer when a signal %s::%s is emited one time */ - g_string_append_printf (message, _("%s::%s emitted one time"), - G_OBJECT_TYPE_NAME (object), query->signal_name); - else - /* translators: this will be shown in glade previewer when a signal %s::%s is emited %d times */ - g_string_append_printf (message, _("%s::%s emitted %d times"), - G_OBJECT_TYPE_NAME (object), query->signal_name, - data->n_invocations); - - if (query->signal_flags & G_SIGNAL_RUN_FIRST) - glade_handler_method_append (message, query, _("Run First")); - - glade_handler_append (message, query, object_name, data->handlers, FALSE); - - if (query->signal_flags & G_SIGNAL_RUN_LAST) - glade_handler_method_append (message, query, _("Run Last")); - - glade_handler_append (message, query, object_name, data->handlers, TRUE); - - if (query->signal_flags & G_SIGNAL_RUN_CLEANUP) - glade_handler_method_append (message, query, _("Run Cleanup")); - - glade_preview_window_set_message (data->window, GTK_MESSAGE_INFO, message->str); - - if (data->window->priv->print_handlers) - g_printf ("\n%s\n", message->str); - - g_string_free (message, TRUE); -} - -/** - * glade_preview_window_connect_function: - * @builder: - * @object: - * @signal_name: - * @handler_name: - * @connect_object: - * @flags: - * @window: a #GladePreviewWindow - * - * Function that collects every signal handler in @builder and shows them - * in @window info bar when the callback is activated - */ -void -glade_preview_window_connect_function (GtkBuilder *builder, - GObject *object, - const gchar *signal_name, - const gchar *handler_name, - GObject *connect_object, - GConnectFlags flags, - gpointer window) -{ - SignalData *data; - HandlerData *hd; - guint signal_id; - gchar *key; - - g_return_if_fail (GLADE_IS_PREVIEW_WINDOW (window)); - - if (!(signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object)))) - return; - - key = g_strconcat ("glade-signal-data-", signal_name, NULL); - data = g_object_get_data (object, key); - - if (!data) - { - data = g_new0 (SignalData, 1); - - data->window = g_object_ref (window); - g_signal_query (signal_id, &data->query); - data->object = g_object_ref (object); - - g_signal_connect_data (object, signal_name, - G_CALLBACK (on_handler_called), - data, signal_data_free, G_CONNECT_SWAPPED); - - g_object_set_data (object, key, data); - } - - hd = g_new0 (HandlerData, 1); - hd->handler_name = g_strdup (handler_name); - hd->connect_object = connect_object ? g_object_ref (connect_object) : NULL; - hd->flags = flags; - - data->handlers = g_list_append (data->handlers, hd); - - g_free (key); -} diff --git a/gladeui/glade-preview-window.h b/gladeui/glade-preview-window.h deleted file mode 100644 index 56276e7b..00000000 --- a/gladeui/glade-preview-window.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * glade-preview-window.h - * - * Copyright (C) 2013 Juan Pablo Ugarte - * - * Author: Juan Pablo Ugarte <juanpablougarte@gmail.com> - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser 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. - * - */ - -#ifndef _GLADE_PREVIEW_WINDOW_H_ -#define _GLADE_PREVIEW_WINDOW_H_ - -#include <gtk/gtk.h> - -G_BEGIN_DECLS - -#define GLADE_TYPE_PREVIEW_WINDOW (glade_preview_window_get_type ()) -#define GLADE_PREVIEW_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PREVIEW_WINDOW, GladePreviewWindow)) -#define GLADE_PREVIEW_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PREVIEW_WINDOW, GladePreviewWindowClass)) -#define GLADE_IS_PREVIEW_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PREVIEW_WINDOW)) -#define GLADE_IS_PREVIEW_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PREVIEW_WINDOW)) -#define GLADE_PREVIEW_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PREVIEW_WINDOW, GladePreviewWindowClass)) - -typedef struct _GladePreviewWindowClass GladePreviewWindowClass; -typedef struct _GladePreviewWindow GladePreviewWindow; -typedef struct _GladePreviewWindowPrivate GladePreviewWindowPrivate; - - -struct _GladePreviewWindowClass -{ - GtkWindowClass parent_class; -}; - -struct _GladePreviewWindow -{ - GtkWindow parent_instance; - - GladePreviewWindowPrivate *priv; -}; - -GType glade_preview_window_get_type (void) G_GNUC_CONST; - -GtkWidget *glade_preview_window_new (void); - -void glade_preview_window_set_widget (GladePreviewWindow *window, - GtkWidget *widget); - -void glade_preview_window_set_print_handlers (GladePreviewWindow *window, - gboolean print); - -void glade_preview_window_set_message (GladePreviewWindow *window, - GtkMessageType type, - const gchar *message); - -void glade_preview_window_set_css_file (GladePreviewWindow *window, - const gchar *css_file); - -void glade_preview_window_set_screenshot_extension (GladePreviewWindow *window, - const gchar *extension); - -void glade_preview_window_screenshot (GladePreviewWindow *window, - gboolean wait, - const gchar *filename); - -void glade_preview_window_slideshow_save (GladePreviewWindow *window, - const gchar *filename); - -void glade_preview_window_connect_function (GtkBuilder *builder, - GObject *object, - const gchar *signal_name, - const gchar *handler_name, - GObject *connect_object, - GConnectFlags flags, - gpointer window); - -G_END_DECLS - -#endif /* _GLADE_PREVIEW_WINDOW_H_ */ diff --git a/gladeui/glade-previewer-main.c b/gladeui/glade-previewer-main.c new file mode 100644 index 00000000..5dab4acd --- /dev/null +++ b/gladeui/glade-previewer-main.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2010 Marco Diego Aurélio Mesquita + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser 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: + * Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com> + */ + +#include <config.h> + +#include <gladeui/glade.h> + +#include <stdlib.h> +#include <locale.h> +#include <glib/gi18n-lib.h> +#include <glib/gstdio.h> + +#include "glade-previewer.h" +#include "glade-preview-template.h" +#include "glade-preview-tokens.h" + +typedef struct +{ + GladePreviewer *preview; + gchar *file_name, *toplevel; + gboolean is_template; +} GladePreviewerApp; + +static GObject * +get_toplevel (GtkBuilder *builder, gchar *name) +{ + GObject *toplevel = NULL; + GObject *object; + + if (name == NULL) + { + GSList *l, *objects = gtk_builder_get_objects (builder); + + /* Iterate trough objects and search for a window or widget */ + for (l = objects; l; l = g_slist_next (l)) + { + GObject *obj = l->data; + + if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) + continue; + + if (toplevel == NULL) + toplevel = obj; + else if (GTK_IS_WINDOW (obj)) + toplevel = obj; + } + + g_slist_free (objects); + if (toplevel == NULL) + { + g_printerr (_("UI definition has no previewable widgets.\n")); + exit (1); + } + } + else + { + object = gtk_builder_get_object (builder, name); + + if (object == NULL) + { + g_printerr (_("Object %s not found in UI definition.\n"), name); + exit (1); + } + + if (!GTK_IS_WIDGET (object)) + { + g_printerr (_("Object is not previewable.\n")); + exit (1); + } + + toplevel = object; + } + + return g_object_ref_sink (toplevel); +} + +static GObject * +get_toplevel_from_string (GladePreviewerApp *app, gchar *name, gchar *string, gsize size) +{ + gchar *wd = NULL; + GObject *retval = NULL; + + /* We need to change the working directory so builder get a chance to load resources */ + if (app->file_name) + { + gchar *dirname = g_path_get_dirname (app->file_name); + wd = g_get_current_dir (); + g_chdir (dirname); + g_free (dirname); + } + + /* We use template flag as a hint since the user can turn on and off template + * while the preview is live. + */ + if (app->is_template) + retval = glade_preview_template_object_new (string, size, + glade_previewer_connect_function, + app->preview); + + if (!retval) + { + GtkBuilder *builder = gtk_builder_new (); + GError *error = NULL; + + /* We do not know if its a template yet */ + app->is_template = FALSE; + + if (gtk_builder_add_from_string (builder, string, size, &error)) + { + gtk_builder_connect_signals_full (builder, + glade_previewer_connect_function, + app->preview); + retval = get_toplevel (builder, name); + } + else + { + if (error->code == GTK_BUILDER_ERROR_UNHANDLED_TAG && + (retval = glade_preview_template_object_new (string, size, + glade_previewer_connect_function, + app->preview))) + { + /* At this point we know it is a template, so keep a hint for next time */ + app->is_template = TRUE; + } + else + { + gchar *message = g_strdup_printf (_("Couldn't load builder definition: %s"), error->message); + glade_previewer_set_message (app->preview, GTK_MESSAGE_ERROR, message); + g_free (message); + } + + g_error_free (error); + } + + g_object_unref (builder); + } + + /* restore directory */ + if (wd) + { + g_chdir (wd); + g_free (wd); + } + + return retval; +} + +static gchar * +read_buffer (GIOChannel * source) +{ + gchar *buffer; + gchar *token; + gchar *tmp; + GError *error = NULL; + + if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != + G_IO_STATUS_NORMAL) + { + g_printerr (_("Error: %s.\n"), error->message); + g_error_free (error); + exit (1); + } + + /* Check for quit token */ + if (g_strcmp0 (QUIT_TOKEN, token) == 0) + { + g_free (token); + return NULL; + } + + /* Loop to load the UI */ + buffer = g_strdup (token); + do + { + g_free (token); + if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != + G_IO_STATUS_NORMAL) + { + g_printerr (_("Error: %s.\n"), error->message); + g_error_free (error); + exit (1); + } + tmp = buffer; + buffer = g_strconcat (buffer, token, NULL); + g_free (tmp); + } + while (g_strcmp0 ("</interface>\n", token) != 0); + g_free (token); + + return buffer; +} + +static gboolean +on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data) +{ + GladePreviewerApp *app = data; + GObject *new_widget; + gchar *buffer; + + buffer = read_buffer (source); + if (buffer == NULL) + { + gtk_main_quit (); + return FALSE; + } + + if (condition & G_IO_HUP) + { + g_printerr (_("Broken pipe!\n")); + exit (1); + } + + /* We have an update */ + if (g_str_has_prefix (buffer, UPDATE_TOKEN)) + { + gchar **split_buffer = g_strsplit_set (buffer + UPDATE_TOKEN_SIZE, "\n", 2); + + if (!split_buffer) + { + g_free (buffer); + return FALSE; + } + + new_widget = get_toplevel_from_string (app, split_buffer[0], split_buffer[1], -1); + + g_strfreev (split_buffer); + } + else + { + new_widget = get_toplevel_from_string (app, app->toplevel, buffer, -1); + } + + if (new_widget) + { + glade_previewer_set_widget (app->preview, GTK_WIDGET (new_widget)); + gtk_widget_show (GTK_WIDGET (new_widget)); + } + + glade_previewer_present (app->preview); + + g_free (buffer); + + return TRUE; +} + +static GladePreviewerApp * +glade_previewer_app_new (gchar *filename, gchar *toplevel) +{ + GladePreviewerApp *app = g_new0 (GladePreviewerApp, 1); + + app->preview = GLADE_PREVIEWER (glade_previewer_new ()); + g_object_ref_sink (app->preview); + + app->file_name = g_strdup (filename); + app->toplevel = g_strdup (toplevel); + + return app; +} + +static void +glade_previewer_free (GladePreviewerApp *app) +{ + g_object_unref (app->preview); + g_free (app->file_name); + g_free (app->toplevel); + g_free (app); +} + +static gboolean listen = FALSE; +static gboolean version = FALSE; +static gboolean slideshow = FALSE; +static gboolean template = FALSE; +static gboolean print_handler = FALSE; +static gchar *file_name = NULL; +static gchar *toplevel_name = NULL; +static gchar *css_file_name = NULL; +static gchar *screenshot_file_name = NULL; + +static GOptionEntry option_entries[] = +{ + {"filename", 'f', 0, G_OPTION_ARG_FILENAME, &file_name, N_("Name of the file to preview"), "FILENAME"}, + {"template", 0, 0, G_OPTION_ARG_NONE, &template, N_("Creates dummy widget class to load a template"), NULL}, + {"toplevel", 't', 0, G_OPTION_ARG_STRING, &toplevel_name, N_("Name of the toplevel to preview"), "TOPLEVELNAME"}, + {"screenshot", 0, 0, G_OPTION_ARG_FILENAME, &screenshot_file_name, N_("File name to save a screenshot"), NULL}, + {"css", 0, 0, G_OPTION_ARG_FILENAME, &css_file_name, N_("CSS file to use"), NULL}, + {"listen", 'l', 0, G_OPTION_ARG_NONE, &listen, N_("Listen standard input"), NULL}, + {"slideshow", 0, 0, G_OPTION_ARG_NONE, &slideshow, N_("make a slideshow of every toplevel widget by adding them in a GtkStack"), NULL}, + {"print-handler", 0, 0, G_OPTION_ARG_NONE, &print_handler, N_("Print handlers signature on invocation"), NULL}, + {"version", 'v', 0, G_OPTION_ARG_NONE, &version, N_("Display previewer version"), NULL}, + {NULL} +}; + +int +main (int argc, char **argv) +{ + GladePreviewerApp *app; + GOptionContext *context; + GError *error = NULL; + GObject *toplevel = NULL; + +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, glade_app_get_locale_dir ()); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + context = g_option_context_new (_("- previews a glade UI definition")); + g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr (_("%s\nRun '%s --help' to see a full list of available command line " + "options.\n"), error->message, argv[0]); + g_error_free (error); + g_option_context_free (context); + return 1; + } + + g_option_context_free (context); + + if (version) + { + g_print ("glade-previewer " VERSION "\n"); + return 0; + } + + if (!listen && !file_name) + { + g_printerr (_("Either --listen or --filename must be specified.\n")); + return 0; + } + + gtk_init (&argc, &argv); + glade_app_get (); + + app = glade_previewer_app_new (file_name, toplevel_name); + + app->is_template = template; + + if (print_handler) + glade_previewer_set_print_handlers (GLADE_PREVIEWER (app->preview), TRUE); + + if (css_file_name) + glade_previewer_set_css_file (app->preview, css_file_name); + + if (listen) + { +#ifdef WINDOWS + GIOChannel *input = g_io_channel_win32_new_fd (fileno (stdin)); +#else + GIOChannel *input = g_io_channel_unix_new (fileno (stdin)); +#endif + + g_io_add_watch (input, G_IO_IN | G_IO_HUP, on_data_incoming, app); + + gtk_main (); + } + else if (template) + { + gchar *contents = NULL; + gsize size; + + if (g_file_get_contents (file_name, &contents, &size, NULL)) + toplevel = get_toplevel_from_string (app, NULL, contents, size); + + g_free (contents); + } + else if (file_name) + { + GtkBuilder *builder = gtk_builder_new (); + GError *error = NULL; + + /* Use from_file() function gives builder a chance to know where to load resources from */ + if (!gtk_builder_add_from_file (builder, app->file_name, &error)) + { + g_printerr (_("Couldn't load builder definition: %s"), error->message); + g_error_free (error); + return 1; + } + + if (slideshow) + { + GSList *objects = gtk_builder_get_objects (builder); + + glade_previewer_set_slideshow_widgets (app->preview, objects); + glade_previewer_present (app->preview); + + if (screenshot_file_name) + glade_previewer_slideshow_save (app->preview, screenshot_file_name); + else + gtk_main (); + + g_slist_free (objects); + } + else + { + toplevel = get_toplevel (builder, toplevel_name); + + gtk_builder_connect_signals_full (builder, + glade_previewer_connect_function, + app->preview); + } + + g_object_unref (builder); + } + + if (toplevel) + { + glade_previewer_set_widget (app->preview, GTK_WIDGET (toplevel)); + g_object_unref (toplevel); + glade_previewer_present (app->preview); + + if (screenshot_file_name) + glade_previewer_screenshot (app->preview, TRUE, screenshot_file_name); + else + gtk_main (); + } + + /* free unused resources */ + g_free (file_name); + g_free (toplevel_name); + g_free (css_file_name); + g_free (screenshot_file_name); + glade_previewer_free (app); + + return 0; +} diff --git a/gladeui/glade-previewer.c b/gladeui/glade-previewer.c index 44a23574..8472359d 100644 --- a/gladeui/glade-previewer.c +++ b/gladeui/glade-previewer.c @@ -1,332 +1,524 @@ /* - * Copyright (C) 2010 Marco Diego Aurélio Mesquita + * glade-previewer.c * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. + * Copyright (C) 2013-2016 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte <juanpablougarte@gmail.com> * - * 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. + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software + * You should have received a copy of the GNU Lesser 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: - * Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com> */ - #include <config.h> -#include <gladeui/glade.h> - -#include <stdlib.h> -#include <locale.h> +#include "glade-previewer.h" #include <glib/gi18n-lib.h> -#include <glib/gstdio.h> +#include <glib/gprintf.h> +#include <cairo-pdf.h> +#include <cairo-svg.h> +#include <cairo-ps.h> -#include "glade-preview-window.h" -#include "glade-preview-template.h" -#include "glade-preview-tokens.h" +struct _GladePreviewerPrivate +{ + GtkWidget *widget; /* Preview widget */ + GList *objects; /* SlideShow objects */ + GtkWidget *dialog; /* Dialog to show messages */ + GtkWidget *textview; -typedef struct + GtkCssProvider *css_provider; + GFileMonitor *css_monitor; + gchar *css_file; + gchar *extension; + + gboolean print_handlers; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladePreviewer, glade_previewer, G_TYPE_OBJECT); + +static void +glade_previewer_init (GladePreviewer *preview) { - GladePreviewWindow *window; - gchar *file_name, *toplevel; - gboolean is_template; -} GladePreviewer; + GladePreviewerPrivate *priv = glade_previewer_get_instance_private (preview); + + preview->priv = priv; +} -static GObject * -get_toplevel (GtkBuilder *builder, gchar *name) +static void +glade_previewer_dispose (GObject *object) { - GObject *toplevel = NULL; - GObject *object; + GladePreviewerPrivate *priv = GLADE_PREVIEWER (object)->priv; - if (name == NULL) - { - GSList *l, *objects = gtk_builder_get_objects (builder); + g_list_free (priv->objects); + + priv->objects = NULL; + priv->dialog = NULL; + g_clear_object (&priv->css_provider); + g_clear_object (&priv->css_monitor); - /* Iterate trough objects and search for a window or widget */ - for (l = objects; l; l = g_slist_next (l)) - { - GObject *obj = l->data; + G_OBJECT_CLASS (glade_previewer_parent_class)->dispose (object); +} - if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) - continue; +static void +glade_previewer_finalize (GObject *object) +{ + GladePreviewerPrivate *priv = GLADE_PREVIEWER (object)->priv; - if (toplevel == NULL) - toplevel = obj; - else if (GTK_IS_WINDOW (obj)) - toplevel = obj; - } + g_free (priv->css_file); + g_free (priv->extension); - g_slist_free (objects); - if (toplevel == NULL) - { - g_printerr (_("UI definition has no previewable widgets.\n")); - exit (1); - } + G_OBJECT_CLASS (glade_previewer_parent_class)->finalize (object); +} + +static gboolean +on_widget_key_press_event (GtkWidget *widget, + GdkEventKey *event, + GladePreviewer *preview) +{ + GladePreviewerPrivate *priv = preview->priv; + GList *node = NULL; + GtkStack *stack; + gchar *extension; + + if (priv->objects) + { + stack = GTK_STACK (gtk_bin_get_child (GTK_BIN (priv->widget))); + node = g_list_find (priv->objects, gtk_stack_get_visible_child (stack)); } - else + + switch (event->keyval) { - object = gtk_builder_get_object (builder, name); + case GDK_KEY_Page_Up: + if (node && node->prev) + gtk_stack_set_visible_child (stack, node->prev->data); + return TRUE; + break; + case GDK_KEY_Page_Down: + if (node && node->next) + gtk_stack_set_visible_child (stack, node->next->data); + return TRUE; + break; + case GDK_KEY_F5: + extension = "svg"; + break; + case GDK_KEY_F6: + extension = "ps"; + break; + case GDK_KEY_F7: + extension = "pdf"; + break; + case GDK_KEY_F8: + extension = priv->extension ? priv->extension : "png"; + break; + case GDK_KEY_F11: + if (gdk_window_get_state (gtk_widget_get_window (widget)) & GDK_WINDOW_STATE_FULLSCREEN) + gtk_window_unfullscreen (GTK_WINDOW (widget)); + else + gtk_window_fullscreen (GTK_WINDOW (widget)); + + return TRUE; + break; + default: + return FALSE; + break; + } - if (object == NULL) - { - g_printerr (_("Object %s not found in UI definition.\n"), name); - exit (1); - } + if (extension) + { + gchar *tmp_file = g_strdup_printf ("glade-screenshot-XXXXXX.%s", extension); - if (!GTK_IS_WIDGET (object)) - { - g_printerr (_("Object is not previewable.\n")); - exit (1); - } + g_mkstemp (tmp_file); + glade_previewer_screenshot (preview, FALSE, tmp_file); + g_free (tmp_file); - toplevel = object; + return TRUE; } - return g_object_ref_sink (toplevel); + return FALSE; } -static GObject * -get_toplevel_from_string (GladePreviewer *app, gchar *name, gchar *string, gsize size) +static void +glade_previewer_class_init (GladePreviewerClass *klass) { - gchar *wd = NULL; - GObject *retval = NULL; + GObjectClass *object_class = G_OBJECT_CLASS (klass); - /* We need to change the working directory so builder get a chance to load resources */ - if (app->file_name) - { - gchar *dirname = g_path_get_dirname (app->file_name); - wd = g_get_current_dir (); - g_chdir (dirname); - g_free (dirname); - } + object_class->dispose = glade_previewer_dispose; + object_class->finalize = glade_previewer_finalize; +} - /* We use template flag as a hint since the user can turn on and off template - * while the preview is live. - */ - if (app->is_template) - retval = glade_preview_template_object_new (string, size, - glade_preview_window_connect_function, - app->window); +GObject * +glade_previewer_new (void) +{ + return g_object_new (GLADE_TYPE_PREVIEWER, NULL); +} - if (!retval) - { - GtkBuilder *builder = gtk_builder_new (); - GError *error = NULL; +void +glade_previewer_present (GladePreviewer *preview) +{ + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + gtk_window_present (GTK_WINDOW (preview->priv->widget)); +} - /* We do not know if its a template yet */ - app->is_template = FALSE; +void +glade_previewer_set_widget (GladePreviewer *preview, GtkWidget *widget) +{ + GladePreviewerPrivate *priv; + GtkWidget *sw; - if (gtk_builder_add_from_string (builder, string, size, &error)) - { - gtk_builder_connect_signals_full (builder, - glade_preview_window_connect_function, - app->window); - retval = get_toplevel (builder, name); - } - else - { - if (error->code == GTK_BUILDER_ERROR_UNHANDLED_TAG && - (retval = glade_preview_template_object_new (string, size, - glade_preview_window_connect_function, - app->window))) - { - /* At this point we know it is a template, so keep a hint for next time */ - app->is_template = TRUE; - } - else - { - gchar *message = g_strdup_printf (_("Couldn't load builder definition: %s"), error->message); - glade_preview_window_set_message (app->window, GTK_MESSAGE_ERROR, message); - g_free (message); - } + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (GTK_IS_WIDGET (widget)); - g_error_free (error); - } + priv = preview->priv; - g_object_unref (builder); - } + if (priv->widget) + gtk_widget_destroy (priv->widget); - /* restore directory */ - if (wd) + if (!gtk_widget_is_toplevel (widget)) { - g_chdir (wd); - g_free (wd); + GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_container_add (GTK_CONTAINER (window), widget); + + priv->widget = window; } - - return retval; + else + { + priv->widget = widget; + } + + /* Create dialog to display messages */ + priv->dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (priv->dialog), 640, 320); + gtk_window_set_title (GTK_WINDOW (priv->dialog), _("Glade Previewer log")); + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (priv->widget)); + + priv->textview = gtk_text_view_new (); + gtk_widget_show (priv->textview); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + + gtk_container_add (GTK_CONTAINER (sw), priv->textview); + gtk_container_add (GTK_CONTAINER (priv->dialog), sw); + + /* Hide dialog on delete event */ + g_signal_connect (priv->dialog, "delete-event", + G_CALLBACK (gtk_widget_hide), + NULL); + + /* Quit on delete event */ + g_signal_connect (priv->widget, "delete-event", + G_CALLBACK (gtk_main_quit), + NULL); + + /* Make sure we get press events */ + gtk_widget_add_events (priv->widget, GDK_KEY_PRESS_MASK); + + /* Handle key presses for screenshot feature */ + g_signal_connect_object (priv->widget, "key-press-event", + G_CALLBACK (on_widget_key_press_event), + preview, 0); } -static gchar * -read_buffer (GIOChannel * source) +void +glade_previewer_set_message (GladePreviewer *preview, + GtkMessageType type, + const gchar *message) { - gchar *buffer; - gchar *token; - gchar *tmp; - GError *error = NULL; + GladePreviewerPrivate *priv; + GtkTextBuffer *buffer; - if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != - G_IO_STATUS_NORMAL) - { - g_printerr (_("Error: %s.\n"), error->message); - g_error_free (error); - exit (1); - } + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; - /* Check for quit token */ - if (g_strcmp0 (QUIT_TOKEN, token) == 0) + if (!priv->textview) + return; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->textview)); + + if (message) { - g_free (token); - return NULL; + GtkTextIter iter; + + /* TODO: use message type to color text */ + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, "\n", -1); + + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, message, -1); + + gtk_window_present (GTK_WINDOW (priv->dialog)); } +} + +static void +on_css_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GladePreviewer *preview) +{ + GladePreviewerPrivate *priv = preview->priv; + GError *error = NULL; + + gtk_css_provider_load_from_file (priv->css_provider, file, &error); - /* Loop to load the UI */ - buffer = g_strdup (token); - do + if (error) { - g_free (token); - if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != - G_IO_STATUS_NORMAL) - { - g_printerr (_("Error: %s.\n"), error->message); - g_error_free (error); - exit (1); - } - tmp = buffer; - buffer = g_strconcat (buffer, token, NULL); - g_free (tmp); + glade_previewer_set_message (preview, GTK_MESSAGE_WARNING, error->message); + g_error_free (error); } - while (g_strcmp0 ("</interface>\n", token) != 0); - g_free (token); - - return buffer; + else + glade_previewer_set_message (preview, GTK_MESSAGE_OTHER, NULL); } -static void -glade_previewer_window_set_title (GtkWindow *window, - gchar *filename, - gchar *toplevel) +void +glade_previewer_set_css_file (GladePreviewer *preview, + const gchar *css_file) { - gchar *title, *pretty_path = NULL; - const gchar *widget_name = toplevel; - if (widget_name && g_str_has_prefix (widget_name, GLADE_UNNAMED_PREFIX)) - widget_name = _("(unnamed)"); + GladePreviewerPrivate *priv; + GError *error = NULL; + GFile *file; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; - if (filename && g_path_is_absolute (filename)) + g_free (priv->css_file); + g_clear_object (&priv->css_monitor); + + priv->css_file = g_strdup (css_file); + + file = g_file_new_for_path (css_file); + + if (!priv->css_provider) { - gchar *canonical_path = glade_util_canonical_path (filename); - filename = pretty_path = glade_utils_replace_home_dir_with_tilde (canonical_path); - g_free (canonical_path); + priv->css_provider = gtk_css_provider_new (); + g_object_ref_sink (priv->css_provider); + + /* Set provider for default screen once */ + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (priv->css_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } - if (filename) + priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); + if (error) { - if (toplevel) - title = g_strdup_printf (_("Previewing %s - %s"), filename, widget_name); - else - title = g_strdup_printf (_("Previewing %s"), filename); + g_warning ("Cant monitor CSS file %s: %s", css_file, error->message); + g_error_free (error); } - else if (toplevel) + else { - title = g_strdup_printf (_("Previewing %s"), widget_name); + g_object_ref_sink (priv->css_monitor); + g_signal_connect (priv->css_monitor, "changed", + G_CALLBACK (on_css_monitor_changed), preview); } - else + + /* load CSS */ + gtk_css_provider_load_from_file (priv->css_provider, file, &error); + if (error) { - gtk_window_set_title (window, _("Glade Preview")); - return; + glade_previewer_set_message (preview, GTK_MESSAGE_INFO, error->message); + g_message ("%s CSS parsing failed: %s", css_file, error->message); + g_error_free (error); } + + g_object_unref (file); +} - gtk_window_set_title (window, title); - g_free (pretty_path); - g_free (title); +void +glade_previewer_set_screenshot_extension (GladePreviewer *preview, + const gchar *extension) +{ + GladePreviewerPrivate *priv; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; + + g_free (priv->extension); + priv->extension = g_strdup (extension); } static gboolean -on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data) +quit_when_idle (gpointer loop) { - GladePreviewer *app = data; - GObject *new_widget; - gchar *buffer; + g_main_loop_quit (loop); - buffer = read_buffer (source); - if (buffer == NULL) - { - gtk_main_quit (); - return FALSE; - } + return G_SOURCE_REMOVE; +} - if (condition & G_IO_HUP) +static void +check_for_draw (GdkEvent *event, gpointer loop) +{ + if (event->type == GDK_EXPOSE) { - g_printerr (_("Broken pipe!\n")); - exit (1); + g_idle_add (quit_when_idle, loop); + gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL); } - /* We have an update */ - if (g_str_has_prefix (buffer, UPDATE_TOKEN)) - { - gchar **split_buffer = g_strsplit_set (buffer + UPDATE_TOKEN_SIZE, "\n", 2); + gtk_main_do_event (event); +} - if (!split_buffer) - { - g_free (buffer); - return FALSE; - } +/* Taken from Gtk sources gtk-reftest.c */ +static void +glade_previewer_wait_for_drawing (GdkWindow *window) +{ + GMainLoop *loop; + + loop = g_main_loop_new (NULL, FALSE); + /* We wait until the widget is drawn for the first time. + * We can not wait for a GtkWidget::draw event, because that might not + * happen if the window is fully obscured by windowed child widgets. + * Alternatively, we could wait for an expose event on widget's window. + * Both of these are rather hairy, not sure what's best. */ + gdk_event_handler_set (check_for_draw, loop, NULL); + g_main_loop_run (loop); + + /* give the WM/server some time to sync. They need it. + * Also, do use popups instead of toplevls in your tests + * whenever you can. */ + gdk_display_sync (gdk_window_get_display (window)); + g_timeout_add (500, quit_when_idle, loop); + g_main_loop_run (loop); +} - new_widget = get_toplevel_from_string (app, split_buffer[0], split_buffer[1], -1); - glade_previewer_window_set_title (GTK_WINDOW (app->window), app->file_name, - split_buffer[0]); +static const gchar * +glade_previewer_get_extension (const gchar *filename) +{ + gchar *extension; + + g_return_val_if_fail (filename != NULL, NULL); - g_strfreev (split_buffer); - } - else - { - new_widget = get_toplevel_from_string (app, app->toplevel, buffer, -1); - glade_previewer_window_set_title (GTK_WINDOW (app->window), app->file_name, app->toplevel); - } + extension = g_strrstr (filename,"."); - if (new_widget) + if (extension) + extension++; + + if (!extension) { - glade_preview_window_set_widget (app->window, GTK_WIDGET (new_widget)); - gtk_widget_show (GTK_WIDGET (new_widget)); + g_warning ("%s has no extension!", filename); + return NULL; } + return extension; +} - gtk_window_present (GTK_WINDOW (app->window)); - - g_free (buffer); - - return TRUE; +static void +glade_previewer_get_scale (GdkScreen *screen, gdouble *sx, gdouble *sy) +{ + if (sx) + *sx = 72.0 / (gdk_screen_get_width (screen) / (gdk_screen_get_width_mm (screen) * 0.03937008)); + + if (sy) + *sy = 72.0 / (gdk_screen_get_height (screen) / (gdk_screen_get_height_mm (screen) * 0.03937008)); } -static GladePreviewer * -glade_previewer_new (gchar *filename, gchar *toplevel) +static cairo_surface_t * +glade_previewer_surface_from_file (const gchar *filename, gdouble w, gdouble h) { - GladePreviewer *app = g_new0 (GladePreviewer, 1); + cairo_surface_t *surface; + const gchar *extension; - app->window = GLADE_PREVIEW_WINDOW (glade_preview_window_new ()); - g_object_ref_sink (app->window); + extension = glade_previewer_get_extension (filename); - g_signal_connect (app->window, "delete-event", G_CALLBACK (gtk_main_quit), NULL); - glade_previewer_window_set_title (GTK_WINDOW (app->window), filename, toplevel); - - app->file_name = g_strdup (filename); - app->toplevel = g_strdup (toplevel); + if (extension == NULL) + return NULL; + + if (g_strcmp0 (extension, "svg") == 0) +#if CAIRO_HAS_SVG_SURFACE + surface = cairo_svg_surface_create (filename, w, h); +#else + g_warning ("PDF not supported by the cairo version used"); +#endif + else if (g_strcmp0 (extension, "ps") == 0) +#if CAIRO_HAS_PS_SURFACE + surface = cairo_ps_surface_create (filename, w, h); +#else + g_warning ("PS not supported by the cairo version used"); +#endif + else if (g_strcmp0 (extension, "pdf") == 0) +#if CAIRO_HAS_PDF_SURFACE + surface = cairo_pdf_surface_create (filename, w, h); +#else + g_warning ("PDF not supported by the cairo version used"); +#endif + else + return NULL; - return app; + return surface; } -static void -glade_previewer_free (GladePreviewer *app) +/** + * glade_previewer_screenshot: + * @preview: A GladePreviewer + * @wait: True if it should wait for widget to draw. + * @filename: a filename to save the image. + * + * Takes a screenshot of the current widget @window is showing and save it to @filename + * Supported extension are svg, ps, pdf and wahtever gdk-pixbuf supports + */ +void +glade_previewer_screenshot (GladePreviewer *preview, + gboolean wait, + const gchar *filename) { - g_object_unref (app->window); - g_free (app->file_name); - g_free (app->toplevel); - g_free (app); + GladePreviewerPrivate *priv; + cairo_surface_t *surface; + GdkWindow *gdkwindow; + GdkScreen *screen; + gdouble sx, sy; + gint w, h; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (filename != NULL); + priv = preview->priv; + + if (!priv->widget) + return; + + gdkwindow = gtk_widget_get_window (priv->widget); + screen = gdk_window_get_screen (gdkwindow); + + if (wait) + glade_previewer_wait_for_drawing (gdkwindow); + + w = gtk_widget_get_allocated_width (priv->widget); + h = gtk_widget_get_allocated_height (priv->widget); + glade_previewer_get_scale (screen, &sx, &sy); + + surface = glade_previewer_surface_from_file (filename, w*sx, h*sy); + + if (surface) + { + cairo_t *cr = cairo_create (surface); + cairo_scale (cr, sx, sy); + gtk_widget_draw (priv->widget, cr); + cairo_destroy (cr); + cairo_surface_destroy(surface); + } + else + { + GdkPixbuf *pix = gdk_pixbuf_get_from_window (gdkwindow, 0, 0, w, h); + const gchar *ext = glade_previewer_get_extension (filename); + GError *error = NULL; + + if (!gdk_pixbuf_save (pix, filename, ext ? ext : "png", &error, NULL)) + { + g_warning ("Could not save screenshot to %s because %s", filename, error->message); + g_error_free (error); + } + + g_object_unref (pix); + } } static gint @@ -337,231 +529,341 @@ objects_cmp_func (gconstpointer a, gconstpointer b) name_b = gtk_buildable_get_name (GTK_BUILDABLE (b)); return g_strcmp0 (name_a, name_b); } -static gboolean -glade_previewer_stack_key_press_event (GtkWidget *window, GdkEventKey *event, GtkWidget *widget) + +/** + * glade_previewer_set_slideshow_widgets: + * @preview: A GladePreviewer + * @objects: GSlist of GObject + * + * Add a list of objects to slideshow + */ +void +glade_previewer_set_slideshow_widgets (GladePreviewer *preview, + GSList *objects) { - GtkWidget *child = gtk_stack_get_visible_child (GTK_STACK (widget)); - GList *children, *node; - gboolean retval = FALSE; - - if (!child) - return FALSE; + GladePreviewerPrivate *priv; + GtkStack *stack; + GSList *l; - children = gtk_container_get_children (GTK_CONTAINER (widget)); + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; - node = g_list_find (children, child); + stack = GTK_STACK (gtk_stack_new ()); + gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE); - if (node) + objects = g_slist_sort (g_slist_copy (objects), objects_cmp_func); + + for (l = objects; l; l = g_slist_next (l)) { - switch (event->keyval) - { - case GDK_KEY_Page_Up: - if (node->prev) - gtk_stack_set_visible_child (GTK_STACK (widget), node->prev->data); - retval = TRUE; - break; - case GDK_KEY_Page_Down: - if (node->next) - gtk_stack_set_visible_child (GTK_STACK (widget), node->next->data); - retval = TRUE; - break; - default: - retval = FALSE; - break; - } + GObject *obj = l->data; + + if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) + continue; + + /* TODO: make sure we can add a toplevel inside a stack */ + if (GTK_IS_WINDOW (obj)) + continue; + + priv->objects = g_list_prepend (priv->objects, obj); + + gtk_stack_add_named (stack, GTK_WIDGET (obj), + gtk_buildable_get_name (GTK_BUILDABLE (obj))); } - g_list_free (children); - return retval; + priv->objects = g_list_reverse (priv->objects); + + glade_previewer_set_widget (preview, GTK_WIDGET (stack)); + gtk_widget_show (GTK_WIDGET (stack)); + + g_slist_free (objects); } +/** + * glade_previewer_slideshow_save: + * @preview: A GladePreviewer + * @filename: a filename to save the slideshow. + * + * Takes a screenshot of every widget GtkStack children and save it to @filename + * each in a different page + */ +void +glade_previewer_slideshow_save (GladePreviewer *preview, + const gchar *filename) +{ + GladePreviewerPrivate *priv; + cairo_surface_t *surface; + GdkWindow *gdkwindow; + GtkWidget *child; + GtkStack *stack; + gdouble sx, sy; -static gboolean listen = FALSE; -static gboolean version = FALSE; -static gboolean slideshow = FALSE; -static gboolean template = FALSE; -static gboolean print_handler = FALSE; -static gchar *file_name = NULL; -static gchar *toplevel_name = NULL; -static gchar *css_file_name = NULL; -static gchar *screenshot_file_name = NULL; - -static GOptionEntry option_entries[] = -{ - {"filename", 'f', 0, G_OPTION_ARG_FILENAME, &file_name, N_("Name of the file to preview"), "FILENAME"}, - {"template", 0, 0, G_OPTION_ARG_NONE, &template, N_("Creates dummy widget class to load a template"), NULL}, - {"toplevel", 't', 0, G_OPTION_ARG_STRING, &toplevel_name, N_("Name of the toplevel to preview"), "TOPLEVELNAME"}, - {"screenshot", 0, 0, G_OPTION_ARG_FILENAME, &screenshot_file_name, N_("File name to save a screenshot"), NULL}, - {"css", 0, 0, G_OPTION_ARG_FILENAME, &css_file_name, N_("CSS file to use"), NULL}, - {"listen", 'l', 0, G_OPTION_ARG_NONE, &listen, N_("Listen standard input"), NULL}, - {"slideshow", 0, 0, G_OPTION_ARG_NONE, &slideshow, N_("make a slideshow of every toplevel widget by adding them in a GtkStack"), NULL}, - {"print-handler", 0, 0, G_OPTION_ARG_NONE, &print_handler, N_("Print handlers signature on invocation"), NULL}, - {"version", 'v', 0, G_OPTION_ARG_NONE, &version, N_("Display previewer version"), NULL}, - {NULL} -}; + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (filename != NULL); + priv = preview->priv; -int -main (int argc, char **argv) -{ - GladePreviewer *app; - GOptionContext *context; - GError *error = NULL; - GObject *toplevel = NULL; + g_return_if_fail (GTK_IS_BIN (priv->widget)); -#ifdef ENABLE_NLS - setlocale (LC_ALL, ""); - bindtextdomain (GETTEXT_PACKAGE, glade_app_get_locale_dir ()); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); -#endif + child = gtk_bin_get_child (GTK_BIN (priv->widget)); + g_return_if_fail (GTK_IS_STACK (child)); + stack = GTK_STACK (child); - context = g_option_context_new (_("- previews a glade UI definition")); - g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE); - g_option_context_add_group (context, gtk_get_option_group (TRUE)); + gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_NONE); + + gdkwindow = gtk_widget_get_window (priv->widget); + glade_previewer_wait_for_drawing (gdkwindow); - if (!g_option_context_parse (context, &argc, &argv, &error)) + glade_previewer_get_scale (gtk_widget_get_screen (GTK_WIDGET (priv->widget)), &sx, &sy); + surface = glade_previewer_surface_from_file (filename, + gtk_widget_get_allocated_width (GTK_WIDGET (stack))*sx, + gtk_widget_get_allocated_height (GTK_WIDGET (stack))*sy); + + if (surface) { - g_printerr (_("%s\nRun '%s --help' to see a full list of available command line " - "options.\n"), error->message, argv[0]); - g_error_free (error); - g_option_context_free (context); - return 1; - } + GList *l, *children = gtk_container_get_children (GTK_CONTAINER (stack)); + cairo_t *cr= cairo_create (surface); - g_option_context_free (context); + cairo_scale (cr, sx, sy); - if (version) - { - g_print ("glade-previewer " VERSION "\n"); - return 0; - } + for (l = children; l; l = g_list_next (l)) + { + GtkWidget *child = l->data; + gtk_stack_set_visible_child (stack, child); + glade_previewer_wait_for_drawing (gdkwindow); + gtk_widget_draw (child, cr); + cairo_show_page (cr); + } - if (!listen && !file_name) - { - g_printerr (_("Either --listen or --filename must be specified.\n")); - return 0; + if (children) + gtk_stack_set_visible_child (stack, children->data); + + g_list_free (children); + cairo_destroy (cr); + cairo_surface_destroy(surface); } + else + g_warning ("Could not save slideshow to %s", filename); +} - gtk_init (&argc, &argv); - glade_app_get (); +/** + * glade_previewer_set_print_handlers: + * @preview: A GladePreviewer + * @print: whether to print handlers or not + * + * Set whether to print handlers when they are activated or not. + * It only works if you use glade_previewer_connect_function() as the + * connect funtion. + */ +void +glade_previewer_set_print_handlers (GladePreviewer *preview, + gboolean print) +{ + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + preview->priv->print_handlers = print; +} - app = glade_previewer_new (file_name, toplevel_name); - gtk_widget_show (GTK_WIDGET (app->window)); +typedef struct +{ + gchar *handler_name; + GObject *connect_object; + GConnectFlags flags; +} HandlerData; + +typedef struct +{ + GladePreviewer *window; + gint n_invocations; - app->is_template = template; + GSignalQuery query; + GObject *object; + GList *handlers; +} SignalData; - if (print_handler) - glade_preview_window_set_print_handlers (GLADE_PREVIEW_WINDOW (app->window), TRUE); +static void +handler_data_free (gpointer udata) +{ + HandlerData *hd = udata; + g_clear_object (&hd->connect_object); + g_free (hd->handler_name); + g_free (hd); +} - if (css_file_name) - glade_preview_window_set_css_file (app->window, css_file_name); +static void +signal_data_free (gpointer udata, GClosure *closure) +{ + SignalData *data = udata; - if (listen) - { -#ifdef WINDOWS - GIOChannel *input = g_io_channel_win32_new_fd (fileno (stdin)); -#else - GIOChannel *input = g_io_channel_unix_new (fileno (stdin)); -#endif + g_list_free_full (data->handlers, handler_data_free); + data->handlers = NULL; - g_io_add_watch (input, G_IO_IN | G_IO_HUP, on_data_incoming, app); + g_clear_object (&data->window); + g_clear_object (&data->object); - gtk_main (); - } - else if (template) - { - gchar *contents = NULL; - gsize size; + g_free (data); +} - if (g_file_get_contents (file_name, &contents, &size, NULL)) - toplevel = get_toplevel_from_string (app, NULL, contents, size); +static inline const gchar * +object_get_name (GObject *object) +{ + if (GTK_IS_BUILDABLE (object)) + return gtk_buildable_get_name (GTK_BUILDABLE (object)); + else + return g_object_get_data (object, "gtk-builder-name"); +} - g_free (contents); - } - else if (file_name) +static void +glade_handler_append (GString *message, + GSignalQuery *query, + const gchar *object, + GList *handlers, + gboolean after) +{ + GList *l; + + for (l = handlers; l; l = g_list_next (l)) { - GtkBuilder *builder = gtk_builder_new (); - GError *error = NULL; + HandlerData *hd = l->data; + gboolean handler_after = (hd->flags & G_CONNECT_AFTER); + gboolean swapped = (hd->flags & G_CONNECT_SWAPPED); + GObject *obj = hd->connect_object; + gint i; + + if ((after && !handler_after) || (!after && handler_after)) + continue; + + g_string_append_printf (message, "\n\t-> %s%s %s (%s%s%s", + g_type_name (query->return_type), + g_type_is_a (query->return_type, G_TYPE_OBJECT) ? " *" : "", + hd->handler_name, + (swapped) ? ((obj) ? G_OBJECT_TYPE_NAME (obj) : "") : g_type_name (query->itype), + (swapped) ? ((obj) ? " *" : "") : " *", + (swapped) ? ((obj) ? object_get_name (obj) : _("user_data")) : object); + + for (i = 1; i < query->n_params; i++) + g_string_append_printf (message, ", %s%s", + g_type_name (query->param_types[i]), + g_type_is_a (query->param_types[i], G_TYPE_OBJECT) ? " *" : ""); + + g_string_append_printf (message, ", %s%s%s); ", + (swapped) ? g_type_name (query->itype) : ((obj) ? G_OBJECT_TYPE_NAME (obj) : ""), + (swapped) ? " *" : ((obj) ? " *" : ""), + (swapped) ? object : ((obj) ? object_get_name (obj) : _("user_data"))); + + if (swapped && after) + /* translators: GConnectFlags values */ + g_string_append (message, _("Swapped | After")); + else if (swapped) + /* translators: GConnectFlags value */ + g_string_append (message, _("Swapped")); + else if (after) + /* translators: GConnectFlags value */ + g_string_append (message, _("After")); + } +} - /* Use from_file() function gives builder a chance to know where to load resources from */ - if (!gtk_builder_add_from_file (builder, app->file_name, &error)) - { - g_printerr (_("Couldn't load builder definition: %s"), error->message); - g_error_free (error); - return 1; - } +static inline void +glade_handler_method_append (GString *msg, GSignalQuery *q, const gchar *flags) +{ + g_string_append_printf (msg, "\n\t%sClass->%s(); %s", g_type_name (q->itype), + q->signal_name, flags); +} - if (slideshow) - { - GSList *l, *objects = gtk_builder_get_objects (builder); - GtkStack *stack = GTK_STACK (gtk_stack_new ()); +static void +on_handler_called (SignalData *data) +{ + GSignalQuery *query = &data->query; + GObject *object = data->object; + const gchar *object_name = object_get_name (object); + GString *message = g_string_new (""); - /* Add Page up and Page down key binding */ - g_signal_connect (app->window, "key-press-event", - G_CALLBACK (glade_previewer_stack_key_press_event), - stack); + data->n_invocations++; - objects = g_slist_sort (objects, objects_cmp_func); + if (data->n_invocations == 1) + /* translators: this will be shown in glade previewer when a signal %s::%s is emited one time */ + g_string_append_printf (message, _("%s::%s emitted one time"), + G_OBJECT_TYPE_NAME (object), query->signal_name); + else + /* translators: this will be shown in glade previewer when a signal %s::%s is emited %d times */ + g_string_append_printf (message, _("%s::%s emitted %d times"), + G_OBJECT_TYPE_NAME (object), query->signal_name, + data->n_invocations); - for (l = objects; l; l = g_slist_next (l)) - { - GObject *obj = l->data; + if (query->signal_flags & G_SIGNAL_RUN_FIRST) + glade_handler_method_append (message, query, _("Run First")); - if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) - continue; + glade_handler_append (message, query, object_name, data->handlers, FALSE); - /* TODO: make sure we can add a toplevel inside a stack */ - if (GTK_IS_WINDOW (obj)) - continue; + if (query->signal_flags & G_SIGNAL_RUN_LAST) + glade_handler_method_append (message, query, _("Run Last")); - gtk_stack_add_named (stack, GTK_WIDGET (obj), - gtk_buildable_get_name (GTK_BUILDABLE (obj))); - } + glade_handler_append (message, query, object_name, data->handlers, TRUE); - glade_preview_window_set_widget (app->window, GTK_WIDGET (stack)); - gtk_widget_show (GTK_WIDGET (stack)); - - if (screenshot_file_name) - glade_preview_window_slideshow_save (app->window, screenshot_file_name); - else - { - gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE); + if (query->signal_flags & G_SIGNAL_RUN_CLEANUP) + glade_handler_method_append (message, query, _("Run Cleanup")); - gtk_main (); - } + glade_previewer_set_message (data->window, GTK_MESSAGE_INFO, message->str); - g_slist_free (objects); - } - else - { - toplevel = get_toplevel (builder, toplevel_name); + if (data->window->priv->print_handlers) + g_printf ("\n%s\n", message->str); - gtk_builder_connect_signals_full (builder, - glade_preview_window_connect_function, - app->window); - } + g_string_free (message, TRUE); +} - g_object_unref (builder); - } +/** + * glade_previewer_connect_function: + * @builder: + * @object: + * @signal_name: + * @handler_name: + * @connect_object: + * @flags: + * @window: a #GladePreviewer + * + * Function that collects every signal handler in @builder and shows them + * in @window info bar when the callback is activated + */ +void +glade_previewer_connect_function (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer window) +{ + SignalData *data; + HandlerData *hd; + guint signal_id; + gchar *key; - if (toplevel) + g_return_if_fail (GLADE_IS_PREVIEWER (window)); + + if (!(signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object)))) + return; + + key = g_strconcat ("glade-signal-data-", signal_name, NULL); + data = g_object_get_data (object, key); + + if (!data) { - glade_preview_window_set_widget (app->window, GTK_WIDGET (toplevel)); - g_object_unref (toplevel); - gtk_widget_show (GTK_WIDGET (toplevel)); - - if (screenshot_file_name) - glade_preview_window_screenshot (app->window, TRUE, screenshot_file_name); - else - gtk_main (); + data = g_new0 (SignalData, 1); + + data->window = g_object_ref (window); + g_signal_query (signal_id, &data->query); + data->object = g_object_ref (object); + + g_signal_connect_data (object, signal_name, + G_CALLBACK (on_handler_called), + data, signal_data_free, G_CONNECT_SWAPPED); + + g_object_set_data (object, key, data); } - /* free unused resources */ - g_free (file_name); - g_free (toplevel_name); - g_free (css_file_name); - g_free (screenshot_file_name); - glade_previewer_free (app); + hd = g_new0 (HandlerData, 1); + hd->handler_name = g_strdup (handler_name); + hd->connect_object = connect_object ? g_object_ref (connect_object) : NULL; + hd->flags = flags; + + data->handlers = g_list_append (data->handlers, hd); - return 0; + g_free (key); } diff --git a/gladeui/glade-previewer.h b/gladeui/glade-previewer.h new file mode 100644 index 00000000..3284c190 --- /dev/null +++ b/gladeui/glade-previewer.h @@ -0,0 +1,97 @@ +/* + * glade-previewer.h + * + * Copyright (C) 2013-2016 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte <juanpablougarte@gmail.com> + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + * + */ + +#ifndef _GLADE_PREVIEWER_H_ +#define _GLADE_PREVIEWER_H_ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GLADE_TYPE_PREVIEWER (glade_previewer_get_type ()) +#define GLADE_PREVIEWER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PREVIEWER, GladePreviewer)) +#define GLADE_PREVIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PREVIEWER, GladePreviewerClass)) +#define GLADE_IS_PREVIEWER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PREVIEWER)) +#define GLADE_IS_PREVIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PREVIEWER)) +#define GLADE_PREVIEWER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PREVIEWER, GladePreviewerClass)) + +typedef struct _GladePreviewerClass GladePreviewerClass; +typedef struct _GladePreviewer GladePreviewer; +typedef struct _GladePreviewerPrivate GladePreviewerPrivate; + + +struct _GladePreviewerClass +{ + GObjectClass parent_class; +}; + +struct _GladePreviewer +{ + GObject parent_instance; + + GladePreviewerPrivate *priv; +}; + +GType glade_previewer_get_type (void) G_GNUC_CONST; + +GObject *glade_previewer_new (void); + +void glade_previewer_set_widget (GladePreviewer *preview, + GtkWidget *widget); + +void glade_previewer_present (GladePreviewer *preview); + +void glade_previewer_set_print_handlers (GladePreviewer *preview, + gboolean print); + +void glade_previewer_set_message (GladePreviewer *preview, + GtkMessageType type, + const gchar *message); + +void glade_previewer_set_css_file (GladePreviewer *preview, + const gchar *css_file); + +void glade_previewer_set_screenshot_extension (GladePreviewer *preview, + const gchar *extension); + +void glade_previewer_screenshot (GladePreviewer *preview, + gboolean wait, + const gchar *filename); + +void glade_previewer_set_slideshow_widgets (GladePreviewer *preview, + GSList *objects); + +void glade_previewer_slideshow_save (GladePreviewer *preview, + const gchar *filename); + +void glade_previewer_connect_function (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer window); + +G_END_DECLS + +#endif /* _GLADE_PREVIEWER_H_ */ |