diff options
Diffstat (limited to 'cut-n-paste')
38 files changed, 15693 insertions, 0 deletions
diff --git a/cut-n-paste/Makefile.am b/cut-n-paste/Makefile.am new file mode 100644 index 00000000..b53b9b9e --- /dev/null +++ b/cut-n-paste/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = zoom-control toolbar-editor totem-screensaver smclient gimpcellrenderertoggle synctex + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/gimpcellrenderertoggle/Makefile.am b/cut-n-paste/gimpcellrenderertoggle/Makefile.am new file mode 100644 index 00000000..fffeffa7 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/Makefile.am @@ -0,0 +1,38 @@ +noinst_LTLIBRARIES = libgimpcellrenderertoggle.la + +libgimpcellrenderertoggle_la_sources = \ + gimpcellrenderertoggle.h \ + gimpcellrenderertoggle.c + +libgimpcellrenderertoggle_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +libgimpcellrenderertoggle_la_built_sources = \ + gimpwidgetsmarshal.h \ + gimpwidgetsmarshal.c + +libgimpcellrenderertoggle_la_SOURCES = \ + $(libgimpcellrenderertoggle_la_built_sources) \ + $(libgimpcellrenderertoggle_la_sources) + +libgimpcellrenderertoggle_la_extra_sources = gimpwidgetsmarshal.list + +gimpwidgetsmarshal.h: $(srcdir)/gimpwidgetsmarshal.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header >> xgen-wmh \ + && (cmp -s xgen-wmh $(@F) || cp xgen-wmh $(@F)) \ + && rm -f xgen-wmh xgen-wmh~ + +gimpwidgetsmarshal.c: gimpwidgetsmarshal.h + $(AM_V_GEN)echo "#include \"gimpwidgetsmarshal.h\"" >> xgen-wmc \ + && $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --body >> xgen-wmc \ + && cp xgen-wmc $(@F) \ + && rm -f xgen-wmc xgen-wmc~ + +gen_sources = xgen-wmh xgen-wmc $(libgimpcellrenderertoggle_la_built_sources) +CLEANFILES = $(gen_sources) + +EXTRA_DIST = $(libgimpcellrenderertoggle_la_extra_sources) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c new file mode 100644 index 00000000..e9a25f15 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c @@ -0,0 +1,492 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.c + * Copyright (C) 2003-2004 Sven Neumann <[email protected]> + * + * 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 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <config.h> + +#include "gimpwidgetsmarshal.h" +#include "gimpcellrenderertoggle.h" + + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_BUTTON + + +enum +{ + CLICKED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_STOCK_ID, + PROP_STOCK_SIZE +}; + + +static void gimp_cell_renderer_toggle_finalize (GObject *object); +static void gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); +static gboolean gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); +static void gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpCellRendererToggle, gimp_cell_renderer_toggle, + GTK_TYPE_CELL_RENDERER_TOGGLE) + +#define parent_class gimp_cell_renderer_toggle_parent_class + +static guint toggle_cell_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_cell_renderer_toggle_class_init (GimpCellRendererToggleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + toggle_cell_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererToggleClass, clicked), + NULL, NULL, + _gimp_widgets_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + object_class->finalize = gimp_cell_renderer_toggle_finalize; + object_class->get_property = gimp_cell_renderer_toggle_get_property; + object_class->set_property = gimp_cell_renderer_toggle_set_property; + + cell_class->get_size = gimp_cell_renderer_toggle_get_size; + cell_class->render = gimp_cell_renderer_toggle_render; + cell_class->activate = gimp_cell_renderer_toggle_activate; + + g_object_class_install_property (object_class, + PROP_STOCK_ID, + g_param_spec_string ("stock-id", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STOCK_SIZE, + g_param_spec_int ("stock-size", + NULL, NULL, + 0, G_MAXINT, + DEFAULT_ICON_SIZE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cell_renderer_toggle_init (GimpCellRendererToggle *toggle) +{ +} + +static void +gimp_cell_renderer_toggle_finalize (GObject *object) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + if (toggle->stock_id) + { + g_free (toggle->stock_id); + toggle->stock_id = NULL; + } + + if (toggle->pixbuf) + { + g_object_unref (toggle->pixbuf); + toggle->pixbuf = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + switch (param_id) + { + case PROP_STOCK_ID: + g_value_set_string (value, toggle->stock_id); + break; + case PROP_STOCK_SIZE: + g_value_set_int (value, toggle->stock_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + switch (param_id) + { + case PROP_STOCK_ID: + if (toggle->stock_id) + g_free (toggle->stock_id); + toggle->stock_id = g_value_dup_string (value); + break; + case PROP_STOCK_SIZE: + toggle->stock_size = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + if (toggle->pixbuf) + { + g_object_unref (toggle->pixbuf); + toggle->pixbuf = NULL; + } +} + +static void +gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + gint calc_width; + gint calc_height; + gint pixbuf_width; + gint pixbuf_height; + gfloat xalign; + gfloat yalign; + gint xpad; + gint ypad; + + if (! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->get_size (cell, + widget, + cell_area, + x_offset, y_offset, + width, height); + return; + } + + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (! toggle->pixbuf) + gimp_cell_renderer_toggle_create_pixbuf (toggle, widget); + + pixbuf_width = gdk_pixbuf_get_width (toggle->pixbuf); + pixbuf_height = gdk_pixbuf_get_height (toggle->pixbuf); + + calc_width = (pixbuf_width + + (gint) xpad * 2 + style->xthickness * 2); + calc_height = (pixbuf_height + + (gint) ypad * 2 + style->ythickness * 2); + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + (1.0 - xalign) : xalign) * + (cell_area->width - calc_width)); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + +static void +gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + GdkRectangle toggle_rect; + GdkRectangle draw_rect; + GtkStateType state; + gboolean active; + gint xpad; + gint ypad; + + if (! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->render (cell, window, widget, + background_area, + cell_area, expose_area, + flags); + return; + } + + gimp_cell_renderer_toggle_get_size (cell, widget, cell_area, + &toggle_rect.x, + &toggle_rect.y, + &toggle_rect.width, + &toggle_rect.height); + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + toggle_rect.x += cell_area->x + xpad; + toggle_rect.y += cell_area->y + ypad; + toggle_rect.width -= xpad * 2; + toggle_rect.height -= ypad * 2; + + if (toggle_rect.width <= 0 || toggle_rect.height <= 0) + return; + + active = + gtk_cell_renderer_toggle_get_active (GTK_CELL_RENDERER_TOGGLE (cell)); + + if (!gtk_cell_renderer_get_sensitive (cell)) + { + state = GTK_STATE_INSENSITIVE; + } + else if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) + { + if (gtk_widget_has_focus (widget)) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_ACTIVE; + } + else + { + if (gtk_cell_renderer_toggle_get_activatable (GTK_CELL_RENDERER_TOGGLE (cell))) + state = GTK_STATE_NORMAL; + else + state = GTK_STATE_INSENSITIVE; + } + + if (gdk_rectangle_intersect (expose_area, cell_area, &draw_rect) && + (flags & GTK_CELL_RENDERER_PRELIT)) + gtk_paint_shadow (style, + window, + state, + active ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + &draw_rect, + widget, NULL, + toggle_rect.x, toggle_rect.y, + toggle_rect.width, toggle_rect.height); + + if (active) + { + GdkPixbuf *insensitive = NULL; + GdkPixbuf *pixbuf = toggle->pixbuf; + + toggle_rect.x += style->xthickness; + toggle_rect.y += style->ythickness; + toggle_rect.width -= style->xthickness * 2; + toggle_rect.height -= style->ythickness * 2; + + if (state == GTK_STATE_INSENSITIVE) + { + GtkIconSource *source; + + source = gtk_icon_source_new (); + gtk_icon_source_set_pixbuf (source, pixbuf); + /* The size here is arbitrary; since size isn't + * wildcarded in the source, it isn't supposed to be + * scaled by the engine function + */ + gtk_icon_source_set_size (source, GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_icon_source_set_size_wildcarded (source, FALSE); + + insensitive = gtk_style_render_icon (gtk_widget_get_style (widget), + source, + gtk_widget_get_direction (widget), + GTK_STATE_INSENSITIVE, + /* arbitrary */ + (GtkIconSize)-1, + widget, + "gimpcellrenderertoggle"); + + gtk_icon_source_free (source); + + pixbuf = insensitive; + } + + if (gdk_rectangle_intersect (&draw_rect, &toggle_rect, &draw_rect)) + { + cairo_t *cr; + + cr = gdk_cairo_create (window); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, toggle_rect.x, toggle_rect.y); + gdk_cairo_rectangle (cr, &draw_rect); + cairo_fill (cr); + + cairo_destroy (cr); + } + + if (insensitive) + g_object_unref (insensitive); + } +} + +static gboolean +gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererToggle *toggle = GTK_CELL_RENDERER_TOGGLE (cell); + + if (gtk_cell_renderer_toggle_get_activatable (toggle)) + { + GdkModifierType state = 0; + + GTK_CELL_RENDERER_CLASS (parent_class)->activate (cell, event, widget, + path, background_area, + cell_area, flags); + + if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS) + state = ((GdkEventButton *) event)->state; + + gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (cell), + path, state); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget) +{ + if (toggle->pixbuf) + g_object_unref (toggle->pixbuf); + + toggle->pixbuf = gtk_widget_render_icon (widget, + toggle->stock_id, + toggle->stock_size, NULL); +} + + +/** + * gimp_cell_renderer_toggle_new: + * @stock_id: the stock_id of the icon to use for the active state + * + * Creates a custom version of the #GtkCellRendererToggle. Instead of + * showing the standard toggle button, it shows a stock icon if the + * cell is active and no icon otherwise. This cell renderer is for + * example used in the Layers treeview to indicate and control the + * layer's visibility by showing %GIMP_STOCK_VISIBLE. + * + * Return value: a new #GimpCellRendererToggle + * + * Since: GIMP 2.2 + **/ +GtkCellRenderer * +gimp_cell_renderer_toggle_new (const gchar *stock_id) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_TOGGLE, + "stock_id", stock_id, + NULL); +} + +/** + * gimp_cell_renderer_toggle_clicked: + * @cell: a #GimpCellRendererToggle + * @path: + * @state: + * + * Emits the "clicked" signal from a #GimpCellRendererToggle. + * + * Since: GIMP 2.2 + **/ +void +gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell)); + g_return_if_fail (path != NULL); + + g_signal_emit (cell, toggle_cell_signals[CLICKED], 0, path, state); +} diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h new file mode 100644 index 00000000..6d516d73 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h @@ -0,0 +1,77 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.h + * Copyright (C) 2003-2004 Sven Neumann <[email protected]> + * + * 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 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GIMP_CELL_RENDERER_TOGGLE_H__ +#define __GIMP_CELL_RENDERER_TOGGLE_H__ + +#include <gtk/gtk.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GIMP_TYPE_CELL_RENDERER_TOGGLE (gimp_cell_renderer_toggle_get_type ()) +#define GIMP_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggle)) +#define GIMP_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) +#define GIMP_IS_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_IS_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_CELL_RENDERER_TOGGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) + +typedef struct _GimpCellRendererToggle GimpCellRendererToggle; +typedef struct _GimpCellRendererToggleClass GimpCellRendererToggleClass; + +struct _GimpCellRendererToggle +{ + GtkCellRendererToggle parent_instance; + + gchar *stock_id; + GtkIconSize stock_size; + GdkPixbuf *pixbuf; +}; + +struct _GimpCellRendererToggleClass +{ + GtkCellRendererToggleClass parent_class; + + void (* clicked) (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_cell_renderer_toggle_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_toggle_new (const gchar *stock_id); + +void gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + +G_END_DECLS + +#endif /* __GIMP_CELL_RENDERER_TOGGLE_H__ */ diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list b/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list new file mode 100644 index 00000000..f32f0f95 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list @@ -0,0 +1,26 @@ +# see glib-genmarshal(1) for a detailed description of the file format, +# possible parameter types are: +# VOID indicates no return type, or no extra +# parameters. if VOID is used as the parameter +# list, no additional parameters may be present. +# BOOLEAN for boolean types (gboolean) +# CHAR for signed char types (gchar) +# UCHAR for unsigned char types (guchar) +# INT for signed integer types (gint) +# UINT for unsigned integer types (guint) +# LONG for signed long integer types (glong) +# ULONG for unsigned long integer types (gulong) +# ENUM for enumeration types (gint) +# FLAGS for flag enumeration types (guint) +# FLOAT for single-precision float types (gfloat) +# DOUBLE for double-precision float types (gdouble) +# STRING for string types (gchar*) +# BOXED for boxed (anonymous but reference counted) types (GBoxed*) +# POINTER for anonymous pointer types (gpointer) +# PARAM for GParamSpec or derived types (GParamSpec*) +# OBJECT for GObject or derived types (GObject*) +# NONE deprecated alias for VOID +# BOOL deprecated alias for BOOLEAN + +VOID: STRING, FLAGS + diff --git a/cut-n-paste/smclient/Makefile.am b/cut-n-paste/smclient/Makefile.am new file mode 100644 index 00000000..498ed756 --- /dev/null +++ b/cut-n-paste/smclient/Makefile.am @@ -0,0 +1,42 @@ +noinst_LTLIBRARIES = libsmclient.la + +NULL = + +if WITH_SMCLIENT +libsmclient_la_SOURCES = \ + eggsmclient.c \ + eggsmclient.h \ + eggsmclient-private.h \ + $(NULL) + +libsmclient_la_CPPFLAGS = \ + -DG_LOG_DOMAIN=\""EggSMClient"\" \ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +libsmclient_la_CFLAGS = \ + $(SMCLIENT_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(AM_CFLAGS) + +libsmclient_la_LIBADD = \ + $(SMCLIENT_LIBS) + +if WITH_SMCLIENT_XSMP +libsmclient_la_SOURCES += \ + eggdesktopfile.c \ + eggdesktopfile.h \ + eggsmclient-xsmp.c \ + $(NULL) +libsmclient_la_CPPFLAGS += -DEGG_SM_CLIENT_BACKEND_XSMP +endif +if WITH_SMCLIENT_WIN32 +libsmclient_la_SOURCES += eggsmclient-win32.c +endif +if WITH_SMCLIENT_QUARTZ +libsmclient_la_SOURCES += eggsmclient-osx.c +endif + +endif # WITH_SMCLIENT + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/smclient/eggdesktopfile.c b/cut-n-paste/smclient/eggdesktopfile.c new file mode 100644 index 00000000..1f432adc --- /dev/null +++ b/cut-n-paste/smclient/eggdesktopfile.c @@ -0,0 +1,1477 @@ +/* eggdesktopfile.c - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * Based on mate-desktop-item.c + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 George Lebl + * + * 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 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 library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eggdesktopfile.h" + +#include <string.h> +#include <unistd.h> + +#include <glib/gi18n.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +struct EggDesktopFile { + GKeyFile *key_file; + char *source; + + char *name, *icon; + EggDesktopFileType type; + char document_code; +}; + +/** + * egg_desktop_file_new: + * @desktop_file_path: path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Creates a new #EggDesktopFile for @desktop_file. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new (const char *desktop_file_path, GError **error) +{ + GKeyFile *key_file; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + return egg_desktop_file_new_from_key_file (key_file, desktop_file_path, + error); +} + +/** + * egg_desktop_file_new_from_data_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @search_dirs: NULL-terminated array of directories to search + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_key_file: + * @key_file: a #GKeyFile representing a desktop file + * @source: the path or URI that @key_file was loaded from, or %NULL + * @error: error pointer + * + * Creates a new #EggDesktopFile for @key_file. Assumes ownership of + * @key_file (on success or failure); you should consider @key_file to + * be freed after calling this function. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error) +{ + EggDesktopFile *desktop_file; + char *version, *type; + + if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP)) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("File is not a valid .desktop file")); + g_key_file_free (key_file); + return NULL; + } + + version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_VERSION, + NULL); + if (version) + { + double version_num; + char *end; + + version_num = g_ascii_strtod (version, &end); + if (*end) + { + g_warning ("Invalid Version string '%s' in %s", + version, source ? source : "(unknown)"); + } + else if (version_num > 1.0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("Unrecognized desktop file Version '%s'"), version); + g_free (version); + g_key_file_free (key_file); + return NULL; + } + g_free (version); + } + + desktop_file = g_new0 (EggDesktopFile, 1); + desktop_file->key_file = key_file; + + if (g_path_is_absolute (source)) + desktop_file->source = g_filename_to_uri (source, NULL, NULL); + else + desktop_file->source = g_strdup (source); + + desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, error); + if (!desktop_file->name) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, error); + if (!type) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + if (!strcmp (type, "Application")) + { + char *exec, *p; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION; + + exec = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + + /* See if it takes paths or URIs or neither */ + for (p = exec; *p; p++) + { + if (*p == '%') + { + if (p[1] == '\0' || strchr ("FfUu", p[1])) + { + desktop_file->document_code = p[1]; + break; + } + p++; + } + } + + g_free (exec); + } + else if (!strcmp (type, "Link")) + { + char *url; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK; + + url = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + g_free (url); + } + else if (!strcmp (type, "Directory")) + desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY; + else + desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED; + + g_free (type); + + /* Check the Icon key */ + desktop_file->icon = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ICON, + NULL); + if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon)) + { + char *ext; + + /* Lots of .desktop files still get this wrong */ + ext = strrchr (desktop_file->icon, '.'); + if (ext && (!strcmp (ext, ".png") || + !strcmp (ext, ".xpm") || + !strcmp (ext, ".svg"))) + { + g_warning ("Desktop file '%s' has malformed Icon key '%s'" + "(should not include extension)", + source ? source : "(unknown)", + desktop_file->icon); + *ext = '\0'; + } + } + + return desktop_file; +} + +/** + * egg_desktop_file_free: + * @desktop_file: an #EggDesktopFile + * + * Frees @desktop_file. + **/ +void +egg_desktop_file_free (EggDesktopFile *desktop_file) +{ + g_key_file_free (desktop_file->key_file); + g_free (desktop_file->source); + g_free (desktop_file->name); + g_free (desktop_file->icon); + g_free (desktop_file); +} + +/** + * egg_desktop_file_get_source: + * @desktop_file: an #EggDesktopFile + * + * Gets the URI that @desktop_file was loaded from. + * + * Return value: @desktop_file's source URI + **/ +const char * +egg_desktop_file_get_source (EggDesktopFile *desktop_file) +{ + return desktop_file->source; +} + +/** + * egg_desktop_file_get_desktop_file_type: + * @desktop_file: an #EggDesktopFile + * + * Gets the desktop file type of @desktop_file. + * + * Return value: @desktop_file's type + **/ +EggDesktopFileType +egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) +{ + return desktop_file->type; +} + +/** + * egg_desktop_file_get_name: + * @desktop_file: an #EggDesktopFile + * + * Gets the (localized) value of @desktop_file's "Name" key. + * + * Return value: the application/link name + **/ +const char * +egg_desktop_file_get_name (EggDesktopFile *desktop_file) +{ + return desktop_file->name; +} + +/** + * egg_desktop_file_get_icon: + * @desktop_file: an #EggDesktopFile + * + * Gets the value of @desktop_file's "Icon" key. + * + * If the icon string is a full path (that is, if g_path_is_absolute() + * returns %TRUE when called on it), it points to a file containing an + * unthemed icon. If the icon string is not a full path, it is the + * name of a themed icon, which can be looked up with %GtkIconTheme, + * or passed directly to a theme-aware widget like %GtkImage or + * %GtkCellRendererPixbuf. + * + * Return value: the icon path or name + **/ +const char * +egg_desktop_file_get_icon (EggDesktopFile *desktop_file) +{ + return desktop_file->icon; +} + +gboolean +egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) +{ + return g_key_file_get_locale_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, locale, + error); +} + +gboolean +egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +double +egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_double (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char ** +egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) +{ + return g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, length, + error); +} + +char ** +egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) +{ + return g_key_file_get_locale_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + locale, length, + error); +} + +/** + * egg_desktop_file_can_launch: + * @desktop_file: an #EggDesktopFile + * @desktop_environment: the name of the running desktop environment, + * or %NULL + * + * Tests if @desktop_file can/should be launched in the current + * environment. If @desktop_environment is non-%NULL, @desktop_file's + * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that + * this desktop_file is appropriate for the named environment. + * + * Furthermore, if @desktop_file has type + * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is + * also checked, to make sure the binary it points to exists. + * + * egg_desktop_file_can_launch() does NOT check the value of the + * "Hidden" key. + * + * Return value: %TRUE if @desktop_file can be launched + **/ +gboolean +egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment) +{ + char *try_exec, *found_program; + char **only_show_in, **not_show_in; + gboolean found; + int i; + + if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION && + desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK) + return FALSE; + + if (desktop_environment) + { + only_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN, + NULL, NULL); + if (only_show_in) + { + for (i = 0, found = FALSE; only_show_in[i] && !found; i++) + { + if (!strcmp (only_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (only_show_in); + + if (!found) + return FALSE; + } + + not_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN, + NULL, NULL); + if (not_show_in) + { + for (i = 0, found = FALSE; not_show_in[i] && !found; i++) + { + if (!strcmp (not_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (not_show_in); + + if (found) + return FALSE; + } + } + + if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION) + { + try_exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TRY_EXEC, + NULL); + if (try_exec) + { + found_program = g_find_program_in_path (try_exec); + g_free (try_exec); + + if (!found_program) + return FALSE; + g_free (found_program); + } + } + + return TRUE; +} + +/** + * egg_desktop_file_accepts_documents: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file represents an application that can accept + * documents on the command line. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file) +{ + return desktop_file->document_code != 0; +} + +/** + * egg_desktop_file_accepts_multiple: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept multiple documents at once. + * + * If this returns %FALSE, you can still pass multiple documents to + * egg_desktop_file_launch(), but that will result in multiple copies + * of the application being launched. See egg_desktop_file_launch() + * for more details. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'F' || + desktop_file->document_code == 'U'); +} + +/** + * egg_desktop_file_accepts_uris: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept (non-"file:") URIs as documents to + * open. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'U' || + desktop_file->document_code == 'u'); +} + +static void +append_quoted_word (GString *str, + const char *s, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + const char *p; + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "\"'"); + + if (!strchr (s, '\'')) + g_string_append (str, s); + else + { + for (p = s; *p != '\0'; p++) + { + if (*p == '\'') + g_string_append (str, "'\\''"); + else + g_string_append_c (str, *p); + } + } + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "'\""); +} + +static void +do_percent_subst (EggDesktopFile *desktop_file, + char code, + GString *str, + GSList **documents, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + GSList *d; + char *doc; + + switch (code) + { + case '%': + g_string_append_c (str, '%'); + break; + + case 'F': + case 'U': + for (d = *documents; d; d = d->next) + { + doc = d->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + } + *documents = NULL; + break; + + case 'f': + case 'u': + if (*documents) + { + doc = (*documents)->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + *documents = (*documents)->next; + } + break; + + case 'i': + if (desktop_file->icon) + { + g_string_append (str, "--icon "); + append_quoted_word (str, desktop_file->icon, + in_single_quotes, in_double_quotes); + } + break; + + case 'c': + if (desktop_file->name) + { + append_quoted_word (str, desktop_file->name, + in_single_quotes, in_double_quotes); + } + break; + + case 'k': + if (desktop_file->source) + { + append_quoted_word (str, desktop_file->source, + in_single_quotes, in_double_quotes); + } + break; + + case 'D': + case 'N': + case 'd': + case 'n': + case 'v': + case 'm': + /* Deprecated; skip */ + break; + + default: + g_warning ("Unrecognized %%-code '%%%c' in Exec", code); + break; + } +} + +static char * +parse_exec (EggDesktopFile *desktop_file, + GSList **documents, + GError **error) +{ + char *exec, *p, *command; + gboolean escape, single_quot, double_quot; + GString *gs; + + exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + return NULL; + + /* Build the command */ + gs = g_string_new (NULL); + escape = single_quot = double_quot = FALSE; + + for (p = exec; *p != '\0'; p++) + { + if (escape) + { + escape = FALSE; + g_string_append_c (gs, *p); + } + else if (*p == '\\') + { + if (!single_quot) + escape = TRUE; + g_string_append_c (gs, *p); + } + else if (*p == '\'') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + single_quot = TRUE; + else if (single_quot) + single_quot = FALSE; + } + else if (*p == '"') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + double_quot = TRUE; + else if (double_quot) + double_quot = FALSE; + } + else if (*p == '%' && p[1]) + { + do_percent_subst (desktop_file, p[1], gs, documents, + single_quot, double_quot); + p++; + } + else + g_string_append_c (gs, *p); + } + + g_free (exec); + command = g_string_free (gs, FALSE); + + /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */ + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + NULL)) + { + GError *terminal_error = NULL; + gboolean use_terminal = + g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + &terminal_error); + if (terminal_error) + { + g_free (command); + g_propagate_error (error, terminal_error); + return NULL; + } + + if (use_terminal) + { + gs = g_string_new ("xdg-terminal "); + append_quoted_word (gs, command, FALSE, FALSE); + g_free (command); + command = g_string_free (gs, FALSE); + } + } + + return command; +} + +static GSList * +translate_document_list (EggDesktopFile *desktop_file, GSList *documents) +{ + gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file); + GSList *ret, *d; + + for (d = documents, ret = NULL; d; d = d->next) + { + const char *document = d->data; + gboolean is_uri = !g_path_is_absolute (document); + char *translated; + + if (accepts_uris) + { + if (is_uri) + translated = g_strdup (document); + else + translated = g_filename_to_uri (document, NULL, NULL); + } + else + { + if (is_uri) + translated = g_filename_from_uri (document, NULL, NULL); + else + translated = g_strdup (document); + } + + if (translated) + ret = g_slist_prepend (ret, translated); + } + + return g_slist_reverse (ret); +} + +static void +free_document_list (GSList *documents) +{ + GSList *d; + + for (d = documents; d; d = d->next) + g_free (d->data); + g_slist_free (documents); +} + +/** + * egg_desktop_file_parse_exec: + * @desktop_file: a #EggDesktopFile + * @documents: a list of document paths or URIs + * @error: error pointer + * + * Parses @desktop_file's Exec key, inserting @documents into it, and + * returns the result. + * + * If @documents contains non-file: URIs and @desktop_file does not + * accept URIs, those URIs will be ignored. Likewise, if @documents + * contains more elements than @desktop_file accepts, the extra + * documents will be ignored. + * + * Return value: the parsed Exec string + **/ +char * +egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error) +{ + GSList *translated, *docs; + char *command; + + docs = translated = translate_document_list (desktop_file, documents); + command = parse_exec (desktop_file, &docs, error); + free_document_list (translated); + + return command; +} + +static gboolean +parse_link (EggDesktopFile *desktop_file, + EggDesktopFile **app_desktop_file, + GSList **documents, + GError **error) +{ + char *url; + GKeyFile *key_file; + + url = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + return FALSE; + *documents = g_slist_prepend (NULL, url); + + /* FIXME: use gvfs */ + key_file = g_key_file_new (); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, + "xdg-open"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, + "Application"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + "xdg-open %u"); + *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL); + return TRUE; +} + +#if GTK_CHECK_VERSION (2, 12, 0) +static char * +start_startup_notification (GdkDisplay *display, + EggDesktopFile *desktop_file, + const char *argv0, + int screen, + int workspace, + guint32 launch_time) +{ + static int sequence = 0; + char *startup_id; + char *description, *wmclass; + char *screen_str, *workspace_str; + + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + { + if (!g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + return NULL; + wmclass = NULL; + } + else + { + wmclass = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS, + NULL); + if (!wmclass) + return NULL; + } + + if (launch_time == (guint32)-1) + launch_time = gdk_x11_display_get_user_time (display); + startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu", + g_get_prgname (), + (unsigned long)getpid (), + g_get_host_name (), + argv0, + sequence++, + (unsigned long)launch_time); + + description = g_strdup_printf (_("Starting %s"), desktop_file->name); + screen_str = g_strdup_printf ("%d", screen); + workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace); + + gdk_x11_display_broadcast_startup_message (display, "new", + "ID", startup_id, + "NAME", desktop_file->name, + "SCREEN", screen_str, + "BIN", argv0, + "ICON", desktop_file->icon, + "DESKTOP", workspace_str, + "DESCRIPTION", description, + "WMCLASS", wmclass, + NULL); + + g_free (description); + g_free (wmclass); + g_free (screen_str); + g_free (workspace_str); + + return startup_id; +} + +static void +end_startup_notification (GdkDisplay *display, + const char *startup_id) +{ + gdk_x11_display_broadcast_startup_message (display, "remove", + "ID", startup_id, + NULL); +} + +#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */) + +typedef struct { + GdkDisplay *display; + char *startup_id; +} StartupNotificationData; + +static gboolean +startup_notification_timeout (gpointer data) +{ + StartupNotificationData *sn_data = data; + + end_startup_notification (sn_data->display, sn_data->startup_id); + g_object_unref (sn_data->display); + g_free (sn_data->startup_id); + g_free (sn_data); + + return FALSE; +} + +static void +set_startup_notification_timeout (GdkDisplay *display, + const char *startup_id) +{ + StartupNotificationData *sn_data; + + sn_data = g_new (StartupNotificationData, 1); + sn_data->display = g_object_ref (display); + sn_data->startup_id = g_strdup (startup_id); + + g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH, + startup_notification_timeout, sn_data); +} +#endif /* GTK 2.12 */ + +static GPtrArray * +array_putenv (GPtrArray *env, char *variable) +{ + guint i, keylen; + + if (!env) + { + char **envp; + + env = g_ptr_array_new (); + + envp = g_listenv (); + for (i = 0; envp[i]; i++) + { + const char *value; + + value = g_getenv (envp[i]); + g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i], + value ? value : "")); + } + g_strfreev (envp); + } + + keylen = strcspn (variable, "="); + + /* Remove old value of key */ + for (i = 0; i < env->len; i++) + { + char *envvar = env->pdata[i]; + + if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=') + { + g_free (envvar); + g_ptr_array_remove_index_fast (env, i); + break; + } + } + + /* Add new value */ + g_ptr_array_add (env, g_strdup (variable)); + + return env; +} + +static gboolean +egg_desktop_file_launchv (EggDesktopFile *desktop_file, + GSList *documents, va_list args, + GError **error) +{ + EggDesktopFileLaunchOption option; + GSList *translated_documents = NULL, *docs = NULL; + char *command, **argv; + int argc, i, screen_num; + gboolean success, current_success; + GdkDisplay *display; + char *startup_id; + + GPtrArray *env = NULL; + char **variables = NULL; + GdkScreen *screen = NULL; + int workspace = -1; + const char *directory = NULL; + guint32 launch_time = (guint32)-1; + GSpawnFlags flags = G_SPAWN_SEARCH_PATH; + GSpawnChildSetupFunc setup_func = NULL; + gpointer setup_data = NULL; + + GPid *ret_pid = NULL; + int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL; + char **ret_startup_id = NULL; + + if (documents && desktop_file->document_code == 0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Application does not accept documents on command line")); + return FALSE; + } + + /* Read the options: technically it's incorrect for the caller to + * NULL-terminate the list of options (rather than 0-terminating + * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED, + * it's more consistent with other glib/gtk methods, and it will + * work as long as sizeof (int) <= sizeof (NULL), and NULL is + * represented as 0. (Which is true everywhere we care about.) + */ + while ((option = va_arg (args, EggDesktopFileLaunchOption))) + { + switch (option) + { + case EGG_DESKTOP_FILE_LAUNCH_CLEARENV: + if (env) + g_ptr_array_free (env, TRUE); + env = g_ptr_array_new (); + break; + case EGG_DESKTOP_FILE_LAUNCH_PUTENV: + variables = va_arg (args, char **); + for (i = 0; variables[i]; i++) + env = array_putenv (env, variables[i]); + break; + + case EGG_DESKTOP_FILE_LAUNCH_SCREEN: + screen = va_arg (args, GdkScreen *); + break; + case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: + workspace = va_arg (args, int); + break; + + case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: + directory = va_arg (args, const char *); + break; + case EGG_DESKTOP_FILE_LAUNCH_TIME: + launch_time = va_arg (args, guint32); + break; + case EGG_DESKTOP_FILE_LAUNCH_FLAGS: + flags |= va_arg (args, GSpawnFlags); + /* Make sure they didn't set any flags that don't make sense. */ + flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO; + break; + case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC: + setup_func = va_arg (args, GSpawnChildSetupFunc); + setup_data = va_arg (args, gpointer); + break; + + case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID: + ret_pid = va_arg (args, GPid *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE: + ret_stdin = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE: + ret_stdout = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE: + ret_stderr = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID: + ret_startup_id = va_arg (args, char **); + break; + + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION, + _("Unrecognized launch option: %d"), + GPOINTER_TO_INT (option)); + success = FALSE; + goto out; + } + } + + if (screen) + { + char *display_name = gdk_screen_make_display_name (screen); + char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); + env = array_putenv (env, display_env); + g_free (display_name); + g_free (display_env); + + display = gdk_screen_get_display (screen); + } + else + { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + screen_num = gdk_screen_get_number (screen); + + translated_documents = translate_document_list (desktop_file, documents); + docs = translated_documents; + + success = FALSE; + + do + { + command = parse_exec (desktop_file, &docs, error); + if (!command) + goto out; + + if (!g_shell_parse_argv (command, &argc, &argv, error)) + { + g_free (command); + goto out; + } + g_free (command); + +#if GTK_CHECK_VERSION (2, 12, 0) + startup_id = start_startup_notification (display, desktop_file, + argv[0], screen_num, + workspace, launch_time); + if (startup_id) + { + char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s", + startup_id); + env = array_putenv (env, startup_id_env); + g_free (startup_id_env); + } +#else + startup_id = NULL; +#endif /* GTK 2.12 */ + + if (env != NULL) + g_ptr_array_add (env, NULL); + + current_success = + g_spawn_async_with_pipes (directory, + argv, + env ? (char **)(env->pdata) : NULL, + flags, + setup_func, setup_data, + ret_pid, + ret_stdin, ret_stdout, ret_stderr, + error); + g_strfreev (argv); + + if (startup_id) + { +#if GTK_CHECK_VERSION (2, 12, 0) + if (current_success) + { + set_startup_notification_timeout (display, startup_id); + + if (ret_startup_id) + *ret_startup_id = startup_id; + else + g_free (startup_id); + } + else +#endif /* GTK 2.12 */ + g_free (startup_id); + } + else if (ret_startup_id) + *ret_startup_id = NULL; + + if (current_success) + { + /* If we successfully launch any instances of the app, make + * sure we return TRUE and don't set @error. + */ + success = TRUE; + error = NULL; + + /* Also, only set the output params on the first one */ + ret_pid = NULL; + ret_stdin = ret_stdout = ret_stderr = NULL; + ret_startup_id = NULL; + } + } + while (docs && current_success); + + out: + if (env) + { + g_strfreev ((char **)env->pdata); + g_ptr_array_free (env, FALSE); + } + free_document_list (translated_documents); + + return success; +} + +/** + * egg_desktop_file_launch: + * @desktop_file: an #EggDesktopFile + * @documents: a list of URIs or paths to documents to open + * @error: error pointer + * @...: additional options + * + * Launches @desktop_file with the given arguments. Additional options + * can be specified as follows: + * + * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments) + * clears the environment in the child process + * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables) + * adds the NAME=VALUE strings in the given %NULL-terminated + * array to the child process's environment + * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen) + * causes the application to be launched on the given screen + * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace) + * causes the application to be launched on the given workspace + * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir) + * causes the application to be launched in the given directory + * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time) + * sets the "launch time" for the application. If the user + * interacts with another window after @launch_time but before + * the launched application creates its first window, the window + * manager may choose to not give focus to the new application. + * Passing 0 for @launch_time will explicitly request that the + * application not receive focus. + * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags) + * Sets additional #GSpawnFlags to use. See g_spawn_async() for + * more details. + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer) + * Sets the child setup callback and the data to pass to it. + * (See g_spawn_async() for more details.) + * + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid) + * On a successful launch, sets *@pid to the PID of the launched + * application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id) + * On a successful launch, sets *@startup_id to the Startup + * Notification "startup id" of the launched application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdin. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdout. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stderr. + * + * The options should be terminated with a single %NULL. + * + * If @documents contains multiple documents, but + * egg_desktop_file_accepts_multiple() returns %FALSE for + * @desktop_file, then egg_desktop_file_launch() will actually launch + * multiple instances of the application. In that case, the return + * value (as well as any values passed via + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the + * first instance of the application that was launched (but the + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each + * instance). + * + * Return value: %TRUE if the application was successfully launched. + **/ +gboolean +egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, GError **error, + ...) +{ + va_list args; + gboolean success; + EggDesktopFile *app_desktop_file; + + switch (desktop_file->type) + { + case EGG_DESKTOP_FILE_TYPE_APPLICATION: + va_start (args, error); + success = egg_desktop_file_launchv (desktop_file, documents, + args, error); + va_end (args); + break; + + case EGG_DESKTOP_FILE_TYPE_LINK: + if (documents) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Can't pass document URIs to a 'Type=Link' desktop entry")); + return FALSE; + } + + if (!parse_link (desktop_file, &app_desktop_file, &documents, error)) + return FALSE; + + va_start (args, error); + success = egg_desktop_file_launchv (app_desktop_file, documents, + args, error); + va_end (args); + + egg_desktop_file_free (app_desktop_file); + free_document_list (documents); + break; + + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Not a launchable item")); + success = FALSE; + break; + } + + return success; +} + + +GQuark +egg_desktop_file_error_quark (void) +{ + return g_quark_from_static_string ("egg-desktop_file-error-quark"); +} + + +G_LOCK_DEFINE_STATIC (egg_desktop_file); +static EggDesktopFile *egg_desktop_file; + +/** + * egg_set_desktop_file: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. This will also call g_set_application_name() + * with the localized application name from the desktop file, and + * gtk_window_set_default_icon_name() or + * gtk_window_set_default_icon_from_file() with the application's + * icon. Other code may use additional information from the desktop + * file. + * + * Note that for thread safety reasons, this function can only + * be called once. + **/ +void +egg_set_desktop_file (const char *desktop_file_path) +{ + GError *error = NULL; + + G_LOCK (egg_desktop_file); + if (egg_desktop_file) + egg_desktop_file_free (egg_desktop_file); + + egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error); + if (error) + { + g_warning ("Could not load desktop file '%s': %s", + desktop_file_path, error->message); + g_error_free (error); + } + + if (egg_desktop_file) { + /* Set localized application name and default window icon */ + if (egg_desktop_file->name) + g_set_application_name (egg_desktop_file->name); + if (egg_desktop_file->icon) + { + if (g_path_is_absolute (egg_desktop_file->icon)) + gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); + else + gtk_window_set_default_icon_name (egg_desktop_file->icon); + } + } + + G_UNLOCK (egg_desktop_file); +} + +/** + * egg_get_desktop_file: + * + * Gets the application's #EggDesktopFile, as set by + * egg_set_desktop_file(). + * + * Return value: the #EggDesktopFile, or %NULL if it hasn't been set. + **/ +EggDesktopFile * +egg_get_desktop_file (void) +{ + EggDesktopFile *retval; + + G_LOCK (egg_desktop_file); + retval = egg_desktop_file; + G_UNLOCK (egg_desktop_file); + + return retval; +} diff --git a/cut-n-paste/smclient/eggdesktopfile.h b/cut-n-paste/smclient/eggdesktopfile.h new file mode 100644 index 00000000..2be36210 --- /dev/null +++ b/cut-n-paste/smclient/eggdesktopfile.h @@ -0,0 +1,159 @@ +/* eggdesktopfile.h - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * 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 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 library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_DESKTOP_FILE_H__ +#define __EGG_DESKTOP_FILE_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +typedef struct EggDesktopFile EggDesktopFile; + +typedef enum { + EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED, + + EGG_DESKTOP_FILE_TYPE_APPLICATION, + EGG_DESKTOP_FILE_TYPE_LINK, + EGG_DESKTOP_FILE_TYPE_DIRECTORY +} EggDesktopFileType; + +EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path, + GError **error); + +EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error); + +void egg_desktop_file_free (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file); + +EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file); +const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file); + +gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment); + +gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file); + +char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error); + +gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, + GError **error, + ...) G_GNUC_NULL_TERMINATED; + +typedef enum { + EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1, + EGG_DESKTOP_FILE_LAUNCH_PUTENV, + EGG_DESKTOP_FILE_LAUNCH_SCREEN, + EGG_DESKTOP_FILE_LAUNCH_WORKSPACE, + EGG_DESKTOP_FILE_LAUNCH_DIRECTORY, + EGG_DESKTOP_FILE_LAUNCH_TIME, + EGG_DESKTOP_FILE_LAUNCH_FLAGS, + EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC, + EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID +} EggDesktopFileLaunchOption; + +/* Standard Keys */ +#define EGG_DESKTOP_FILE_GROUP "Desktop Entry" + +#define EGG_DESKTOP_FILE_KEY_TYPE "Type" +#define EGG_DESKTOP_FILE_KEY_VERSION "Version" +#define EGG_DESKTOP_FILE_KEY_NAME "Name" +#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName" +#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay" +#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment" +#define EGG_DESKTOP_FILE_KEY_ICON "Icon" +#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden" +#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn" +#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn" +#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec" +#define EGG_DESKTOP_FILE_KEY_EXEC "Exec" +#define EGG_DESKTOP_FILE_KEY_PATH "Path" +#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal" +#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType" +#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories" +#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify" +#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass" +#define EGG_DESKTOP_FILE_KEY_URL "URL" + +/* Accessors */ +gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char *egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) G_GNUC_MALLOC; +char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) G_GNUC_MALLOC; +gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error); +double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) G_GNUC_MALLOC; +char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) G_GNUC_MALLOC; + + +/* Errors */ +#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark() + +GQuark egg_desktop_file_error_quark (void); + +typedef enum { + EGG_DESKTOP_FILE_ERROR_INVALID, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION +} EggDesktopFileError; + +/* Global application desktop file */ +void egg_set_desktop_file (const char *desktop_file_path); +EggDesktopFile *egg_get_desktop_file (void); + + +G_END_DECLS + +#endif /* __EGG_DESKTOP_FILE_H__ */ diff --git a/cut-n-paste/smclient/eggsmclient-osx.c b/cut-n-paste/smclient/eggsmclient-osx.c new file mode 100644 index 00000000..5428c09f --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-osx.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* EggSMClientOSX + * + * For details on the OS X logout process, see: + * http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/BootProcess.html#//apple_ref/doc/uid/20002130-114618 + * + * EggSMClientOSX registers for the kAEQuitApplication AppleEvent; the + * handler we register (quit_requested()) will be invoked from inside + * the quartz event-handling code (specifically, from inside + * [NSApplication nextEventMatchingMask]) when an AppleEvent arrives. + * We use AESuspendTheCurrentEvent() and AEResumeTheCurrentEvent() to + * allow asynchronous / non-main-loop-reentering processing of the + * quit request. (These are part of the Carbon framework; it doesn't + * seem to be possible to handle AppleEvents asynchronously from + * Cocoa.) + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include <gdk/gdkquartz.h> +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> + +#define EGG_TYPE_SM_CLIENT_OSX (egg_sm_client_osx_get_type ()) +#define EGG_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSX)) +#define EGG_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) +#define EGG_IS_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_IS_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_SM_CLIENT_OSX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) + +typedef struct _EggSMClientOSX EggSMClientOSX; +typedef struct _EggSMClientOSXClass EggSMClientOSXClass; + +struct _EggSMClientOSX { + EggSMClient parent; + + AppleEvent quit_event, quit_reply; + gboolean quit_requested, quitting; +}; + +struct _EggSMClientOSXClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_osx_startup (EggSMClient *client, + const char *client_id); +static void sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static pascal OSErr quit_requested (const AppleEvent *, AppleEvent *, long); + +G_DEFINE_TYPE (EggSMClientOSX, egg_sm_client_osx, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_osx_init (EggSMClientOSX *osx) +{ + ; +} + +static void +egg_sm_client_osx_class_init (EggSMClientOSXClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_osx_startup; + sm_client_class->will_quit = sm_client_osx_will_quit; + sm_client_class->end_session = sm_client_osx_end_session; +} + +EggSMClient * +egg_sm_client_osx_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_OSX, NULL); +} + +static void +sm_client_osx_startup (EggSMClient *client, + const char *client_id) +{ + AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, + NewAEEventHandlerUPP (quit_requested), + (long)GPOINTER_TO_SIZE (client), false); +} + +static gboolean +idle_quit_requested (gpointer client) +{ + egg_sm_client_quit_requested (client); + return FALSE; +} + +static pascal OSErr +quit_requested (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClient *client = GSIZE_TO_POINTER ((gsize)refcon); + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + g_return_val_if_fail (!osx->quit_requested, userCanceledErr); + + /* FIXME AEInteractWithUser? */ + + osx->quit_requested = TRUE; + AEDuplicateDesc (aevt, &osx->quit_event); + AEDuplicateDesc (reply, &osx->quit_reply); + AESuspendTheCurrentEvent (aevt); + + /* Don't emit the "quit_requested" signal immediately, since we're + * called from a weird point in the guts of gdkeventloop-quartz.c + */ + g_idle_add (idle_quit_requested, client); + return noErr; +} + +static pascal OSErr +quit_requested_resumed (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + osx->quit_requested = FALSE; + return osx->quitting ? noErr : userCanceledErr; +} + +static gboolean +idle_will_quit (gpointer client) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + /* Resume the event with a new handler that will return a value to + * the system. + */ + AEResumeTheCurrentEvent (&osx->quit_event, &osx->quit_reply, + NewAEEventHandlerUPP (quit_requested_resumed), + (long)GPOINTER_TO_SIZE (client)); + AEDisposeDesc (&osx->quit_event); + AEDisposeDesc (&osx->quit_reply); + + if (osx->quitting) + egg_sm_client_quit (client); + return FALSE; +} + +static void +sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + g_return_if_fail (osx->quit_requested); + + osx->quitting = will_quit; + + /* Finish in an idle handler since the caller might have called + * egg_sm_client_will_quit() from inside the "quit_requested" signal + * handler, but may not expect the "quit" signal to arrive during + * the _will_quit() call. + */ + g_idle_add (idle_will_quit, client); +} + +static gboolean +sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + static const ProcessSerialNumber loginwindow_psn = { 0, kSystemProcess }; + AppleEvent event = { typeNull, NULL }, reply = { typeNull, NULL }; + AEAddressDesc target; + AEEventID id; + OSErr err; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + id = request_confirmation ? kAELogOut : kAEReallyLogOut; + break; + case EGG_SM_CLIENT_REBOOT: + id = request_confirmation ? kAEShowRestartDialog : kAERestart; + break; + case EGG_SM_CLIENT_SHUTDOWN: + id = request_confirmation ? kAEShowShutdownDialog : kAEShutDown; + break; + } + + err = AECreateDesc (typeProcessSerialNumber, &loginwindow_psn, + sizeof (loginwindow_psn), &target); + if (err != noErr) + { + g_warning ("Could not create descriptor for loginwindow: %d", err); + return FALSE; + } + + err = AECreateAppleEvent (kCoreEventClass, id, &target, + kAutoGenerateReturnID, kAnyTransactionID, + &event); + AEDisposeDesc (&target); + if (err != noErr) + { + g_warning ("Could not create logout AppleEvent: %d", err); + return FALSE; + } + + err = AESend (&event, &reply, kAENoReply, kAENormalPriority, + kAEDefaultTimeout, NULL, NULL); + AEDisposeDesc (&event); + if (err == noErr) + AEDisposeDesc (&reply); + + return err == noErr; +} diff --git a/cut-n-paste/smclient/eggsmclient-private.h b/cut-n-paste/smclient/eggsmclient-private.h new file mode 100644 index 00000000..fc3a0a2d --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-private.h @@ -0,0 +1,53 @@ +/* eggsmclient-private.h + * Copyright (C) 2007 Novell, Inc. + * + * 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 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 library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_SM_CLIENT_PRIVATE_H__ +#define __EGG_SM_CLIENT_PRIVATE_H__ + +#include <gdkconfig.h> +#include "eggsmclient.h" + +G_BEGIN_DECLS + +GKeyFile *egg_sm_client_save_state (EggSMClient *client); +void egg_sm_client_quit_requested (EggSMClient *client); +void egg_sm_client_quit_cancelled (EggSMClient *client); +void egg_sm_client_quit (EggSMClient *client); + +#if defined (GDK_WINDOWING_X11) +# ifdef EGG_SM_CLIENT_BACKEND_XSMP +GType egg_sm_client_xsmp_get_type (void); +EggSMClient *egg_sm_client_xsmp_new (void); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS +GType egg_sm_client_dbus_get_type (void); +EggSMClient *egg_sm_client_dbus_new (void); +# endif +#elif defined (GDK_WINDOWING_WIN32) +GType egg_sm_client_win32_get_type (void); +EggSMClient *egg_sm_client_win32_new (void); +#elif defined (GDK_WINDOWING_QUARTZ) +GType egg_sm_client_osx_get_type (void); +EggSMClient *egg_sm_client_osx_new (void); +#endif + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */ diff --git a/cut-n-paste/smclient/eggsmclient-win32.c b/cut-n-paste/smclient/eggsmclient-win32.c new file mode 100644 index 00000000..3fec775c --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-win32.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* EggSMClientWin32 + * + * For details on the Windows XP logout process, see: + * http://msdn.microsoft.com/en-us/library/aa376876.aspx. + * + * Vista adds some new APIs which EggSMClient does not make use of; see + * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx + * + * When shutting down, Windows sends every top-level window a + * WM_QUERYENDSESSION event, which the application must respond to + * synchronously, saying whether or not it will quit. To avoid main + * loop re-entrancy problems (and to avoid having to muck about too + * much with the guts of the gdk-win32 main loop), we watch for this + * event in a separate thread, which then signals the main thread and + * waits for the main thread to handle the event. Since we don't want + * to require g_thread_init() to be called, we do this all using + * Windows-specific thread methods. + * + * After the application handles the WM_QUERYENDSESSION event, + * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE + * parameter indicating whether the session is or is not actually + * going to end now. We handle this from the other thread as well. + * + * As mentioned above, Vista introduces several additional new APIs + * that don't fit into the (current) EggSMClient API. Windows also has + * an entirely separate shutdown-notification scheme for non-GUI apps, + * which we also don't handle here. + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include <gdk/gdk.h> + +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#include <windows.h> +#include <process.h> + +#define EGG_TYPE_SM_CLIENT_WIN32 (egg_sm_client_win32_get_type ()) +#define EGG_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32)) +#define EGG_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) +#define EGG_IS_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_SM_CLIENT_WIN32_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) + +typedef struct _EggSMClientWin32 EggSMClientWin32; +typedef struct _EggSMClientWin32Class EggSMClientWin32Class; + +struct _EggSMClientWin32 { + EggSMClient parent; + + HANDLE message_event, response_event; + + volatile GSourceFunc event; + volatile gboolean will_quit; +}; + +struct _EggSMClientWin32Class +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_win32_startup (EggSMClient *client, + const char *client_id); +static void sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_win32_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, + gpointer user_data); +static gboolean got_message (gpointer user_data); +static void sm_client_thread (gpointer data); + +G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_win32_init (EggSMClientWin32 *win32) +{ + ; +} + +static void +egg_sm_client_win32_class_init (EggSMClientWin32Class *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_win32_startup; + sm_client_class->will_quit = sm_client_win32_will_quit; + sm_client_class->end_session = sm_client_win32_end_session; +} + +EggSMClient * +egg_sm_client_win32_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL); +} + +static void +sm_client_win32_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL); + win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL); + g_win32_handle_source_add (win32->message_event, got_message, win32); + _beginthread (sm_client_thread, 0, client); +} + +static void +sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->will_quit = will_quit; + SetEvent (win32->response_event); +} + +static gboolean +sm_client_win32_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + UINT uFlags = EWX_LOGOFF; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + uFlags = EWX_LOGOFF; + break; + case EGG_SM_CLIENT_REBOOT: + uFlags = EWX_REBOOT; + break; + case EGG_SM_CLIENT_SHUTDOWN: + uFlags = EWX_POWEROFF; + break; + } + + /* There's no way to make ExitWindowsEx() show a logout dialog, so + * we ignore @request_confirmation. + */ + +#ifdef SHTDN_REASON_FLAG_PLANNED + ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED); +#else + ExitWindowsEx (uFlags, 0); +#endif + + return TRUE; +} + + +/* callbacks from logout-listener thread */ + +static gboolean +emit_quit_requested (gpointer smclient) +{ + gdk_threads_enter (); + egg_sm_client_quit_requested (smclient); + gdk_threads_leave (); + + return FALSE; +} + +static gboolean +emit_quit (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +emit_quit_cancelled (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit_cancelled (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +got_message (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + win32->event (win32); + return TRUE; +} + +/* Windows HANDLE GSource */ + +typedef struct { + GSource source; + GPollFD pollfd; +} GWin32HandleSource; + +static gboolean +g_win32_handle_source_prepare (GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +g_win32_handle_source_check (GSource *source) +{ + GWin32HandleSource *hsource = (GWin32HandleSource *)source; + + return hsource->pollfd.revents; +} + +static gboolean +g_win32_handle_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) +{ + return (*callback) (user_data); +} + +static void +g_win32_handle_source_finalize (GSource *source) +{ + ; +} + +GSourceFuncs g_win32_handle_source_funcs = { + g_win32_handle_source_prepare, + g_win32_handle_source_check, + g_win32_handle_source_dispatch, + g_win32_handle_source_finalize +}; + +static GSource * +g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data) +{ + GWin32HandleSource *hsource; + GSource *source; + + source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource)); + hsource = (GWin32HandleSource *)source; + hsource->pollfd.fd = (int)handle; + hsource->pollfd.events = G_IO_IN; + hsource->pollfd.revents = 0; + g_source_add_poll (source, &hsource->pollfd); + + g_source_set_callback (source, callback, user_data, NULL); + g_source_attach (source, NULL); + return source; +} + +/* logout-listener thread */ + +LRESULT CALLBACK +sm_client_win32_window_procedure (HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + EggSMClientWin32 *win32 = + (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA); + + switch (message) + { + case WM_QUERYENDSESSION: + win32->event = emit_quit_requested; + SetEvent (win32->message_event); + + WaitForSingleObject (win32->response_event, INFINITE); + return win32->will_quit; + + case WM_ENDSESSION: + if (wParam) + { + /* The session is ending */ + win32->event = emit_quit; + } + else + { + /* Nope, the session *isn't* ending */ + win32->event = emit_quit_cancelled; + } + + SetEvent (win32->message_event); + WaitForSingleObject (win32->response_event, INFINITE); + + return 0; + + default: + return DefWindowProc (hwnd, message, wParam, lParam); + } +} + +static void +sm_client_thread (gpointer smclient) +{ + HINSTANCE instance; + WNDCLASSEXW wcl; + ATOM klass; + HWND window; + MSG msg; + + instance = GetModuleHandle (NULL); + + memset (&wcl, 0, sizeof (WNDCLASSEX)); + wcl.cbSize = sizeof (WNDCLASSEX); + wcl.lpfnWndProc = sm_client_win32_window_procedure; + wcl.hInstance = instance; + wcl.lpszClassName = L"EggSmClientWindow"; + klass = RegisterClassEx (&wcl); + + window = CreateWindowEx (0, MAKEINTRESOURCE (klass), + L"EggSmClientWindow", 0, + 10, 10, 50, 50, GetDesktopWindow (), + NULL, instance, NULL); + SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient); + + /* main loop */ + while (GetMessage (&msg, NULL, 0, 0)) + DispatchMessage (&msg); +} diff --git a/cut-n-paste/smclient/eggsmclient-xsmp.c b/cut-n-paste/smclient/eggsmclient-xsmp.c new file mode 100644 index 00000000..90f201d9 --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-xsmp.c @@ -0,0 +1,1370 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * Inspired by various other pieces of code including GsmClient (C) + * 2001 Havoc Pennington, MateClient (C) 1998 Carsten Schaar, and twm + * session code (C) 1998 The Open Group. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +#include "eggdesktopfile.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <X11/SM/SMlib.h> + +#include <gdk/gdk.h> + +#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ()) +#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP)) +#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) +#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) + +typedef struct _EggSMClientXSMP EggSMClientXSMP; +typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass; + +/* These mostly correspond to the similarly-named states in section + * 9.1 of the XSMP spec. Some of the states there aren't represented + * here, because we don't need them. SHUTDOWN_CANCELLED is slightly + * different from the spec; we use it when the client is IDLE after a + * ShutdownCancelled message, but the application is still interacting + * and doesn't know the shutdown has been cancelled yet. + */ +typedef enum +{ + XSMP_STATE_IDLE, + XSMP_STATE_SAVE_YOURSELF, + XSMP_STATE_INTERACT_REQUEST, + XSMP_STATE_INTERACT, + XSMP_STATE_SAVE_YOURSELF_DONE, + XSMP_STATE_SHUTDOWN_CANCELLED, + XSMP_STATE_CONNECTION_CLOSED +} EggSMClientXSMPState; + +static const char *state_names[] = { + "idle", + "save-yourself", + "interact-request", + "interact", + "save-yourself-done", + "shutdown-cancelled", + "connection-closed" +}; + +#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state]) + +struct _EggSMClientXSMP +{ + EggSMClient parent; + + SmcConn connection; + char *client_id; + + EggSMClientXSMPState state; + char **restart_command; + gboolean set_restart_command; + int restart_style; + + guint idle; + + /* Current SaveYourself state */ + guint expecting_initial_save_yourself : 1; + guint need_save_state : 1; + guint need_quit_requested : 1; + guint interact_errors : 1; + guint shutting_down : 1; + + /* Todo list */ + guint waiting_to_set_initial_properties : 1; + guint waiting_to_emit_quit : 1; + guint waiting_to_emit_quit_cancelled : 1; + guint waiting_to_save_myself : 1; + +}; + +struct _EggSMClientXSMPClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_xsmp_startup (EggSMClient *client, + const char *client_id); +static void sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv); +static void sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_xsmp_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static void xsmp_save_yourself (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast); +static void xsmp_die (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_save_complete (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_shutdown_cancelled (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_interact (SmcConn smc_conn, + SmPointer client_data); + +static SmProp *array_prop (const char *name, + ...); +static SmProp *ptrarray_prop (const char *name, + GPtrArray *values); +static SmProp *string_prop (const char *name, + const char *value); +static SmProp *card8_prop (const char *name, + unsigned char value); + +static void set_properties (EggSMClientXSMP *xsmp, ...); +static void delete_properties (EggSMClientXSMP *xsmp, ...); + +static GPtrArray *generate_command (char **restart_command, + const char *client_id, + const char *state_file); + +static void save_state (EggSMClientXSMP *xsmp); +static void do_save_yourself (EggSMClientXSMP *xsmp); +static void update_pending_events (EggSMClientXSMP *xsmp); + +static void ice_init (void); +static gboolean process_ice_messages (IceConn ice_conn); +static void smc_error_handler (SmcConn smc_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + SmPointer values); + +G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp) +{ + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + xsmp->connection = NULL; + xsmp->restart_style = SmRestartIfRunning; +} + +static void +egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_xsmp_startup; + sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command; + sm_client_class->will_quit = sm_client_xsmp_will_quit; + sm_client_class->end_session = sm_client_xsmp_end_session; +} + +EggSMClient * +egg_sm_client_xsmp_new (void) +{ + if (!g_getenv ("SESSION_MANAGER")) + return NULL; + + return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL); +} + +static gboolean +sm_client_xsmp_set_initial_properties (gpointer user_data) +{ + EggSMClientXSMP *xsmp = user_data; + EggDesktopFile *desktop_file; + GPtrArray *clone, *restart; + char pid_str[64]; + + if (xsmp->idle) + { + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } + xsmp->waiting_to_set_initial_properties = FALSE; + + if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART) + xsmp->restart_style = SmRestartNever; + + /* Parse info out of desktop file */ + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GError *err = NULL; + char *cmdline, **argv; + int argc; + + if (xsmp->restart_style == SmRestartIfRunning) + { + if (egg_desktop_file_get_boolean (desktop_file, + "X-MATE-AutoRestart", NULL)) + xsmp->restart_style = SmRestartImmediately; + } + + if (!xsmp->set_restart_command) + { + cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err); + if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err)) + { + egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp), + argc, (const char **)argv); + g_strfreev (argv); + } + else + { + g_warning ("Could not parse Exec line in desktop file: %s", + err->message); + g_error_free (err); + } + g_free (cmdline); + } + } + + if (!xsmp->set_restart_command) + xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1); + + clone = generate_command (xsmp->restart_command, NULL, NULL); + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + + g_debug ("Setting initial properties"); + + /* Program, CloneCommand, RestartCommand, and UserID are required. + * ProcessID isn't required, but the SM may be able to do something + * useful with it. + */ + g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ()); + set_properties (xsmp, + string_prop (SmProgram, g_get_prgname ()), + ptrarray_prop (SmCloneCommand, clone), + ptrarray_prop (SmRestartCommand, restart), + string_prop (SmUserID, g_get_user_name ()), + string_prop (SmProcessID, pid_str), + card8_prop (SmRestartStyleHint, xsmp->restart_style), + NULL); + g_ptr_array_free (clone, TRUE); + g_ptr_array_free (restart, TRUE); + + if (desktop_file) + { + set_properties (xsmp, + string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)), + NULL); + } + + update_pending_events (xsmp); + return FALSE; +} + +/* This gets called from two different places: xsmp_die() (when the + * server asks us to disconnect) and process_ice_messages() (when the + * server disconnects unexpectedly). + */ +static void +sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp) +{ + SmcConn connection; + + if (!xsmp->connection) + return; + + g_debug ("Disconnecting"); + + connection = xsmp->connection; + xsmp->connection = NULL; + SmcCloseConnection (connection, 0, NULL); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); +} + +static void +sm_client_xsmp_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + SmcCallbacks callbacks; + char *ret_client_id; + char error_string_ret[256]; + + xsmp->client_id = g_strdup (client_id); + + ice_init (); + SmcSetErrorHandler (smc_error_handler); + + callbacks.save_yourself.callback = xsmp_save_yourself; + callbacks.die.callback = xsmp_die; + callbacks.save_complete.callback = xsmp_save_complete; + callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; + + callbacks.save_yourself.client_data = xsmp; + callbacks.die.client_data = xsmp; + callbacks.save_complete.client_data = xsmp; + callbacks.shutdown_cancelled.client_data = xsmp; + + client_id = NULL; + error_string_ret[0] = '\0'; + xsmp->connection = + SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &callbacks, + xsmp->client_id, &ret_client_id, + sizeof (error_string_ret), error_string_ret); + + if (!xsmp->connection) + { + g_warning ("Failed to connect to the session manager: %s\n", + error_string_ret[0] ? + error_string_ret : "no error message given"); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + return; + } + + /* We expect a pointless initial SaveYourself if either (a) we + * didn't have an initial client ID, or (b) we DID have an initial + * client ID, but the server rejected it and gave us a new one. + */ + if (!xsmp->client_id || + (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0)) + xsmp->expecting_initial_save_yourself = TRUE; + + if (ret_client_id) + { + g_free (xsmp->client_id); + xsmp->client_id = g_strdup (ret_client_id); + free (ret_client_id); + + gdk_threads_enter (); + gdk_set_sm_client_id (xsmp->client_id); + gdk_threads_leave (); + + g_debug ("Got client ID \"%s\"", xsmp->client_id); + } + + xsmp->state = XSMP_STATE_IDLE; + + /* Do not set the initial properties until we reach the main loop, + * so that the application has a chance to call + * egg_set_desktop_file(). (This may also help the session manager + * have a better idea of when the application is fully up and + * running.) + */ + xsmp->waiting_to_set_initial_properties = TRUE; + xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client); +} + +static void +sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int i; + + g_strfreev (xsmp->restart_command); + + xsmp->restart_command = g_new (char *, argc + 1); + for (i = 0; i < argc; i++) + xsmp->restart_command[i] = g_strdup (argv[i]); + xsmp->restart_command[i] = NULL; + + xsmp->set_restart_command = TRUE; +} + +static void +sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + + if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED) + { + /* The session manager has already exited! Schedule a quit + * signal. + */ + xsmp->waiting_to_emit_quit = TRUE; + update_pending_events (xsmp); + return; + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* We received a ShutdownCancelled message while the application + * was interacting; Schedule a quit_cancelled signal. + */ + xsmp->waiting_to_emit_quit_cancelled = TRUE; + update_pending_events (xsmp); + return; + } + + g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT); + + g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True"); + SmcInteractDone (xsmp->connection, !will_quit); + + if (will_quit && xsmp->need_save_state) + save_state (xsmp); + + g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False"); + SmcSaveYourselfDone (xsmp->connection, will_quit); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static gboolean +sm_client_xsmp_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int save_type; + + /* To end the session via XSMP, we have to send a + * SaveYourselfRequest. We aren't allowed to do that if anything + * else is going on, but we don't want to expose this fact to the + * application. So we do our best to patch things up here... + * + * In the worst case, this method might block for some length of + * time in process_ice_messages, but the only time that code path is + * honestly likely to get hit is if the application tries to end the + * session as the very first thing it does, in which case it + * probably won't actually block anyway. It's not worth gunking up + * the API to try to deal nicely with the other 0.01% of cases where + * this happens. + */ + + while (xsmp->state != XSMP_STATE_IDLE || + xsmp->expecting_initial_save_yourself) + { + /* If we're already shutting down, we don't need to do anything. */ + if (xsmp->shutting_down) + return TRUE; + + switch (xsmp->state) + { + case XSMP_STATE_CONNECTION_CLOSED: + return FALSE; + + case XSMP_STATE_SAVE_YOURSELF: + /* Trying to log out from the save_state callback? Whatever. + * Abort the save_state. + */ + SmcSaveYourselfDone (xsmp->connection, FALSE); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + break; + + case XSMP_STATE_INTERACT_REQUEST: + case XSMP_STATE_INTERACT: + case XSMP_STATE_SHUTDOWN_CANCELLED: + /* Already in a shutdown-related state, just ignore + * the new shutdown request... + */ + return TRUE; + + case XSMP_STATE_IDLE: + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + if (!xsmp->expecting_initial_save_yourself) + break; + /* else fall through */ + + case XSMP_STATE_SAVE_YOURSELF_DONE: + /* We need to wait for some response from the server.*/ + process_ice_messages (SmcGetIceConnection (xsmp->connection)); + break; + + default: + /* Hm... shouldn't happen */ + return FALSE; + } + } + + /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and + * the user chooses to save the session. But mate-session will do + * the wrong thing if we pass SmSaveBoth and the user chooses NOT to + * save the session... Sigh. + */ + if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session")) + save_type = SmSaveBoth; + else + save_type = SmSaveGlobal; + + g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : ""); + SmcRequestSaveYourself (xsmp->connection, + save_type, + True, /* shutdown */ + SmInteractStyleAny, + !request_confirmation, /* fast */ + True /* global */); + return TRUE; +} + +static gboolean +idle_do_pending_events (gpointer data) +{ + EggSMClientXSMP *xsmp = data; + EggSMClient *client = data; + + gdk_threads_enter (); + + xsmp->idle = 0; + + if (xsmp->waiting_to_emit_quit) + { + xsmp->waiting_to_emit_quit = FALSE; + egg_sm_client_quit (client); + goto out; + } + + if (xsmp->waiting_to_emit_quit_cancelled) + { + xsmp->waiting_to_emit_quit_cancelled = FALSE; + egg_sm_client_quit_cancelled (client); + xsmp->state = XSMP_STATE_IDLE; + } + + if (xsmp->waiting_to_save_myself) + { + xsmp->waiting_to_save_myself = FALSE; + do_save_yourself (xsmp); + } + + out: + gdk_threads_leave (); + return FALSE; +} + +static void +update_pending_events (EggSMClientXSMP *xsmp) +{ + gboolean want_idle = + xsmp->waiting_to_emit_quit || + xsmp->waiting_to_emit_quit_cancelled || + xsmp->waiting_to_save_myself; + + if (want_idle) + { + if (xsmp->idle == 0) + xsmp->idle = g_idle_add (idle_do_pending_events, xsmp); + } + else + { + if (xsmp->idle != 0) + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } +} + +static void +fix_broken_state (EggSMClientXSMP *xsmp, const char *message, + gboolean send_interact_done, + gboolean send_save_yourself_done) +{ + g_warning ("Received XSMP %s message in state %s: client or server error", + message, EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + /* Forget any pending SaveYourself plans we had */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + + if (send_interact_done) + SmcInteractDone (xsmp->connection, False); + if (send_save_yourself_done) + SmcSaveYourselfDone (xsmp->connection, True); + + xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE; +} + +/* SM callbacks */ + +static void +xsmp_save_yourself (SmcConn smc_conn, + SmPointer client_data, + int save_type, + Bool shutdown, + int interact_style, + Bool fast) +{ + EggSMClientXSMP *xsmp = client_data; + gboolean wants_quit_requested; + + g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s", + save_type == SmSaveLocal ? "SmSaveLocal" : + save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", + shutdown ? "Shutdown" : "!Shutdown", + interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : + interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : + "SmInteractStyleNone", fast ? "Fast" : "!Fast", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_IDLE && + xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED) + { + fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE); + return; + } + + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + /* If this is the initial SaveYourself, ignore it; we've already set + * properties and there's no reason to actually save state too. + */ + if (xsmp->expecting_initial_save_yourself) + { + xsmp->expecting_initial_save_yourself = FALSE; + + if (save_type == SmSaveLocal && + interact_style == SmInteractStyleNone && + !shutdown && !fast) + { + g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself"); + SmcSaveYourselfDone (xsmp->connection, True); + /* As explained in the comment at the end of + * do_save_yourself(), SAVE_YOURSELF_DONE is the correct + * state here, not IDLE. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + return; + } + else + g_warning ("First SaveYourself was not the expected one!"); + } + + /* Even ignoring the "fast" flag completely, there are still 18 + * different combinations of save_type, shutdown and interact_style. + * We interpret them as follows: + * + * Type Shutdown Interact Interpretation + * G F A/E/N do nothing (1) + * G T N do nothing (1)* + * G T A/E quit_requested (2) + * L/B F A/E/N save_state (3) + * L/B T N save_state (3)* + * L/B T A/E quit_requested, then save_state (4) + * + * 1. Do nothing, because the SM asked us to do something + * uninteresting (save open files, but then don't quit + * afterward) or rude (save open files without asking the user + * for confirmation). + * + * 2. Request interaction and then emit ::quit_requested. This + * perhaps isn't quite correct for the SmInteractStyleErrors + * case, but we don't care. + * + * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these + * rows essentially get demoted to SmSaveLocal, because their + * Global halves correspond to "do nothing". + * + * 4. Request interaction, emit ::quit_requested, and then emit + * ::save_state after interacting. This is the SmSaveBoth + * equivalent of #2, but we also promote SmSaveLocal shutdown + * SaveYourselfs to SmSaveBoth here, because we want to give + * the user a chance to save open files before quitting. + * + * (* It would be nice if we could do something useful when the + * session manager sends a SaveYourself with shutdown True and + * SmInteractStyleNone. But we can't, so we just pretend it didn't + * even tell us it was shutting down. The docs for ::quit mention + * that it might not always be preceded by ::quit_requested.) + */ + + /* As an optimization, we don't actually request interaction and + * emit ::quit_requested if the application isn't listening to the + * signal. + */ + wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE); + + xsmp->need_save_state = (save_type != SmSaveGlobal); + xsmp->need_quit_requested = (shutdown && wants_quit_requested && + interact_style != SmInteractStyleNone); + xsmp->interact_errors = (interact_style == SmInteractStyleErrors); + + xsmp->shutting_down = shutdown; + + do_save_yourself (xsmp); +} + +static void +do_save_yourself (EggSMClientXSMP *xsmp) +{ + if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* The SM cancelled a previous SaveYourself, but we haven't yet + * had a chance to tell the application, so we can't start + * processing this SaveYourself yet. + */ + xsmp->waiting_to_save_myself = TRUE; + update_pending_events (xsmp); + return; + } + + if (xsmp->need_quit_requested) + { + xsmp->state = XSMP_STATE_INTERACT_REQUEST; + + g_debug ("Sending InteractRequest(%s)", + xsmp->interact_errors ? "Error" : "Normal"); + SmcInteractRequest (xsmp->connection, + xsmp->interact_errors ? SmDialogError : SmDialogNormal, + xsmp_interact, + xsmp); + return; + } + + if (xsmp->need_save_state) + { + save_state (xsmp); + + /* Though unlikely, the client could have been disconnected + * while the application was saving its state. + */ + if (!xsmp->connection) + return; + } + + g_debug ("Sending SaveYourselfDone(True)"); + SmcSaveYourselfDone (xsmp->connection, True); + + /* The client state diagram in the XSMP spec says that after a + * non-shutdown SaveYourself, we go directly back to "idle". But + * everything else in both the XSMP spec and the libSM docs + * disagrees. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static void +save_state (EggSMClientXSMP *xsmp) +{ + GKeyFile *state_file; + char *state_file_path, *data; + EggDesktopFile *desktop_file; + GPtrArray *restart; + int offset, fd; + + /* We set xsmp->state before emitting save_state, but our caller is + * responsible for setting it back afterward. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF; + + state_file = egg_sm_client_save_state ((EggSMClient *)xsmp); + if (!state_file) + { + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + delete_properties (xsmp, SmDiscardCommand, NULL); + return; + } + + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GKeyFile *merged_file; + char *desktop_file_path; + + merged_file = g_key_file_new (); + desktop_file_path = + g_filename_from_uri (egg_desktop_file_get_source (desktop_file), + NULL, NULL); + if (desktop_file_path && + g_key_file_load_from_file (merged_file, desktop_file_path, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) + { + guint g, k, i; + char **groups, **keys, *value, *exec; + + groups = g_key_file_get_groups (state_file, NULL); + for (g = 0; groups[g]; g++) + { + keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL); + for (k = 0; keys[k]; k++) + { + value = g_key_file_get_value (state_file, groups[g], + keys[k], NULL); + if (value) + { + g_key_file_set_value (merged_file, groups[g], + keys[k], value); + g_free (value); + } + } + g_strfreev (keys); + } + g_strfreev (groups); + + g_key_file_free (state_file); + state_file = merged_file; + + /* Update Exec key using "--sm-client-state-file %k" */ + restart = generate_command (xsmp->restart_command, + NULL, "%k"); + for (i = 0; i < restart->len; i++) + restart->pdata[i] = g_shell_quote (restart->pdata[i]); + g_ptr_array_add (restart, NULL); + exec = g_strjoinv (" ", (char **)restart->pdata); + g_strfreev ((char **)restart->pdata); + g_ptr_array_free (restart, FALSE); + + g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + exec); + g_free (exec); + } + else + desktop_file = NULL; + + g_free (desktop_file_path); + } + + /* Now write state_file to disk. (We can't use mktemp(), because + * that requires the filename to end with "XXXXXX", and we want + * it to end with ".desktop".) + */ + + data = g_key_file_to_data (state_file, NULL, NULL); + g_key_file_free (state_file); + + offset = 0; + while (1) + { + state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s", + g_get_user_config_dir (), + G_DIR_SEPARATOR, G_DIR_SEPARATOR, + g_get_prgname (), + (long)time (NULL) + offset, + desktop_file ? "desktop" : "state"); + + fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd == -1) + { + if (errno == EEXIST) + { + offset++; + g_free (state_file_path); + continue; + } + else if (errno == ENOTDIR || errno == ENOENT) + { + char *sep = strrchr (state_file_path, G_DIR_SEPARATOR); + + *sep = '\0'; + if (g_mkdir_with_parents (state_file_path, 0755) != 0) + { + g_warning ("Could not create directory '%s'", + state_file_path); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + continue; + } + + g_warning ("Could not create file '%s': %s", + state_file_path, g_strerror (errno)); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + close (fd); + g_file_set_contents (state_file_path, data, -1, NULL); + break; + } + g_free (data); + + restart = generate_command (xsmp->restart_command, xsmp->client_id, + state_file_path); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + + if (state_file_path) + { + set_properties (xsmp, + array_prop (SmDiscardCommand, + "/bin/rm", "-rf", state_file_path, + NULL), + NULL); + g_free (state_file_path); + } +} + +static void +xsmp_interact (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Interact message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_INTERACT_REQUEST) + { + fix_broken_state (xsmp, "Interact", TRUE, TRUE); + return; + } + + xsmp->state = XSMP_STATE_INTERACT; + egg_sm_client_quit_requested (client); +} + +static void +xsmp_die (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Die message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + sm_client_xsmp_disconnect (xsmp); + egg_sm_client_quit (client); +} + +static void +xsmp_save_complete (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + + g_debug ("Received SaveComplete message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + xsmp->state = XSMP_STATE_IDLE; + else + fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE); +} + +static void +xsmp_shutdown_cancelled (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received ShutdownCancelled message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + xsmp->shutting_down = FALSE; + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + { + /* We've finished interacting and now the SM has agreed to + * cancel the shutdown. + */ + xsmp->state = XSMP_STATE_IDLE; + egg_sm_client_quit_cancelled (client); + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* Hm... ok, so we got a shutdown SaveYourself, which got + * cancelled, but the application was still interacting, so we + * didn't tell it yet, and then *another* SaveYourself arrived, + * which we must still be waiting to tell the app about, except + * that now that SaveYourself has been cancelled too! Dizzy yet? + */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + } + else + { + g_debug ("Sending SaveYourselfDone(False)"); + SmcSaveYourselfDone (xsmp->connection, False); + + if (xsmp->state == XSMP_STATE_INTERACT) + { + /* The application is currently interacting, so we can't + * tell it about the cancellation yet; we will wait until + * after it calls egg_sm_client_will_quit(). + */ + xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED; + } + else + { + /* The shutdown was cancelled before the application got a + * chance to interact. + */ + xsmp->state = XSMP_STATE_IDLE; + } + } +} + +/* Utilities */ + +/* Create a restart/clone/Exec command based on @restart_command. + * If @client_id is non-%NULL, add "--sm-client-id @client_id". + * If @state_file is non-%NULL, add "--sm-client-state-file @state_file". + * + * None of the input strings are g_strdup()ed; the caller must keep + * them around until it is done with the returned GPtrArray, and must + * then free the array, but not its contents. + */ +static GPtrArray * +generate_command (char **restart_command, const char *client_id, + const char *state_file) +{ + GPtrArray *cmd; + int i; + + cmd = g_ptr_array_new (); + g_ptr_array_add (cmd, restart_command[0]); + + if (client_id) + { + g_ptr_array_add (cmd, "--sm-client-id"); + g_ptr_array_add (cmd, (char *)client_id); + } + + if (state_file) + { + g_ptr_array_add (cmd, "--sm-client-state-file"); + g_ptr_array_add (cmd, (char *)state_file); + } + + for (i = 1; restart_command[i]; i++) + g_ptr_array_add (cmd, restart_command[i]); + + return cmd; +} + +/* Takes a NULL-terminated list of SmProp * values, created by + * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and + * frees them. + */ +static void +set_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + SmProp *prop; + va_list ap; + guint i; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, SmProp *))) + g_ptr_array_add (props, prop); + va_end (ap); + + if (xsmp->connection) + { + SmcSetProperties (xsmp->connection, props->len, + (SmProp **)props->pdata); + } + + for (i = 0; i < props->len; i++) + { + prop = props->pdata[i]; + g_free (prop->vals); + g_free (prop); + } + g_ptr_array_free (props, TRUE); +} + +/* Takes a NULL-terminated list of property names and deletes them. */ +static void +delete_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + char *prop; + va_list ap; + + if (!xsmp->connection) + return; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, char *))) + g_ptr_array_add (props, prop); + va_end (ap); + + SmcDeleteProperties (xsmp->connection, props->len, + (char **)props->pdata); + + g_ptr_array_free (props, TRUE); +} + +/* Takes an array of strings and creates a LISTofARRAY8 property. The + * strings are neither dupped nor freed; they need to remain valid + * until you're done with the SmProp. + */ +static SmProp * +array_prop (const char *name, ...) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + char *value; + va_list ap; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + va_start (ap, name); + while ((value = va_arg (ap, char *))) + { + pv.length = strlen (value); + pv.value = value; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property. + * The array contents are neither dupped nor freed; they need to + * remain valid until you're done with the SmProp. + */ +static SmProp * +ptrarray_prop (const char *name, GPtrArray *values) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + guint i; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + for (i = 0; i < values->len; i++) + { + pv.length = strlen (values->pdata[i]); + pv.value = values->pdata[i]; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a string and creates an ARRAY8 property. The string is + * neither dupped nor freed; it needs to remain valid until you're + * done with the SmProp. + */ +static SmProp * +string_prop (const char *name, const char *value) +{ + SmProp *prop; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmARRAY8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 1); + + prop->vals[0].length = strlen (value); + prop->vals[0].value = (char *)value; + + return prop; +} + +/* Takes a char and creates a CARD8 property. */ +static SmProp * +card8_prop (const char *name, unsigned char value) +{ + SmProp *prop; + char *card8val; + + /* To avoid having to allocate and free prop->vals[0], we cheat and + * make vals a 2-element-long array and then use the second element + * to store value. + */ + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmCARD8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 2); + card8val = (char *)(&prop->vals[1]); + card8val[0] = value; + + prop->vals[0].length = 1; + prop->vals[0].value = card8val; + + return prop; +} + +/* ICE code. This makes no effort to play nice with anyone else trying + * to use libICE. Fortunately, no one uses libICE for anything other + * than SM. (DCOP uses ICE, but it has its own private copy of + * libICE.) + * + * When this moves to gtk, it will need to be cleverer, to avoid + * tripping over old apps that use MateClient or that use libSM + * directly. + */ + +#include <X11/ICE/ICElib.h> +#include <fcntl.h> + +static void ice_error_handler (IceConn ice_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values); +static void ice_io_error_handler (IceConn ice_conn); +static void ice_connection_watch (IceConn ice_conn, + IcePointer client_data, + Bool opening, + IcePointer *watch_data); + +static void +ice_init (void) +{ + IceSetIOErrorHandler (ice_io_error_handler); + IceSetErrorHandler (ice_error_handler); + IceAddConnectionWatch (ice_connection_watch, NULL); +} + +static gboolean +process_ice_messages (IceConn ice_conn) +{ + IceProcessMessagesStatus status; + + gdk_threads_enter (); + status = IceProcessMessages (ice_conn, NULL, NULL); + gdk_threads_leave (); + + switch (status) + { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: + sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn)); + return FALSE; + + case IceProcessMessagesConnectionClosed: + return FALSE; + + default: + g_assert_not_reached (); + } +} + +static gboolean +ice_iochannel_watch (GIOChannel *channel, + GIOCondition condition, + gpointer client_data) +{ + return process_ice_messages (client_data); +} + +static void +ice_connection_watch (IceConn ice_conn, + IcePointer client_data, + Bool opening, + IcePointer *watch_data) +{ + guint watch_id; + + if (opening) + { + GIOChannel *channel; + int fd = IceConnectionNumber (ice_conn); + + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new (fd); + watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR, + ice_iochannel_watch, ice_conn); + g_io_channel_unref (channel); + + *watch_data = GUINT_TO_POINTER (watch_id); + } + else + { + watch_id = GPOINTER_TO_UINT (*watch_data); + g_source_remove (watch_id); + } +} + +static void +ice_error_handler (IceConn ice_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values) +{ + /* Do nothing */ +} + +static void +ice_io_error_handler (IceConn ice_conn) +{ + /* Do nothing */ +} + +static void +smc_error_handler (SmcConn smc_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + SmPointer values) +{ + /* Do nothing */ +} diff --git a/cut-n-paste/smclient/eggsmclient.c b/cut-n-paste/smclient/eggsmclient.c new file mode 100644 index 00000000..c568e9be --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +static void egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data); + +enum { + SAVE_STATE, + QUIT_REQUESTED, + QUIT_CANCELLED, + QUIT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +struct _EggSMClientPrivate { + GKeyFile *state_file; +}; + +#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate)) + +G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT) + +static EggSMClient *global_client; +static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL; + +static void +egg_sm_client_init (EggSMClient *client) +{ + ; +} + +static void +egg_sm_client_class_init (EggSMClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EggSMClientPrivate)); + + /** + * EggSMClient::save_state: + * @client: the client + * @state_file: a #GKeyFile to save state information into + * + * Emitted when the session manager has requested that the + * application save information about its current state. The + * application should save its state into @state_file, and then the + * session manager may then restart the application in a future + * session and tell it to initialize itself from that state. + * + * You should not save any data into @state_file's "start group" + * (ie, the %NULL group). Instead, applications should save their + * data into groups with names that start with the application name, + * and libraries that connect to this signal should save their data + * into groups with names that start with the library name. + * + * Alternatively, rather than (or in addition to) using @state_file, + * the application can save its state by calling + * egg_sm_client_set_restart_command() during the processing of this + * signal (eg, to include a list of files to open). + **/ + signals[SAVE_STATE] = + g_signal_new ("save_state", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, save_state), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * EggSMClient::quit_requested: + * @client: the client + * + * Emitted when the session manager requests that the application + * exit (generally because the user is logging out). The application + * should decide whether or not it is willing to quit (perhaps after + * asking the user what to do with documents that have unsaved + * changes) and then call egg_sm_client_will_quit(), passing %TRUE + * or %FALSE to give its answer to the session manager. (It does not + * need to give an answer before returning from the signal handler; + * it can interact with the user asynchronously and then give its + * answer later on.) If the application does not connect to this + * signal, then #EggSMClient will automatically return %TRUE on its + * behalf. + * + * The application should not save its session state as part of + * handling this signal; if the user has requested that the session + * be saved when logging out, then ::save_state will be emitted + * separately. + * + * If the application agrees to quit, it should then wait for either + * the ::quit_cancelled or ::quit signals to be emitted. + **/ + signals[QUIT_REQUESTED] = + g_signal_new ("quit_requested", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_requested), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit_cancelled: + * @client: the client + * + * Emitted when the session manager decides to cancel a logout after + * the application has already agreed to quit. After receiving this + * signal, the application can go back to what it was doing before + * receiving the ::quit_requested signal. + **/ + signals[QUIT_CANCELLED] = + g_signal_new ("quit_cancelled", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit: + * @client: the client + * + * Emitted when the session manager wants the application to quit + * (generally because the user is logging out). The application + * should exit as soon as possible after receiving this signal; if + * it does not, the session manager may choose to forcibly kill it. + * + * Normally a GUI application would only be sent a ::quit if it + * agreed to quit in response to a ::quit_requested signal. However, + * this is not guaranteed; in some situations the session manager + * may decide to end the session without giving applications a + * chance to object. + **/ + signals[QUIT] = + g_signal_new ("quit", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static gboolean sm_client_disable = FALSE; +static char *sm_client_state_file = NULL; +static char *sm_client_id = NULL; +static char *sm_config_prefix = NULL; + +static gboolean +sm_client_post_parse_func (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + EggSMClient *client = egg_sm_client_get (); + + if (sm_client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + + if (desktop_autostart_id != NULL) + sm_client_id = g_strdup (desktop_autostart_id); + } + + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + if (EGG_SM_CLIENT_GET_CLASS (client)->startup) + EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id); + return TRUE; +} + +/** + * egg_sm_client_get_option_group: + * + * Creates a %GOptionGroup containing the session-management-related + * options. You should add this group to the application's + * %GOptionContext if you want to use #EggSMClient. + * + * Return value: the %GOptionGroup + **/ +GOptionGroup * +egg_sm_client_get_option_group (void) +{ + const GOptionEntry entries[] = { + { "sm-client-disable", 0, 0, + G_OPTION_ARG_NONE, &sm_client_disable, + N_("Disable connection to session manager"), NULL }, + { "sm-client-state-file", 0, 0, + G_OPTION_ARG_FILENAME, &sm_client_state_file, + N_("Specify file containing saved configuration"), N_("FILE") }, + { "sm-client-id", 0, 0, + G_OPTION_ARG_STRING, &sm_client_id, + N_("Specify session management ID"), N_("ID") }, + /* MateClient compatibility option */ + { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &sm_client_disable, + NULL, NULL }, + /* MateClient compatibility option. This is a dummy option that only + * exists so that sessions saved by apps with MateClient can be restored + * later when they've switched to EggSMClient. See bug #575308. + */ + { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &sm_config_prefix, + NULL, NULL }, + { NULL } + }; + GOptionGroup *group; + + /* Use our own debug handler for the "EggSMClient" domain. */ + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + egg_sm_client_debug_handler, NULL); + + group = g_option_group_new ("sm-client", + _("Session management options:"), + _("Show session management options"), + NULL, NULL); + g_option_group_add_entries (group, entries); + g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func); + + return group; +} + +/** + * egg_sm_client_set_mode: + * @mode: an #EggSMClient mode + * + * Sets the "mode" of #EggSMClient as follows: + * + * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely + * disabled. The application will not even connect to the session + * manager. (egg_sm_client_get() will still return an #EggSMClient, + * but it will just be a dummy object.) + * + * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to + * the session manager (and thus will receive notification when the + * user is logging out, etc), but will request to not be + * automatically restarted with saved state in future sessions. + * + * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will + * function normally. + * + * This must be called before the application's main loop begins. + **/ +void +egg_sm_client_set_mode (EggSMClientMode mode) +{ + global_client_mode = mode; +} + +/** + * egg_sm_client_get_mode: + * + * Gets the global #EggSMClientMode. See egg_sm_client_set_mode() + * for details. + * + * Return value: the global #EggSMClientMode + **/ +EggSMClientMode +egg_sm_client_get_mode (void) +{ + return global_client_mode; +} + +/** + * egg_sm_client_get: + * + * Returns the master #EggSMClient for the application. + * + * On platforms that support saved sessions (ie, POSIX/X11), the + * application will only request to be restarted by the session + * manager if you call egg_set_desktop_file() to set an application + * desktop file. In particular, if the desktop file contains the key + * "X + * + * Return value: the master #EggSMClient. + **/ +EggSMClient * +egg_sm_client_get (void) +{ + if (!global_client) + { + if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && + !sm_client_disable) + { +#if defined (GDK_WINDOWING_WIN32) + global_client = egg_sm_client_win32_new (); +#elif defined (GDK_WINDOWING_QUARTZ) + global_client = egg_sm_client_osx_new (); +#else + /* If both D-Bus and XSMP are compiled in, try XSMP first + * (since it supports state saving) and fall back to D-Bus + * if XSMP isn't available. + */ +# ifdef EGG_SM_CLIENT_BACKEND_XSMP + global_client = egg_sm_client_xsmp_new (); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS + if (!global_client) + global_client = egg_sm_client_dbus_new (); +# endif +#endif + } + + /* Fallback: create a dummy client, so that callers don't have + * to worry about a %NULL return value. + */ + if (!global_client) + global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL); + } + + return global_client; +} + +/** + * egg_sm_client_is_resumed: + * @client: the client + * + * Checks whether or not the current session has been resumed from + * a previous saved session. If so, the application should call + * egg_sm_client_get_state_file() and restore its state from the + * returned #GKeyFile. + * + * Return value: %TRUE if the session has been resumed + **/ +gboolean +egg_sm_client_is_resumed (EggSMClient *client) +{ + g_return_val_if_fail (client == global_client, FALSE); + + return sm_client_state_file != NULL; +} + +/** + * egg_sm_client_get_state_file: + * @client: the client + * + * If the application was resumed by the session manager, this will + * return the #GKeyFile containing its state from the previous + * session. + * + * Note that other libraries and #EggSMClient itself may also store + * state in the key file, so if you call egg_sm_client_get_groups(), + * on it, the return value will likely include groups that you did not + * put there yourself. (It is also not guaranteed that the first + * group created by the application will still be the "start group" + * when it is resumed.) + * + * Return value: the #GKeyFile containing the application's earlier + * state, or %NULL on error. You should not free this key file; it + * is owned by @client. + **/ +GKeyFile * +egg_sm_client_get_state_file (EggSMClient *client) +{ + EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client); + char *state_file_path; + GError *err = NULL; + + g_return_val_if_fail (client == global_client, NULL); + + if (!sm_client_state_file) + return NULL; + if (priv->state_file) + return priv->state_file; + + if (!strncmp (sm_client_state_file, "file://", 7)) + state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL); + else + state_file_path = g_strdup (sm_client_state_file); + + priv->state_file = g_key_file_new (); + if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err)) + { + g_warning ("Could not load SM state file '%s': %s", + sm_client_state_file, err->message); + g_clear_error (&err); + g_key_file_free (priv->state_file); + priv->state_file = NULL; + } + + g_free (state_file_path); + return priv->state_file; +} + +/** + * egg_sm_client_set_restart_command: + * @client: the client + * @argc: the length of @argv + * @argv: argument vector + * + * Sets the command used to restart @client if it does not have a + * .desktop file that can be used to find its restart command. + * + * This can also be used when handling the ::save_state signal, to + * save the current state via an updated command line. (Eg, providing + * a list of filenames to open when the application is resumed.) + **/ +void +egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command) + EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv); +} + +/** + * egg_sm_client_will_quit: + * @client: the client + * @will_quit: whether or not the application is willing to quit + * + * This MUST be called in response to the ::quit_requested signal, to + * indicate whether or not the application is willing to quit. The + * application may call it either directly from the signal handler, or + * at some later point (eg, after asynchronously interacting with the + * user). + * + * If the application does not connect to ::quit_requested, + * #EggSMClient will call this method on its behalf (passing %TRUE + * for @will_quit). + * + * After calling this method, the application should wait to receive + * either ::quit_cancelled or ::quit. + **/ +void +egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit) + EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit); +} + +/** + * egg_sm_client_end_session: + * @style: a hint at how to end the session + * @request_confirmation: whether or not the user should get a chance + * to confirm the action + * + * Requests that the session manager end the current session. @style + * indicates how the session should be ended, and + * @request_confirmation indicates whether or not the user should be + * given a chance to confirm the logout/reboot/shutdown. Both of these + * flags are merely hints though; the session manager may choose to + * ignore them. + * + * Return value: %TRUE if the request was sent; %FALSE if it could not + * be (eg, because it could not connect to the session manager). + **/ +gboolean +egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClient *client = egg_sm_client_get (); + + g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE); + + if (EGG_SM_CLIENT_GET_CLASS (client)->end_session) + { + return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style, + request_confirmation); + } + else + return FALSE; +} + +/* Signal-emitting callbacks from platform-specific code */ + +GKeyFile * +egg_sm_client_save_state (EggSMClient *client) +{ + GKeyFile *state_file; + char *group; + + g_return_val_if_fail (client == global_client, NULL); + + state_file = g_key_file_new (); + + g_debug ("Emitting save_state"); + g_signal_emit (client, signals[SAVE_STATE], 0, state_file); + g_debug ("Done emitting save_state"); + + group = g_key_file_get_start_group (state_file); + if (group) + { + g_free (group); + return state_file; + } + else + { + g_key_file_free (state_file); + return NULL; + } +} + +void +egg_sm_client_quit_requested (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE)) + { + g_debug ("Not emitting quit_requested because no one is listening"); + egg_sm_client_will_quit (client, TRUE); + return; + } + + g_debug ("Emitting quit_requested"); + g_signal_emit (client, signals[QUIT_REQUESTED], 0); + g_debug ("Done emitting quit_requested"); +} + +void +egg_sm_client_quit_cancelled (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit_cancelled"); + g_signal_emit (client, signals[QUIT_CANCELLED], 0); + g_debug ("Done emitting quit_cancelled"); +} + +void +egg_sm_client_quit (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit"); + g_signal_emit (client, signals[QUIT], 0); + g_debug ("Done emitting quit"); + + /* FIXME: should we just call gtk_main_quit() here? */ +} + +static void +egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data) +{ + static int debug = -1; + + if (debug < 0) + debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL); + + if (debug) + g_log_default_handler (log_domain, log_level, message, NULL); +} diff --git a/cut-n-paste/smclient/eggsmclient.h b/cut-n-paste/smclient/eggsmclient.h new file mode 100644 index 00000000..65e258b3 --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient.h @@ -0,0 +1,117 @@ +/* eggsmclient.h + * Copyright (C) 2007 Novell, Inc. + * + * 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 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 library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_SM_CLIENT_H__ +#define __EGG_SM_CLIENT_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ()) +#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient)) +#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass)) +#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT)) +#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT)) +#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass)) + +typedef struct _EggSMClient EggSMClient; +typedef struct _EggSMClientClass EggSMClientClass; +typedef struct _EggSMClientPrivate EggSMClientPrivate; + +typedef enum { + EGG_SM_CLIENT_END_SESSION_DEFAULT, + EGG_SM_CLIENT_LOGOUT, + EGG_SM_CLIENT_REBOOT, + EGG_SM_CLIENT_SHUTDOWN +} EggSMClientEndStyle; + +typedef enum { + EGG_SM_CLIENT_MODE_DISABLED, + EGG_SM_CLIENT_MODE_NO_RESTART, + EGG_SM_CLIENT_MODE_NORMAL +} EggSMClientMode; + +struct _EggSMClient +{ + GObject parent; + +}; + +struct _EggSMClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*save_state) (EggSMClient *client, + GKeyFile *state_file); + + void (*quit_requested) (EggSMClient *client); + void (*quit_cancelled) (EggSMClient *client); + void (*quit) (EggSMClient *client); + + /* virtual methods */ + void (*startup) (EggSMClient *client, + const char *client_id); + void (*set_restart_command) (EggSMClient *client, + int argc, + const char **argv); + void (*will_quit) (EggSMClient *client, + gboolean will_quit); + gboolean (*end_session) (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + + /* Padding for future expansion */ + void (*_egg_reserved1) (void); + void (*_egg_reserved2) (void); + void (*_egg_reserved3) (void); + void (*_egg_reserved4) (void); +}; + +GType egg_sm_client_get_type (void) G_GNUC_CONST; + +GOptionGroup *egg_sm_client_get_option_group (void); + +/* Initialization */ +void egg_sm_client_set_mode (EggSMClientMode mode); +EggSMClientMode egg_sm_client_get_mode (void); +EggSMClient *egg_sm_client_get (void); + +/* Resuming a saved session */ +gboolean egg_sm_client_is_resumed (EggSMClient *client); +GKeyFile *egg_sm_client_get_state_file (EggSMClient *client); + +/* Alternate means of saving state */ +void egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv); + +/* Handling "quit_requested" signal */ +void egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit); + +/* Initiate a logout/reboot/shutdown */ +gboolean egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation); + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_H__ */ diff --git a/cut-n-paste/synctex/Makefile.am b/cut-n-paste/synctex/Makefile.am new file mode 100644 index 00000000..881ef4ab --- /dev/null +++ b/cut-n-paste/synctex/Makefile.am @@ -0,0 +1,14 @@ +noinst_LTLIBRARIES = libsynctex.la + +libsynctex_la_SOURCES = \ + synctex_parser.c \ + synctex_parser.h \ + synctex_parser_utils.h \ + synctex_parser_utils.c + +libsynctex_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/synctex/synctex_parser.c b/cut-n-paste/synctex/synctex_parser.c new file mode 100644 index 00000000..060f32d1 --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser.c @@ -0,0 +1,4171 @@ +/* +Copyright (c) 2008, 2009; 2010 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Version: 1.12 +See synctex_parser_readme.txt for more details + +Latest Revision: Mon Jul 19 21:50:36 UTC 2010 + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +/* We assume that high level application like pdf viewers will want + * to embed this code as is. We assume that they also have locale.h and setlocale. + * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, + * when building. You also have to create and customize synctex_parser_local.h to fit your system. + * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. + * With this design, you should not need to edit this file. */ + +# if defined(SYNCTEX_USE_LOCAL_HEADER) +# include "synctex_parser_local.h" +# else +# define HAVE_LOCALE_H 1 +# define HAVE_SETLOCALE 1 +# if defined(_MSC_VER) +# define SYNCTEX_INLINE __inline +# else +# define SYNCTEX_INLINE inline +# endif +# endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <limits.h> + +#if defined(HAVE_LOCALE_H) +#include <locale.h> +#endif + +/* The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each leaf of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ + +#include "synctex_parser.h" +#include <synctex_parser_utils.h> + +/* These are the possible extensions of the synctex file */ +const char * synctex_suffix = ".synctex"; +const char * synctex_suffix_gz = ".gz"; + +static const char * synctex_io_modes[synctex_io_mode_append+2] = {"r","rb","a","ab"}; + +/* each synctex node has a class */ +typedef struct __synctex_class_t _synctex_class_t; +typedef _synctex_class_t * synctex_class_t; + + +/* synctex_node_t is a pointer to a node + * _synctex_node is the target of the synctex_node_t pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ +typedef union _synctex_info_t { + int INT; + char * PTR; +} synctex_info_t; + +struct _synctex_node { + synctex_class_t class; + synctex_info_t * implementation; +}; + +/* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + +typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t); +typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t); + +struct __synctex_class_t { + synctex_scanner_t scanner; + int type; + synctex_node_t (*new)(synctex_scanner_t scanner); + void (*free)(synctex_node_t); + void (*log)(synctex_node_t); + void (*display)(synctex_node_t); + _synctex_node_getter_t parent; + _synctex_node_getter_t child; + _synctex_node_getter_t sibling; + _synctex_node_getter_t friend; + _synctex_node_getter_t next_box; + _synctex_info_getter_t info; +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Abstract OBJECTS and METHODS +# endif + +/* These macros are shortcuts + * This macro checks if a message can be sent. + */ +# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\ + (NULL!=((((NODE)->class))->SELECTOR)) + +/* This macro is some kind of objc_msg_send. + * It takes care of sending the proper message if possible. + */ +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if(NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\ + (*((((NODE)->class))->SELECTOR))(NODE);\ + } + +/* read only safe getter + */ +# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL)) + +/* read/write getter + */ +# define SYNCTEX_GETTER(NODE,SELECTOR)\ + ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE))) + +# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free); + +/* Parent getter and setter + */ +# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent) +# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if(NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\ + SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\ + } + +/* Child getter and setter + */ +# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child) +# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if(NODE && NEW_CHILD){\ + SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\ + SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\ + } + +/* Sibling getter and setter + */ +# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling) +# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if(NODE && NEW_SIBLING) {\ + SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\ + if(SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\ + SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\ + }\ + } +/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar. + * This is a first filter on the nodes that avoids testing all of them. + * Friends are used mainly in forward synchronization aka from source to output. + */ +# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend) +# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if(NODE && NEW_FRIEND){\ + SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\ + } + +/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other. + * Navigation starts with the deeper boxes. + */ +# define SYNCTEX_NEXT_HORIZ_BOX(NODE) SYNCTEX_GET(NODE,next_box) +# define SYNCTEX_SET_NEXT_HORIZ_BOX(NODE,NEXT_BOX) if(NODE && NEXT_BOX){\ + SYNCTEX_GETTER(NODE,next_box)[0]=NEXT_BOX;\ + } + +void _synctex_free_node(synctex_node_t node); +void _synctex_free_leaf(synctex_node_t node); + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +void _synctex_free_node(synctex_node_t node) { + if(node) { + (*((node->class)->sibling))(node); + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + SYNCTEX_FREE(SYNCTEX_CHILD(node)); + free(node); + } + return; +} + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for nodes with no child. + */ +void _synctex_free_leaf(synctex_node_t node) { + if(node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(node); + } + return; +} +# ifdef __SYNCTEX_WORK__ +# include "/usr/include/zlib.h" +# else +# include <zlib.h> +# endif + +/* The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_? are first used to parse the text. + */ +struct __synctex_scanner_t { + gzFile file; /* The (possibly compressed) file */ + char * buffer_cur; /* current location in the buffer */ + char * buffer_start; /* start of the buffer */ + char * buffer_end; /* end of the buffer */ + char * output_fmt; /* dvi or pdf, not yet used */ + char * output; /* the output name used to create the scanner */ + char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-1; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offste from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_t input; /* The first input node, its siblings are the other input nodes */ + int number_of_lists; /* The number of friend lists */ + synctex_node_t * lists_of_friends;/* The friend lists */ + _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ +}; + +/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->buffer_cur) +# define SYNCTEX_START (scanner->buffer_start) +# define SYNCTEX_END (scanner->buffer_end) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark OBJECTS, their creators and destructors. +# endif + +/* Here, we define the indices for the different informations. + * They are used to declare the size of the implementation. + * For example, if one object uses SYNCTEX_HORIZ_IDX is its size, + * then its info will contain a tag, line, column, horiz but no width nor height nor depth + */ + +/* The sheet is a first level node. + * It has no parent (the parent is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ +/* The next macros are used to access the node info + * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node + * SYNCTEX_INFO(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX] + */ +# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE)) +# define SYNCTEX_PAGE_IDX 0 +# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT + +/* This macro defines implementation offsets + * It is only used for pointer values + */ +# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\ + return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\ +} +SYNCTEX_MAKE_GET(_synctex_implementation_0,0) +SYNCTEX_MAKE_GET(_synctex_implementation_1,1) +SYNCTEX_MAKE_GET(_synctex_implementation_2,2) +SYNCTEX_MAKE_GET(_synctex_implementation_3,3) +SYNCTEX_MAKE_GET(_synctex_implementation_4,4) +SYNCTEX_MAKE_GET(_synctex_implementation_5,5) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box, + * SYNCTEX_PAGE_IDX */ +} synctex_sheet_t; + +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner); +void _synctex_display_sheet(synctex_node_t sheet); +void _synctex_log_sheet(synctex_node_t sheet); + +static _synctex_class_t synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + NULL, /* No parent */ + &_synctex_implementation_0, /* child */ + &_synctex_implementation_1, /* sibling */ + NULL, /* No friend */ + &_synctex_implementation_2, /* Next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 /* info */ +}; + +/* sheet node creator */ +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_sheet_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_sheet:(synctex_class_t)&synctex_class_sheet; + } + return node; +} + +/* A box node contains navigation and synctex information + * There are different kind of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +# define SYNCTEX_TAG_IDX 0 +# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1) +# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1) +# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1) +# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1) +# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1) +# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT +# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT +# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT +# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT +# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT +# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT +# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT +# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT +# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE))) +# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE))) +# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE))) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */ +} synctex_vert_box_node_t; + +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner); +void _synctex_log_box(synctex_node_t sheet); +void _synctex_display_vbox(synctex_node_t node); + +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static _synctex_class_t synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_box, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* vertical box node creator */ +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_vert_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_vbox:(synctex_class_t)&synctex_class_vbox; + } + return node; +} + +# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_DEPTH_IDX+1) +# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1) +# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1) +# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1) +# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT +# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT +# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT +# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT +# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT +# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE))) +# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE))) +# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE))) + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH, + * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/ +} synctex_horiz_box_node_t; + +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner); +void _synctex_display_hbox(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t sheet); + + +static _synctex_class_t synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_horiz_box, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* horizontal box node creator */ +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_horiz_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_hbox:(synctex_class_t)&synctex_class_hbox; + } + return node; +} + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/ +} synctex_void_box_node_t; + +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner); +void _synctex_log_void_box(synctex_node_t sheet); +void _synctex_display_void_vbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* vertical void box node creator */ +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_void_vbox:(synctex_class_t)&synctex_class_void_vbox; + } + return node; +} + +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner); +void _synctex_display_void_hbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* horizontal void box node creator */ +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_void_hbox:(synctex_class_t)&synctex_class_void_hbox; + } + return node; +} + +/* The medium nodes correspond to kern, glue, penalty and math nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */ +} synctex_medium_node_t; + +#define SYNCTEX_IS_BOX(NODE)\ + ((NODE->class->type == synctex_node_type_vbox)\ + || (NODE->class->type == synctex_node_type_void_vbox)\ + || (NODE->class->type == synctex_node_type_hbox)\ + || (NODE->class->type == synctex_node_type_void_hbox)) + +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE)) + +void _synctex_log_medium_node(synctex_node_t node); + +/* math node creator */ +synctex_node_t _synctex_new_math(synctex_scanner_t scanner); +void _synctex_display_math(synctex_node_t node); + +static _synctex_class_t synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_math(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_math:(synctex_class_t)&synctex_class_math; + } + return node; +} + +/* kern node creator */ +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner); +void _synctex_display_kern(synctex_node_t node); + +static _synctex_class_t synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_kern:(synctex_class_t)&synctex_class_kern; + } + return node; +} + +/* The small nodes correspond to glue and boundary nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT */ +} synctex_small_node_t; + +void _synctex_log_small_node(synctex_node_t node); +/* glue node creator */ +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner); +void _synctex_display_glue(synctex_node_t node); + +static _synctex_class_t synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_glue:(synctex_class_t)&synctex_class_glue; + } + return node; +} + +/* boundary node creator */ +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner); +void _synctex_display_boundary(synctex_node_t node); + +static _synctex_class_t synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_small_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_small_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_boundary:(synctex_class_t)&synctex_class_boundary; + } + return node; +} + +# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR + +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling, + * SYNCTEX_TAG,SYNCTEX_NAME */ +} synctex_input_t; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner); +void _synctex_free_input(synctex_node_t node); +void _synctex_display_input(synctex_node_t node); +void _synctex_log_input(synctex_node_t sheet); + +static _synctex_class_t synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + NULL, /* No parent */ + NULL, /* No child */ + &_synctex_implementation_0, /* sibling */ + NULL, /* No friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_1 +}; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_input:(synctex_class_t)&synctex_class_input; + } + return node; +} +void _synctex_free_input(synctex_node_t node){ + if(node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(SYNCTEX_NAME(node)); + free(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_t synctex_node_parent(synctex_node_t node) +{ + return SYNCTEX_PARENT(node); +} +synctex_node_t synctex_node_sheet(synctex_node_t node) +{ + while(node && node->class->type != synctex_node_type_sheet) { + node = SYNCTEX_PARENT(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_t synctex_node_child(synctex_node_t node) +{ + return SYNCTEX_CHILD(node); +} +synctex_node_t synctex_node_sibling(synctex_node_t node) +{ + return SYNCTEX_SIBLING(node); +} +synctex_node_t synctex_node_next(synctex_node_t node) { + if(SYNCTEX_CHILD(node)) { + return SYNCTEX_CHILD(node); + } +sibling: + if(SYNCTEX_SIBLING(node)) { + return SYNCTEX_SIBLING(node); + } + if((node = SYNCTEX_PARENT(node))) { + if(node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } + goto sibling; + } + return NULL; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_t node) { + if(node) { + return (((node)->class))->type; + } + return synctex_node_type_error; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_t node) { +static const char * isa[synctex_node_number_of_types] = + {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_LOG +# endif + +# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log) + +/* Public node logger */ +void synctex_node_log(synctex_node_t node) { + SYNCTEX_LOG(node); +} + +# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display) + +void synctex_node_display(synctex_node_t node) { + SYNCTEX_DISPLAY(node); +} + +void _synctex_display_input(synctex_node_t node) { + printf("....Input:%i:%s\n", + SYNCTEX_TAG(node), + SYNCTEX_NAME(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_log_sheet(synctex_node_t sheet) { + if(sheet) { + printf("%s:%i\n",synctex_node_isa(sheet),SYNCTEX_PAGE(sheet)); + printf("SELF:%p",(void *)sheet); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(sheet)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(sheet)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(sheet)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(sheet)); + } +} + +void _synctex_log_small_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_medium_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i:%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_void_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_horiz_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("/%i",SYNCTEX_HORIZ_V(node)); + printf(",%i",SYNCTEX_VERT_V(node)); + printf(":%i",SYNCTEX_WIDTH_V(node)); + printf(",%i",SYNCTEX_HEIGHT_V(node)); + printf(",%i",SYNCTEX_DEPTH_V(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_input(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%s",SYNCTEX_NAME(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); +} + +void _synctex_display_sheet(synctex_node_t sheet) { + if(sheet) { + printf("....{%i\n",SYNCTEX_PAGE(sheet)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(sheet)); + printf("....}\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(sheet)); + } +} + +void _synctex_display_vbox(synctex_node_t node) { + printf("....[%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....]\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_hbox(synctex_node_t node) { + printf("....(%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....)\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_vbox(synctex_node_t node) { + printf("....v%i,%i;%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_hbox(synctex_node_t node) { + printf("....h%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_glue(synctex_node_t node) { + printf("....glue:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_math(synctex_node_t node) { + printf("....math:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_kern(synctex_node_t node) { + printf("....kern:%i,%i:%i,%i:%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_boundary(synctex_node_t node) { + printf("....boundary:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif + +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * status<SYNCTEX_STATUS_EOF means an error + */ +typedef int synctex_status_t; +/* When the end of the synctex file has been reached: */ +# define SYNCTEX_STATUS_EOF 0 +/* When the function could not return the value it was asked for: */ +# define SYNCTEX_STATUS_NOT_OK (SYNCTEX_STATUS_EOF+1) +/* When the function returns the value it was asked for: */ +# define SYNCTEX_STATUS_OK (SYNCTEX_STATUS_NOT_OK+1) +/* Generic error: */ +# define SYNCTEX_STATUS_ERROR -1 +/* Parameter error: */ +# define SYNCTEX_STATUS_BAD_ARGUMENT -2 + +# define SYNCTEX_FILE (scanner->file) + +/* Actually, the minimum buffer size is driven by integer and float parsing. + * ¬±0.123456789e123 + */ +# define SYNCTEX_BUFFER_MIN_SIZE 16 +# define SYNCTEX_BUFFER_SIZE 32768 + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +void _synctex_log_void_box(synctex_node_t node); +void _synctex_log_box(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t node); +void _synctex_log_input(synctex_node_t node); +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr); +synctex_status_t _synctex_next_line(synctex_scanner_t scanner); +synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string); +synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref); +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref); +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref); +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner); +int _synctex_scan_postamble(synctex_scanner_t scanner); +synctex_status_t _synctex_setup_visible_box(synctex_node_t box); +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v); +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent); +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner); +int _synctex_node_is_box(synctex_node_t node); +int _synctex_bail(void); + +/* Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a null size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL, + * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned. + * The value returned in size_ptr is the number of bytes now available in the buffer. + * This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. */ +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) { + size_t available = 0; + if(NULL == scanner || NULL == size_ptr) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +# define size (* size_ptr) + if(size>SYNCTEX_BUFFER_SIZE){ + size = SYNCTEX_BUFFER_SIZE; + } + available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if(size<=available) { + /* There are already sufficiently many characters in the buffer */ + size = available; + return SYNCTEX_STATUS_OK; + } + if(SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; + if(available) { + memmove(SYNCTEX_START, SYNCTEX_CUR, available); + } + SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available); + if(already_read>0) { + /* We assume that 0<already_read<=SYNCTEX_BUFFER_SIZE - available, such that + * SYNCTEX_CUR + already_read = SYNCTEX_START + available + already_read <= SYNCTEX_START + SYNCTEX_BUFFER_SIZE */ + SYNCTEX_END = SYNCTEX_CUR + already_read; + /* If the end of the file was reached, all the required SYNCTEX_BUFFER_SIZE - available + * may not be filled with values from the file. + * In that case, the buffer should stop properly after already_read characters. */ + * SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_START; + size = SYNCTEX_END - SYNCTEX_CUR; /* == old available + already_read*/ + return SYNCTEX_STATUS_OK; /* May be available is less than size, the caller will have to test. */ + } else if(0>already_read) { + /* There is an error in zlib */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if(Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + } else { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + } + return SYNCTEX_STATUS_ERROR; + } else { + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + size = SYNCTEX_END - SYNCTEX_CUR; + return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */ + } + /* At this point, the function has already returned from above */ + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + size = available; + return SYNCTEX_STATUS_EOF; +# undef size +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +synctex_status_t _synctex_next_line(synctex_scanner_t scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + size_t available = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CUR<SYNCTEX_END) { + if(*SYNCTEX_CUR == '\n') { + ++SYNCTEX_CUR; + available = 1; + return _synctex_buffer_get_available_size(scanner, &available); + } + ++SYNCTEX_CUR; + } + /* Here, we have SYNCTEX_CUR == SYNCTEX_END, such that the next call to _synctex_buffer_get_available_size + * will read another bunch of synctex file. Little by little, we advance to the end of the file. */ + available = 1; + status = _synctex_buffer_get_available_size(scanner, &available); + if(status<=0) { + return status; + } + goto infinite_loop; +} + +/* Scan the given string. + * Both scanner and the_string must not be NULL, and the_string must not be 0 length. + * SYNCTEX_STATUS_OK is returned if the string is found, + * SYNCTEX_STATUS_EOF is returned when the EOF is reached, + * SYNCTEX_STATUS_NOT_OK is returned is the string is not found, + * an error status is returned otherwise. + * This is a critical method because buffering renders things more difficult. + * The given string might be as long as the maximum size_t value. + * As side effect, the buffer state may have changed if the given argument string can't fit into the buffer. + */ +synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string) { + size_t tested_len = 0; /* the number of characters at the beginning of the_string that match */ + size_t remaining_len = 0; /* the number of remaining characters of the_string that should match */ + size_t available = 0; + synctex_status_t status = 0; + if(NULL == scanner || NULL == the_string) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + remaining_len = strlen(the_string); /* All the_string should match */ + if(0 == remaining_len) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* How many characters available in the buffer? */ + available = remaining_len; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status<SYNCTEX_STATUS_EOF) { + return status; + } + /* Maybe we have less characters than expected because the buffer is too small. */ + if(available>=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if(strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } +return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if(strncmp((char *)SYNCTEX_CUR,the_string,available)) { + /* No need to goo further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if(SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= available; + tested_len += available; + SYNCTEX_CUR += available; /* We validate the tested characters. */ + if(0 == remaining_len) { + /* Nothing left to test, we have found the given string, we return the length. */ + return tested_len; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */ + /* available now corresponds to the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the buffer contents is completely replaced by _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= available; +more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + available = remaining_len; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status<SYNCTEX_STATUS_EOF) { + return status; /* This is an error, no need to go further. */ + } + if(available==0) { + /* Missing characters: recover the initial state of the file and return. */ +return_NOT_OK: + if(offset != gzseek(SYNCTEX_FILE,offset,SEEK_SET)) { + /* This is a critical error, we could not recover the previous state. */ + _synctex_error("can't seek file"); + return SYNCTEX_STATUS_ERROR; + } + /* Next time we are asked to fill the buffer, + * we will read a complete bunch of text from the file. */ + SYNCTEX_CUR = SYNCTEX_END; + return SYNCTEX_STATUS_NOT_OK; + } + if(available<remaining_len) { + /* We'll have to loop one more time. */ + if(strncmp((char *)SYNCTEX_CUR,the_string,available)) { + /* This is not the expected string, recover the previous state and return. */ + goto return_NOT_OK; + } + /* Advance the_string to the first untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= available; + tested_len += available; + SYNCTEX_CUR += available; /* We validate the tested characters. */ + if(0 == remaining_len) { + /* Nothing left to test, we have found the given string. */ + return SYNCTEX_STATUS_OK; + } + goto more_characters; + } + /* This is the last step. */ + if(strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + /* This is not the expected string, recover the previous state and return. */ + goto return_NOT_OK; + } + goto return_OK; + } else { + /* The buffer can't contain the given string argument, and the EOF was reached */ + return SYNCTEX_STATUS_EOF; + } +} + +/* Used when parsing the synctex file. + * Decode an integer. + * First, field separators, namely ':' and ',' characters are skipped + * The returned value is negative if there is an unrecoverable error. + * It is SYNCTEX_STATUS_NOT_OK if an integer could not be parsed, for example + * if the characters at the current cursor position are not digits or + * if the end of the file has been reached. + * It is SYNCTEX_STATUS_OK if an int has been successfully parsed. + * The given scanner argument must not be NULL, on the contrary, value_ref may be NULL. + */ +synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref) { + char * ptr = NULL; + char * end = NULL; + int result = 0; + size_t available = 0; + synctex_status_t status = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + available = SYNCTEX_BUFFER_MIN_SIZE; + status = _synctex_buffer_get_available_size(scanner, &available); + if(status<SYNCTEX_STATUS_EOF) { + return status;/* Forward error. */ + } + if(available==0) { + return SYNCTEX_STATUS_EOF;/* it is the end of file. */ + } + ptr = SYNCTEX_CUR; + if(*ptr==':' || *ptr==',') { + ++ptr; + --available; + if(available==0) { + return SYNCTEX_STATUS_NOT_OK;/* It is not possible to scan an int */ + } + } + result = (int)strtol(ptr, &end, 10); + if(end>ptr) { + SYNCTEX_CUR = end; + if(value_ref) { + * value_ref = result; + } + return SYNCTEX_STATUS_OK;/* Successfully scanned an int */ + } + return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */ +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was alloced on the heap, the caller is the owner and + * is responsible to free it in due time. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) { + char * end = NULL; + size_t current_size = 0; + size_t new_size = 0; + size_t len = 0;/* The number of bytes to copy */ + size_t available = 0; + synctex_status_t status = 0; + if(NULL == scanner || NULL == value_ref) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if(SYNCTEX_CUR>=SYNCTEX_END) { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status < 0) { + return status; + } + if(0 == available) { + return SYNCTEX_STATUS_EOF; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + * value_ref = NULL;/* Initialize, it will be realloc'ed */ + /* We scan all the characters up to the next '\n' */ +next_character: + if(end<SYNCTEX_END) { + if(*end == '\n') { + /* OK, we found where to stop */ + len = end - SYNCTEX_CUR; + if(current_size>UINT_MAX-len-1) { + /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX. + * We return the missing amount of memory. + * This will never occur in practice. */ + return UINT_MAX-len-1 - current_size; + } + new_size = current_size+len; + /* We have current_size+len+1<=UINT_MAX + * or equivalently new_size<UINT_MAX, + * where we have assumed that len<UINT_MAX */ + if((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { + if(memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { + (* value_ref)[new_size]='\0'; /* Terminate the string */ + SYNCTEX_CUR += len;/* Advance to the terminating '\n' */ + return SYNCTEX_STATUS_OK; + } + free(* value_ref); + * value_ref = NULL; + _synctex_error("could not copy memory (1)."); + return SYNCTEX_STATUS_ERROR; + } + _synctex_error("could not allocate memory (1)."); + return SYNCTEX_STATUS_ERROR; + } else { + ++end; + goto next_character; + } + } else { + /* end == SYNCTEX_END */ + len = SYNCTEX_END - SYNCTEX_CUR; + if(current_size>UINT_MAX-len-1) { + /* We have reached the limit. */ + _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1)); + return SYNCTEX_STATUS_ERROR; + } + new_size = current_size+len; + if((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { + if(memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { + (* value_ref)[new_size]='\0'; /* Terminate the string */ + SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */ + return SYNCTEX_STATUS_OK; + } + free(* value_ref); + * value_ref = NULL; + _synctex_error("could not copy memory (2)."); + return SYNCTEX_STATUS_ERROR; + } + /* Huge memory problem */ + _synctex_error("could not allocate memory (2)."); + return SYNCTEX_STATUS_ERROR; + } +} + +/* Used when parsing the synctex file. + * Read an Input record. + */ +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) { + synctex_status_t status = 0; + size_t available = 0; + synctex_node_t input = NULL; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_match_string(scanner,"Input:"); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + /* Create a node */ + input = _synctex_new_input(scanner); + if(NULL == input) { + _synctex_error("could not create an input node."); + return SYNCTEX_STATUS_ERROR; + } + /* Decode the synctag */ + status = _synctex_decode_int(scanner,&(SYNCTEX_TAG(input))); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("bad format of input node."); + SYNCTEX_FREE(input); + return status; + } + /* The next character is a field separator, we expect one character in the buffer. */ + available = 1; + status = _synctex_buffer_get_available_size(scanner, &available); + if(status<=SYNCTEX_STATUS_ERROR) { + return status; + } + if(0 == available) { + return SYNCTEX_STATUS_EOF; + } + /* We can now safely advance to the next character, stepping over the field separator. */ + ++SYNCTEX_CUR; + --available; + /* Then we scan the file name */ + status = _synctex_decode_string(scanner,&(SYNCTEX_NAME(input))); + if(status<SYNCTEX_STATUS_OK) { + SYNCTEX_FREE(input); + return status; + } + /* Prepend this input node to the input linked list of the scanner */ + SYNCTEX_SET_SIBLING(input,scanner->input); + scanner->input = input; + return _synctex_next_line(scanner);/* read the line termination character, if any */ + /* Now, set up the path */ +} + +typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *); + +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) { + synctex_status_t status = 0; + if(NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if(status<SYNCTEX_STATUS_NOT_OK) { + return status; + } else if(status == SYNCTEX_STATUS_NOT_OK) { + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + goto not_found; + } + /* A line is found, scan the value */ + return (*decoder)(scanner,value_ref); +} + +/* Used when parsing the synctex file. + * Read the preamble. + */ +synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner) { + synctex_status_t status = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_scan_named(scanner,"SyncTeX Version:",&(scanner->version),(synctex_decoder_t)&_synctex_decode_int); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + /* Read all the input records */ + do { + status = _synctex_scan_input(scanner); + if(status<SYNCTEX_STATUS_NOT_OK) { + return status; + } + } while(status == SYNCTEX_STATUS_OK); + /* the loop exits when status == SYNCTEX_STATUS_NOT_OK */ + /* Now read all the required settings. */ + status = _synctex_scan_named(scanner,"Output:",&(scanner->output_fmt),(synctex_decoder_t)&_synctex_decode_string); + if(status<SYNCTEX_STATUS_NOT_OK) { + return status; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_scan_named(scanner,"Magnification:",&(scanner->pre_magnification),(synctex_decoder_t)&_synctex_decode_int); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_scan_named(scanner,"Unit:",&(scanner->pre_unit),(synctex_decoder_t)&_synctex_decode_int); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_scan_named(scanner,"X Offset:",&(scanner->pre_x_offset),(synctex_decoder_t)&_synctex_decode_int); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_scan_named(scanner,"Y Offset:",&(scanner->pre_y_offset),(synctex_decoder_t)&_synctex_decode_int); + if(status<SYNCTEX_STATUS_OK) { + return status; + } + return _synctex_next_line(scanner); +} + +/* parse a float with a dimension */ +synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref) { + synctex_status_t status = 0; + char * endptr = NULL; + float f = 0; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + size_t available = 0; + if(NULL == scanner || NULL == value_ref) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + available = SYNCTEX_BUFFER_MIN_SIZE; + status = _synctex_buffer_get_available_size(scanner, &available); + if(status<SYNCTEX_STATUS_EOF) { + _synctex_error("problem with float."); + return status; + } +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, "C"); +#endif + f = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if(endptr == SYNCTEX_CUR) { + _synctex_error("a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + if((status = _synctex_match_string(scanner,"in")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536; + } else if(status<SYNCTEX_STATUS_EOF) { +report_unit_error: + _synctex_error("problem with unit."); + return status; + } else if((status = _synctex_match_string(scanner,"cm")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/2.54f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/25.4f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { + f *= 65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f/72*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { + f *= 12.0*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { + f *= 1.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { + f *= 1238.0f/1157*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { + f *= 14856.0f/1157*65536; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { + f *= 685.0f/642*65536; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { + f *= 1370.0f/107*65536; + } else if(status<0) { + goto report_unit_error; + } + *value_ref = f; + return SYNCTEX_STATUS_OK; +} + +/* parse the post scriptum + * SYNCTEX_STATUS_OK is returned on completion + * a negative error is returned otherwise */ +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) { + synctex_status_t status = 0; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Scan the file until a post scriptum line is found */ +post_scriptum_not_found: + status = _synctex_match_string(scanner,"Post scriptum:"); + if(status<SYNCTEX_STATUS_NOT_OK) { + return status; + } + if(status == SYNCTEX_STATUS_NOT_OK) { + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_EOF) { + return status; + } else if(status<SYNCTEX_STATUS_OK) { + return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */ + } + goto post_scriptum_not_found; + } + /* We found the name, advance to the next line. */ +next_line: + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_EOF) { + return status; + } else if(status<SYNCTEX_STATUS_OK) { + return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */ + } + /* Scanning the information */ + status = _synctex_match_string(scanner,"Magnification:"); + if(status == SYNCTEX_STATUS_OK ) { +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, "C"); +#endif + scanner->unit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if(endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if(scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if(status<SYNCTEX_STATUS_EOF){ +report_record_problem: + _synctex_error("Problem reading the Post Scriptum records"); + return status; /* echo the error. */ + } + status = _synctex_match_string(scanner,"X Offset:"); + if(status == SYNCTEX_STATUS_OK) { + status = _synctex_scan_float_and_dimension(scanner, &(scanner->x_offset)); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("problem with X offset in the Post Scriptum."); + return status; + } + goto next_line; + } else if(status<SYNCTEX_STATUS_EOF){ + goto report_record_problem; + } + status = _synctex_match_string(scanner,"Y Offset:"); + if(status==SYNCTEX_STATUS_OK) { + status = _synctex_scan_float_and_dimension(scanner, &(scanner->y_offset)); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("problem with Y offset in the Post Scriptum."); + return status; + } + goto next_line; + } else if(status<SYNCTEX_STATUS_EOF){ + goto report_record_problem; + } + goto next_line; +} + +/* SYNCTEX_STATUS_OK is returned if the postamble is read + * SYNCTEX_STATUS_NOT_OK is returned if the postamble is not at the current location + * a negative error otherwise + * The postamble comprises the post scriptum section. + */ +int _synctex_scan_postamble(synctex_scanner_t scanner) { + int status = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_match_string(scanner,"Postamble:"); + if(status < SYNCTEX_STATUS_OK) { + return status; + } +count_again: + status = _synctex_next_line(scanner); + if(status < SYNCTEX_STATUS_OK) { + return status; + } + status = _synctex_scan_named(scanner,"Count:",&(scanner->count),(synctex_decoder_t)&_synctex_decode_int); + if(status < SYNCTEX_STATUS_EOF) { + return status; /* forward the error */ + } else if(status < SYNCTEX_STATUS_OK) { /* No Count record found */ + status = _synctex_next_line(scanner); /* Advance one more line */ + if(status<SYNCTEX_STATUS_OK) { + return status; + } + goto count_again; + } + /* Now we scan the last part of the SyncTeX file: the Post Scriptum section. */ + return _synctex_scan_post_scriptum(scanner); +} + +/* Horizontal boxes also have visible size. + * Visible size are bigger than real size. + * For example 0 width boxes may contain text. + * At creation time, the visible size is set to the values of the real size. + */ +synctex_status_t _synctex_setup_visible_box(synctex_node_t box) { + if(box) { + switch(box->class->type) { + case synctex_node_type_hbox: + if(SYNCTEX_INFO(box) != NULL) { + SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box); + SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box); + SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box); + SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box); + SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box); + return SYNCTEX_STATUS_OK; + } + return SYNCTEX_STATUS_ERROR; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v) { +# ifdef __DARWIN_UNIX03 +# pragma unused(v) +# endif + int itsBtm, itsTop; + if(NULL == node || node->class->type != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if(SYNCTEX_WIDTH_V(node)<0) { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node); + if(h<itsBtm) { + SYNCTEX_HORIZ_V(node) = h; + SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - itsTop; + } else if(h>itsTop) { + SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h; + } + } else { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node); + if(h<itsBtm) { + SYNCTEX_HORIZ_V(node) = h; + SYNCTEX_WIDTH_V(node) = itsTop - SYNCTEX_HORIZ_V(node); + } else if(h>itsTop) { + SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node); + } + } + return SYNCTEX_STATUS_OK; +} + +/* Used when parsing the synctex file. + * The sheet argument is a newly created sheet node that will hold the contents. + * Something is returned in case of error. + */ +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t sheet) { + synctex_node_t parent = sheet; + synctex_node_t child = NULL; + synctex_node_t sibling = NULL; + synctex_node_t box = sheet; + int friend_index = 0; + synctex_info_t * info = NULL; + synctex_status_t status = 0; + size_t available = 0; + if((NULL == scanner) || (NULL == sheet)) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* We MUST start with a box, so at this level, the unique possibility is '[', '(' or "}". */ +prepare_loop: + if(SYNCTEX_CUR<SYNCTEX_END) { + if(*SYNCTEX_CUR == '[') { +scan_vbox: + ++SYNCTEX_CUR; + if((child = _synctex_new_vbox(scanner)) && (info = SYNCTEX_INFO(child))) { +# define SYNCTEX_DECODE_FAILED(WHAT) \ + (_synctex_decode_int(scanner,&(info[WHAT].INT))<SYNCTEX_STATUS_OK) + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad vbox record."); + #define SYNCTEX_RETURN(STATUS) return STATUS; + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + parent = child; + child = NULL; + goto child_loop;/* next created node will be a child */ + } else { + _synctex_error("Can't create vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '(') { +scan_hbox: + ++SYNCTEX_CUR; + if((child = _synctex_new_hbox(scanner)) && (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_setup_visible_box(child)<SYNCTEX_STATUS_OK + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad hbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child)); + SYNCTEX_SET_CHILD(parent,child); + parent = child; + child = NULL; + goto child_loop;/* next created node will be a child */ + } else { + _synctex_error("Can't create hbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '}') { +scan_teehs: + ++SYNCTEX_CUR; + if(NULL == parent || parent->class->type != synctex_node_type_sheet + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Unexpected end of sheet."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_RETURN(SYNCTEX_STATUS_OK); + } else if(*SYNCTEX_CUR == '!') { +scan_anchor: + ++SYNCTEX_CUR; + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing anchor."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto prepare_loop; + } else { + /* _synctex_error("Ignored record %c\n",*SYNCTEX_CUR); */ + ++SYNCTEX_CUR; + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Unexpected end."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto prepare_loop; + } + } else { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status<SYNCTEX_STATUS_OK && available>0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto prepare_loop; + } + } + _synctex_bail(); +/* The child loop means that we go do one level, when we just created a box node, + * the next node created is a child of this box. */ +child_loop: + if(SYNCTEX_CUR<SYNCTEX_END) { + if(*SYNCTEX_CUR == '[') { + goto scan_vbox; + } else if(*SYNCTEX_CUR == ']') { +scan_xobv: + ++SYNCTEX_CUR; + if(NULL != parent && parent->class->type == synctex_node_type_vbox) { + #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\ + friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + if(NULL == SYNCTEX_CHILD(parent)) { + /* only void boxes are friends */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected ']', ignored."); + } + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Uncomplete sheet."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto sibling_loop; + } else if(*SYNCTEX_CUR == '(') { + goto scan_hbox; + } else if(*SYNCTEX_CUR == ')') { +scan_xobh: + ++SYNCTEX_CUR; + if((parent) && parent->class->type == synctex_node_type_hbox) { + if(NULL == child) { + /* Only boxes with no children are friends, + * boxes with children are indirectly friends through one of their descendants. */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */ + SYNCTEX_SET_NEXT_HORIZ_BOX(box,parent); + box = parent; + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected ')', ignored."); + } + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Uncomplete sheet."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto sibling_loop; + } else if(*SYNCTEX_CUR == 'v') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_void_vbox(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad void vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + #define SYNCTEX_UPDATE_FRIEND(NODE)\ + friend_index = (info[SYNCTEX_TAG_IDX].INT+info[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'h') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_void_hbox(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad void hbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create void hbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'k') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_kern(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad kern record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)-SYNCTEX_WIDTH(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create kern record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'g') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_glue(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad glue record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create glue record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '$') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_math(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad math record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create math record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'x') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_boundary(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad boundary record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_CHILD(parent,child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create math record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '}') { + goto scan_teehs; + } else if(*SYNCTEX_CUR == '!') { + goto scan_anchor; + } else { + /* _synctex_error("Ignored record %c\n",*SYNCTEX_CUR); */ + ++SYNCTEX_CUR; + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Unexpected end."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto child_loop; + } + } else { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status<SYNCTEX_STATUS_OK && available>0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto child_loop; + } + } + _synctex_bail(); +/* The vertical loop means that we are on the same level, for example when we just ended a box. + * If a node is created now, it will be a sibling of the current node, sharing the same parent. */ +sibling_loop: + if(SYNCTEX_CUR<SYNCTEX_END) { + if(*SYNCTEX_CUR == '[') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_vbox(scanner)) + && NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad vbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + parent = sibling; + child = NULL; + goto child_loop; + } else { + _synctex_error("Can't create vbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == ']') { + goto scan_xobv; + } else if(*SYNCTEX_CUR == '(') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_hbox(scanner)) && + NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_setup_visible_box(sibling)<SYNCTEX_STATUS_OK + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad hbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child)); + parent = child; + child = NULL; + goto child_loop; + } else { + _synctex_error("Can't create hbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == ')') { + goto scan_xobh; + } else if(*SYNCTEX_CUR == 'v') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_void_vbox(scanner)) && + NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad void vbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("can't create void vbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'h') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_void_hbox(scanner)) && + NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad void hbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("can't create void hbox record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'k') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_kern(scanner)) + && NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad kern record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child)-SYNCTEX_WIDTH(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create kern record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'g') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_glue(scanner)) + && NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad glue record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create glue record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '$') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_math(scanner)) + && NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad math record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create math record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'x') { + ++SYNCTEX_CUR; + if(NULL != (sibling = _synctex_new_boundary(scanner)) + && NULL != (info = SYNCTEX_INFO(sibling))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Bad boundary record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + SYNCTEX_SET_SIBLING(child,sibling); + child = sibling; + SYNCTEX_UPDATE_FRIEND(child); + _synctex_horiz_box_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child)); + goto sibling_loop; + } else { + _synctex_error("Can't create boundary record (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == '}') { + goto scan_teehs; + } else if(*SYNCTEX_CUR == '!') { + ++SYNCTEX_CUR; + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing anchor (2)."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto sibling_loop; + } else { + ++SYNCTEX_CUR; + /* _synctex_error("Ignored record %c(2)\n",*SYNCTEX_CUR); */ + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto sibling_loop; + } + } else { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status<SYNCTEX_STATUS_OK && available>0){ + goto sibling_loop; + } else { + _synctex_error("Uncomplete sheet(2)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } +# undef SYNCTEX_DECODE_FAILED +} + +/* Used when parsing the synctex file + */ +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) { + synctex_node_t sheet = NULL; + synctex_status_t status = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* set up the lists of friends */ + if(NULL == scanner->lists_of_friends) { + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t)); + if(NULL == scanner->lists_of_friends) { + _synctex_error("malloc:2"); + return SYNCTEX_STATUS_ERROR; + } + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if(status<SYNCTEX_STATUS_EOF) { + return status; + } + if(_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Uncomplete Content."); + return SYNCTEX_STATUS_ERROR; + } + if(status == SYNCTEX_STATUS_NOT_OK) { + goto content_not_found; + } +next_sheet: + if(*SYNCTEX_CUR != '{') { + status = _synctex_scan_postamble(scanner); + if(status < SYNCTEX_STATUS_EOF) { + _synctex_error("Bad content."); + return status; + } + if(status<SYNCTEX_STATUS_OK) { + status = _synctex_next_line(scanner); + if(status < SYNCTEX_STATUS_OK) { + _synctex_error("Bad content."); + return status; + } + goto next_sheet; + } + return SYNCTEX_STATUS_OK; + } + ++SYNCTEX_CUR; + /* Create a new sheet node */ + sheet = _synctex_new_sheet(scanner); + status = _synctex_decode_int(scanner,&(SYNCTEX_PAGE(sheet))); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("Missing sheet number."); +bail: + SYNCTEX_FREE(sheet); + return SYNCTEX_STATUS_ERROR; + } + status = _synctex_next_line(scanner); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("Uncomplete file."); + goto bail; + } + status = _synctex_scan_sheet(scanner,sheet); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("Bad sheet content."); + goto bail; + } + SYNCTEX_SET_SIBLING(sheet,scanner->sheet); + scanner->sheet = sheet; + sheet = NULL; + /* Now read the list of Inputs between 2 sheets. */ + do { + status = _synctex_scan_input(scanner); + if(status<SYNCTEX_STATUS_EOF) { + _synctex_error("Bad input section."); + goto bail; + } + } + while(status >= SYNCTEX_STATUS_OK); + goto next_sheet; +} + +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* Where the synctex scanner is created. */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + gzFile file = NULL; + char * synctex = NULL; + synctex_scanner_t scanner = NULL; + synctex_io_mode_t io_mode = synctex_io_mode_read; + /* Here we assume that int are smaller than void * */ + if(sizeof(int)>sizeof(void*)) { + _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out."); + return NULL; + } + /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ + if(SYNCTEX_BUFFER_SIZE >= UINT_MAX) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)"); + return NULL; + } + /* for integers: */ + if(SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)"); + return NULL; + } + /* now open the synctex file */ + if(_synctex_open(output,build_directory,&synctex,&file,synctex_NO,&io_mode) || !file) { + if(_synctex_open(output,build_directory,&synctex,&file,synctex_YES,&io_mode) || !file) { + return NULL; + } + } + scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t)); + if(NULL == scanner) { + _synctex_error("SyncTeX: malloc problem"); + free(synctex); + gzclose(file); + return NULL; + } + /* make a private copy of output for the scanner */ + if(NULL == (scanner->output = (char *)malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable."); + } else if(scanner->output != strcpy(scanner->output,output)) { + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable."); + } + scanner->synctex = synctex;/* Now the scanner owns synctex */ + SYNCTEX_FILE = file; + return parse? synctex_scanner_parse(scanner):scanner; +} + +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex an filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * 0 on success, non 0 on error. */ +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + if(synctex_name_ref && file_ref) { + char * quoteless = NULL; + synctex_io_mode_t io_mode = *io_modeRef; + const char * mode = synctex_io_modes[io_mode]; + size_t size = 0; + /* now create the synctex file name */ + size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + synctex_name = (char *)malloc(size); + if(NULL == synctex_name) { + _synctex_error("! __synctex_open: Memory problem (1)\n"); + return 1; + } + /* we have reserved for synctex enough memory to copy output, both suffices and 2 quotes, + * including the terminating character. size is free now. */ + if(synctex_name != strcpy(synctex_name,output)) { + _synctex_error("! __synctex_open: Copy problem\n"); +return_on_error: + free(synctex_name); + synctex_name = NULL;/* Don't forget to reinitialize. */ + the_file = NULL; /* Here as well */ + free(quoteless); + return 2; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(synctex_name); + if(!strlen(synctex_name)) { + goto return_on_error; + } + /* now insert quotes. */ + if(add_quotes) { + char * quoted = NULL; + if(_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless = synctex_name; + synctex_name = quoted; + } + /* Now add the first path extension. */ + if(synctex_name != strcat(synctex_name,synctex_suffix)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* To quoteless as well. */ + if(quoteless && (quoteless != strcat(quoteless,synctex_suffix))){ + free(quoteless); + quoteless = NULL; + } + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + goto return_on_error; + } + /* Try the compressed version */ + if(synctex_name != strcat(synctex_name,synctex_suffix_gz)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + ++io_mode; + mode = synctex_io_modes[io_mode]; /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* To quoteless as well. */ + if(quoteless && (quoteless != strcat(quoteless,synctex_suffix_gz))){ + free(quoteless); + quoteless = NULL; + } + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if(quoteless) { + gzclose(the_file); + if(rename(synctex_name,quoteless)) { + _synctex_error("SyncTeX: could not rename %s to %s, error %i\n",synctex_name,quoteless,errno); + /* Reopen the file. */ + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open again %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } else { + if(NULL == (the_file = gzopen(quoteless,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open renamed %s, error %i\n",quoteless,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(synctex_name); + synctex_name = quoteless; + quoteless = NULL; + } + } + /* We are returning properly so we can also return the proper io_mode */ + *io_modeRef = io_mode; + return 0; + } + return 3; /* Bad parameter. */ +# undef synctex_name +# undef the_file +} + +/* Opens the ouput file, taking into account the eventual build_directory. + * 0 on success, non 0 on error. */ +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_modeRef); + if((result || !*file_ref) && build_directory && strlen(build_directory)) { + char * build_output; + const char * lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; + is_absolute = _synctex_path_is_absolute(build_directory); + if(!is_absolute) { + size += strlen(output); + } + if((build_output = (char *)malloc(size))) { + if(is_absolute) { + build_output[0] = '\0'; + } else { + if(build_output != strcpy(build_output,output)) { + return -4; + } + build_output[lpc-output]='\0'; + } + if(build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if(!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if(build_output != strcat(build_output,"/")) { + return -2; + } + } + /* Append the last path component of the output. */ + if(build_output != strcat(build_output,lpc)) { + return -3; + } + return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_modeRef); + } + } + return -1; + } + return result; +# undef synctex_name +# undef the_file +} + +/* The scanner destructor + */ +void synctex_scanner_free(synctex_scanner_t scanner) { + if(NULL == scanner) { + return; + } + if(SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + SYNCTEX_FREE(scanner->sheet); + SYNCTEX_FREE(scanner->input); + free(SYNCTEX_START); + free(scanner->output_fmt); + free(scanner->output); + free(scanner->synctex); + free(scanner->lists_of_friends); + free(scanner); +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner) { + synctex_status_t status = 0; + if(!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overriden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->class[synctex_node_type_sheet] = synctex_class_sheet; + scanner->class[synctex_node_type_input] = synctex_class_input; + (scanner->class[synctex_node_type_input]).scanner = scanner; + (scanner->class[synctex_node_type_sheet]).scanner = scanner; + scanner->class[synctex_node_type_vbox] = synctex_class_vbox; + (scanner->class[synctex_node_type_vbox]).scanner = scanner; + scanner->class[synctex_node_type_void_vbox] = synctex_class_void_vbox; + (scanner->class[synctex_node_type_void_vbox]).scanner = scanner; + scanner->class[synctex_node_type_hbox] = synctex_class_hbox; + (scanner->class[synctex_node_type_hbox]).scanner = scanner; + scanner->class[synctex_node_type_void_hbox] = synctex_class_void_hbox; + (scanner->class[synctex_node_type_void_hbox]).scanner = scanner; + scanner->class[synctex_node_type_kern] = synctex_class_kern; + (scanner->class[synctex_node_type_kern]).scanner = scanner; + scanner->class[synctex_node_type_glue] = synctex_class_glue; + (scanner->class[synctex_node_type_glue]).scanner = scanner; + scanner->class[synctex_node_type_math] = synctex_class_math; + (scanner->class[synctex_node_type_math]).scanner = scanner; + scanner->class[synctex_node_type_boundary] = synctex_class_boundary; + (scanner->class[synctex_node_type_boundary]).scanner = scanner; + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if(NULL == SYNCTEX_START) { + _synctex_error("SyncTeX: malloc error"); + synctex_scanner_free(scanner); + return NULL; + } + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; + status = _synctex_scan_preamble(scanner); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("SyncTeX Error: Bad preamble\n"); +bailey: + synctex_scanner_free(scanner); + return NULL; + } + status = _synctex_scan_content(scanner); + if(status<SYNCTEX_STATUS_OK) { + _synctex_error("SyncTeX Error: Bad content\n"); + goto bailey; + } + /* Everything is finished, free the buffer, close the file */ + free((void *)SYNCTEX_START); + SYNCTEX_START = SYNCTEX_CUR = SYNCTEX_END = NULL; + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + /* Final tuning: set the default values for various parameters */ + /* 1 pre_unit = (scanner->pre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if(scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if(scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if(scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if(scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; + #undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_t scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_t scanner) { + if(NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + SYNCTEX_DISPLAY(scanner->input); + if(scanner->count<1000) { + printf("The sheets:\n"); + SYNCTEX_DISPLAY(scanner->sheet); + printf("The friends:\n"); + if(scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_t node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node) + ); + node = SYNCTEX_FRIEND(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public*/ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) { + synctex_node_t input = NULL; + if(NULL == scanner) { + return NULL; + } + input = scanner->input; + do { + if(tag == SYNCTEX_TAG(input)) { + return (SYNCTEX_NAME(input)); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return NULL; +} + +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + synctex_node_t input = NULL; + if(NULL == scanner) { + return 0; + } + input = scanner->input; + do { + if(_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) { + return SYNCTEX_TAG(input); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + size_t char_index = strlen(name); + if((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if(!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if(result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if(SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if(SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0<char_index) { + char_index -= 1; + if(SYNCTEX_IS_PATH_SEPARATOR(name[char_index]) + && (result = _synctex_scanner_get_tag(scanner,name+char_index+1))) { + return result; + } + } + } + } + return result; + } + } + return 0; +} +synctex_node_t synctex_scanner_input(synctex_scanner_t scanner) { + return scanner?scanner->input:NULL; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output?scanner->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) { + return NULL != scanner && scanner->synctex?scanner->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif +int synctex_node_h(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_HORIZ(node); +} +int synctex_node_v(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_VERT(node); +} +int synctex_node_width(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_WIDTH(node); +} +int synctex_node_box_h(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HORIZ(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_v(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_VERT(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_width(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_WIDTH(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_height(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HEIGHT(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_depth(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_DEPTH(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif +float synctex_node_visible_h(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; +} +float synctex_node_visible_v(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; +} +float synctex_node_visible_width(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; +} +float synctex_node_box_visible_h(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_v(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_width(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_height(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HEIGHT(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_depth(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_DEPTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Other public node attributes +# endif + +int synctex_node_page(synctex_node_t node){ + synctex_node_t parent = NULL; + if(!node) { + return -1; + } + parent = SYNCTEX_PARENT(node); + while(parent) { + node = parent; + parent = SYNCTEX_PARENT(node); + } + if(node->class->type == synctex_node_type_sheet) { + return SYNCTEX_PAGE(node); + } + return -1; +} +int synctex_node_tag(synctex_node_t node) { + return node?SYNCTEX_TAG(node):-1; +} +int synctex_node_line(synctex_node_t node) { + return node?SYNCTEX_LINE(node):-1; +} +int synctex_node_column(synctex_node_t node) { +# ifdef __DARWIN_UNIX03 +# pragma unused(node) +# endif + return -1; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Sheet +# endif + +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) { + if(scanner) { + synctex_node_t sheet = scanner->sheet; + while(sheet) { + if(page == SYNCTEX_PAGE(sheet)) { + return SYNCTEX_CHILD(sheet); + } + sheet = SYNCTEX_SIBLING(sheet); + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Query +# endif + +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) { +# ifdef __DARWIN_UNIX03 +# pragma unused(column) +# endif + int tag = synctex_scanner_get_tag(scanner,name); + size_t size = 0; + int friend_index = 0; + int max_line = 0; + synctex_node_t node = NULL; + if(tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return -1; + } + free(SYNCTEX_START); + SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL; + max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX; + while(line<max_line) { + /* This loop will only be performed once for advanced viewers */ + friend_index = (tag+line)%(scanner->number_of_lists); + if((node = (scanner->lists_of_friends)[friend_index])) { + do { + if((synctex_node_type(node)>=synctex_node_type_boundary) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if(SYNCTEX_START == NULL) { + /* We did not find any matching boundary, retry with glue or kern */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if((synctex_node_type(node)>=synctex_node_type_kern) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if(SYNCTEX_START == NULL) { + /* We did not find any matching glue or kern, retry with boxes */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if((tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + } + } + SYNCTEX_END = SYNCTEX_CUR; + /* Now reverse the order to have nodes in display order, and keep just a few nodes */ + if((SYNCTEX_START) && (SYNCTEX_END)) + { + synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START; + synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END; + end_ref -= 1; + while(start_ref < end_ref) { + node = *start_ref; + *start_ref = *end_ref; + *end_ref = node; + start_ref += 1; + end_ref -= 1; + } + /* Basically, we keep the first node for each parent. + * More precisely, we keep only nodes that are not descendants of + * their predecessor's parent. */ + start_ref = (synctex_node_t *)SYNCTEX_START; + end_ref = (synctex_node_t *)SYNCTEX_START; + next_end: + end_ref += 1; /* we allways have start_ref<= end_ref*/ + if(end_ref < (synctex_node_t *)SYNCTEX_END) { + node = *end_ref; + while((node = SYNCTEX_PARENT(node))) { + if(SYNCTEX_PARENT(*start_ref) == node) { + goto next_end; + } + } + start_ref += 1; + *start_ref = *end_ref; + goto next_end; + } + start_ref += 1; + SYNCTEX_END = (char *)start_ref; + } + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + ++line; +# endif + } + return 0; +} + +synctex_node_t synctex_next_result(synctex_scanner_t scanner) { + if(NULL == SYNCTEX_CUR) { + SYNCTEX_CUR = SYNCTEX_START; + } else { + SYNCTEX_CUR+=sizeof(synctex_node_t); + } + if(SYNCTEX_CUR<SYNCTEX_END) { + return *(synctex_node_t*)SYNCTEX_CUR; + } else { + return NULL; + } +} + +/* This struct records a point in TeX coordinates.*/ +typedef struct { + int h; + int v; +} synctex_point_t; + +/* This struct records distances, the left one is positive or 0 and the right one is negative or 0. + * When comparing the locations of 2 different graphical objects on the page, we will have to also record the + * horizontal distance as signed to keep track of the typesetting order.*/ +typedef struct { + int left; + int right; +} synctex_distances_t; + +typedef struct { + synctex_point_t left; + synctex_point_t right; +} synctex_offsets_t; + + +typedef struct { + synctex_node_t left; + synctex_node_t right; +} synctex_node_set_t; + +/* The smallest container between two has the smallest width or height. + * This comparison is used when there are 2 overlapping boxes that contain the hit point. + * For ConTeXt, the problem appears at each page. + * The chosen box is the one with the smallest height, then the smallest width. */ +SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node); + +/* Returns the distance between the hit point hitPoint=(H,V) and the given node. */ +synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); + +/* The best container is the deeper box that contains the hit point (H,V). + * _synctex_eq_deepest_container starts with node whereas + * _synctex_box_child_deepest starts with node's children, if any + * if node is not a box, or a void box, NULL is returned. + * We traverse the node tree in a deep first manner and stop as soon as a result is found. */ +static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible); + +/* Once a best container is found, the closest children are the closest nodes to the left or right of the hit point. + * Only horizontal and vertical offsets are used to compare the positions of the nodes. */ +SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); + +/* The closest container is the box that is the one closest to the given point. + * The "visible" version takes into account the visible dimensions instead of the real ones given by TeX. */ +SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible); + +#define SYNCTEX_MASK_LEFT 1 +#define SYNCTEX_MASK_RIGHT 2 + +int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v) { + synctex_node_t sheet = NULL; + synctex_node_t node = NULL; /* placeholder */ + synctex_node_t other_node = NULL; /* placeholder */ + synctex_point_t hitPoint = {0,0}; /* placeholder */ + synctex_node_set_t bestNodes = {NULL,NULL}; /* holds the best node */ + synctex_distances_t bestDistances = {INT_MAX,INT_MAX}; /* holds the best distances for the best node */ + synctex_node_t bestContainer = NULL; /* placeholder */ + if(NULL == (scanner = synctex_scanner_parse(scanner)) || 0 >= scanner->unit) {/* scanner->unit must be >0 */ + return 0; + } + /* Convert the given point to scanner integer coordinates */ + hitPoint.h = (h-scanner->x_offset)/scanner->unit; + hitPoint.v = (v-scanner->y_offset)/scanner->unit; + /* We will store in the scanner's buffer the result of the query. */ + free(SYNCTEX_START); + SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL; + /* Find the proper sheet */ + sheet = scanner->sheet; + while((sheet) && SYNCTEX_PAGE(sheet) != page) { + sheet = SYNCTEX_SIBLING(sheet); + } + if(NULL == sheet) { + return -1; + } + /* Now sheet points to the sheet node with proper page number */ + /* Here is how we work: + * At first we do not consider the visible box dimensions. This will cover the most frequent cases. + * Then we try with the visible box dimensions. + * We try to find a non void box containing the hit point. + * We browse all the horizontal boxes until we find one containing the hit point. */ + if((node = SYNCTEX_NEXT_HORIZ_BOX(sheet))) { + do { + if(_synctex_point_in_box(hitPoint,node,synctex_YES)) { + /* Maybe the hitPoint belongs to a contained vertical box. */ +end: + /* This trick is for catching overlapping boxes */ + if((other_node = SYNCTEX_NEXT_HORIZ_BOX(node))) { + do { + if(_synctex_point_in_box(hitPoint,other_node,synctex_YES)) { + node = _synctex_smallest_container(other_node,node); + } + } while((other_node = SYNCTEX_NEXT_HORIZ_BOX(other_node))); + } + /* node is the smallest horizontal box that contains hitPoint. */ + if((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) { + node = bestContainer; + } + _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES); + if(bestNodes.right && bestNodes.left) { + if((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left)) + || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left)) + || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) { + if((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) { + if(bestDistances.left>bestDistances.right) { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left; + } else { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right; + } + SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if(bestDistances.left>bestDistances.right) { + bestNodes.left = bestNodes.right; + } + bestNodes.right = NULL; + } else if(bestNodes.right) { + bestNodes.left = bestNodes.right; + } else if(!bestNodes.left){ + bestNodes.left = node; + } + if((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) { + * (synctex_node_t *)SYNCTEX_START = bestNodes.left; + SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + } while ((node = SYNCTEX_NEXT_HORIZ_BOX(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky */ + if((node = SYNCTEX_CHILD(sheet))) { + goto end; + } + return 0; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Utilities +# endif + +int _synctex_bail(void) { + _synctex_error("SyncTeX ERROR\n"); + return -1; +} +/* Rougly speaking, this is: + * node's h coordinate - hitPoint's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative.*/ +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if(node) { + int min,med,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node); + max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node)); + /* We allways have min <= max */ + if(hitPoint.h<min) { + return min - hitPoint.h; /* regions 1+4+7, result is > 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. + * For these boxes, no visible dimension available */ + min = SYNCTEX_HORIZ(node); + max = min + SYNCTEX_ABS_WIDTH(node); + /* We allways have min <= max */ + if(hitPoint.h<min) { + return min - hitPoint.h; /* regions 1+4+7, result is > 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = SYNCTEX_WIDTH(node); + if(max<0) { + min = SYNCTEX_HORIZ(node); + max = min - max; + } else { + min = -max; + max = SYNCTEX_HORIZ(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<<med<<<<max + * ................................. + * Actually, we do not take into account negative widths. + * There is a problem for such situation when there is efectively overlapping text. + * But this should be extremely rare. I guess that in that case, many different choices + * could be made, one being in contradiction of the other. + * It means that the best choice should be made according to the situation that occurs + * most frequently. + */ + if(hitPoint.h<min) { + return min - hitPoint.h + 1; /* penalty to ensure other nodes are chosen first in case of overlapping ones */ + } else if (hitPoint.h>max) { + return max - hitPoint.h - 1; /* same kind of penalty */ + } else if (hitPoint.h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */ + } else { + return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */ + } + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_HORIZ(node) - hitPoint.h; + } + } + return INT_MAX;/* We always assume that the node is faraway to the right*/ +} +/* Rougly speaking, this is: + * node's v coordinate - hitPoint's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative.*/ +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible); +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + if(node) { + int min,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT_V(node); + max = min + SYNCTEX_ABS_DEPTH_V(node); + min -= SYNCTEX_ABS_HEIGHT_V(node); + /* We allways have min <= max */ + if(hitPoint.v<min) { + return min - hitPoint.v; /* regions 1+2+3, result is > 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT(node); + max = min + SYNCTEX_ABS_DEPTH(node); + min -= SYNCTEX_ABS_HEIGHT(node); + /* We allways have min <= max */ + if(hitPoint.v<min) { + return min - hitPoint.v; /* regions 1+2+3, result is > 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_VERT(node) - hitPoint.v; + } + } + return INT_MAX;/* We always assume that the node is faraway to the top*/ +} + +SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) { + float height, other_height; + if(SYNCTEX_ABS_WIDTH(node)<SYNCTEX_ABS_WIDTH(other_node)) { + return node; + } + if(SYNCTEX_ABS_WIDTH(node)>SYNCTEX_ABS_WIDTH(other_node)) { + return other_node; + } + height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node); + other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node); + if(height<other_height) { + return node; + } + if(height>other_height) { + return other_node; + } + return node; +} + +synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if(node) { + if(0 == _synctex_point_h_distance(hitPoint,node,visible) + && 0 == _synctex_point_v_distance(hitPoint,node,visible)) { + return synctex_YES; + } + } + return synctex_NO; +} + +int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */ + if(node) { + int minH,maxH,minV,maxV; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_hbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative widths. */ + minH = SYNCTEX_HORIZ(node); + maxH = minH + SYNCTEX_ABS_WIDTH(node); + minV = SYNCTEX_VERT(node); + maxV = minV + SYNCTEX_ABS_DEPTH(node); + minV -= SYNCTEX_ABS_HEIGHT(node); + /* In what region is the point hitPoint=(H,V) ? */ + if(hitPoint.v<minV) { + if(hitPoint.h<minH) { + /* This is region 1. The distance to the box is the L1 distance PA. */ + result = minV - hitPoint.v + minH - hitPoint.h;/* Integer overflow? probability epsilon */ + } else if(hitPoint.h<=maxH) { + /* This is region 2. The distance to the box is the geometrical distance to the top edge. */ + result = minV - hitPoint.v; + } else { + /* This is region 3. The distance to the box is the L1 distance PB. */ + result = minV - hitPoint.v + hitPoint.h - maxH; + } + } else if(hitPoint.v<=maxV) { + if(hitPoint.h<minH) { + /* This is region 4. The distance to the box is the geometrical distance to the left edge. */ + result = minH - hitPoint.h; + } else if(hitPoint.h<=maxH) { + /* This is region 4. We are inside the box. */ + result = 0; + } else { + /* This is region 6. The distance to the box is the geometrical distance to the right edge. */ + result = hitPoint.h - maxH; + } + } else { + if(hitPoint.h<minH) { + /* This is region 7. The distance to the box is the L1 distance PC. */ + result = hitPoint.v - maxV + minH - hitPoint.h; + } else if(hitPoint.h<=maxH) { + /* This is region 8. The distance to the box is the geometrical distance to the top edge. */ + result = hitPoint.v - maxV; + } else { + /* This is region 9. The distance to the box is the L1 distance PD. */ + result = hitPoint.v - maxV + hitPoint.h - maxH; + } + } + break; + case synctex_node_type_kern: + maxH = SYNCTEX_WIDTH(node); + if(maxH<0) { + minH = SYNCTEX_HORIZ(node); + maxH = minH - maxH; + } else { + minH = -maxH; + maxH = SYNCTEX_HORIZ(node); + minH += maxH; + } + minV = SYNCTEX_VERT(node); + if(hitPoint.h<minH) { + if(hitPoint.v>minV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.h>maxH) { + if(hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - maxH; + } else { + result = minV - hitPoint.v + hitPoint.h - maxH; + } + } else if(hitPoint.v>minV) { + result = hitPoint.v - minV; + } else { + result = minV - hitPoint.v; + } + break; + case synctex_node_type_glue: + case synctex_node_type_math: + minH = SYNCTEX_HORIZ(node); + minV = SYNCTEX_VERT(node); + if(hitPoint.h<minH) { + if(hitPoint.v>minV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if(hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - minH; + } else { + result = minV - hitPoint.v + hitPoint.h - minH; + } + break; + } + } + return result; +} + +static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if(node) { + synctex_node_t result = NULL; + synctex_node_t child = NULL; + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + /* test the deep nodes first */ + if((child = SYNCTEX_CHILD(node))) { + do { + if((result = _synctex_eq_deepest_container(hitPoint,child,visible))) { + return result; + } + } while((child = SYNCTEX_SIBLING(child))); + } + /* is the hit point inside the box? */ + if(_synctex_point_in_box(hitPoint,node,visible)) { + /* for vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children. */ + if((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) { + int bestDistance = INT_MAX; + do { + if(SYNCTEX_CHILD(child)) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if(distance < bestDistance) { + bestDistance = distance; + node = child; + } + } + } while((child = SYNCTEX_SIBLING(child))); + } + return node; + } + } + } + return NULL; +} + +/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. */ +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) { + int result = 0; + if((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_h_distance(hitPoint,node,visible); + if(off7 > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if(bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if(bestDistancesRef->right == off7 && bestNodesRef->right) { + if(SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if(off7 == 0) { + /* hitPoint is inside node. */ + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0, hitPoint is to the right of node */ + off7 = -off7; + if(bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if(bestDistancesRef->left == off7 && bestNodesRef->left) { + if(SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if(result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if(result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + int result = 0; + if((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */ + if(off7 > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if(bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if(bestDistancesRef->right == off7 && bestNodesRef->right) { + if(SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if(off7 == 0) { + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0 */ + off7 = -off7; + if(bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if(bestDistancesRef->left == off7 && bestNodesRef->left) { + if(SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if(result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if(result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + if(node) { + switch(node->class->type) { + case synctex_node_type_hbox: + return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + case synctex_node_type_vbox: + return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + } + } + return 0; +} + +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible); +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) { + synctex_node_t best_node = NULL; + if((node = SYNCTEX_CHILD(node))) { + do { + int distance = _synctex_node_distance_to_point(hitPoint,node,visible); + synctex_node_t candidate = NULL; + if(distance<=*distanceRef) { + *distanceRef = distance; + best_node = node; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) { + best_node = candidate; + } + } + } while((node = SYNCTEX_SIBLING(node))); + } + return best_node; +} +SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if(node) { + switch(node->class->type) { + case synctex_node_type_hbox: + case synctex_node_type_vbox: + { + int best_distance = INT_MAX; + synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible); + if((best_node)) { + synctex_node_t child = NULL; + switch(best_node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if((child = SYNCTEX_CHILD(best_node))) { + best_distance = _synctex_node_distance_to_point(hitPoint,child,visible); + while((child = SYNCTEX_SIBLING(child))) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if(distance<=best_distance) { + best_distance = distance; + best_node = child; + } + } + } + } + } + return best_node; + } + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Updater +# endif + +typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */ + +# define SYNCTEX_BITS_PER_BYTE 8 + +struct __synctex_updater_t { + void *file; /* the foo.synctex or foo.synctex.gz I/O identifier */ + synctex_fprintf_t fprintf; /* either fprintf or gzprintf */ + int length; /* the number of chars appended */ + struct _flags { + unsigned int no_gz:1; /* Whether zlib is used or not */ + unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */ + } flags; +}; +# define SYNCTEX_FILE updater->file +# define SYNCTEX_NO_GZ ((updater->flags).no_gz) +# define SYNCTEX_fprintf (*(updater->fprintf)) + +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_t updater = NULL; + char * synctex = NULL; + synctex_io_mode_t io_mode = synctex_io_mode_read; + const char * mode; + /* prepare the updater */ + updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t)); + if(NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + if(_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_NO,&io_mode) + && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_YES,&io_mode)) { +return_on_error: + free(updater); + return NULL; + } + /* OK, the file exists */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_NO_GZ = io_mode%2?synctex_NO:synctex_YES; + mode = synctex_io_modes[io_mode+synctex_io_mode_append];/* either "a" or "ab", depending on the file extension */ + if(SYNCTEX_NO_GZ) { + if(NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) { +no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex); + free(synctex); + goto return_on_error; + } + updater->fprintf = (synctex_fprintf_t)(&fprintf); + } else { + if(NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) { + goto no_write_error; + } + updater->fprintf = (synctex_fprintf_t)(&gzprintf); + } + printf("SyncTeX: updating %s...",synctex); + free(synctex); + return updater; +} + + +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){ + if(NULL==updater) { + return; + } + if(magnification && strlen(magnification)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification); + } +} + +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){ + if(NULL==updater) { + return; + } + if(x_offset && strlen(x_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset); + } +} + +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){ + if(NULL==updater) { + return; + } + if(y_offset && strlen(y_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset); + } +} + +void synctex_updater_free(synctex_updater_t updater){ + if(NULL==updater) { + return; + } + if(updater->length>0) { + SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length); + } + if (SYNCTEX_NO_GZ) { + fclose((FILE *)SYNCTEX_FILE); + } else { + gzclose((gzFile)SYNCTEX_FILE); + } + free(updater); + printf("... done.\n"); + return; +} diff --git a/cut-n-paste/synctex/synctex_parser.h b/cut-n-paste/synctex/synctex_parser.h new file mode 100644 index 00000000..b164b7fb --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser.h @@ -0,0 +1,345 @@ +/* +Copyright (c) 2008, 2009, 2010 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Wed Jul 1 11:16:51 UTC 2009 + +Version: 1.12 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +#ifndef __SYNCTEX_PARSER__ +# define __SYNCTEX_PARSER__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* synctex_node_t is the type for all synctex nodes. + * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */ +typedef struct _synctex_node * synctex_node_t; + +/* The main synctex object is a scanner + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like display or edit queries + * - free the scanner when the work is done + */ +typedef struct __synctex_scanner_t _synctex_scanner_t; +typedef _synctex_scanner_t * synctex_scanner_t; + +/* This is the designated method to create a new synctex scanner object. + * output is the pdf/dvi/xdv file associated to the synctex file. + * If necessary, it can be the tex file that originated the synctex file + * but this might cause problems if the \jobname has a custom value. + * Despite this method can accept a relative path in practice, + * you should only pass a full path name. + * The path should be encoded by the underlying file system, + * assuming that it is based on 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and replaced by the proper extension. + * Then the private method _synctex_scanner_new_with_contents_of_file is called. + * NULL is returned in case of an error or non existent file. + * Once you have a scanner, use the synctex_display_query and synctex_edit_query below. + * The new "build_directory" argument is available since version 1.5. + * It is the directory where all the auxiliary stuff is created. + * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory. + * This is the case in MikTeX (I will include this into TeX Live). + * This directory path can be nil, it will be ignored. + * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file. + * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory. + * Please note that this new "build_directory" is provided as a convenient argument but should not be used. + * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file. + * The new "parse" argument is available since version 1.5. In general, use 1. + * Use 0 only if you do not want to parse the content but just check the existence. + */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + +/* This is the designated method to delete a synctex scanner object. + * Frees all the memory, you must call it when you are finished with the scanner. + */ +void synctex_scanner_free(synctex_scanner_t scanner); + +/* Send this message to force the scanner to parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, but if you need to access information more directly, + * you must be sure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner); + +/* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column)>0) { + * synctex_node_t node; + * while((node = synctex_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v + * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_t node; + * while(node = synctex_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. + * If one of this function returns a non positive integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column); +int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v); +synctex_node_t synctex_next_result(synctex_scanner_t scanner); + +/* Display all the information contained in the scanner object. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informatinal purpose to help developers. + */ +void synctex_scanner_display(synctex_scanner_t scanner); + +/* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ +int synctex_scanner_x_offset(synctex_scanner_t scanner); +int synctex_scanner_y_offset(synctex_scanner_t scanner); +float synctex_scanner_magnification(synctex_scanner_t scanner); + +/* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * + * if((input_node = synctex_scanner_input(scanner))){ + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag); +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +synctex_node_t synctex_scanner_input(synctex_scanner_t scanner); +const char * synctex_scanner_get_output(synctex_scanner_t scanner); +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner); + +/* Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, the child is one level deeper, + * and the sibling is at the same level. + * The sheet of a node is the first ancestor, it is of type sheet. + * A node and its sibling have the same parent. + * A node is the parent of its child. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_t node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content, you can retrieve the sheet node given the page. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node. + */ +synctex_node_t synctex_node_parent(synctex_node_t node); +synctex_node_t synctex_node_sheet(synctex_node_t node); +synctex_node_t synctex_node_child(synctex_node_t node); +synctex_node_t synctex_node_sibling(synctex_node_t node); +synctex_node_t synctex_node_next(synctex_node_t node); +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page); + +/* These are the types of the synctex nodes */ +typedef enum { + synctex_node_type_error = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_number_of_types +} synctex_node_type_t; + +/* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ +synctex_node_type_t synctex_node_type(synctex_node_t node); +const char * synctex_node_isa(synctex_node_t node); + +/* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ +void synctex_node_log(synctex_node_t node); +void synctex_node_display(synctex_node_t node); + +/* Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line. + * When the tag is known, the scanner of the node will give the corresponding file name. + * When the tag is known, the scanner of the node will give the name. + */ +int synctex_node_tag(synctex_node_t node); +int synctex_node_line(synctex_node_t node); +int synctex_node_column(synctex_node_t node); + +/* This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ +int synctex_node_page(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ +int synctex_node_h(synctex_node_t node); +int synctex_node_v(synctex_node_t node); +int synctex_node_width(synctex_node_t node); + +/* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +int synctex_node_box_h(synctex_node_t node); +int synctex_node_box_v(synctex_node_t node); +int synctex_node_box_width(synctex_node_t node); +int synctex_node_box_height(synctex_node_t node); +int synctex_node_box_depth(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + * These are expressed in page coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +float synctex_node_visible_h(synctex_node_t node); +float synctex_node_visible_v(synctex_node_t node); +float synctex_node_visible_width(synctex_node_t node); +/* For all nodes, visible dimensions of the enclosing box. + * A box is enclosing itself. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + */ +float synctex_node_box_visible_h(synctex_node_t node); +float synctex_node_box_visible_v(synctex_node_t node); +float synctex_node_box_visible_width(synctex_node_t node); +float synctex_node_box_visible_height(synctex_node_t node); +float synctex_node_box_visible_depth(synctex_node_t node); + +/* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ +typedef struct __synctex_updater_t _synctex_updater_t; +typedef _synctex_updater_t * synctex_updater_t; + +/* Designated initializer. + * Once you are done with your whole job, + * free the updater */ +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory); + +/* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification); +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset); +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset); + +/* You MUST free the updater, once everything is properly appended */ +void synctex_updater_free(synctex_updater_t updater); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cut-n-paste/synctex/synctex_parser_utils.c b/cut-n-paste/synctex/synctex_parser_utils.c new file mode 100644 index 00000000..0aef5777 --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser_utils.c @@ -0,0 +1,462 @@ +/* +Copyright (c) 2008, 2009 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Wed Nov 4 11:52:35 UTC 2009 + +Version: 1.9 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* In this file, we find all the functions that may depend on the operating system. */ + +#include <synctex_parser_utils.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include <limits.h> +#include <ctype.h> +#include <string.h> + +#include <sys/stat.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +#define SYNCTEX_WINDOWS 1 +#endif + +#ifdef _WIN32_WINNT_WINXP +#define SYNCTEX_RECENT_WINDOWS 1 +#endif + +#ifdef SYNCTEX_WINDOWS +#include <windows.h> +#endif + +void *_synctex_malloc(size_t size) { + void * ptr = malloc(size); + if(ptr) { +/* There used to be a switch to use bzero because it is more secure. JL */ + memset(ptr,0, size); + } + return (void *)ptr; +} + +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +# ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. + As it does not work on some older computers, + the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. + According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx + Minimum supported client Windows 2000 Professional + Minimum supported server Windows 2000 Server + People running Windows 2K standard edition will not have OutputDebugStringA. + JL.*/ + char *buff; + size_t len; + OutputDebugStringA("SyncTeX ERROR: "); + len = _vscprintf(reason, arg) + 1; + buff = (char*)malloc( len * sizeof(char) ); + result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); + OutputDebugStringA(buff); + OutputDebugStringA("\n"); + free(buff); + } +# else + result = fprintf(stderr,"SyncTeX ERROR: "); + result += vfprintf(stderr, reason, arg); + result += fprintf(stderr,"\n"); +# endif + va_end (arg); + return result; +} + +/* strip the last extension of the given string, this string is modified! */ +void _synctex_strip_last_path_extension(char * string) { + if(NULL != string){ + char * last_component = NULL; + char * last_extension = NULL; + char * next = NULL; + /* first we find the last path component */ + if(NULL == (last_component = strstr(string,"/"))){ + last_component = string; + } else { + ++last_component; + while((next = strstr(last_component,"/"))){ + last_component = next+1; + } + } +# ifdef SYNCTEX_WINDOWS + /* On Windows, the '\' is also a path separator. */ + while((next = strstr(last_component,"\\"))){ + last_component = next+1; + } +# endif + /* then we find the last path extension */ + if((last_extension = strstr(last_component,"."))){ + ++last_extension; + while((next = strstr(last_extension,"."))){ + last_extension = next+1; + } + --last_extension;/* back to the "." */ + if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ + last_extension[0] = '\0'; + } + } + } +} + +/* Compare two file names, windows is sometimes case insensitive... */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { +# if SYNCTEX_WINDOWS + /* On Windows, filename should be compared case insensitive. + * The characters '/' and '\' are both valid path separators. + * There will be a very serious problem concerning UTF8 because + * not all the characters must be toupper... + * I would like to have URL's instead of filenames. */ +next_character: + if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + return synctex_NO; + } + } else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + return synctex_NO; + } else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */ + return synctex_NO; + } else if (!*lhs) {/* lhs is at the end of the string */ + return *rhs ? synctex_NO : synctex_YES; + } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + goto next_character; +# else + return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; +# endif +} + +synctex_bool_t _synctex_path_is_absolute(const char * name) { + if(!strlen(name)) { + return synctex_NO; + } +# if SYNCTEX_WINDOWS + if(strlen(name)>2) { + return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; + } + return synctex_NO; +# else + return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; +# endif +} + +/* We do not take care of UTF-8 */ +const char * _synctex_last_path_component(const char * name) { + const char * c = name+strlen(name); + if(c>name) { + if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { + do { + --c; + if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { + return c+1; + } + } while(c>name); + } + return c;/* the last path component is the void string*/ + } + return c; +} + +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { + const char * lpc; + if(src && dest_ref) { +# define dest (*dest_ref) + dest = NULL; /* Default behavior: no change and sucess. */ + lpc = _synctex_last_path_component(src); + if(strlen(lpc)) { + if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { + /* We are in the situation where adding the quotes is allowed. */ + /* Time to add the quotes. */ + /* Consistency test: we must have dest+size>dest+strlen(dest)+2 + * or equivalently: strlen(dest)+2<size (see below) */ + if(strlen(src)<size) { + if((dest = (char *)malloc(size+2))) { + char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */ + if(dest != strncpy(dest,src,size)) { + _synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem"); + free(dest); + dest = NULL;/* Don't forget to reinitialize. */ + return -2; + } + memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */ + dpc[0]='"'; + dpc[strlen(dpc)+1]='\0';/* Consistency test */ + dpc[strlen(dpc)]='"'; + return 0; /* Success. */ + } + return -1; /* Memory allocation error. */ + } + _synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency"); + return -3; + } + return 0; /* Success. */ + } + return 0; /* No last path component. */ +# undef dest + } + return 1; /* Bad parameter, this value is subject to changes. */ +} + +/* The client is responsible of the management of the returned string, if any. */ +char * _synctex_merge_strings(const char * first,...); + +char * _synctex_merge_strings(const char * first,...) { + va_list arg; + size_t size = 0; + const char * temp; + /* First retrieve the size necessary to store the merged string */ + va_start (arg, first); + temp = first; + do { + size_t len = strlen(temp); + if(UINT_MAX-len<size) { + _synctex_error("! _synctex_merge_strings: Capacity exceeded."); + return NULL; + } + size+=len; + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + if(size>0) { + char * result = NULL; + ++size; + /* Create the memory storage */ + if(NULL!=(result = (char *)malloc(size))) { + char * dest = result; + va_start (arg, first); + temp = first; + do { + if((size = strlen(temp))>0) { + /* There is something to merge */ + if(dest != strncpy(dest,temp,size)) { + _synctex_error("! _synctex_merge_strings: Copy problem"); + free(result); + result = NULL; + return NULL; + } + dest += size; + } + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + dest[0]='\0';/* Terminate the merged string */ + return result; + } + _synctex_error("! _synctex_merge_strings: Memory problem"); + return NULL; + } + return NULL; +} + +/* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. + * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. + */ +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_compress_mode_t * compress_mode_ref) +{ + if(output && synctex_name_ref && compress_mode_ref) { +# define synctex_name (*synctex_name_ref) +# define compress_mode (*compress_mode_ref) + /* If output is already absolute, we just have to manage the quotes and the compress mode */ + const char * basename = NULL; /* base name of output*/ + size_t size = 0; + /* Initialize the return values. */ + synctex_name = NULL; + compress_mode = synctex_compress_mode_none; + basename = _synctex_last_path_component(output); /* do not free, output is the owner. */ + /* Do we have a real base name ? */ + if((size = strlen(basename))>0) { + /* Yes, we do. */ + const char * temp = NULL; + char * corename = NULL; /* base name of output without path extension. */ + char * dirname = NULL; /* dir name of output */ + char * quoted_corename = NULL; + char * none = NULL; + char * gz = NULL; + char * quoted = NULL; + char * quoted_gz = NULL; + char * build = NULL; + char * build_gz = NULL; + char * build_quoted = NULL; + char * build_quoted_gz = NULL; + struct stat buf; + time_t time = 0; + /* Create corename: let temp point to the dot before the path extension of basename; + * We start form the \0 terminating character and scan the string upward until we find a dot. + * The first dot is not accepted. */ + temp = strrchr(basename,'.'); + size = temp - basename; + if(size>0) { + /* dot properly found, now create corename */ + if(NULL == (corename = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem 1"); + return -1; + } + if(corename != strncpy(corename,basename,size)) { + _synctex_error("! _synctex_get_name: Copy problem 1"); + free(corename); + dirname = NULL; + return -2; + } + corename[size] = '\0'; + } else { + /* There is no path extension, + * Just make a copy of basename */ + corename = _synctex_merge_strings(basename); + } + /* corename is properly set up, owned by "self". */ + /* creating dirname. */ + size = strlen(output)-strlen(basename); + if(size>0) { + /* output contains more than one path component */ + if(NULL == (dirname = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem"); + free(corename); + dirname = NULL; + return -1; + } + if(dirname != strncpy(dirname,output,size)) { + _synctex_error("! _synctex_get_name: Copy problem"); + free(dirname); + dirname = NULL; + free(corename); + dirname = NULL; + return -2; + } + dirname[size] = '\0'; + } + /* dirname is properly set up. It ends with a path separator, if non void. */ + /* creating quoted_corename. */ + if(strchr(corename,' ')) { + quoted_corename = _synctex_merge_strings("\"",corename,"\""); + } + /* quoted_corename is properly set up. */ + if(dirname &&strlen(dirname)>0) { + none = _synctex_merge_strings(dirname,corename,synctex_suffix,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + quoted = _synctex_merge_strings(dirname,quoted_corename,synctex_suffix,NULL); + } + } else { + none = _synctex_merge_strings(corename,synctex_suffix,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + quoted = _synctex_merge_strings(quoted_corename,synctex_suffix,NULL); + } + } + if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { + temp = build_directory + size - 1; + if(_synctex_path_is_absolute(temp)) { + build = _synctex_merge_strings(build_directory,none,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + build_quoted = _synctex_merge_strings(build_directory,quoted,NULL); + } + } else { + build = _synctex_merge_strings(build_directory,"/",none,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + build_quoted = _synctex_merge_strings(build_directory,"/",quoted,NULL); + } + } + } + if(none) { + gz = _synctex_merge_strings(none,synctex_suffix_gz,NULL); + } + if(quoted) { + quoted_gz = _synctex_merge_strings(quoted,synctex_suffix_gz,NULL); + } + if(build) { + build_gz = _synctex_merge_strings(build,synctex_suffix_gz,NULL); + } + if(build_quoted) { + build_quoted_gz = _synctex_merge_strings(build_quoted,synctex_suffix_gz,NULL); + } + /* All the others names are properly set up... */ + /* retain the most recently modified file */ +# define TEST(FILENAME,COMPRESS_MODE) \ + if(FILENAME) {\ + if (stat(FILENAME, &buf)) { \ + free(FILENAME);\ + FILENAME = NULL;\ + } else { \ + if(buf.st_mtime>time) { \ + time=buf.st_mtime; \ + synctex_name = FILENAME; \ + compress_mode = COMPRESS_MODE; \ + } \ + } \ + } + TEST(none,synctex_compress_mode_none); + TEST(gz,synctex_compress_mode_gz); + TEST(quoted,synctex_compress_mode_none); + TEST(quoted_gz,synctex_compress_mode_gz); + TEST(build,synctex_compress_mode_none); + TEST(build_gz,synctex_compress_mode_gz); + TEST(build_quoted,synctex_compress_mode_none); + TEST(build_quoted_gz,synctex_compress_mode_gz); +# undef TEST + /* Free all the intermediate filenames, except the one that will be used as returned value. */ +# define CLEAN_AND_REMOVE(FILENAME) \ + if(FILENAME && (FILENAME!=synctex_name)) {\ + remove(FILENAME);\ + printf("synctex tool info: %s removed\n",FILENAME);\ + free(FILENAME);\ + FILENAME = NULL;\ + } + CLEAN_AND_REMOVE(none); + CLEAN_AND_REMOVE(gz); + CLEAN_AND_REMOVE(quoted); + CLEAN_AND_REMOVE(quoted_gz); + CLEAN_AND_REMOVE(build); + CLEAN_AND_REMOVE(build_gz); + CLEAN_AND_REMOVE(build_quoted); + CLEAN_AND_REMOVE(build_quoted_gz); +# undef CLEAN_AND_REMOVE + return 0; + } + return -1;/* bad argument */ +# undef synctex_name +# undef compress_mode + } + return -2; +} + diff --git a/cut-n-paste/synctex/synctex_parser_utils.h b/cut-n-paste/synctex/synctex_parser_utils.h new file mode 100644 index 00000000..e28ff58a --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser_utils.h @@ -0,0 +1,123 @@ +/* +Copyright (c) 2008, 2009 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Version: 1.8 +Latest Revision: Wed Jul 1 11:16:01 UTC 2009 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* The utilities declared here are subject to conditional implementation. + * All the operating system special stuff goes here. + * The problem mainly comes from file name management: path separator, encoding... + */ + +# define synctex_bool_t int +# define synctex_YES -1 +# define synctex_NO 0 + +#ifndef __SYNCTEX_PARSER_UTILS__ +# define __SYNCTEX_PARSER_UTILS__ + +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +# if _WIN32 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) +# else +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) +# endif + +/* This custom malloc functions initializes to 0 the newly allocated memory. + * There is no bzero function on windows. */ +void *_synctex_malloc(size_t size); + +/* This is used to log some informational message to the standard error stream. + * On Windows, the stderr stream is not exposed and another method is used. + * The return value is the number of characters printed. */ +int _synctex_error(const char * reason,...); + +/* strip the last extension of the given string, this string is modified! + * This function depends on the OS because the path separator may differ. + * This should be discussed more precisely. */ +void _synctex_strip_last_path_extension(char * string); + +/* Compare two file names, windows is sometimes case insensitive... + * The given strings may differ stricto sensu, but represent the same file name. + * It might not be the real way of doing things. + * The return value is an undefined non 0 value when the two file names are equivalent. + * It is 0 otherwise. */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); + +/* Description forthcoming.*/ +synctex_bool_t _synctex_path_is_absolute(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_last_path_component(const char * name); + +/* If the core of the last path component of src is not already enclosed with double quotes ('"') + * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. + * In all other cases, no destination buffer is created and the src is not copied. + * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. + * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces + * were not managed in a standard way. + * On success, the caller owns the buffer pointed to by dest_ref (is any) and + * is responsible of freeing the memory when done. + * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); + +/* These are the possible extensions of the synctex file */ +extern const char * synctex_suffix; +extern const char * synctex_suffix_gz; + +typedef enum { + synctex_io_mode_read = 0, + synctex_io_mode_append = 2 +} synctex_io_mode_t; + +typedef enum { + synctex_compress_mode_none = 0, + synctex_compress_mode_gz = 1 +} synctex_compress_mode_t; + +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_compress_mode_t * compress_mode_ref); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cut-n-paste/toolbar-editor/Makefile.am b/cut-n-paste/toolbar-editor/Makefile.am new file mode 100644 index 00000000..b3e843ec --- /dev/null +++ b/cut-n-paste/toolbar-editor/Makefile.am @@ -0,0 +1,108 @@ +EGGSOURCES = \ + egg-editable-toolbar.c \ + egg-toolbars-model.c \ + egg-toolbar-editor.c + +EGGHEADERS = \ + egg-editable-toolbar.h \ + egg-toolbars-model.h \ + egg-toolbar-editor.h + +noinst_HEADERS = \ + $(EGGHEADERS) \ + eggmarshalers.h + +noinst_LTLIBRARIES = libtoolbareditor.la + +libtoolbareditor_la_SOURCES = \ + $(BUILT_SOURCES) \ + $(EGGSOURCES) \ + $(EGGHEADERS) + +libtoolbareditor_la_CPPFLAGS = \ + $(AM_CPPFLAGS) + +libtoolbareditor_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + -DCURSOR_DIR=\"$(pkgdatadir)\" \ + $(AM_CFLAGS) + +BUILT_SOURCES = \ + eggmarshalers.c \ + eggmarshalers.h \ + eggtypebuiltins.c \ + eggtypebuiltins.h + +stamp_files = \ + stamp-eggmarshalers.c \ + stamp-eggmarshalers.h \ + stamp-eggtypebuiltins.c \ + stamp-eggtypebuiltins.h + +eggmarshalers.h: stamp-eggmarshalers.h + @true +stamp-eggmarshalers.h: eggmarshalers.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_egg_marshal $(srcdir)/eggmarshalers.list --header > eggmarshalers.h \ + && echo timestamp > $(@F) + +eggmarshalers.c: stamp-eggmarshalers.c + @true +stamp-eggmarshalers.c: eggmarshalers.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_egg_marshal $(srcdir)/eggmarshalers.list --header --body > eggmarshalers.c \ + && echo timestamp > $(@F) + +eggtypebuiltins.c: stamp-eggtypebuiltins.c + @true +stamp-eggtypebuiltins.c: $(EGGHEADERS) + $(AM_V_GEN)( cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#include <config.h>\n\n" \ + --fhead "#include \"eggtypebuiltins.h\"\n\n" \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --fprod "\n#include \"@filename@\"" \ + --vhead "static const G@Type@Value _@enum_name@_values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n};\n\n" \ + --vtail "GType\n@enum_name@_get_type (void)\n{\n" \ + --vtail " static GType type = 0;\n\n" \ + --vtail " if (G_UNLIKELY (type == 0))\n" \ + --vtail " type = g_@type@_register_static (\"@EnumName@\", _@enum_name@_values);\n\n" \ + --vtail " return type;\n}\n\n" \ + $(^F) ) > xgen-$(@F) \ + && ( cmp -s xgen-$(@F) $(@F:stamp-%=%) || cp xgen-$(@F) $(@F:stamp-%=%) ) \ + && rm -f xgen-$(@F) \ + && echo timestamp > $(@F) + +eggtypebuiltins.h: stamp-eggtypebuiltins.h + @true +stamp-eggtypebuiltins.h: $(EGGHEADERS) + $(AM_V_GEN)( cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#ifndef __EGGTYPEBUILTINS_H__\n" \ + --fhead "#define __EGGTYPEBUILTINS_H__ 1\n\n" \ + --fhead "#include <glib-object.h>\n\n" \ + --fhead "G_BEGIN_DECLS\n\n" \ + --ftail "G_END_DECLS\n\n" \ + --ftail "#endif /* __EGGTYPEBUILTINS_H__ */\n" \ + --fprod "\n/* --- @filename@ --- */" \ + --eprod "#define EGG_TYPE_@ENUMSHORT@ @enum_name@_get_type()\n" \ + --eprod "GType @enum_name@_get_type (void);\n" \ + $(^F) ) > xgen-$(@F) \ + && ( cmp -s xgen-$(@F) $(@F:stamp-%=%) || cp xgen-$(@F) $(@F:stamp-%=%) ) \ + && rm -f xgen-$(@F) \ + && echo timestamp > $(@F) + +EXTRA_DIST = \ + eggmarshalers.list + +EGGFILES=$(EGGSOURCES) $(EGGHEADERS) +EGGDIR=$(srcdir)/../../../libegg/libegg + +regenerate-built-sources: + EGGFILES="$(EGGFILES) eggmarshalers.list" EGGDIR="$(EGGDIR)" $(top_srcdir)/cut-n-paste/update-from-egg.sh + +CLEANFILES = $(stamp_files) $(BUILT_SOURCES) +DISTCLEANFILES = $(stamp_files) $(BUILT_SOURCES) +MAINTAINERCLEANFILES = $(stamp_files) $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/toolbar-editor/egg-editable-toolbar.c b/cut-n-paste/toolbar-editor/egg-editable-toolbar.c new file mode 100644 index 00000000..9193120a --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-editable-toolbar.c @@ -0,0 +1,1828 @@ +/* + * Copyright (C) 2003, 2004 Marco Pesenti Gritti + * Copyright (C) 2003, 2004, 2005 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-editable-toolbar.h" +#include "egg-toolbars-model.h" +#include "egg-toolbar-editor.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <string.h> + +static GdkPixbuf * new_separator_pixbuf (void); + +#define MIN_TOOLBAR_HEIGHT 20 +#define EGG_ITEM_NAME "egg-item-name" +#define STOCK_DRAG_MODE "stock_drag-mode" + +static const GtkTargetEntry dest_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + +enum +{ + PROP_0, + PROP_TOOLBARS_MODEL, + PROP_UI_MANAGER, + PROP_POPUP_PATH, + PROP_SELECTED, + PROP_EDIT_MODE +}; + +enum +{ + ACTION_REQUEST, + LAST_SIGNAL +}; + +static guint egg_editable_toolbar_signals[LAST_SIGNAL] = { 0 }; + +#define EGG_EDITABLE_TOOLBAR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarPrivate)) + +struct _EggEditableToolbarPrivate +{ + GtkUIManager *manager; + EggToolbarsModel *model; + guint edit_mode; + gboolean save_hidden; + GtkWidget *fixed_toolbar; + + GtkWidget *selected; + GtkActionGroup *actions; + + guint visibility_id; + GList *visibility_paths; + GPtrArray *visibility_actions; + + char *popup_path; + + guint dnd_pending; + GtkToolbar *dnd_toolbar; + GtkToolItem *dnd_toolitem; +}; + +G_DEFINE_TYPE (EggEditableToolbar, egg_editable_toolbar, GTK_TYPE_VBOX); + +static int +get_dock_position (EggEditableToolbar *etoolbar, + GtkWidget *dock) +{ + GList *l; + int result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_index (l, dock); + g_list_free (l); + + return result; +} + +static int +get_toolbar_position (EggEditableToolbar *etoolbar, GtkWidget *toolbar) +{ + return get_dock_position (etoolbar, gtk_widget_get_parent (toolbar)); +} + +static int +get_n_toolbars (EggEditableToolbar *etoolbar) +{ + GList *l; + int result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_length (l); + g_list_free (l); + + return result; +} + +static GtkWidget * +get_dock_nth (EggEditableToolbar *etoolbar, + int position) +{ + GList *l; + GtkWidget *result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_nth_data (l, position); + g_list_free (l); + + return result; +} + +static GtkWidget * +get_toolbar_nth (EggEditableToolbar *etoolbar, + int position) +{ + GList *l; + GtkWidget *dock; + GtkWidget *result; + + dock = get_dock_nth (etoolbar, position); + g_return_val_if_fail (dock != NULL, NULL); + + l = gtk_container_get_children (GTK_CONTAINER (dock)); + result = GTK_WIDGET (l->data); + g_list_free (l); + + return result; +} + +static GtkAction * +find_action (EggEditableToolbar *etoolbar, + const char *name) +{ + GList *l; + GtkAction *action = NULL; + + l = gtk_ui_manager_get_action_groups (etoolbar->priv->manager); + + g_return_val_if_fail (name != NULL, NULL); + + for (; l != NULL; l = l->next) + { + GtkAction *tmp; + + tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name); + if (tmp) + action = tmp; + } + + return action; +} + +static void +drag_data_delete_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + int pos, toolbar_pos; + GtkWidget *parent; + + widget = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM); + g_return_if_fail (widget != NULL); + g_return_if_fail (EGG_IS_EDITABLE_TOOLBAR (etoolbar)); + + parent = gtk_widget_get_parent (widget); + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (parent), + GTK_TOOL_ITEM (widget)); + toolbar_pos = get_toolbar_position (etoolbar, parent); + + egg_toolbars_model_remove_item (etoolbar->priv->model, + toolbar_pos, pos); +} + +static void +drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + GtkAction *action; + gint flags; + + gtk_widget_hide (widget); + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); +#else + action = gtk_widget_get_action (widget); +#endif + + if (action == NULL) return; + + flags = egg_toolbars_model_get_name_flags (etoolbar->priv->model, + gtk_action_get_name (action)); + if (!(flags & EGG_TB_MODEL_NAME_INFINITE)) + { + flags &= ~EGG_TB_MODEL_NAME_USED; + egg_toolbars_model_set_name_flags (etoolbar->priv->model, + gtk_action_get_name (action), + flags); + } +} + +static void +drag_end_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + GtkAction *action; + gint flags; + + if (gtk_widget_get_parent (widget) != NULL) + { + gtk_widget_show (widget); + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); +#else + action = gtk_widget_get_action (widget); +#endif + + if (action == NULL) return; + + flags = egg_toolbars_model_get_name_flags (etoolbar->priv->model, + gtk_action_get_name (action)); + if (!(flags & EGG_TB_MODEL_NAME_INFINITE)) + { + flags |= EGG_TB_MODEL_NAME_USED; + egg_toolbars_model_set_name_flags (etoolbar->priv->model, + gtk_action_get_name (action), + flags); + } + } +} + +static void +drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + EggEditableToolbar *etoolbar) +{ + EggToolbarsModel *model; + const char *name; + char *data; + GdkAtom target; + + g_return_if_fail (EGG_IS_EDITABLE_TOOLBAR (etoolbar)); + model = egg_editable_toolbar_get_model (etoolbar); + + name = g_object_get_data (G_OBJECT (widget), EGG_ITEM_NAME); + if (name == NULL) + { + name = g_object_get_data (G_OBJECT (gtk_widget_get_parent (widget)), EGG_ITEM_NAME); + g_return_if_fail (name != NULL); + } + + target = gtk_selection_data_get_target (selection_data); + data = egg_toolbars_model_get_data (model, target, name); + if (data != NULL) + { + gtk_selection_data_set (selection_data, target, 8, (unsigned char *)data, strlen (data)); + g_free (data); + } +} + +static void +move_item_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolitem = gtk_widget_get_ancestor (egg_editable_toolbar_get_selected (etoolbar), GTK_TYPE_TOOL_ITEM); + GtkTargetList *list = gtk_target_list_new (dest_drag_types, G_N_ELEMENTS (dest_drag_types)); + + GdkEvent *realevent = gtk_get_current_event(); + GdkEventMotion event; + event.type = GDK_MOTION_NOTIFY; + event.window = realevent->any.window; + event.send_event = FALSE; + event.axes = NULL; + event.time = gdk_event_get_time (realevent); + gdk_event_get_state (realevent, &event.state); + gdk_event_get_coords (realevent, &event.x, &event.y); + gdk_event_get_root_coords (realevent, &event.x_root, &event.y_root); + + gtk_drag_begin (toolitem, list, GDK_ACTION_MOVE, 1, (GdkEvent *)&event); + gtk_target_list_unref (list); +} + +static void +remove_item_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolitem = gtk_widget_get_ancestor (egg_editable_toolbar_get_selected (etoolbar), GTK_TYPE_TOOL_ITEM); + GtkWidget *parent = gtk_widget_get_parent (toolitem); + int pos, toolbar_pos; + + toolbar_pos = get_toolbar_position (etoolbar, parent); + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (parent), + GTK_TOOL_ITEM (toolitem)); + + egg_toolbars_model_remove_item (etoolbar->priv->model, + toolbar_pos, pos); + + if (egg_toolbars_model_n_items (etoolbar->priv->model, toolbar_pos) == 0) + { + egg_toolbars_model_remove_toolbar (etoolbar->priv->model, toolbar_pos); + } +} + +static void +remove_toolbar_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *selected = egg_editable_toolbar_get_selected (etoolbar); + GtkWidget *toolbar = gtk_widget_get_ancestor (selected, GTK_TYPE_TOOLBAR); + int toolbar_pos; + + toolbar_pos = get_toolbar_position (etoolbar, toolbar); + egg_toolbars_model_remove_toolbar (etoolbar->priv->model, toolbar_pos); +} + +static void +popup_context_deactivate (GtkMenuShell *menu, + EggEditableToolbar *etoolbar) +{ + egg_editable_toolbar_set_selected (etoolbar, NULL); + g_object_notify (G_OBJECT (etoolbar), "selected"); +} + +static void +popup_context_menu_cb (GtkWidget *toolbar, + gint x, + gint y, + gint button_number, + EggEditableToolbar *etoolbar) +{ + if (etoolbar->priv->popup_path != NULL) + { + GtkMenu *menu; + + egg_editable_toolbar_set_selected (etoolbar, toolbar); + g_object_notify (G_OBJECT (etoolbar), "selected"); + + menu = GTK_MENU (gtk_ui_manager_get_widget (etoolbar->priv->manager, + etoolbar->priv->popup_path)); + g_return_if_fail (menu != NULL); + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, button_number, gtk_get_current_event_time ()); + g_signal_connect_object (menu, "selection-done", + G_CALLBACK (popup_context_deactivate), + etoolbar, 0); + } +} + +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + EggEditableToolbar *etoolbar) +{ + if (event->button == 3 && etoolbar->priv->popup_path != NULL) + { + GtkMenu *menu; + + egg_editable_toolbar_set_selected (etoolbar, widget); + g_object_notify (G_OBJECT (etoolbar), "selected"); + + menu = GTK_MENU (gtk_ui_manager_get_widget (etoolbar->priv->manager, + etoolbar->priv->popup_path)); + g_return_val_if_fail (menu != NULL, FALSE); + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); + g_signal_connect_object (menu, "selection-done", + G_CALLBACK (popup_context_deactivate), + etoolbar, 0); + + return TRUE; + } + + return FALSE; +} + +static void +configure_item_sensitivity (GtkToolItem *item, EggEditableToolbar *etoolbar) +{ + GtkAction *action; + char *name; + + name = g_object_get_data (G_OBJECT (item), EGG_ITEM_NAME); + action = name ? find_action (etoolbar, name) : NULL; + + if (action) + { + g_object_notify (G_OBJECT (action), "sensitive"); + } + + gtk_tool_item_set_use_drag_window (item, + (etoolbar->priv->edit_mode > 0) || + GTK_IS_SEPARATOR_TOOL_ITEM (item)); + +} + +static void +configure_item_cursor (GtkToolItem *item, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + GtkWidget *widget = GTK_WIDGET (item); + GdkWindow *window = gtk_widget_get_window (widget); + + if (window != NULL) + { + if (priv->edit_mode > 0) + { + GdkCursor *cursor; + GdkScreen *screen; + GdkPixbuf *pixbuf = NULL; + + screen = gtk_widget_get_screen (GTK_WIDGET (etoolbar)); + + cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen), + GDK_HAND2); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + + gtk_drag_source_set (widget, GDK_BUTTON1_MASK, dest_drag_types, + G_N_ELEMENTS (dest_drag_types), GDK_ACTION_MOVE); + if (GTK_IS_SEPARATOR_TOOL_ITEM (item)) + { + pixbuf = new_separator_pixbuf (); + } + else + { + char *icon_name=NULL; + char *stock_id=NULL; + GtkAction *action; + char *name; + + name = g_object_get_data (G_OBJECT (widget), EGG_ITEM_NAME); + action = name ? find_action (etoolbar, name) : NULL; + + if (action) + { + g_object_get (action, + "icon-name", &icon_name, + "stock-id", &stock_id, + NULL); + } + if (icon_name) + { + GdkScreen *screen; + GtkIconTheme *icon_theme; + GtkSettings *settings; + gint width, height; + + screen = gtk_widget_get_screen (widget); + icon_theme = gtk_icon_theme_get_for_screen (screen); + settings = gtk_settings_get_for_screen (screen); + + if (!gtk_icon_size_lookup_for_settings (settings, + GTK_ICON_SIZE_LARGE_TOOLBAR, + &width, &height)) + { + width = height = 24; + } + + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, + MIN (width, height), 0, NULL); + } + else if (stock_id) + { + pixbuf = gtk_widget_render_icon (widget, stock_id, + GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + } + g_free (icon_name); + g_free (stock_id); + } + + if (G_UNLIKELY (!pixbuf)) + { + return; + } + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET(item)), NULL); + } + } +} + + +static void +configure_item_tooltip (GtkToolItem *item) +{ + GtkAction *action; + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (item)); +#else + action = gtk_widget_get_action (GTK_WIDGET (item)); +#endif + + if (action != NULL) + { + g_object_notify (G_OBJECT (action), "tooltip"); + } +} + + +static void +connect_widget_signals (GtkWidget *proxy, EggEditableToolbar *etoolbar) +{ + if (GTK_IS_CONTAINER (proxy)) + { + gtk_container_forall (GTK_CONTAINER (proxy), + (GtkCallback) connect_widget_signals, + (gpointer) etoolbar); + } + + if (GTK_IS_TOOL_ITEM (proxy)) + { + g_signal_connect_object (proxy, "drag_begin", + G_CALLBACK (drag_begin_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_end", + G_CALLBACK (drag_end_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_data_get", + G_CALLBACK (drag_data_get_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_data_delete", + G_CALLBACK (drag_data_delete_cb), + etoolbar, 0); + } + + if (GTK_IS_BUTTON (proxy) || GTK_IS_TOOL_ITEM (proxy)) + { + g_signal_connect_object (proxy, "button-press-event", + G_CALLBACK (button_press_event_cb), + etoolbar, 0); + } +} + +static void +action_sensitive_cb (GtkAction *action, + GParamSpec *pspec, + GtkToolItem *item) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR + (gtk_widget_get_ancestor (GTK_WIDGET (item), EGG_TYPE_EDITABLE_TOOLBAR)); + + if (etoolbar->priv->edit_mode > 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (item), TRUE); + } +} + +static GtkToolItem * +create_item_from_action (EggEditableToolbar *etoolbar, + const char *name) +{ + GtkToolItem *item; + + g_return_val_if_fail (name != NULL, NULL); + + if (strcmp (name, "_separator") == 0) + { + item = gtk_separator_tool_item_new (); + } + else + { + GtkAction *action = find_action (etoolbar, name); + if (action == NULL) return NULL; + + item = GTK_TOOL_ITEM (gtk_action_create_tool_item (action)); + + /* Normally done on-demand by the GtkUIManager, but no + * such demand may have been made yet, so do it ourselves. + */ + gtk_action_set_accel_group + (action, gtk_ui_manager_get_accel_group(etoolbar->priv->manager)); + + g_signal_connect_object (action, "notify::sensitive", + G_CALLBACK (action_sensitive_cb), item, 0); + } + + gtk_widget_show (GTK_WIDGET (item)); + + g_object_set_data_full (G_OBJECT (item), EGG_ITEM_NAME, + g_strdup (name), g_free); + + return item; +} + +static GtkToolItem * +create_item_from_position (EggEditableToolbar *etoolbar, + int toolbar_position, + int position) +{ + GtkToolItem *item; + const char *name; + + name = egg_toolbars_model_item_nth (etoolbar->priv->model, toolbar_position, position); + item = create_item_from_action (etoolbar, name); + + return item; +} + +static void +toolbar_drag_data_received_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + EggEditableToolbar *etoolbar) +{ + /* This function can be called for two reasons + * + * (1) drag_motion() needs an item to pass to + * gtk_toolbar_set_drop_highlight_item(). We can + * recognize this case by etoolbar->priv->pending being TRUE + * We should just create an item and return. + * + * (2) The drag has finished, and drag_drop() wants us to + * actually add a new item to the toolbar. + */ + + GdkAtom type = gtk_selection_data_get_data_type (selection_data); + const char *data = (char *)gtk_selection_data_get_data (selection_data); + + int ipos = -1; + char *name = NULL; + gboolean used = FALSE; + + /* Find out where the drop is occuring, and the name of what is being dropped. */ + if (gtk_selection_data_get_length (selection_data) >= 0) + { + ipos = gtk_toolbar_get_drop_index (toolbar, x, y); + name = egg_toolbars_model_get_name (etoolbar->priv->model, type, data, FALSE); + if (name != NULL) + { + used = ((egg_toolbars_model_get_name_flags (etoolbar->priv->model, name) & EGG_TB_MODEL_NAME_USED) != 0); + } + } + + /* If we just want a highlight item, then . */ + if (etoolbar->priv->dnd_pending > 0) + { + etoolbar->priv->dnd_pending--; + + if (name != NULL && etoolbar->priv->dnd_toolbar == toolbar && !used) + { + etoolbar->priv->dnd_toolitem = create_item_from_action (etoolbar, name); + gtk_toolbar_set_drop_highlight_item (etoolbar->priv->dnd_toolbar, + etoolbar->priv->dnd_toolitem, ipos); + } + } + else + { + gtk_toolbar_set_drop_highlight_item (toolbar, NULL, 0); + etoolbar->priv->dnd_toolbar = NULL; + etoolbar->priv->dnd_toolitem = NULL; + + /* If we don't have a name to use yet, try to create one. */ + if (name == NULL && gtk_selection_data_get_length (selection_data) >= 0) + { + name = egg_toolbars_model_get_name (etoolbar->priv->model, type, data, TRUE); + } + + if (name != NULL && !used) + { + gint tpos = get_toolbar_position (etoolbar, GTK_WIDGET (toolbar)); + egg_toolbars_model_add_item (etoolbar->priv->model, tpos, ipos, name); + gtk_drag_finish (context, TRUE, + gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE, time); + } + else + { + gtk_drag_finish (context, FALSE, + gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE, time); + } + } + + g_free (name); +} + +static gboolean +toolbar_drag_drop_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + guint time, + EggEditableToolbar *etoolbar) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target (GTK_WIDGET (toolbar), context, NULL); + if (target != GDK_NONE) + { + gtk_drag_get_data (GTK_WIDGET (toolbar), context, target, time); + return TRUE; + } + + return FALSE; +} + +static gboolean +toolbar_drag_motion_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + guint time, + EggEditableToolbar *etoolbar) +{ + GdkAtom target = gtk_drag_dest_find_target (GTK_WIDGET (toolbar), context, NULL); + if (target == GDK_NONE) + { + gdk_drag_status (context, 0, time); + return FALSE; + } + + /* Make ourselves the current dnd toolbar, and request a highlight item. */ + if (etoolbar->priv->dnd_toolbar != toolbar) + { + etoolbar->priv->dnd_toolbar = toolbar; + etoolbar->priv->dnd_toolitem = NULL; + etoolbar->priv->dnd_pending++; + gtk_drag_get_data (GTK_WIDGET (toolbar), context, target, time); + } + + /* If a highlight item is available, use it. */ + else if (etoolbar->priv->dnd_toolitem) + { + gint ipos = gtk_toolbar_get_drop_index (etoolbar->priv->dnd_toolbar, x, y); + gtk_toolbar_set_drop_highlight_item (etoolbar->priv->dnd_toolbar, + etoolbar->priv->dnd_toolitem, ipos); + } + + gdk_drag_status (context, gdk_drag_context_get_suggested_action (context), time); + + return TRUE; +} + +static void +toolbar_drag_leave_cb (GtkToolbar *toolbar, + GdkDragContext *context, + guint time, + EggEditableToolbar *etoolbar) +{ + gtk_toolbar_set_drop_highlight_item (toolbar, NULL, 0); + + /* If we were the current dnd toolbar target, remove the item. */ + if (etoolbar->priv->dnd_toolbar == toolbar) + { + etoolbar->priv->dnd_toolbar = NULL; + etoolbar->priv->dnd_toolitem = NULL; + } +} + +static void +configure_drag_dest (EggEditableToolbar *etoolbar, + GtkToolbar *toolbar) +{ + EggToolbarsItemType *type; + GtkTargetList *targets; + GList *list; + + /* Make every toolbar able to receive drag-drops. */ + gtk_drag_dest_set (GTK_WIDGET (toolbar), 0, + dest_drag_types, G_N_ELEMENTS (dest_drag_types), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + /* Add any specialist drag-drop abilities. */ + targets = gtk_drag_dest_get_target_list (GTK_WIDGET (toolbar)); + list = egg_toolbars_model_get_types (etoolbar->priv->model); + while (list) + { + type = list->data; + if (type->new_name != NULL || type->get_name != NULL) + gtk_target_list_add (targets, type->type, 0, 0); + list = list->next; + } +} + +static void +toggled_visibility_cb (GtkToggleAction *action, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + GtkWidget *dock; + EggTbModelFlags flags; + gboolean visible; + gint i; + + visible = gtk_toggle_action_get_active (action); + for (i = 0; i < priv->visibility_actions->len; i++) + if (g_ptr_array_index (priv->visibility_actions, i) == action) + break; + + g_return_if_fail (i < priv->visibility_actions->len); + + dock = get_dock_nth (etoolbar, i); + if (visible) + { + gtk_widget_show (dock); + } + else + { + gtk_widget_hide (dock); + } + + if (priv->save_hidden) + { + flags = egg_toolbars_model_get_flags (priv->model, i); + + if (visible) + { + flags &= ~(EGG_TB_MODEL_HIDDEN); + } + else + { + flags |= (EGG_TB_MODEL_HIDDEN); + } + + egg_toolbars_model_set_flags (priv->model, i, flags); + } +} + +static void +toolbar_visibility_refresh (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + gint n_toolbars, n_items, i, j, k; + GtkToggleAction *action; + GList *list; + GString *string; + gboolean showing; + char action_name[40]; + char *action_label; + char *tmp; + + if (priv == NULL || priv->model == NULL || priv->manager == NULL || + priv->visibility_paths == NULL || priv->actions == NULL) + { + return; + } + + if (priv->visibility_actions == NULL) + { + priv->visibility_actions = g_ptr_array_new (); + } + + if (priv->visibility_id != 0) + { + gtk_ui_manager_remove_ui (priv->manager, priv->visibility_id); + } + + priv->visibility_id = gtk_ui_manager_new_merge_id (priv->manager); + +#if GTK_CHECK_VERSION(2,20,0) + showing = gtk_widget_get_visible (GTK_WIDGET (etoolbar)); +#else + showing = GTK_WIDGET_VISIBLE (etoolbar); +#endif + + n_toolbars = egg_toolbars_model_n_toolbars (priv->model); + for (i = 0; i < n_toolbars; i++) + { + string = g_string_sized_new (0); + n_items = egg_toolbars_model_n_items (priv->model, i); + for (k = 0, j = 0; j < n_items; j++) + { + GValue value = { 0, }; + GtkAction *action; + const char *name; + + name = egg_toolbars_model_item_nth (priv->model, i, j); + if (name == NULL) continue; + action = find_action (etoolbar, name); + if (action == NULL) continue; + + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (action), "label", &value); + name = g_value_get_string (&value); + if (name == NULL) + { + g_value_unset (&value); + continue; + } + k += g_utf8_strlen (name, -1) + 2; + if (j > 0) + { + g_string_append (string, ", "); + if (j > 1 && k > 25) + { + g_value_unset (&value); + break; + } + } + g_string_append (string, name); + g_value_unset (&value); + } + if (j < n_items) + { + g_string_append (string, " ..."); + } + + tmp = g_string_free (string, FALSE); + for (j = 0, k = 0; tmp[j]; j++) + { + if (tmp[j] == '_') continue; + tmp[k] = tmp[j]; + k++; + } + tmp[k] = 0; + /* Translaters: This string is for a toggle to display a toolbar. + * The name of the toolbar is automatically computed from the widgets + * on the toolbar, and is placed at the %s. Note the _ before the %s + * which is used to add mnemonics. We know that this is likely to + * produce duplicates, but don't worry about it. If your language + * normally has a mnemonic at the start, please use the _. If not, + * please remove. */ + action_label = g_strdup_printf (_("Show “_%s”"), tmp); + g_free (tmp); + + sprintf(action_name, "ToolbarToggle%d", i); + + if (i >= priv->visibility_actions->len) + { + action = gtk_toggle_action_new (action_name, action_label, NULL, NULL); + g_ptr_array_add (priv->visibility_actions, action); + g_signal_connect_object (action, "toggled", + G_CALLBACK (toggled_visibility_cb), + etoolbar, 0); + gtk_action_group_add_action (priv->actions, GTK_ACTION (action)); + } + else + { + action = g_ptr_array_index (priv->visibility_actions, i); + g_object_set (action, "label", action_label, NULL); + } + + gtk_action_set_visible (GTK_ACTION (action), (egg_toolbars_model_get_flags (priv->model, i) + & EGG_TB_MODEL_NOT_REMOVABLE) == 0); + gtk_action_set_sensitive (GTK_ACTION (action), showing); +#if GTK_CHECK_VERSION(2,20,0) + gtk_toggle_action_set_active (action, gtk_widget_get_visible + (get_dock_nth (etoolbar, i))); +#else + gtk_toggle_action_set_active (action, GTK_WIDGET_VISIBLE + (get_dock_nth (etoolbar, i))); +#endif + + for (list = priv->visibility_paths; list != NULL; list = g_list_next (list)) + { + gtk_ui_manager_add_ui (priv->manager, priv->visibility_id, + (const char *)list->data, action_name, action_name, + GTK_UI_MANAGER_MENUITEM, FALSE); + } + + g_free (action_label); + } + + gtk_ui_manager_ensure_update (priv->manager); + + while (i < priv->visibility_actions->len) + { + action = g_ptr_array_index (priv->visibility_actions, i); + g_ptr_array_remove_index_fast (priv->visibility_actions, i); + gtk_action_group_remove_action (priv->actions, GTK_ACTION (action)); + i++; + } +} + +static GtkWidget * +create_dock (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *hbox; + + hbox = gtk_hbox_new (0, FALSE); + + toolbar = gtk_toolbar_new (); + gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE); + gtk_widget_show (toolbar); + gtk_box_pack_start (GTK_BOX (hbox), toolbar, TRUE, TRUE, 0); + + g_signal_connect (toolbar, "drag_drop", + G_CALLBACK (toolbar_drag_drop_cb), etoolbar); + g_signal_connect (toolbar, "drag_motion", + G_CALLBACK (toolbar_drag_motion_cb), etoolbar); + g_signal_connect (toolbar, "drag_leave", + G_CALLBACK (toolbar_drag_leave_cb), etoolbar); + + g_signal_connect (toolbar, "drag_data_received", + G_CALLBACK (toolbar_drag_data_received_cb), etoolbar); + g_signal_connect (toolbar, "popup_context_menu", + G_CALLBACK (popup_context_menu_cb), etoolbar); + + configure_drag_dest (etoolbar, GTK_TOOLBAR (toolbar)); + + return hbox; +} + +static void +set_fixed_style (EggEditableToolbar *t, GtkToolbarStyle style) +{ + g_return_if_fail (GTK_IS_TOOLBAR (t->priv->fixed_toolbar)); + gtk_toolbar_set_style (GTK_TOOLBAR (t->priv->fixed_toolbar), + style == GTK_TOOLBAR_ICONS ? GTK_TOOLBAR_BOTH_HORIZ : style); +} + +static void +unset_fixed_style (EggEditableToolbar *t) +{ + g_return_if_fail (GTK_IS_TOOLBAR (t->priv->fixed_toolbar)); + gtk_toolbar_unset_style (GTK_TOOLBAR (t->priv->fixed_toolbar)); +} + +static void +toolbar_changed_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar; + EggTbModelFlags flags; + GtkToolbarStyle style; + + flags = egg_toolbars_model_get_flags (model, position); + toolbar = get_toolbar_nth (etoolbar, position); + + if (flags & EGG_TB_MODEL_ICONS) + { + style = GTK_TOOLBAR_ICONS; + } + else if (flags & EGG_TB_MODEL_TEXT) + { + style = GTK_TOOLBAR_TEXT; + } + else if (flags & EGG_TB_MODEL_BOTH) + { + style = GTK_TOOLBAR_BOTH; + } + else if (flags & EGG_TB_MODEL_BOTH_HORIZ) + { + style = GTK_TOOLBAR_BOTH_HORIZ; + } + else + { + gtk_toolbar_unset_style (GTK_TOOLBAR (toolbar)); + if (position == 0 && etoolbar->priv->fixed_toolbar) + { + unset_fixed_style (etoolbar); + } + return; + } + + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), style); + if (position == 0 && etoolbar->priv->fixed_toolbar) + { + set_fixed_style (etoolbar, style); + } + + toolbar_visibility_refresh (etoolbar); +} + +static void +unparent_fixed (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *dock; + g_return_if_fail (GTK_IS_TOOLBAR (etoolbar->priv->fixed_toolbar)); + + toolbar = etoolbar->priv->fixed_toolbar; + dock = get_dock_nth (etoolbar, 0); + + if (dock && gtk_widget_get_parent (toolbar) != NULL) + { + gtk_container_remove (GTK_CONTAINER (dock), toolbar); + } +} + +static void +update_fixed (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *dock; + if (!etoolbar->priv->fixed_toolbar) return; + + toolbar = etoolbar->priv->fixed_toolbar; + dock = get_dock_nth (etoolbar, 0); + + if (dock && toolbar && gtk_widget_get_parent (toolbar) == NULL) + { + gtk_box_pack_end (GTK_BOX (dock), toolbar, FALSE, TRUE, 0); + + gtk_widget_show (toolbar); + + gtk_widget_set_size_request (dock, -1, -1); + gtk_widget_queue_resize_no_redraw (dock); + } +} + +static void +toolbar_added_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + + dock = create_dock (etoolbar); + if ((egg_toolbars_model_get_flags (model, position) & EGG_TB_MODEL_HIDDEN) == 0) + gtk_widget_show (dock); + + gtk_widget_set_size_request (dock, -1, MIN_TOOLBAR_HEIGHT); + + gtk_box_pack_start (GTK_BOX (etoolbar), dock, TRUE, TRUE, 0); + + gtk_box_reorder_child (GTK_BOX (etoolbar), dock, position); + + gtk_widget_show_all (dock); + + update_fixed (etoolbar); + + toolbar_visibility_refresh (etoolbar); +} + +static void +toolbar_removed_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + + if (position == 0 && etoolbar->priv->fixed_toolbar != NULL) + { + unparent_fixed (etoolbar); + } + + dock = get_dock_nth (etoolbar, position); + gtk_widget_destroy (dock); + + update_fixed (etoolbar); + + toolbar_visibility_refresh (etoolbar); +} + +static void +item_added_cb (EggToolbarsModel *model, + int tpos, + int ipos, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + GtkWidget *toolbar; + GtkToolItem *item; + + toolbar = get_toolbar_nth (etoolbar, tpos); + item = create_item_from_position (etoolbar, tpos, ipos); + if (item == NULL) return; + + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, ipos); + + connect_widget_signals (GTK_WIDGET (item), etoolbar); + configure_item_tooltip (item); + configure_item_cursor (item, etoolbar); + configure_item_sensitivity (item, etoolbar); + + dock = get_dock_nth (etoolbar, tpos); + gtk_widget_set_size_request (dock, -1, -1); + gtk_widget_queue_resize_no_redraw (dock); + + toolbar_visibility_refresh (etoolbar); +} + +static void +item_removed_cb (EggToolbarsModel *model, + int toolbar_position, + int position, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + GtkWidget *toolbar; + GtkWidget *item; + + toolbar = get_toolbar_nth (etoolbar, toolbar_position); + item = GTK_WIDGET (gtk_toolbar_get_nth_item + (GTK_TOOLBAR (toolbar), position)); + g_return_if_fail (item != NULL); + + if (item == priv->selected) + { + /* FIXME */ + } + + gtk_container_remove (GTK_CONTAINER (toolbar), item); + + toolbar_visibility_refresh (etoolbar); +} + +static void +egg_editable_toolbar_build (EggEditableToolbar *etoolbar) +{ + int i, l, n_items, n_toolbars; + EggToolbarsModel *model = etoolbar->priv->model; + + g_return_if_fail (model != NULL); + g_return_if_fail (etoolbar->priv->manager != NULL); + + n_toolbars = egg_toolbars_model_n_toolbars (model); + + for (i = 0; i < n_toolbars; i++) + { + GtkWidget *toolbar, *dock; + + dock = create_dock (etoolbar); + if ((egg_toolbars_model_get_flags (model, i) & EGG_TB_MODEL_HIDDEN) == 0) + gtk_widget_show (dock); + gtk_box_pack_start (GTK_BOX (etoolbar), dock, TRUE, TRUE, 0); + toolbar = get_toolbar_nth (etoolbar, i); + + n_items = egg_toolbars_model_n_items (model, i); + for (l = 0; l < n_items; l++) + { + GtkToolItem *item; + + item = create_item_from_position (etoolbar, i, l); + if (item) + { + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, l); + + connect_widget_signals (GTK_WIDGET (item), etoolbar); + configure_item_tooltip (item); + configure_item_sensitivity (item, etoolbar); + } + else + { + egg_toolbars_model_remove_item (model, i, l); + l--; + n_items--; + } + } + + if (n_items == 0) + { + gtk_widget_set_size_request (dock, -1, MIN_TOOLBAR_HEIGHT); + } + } + + update_fixed (etoolbar); + + /* apply styles */ + for (i = 0; i < n_toolbars; i ++) + { + toolbar_changed_cb (model, i, etoolbar); + } +} + +static void +egg_editable_toolbar_disconnect_model (EggEditableToolbar *toolbar) +{ + EggToolbarsModel *model = toolbar->priv->model; + + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (item_added_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (item_removed_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_added_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_removed_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_changed_cb), toolbar); +} + +static void +egg_editable_toolbar_deconstruct (EggEditableToolbar *toolbar) +{ + EggToolbarsModel *model = toolbar->priv->model; + GList *children; + + g_return_if_fail (model != NULL); + + if (toolbar->priv->fixed_toolbar) + { + unset_fixed_style (toolbar); + unparent_fixed (toolbar); + } + + children = gtk_container_get_children (GTK_CONTAINER (toolbar)); + g_list_foreach (children, (GFunc) gtk_widget_destroy, NULL); + g_list_free (children); +} + +void +egg_editable_toolbar_set_model (EggEditableToolbar *etoolbar, + EggToolbarsModel *model) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + if (priv->model == model) return; + + if (priv->model) + { + egg_editable_toolbar_disconnect_model (etoolbar); + egg_editable_toolbar_deconstruct (etoolbar); + + g_object_unref (priv->model); + } + + priv->model = g_object_ref (model); + + egg_editable_toolbar_build (etoolbar); + + toolbar_visibility_refresh (etoolbar); + + g_signal_connect (model, "item_added", + G_CALLBACK (item_added_cb), etoolbar); + g_signal_connect (model, "item_removed", + G_CALLBACK (item_removed_cb), etoolbar); + g_signal_connect (model, "toolbar_added", + G_CALLBACK (toolbar_added_cb), etoolbar); + g_signal_connect (model, "toolbar_removed", + G_CALLBACK (toolbar_removed_cb), etoolbar); + g_signal_connect (model, "toolbar_changed", + G_CALLBACK (toolbar_changed_cb), etoolbar); +} + +static void +egg_editable_toolbar_init (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv; + + priv = etoolbar->priv = EGG_EDITABLE_TOOLBAR_GET_PRIVATE (etoolbar); + + priv->save_hidden = TRUE; + + g_signal_connect (etoolbar, "notify::visible", + G_CALLBACK (toolbar_visibility_refresh), NULL); +} + +static void +egg_editable_toolbar_dispose (GObject *object) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + EggEditableToolbarPrivate *priv = etoolbar->priv; + GList *children; + + if (priv->fixed_toolbar != NULL) + { + g_object_unref (priv->fixed_toolbar); + priv->fixed_toolbar = NULL; + } + + if (priv->visibility_paths) + { + children = priv->visibility_paths; + g_list_foreach (children, (GFunc) g_free, NULL); + g_list_free (children); + priv->visibility_paths = NULL; + } + + g_free (priv->popup_path); + priv->popup_path = NULL; + + if (priv->manager != NULL) + { + if (priv->visibility_id) + { + gtk_ui_manager_remove_ui (priv->manager, priv->visibility_id); + priv->visibility_id = 0; + } + + g_object_unref (priv->manager); + priv->manager = NULL; + } + + if (priv->model) + { + egg_editable_toolbar_disconnect_model (etoolbar); + g_object_unref (priv->model); + priv->model = NULL; + } + + G_OBJECT_CLASS (egg_editable_toolbar_parent_class)->dispose (object); +} + +static void +egg_editable_toolbar_set_ui_manager (EggEditableToolbar *etoolbar, + GtkUIManager *manager) +{ + static const GtkActionEntry actions[] = { + { "MoveToolItem", STOCK_DRAG_MODE, N_("_Move on Toolbar"), NULL, + N_("Move the selected item on the toolbar"), G_CALLBACK (move_item_cb) }, + { "RemoveToolItem", GTK_STOCK_REMOVE, N_("_Remove from Toolbar"), NULL, + N_("Remove the selected item from the toolbar"), G_CALLBACK (remove_item_cb) }, + { "RemoveToolbar", GTK_STOCK_DELETE, N_("_Delete Toolbar"), NULL, + N_("Remove the selected toolbar"), G_CALLBACK (remove_toolbar_cb) }, + }; + + etoolbar->priv->manager = g_object_ref (manager); + + etoolbar->priv->actions = gtk_action_group_new ("ToolbarActions"); + gtk_action_group_set_translation_domain (etoolbar->priv->actions, GETTEXT_PACKAGE); + gtk_action_group_add_actions (etoolbar->priv->actions, actions, + G_N_ELEMENTS (actions), etoolbar); + gtk_ui_manager_insert_action_group (manager, etoolbar->priv->actions, -1); + g_object_unref (etoolbar->priv->actions); + + toolbar_visibility_refresh (etoolbar); +} + +GtkWidget * egg_editable_toolbar_get_selected (EggEditableToolbar *etoolbar) +{ + return etoolbar->priv->selected; +} + +void +egg_editable_toolbar_set_selected (EggEditableToolbar *etoolbar, + GtkWidget *widget) +{ + GtkWidget *toolbar, *toolitem; + gboolean editable; + + etoolbar->priv->selected = widget; + + toolbar = (widget != NULL) ? gtk_widget_get_ancestor (widget, GTK_TYPE_TOOLBAR) : NULL; + toolitem = (widget != NULL) ? gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM) : NULL; + + if(toolbar != NULL) + { + gint tpos = get_toolbar_position (etoolbar, toolbar); + editable = ((egg_toolbars_model_get_flags (etoolbar->priv->model, tpos) & EGG_TB_MODEL_NOT_EDITABLE) == 0); + } + else + { + editable = FALSE; + } + + gtk_action_set_visible (find_action (etoolbar, "RemoveToolbar"), (toolbar != NULL) && (etoolbar->priv->edit_mode > 0)); + gtk_action_set_visible (find_action (etoolbar, "RemoveToolItem"), (toolitem != NULL) && editable); + gtk_action_set_visible (find_action (etoolbar, "MoveToolItem"), (toolitem != NULL) && editable); +} + +static void +set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + int i, l, n_items; + + i = priv->edit_mode; + if (mode) + { + priv->edit_mode++; + } + else + { + g_return_if_fail (priv->edit_mode > 0); + priv->edit_mode--; + } + i *= priv->edit_mode; + + if (i == 0) + { + for (i = get_n_toolbars (etoolbar)-1; i >= 0; i--) + { + GtkWidget *toolbar; + + toolbar = get_toolbar_nth (etoolbar, i); + n_items = gtk_toolbar_get_n_items (GTK_TOOLBAR (toolbar)); + + if (n_items == 0 && priv->edit_mode == 0) + { + egg_toolbars_model_remove_toolbar (priv->model, i); + } + else + { + for (l = 0; l < n_items; l++) + { + GtkToolItem *item; + + item = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar), l); + + configure_item_cursor (item, etoolbar); + configure_item_sensitivity (item, etoolbar); + } + } + } + } +} + +static void +egg_editable_toolbar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + egg_editable_toolbar_set_ui_manager (etoolbar, g_value_get_object (value)); + break; + case PROP_TOOLBARS_MODEL: + egg_editable_toolbar_set_model (etoolbar, g_value_get_object (value)); + break; + case PROP_SELECTED: + egg_editable_toolbar_set_selected (etoolbar, g_value_get_object (value)); + break; + case PROP_POPUP_PATH: + etoolbar->priv->popup_path = g_strdup (g_value_get_string (value)); + break; + case PROP_EDIT_MODE: + set_edit_mode (etoolbar, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_editable_toolbar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + g_value_set_object (value, etoolbar->priv->manager); + break; + case PROP_TOOLBARS_MODEL: + g_value_set_object (value, etoolbar->priv->model); + break; + case PROP_SELECTED: + g_value_set_object (value, etoolbar->priv->selected); + break; + case PROP_EDIT_MODE: + g_value_set_boolean (value, etoolbar->priv->edit_mode>0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_editable_toolbar_class_init (EggEditableToolbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = egg_editable_toolbar_dispose; + object_class->set_property = egg_editable_toolbar_set_property; + object_class->get_property = egg_editable_toolbar_get_property; + + egg_editable_toolbar_signals[ACTION_REQUEST] = + g_signal_new ("action_request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggEditableToolbarClass, action_request), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_UI_MANAGER, + g_param_spec_object ("ui-manager", + "UI-Mmanager", + "UI Manager", + GTK_TYPE_UI_MANAGER, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + g_object_class_install_property (object_class, + PROP_TOOLBARS_MODEL, + g_param_spec_object ("model", + "Model", + "Toolbars Model", + EGG_TYPE_TOOLBARS_MODEL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + g_object_class_install_property (object_class, + PROP_SELECTED, + g_param_spec_object ("selected", + "Selected", + "Selected toolitem", + GTK_TYPE_TOOL_ITEM, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_POPUP_PATH, + g_param_spec_string ("popup-path", + "popup-path", + "popup-path", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_EDIT_MODE, + g_param_spec_boolean ("edit-mode", + "Edit-Mode", + "Edit Mode", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (object_class, sizeof (EggEditableToolbarPrivate)); +} + +GtkWidget * +egg_editable_toolbar_new (GtkUIManager *manager, + const char *popup_path) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_EDITABLE_TOOLBAR, + "ui-manager", manager, + "popup-path", popup_path, + NULL)); +} + +GtkWidget * +egg_editable_toolbar_new_with_model (GtkUIManager *manager, + EggToolbarsModel *model, + const char *popup_path) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_EDITABLE_TOOLBAR, + "ui-manager", manager, + "model", model, + "popup-path", popup_path, + NULL)); +} + +gboolean +egg_editable_toolbar_get_edit_mode (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + return priv->edit_mode > 0; +} + +void +egg_editable_toolbar_set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode) +{ + set_edit_mode (etoolbar, mode); + g_object_notify (G_OBJECT (etoolbar), "edit-mode"); +} + +void +egg_editable_toolbar_add_visibility (EggEditableToolbar *etoolbar, + const char *path) +{ + etoolbar->priv->visibility_paths = g_list_prepend + (etoolbar->priv->visibility_paths, g_strdup (path)); +} + +void +egg_editable_toolbar_show (EggEditableToolbar *etoolbar, + const char *name) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + EggToolbarsModel *model = priv->model; + int i, n_toolbars; + + n_toolbars = egg_toolbars_model_n_toolbars (model); + for (i = 0; i < n_toolbars; i++) + { + const char *toolbar_name; + + toolbar_name = egg_toolbars_model_toolbar_nth (model, i); + if (strcmp (toolbar_name, name) == 0) + { + gtk_widget_show (get_dock_nth (etoolbar, i)); + } + } +} + +void +egg_editable_toolbar_hide (EggEditableToolbar *etoolbar, + const char *name) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + EggToolbarsModel *model = priv->model; + int i, n_toolbars; + + n_toolbars = egg_toolbars_model_n_toolbars (model); + for (i = 0; i < n_toolbars; i++) + { + const char *toolbar_name; + + toolbar_name = egg_toolbars_model_toolbar_nth (model, i); + if (strcmp (toolbar_name, name) == 0) + { + gtk_widget_hide (get_dock_nth (etoolbar, i)); + } + } +} + +void +egg_editable_toolbar_set_fixed (EggEditableToolbar *etoolbar, + GtkToolbar *toolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + g_return_if_fail (!toolbar || GTK_IS_TOOLBAR (toolbar)); + + if (priv->fixed_toolbar) + { + unparent_fixed (etoolbar); + g_object_unref (priv->fixed_toolbar); + priv->fixed_toolbar = NULL; + } + + if (toolbar) + { + priv->fixed_toolbar = GTK_WIDGET (toolbar); + gtk_toolbar_set_show_arrow (toolbar, FALSE); + g_object_ref_sink (toolbar); + } + + update_fixed (etoolbar); +} + +#define DEFAULT_ICON_HEIGHT 20 + +/* We should probably experiment some more with this. + * Right now the rendered icon is pretty good for most + * themes. However, the icon is slightly large for themes + * with large toolbar icons. + */ +static GdkPixbuf * +new_pixbuf_from_widget (GtkWidget *widget) +{ + GtkWidget *window; + GdkPixbuf *pixbuf; + gint icon_height; + GdkScreen *screen; + + screen = gtk_widget_get_screen (widget); + + if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen), + GTK_ICON_SIZE_LARGE_TOOLBAR, + NULL, + &icon_height)) + { + icon_height = DEFAULT_ICON_HEIGHT; + } + + window = gtk_offscreen_window_new (); + /* Set the width to -1 as we want the separator to be as thin as possible. */ + gtk_widget_set_size_request (widget, -1, icon_height); + + gtk_container_add (GTK_CONTAINER (window), widget); + gtk_widget_show_all (window); + + /* Process the waiting events to have the widget actually drawn */ + gdk_window_process_updates (gtk_widget_get_window (window), TRUE); + pixbuf = gtk_offscreen_window_get_pixbuf (GTK_OFFSCREEN_WINDOW (window)); + gtk_widget_destroy (window); + + return pixbuf; +} + +static GdkPixbuf * +new_separator_pixbuf (void) +{ + GtkWidget *separator; + GdkPixbuf *pixbuf; + + separator = gtk_vseparator_new (); + pixbuf = new_pixbuf_from_widget (separator); + return pixbuf; +} + +static void +update_separator_image (GtkImage *image) +{ + GdkPixbuf *pixbuf = new_separator_pixbuf (); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + g_object_unref (pixbuf); +} + +static gboolean +style_set_cb (GtkWidget *widget, + GtkStyle *previous_style, + GtkImage *image) +{ + + update_separator_image (image); + return FALSE; +} + +GtkWidget * +_egg_editable_toolbar_new_separator_image (void) +{ + GtkWidget *image = gtk_image_new (); + update_separator_image (GTK_IMAGE (image)); + g_signal_connect (G_OBJECT (image), "style_set", + G_CALLBACK (style_set_cb), GTK_IMAGE (image)); + + return image; +} + +EggToolbarsModel * +egg_editable_toolbar_get_model (EggEditableToolbar *etoolbar) +{ + return etoolbar->priv->model; +} diff --git a/cut-n-paste/toolbar-editor/egg-editable-toolbar.h b/cut-n-paste/toolbar-editor/egg-editable-toolbar.h new file mode 100644 index 00000000..669af415 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-editable-toolbar.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003, 2004 Marco Pesenti Gritti + * Copyright (C) 2003, 2004, 2005 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EGG_EDITABLE_TOOLBAR_H +#define EGG_EDITABLE_TOOLBAR_H + +#include "egg-toolbars-model.h" + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_EDITABLE_TOOLBAR (egg_editable_toolbar_get_type ()) +#define EGG_EDITABLE_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbar)) +#define EGG_EDITABLE_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarClass)) +#define EGG_IS_EDITABLE_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_EDITABLE_TOOLBAR)) +#define EGG_IS_EDITABLE_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_EDITABLE_TOOLBAR)) +#define EGG_EDITABLE_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarClass)) + +typedef struct _EggEditableToolbar EggEditableToolbar; +typedef struct _EggEditableToolbarPrivate EggEditableToolbarPrivate; +typedef struct _EggEditableToolbarClass EggEditableToolbarClass; + +struct _EggEditableToolbar +{ + GtkVBox parent_object; + + /*< private >*/ + EggEditableToolbarPrivate *priv; +}; + +struct _EggEditableToolbarClass +{ + GtkVBoxClass parent_class; + + void (* action_request) (EggEditableToolbar *etoolbar, + const char *action_name); +}; + +GType egg_editable_toolbar_get_type (void); +GtkWidget *egg_editable_toolbar_new (GtkUIManager *manager, + const char *visibility_path); +GtkWidget *egg_editable_toolbar_new_with_model (GtkUIManager *manager, + EggToolbarsModel *model, + const char *visibility_path); +void egg_editable_toolbar_set_model (EggEditableToolbar *etoolbar, + EggToolbarsModel *model); +EggToolbarsModel *egg_editable_toolbar_get_model (EggEditableToolbar *etoolbar); +GtkUIManager *egg_editable_toolbar_get_manager (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode); +gboolean egg_editable_toolbar_get_edit_mode (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_show (EggEditableToolbar *etoolbar, + const char *name); +void egg_editable_toolbar_hide (EggEditableToolbar *etoolbar, + const char *name); +void egg_editable_toolbar_set_fixed (EggEditableToolbar *etoolbar, + GtkToolbar *fixed_toolbar); + +GtkWidget * egg_editable_toolbar_get_selected (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_set_selected (EggEditableToolbar *etoolbar, + GtkWidget *widget); + +void egg_editable_toolbar_add_visibility (EggEditableToolbar *etoolbar, + const char *path); + +/* Private Functions */ + +GtkWidget *_egg_editable_toolbar_new_separator_image (void); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/egg-toolbar-editor.c b/cut-n-paste/toolbar-editor/egg-toolbar-editor.c new file mode 100644 index 00000000..5e75ba82 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbar-editor.c @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-toolbar-editor.h" +#include "egg-editable-toolbar.h" + +#include <string.h> +#include <libxml/tree.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +static const GtkTargetEntry dest_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + +static const GtkTargetEntry source_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + + +static void egg_toolbar_editor_finalize (GObject *object); +static void update_editor_sheet (EggToolbarEditor *editor); + +enum +{ + PROP_0, + PROP_UI_MANAGER, + PROP_TOOLBARS_MODEL +}; + +enum +{ + SIGNAL_HANDLER_ITEM_ADDED, + SIGNAL_HANDLER_ITEM_REMOVED, + SIGNAL_HANDLER_TOOLBAR_REMOVED, + SIGNAL_HANDLER_LIST_SIZE /* Array size */ +}; + +#define EGG_TOOLBAR_EDITOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorPrivate)) + +struct EggToolbarEditorPrivate +{ + GtkUIManager *manager; + EggToolbarsModel *model; + + GtkWidget *table; + GtkWidget *scrolled_window; + GList *actions_list; + GList *factory_list; + + /* These handlers need to be sanely disconnected when switching models */ + gulong sig_handlers[SIGNAL_HANDLER_LIST_SIZE]; +}; + +G_DEFINE_TYPE (EggToolbarEditor, egg_toolbar_editor, GTK_TYPE_VBOX); + +static gint +compare_items (gconstpointer a, + gconstpointer b) +{ + const GtkWidget *item1 = a; + const GtkWidget *item2 = b; + + char *key1 = g_object_get_data (G_OBJECT (item1), + "egg-collate-key"); + char *key2 = g_object_get_data (G_OBJECT (item2), + "egg-collate-key"); + + return strcmp (key1, key2); +} + +static GtkAction * +find_action (EggToolbarEditor *t, + const char *name) +{ + GList *l; + GtkAction *action = NULL; + + l = gtk_ui_manager_get_action_groups (t->priv->manager); + + g_return_val_if_fail (EGG_IS_TOOLBAR_EDITOR (t), NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (; l != NULL; l = l->next) + { + GtkAction *tmp; + + tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name); + if (tmp) + action = tmp; + } + + return action; +} + +static void +egg_toolbar_editor_set_ui_manager (EggToolbarEditor *t, + GtkUIManager *manager) +{ + g_return_if_fail (GTK_IS_UI_MANAGER (manager)); + + t->priv->manager = g_object_ref (manager); +} + +static void +item_added_or_removed_cb (EggToolbarsModel *model, + int tpos, + int ipos, + EggToolbarEditor *editor) +{ + update_editor_sheet (editor); +} + +static void +toolbar_removed_cb (EggToolbarsModel *model, + int position, + EggToolbarEditor *editor) +{ + update_editor_sheet (editor); +} + +static void +egg_toolbar_editor_disconnect_model (EggToolbarEditor *t) +{ + EggToolbarEditorPrivate *priv = t->priv; + EggToolbarsModel *model = priv->model; + gulong handler; + int i; + + for (i = 0; i < SIGNAL_HANDLER_LIST_SIZE; i++) + { + handler = priv->sig_handlers[i]; + + if (handler != 0) + { + if (g_signal_handler_is_connected (model, handler)) + { + g_signal_handler_disconnect (model, handler); + } + + priv->sig_handlers[i] = 0; + } + } +} + +void +egg_toolbar_editor_set_model (EggToolbarEditor *t, + EggToolbarsModel *model) +{ + EggToolbarEditorPrivate *priv; + + g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (t)); + g_return_if_fail (model != NULL); + + priv = t->priv; + + if (priv->model) + { + if (G_UNLIKELY (priv->model == model)) return; + + egg_toolbar_editor_disconnect_model (t); + g_object_unref (priv->model); + } + + priv->model = g_object_ref (model); + + update_editor_sheet (t); + + priv->sig_handlers[SIGNAL_HANDLER_ITEM_ADDED] = + g_signal_connect_object (model, "item_added", + G_CALLBACK (item_added_or_removed_cb), t, 0); + priv->sig_handlers[SIGNAL_HANDLER_ITEM_REMOVED] = + g_signal_connect_object (model, "item_removed", + G_CALLBACK (item_added_or_removed_cb), t, 0); + priv->sig_handlers[SIGNAL_HANDLER_TOOLBAR_REMOVED] = + g_signal_connect_object (model, "toolbar_removed", + G_CALLBACK (toolbar_removed_cb), t, 0); +} + +static void +egg_toolbar_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + egg_toolbar_editor_set_ui_manager (t, g_value_get_object (value)); + break; + case PROP_TOOLBARS_MODEL: + egg_toolbar_editor_set_model (t, g_value_get_object (value)); + break; + } +} + +static void +egg_toolbar_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + g_value_set_object (value, t->priv->manager); + break; + case PROP_TOOLBARS_MODEL: + g_value_set_object (value, t->priv->model); + break; + } +} + +static void +egg_toolbar_editor_class_init (EggToolbarEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = egg_toolbar_editor_finalize; + object_class->set_property = egg_toolbar_editor_set_property; + object_class->get_property = egg_toolbar_editor_get_property; + + g_object_class_install_property (object_class, + PROP_UI_MANAGER, + g_param_spec_object ("ui-manager", + "UI-Manager", + "UI Manager", + GTK_TYPE_UI_MANAGER, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_TOOLBARS_MODEL, + g_param_spec_object ("model", + "Model", + "Toolbars Model", + EGG_TYPE_TOOLBARS_MODEL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_CONSTRUCT)); + + g_type_class_add_private (object_class, sizeof (EggToolbarEditorPrivate)); +} + +static void +egg_toolbar_editor_finalize (GObject *object) +{ + EggToolbarEditor *editor = EGG_TOOLBAR_EDITOR (object); + + if (editor->priv->manager) + { + g_object_unref (editor->priv->manager); + } + + if (editor->priv->model) + { + egg_toolbar_editor_disconnect_model (editor); + g_object_unref (editor->priv->model); + } + + g_list_free (editor->priv->actions_list); + g_list_free (editor->priv->factory_list); + + G_OBJECT_CLASS (egg_toolbar_editor_parent_class)->finalize (object); +} + +GtkWidget * +egg_toolbar_editor_new (GtkUIManager *manager, + EggToolbarsModel *model) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_TOOLBAR_EDITOR, + "ui-manager", manager, + "model", model, + NULL)); +} + +static void +drag_begin_cb (GtkWidget *widget, + GdkDragContext *context) +{ + gtk_widget_hide (widget); +} + +static void +drag_end_cb (GtkWidget *widget, + GdkDragContext *context) +{ + gtk_widget_show (widget); +} + +static void +drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + EggToolbarEditor *editor) +{ + const char *target; + + target = g_object_get_data (G_OBJECT (widget), "egg-item-name"); + g_return_if_fail (target != NULL); + + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), 8, + (const guchar *) target, strlen (target)); +} + +static gchar * +elide_underscores (const gchar *original) +{ + gchar *q, *result; + const gchar *p; + gboolean last_underscore; + + q = result = g_malloc (strlen (original) + 1); + last_underscore = FALSE; + + for (p = original; *p; p++) + { + if (!last_underscore && *p == '_') + last_underscore = TRUE; + else + { + last_underscore = FALSE; + *q++ = *p; + } + } + + *q = '\0'; + + return result; +} + +static void +set_drag_cursor (GtkWidget *widget) +{ + GdkCursor *cursor; + GdkScreen *screen; + + screen = gtk_widget_get_screen (widget); + + cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen), + GDK_HAND2); + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); +} + +static void +event_box_realize_cb (GtkWidget *widget, GtkImage *icon) +{ + GtkImageType type; + + set_drag_cursor (widget); + + type = gtk_image_get_storage_type (icon); + if (type == GTK_IMAGE_STOCK) + { + gchar *stock_id; + GdkPixbuf *pixbuf; + + gtk_image_get_stock (icon, &stock_id, NULL); + pixbuf = gtk_widget_render_icon (widget, stock_id, + GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + } + else if (type == GTK_IMAGE_ICON_NAME) + { + const gchar *icon_name; + GdkScreen *screen; + GtkIconTheme *icon_theme; + GtkSettings *settings; + gint width, height; + GdkPixbuf *pixbuf; + + gtk_image_get_icon_name (icon, &icon_name, NULL); + screen = gtk_widget_get_screen (widget); + icon_theme = gtk_icon_theme_get_for_screen (screen); + settings = gtk_settings_get_for_screen (screen); + + if (!gtk_icon_size_lookup_for_settings (settings, + GTK_ICON_SIZE_LARGE_TOOLBAR, + &width, &height)) + { + width = height = 24; + } + + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, + MIN (width, height), 0, NULL); + if (G_UNLIKELY (!pixbuf)) + return; + + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + + } + else if (type == GTK_IMAGE_PIXBUF) + { + GdkPixbuf *pixbuf = gtk_image_get_pixbuf (icon); + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + } +} + +static GtkWidget * +editor_create_item (EggToolbarEditor *editor, + GtkImage *icon, + const char *label_text, + GdkDragAction action) +{ + GtkWidget *event_box; + GtkWidget *vbox; + GtkWidget *label; + gchar *label_no_mnemonic = NULL; + + event_box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE); + gtk_widget_show (event_box); + gtk_drag_source_set (event_box, + GDK_BUTTON1_MASK, + source_drag_types, G_N_ELEMENTS (source_drag_types), action); + g_signal_connect (event_box, "drag_data_get", + G_CALLBACK (drag_data_get_cb), editor); + g_signal_connect_after (event_box, "realize", + G_CALLBACK (event_box_realize_cb), icon); + + if (action == GDK_ACTION_MOVE) + { + g_signal_connect (event_box, "drag_begin", + G_CALLBACK (drag_begin_cb), NULL); + g_signal_connect (event_box, "drag_end", + G_CALLBACK (drag_end_cb), NULL); + } + + vbox = gtk_vbox_new (0, FALSE); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (event_box), vbox); + + gtk_widget_show (GTK_WIDGET (icon)); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (icon), FALSE, TRUE, 0); + label_no_mnemonic = elide_underscores (label_text); + label = gtk_label_new (label_no_mnemonic); + g_free (label_no_mnemonic); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + + return event_box; +} + +static GtkWidget * +editor_create_item_from_name (EggToolbarEditor *editor, + const char * name, + GdkDragAction drag_action) +{ + GtkWidget *item; + const char *item_name; + char *short_label; + const char *collate_key; + + if (strcmp (name, "_separator") == 0) + { + GtkWidget *icon; + + icon = _egg_editable_toolbar_new_separator_image (); + short_label = _("Separator"); + item_name = g_strdup (name); + collate_key = g_utf8_collate_key (short_label, -1); + item = editor_create_item (editor, GTK_IMAGE (icon), + short_label, drag_action); + } + else + { + GtkAction *action; + GtkWidget *icon; + char *stock_id, *icon_name = NULL; + + action = find_action (editor, name); + g_return_val_if_fail (action != NULL, NULL); + + g_object_get (action, + "icon-name", &icon_name, + "stock-id", &stock_id, + "short-label", &short_label, + NULL); + + /* This is a workaround to catch named icons. */ + if (icon_name) + icon = gtk_image_new_from_icon_name (icon_name, + GTK_ICON_SIZE_LARGE_TOOLBAR); + else + icon = gtk_image_new_from_stock (stock_id ? stock_id : GTK_STOCK_DND, + GTK_ICON_SIZE_LARGE_TOOLBAR); + + item_name = g_strdup (name); + collate_key = g_utf8_collate_key (short_label, -1); + item = editor_create_item (editor, GTK_IMAGE (icon), + short_label, drag_action); + + g_free (short_label); + g_free (stock_id); + g_free (icon_name); + } + + g_object_set_data_full (G_OBJECT (item), "egg-collate-key", + (gpointer) collate_key, g_free); + g_object_set_data_full (G_OBJECT (item), "egg-item-name", + (gpointer) item_name, g_free); + + return item; +} + +static gint +append_table (GtkTable *table, GList *items, gint y, gint width) +{ + if (items != NULL) + { + gint x = 0, height; + GtkWidget *alignment; + GtkWidget *item; + + height = g_list_length (items) / width + 1; + gtk_table_resize (table, height, width); + + if (y > 0) + { + item = gtk_hseparator_new (); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 0.0); + gtk_container_add (GTK_CONTAINER (alignment), item); + gtk_widget_show (alignment); + gtk_widget_show (item); + + gtk_table_attach_defaults (table, alignment, 0, width, y-1, y+1); + } + + for (; items != NULL; items = items->next) + { + item = items->data; + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (alignment), item); + gtk_widget_show (alignment); + gtk_widget_show (item); + + if (x >= width) + { + x = 0; + y++; + } + gtk_table_attach_defaults (table, alignment, x, x+1, y, y+1); + x++; + } + + y++; + } + return y; +} + +static void +update_editor_sheet (EggToolbarEditor *editor) +{ + gint y; + GPtrArray *items; + GList *to_move = NULL, *to_copy = NULL; + GtkWidget *table; + GtkWidget *viewport; + + g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor)); + + /* Create new table. */ + table = gtk_table_new (0, 0, TRUE); + editor->priv->table = table; + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 24); + gtk_widget_show (table); + gtk_drag_dest_set (table, GTK_DEST_DEFAULT_ALL, + dest_drag_types, G_N_ELEMENTS (dest_drag_types), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + /* Build two lists of items (one for copying, one for moving). */ + items = egg_toolbars_model_get_name_avail (editor->priv->model); + while (items->len > 0) + { + GtkWidget *item; + const char *name; + gint flags; + + name = g_ptr_array_index (items, 0); + g_ptr_array_remove_index_fast (items, 0); + + flags = egg_toolbars_model_get_name_flags (editor->priv->model, name); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0) + { + item = editor_create_item_from_name (editor, name, GDK_ACTION_MOVE); + if (item != NULL) + to_move = g_list_insert_sorted (to_move, item, compare_items); + } + else + { + item = editor_create_item_from_name (editor, name, GDK_ACTION_COPY); + if (item != NULL) + to_copy = g_list_insert_sorted (to_copy, item, compare_items); + } + } + + /* Add them to the sheet. */ + y = 0; + y = append_table (GTK_TABLE (table), to_move, y, 4); + y = append_table (GTK_TABLE (table), to_copy, y, 4); + + g_list_free (to_move); + g_list_free (to_copy); + g_ptr_array_free (items, TRUE); + + /* Delete old table. */ + viewport = gtk_bin_get_child (GTK_BIN (editor->priv->scrolled_window)); + if (viewport) + { + gtk_container_remove (GTK_CONTAINER (viewport), + gtk_bin_get_child (GTK_BIN (viewport))); + } + + /* Add table to window. */ + gtk_scrolled_window_add_with_viewport + (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window), table); + +} + +static void +setup_editor (EggToolbarEditor *editor) +{ + GtkWidget *scrolled_window; + + gtk_container_set_border_width (GTK_CONTAINER (editor), 12); + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + editor->priv->scrolled_window = scrolled_window; + gtk_widget_show (scrolled_window); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0); +} + +static void +egg_toolbar_editor_init (EggToolbarEditor *t) +{ + t->priv = EGG_TOOLBAR_EDITOR_GET_PRIVATE (t); + + t->priv->manager = NULL; + t->priv->actions_list = NULL; + + setup_editor (t); +} + diff --git a/cut-n-paste/toolbar-editor/egg-toolbar-editor.h b/cut-n-paste/toolbar-editor/egg-toolbar-editor.h new file mode 100644 index 00000000..3b897c50 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbar-editor.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EGG_TOOLBAR_EDITOR_H +#define EGG_TOOLBAR_EDITOR_H + +#include <gtk/gtk.h> + +#include "egg-toolbars-model.h" + +G_BEGIN_DECLS + +typedef struct EggToolbarEditorClass EggToolbarEditorClass; + +#define EGG_TYPE_TOOLBAR_EDITOR (egg_toolbar_editor_get_type ()) +#define EGG_TOOLBAR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditor)) +#define EGG_TOOLBAR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorClass)) +#define EGG_IS_TOOLBAR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TOOLBAR_EDITOR)) +#define EGG_IS_TOOLBAR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TOOLBAR_EDITOR)) +#define EGG_TOOLBAR_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorClass)) + + +typedef struct EggToolbarEditor EggToolbarEditor; +typedef struct EggToolbarEditorPrivate EggToolbarEditorPrivate; + +struct EggToolbarEditor +{ + GtkVBox parent_object; + + /*< private >*/ + EggToolbarEditorPrivate *priv; +}; + +struct EggToolbarEditorClass +{ + GtkVBoxClass parent_class; +}; + + +GType egg_toolbar_editor_get_type (void); +GtkWidget *egg_toolbar_editor_new (GtkUIManager *manager, + EggToolbarsModel *model); +void egg_toolbar_editor_set_model (EggToolbarEditor *t, + EggToolbarsModel *model); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/egg-toolbars-model.c b/cut-n-paste/toolbar-editor/egg-toolbars-model.c new file mode 100644 index 00000000..27dbedf6 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbars-model.c @@ -0,0 +1,987 @@ +/* + * Copyright (C) 2002-2004 Marco Pesenti Gritti + * Copyright (C) 2004 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-toolbars-model.h" +#include "eggtypebuiltins.h" +#include "eggmarshalers.h" + +#include <unistd.h> +#include <string.h> +#include <libxml/tree.h> +#include <gdk/gdk.h> + +static void egg_toolbars_model_finalize (GObject *object); + +enum +{ + ITEM_ADDED, + ITEM_REMOVED, + TOOLBAR_ADDED, + TOOLBAR_CHANGED, + TOOLBAR_REMOVED, + LAST_SIGNAL +}; + +typedef struct +{ + char *name; + EggTbModelFlags flags; +} EggToolbarsToolbar; + +typedef struct +{ + char *name; +} EggToolbarsItem; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define EGG_TOOLBARS_MODEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelPrivate)) + +struct EggToolbarsModelPrivate +{ + GNode *toolbars; + GList *types; + GHashTable *flags; +}; + +G_DEFINE_TYPE (EggToolbarsModel, egg_toolbars_model, G_TYPE_OBJECT) + +static xmlDocPtr +egg_toolbars_model_to_xml (EggToolbarsModel *model) +{ + GNode *l1, *l2, *tl; + GList *l3; + xmlDocPtr doc; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), NULL); + + tl = model->priv->toolbars; + + xmlIndentTreeOutput = TRUE; + doc = xmlNewDoc ((const xmlChar*) "1.0"); + doc->children = xmlNewDocNode (doc, NULL, (const xmlChar*) "toolbars", NULL); + + for (l1 = tl->children; l1 != NULL; l1 = l1->next) + { + xmlNodePtr tnode; + EggToolbarsToolbar *toolbar = l1->data; + + tnode = xmlNewChild (doc->children, NULL, (const xmlChar*) "toolbar", NULL); + xmlSetProp (tnode, (const xmlChar*) "name", (const xmlChar*) toolbar->name); + xmlSetProp (tnode, (const xmlChar*) "hidden", + (toolbar->flags&EGG_TB_MODEL_HIDDEN) ? (const xmlChar*) "true" : (const xmlChar*) "false"); + xmlSetProp (tnode, (const xmlChar*) "editable", + (toolbar->flags&EGG_TB_MODEL_NOT_EDITABLE) ? (const xmlChar*) "false" : (const xmlChar*) "true"); + + for (l2 = l1->children; l2 != NULL; l2 = l2->next) + { + xmlNodePtr node; + EggToolbarsItem *item = l2->data; + + if (strcmp (item->name, "_separator") == 0) + { + node = xmlNewChild (tnode, NULL, (const xmlChar*) "separator", NULL); + continue; + } + + node = xmlNewChild (tnode, NULL, (const xmlChar*) "toolitem", NULL); + xmlSetProp (node, (const xmlChar*) "name", (const xmlChar*) item->name); + + /* Add 'data' nodes for each data type which can be written out for this + * item. Only write types which can be used to restore the data. */ + for (l3 = model->priv->types; l3 != NULL; l3 = l3->next) + { + EggToolbarsItemType *type = l3->data; + if (type->get_name != NULL && type->get_data != NULL) + { + xmlNodePtr dnode; + char *tmp; + + tmp = type->get_data (type, item->name); + if (tmp != NULL) + { + dnode = xmlNewTextChild (node, NULL, (const xmlChar*) "data", (const xmlChar*) tmp); + g_free (tmp); + + tmp = gdk_atom_name (type->type); + xmlSetProp (dnode, (const xmlChar*) "type", (const xmlChar*) tmp); + g_free (tmp); + } + } + } + } + } + + return doc; +} + +static gboolean +safe_save_xml (const char *xml_file, xmlDocPtr doc) +{ + char *tmp_file; + char *old_file; + gboolean old_exist; + gboolean retval = TRUE; + + tmp_file = g_strconcat (xml_file, ".tmp", NULL); + old_file = g_strconcat (xml_file, ".old", NULL); + + if (xmlSaveFormatFile (tmp_file, doc, 1) <= 0) + { + g_warning ("Failed to write XML data to %s", tmp_file); + goto failed; + } + + old_exist = g_file_test (xml_file, G_FILE_TEST_EXISTS); + + if (old_exist) + { + if (rename (xml_file, old_file) < 0) + { + g_warning ("Failed to rename %s to %s", xml_file, old_file); + retval = FALSE; + goto failed; + } + } + + if (rename (tmp_file, xml_file) < 0) + { + g_warning ("Failed to rename %s to %s", tmp_file, xml_file); + + if (rename (old_file, xml_file) < 0) + { + g_warning ("Failed to restore %s from %s", xml_file, tmp_file); + } + retval = FALSE; + goto failed; + } + + if (old_exist) + { + if (unlink (old_file) < 0) + { + g_warning ("Failed to delete old file %s", old_file); + } + } + + failed: + g_free (old_file); + g_free (tmp_file); + + return retval; +} + +void +egg_toolbars_model_save_toolbars (EggToolbarsModel *model, + const char *xml_file, + const char *version) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + doc = egg_toolbars_model_to_xml (model); + root = xmlDocGetRootElement (doc); + xmlSetProp (root, (const xmlChar*) "version", (const xmlChar*) version); + safe_save_xml (xml_file, doc); + xmlFreeDoc (doc); +} + +static gboolean +is_unique (EggToolbarsModel *model, + EggToolbarsItem *idata) +{ + EggToolbarsItem *idata2; + GNode *toolbar, *item; + + + for(toolbar = g_node_first_child (model->priv->toolbars); + toolbar != NULL; toolbar = g_node_next_sibling (toolbar)) + { + for(item = g_node_first_child (toolbar); + item != NULL; item = g_node_next_sibling (item)) + { + idata2 = item->data; + + if (idata != idata2 && strcmp (idata->name, idata2->name) == 0) + { + return FALSE; + } + } + } + + return TRUE; +} + +static GNode * +toolbar_node_new (const char *name) +{ + EggToolbarsToolbar *toolbar; + + toolbar = g_new (EggToolbarsToolbar, 1); + toolbar->name = g_strdup (name); + toolbar->flags = 0; + + return g_node_new (toolbar); +} + +static GNode * +item_node_new (const char *name, EggToolbarsModel *model) +{ + EggToolbarsItem *item; + int flags; + + g_return_val_if_fail (name != NULL, NULL); + + item = g_new (EggToolbarsItem, 1); + item->name = g_strdup (name); + + flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name)); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0) + g_hash_table_insert (model->priv->flags, + g_strdup (item->name), + GINT_TO_POINTER (flags | EGG_TB_MODEL_NAME_USED)); + + return g_node_new (item); +} + +static void +item_node_free (GNode *item_node, EggToolbarsModel *model) +{ + EggToolbarsItem *item = item_node->data; + int flags; + + flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name)); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0 && is_unique (model, item)) + g_hash_table_insert (model->priv->flags, + g_strdup (item->name), + GINT_TO_POINTER (flags & ~EGG_TB_MODEL_NAME_USED)); + + g_free (item->name); + g_free (item); + + g_node_destroy (item_node); +} + +static void +toolbar_node_free (GNode *toolbar_node, EggToolbarsModel *model) +{ + EggToolbarsToolbar *toolbar = toolbar_node->data; + + g_node_children_foreach (toolbar_node, G_TRAVERSE_ALL, + (GNodeForeachFunc) item_node_free, model); + + g_free (toolbar->name); + g_free (toolbar); + + g_node_destroy (toolbar_node); +} + +EggTbModelFlags +egg_toolbars_model_get_flags (EggToolbarsModel *model, + int toolbar_position) +{ + GNode *toolbar_node; + EggToolbarsToolbar *toolbar; + + toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar_node != NULL, 0); + + toolbar = toolbar_node->data; + + return toolbar->flags; +} + +void +egg_toolbars_model_set_flags (EggToolbarsModel *model, + int toolbar_position, + EggTbModelFlags flags) +{ + GNode *toolbar_node; + EggToolbarsToolbar *toolbar; + + toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar_node != NULL); + + toolbar = toolbar_node->data; + + toolbar->flags = flags; + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_CHANGED], + 0, toolbar_position); +} + + +char * +egg_toolbars_model_get_data (EggToolbarsModel *model, + GdkAtom type, + const char *name) +{ + EggToolbarsItemType *t; + char *data = NULL; + GList *l; + + if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE)) + { + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (*name != 0, NULL); + return strdup (name); + } + + for (l = model->priv->types; l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->get_data != NULL) + { + data = t->get_data (t, name); + if (data != NULL) break; + } + } + + return data; +} + +char * +egg_toolbars_model_get_name (EggToolbarsModel *model, + GdkAtom type, + const char *data, + gboolean create) +{ + EggToolbarsItemType *t; + char *name = NULL; + GList *l; + + if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE)) + { + g_return_val_if_fail (data, NULL); + g_return_val_if_fail (*data, NULL); + return strdup (data); + } + + if (create) + { + for (l = model->priv->types; name == NULL && l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->new_name != NULL) + name = t->new_name (t, data); + } + + return name; + } + else + { + for (l = model->priv->types; name == NULL && l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->get_name != NULL) + name = t->get_name (t, data); + } + + return name; + } +} + +static gboolean +impl_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name) +{ + GNode *parent_node; + GNode *child_node; + int real_position; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + parent_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + child_node = item_node_new (name, model); + g_node_insert (parent_node, position, child_node); + + real_position = g_node_child_position (parent_node, child_node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0, + toolbar_position, real_position); + + return TRUE; +} + +gboolean +egg_toolbars_model_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name) +{ + EggToolbarsModelClass *klass = EGG_TOOLBARS_MODEL_GET_CLASS (model); + return klass->add_item (model, toolbar_position, position, name); +} + +int +egg_toolbars_model_add_toolbar (EggToolbarsModel *model, + int position, + const char *name) +{ + GNode *node; + int real_position; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), -1); + + node = toolbar_node_new (name); + g_node_insert (model->priv->toolbars, position, node); + + real_position = g_node_child_position (model->priv->toolbars, node); + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_ADDED], + 0, real_position); + + return g_node_child_position (model->priv->toolbars, node); +} + +static char * +parse_data_list (EggToolbarsModel *model, + xmlNodePtr child, + gboolean create) +{ + char *name = NULL; + while (child && name == NULL) + { + if (xmlStrEqual (child->name, (const xmlChar*) "data")) + { + xmlChar *type = xmlGetProp (child, (const xmlChar*) "type"); + xmlChar *data = xmlNodeGetContent (child); + + if (type != NULL) + { + GdkAtom atom = gdk_atom_intern ((const char*) type, TRUE); + name = egg_toolbars_model_get_name (model, atom, (const char*) data, create); + } + + xmlFree (type); + xmlFree (data); + } + + child = child->next; + } + + return name; +} + +static void +parse_item_list (EggToolbarsModel *model, + xmlNodePtr child, + int position) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolitem")) + { + char *name; + + /* Try to get the name using the data elements first, + as they are more 'portable' or 'persistent'. */ + name = parse_data_list (model, child->children, FALSE); + if (name == NULL) + { + name = parse_data_list (model, child->children, TRUE); + } + + /* If that fails, try to use the name. */ + if (name == NULL) + { + xmlChar *type = xmlGetProp (child, (const xmlChar*) "type"); + xmlChar *data = xmlGetProp (child, (const xmlChar*) "name"); + GdkAtom atom = type ? gdk_atom_intern ((const char*) type, TRUE) : GDK_NONE; + + /* If an old format, try to use it. */ + name = egg_toolbars_model_get_name (model, atom, (const char*) data, FALSE); + if (name == NULL) + { + name = egg_toolbars_model_get_name (model, atom, (const char*) data, TRUE); + } + + xmlFree (type); + xmlFree (data); + } + + if (name != NULL) + { + egg_toolbars_model_add_item (model, position, -1, name); + g_free (name); + } + } + else if (xmlStrEqual (child->name, (const xmlChar*) "separator")) + { + egg_toolbars_model_add_item (model, position, -1, "_separator"); + } + + child = child->next; + } +} + +static void +parse_toolbars (EggToolbarsModel *model, + xmlNodePtr child) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolbar")) + { + xmlChar *string; + int position; + EggTbModelFlags flags; + + string = xmlGetProp (child, (const xmlChar*) "name"); + position = egg_toolbars_model_add_toolbar (model, -1, (const char*) string); + flags = egg_toolbars_model_get_flags (model, position); + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "editable"); + if (string && xmlStrEqual (string, (const xmlChar*) "false")) + flags |= EGG_TB_MODEL_NOT_EDITABLE; + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "hidden"); + if (string && xmlStrEqual (string, (const xmlChar*) "true")) + flags |= EGG_TB_MODEL_HIDDEN; + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "style"); + if (string && xmlStrEqual (string, (const xmlChar*) "icons-only")) + flags |= EGG_TB_MODEL_ICONS; + xmlFree (string); + + egg_toolbars_model_set_flags (model, position, flags); + + parse_item_list (model, child->children, position); + } + + child = child->next; + } +} + +gboolean +egg_toolbars_model_load_toolbars (EggToolbarsModel *model, + const char *xml_file) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + + if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE; + + doc = xmlParseFile (xml_file); + if (doc == NULL) + { + g_warning ("Failed to load XML data from %s", xml_file); + return FALSE; + } + root = xmlDocGetRootElement (doc); + + parse_toolbars (model, root->children); + + xmlFreeDoc (doc); + + return TRUE; +} + +static void +parse_available_list (EggToolbarsModel *model, + xmlNodePtr child) +{ + gint flags; + + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolitem")) + { + xmlChar *name; + + name = xmlGetProp (child, (const xmlChar*) "name"); + flags = egg_toolbars_model_get_name_flags + (model, (const char*)name); + egg_toolbars_model_set_name_flags + (model, (const char*)name, flags | EGG_TB_MODEL_NAME_KNOWN); + xmlFree (name); + } + child = child->next; + } +} + +static void +parse_names (EggToolbarsModel *model, + xmlNodePtr child) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "available")) + { + parse_available_list (model, child->children); + } + + child = child->next; + } +} + +gboolean +egg_toolbars_model_load_names (EggToolbarsModel *model, + const char *xml_file) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + + if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE; + + doc = xmlParseFile (xml_file); + if (doc == NULL) + { + g_warning ("Failed to load XML data from %s", xml_file); + return FALSE; + } + root = xmlDocGetRootElement (doc); + + parse_names (model, root->children); + + xmlFreeDoc (doc); + + return TRUE; +} + +static void +egg_toolbars_model_class_init (EggToolbarsModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + volatile GType flags_type; /* work around gcc's optimiser */ + + /* make sure the flags type is known */ + flags_type = EGG_TYPE_TB_MODEL_FLAGS; + + object_class->finalize = egg_toolbars_model_finalize; + + klass->add_item = impl_add_item; + + signals[ITEM_ADDED] = + g_signal_new ("item_added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, item_added), + NULL, NULL, _egg_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + signals[TOOLBAR_ADDED] = + g_signal_new ("toolbar_added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_added), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + signals[ITEM_REMOVED] = + g_signal_new ("item_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, item_removed), + NULL, NULL, _egg_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + signals[TOOLBAR_REMOVED] = + g_signal_new ("toolbar_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_removed), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + signals[TOOLBAR_CHANGED] = + g_signal_new ("toolbar_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_changed), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + + g_type_class_add_private (object_class, sizeof (EggToolbarsModelPrivate)); +} + +static void +egg_toolbars_model_init (EggToolbarsModel *model) +{ + model->priv =EGG_TOOLBARS_MODEL_GET_PRIVATE (model); + + model->priv->toolbars = g_node_new (NULL); + model->priv->flags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + egg_toolbars_model_set_name_flags (model, "_separator", + EGG_TB_MODEL_NAME_KNOWN | + EGG_TB_MODEL_NAME_INFINITE); +} + +static void +egg_toolbars_model_finalize (GObject *object) +{ + EggToolbarsModel *model = EGG_TOOLBARS_MODEL (object); + + g_node_children_foreach (model->priv->toolbars, G_TRAVERSE_ALL, + (GNodeForeachFunc) toolbar_node_free, model); + g_node_destroy (model->priv->toolbars); + g_hash_table_destroy (model->priv->flags); + + G_OBJECT_CLASS (egg_toolbars_model_parent_class)->finalize (object); +} + +EggToolbarsModel * +egg_toolbars_model_new (void) +{ + return EGG_TOOLBARS_MODEL (g_object_new (EGG_TYPE_TOOLBARS_MODEL, NULL)); +} + +void +egg_toolbars_model_remove_toolbar (EggToolbarsModel *model, + int position) +{ + GNode *node; + EggTbModelFlags flags; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + flags = egg_toolbars_model_get_flags (model, position); + + if (!(flags & EGG_TB_MODEL_NOT_REMOVABLE)) + { + node = g_node_nth_child (model->priv->toolbars, position); + g_return_if_fail (node != NULL); + + toolbar_node_free (node, model); + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_REMOVED], + 0, position); + } +} + +void +egg_toolbars_model_remove_item (EggToolbarsModel *model, + int toolbar_position, + int position) +{ + GNode *node, *toolbar; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar != NULL); + + node = g_node_nth_child (toolbar, position); + g_return_if_fail (node != NULL); + + item_node_free (node, model); + + g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0, + toolbar_position, position); +} + +void +egg_toolbars_model_move_item (EggToolbarsModel *model, + int toolbar_position, + int position, + int new_toolbar_position, + int new_position) +{ + GNode *node, *toolbar, *new_toolbar; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar != NULL); + + new_toolbar = g_node_nth_child (model->priv->toolbars, new_toolbar_position); + g_return_if_fail (new_toolbar != NULL); + + node = g_node_nth_child (toolbar, position); + g_return_if_fail (node != NULL); + + g_node_unlink (node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0, + toolbar_position, position); + + g_node_insert (new_toolbar, new_position, node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0, + new_toolbar_position, new_position); +} + +void +egg_toolbars_model_delete_item (EggToolbarsModel *model, + const char *name) +{ + EggToolbarsItem *idata; + EggToolbarsToolbar *tdata; + GNode *toolbar, *item, *next; + int tpos, ipos; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_first_child (model->priv->toolbars); + tpos = 0; + + while (toolbar != NULL) + { + item = g_node_first_child (toolbar); + ipos = 0; + + /* Don't delete toolbars that were already empty */ + if (item == NULL) + { + toolbar = g_node_next_sibling (toolbar); + continue; + } + + while (item != NULL) + { + next = g_node_next_sibling (item); + idata = item->data; + if (strcmp (idata->name, name) == 0) + { + item_node_free (item, model); + g_signal_emit (G_OBJECT (model), + signals[ITEM_REMOVED], + 0, tpos, ipos); + } + else + { + ipos++; + } + + item = next; + } + + next = g_node_next_sibling (toolbar); + tdata = toolbar->data; + if (!(tdata->flags & EGG_TB_MODEL_NOT_REMOVABLE) && + g_node_first_child (toolbar) == NULL) + { + toolbar_node_free (toolbar, model); + + g_signal_emit (G_OBJECT (model), + signals[TOOLBAR_REMOVED], + 0, tpos); + } + else + { + tpos++; + } + + toolbar = next; + } +} + +int +egg_toolbars_model_n_items (EggToolbarsModel *model, + int toolbar_position) +{ + GNode *toolbar; + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar != NULL, -1); + + return g_node_n_children (toolbar); +} + +const char * +egg_toolbars_model_item_nth (EggToolbarsModel *model, + int toolbar_position, + int position) +{ + GNode *toolbar; + GNode *item; + EggToolbarsItem *idata; + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar != NULL, NULL); + + item = g_node_nth_child (toolbar, position); + g_return_val_if_fail (item != NULL, NULL); + + idata = item->data; + return idata->name; +} + +int +egg_toolbars_model_n_toolbars (EggToolbarsModel *model) +{ + return g_node_n_children (model->priv->toolbars); +} + +const char * +egg_toolbars_model_toolbar_nth (EggToolbarsModel *model, + int position) +{ + GNode *toolbar; + EggToolbarsToolbar *tdata; + + toolbar = g_node_nth_child (model->priv->toolbars, position); + g_return_val_if_fail (toolbar != NULL, NULL); + + tdata = toolbar->data; + + return tdata->name; +} + +GList * +egg_toolbars_model_get_types (EggToolbarsModel *model) +{ + return model->priv->types; +} + +void +egg_toolbars_model_set_types (EggToolbarsModel *model, GList *types) +{ + model->priv->types = types; +} + +static void +fill_avail_array (gpointer key, gpointer value, GPtrArray *array) +{ + int flags = GPOINTER_TO_INT (value); + if ((flags & EGG_TB_MODEL_NAME_KNOWN) && !(flags & EGG_TB_MODEL_NAME_USED)) + g_ptr_array_add (array, key); +} + +GPtrArray * +egg_toolbars_model_get_name_avail (EggToolbarsModel *model) +{ + GPtrArray *array = g_ptr_array_new (); + g_hash_table_foreach (model->priv->flags, (GHFunc) fill_avail_array, array); + return array; +} + +gint +egg_toolbars_model_get_name_flags (EggToolbarsModel *model, const char *name) +{ + return GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, name)); +} + +void +egg_toolbars_model_set_name_flags (EggToolbarsModel *model, const char *name, gint flags) +{ + g_hash_table_insert (model->priv->flags, g_strdup (name), GINT_TO_POINTER (flags)); +} diff --git a/cut-n-paste/toolbar-editor/egg-toolbars-model.h b/cut-n-paste/toolbar-editor/egg-toolbars-model.h new file mode 100644 index 00000000..5d9841f8 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbars-model.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2004 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EGG_TOOLBARS_MODEL_H +#define EGG_TOOLBARS_MODEL_H + +#include <glib.h> +#include <glib-object.h> +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_TOOLBARS_MODEL (egg_toolbars_model_get_type ()) +#define EGG_TOOLBARS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModel)) +#define EGG_TOOLBARS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelClass)) +#define EGG_IS_TOOLBARS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TOOLBARS_MODEL)) +#define EGG_IS_TOOLBARS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TOOLBARS_MODEL)) +#define EGG_TOOLBARS_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelClass)) + +typedef struct EggToolbarsModel EggToolbarsModel; +typedef struct EggToolbarsModelPrivate EggToolbarsModelPrivate; +typedef struct EggToolbarsModelClass EggToolbarsModelClass; + +#define EGG_TOOLBAR_ITEM_TYPE "application/x-toolbar-item" + +typedef enum +{ + EGG_TB_MODEL_NOT_REMOVABLE = 1 << 0, + EGG_TB_MODEL_NOT_EDITABLE = 1 << 1, + EGG_TB_MODEL_BOTH = 1 << 2, + EGG_TB_MODEL_BOTH_HORIZ = 1 << 3, + EGG_TB_MODEL_ICONS = 1 << 4, + EGG_TB_MODEL_TEXT = 1 << 5, + EGG_TB_MODEL_STYLES_MASK = 0x3C, + EGG_TB_MODEL_ACCEPT_ITEMS_ONLY = 1 << 6, + EGG_TB_MODEL_HIDDEN = 1 << 7 +} EggTbModelFlags; + +typedef enum +{ + EGG_TB_MODEL_NAME_USED = 1 << 0, + EGG_TB_MODEL_NAME_INFINITE = 1 << 1, + EGG_TB_MODEL_NAME_KNOWN = 1 << 2 +} EggTbModelNameFlags; + +struct EggToolbarsModel +{ + GObject parent_object; + + /*< private >*/ + EggToolbarsModelPrivate *priv; +}; + +struct EggToolbarsModelClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* item_added) (EggToolbarsModel *model, + int toolbar_position, + int position); + void (* item_removed) (EggToolbarsModel *model, + int toolbar_position, + int position); + void (* toolbar_added) (EggToolbarsModel *model, + int position); + void (* toolbar_changed) (EggToolbarsModel *model, + int position); + void (* toolbar_removed) (EggToolbarsModel *model, + int position); + + /* Virtual Table */ + gboolean (* add_item) (EggToolbarsModel *t, + int toolbar_position, + int position, + const char *name); +}; + +typedef struct EggToolbarsItemType EggToolbarsItemType; + +struct EggToolbarsItemType +{ + GdkAtom type; + + gboolean (* has_data) (EggToolbarsItemType *type, + const char *name); + char * (* get_data) (EggToolbarsItemType *type, + const char *name); + + char * (* new_name) (EggToolbarsItemType *type, + const char *data); + char * (* get_name) (EggToolbarsItemType *type, + const char *data); +}; + +GType egg_tb_model_flags_get_type (void); +GType egg_toolbars_model_get_type (void); +EggToolbarsModel *egg_toolbars_model_new (void); +gboolean egg_toolbars_model_load_names (EggToolbarsModel *model, + const char *xml_file); +gboolean egg_toolbars_model_load_toolbars (EggToolbarsModel *model, + const char *xml_file); +void egg_toolbars_model_save_toolbars (EggToolbarsModel *model, + const char *xml_file, + const char *version); + +/* Functions for manipulating the types of portable data this toolbar understands. */ +GList * egg_toolbars_model_get_types (EggToolbarsModel *model); +void egg_toolbars_model_set_types (EggToolbarsModel *model, + GList *types); + +/* Functions for converting between name and portable data. */ +char * egg_toolbars_model_get_name (EggToolbarsModel *model, + GdkAtom type, + const char *data, + gboolean create); +char * egg_toolbars_model_get_data (EggToolbarsModel *model, + GdkAtom type, + const char *name); + +/* Functions for retrieving what items are available for adding to the toolbars. */ +GPtrArray * egg_toolbars_model_get_name_avail (EggToolbarsModel *model); +gint egg_toolbars_model_get_name_flags (EggToolbarsModel *model, + const char *name); +void egg_toolbars_model_set_name_flags (EggToolbarsModel *model, + const char *name, + gint flags); + +/* Functions for manipulating flags on individual toolbars. */ +EggTbModelFlags egg_toolbars_model_get_flags (EggToolbarsModel *model, + int toolbar_position); +void egg_toolbars_model_set_flags (EggToolbarsModel *model, + int toolbar_position, + EggTbModelFlags flags); + +/* Functions for adding and removing toolbars. */ +int egg_toolbars_model_add_toolbar (EggToolbarsModel *model, + int position, + const char *name); +void egg_toolbars_model_remove_toolbar (EggToolbarsModel *model, + int position); + +/* Functions for adding, removing and moving items. */ +gboolean egg_toolbars_model_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name); +void egg_toolbars_model_remove_item (EggToolbarsModel *model, + int toolbar_position, + int position); +void egg_toolbars_model_move_item (EggToolbarsModel *model, + int toolbar_position, + int position, + int new_toolbar_position, + int new_position); +void egg_toolbars_model_delete_item (EggToolbarsModel *model, + const char *name); + +/* Functions for accessing the names of items. */ +int egg_toolbars_model_n_items (EggToolbarsModel *model, + int toolbar_position); +const char * egg_toolbars_model_item_nth (EggToolbarsModel *model, + int toolbar_position, + int position); + +/* Functions for accessing the names of toolbars. */ +int egg_toolbars_model_n_toolbars (EggToolbarsModel *model); +const char *egg_toolbars_model_toolbar_nth (EggToolbarsModel *model, + int position); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/eggmarshalers.list b/cut-n-paste/toolbar-editor/eggmarshalers.list new file mode 100644 index 00000000..1f953ddf --- /dev/null +++ b/cut-n-paste/toolbar-editor/eggmarshalers.list @@ -0,0 +1 @@ +VOID:INT,INT diff --git a/cut-n-paste/totem-screensaver/Makefile.am b/cut-n-paste/totem-screensaver/Makefile.am new file mode 100644 index 00000000..1304c8d4 --- /dev/null +++ b/cut-n-paste/totem-screensaver/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = libtotemscrsaver.la +libtotemscrsaver_la_SOURCES = \ + totem-scrsaver.h \ + totem-scrsaver.c + +libtotemscrsaver_la_CPPFLAGS = \ + $(AM_CPPFLAGS) + +libtotemscrsaver_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(AM_CFLAGS) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/totem-screensaver/README b/cut-n-paste/totem-screensaver/README new file mode 100644 index 00000000..a5be11b2 --- /dev/null +++ b/cut-n-paste/totem-screensaver/README @@ -0,0 +1,3 @@ +The sources for the screensaver enabling/disabling code are copied from Totem. +A simple replacement (s/WITH_DBUS/ENABLE_DBUS/g) was needed. The hardcoded +"reason for inhibiting" string was also modified. diff --git a/cut-n-paste/totem-screensaver/totem-scrsaver.c b/cut-n-paste/totem-screensaver/totem-scrsaver.c new file mode 100644 index 00000000..f30f533e --- /dev/null +++ b/cut-n-paste/totem-screensaver/totem-scrsaver.c @@ -0,0 +1,554 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + + Copyright (C) 2004-2006 Bastien Nocera <[email protected]> + Copyright © 2010 Christian Persch + Copyright © 2010 Carlos Garcia Campos + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA. + + Authors: Bastien Nocera <[email protected]> + Christian Persch + Carlos Garcia Campos + */ + +#include "config.h" + +#include <gdk/gdk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#include <X11/keysym.h> + +#ifdef HAVE_XTEST +#include <X11/extensions/XTest.h> +#endif /* HAVE_XTEST */ +#endif /* GDK_WINDOWING_X11 */ + +#include "totem-scrsaver.h" + +#define GS_SERVICE "org.mate.ScreenSaver" +#define GS_PATH "/org/mate/ScreenSaver" +#define GS_INTERFACE "org.mate.ScreenSaver" + +#define XSCREENSAVER_MIN_TIMEOUT 60 + +enum { + PROP_0, + PROP_REASON +}; + +static void totem_scrsaver_finalize (GObject *object); + +struct TotemScrsaverPrivate { + /* Whether the screensaver is disabled */ + gboolean disabled; + /* The reason for the inhibition */ + char *reason; + + GDBusProxy *gs_proxy; + gboolean have_screensaver_dbus; + guint32 cookie; + gboolean old_dbus_api; + + /* To save the screensaver info */ + int timeout; + int interval; + int prefer_blanking; + int allow_exposures; + + /* For use with XTest */ + int keycode1, keycode2; + int *keycode; + gboolean have_xtest; +}; + +G_DEFINE_TYPE(TotemScrsaver, totem_scrsaver, G_TYPE_OBJECT) + +static gboolean +screensaver_is_running_dbus (TotemScrsaver *scr) +{ + return scr->priv->have_screensaver_dbus; +} + +static void +on_inhibit_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source_object); + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + GVariant *value; + GError *error = NULL; + + value = g_dbus_proxy_call_finish (proxy, res, &error); + if (!value) { + if (!scr->priv->old_dbus_api && + g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { + g_return_if_fail (scr->priv->reason != NULL); + /* try the old API */ + scr->priv->old_dbus_api = TRUE; + g_dbus_proxy_call (proxy, + "InhibitActivation", + g_variant_new ("(s)", + scr->priv->reason), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_inhibit_cb, + scr); + } else { + g_warning ("Problem inhibiting the screensaver: %s", error->message); + } + g_error_free (error); + + return; + } + + /* save the cookie */ + if (g_variant_is_of_type (value, G_VARIANT_TYPE ("(u)"))) + g_variant_get (value, "(u)", &scr->priv->cookie); + else + scr->priv->cookie = 0; + g_variant_unref (value); +} + +static void +on_uninhibit_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source_object); + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + GVariant *value; + GError *error = NULL; + + value = g_dbus_proxy_call_finish (proxy, res, &error); + if (!value) { + if (!scr->priv->old_dbus_api && + g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { + /* try the old API */ + scr->priv->old_dbus_api = TRUE; + g_dbus_proxy_call (proxy, + "AllowActivation", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_uninhibit_cb, + scr); + } else { + g_warning ("Problem uninhibiting the screensaver: %s", error->message); + } + g_error_free (error); + + return; + } + + /* clear the cookie */ + scr->priv->cookie = 0; + g_variant_unref (value); +} + +static void +screensaver_inhibit_dbus (TotemScrsaver *scr, + gboolean inhibit) +{ + TotemScrsaverPrivate *priv = scr->priv; + + if (!priv->have_screensaver_dbus) + return; + + scr->priv->old_dbus_api = FALSE; + + if (inhibit) { + g_return_if_fail (scr->priv->reason != NULL); + g_dbus_proxy_call (priv->gs_proxy, + "Inhibit", + g_variant_new ("(ss)", + g_get_application_name (), + scr->priv->reason), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_inhibit_cb, + scr); + } else { + g_dbus_proxy_call (priv->gs_proxy, + "UnInhibit", + g_variant_new ("(u)", priv->cookie), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_uninhibit_cb, + scr); + } +} + +static void +screensaver_enable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, FALSE); +} + +static void +screensaver_disable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, TRUE); +} + +static void +screensaver_update_dbus_presence (TotemScrsaver *scr) +{ + TotemScrsaverPrivate *priv = scr->priv; + gchar *name_owner; + + name_owner = g_dbus_proxy_get_name_owner (priv->gs_proxy); + if (name_owner) { + priv->have_screensaver_dbus = TRUE; + g_free (name_owner); + } else { + priv->have_screensaver_dbus = FALSE; + } +} + +static void +screensaver_dbus_owner_changed_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + + screensaver_update_dbus_presence (scr); +} + +static void +screensaver_dbus_proxy_new_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + TotemScrsaverPrivate *priv = scr->priv; + + priv->gs_proxy = g_dbus_proxy_new_for_bus_finish (result, NULL); + if (!priv->gs_proxy) + return; + + screensaver_update_dbus_presence (scr); + + g_signal_connect (priv->gs_proxy, "notify::g-name-owner", + G_CALLBACK (screensaver_dbus_owner_changed_cb), + scr); +} + +static void +screensaver_init_dbus (TotemScrsaver *scr) +{ + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + GS_SERVICE, + GS_PATH, + GS_INTERFACE, + NULL, + screensaver_dbus_proxy_new_cb, + scr); +} + +static void +screensaver_finalize_dbus (TotemScrsaver *scr) +{ + if (scr->priv->gs_proxy) { + g_object_unref (scr->priv->gs_proxy); + } +} + +#ifdef GDK_WINDOWING_X11 +static void +screensaver_enable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + g_source_remove_by_user_data (scr); + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XSetScreenSaver (GDK_DISPLAY(), + scr->priv->timeout, + scr->priv->interval, + scr->priv->prefer_blanking, + scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +#ifdef HAVE_XTEST +static gboolean +fake_event (TotemScrsaver *scr) +{ + if (scr->priv->disabled) + { + XLockDisplay (GDK_DISPLAY()); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + True, CurrentTime); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + False, CurrentTime); + XUnlockDisplay (GDK_DISPLAY()); + /* Swap the keycode */ + if (scr->priv->keycode == &scr->priv->keycode1) + scr->priv->keycode = &scr->priv->keycode2; + else + scr->priv->keycode = &scr->priv->keycode1; + } + + return TRUE; +} +#endif /* HAVE_XTEST */ + +static void +screensaver_disable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); + + if (scr->priv->timeout != 0) { + g_timeout_add_seconds (scr->priv->timeout / 2, + (GSourceFunc) fake_event, scr); + } else { + g_timeout_add_seconds (XSCREENSAVER_MIN_TIMEOUT / 2, + (GSourceFunc) fake_event, scr); + } + + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XSetScreenSaver(GDK_DISPLAY(), 0, 0, + DontPreferBlanking, DontAllowExposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +static void +screensaver_init_x11 (TotemScrsaver *scr) +{ +#ifdef HAVE_XTEST + int a, b, c, d; + + XLockDisplay (GDK_DISPLAY()); + scr->priv->have_xtest = (XTestQueryExtension (GDK_DISPLAY(), &a, &b, &c, &d) == True); + if (scr->priv->have_xtest != FALSE) + { + scr->priv->keycode1 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode1 == 0) { + g_warning ("scr->priv->keycode1 not existant"); + } + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R); + if (scr->priv->keycode2 == 0) { + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode2 == 0) { + g_warning ("scr->priv->keycode2 not existant"); + } + } + scr->priv->keycode = &scr->priv->keycode1; + } + XUnlockDisplay (GDK_DISPLAY()); +#endif /* HAVE_XTEST */ +} + +static void +screensaver_finalize_x11 (TotemScrsaver *scr) +{ + g_source_remove_by_user_data (scr); +} +#endif + +static void +totem_scrsaver_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TotemScrsaver *scr; + + scr = TOTEM_SCRSAVER (object); + + switch (property_id) + { + case PROP_REASON: + g_value_set_string (value, scr->priv->reason); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +totem_scrsaver_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TotemScrsaver *scr; + + scr = TOTEM_SCRSAVER (object); + + switch (property_id) + { + case PROP_REASON: + g_free (scr->priv->reason); + scr->priv->reason = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +totem_scrsaver_class_init (TotemScrsaverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TotemScrsaverPrivate)); + + object_class->set_property = totem_scrsaver_set_property; + object_class->get_property = totem_scrsaver_get_property; + object_class->finalize = totem_scrsaver_finalize; + + g_object_class_install_property (object_class, PROP_REASON, + g_param_spec_string ("reason", NULL, NULL, + NULL, G_PARAM_READWRITE)); + +} + +/** + * totem_scrsaver_new: + * + * Creates a #TotemScrsaver object. + * If the MATE screen saver is running, it uses its DBUS interface to + * inhibit the screensaver; otherwise it falls back to using the X + * screensaver functionality for this. + * + * Returns: a newly created #TotemScrsaver + */ +TotemScrsaver * +totem_scrsaver_new (void) +{ + return TOTEM_SCRSAVER (g_object_new (TOTEM_TYPE_SCRSAVER, NULL)); +} + +static void +totem_scrsaver_init (TotemScrsaver *scr) +{ + scr->priv = G_TYPE_INSTANCE_GET_PRIVATE (scr, + TOTEM_TYPE_SCRSAVER, + TotemScrsaverPrivate); + + screensaver_init_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_init_x11 (scr); +#else +#warning Unimplemented +#endif +} + +void +totem_scrsaver_disable (TotemScrsaver *scr) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled != FALSE) + return; + + scr->priv->disabled = TRUE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_disable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_disable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_enable (TotemScrsaver *scr) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled == FALSE) + return; + + scr->priv->disabled = FALSE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_enable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_enable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_set_state (TotemScrsaver *scr, gboolean enable) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled == !enable) + return; + + if (enable == FALSE) + totem_scrsaver_disable (scr); + else + totem_scrsaver_enable (scr); +} + +static void +totem_scrsaver_finalize (GObject *object) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (object); + + g_free (scr->priv->reason); + + screensaver_finalize_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_finalize_x11 (scr); +#else +#warning Unimplemented + {} +#endif + + G_OBJECT_CLASS (totem_scrsaver_parent_class)->finalize (object); +} diff --git a/cut-n-paste/totem-screensaver/totem-scrsaver.h b/cut-n-paste/totem-screensaver/totem-scrsaver.h new file mode 100644 index 00000000..bb95174d --- /dev/null +++ b/cut-n-paste/totem-screensaver/totem-scrsaver.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2004, Bastien Nocera <[email protected]> + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA. + + Author: Bastien Nocera <[email protected]> + */ + +#ifndef TOTEM_SCRSAVER_H +#define TOTEM_SCRSAVER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define TOTEM_TYPE_SCRSAVER (totem_scrsaver_get_type ()) +#define TOTEM_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_SCRSAVER, TotemScrsaver)) +#define TOTEM_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_SCRSAVER, TotemScrsaverClass)) +#define TOTEM_IS_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_SCRSAVER)) +#define TOTEM_IS_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_SCRSAVER)) + +typedef struct TotemScrsaver TotemScrsaver; +typedef struct TotemScrsaverClass TotemScrsaverClass; +typedef struct TotemScrsaverPrivate TotemScrsaverPrivate; + +struct TotemScrsaver { + GObject parent; + TotemScrsaverPrivate *priv; +}; + +struct TotemScrsaverClass { + GObjectClass parent_class; +}; + +GType totem_scrsaver_get_type (void) G_GNUC_CONST; +TotemScrsaver *totem_scrsaver_new (void); +void totem_scrsaver_enable (TotemScrsaver *scr); +void totem_scrsaver_disable (TotemScrsaver *scr); +void totem_scrsaver_set_state (TotemScrsaver *scr, + gboolean enable); + +G_END_DECLS + +#endif /* !TOTEM_SCRSAVER_H */ diff --git a/cut-n-paste/zoom-control/Makefile.am b/cut-n-paste/zoom-control/Makefile.am new file mode 100644 index 00000000..af8bd48e --- /dev/null +++ b/cut-n-paste/zoom-control/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = libephyzoom.la + +libephyzoom_la_SOURCES = \ + ephy-zoom-action.h \ + ephy-zoom-action.c \ + ephy-zoom-control.c \ + ephy-zoom-control.h \ + ephy-zoom.c \ + ephy-zoom.h + +libephyzoom_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/zoom-control/ephy-zoom-action.c b/cut-n-paste/zoom-control/ephy-zoom-action.c new file mode 100644 index 00000000..b6250c8b --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-action.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * Copyright (C) 2003, 2004 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom-action.h" +#include "ephy-zoom-control.h" +#include "ephy-zoom.h" + +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#define EPHY_ZOOM_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionPrivate)) + +struct _EphyZoomActionPrivate +{ + float zoom; + float min_zoom; + float max_zoom; +}; + +enum +{ + PROP_0, + PROP_ZOOM, + PROP_MIN_ZOOM, + PROP_MAX_ZOOM +}; + + +static void ephy_zoom_action_init (EphyZoomAction *action); +static void ephy_zoom_action_class_init (EphyZoomActionClass *class); + +enum +{ + ZOOM_TO_LEVEL_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (EphyZoomAction, ephy_zoom_action, GTK_TYPE_ACTION) + +static void +zoom_to_level_cb (EphyZoomControl *control, + float zoom, + EphyZoomAction *action) +{ + g_signal_emit (action, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); +} + +static void +sync_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "zoom", zoom_action->priv->zoom, NULL); +} + +static void +sync_min_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "min-zoom", zoom_action->priv->min_zoom, NULL); +} + +static void +sync_max_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "max-zoom", zoom_action->priv->max_zoom, NULL); +} + +static void +connect_proxy (GtkAction *action, GtkWidget *proxy) +{ + if (EPHY_IS_ZOOM_CONTROL (proxy)) + { + g_signal_connect_object (action, "notify::zoom", + G_CALLBACK (sync_zoom_cb), proxy, 0); + g_signal_connect_object (action, "notify::min-zoom", + G_CALLBACK (sync_min_zoom_cb), proxy, 0); + g_signal_connect_object (action, "notify::max-zoom", + G_CALLBACK (sync_max_zoom_cb), proxy, 0); + g_signal_connect (proxy, "zoom_to_level", + G_CALLBACK (zoom_to_level_cb), action); + } + + GTK_ACTION_CLASS (ephy_zoom_action_parent_class)->connect_proxy (action, proxy); +} + +static void +proxy_menu_activate_cb (GtkMenuItem *menu_item, EphyZoomAction *action) +{ + gint index; + float zoom; + + /* menu item was toggled OFF */ + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item))) return; + + index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "zoom-level")); + zoom = zoom_levels[index].level; + + if (zoom != action->priv->zoom) + { + g_signal_emit (action, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); + } +} + +static GtkWidget * +create_menu_item (GtkAction *action) +{ + EphyZoomActionPrivate *p = EPHY_ZOOM_ACTION (action)->priv; + GtkWidget *menu, *menu_item; + GSList *group = NULL; + int i; + + menu = gtk_menu_new (); + + for (i = 0; i < n_zoom_levels; i++) + { + if (zoom_levels[i].level == EPHY_ZOOM_SEPARATOR) + { + menu_item = gtk_separator_menu_item_new (); + } + else + { + menu_item = gtk_radio_menu_item_new_with_label (group, + _(zoom_levels[i].name)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item)); + + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), + p->zoom == zoom_levels[i].level); + + g_object_set_data (G_OBJECT (menu_item), "zoom-level", GINT_TO_POINTER (i)); + g_signal_connect_object (G_OBJECT (menu_item), "activate", + G_CALLBACK (proxy_menu_activate_cb), action, 0); + } + + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + } + + gtk_widget_show (menu); + + menu_item = GTK_ACTION_CLASS (ephy_zoom_action_parent_class)->create_menu_item (action); + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + + gtk_widget_show (menu_item); + + return menu_item; +} + +static void +ephy_zoom_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyZoomAction *action; + + action = EPHY_ZOOM_ACTION (object); + + switch (prop_id) + { + case PROP_ZOOM: + action->priv->zoom = g_value_get_float (value); + break; + case PROP_MIN_ZOOM: + action->priv->min_zoom = g_value_get_float (value); + break; + case PROP_MAX_ZOOM: + action->priv->max_zoom = g_value_get_float (value); + break; + } +} + +static void +ephy_zoom_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyZoomAction *action; + + action = EPHY_ZOOM_ACTION (object); + + switch (prop_id) + { + case PROP_ZOOM: + g_value_set_float (value, action->priv->zoom); + break; + case PROP_MIN_ZOOM: + g_value_set_float (value, action->priv->min_zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, action->priv->max_zoom); + break; + } +} + +static void +ephy_zoom_action_class_init (EphyZoomActionClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkActionClass *action_class = GTK_ACTION_CLASS (class); + + object_class->set_property = ephy_zoom_action_set_property; + object_class->get_property = ephy_zoom_action_get_property; + + action_class->toolbar_item_type = EPHY_TYPE_ZOOM_CONTROL; + action_class->connect_proxy = connect_proxy; + action_class->create_menu_item = create_menu_item; + + g_object_class_install_property (object_class, + PROP_ZOOM, + g_param_spec_float ("zoom", + "Zoom", + "Zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + 1.0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MIN_ZOOM, + g_param_spec_float ("min-zoom", + "MinZoom", + "The minimum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MINIMAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", + "MaxZoom", + "The maximum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MAXIMAL, + G_PARAM_READWRITE)); + + signals[ZOOM_TO_LEVEL_SIGNAL] = + g_signal_new ("zoom_to_level", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EphyZoomActionClass, zoom_to_level), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, + 1, + G_TYPE_FLOAT); + + g_type_class_add_private (object_class, sizeof (EphyZoomActionPrivate)); +} + +static void +ephy_zoom_action_init (EphyZoomAction *action) +{ + action->priv = EPHY_ZOOM_ACTION_GET_PRIVATE (action); + + action->priv->zoom = 1.0; +} + +void +ephy_zoom_action_set_zoom_level (EphyZoomAction *action, float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->zoom = zoom; + g_object_notify (G_OBJECT (action), "zoom"); +} + +float +ephy_zoom_action_get_zoom_level (EphyZoomAction *action) +{ + g_return_val_if_fail (EPHY_IS_ZOOM_ACTION (action), 1.0); + + return action->priv->zoom; +} + +void +ephy_zoom_action_set_min_zoom_level (EphyZoomAction *action, + float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->min_zoom = zoom; + if (action->priv->zoom > 0 && action->priv->zoom < zoom) + ephy_zoom_action_set_zoom_level (action, zoom); + + g_object_notify (G_OBJECT (action), "min-zoom"); +} + +void +ephy_zoom_action_set_max_zoom_level (EphyZoomAction *action, + float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->max_zoom = zoom; + if (action->priv->zoom > 0 && action->priv->zoom > zoom) + ephy_zoom_action_set_zoom_level (action, zoom); + + g_object_notify (G_OBJECT (action), "max-zoom"); +} diff --git a/cut-n-paste/zoom-control/ephy-zoom-action.h b/cut-n-paste/zoom-control/ephy-zoom-action.h new file mode 100644 index 00000000..cf9f6feb --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-action.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_ACTION_H +#define EPHY_ZOOM_ACTION_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_ZOOM_ACTION (ephy_zoom_action_get_type ()) +#define EPHY_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPHY_TYPE_ZOOM_ACTION, EphyZoomAction)) +#define EPHY_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionClass)) +#define EPHY_IS_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPHY_TYPE_ZOOM_ACTION)) +#define EPHY_IS_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EPHY_TYPE_ZOOM_ACTION)) +#define EPHY_ZOOM_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionClass)) + +typedef struct _EphyZoomAction EphyZoomAction; +typedef struct _EphyZoomActionClass EphyZoomActionClass; +typedef struct _EphyZoomActionPrivate EphyZoomActionPrivate; + +struct _EphyZoomAction +{ + GtkAction parent; + + /*< private >*/ + EphyZoomActionPrivate *priv; +}; + +struct _EphyZoomActionClass +{ + GtkActionClass parent_class; + + void (* zoom_to_level) (EphyZoomAction *action, float level); +}; + +GType ephy_zoom_action_get_type (void) G_GNUC_CONST; + +void ephy_zoom_action_set_zoom_level (EphyZoomAction *action, + float zoom); +float ephy_zoom_action_get_zoom_level (EphyZoomAction *action); + +void ephy_zoom_action_set_min_zoom_level (EphyZoomAction *action, + float zoom); +void ephy_zoom_action_set_max_zoom_level (EphyZoomAction *action, + float zoom); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/zoom-control/ephy-zoom-control.c b/cut-n-paste/zoom-control/ephy-zoom-control.c new file mode 100644 index 00000000..6c86b4a4 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-control.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2003, 2004 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom-control.h" +#include "ephy-zoom.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#define EPHY_ZOOM_CONTROL_GET_PRIVATE(object)\ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlPrivate)) + +struct _EphyZoomControlPrivate +{ + GtkComboBox *combo; + float zoom; + float min_zoom; + float max_zoom; + guint handler_id; +}; + +enum +{ + COL_TEXT, + COL_IS_SEP +}; + +enum +{ + PROP_0, + PROP_ZOOM, + PROP_MIN_ZOOM, + PROP_MAX_ZOOM +}; + +enum +{ + ZOOM_TO_LEVEL_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (EphyZoomControl, ephy_zoom_control, GTK_TYPE_TOOL_ITEM) + +static void +combo_changed_cb (GtkComboBox *combo, EphyZoomControl *control) +{ + gint index; + float zoom; + + index = gtk_combo_box_get_active (combo); + zoom = zoom_levels[index].level; + + if (zoom != control->priv->zoom) + { + g_signal_emit (control, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); + } +} + +static void +sync_zoom_cb (EphyZoomControl *control, GParamSpec *pspec, gpointer data) +{ + EphyZoomControlPrivate *p = control->priv; + guint index; + + index = ephy_zoom_get_zoom_level_index (p->zoom); + + g_signal_handler_block (p->combo, p->handler_id); + gtk_combo_box_set_active (p->combo, index); + g_signal_handler_unblock (p->combo, p->handler_id); +} + +static void +sync_zoom_max_min_cb (EphyZoomControl *control, GParamSpec *pspec, gpointer data) +{ + EphyZoomControlPrivate *p = control->priv; + GtkListStore *model = (GtkListStore *)gtk_combo_box_get_model (p->combo); + GtkTreeIter iter; + gint i; + + g_signal_handler_block (p->combo, p->handler_id); + gtk_list_store_clear (model); + + for (i = 0; i < n_zoom_levels; i++) + { + if (zoom_levels[i].level > 0) { + if (zoom_levels[i].level < p->min_zoom) + continue; + + if (zoom_levels[i].level > p->max_zoom) + break; + } + + gtk_list_store_append (model, &iter); + + if (zoom_levels[i].name != NULL) { + gtk_list_store_set (model, &iter, + COL_TEXT, _(zoom_levels[i].name), + -1); + } else { + gtk_list_store_set (model, &iter, + COL_IS_SEP, zoom_levels[i].name == NULL, + -1); + } + } + + gtk_combo_box_set_active (p->combo, ephy_zoom_get_zoom_level_index (p->zoom)); + g_signal_handler_unblock (p->combo, p->handler_id); +} + +static gboolean +row_is_separator (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean is_sep; + gtk_tree_model_get (model, iter, COL_IS_SEP, &is_sep, -1); + return is_sep; +} + +static void +ephy_zoom_control_finalize (GObject *o) +{ + EphyZoomControl *control = EPHY_ZOOM_CONTROL (o); + + g_object_unref (control->priv->combo); + + G_OBJECT_CLASS (ephy_zoom_control_parent_class)->finalize (o); +} + +static void +ephy_zoom_control_init (EphyZoomControl *control) +{ + EphyZoomControlPrivate *p; + GtkWidget *vbox; + GtkCellRenderer *renderer; + GtkListStore *store; + GtkTreeIter iter; + guint i; + + p = EPHY_ZOOM_CONTROL_GET_PRIVATE (control); + control->priv = p; + + p->zoom = 1.0; + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + for (i = 0; i < n_zoom_levels; i++) + { + gtk_list_store_append (store, &iter); + + if (zoom_levels[i].name != NULL) { + gtk_list_store_set (store, &iter, + COL_TEXT, _(zoom_levels[i].name), + -1); + } else { + gtk_list_store_set (store, &iter, + COL_IS_SEP, zoom_levels[i].name == NULL, + -1); + } + } + + p->combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + g_object_unref (store); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (p->combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (p->combo), renderer, + "text", COL_TEXT, NULL); + gtk_combo_box_set_row_separator_func (p->combo, + (GtkTreeViewRowSeparatorFunc) row_is_separator, + NULL, NULL); + + gtk_combo_box_set_focus_on_click (p->combo, FALSE); + g_object_ref_sink (G_OBJECT (p->combo)); + gtk_widget_show (GTK_WIDGET (p->combo)); + + i = ephy_zoom_get_zoom_level_index (p->zoom); + gtk_combo_box_set_active (p->combo, i); + + vbox = gtk_vbox_new (TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (p->combo), TRUE, FALSE, 0); + gtk_widget_show (vbox); + + gtk_container_add (GTK_CONTAINER (control), vbox); + + p->handler_id = g_signal_connect (p->combo, "changed", + G_CALLBACK (combo_changed_cb), control); + + g_signal_connect_object (control, "notify::zoom", + G_CALLBACK (sync_zoom_cb), NULL, 0); + g_signal_connect_object (control, "notify::min-zoom", + G_CALLBACK (sync_zoom_max_min_cb), NULL, 0); + g_signal_connect_object (control, "notify::max-zoom", + G_CALLBACK (sync_zoom_max_min_cb), NULL, 0); +} + +static void +ephy_zoom_control_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyZoomControl *control; + EphyZoomControlPrivate *p; + + control = EPHY_ZOOM_CONTROL (object); + p = control->priv; + + switch (prop_id) + { + case PROP_ZOOM: + p->zoom = g_value_get_float (value); + break; + case PROP_MIN_ZOOM: + p->min_zoom = g_value_get_float (value); + break; + case PROP_MAX_ZOOM: + p->max_zoom = g_value_get_float (value); + break; + } +} + +static void +ephy_zoom_control_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyZoomControl *control; + EphyZoomControlPrivate *p; + + control = EPHY_ZOOM_CONTROL (object); + p = control->priv; + + switch (prop_id) + { + case PROP_ZOOM: + g_value_set_float (value, p->zoom); + break; + case PROP_MIN_ZOOM: + g_value_set_float (value, p->min_zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, p->max_zoom); + break; + } +} + +static void +ephy_zoom_control_class_init (EphyZoomControlClass *klass) +{ + GObjectClass *object_class; + GtkToolItemClass *tool_item_class; + + object_class = (GObjectClass *)klass; + tool_item_class = (GtkToolItemClass *)klass; + + object_class->set_property = ephy_zoom_control_set_property; + object_class->get_property = ephy_zoom_control_get_property; + object_class->finalize = ephy_zoom_control_finalize; + + g_object_class_install_property (object_class, + PROP_ZOOM, + g_param_spec_float ("zoom", + "Zoom", + "Zoom level to display in the item.", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + 1.0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MIN_ZOOM, + g_param_spec_float ("min-zoom", + "MinZoom", + "The minimum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MINIMAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", + "MaxZoom", + "The maximum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MAXIMAL, + G_PARAM_READWRITE)); + + signals[ZOOM_TO_LEVEL_SIGNAL] = + g_signal_new ("zoom_to_level", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EphyZoomControlClass, + zoom_to_level), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, + 1, + G_TYPE_FLOAT); + + g_type_class_add_private (object_class, sizeof (EphyZoomControlPrivate)); +} + +void +ephy_zoom_control_set_zoom_level (EphyZoomControl *control, float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_CONTROL (control)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + control->priv->zoom = zoom; + g_object_notify (G_OBJECT (control), "zoom"); +} + +float +ephy_zoom_control_get_zoom_level (EphyZoomControl *control) +{ + g_return_val_if_fail (EPHY_IS_ZOOM_CONTROL (control), 1.0); + + return control->priv->zoom; +} diff --git a/cut-n-paste/zoom-control/ephy-zoom-control.h b/cut-n-paste/zoom-control/ephy-zoom-control.h new file mode 100644 index 00000000..8e74e7b1 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-control.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_CONTROL_H +#define EPHY_ZOOM_CONTROL_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_ZOOM_CONTROL (ephy_zoom_control_get_type()) +#define EPHY_ZOOM_CONTROL(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControl)) +#define EPHY_ZOOM_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlClass)) +#define EPHY_IS_ZOOM_CONTROL(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_ZOOM_CONTROL)) +#define EPHY_IS_ZOOM_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_ZOOM_CONTROL)) +#define EPHY_ZOOM_CONTROL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlClass)) + +typedef struct _EphyZoomControl EphyZoomControl; +typedef struct _EphyZoomControlClass EphyZoomControlClass; +typedef struct _EphyZoomControlPrivate EphyZoomControlPrivate; + +struct _EphyZoomControlClass +{ + GtkToolItemClass parent_class; + + /* signals */ + void (*zoom_to_level) (EphyZoomControl *control, float level); +}; + +struct _EphyZoomControl +{ + GtkToolItem parent_object; + + /*< private >*/ + EphyZoomControlPrivate *priv; +}; + +GType ephy_zoom_control_get_type (void); + +void ephy_zoom_control_set_zoom_level (EphyZoomControl *control, float zoom); + +float ephy_zoom_control_get_zoom_level (EphyZoomControl *control); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/zoom-control/ephy-zoom.c b/cut-n-paste/zoom-control/ephy-zoom.c new file mode 100644 index 00000000..83bb8291 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom.h" + +#include <math.h> + +guint +ephy_zoom_get_zoom_level_index (float level) +{ + guint i; + float previous, current, mean; + + /* Handle our options at the beginning of the list. */ + if (level == EPHY_ZOOM_BEST_FIT) { + return 0; + } else if (level == EPHY_ZOOM_FIT_WIDTH) { + return 1; + } + + previous = zoom_levels[3].level; + + for (i = 4; i < n_zoom_levels; i++) + { + current = zoom_levels[i].level; + mean = sqrt (previous * current); + + if (level <= mean) return i - 1; + + previous = current; + } + + return n_zoom_levels - 1; +} + + +float +ephy_zoom_get_changed_zoom_level (float level, gint steps) +{ + guint index; + + index = ephy_zoom_get_zoom_level_index (level); + return zoom_levels[CLAMP(index + steps, 3, n_zoom_levels - 1)].level; +} + +float ephy_zoom_get_nearest_zoom_level (float level) +{ + return ephy_zoom_get_changed_zoom_level (level, 0); +} diff --git a/cut-n-paste/zoom-control/ephy-zoom.h b/cut-n-paste/zoom-control/ephy-zoom.h new file mode 100644 index 00000000..bf01f0d7 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_H +#define EPHY_ZOOM_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> + +G_BEGIN_DECLS + +#define EPHY_ZOOM_BEST_FIT (-3.0) +#define EPHY_ZOOM_FIT_WIDTH (-4.0) +#define EPHY_ZOOM_SEPARATOR (-5.0) + +static const +struct +{ + gchar *name; + float level; +} + +zoom_levels[] = +{ + { N_("Best Fit"), EPHY_ZOOM_BEST_FIT }, + { N_("Fit Page Width"), EPHY_ZOOM_FIT_WIDTH }, + { NULL, EPHY_ZOOM_SEPARATOR }, + { N_("50%"), 0.5 }, + { N_("70%"), 0.7071067811 }, + { N_("85%"), 0.8408964152 }, + { N_("100%"), 1.0 }, + { N_("125%"), 1.1892071149 }, + { N_("150%"), 1.4142135623 }, + { N_("175%"), 1.6817928304 }, + { N_("200%"), 2.0 }, + { N_("300%"), 2.8284271247 }, + { N_("400%"), 4.0 }, + { N_("800%"), 8.0 }, + { N_("1600%"), 16.0 }, + { N_("3200%"), 32.0 }, + { N_("6400%"), 64.0 } +}; +static const guint n_zoom_levels = G_N_ELEMENTS (zoom_levels); + +#define ZOOM_MINIMAL (EPHY_ZOOM_SEPARATOR) +#define ZOOM_MAXIMAL (zoom_levels[n_zoom_levels - 1].level) +#define ZOOM_IN (-1.0) +#define ZOOM_OUT (-2.0) + +guint ephy_zoom_get_zoom_level_index (float level); + +float ephy_zoom_get_changed_zoom_level (float level, gint steps); + +float ephy_zoom_get_nearest_zoom_level (float level); + +G_END_DECLS + +#endif |