summaryrefslogtreecommitdiff
path: root/cut-n-paste
diff options
context:
space:
mode:
Diffstat (limited to 'cut-n-paste')
-rw-r--r--cut-n-paste/Makefile.am3
-rw-r--r--cut-n-paste/gimpcellrenderertoggle/Makefile.am38
-rw-r--r--cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c492
-rw-r--r--cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h77
-rw-r--r--cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list26
-rw-r--r--cut-n-paste/smclient/Makefile.am42
-rw-r--r--cut-n-paste/smclient/eggdesktopfile.c1477
-rw-r--r--cut-n-paste/smclient/eggdesktopfile.h159
-rw-r--r--cut-n-paste/smclient/eggsmclient-osx.c235
-rw-r--r--cut-n-paste/smclient/eggsmclient-private.h53
-rw-r--r--cut-n-paste/smclient/eggsmclient-win32.c353
-rw-r--r--cut-n-paste/smclient/eggsmclient-xsmp.c1370
-rw-r--r--cut-n-paste/smclient/eggsmclient.c589
-rw-r--r--cut-n-paste/smclient/eggsmclient.h117
-rw-r--r--cut-n-paste/synctex/Makefile.am14
-rw-r--r--cut-n-paste/synctex/synctex_parser.c4171
-rw-r--r--cut-n-paste/synctex/synctex_parser.h345
-rw-r--r--cut-n-paste/synctex/synctex_parser_utils.c462
-rw-r--r--cut-n-paste/synctex/synctex_parser_utils.h123
-rw-r--r--cut-n-paste/toolbar-editor/Makefile.am108
-rw-r--r--cut-n-paste/toolbar-editor/egg-editable-toolbar.c1828
-rw-r--r--cut-n-paste/toolbar-editor/egg-editable-toolbar.h91
-rw-r--r--cut-n-paste/toolbar-editor/egg-toolbar-editor.c672
-rw-r--r--cut-n-paste/toolbar-editor/egg-toolbar-editor.h63
-rw-r--r--cut-n-paste/toolbar-editor/egg-toolbars-model.c987
-rw-r--r--cut-n-paste/toolbar-editor/egg-toolbars-model.h190
-rw-r--r--cut-n-paste/toolbar-editor/eggmarshalers.list1
-rw-r--r--cut-n-paste/totem-screensaver/Makefile.am16
-rw-r--r--cut-n-paste/totem-screensaver/README3
-rw-r--r--cut-n-paste/totem-screensaver/totem-scrsaver.c554
-rw-r--r--cut-n-paste/totem-screensaver/totem-scrsaver.h57
-rw-r--r--cut-n-paste/zoom-control/Makefile.am16
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom-action.c336
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom-action.h68
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom-control.c345
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom-control.h63
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom.c68
-rw-r--r--cut-n-paste/zoom-control/ephy-zoom.h81
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,&quoted,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