aboutsummaryrefslogtreecommitdiff
path: root/liblogdiag
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2011-01-10 16:49:13 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2011-01-10 17:07:02 +0100
commit616c49a5053830a5e0a31c71fd6114926e43235f (patch)
tree8a21f60862a86d5fb2faf5ed7fd70aa7a2ce69d5 /liblogdiag
parent63b36a2b5b8e04f5d96fa9aa8d212a01c73aad49 (diff)
downloadlogdiag-616c49a5053830a5e0a31c71fd6114926e43235f.tar.gz
logdiag-616c49a5053830a5e0a31c71fd6114926e43235f.tar.xz
logdiag-616c49a5053830a5e0a31c71fd6114926e43235f.zip
Make a separate library.
This is required for gtkdoc-scangobj. So far it's much like it's been before, the main differences are that source files are in two directories from now on and the build process has two stages.
Diffstat (limited to 'liblogdiag')
-rw-r--r--liblogdiag/ld-canvas.c1417
-rw-r--r--liblogdiag/ld-canvas.h91
-rw-r--r--liblogdiag/ld-diagram-object.c186
-rw-r--r--liblogdiag/ld-diagram-object.h65
-rw-r--r--liblogdiag/ld-diagram-symbol.c171
-rw-r--r--liblogdiag/ld-diagram-symbol.h64
-rw-r--r--liblogdiag/ld-diagram.c550
-rw-r--r--liblogdiag/ld-diagram.h98
-rw-r--r--liblogdiag/ld-library.c524
-rw-r--r--liblogdiag/ld-library.h73
-rw-r--r--liblogdiag/ld-lua-private.h26
-rw-r--r--liblogdiag/ld-lua-symbol-private.h40
-rw-r--r--liblogdiag/ld-lua-symbol.c138
-rw-r--r--liblogdiag/ld-lua-symbol.h60
-rw-r--r--liblogdiag/ld-lua.c808
-rw-r--r--liblogdiag/ld-lua.h70
-rw-r--r--liblogdiag/ld-marshal.c88
-rw-r--r--liblogdiag/ld-marshal.h20
-rw-r--r--liblogdiag/ld-marshal.list1
-rw-r--r--liblogdiag/ld-symbol-category.c339
-rw-r--r--liblogdiag/ld-symbol-category.h79
-rw-r--r--liblogdiag/ld-symbol.c232
-rw-r--r--liblogdiag/ld-symbol.h74
-rw-r--r--liblogdiag/ld-types.c221
-rw-r--r--liblogdiag/ld-types.h101
-rw-r--r--liblogdiag/liblogdiag.h34
26 files changed, 5570 insertions, 0 deletions
diff --git a/liblogdiag/ld-canvas.c b/liblogdiag/ld-canvas.c
new file mode 100644
index 0000000..9523d9d
--- /dev/null
+++ b/liblogdiag/ld-canvas.c
@@ -0,0 +1,1417 @@
+/*
+ * ld-canvas.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include <math.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-canvas
+ * @short_description: A canvas.
+ * @see_also: #LdDiagram
+ *
+ * #LdCanvas displays and enables the user to manipulate with an #LdDiagram.
+ */
+
+/* Milimetres per inch. */
+#define MM_PER_INCH 25.4
+/* The default screen resolution in DPI units. */
+#define DEFAULT_SCREEN_RESOLUTION 96
+
+/* The maximal, minimal and default values of zoom. */
+#define ZOOM_MIN 0.01
+#define ZOOM_MAX 100
+#define ZOOM_DEFAULT 1
+/* Multiplication factor for zooming with mouse wheel. */
+#define ZOOM_WHEEL_STEP 1.4
+
+/* When drawing is requested, extend all sides of
+ * the rectangle to be drawn by this number of pixels.
+ */
+#define QUEUE_DRAW_EXTEND 3
+/* Cursor tolerance for object borders. */
+#define OBJECT_BORDER_TOLERANCE 3
+/* Tolerance on all sides of symbols for strokes. */
+#define SYMBOL_CLIP_TOLERANCE 5
+
+/* Size of a highlighted terminal. */
+#define TERMINAL_RADIUS 5
+/* Tolerance around terminal points. */
+#define TERMINAL_HOVER_TOLERANCE 8
+
+/*
+ * OperationEnd:
+ *
+ * Called upon ending an operation.
+ */
+typedef void (*OperationEnd) (LdCanvas *self);
+
+enum
+{
+ OPER_0,
+ OPER_ADD_OBJECT
+};
+
+typedef struct _AddObjectData AddObjectData;
+
+struct _AddObjectData
+{
+ LdDiagramObject *object;
+ gboolean visible;
+};
+
+enum
+{
+ COLOR_BASE,
+ COLOR_GRID,
+ COLOR_OBJECT,
+ COLOR_SELECTION,
+ COLOR_TERMINAL,
+ COLOR_COUNT
+};
+
+typedef struct _LdCanvasColor LdCanvasColor;
+
+struct _LdCanvasColor
+{
+ gdouble r;
+ gdouble g;
+ gdouble b;
+ gdouble a;
+};
+
+/*
+ * LdCanvasPrivate:
+ * @diagram: A diagram object assigned to this canvas as a model.
+ * @library: A library object assigned to this canvas as a model.
+ * @adjustment_h: An adjustment object for the horizontal axis, if any.
+ * @adjustment_v: An adjustment object for the vertical axis, if any.
+ * @x: The X coordinate of the center of view.
+ * @y: The Y coordinate of the center of view.
+ * @zoom: The current zoom of the canvas.
+ * @operation: The current operation.
+ * @operation_data: Data related to the current operation.
+ * @operation_end: A callback to end the operation.
+ * @palette: Colors used by the widget.
+ */
+struct _LdCanvasPrivate
+{
+ LdDiagram *diagram;
+ LdLibrary *library;
+
+ GtkAdjustment *adjustment_h;
+ GtkAdjustment *adjustment_v;
+
+ gdouble x;
+ gdouble y;
+ gdouble zoom;
+
+ LdPoint terminal;
+ gboolean terminal_highlighted;
+
+ gint operation;
+ union
+ {
+ AddObjectData add_object;
+ }
+ operation_data;
+ OperationEnd operation_end;
+
+ LdCanvasColor palette[COLOR_COUNT];
+};
+
+#define OPER_DATA(self, member) ((self)->priv->operation_data.member)
+#define COLOR_GET(self, name) (&(self)->priv->palette[name])
+
+/*
+ * DrawData:
+ * @self: Our #LdCanvas.
+ * @cr: A cairo context to draw on.
+ * @exposed_rect: The area that is to be redrawn.
+ * @scale: Computed size of one diagram unit in pixels.
+ */
+typedef struct _DrawData DrawData;
+
+struct _DrawData
+{
+ LdCanvas *self;
+ cairo_t *cr;
+ LdRectangle exposed_rect;
+ gdouble scale;
+};
+
+enum
+{
+ PROP_0,
+ PROP_DIAGRAM,
+ PROP_LIBRARY,
+ PROP_ZOOM
+};
+
+static void ld_canvas_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_canvas_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+static void ld_canvas_finalize (GObject *gobject);
+
+static void ld_canvas_real_set_scroll_adjustments
+ (LdCanvas *self, GtkAdjustment *horizontal, GtkAdjustment *vertical);
+static void on_adjustment_value_changed
+ (GtkAdjustment *adjustment, LdCanvas *self);
+static void on_size_allocate (GtkWidget *widget, GtkAllocation *allocation,
+ gpointer user_data);
+static void update_adjustments (LdCanvas *self);
+
+static void diagram_connect_signals (LdCanvas *self);
+static void diagram_disconnect_signals (LdCanvas *self);
+
+static gdouble ld_canvas_get_base_unit_in_px (GtkWidget *self);
+static gdouble ld_canvas_get_scale_in_px (LdCanvas *self);
+
+static void simulate_motion (LdCanvas *self);
+static gboolean on_motion_notify (GtkWidget *widget, GdkEventMotion *event,
+ gpointer user_data);
+static gboolean on_leave_notify (GtkWidget *widget, GdkEventCrossing *event,
+ gpointer user_data);
+static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event,
+ gpointer user_data);
+static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event,
+ gpointer user_data);
+static gboolean on_scroll (GtkWidget *widget, GdkEventScroll *event,
+ gpointer user_data);
+
+static void ld_canvas_color_set (LdCanvasColor *color,
+ gdouble r, gdouble g, gdouble b, gdouble a);
+static void ld_canvas_color_apply (LdCanvasColor *color, cairo_t *cr);
+
+static void move_object_to_coords (LdCanvas *self, LdDiagramObject *object,
+ gdouble x, gdouble y);
+static LdDiagramObject *get_object_at_coords (LdCanvas *self,
+ gdouble x, gdouble y);
+static gboolean is_object_selected (LdCanvas *self, LdDiagramObject *object);
+static LdSymbol *resolve_diagram_symbol (LdCanvas *self,
+ LdDiagramSymbol *diagram_symbol);
+static gboolean get_symbol_area (LdCanvas *self, LdDiagramSymbol *symbol,
+ LdRectangle *rect);
+static gboolean get_symbol_clip_area (LdCanvas *self, LdDiagramSymbol *symbol,
+ LdRectangle *rect);
+static gboolean get_object_area (LdCanvas *self, LdDiagramObject *object,
+ LdRectangle *rect);
+static gboolean object_hit_test (LdCanvas *self, LdDiagramObject *object,
+ gdouble x, gdouble y);
+static void check_terminals (LdCanvas *self, gdouble x, gdouble y);
+static void hide_terminals (LdCanvas *self);
+static void queue_draw (LdCanvas *self, LdRectangle *rect);
+static void queue_object_draw (LdCanvas *self, LdDiagramObject *object);
+static void queue_terminal_draw (LdCanvas *self, LdPoint *terminal);
+
+static void ld_canvas_real_cancel_operation (LdCanvas *self);
+static void ld_canvas_add_object_end (LdCanvas *self);
+
+static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event,
+ gpointer user_data);
+static void draw_grid (GtkWidget *widget, DrawData *data);
+static void draw_diagram (GtkWidget *widget, DrawData *data);
+static void draw_terminal (GtkWidget *widget, DrawData *data);
+static void draw_object (LdDiagramObject *diagram_object, DrawData *data);
+static void draw_symbol (LdDiagramSymbol *diagram_symbol, DrawData *data);
+
+
+G_DEFINE_TYPE (LdCanvas, ld_canvas, GTK_TYPE_DRAWING_AREA);
+
+static void
+ld_canvas_class_init (LdCanvasClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkBindingSet *binding_set;
+ GParamSpec *pspec;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_canvas_get_property;
+ object_class->set_property = ld_canvas_set_property;
+ object_class->finalize = ld_canvas_finalize;
+
+ klass->set_scroll_adjustments = ld_canvas_real_set_scroll_adjustments;
+ klass->cancel_operation = ld_canvas_real_cancel_operation;
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
+ "cancel-operation", 0);
+
+/**
+ * LdCanvas:diagram:
+ *
+ * The underlying #LdDiagram object of this canvas.
+ */
+ pspec = g_param_spec_object ("diagram", "Diagram",
+ "The underlying diagram object of this canvas.",
+ LD_TYPE_DIAGRAM, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DIAGRAM, pspec);
+
+/**
+ * LdCanvas:library:
+ *
+ * The #LdLibrary that this canvas retrieves symbols from.
+ */
+ pspec = g_param_spec_object ("library", "Library",
+ "The library that this canvas retrieves symbols from.",
+ LD_TYPE_LIBRARY, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_LIBRARY, pspec);
+
+/**
+ * LdCanvas:zoom:
+ *
+ * The zoom of this canvas.
+ */
+ pspec = g_param_spec_double ("zoom", "Zoom",
+ "The zoom of this canvas.",
+ ZOOM_MIN, ZOOM_MAX, ZOOM_DEFAULT, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_ZOOM, pspec);
+
+/**
+ * LdCanvas::set-scroll-adjustments:
+ * @horizontal: The horizontal #GtkAdjustment.
+ * @vertical: The vertical #GtkAdjustment.
+ *
+ * Set scroll adjustments for the canvas.
+ */
+ widget_class->set_scroll_adjustments_signal = g_signal_new
+ ("set-scroll-adjustments", G_TYPE_FROM_CLASS (widget_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (LdCanvasClass, set_scroll_adjustments),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
+
+/**
+ * LdCanvas::cancel-operation:
+ *
+ * Cancel any current operation.
+ */
+ klass->cancel_operation_signal = g_signal_new
+ ("cancel-operation", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (LdCanvasClass, cancel_operation), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LdCanvasPrivate));
+}
+
+static void
+ld_canvas_init (LdCanvas *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_CANVAS, LdCanvasPrivate);
+
+ self->priv->x = 0;
+ self->priv->y = 0;
+ self->priv->zoom = ZOOM_DEFAULT;
+
+ ld_canvas_color_set (COLOR_GET (self, COLOR_BASE), 1, 1, 1, 1);
+ ld_canvas_color_set (COLOR_GET (self, COLOR_GRID), 0.5, 0.5, 0.5, 1);
+ ld_canvas_color_set (COLOR_GET (self, COLOR_OBJECT), 0, 0, 0, 1);
+ ld_canvas_color_set (COLOR_GET (self, COLOR_SELECTION), 0, 0, 1, 1);
+ ld_canvas_color_set (COLOR_GET (self, COLOR_TERMINAL), 1, 0.5, 0.5, 1);
+
+ g_signal_connect (self, "size-allocate",
+ G_CALLBACK (on_size_allocate), NULL);
+ g_signal_connect (self, "expose-event",
+ G_CALLBACK (on_expose_event), NULL);
+
+ g_signal_connect (self, "motion-notify-event",
+ G_CALLBACK (on_motion_notify), NULL);
+ g_signal_connect (self, "leave-notify-event",
+ G_CALLBACK (on_leave_notify), NULL);
+ g_signal_connect (self, "button-press-event",
+ G_CALLBACK (on_button_press), NULL);
+ g_signal_connect (self, "button-release-event",
+ G_CALLBACK (on_button_release), NULL);
+ g_signal_connect (self, "scroll-event",
+ G_CALLBACK (on_scroll), NULL);
+
+ g_object_set (self, "can-focus", TRUE, NULL);
+
+ gtk_widget_add_events (GTK_WIDGET (self),
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_LEAVE_NOTIFY_MASK);
+}
+
+static void
+ld_canvas_finalize (GObject *gobject)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (gobject);
+
+ ld_canvas_real_set_scroll_adjustments (self, NULL, NULL);
+
+ if (self->priv->diagram)
+ {
+ diagram_disconnect_signals (self);
+ g_object_unref (self->priv->diagram);
+ }
+ if (self->priv->library)
+ g_object_unref (self->priv->library);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_canvas_parent_class)->finalize (gobject);
+}
+
+static void
+ld_canvas_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (object);
+ switch (property_id)
+ {
+ case PROP_DIAGRAM:
+ g_value_set_object (value, ld_canvas_get_diagram (self));
+ break;
+ case PROP_LIBRARY:
+ g_value_set_object (value, ld_canvas_get_library (self));
+ break;
+ case PROP_ZOOM:
+ g_value_set_double (value, ld_canvas_get_zoom (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_canvas_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (object);
+ switch (property_id)
+ {
+ case PROP_DIAGRAM:
+ ld_canvas_set_diagram (self, LD_DIAGRAM (g_value_get_object (value)));
+ break;
+ case PROP_LIBRARY:
+ ld_canvas_set_library (self, LD_LIBRARY (g_value_get_object (value)));
+ break;
+ case PROP_ZOOM:
+ ld_canvas_set_zoom (self, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_canvas_real_set_scroll_adjustments (LdCanvas *self,
+ GtkAdjustment *horizontal, GtkAdjustment *vertical)
+{
+ /* TODO: Infinite canvas. */
+ GtkWidget *widget;
+ gdouble scale;
+
+ widget = GTK_WIDGET (self);
+ scale = ld_canvas_get_scale_in_px (self);
+
+ if (horizontal != self->priv->adjustment_h)
+ {
+ if (self->priv->adjustment_h)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->adjustment_h,
+ on_adjustment_value_changed, self);
+ g_object_unref (self->priv->adjustment_h);
+
+ self->priv->adjustment_h = NULL;
+ }
+ if (horizontal)
+ {
+ g_object_ref (horizontal);
+ g_signal_connect (horizontal, "value-changed",
+ G_CALLBACK (on_adjustment_value_changed), self);
+
+ horizontal->upper = 100;
+ horizontal->lower = -100;
+ horizontal->step_increment = 0.5;
+ horizontal->page_increment = 5;
+ horizontal->page_size = widget->allocation.width / scale;
+ horizontal->value = -horizontal->page_size / 2;
+
+ self->priv->adjustment_h = horizontal;
+ }
+ }
+
+ if (vertical != self->priv->adjustment_v)
+ {
+ if (self->priv->adjustment_v)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->adjustment_v,
+ on_adjustment_value_changed, self);
+ g_object_unref (self->priv->adjustment_v);
+
+ self->priv->adjustment_v = NULL;
+ }
+ if (vertical)
+ {
+ g_object_ref (vertical);
+ g_signal_connect (vertical, "value-changed",
+ G_CALLBACK (on_adjustment_value_changed), self);
+
+ vertical->upper = 100;
+ vertical->lower = -100;
+ vertical->step_increment = 0.5;
+ vertical->page_increment = 5;
+ vertical->page_size = widget->allocation.height / scale;
+ vertical->value = -vertical->page_size / 2;
+
+ self->priv->adjustment_v = vertical;
+ }
+ }
+}
+
+static void
+on_adjustment_value_changed (GtkAdjustment *adjustment, LdCanvas *self)
+{
+ GtkWidget *widget;
+ gdouble scale;
+
+ widget = GTK_WIDGET (self);
+ scale = ld_canvas_get_scale_in_px (self);
+
+ if (adjustment == self->priv->adjustment_h)
+ {
+ self->priv->x = adjustment->value
+ + widget->allocation.width / scale / 2;
+ gtk_widget_queue_draw (widget);
+ }
+ else if (adjustment == self->priv->adjustment_v)
+ {
+ self->priv->y = adjustment->value
+ + widget->allocation.height / scale / 2;
+ gtk_widget_queue_draw (widget);
+ }
+}
+
+static void
+on_size_allocate (GtkWidget *widget, GtkAllocation *allocation,
+ gpointer user_data)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (widget);
+
+ /* FIXME: If the new allocation is bigger, we may see more than
+ * what we're supposed to be able to see -> adjust X and Y.
+ *
+ * If the visible area is so large that we simply must see more,
+ * let's disable the scrollbars in question.
+ */
+ update_adjustments (self);
+}
+
+static void
+update_adjustments (LdCanvas *self)
+{
+ gdouble scale;
+
+ scale = ld_canvas_get_scale_in_px (self);
+
+ if (self->priv->adjustment_h)
+ {
+ self->priv->adjustment_h->page_size
+ = GTK_WIDGET (self)->allocation.width / scale;
+ self->priv->adjustment_h->value
+ = self->priv->x - self->priv->adjustment_h->page_size / 2;
+ gtk_adjustment_changed (self->priv->adjustment_h);
+ }
+ if (self->priv->adjustment_v)
+ {
+ self->priv->adjustment_v->page_size
+ = GTK_WIDGET (self)->allocation.height / scale;
+ self->priv->adjustment_v->value
+ = self->priv->y - self->priv->adjustment_v->page_size / 2;
+ gtk_adjustment_changed (self->priv->adjustment_v);
+ }
+}
+
+
+/* ===== Generic interface etc. ============================================ */
+
+/**
+ * ld_canvas_new:
+ *
+ * Create an instance.
+ */
+LdCanvas *
+ld_canvas_new (void)
+{
+ return g_object_new (LD_TYPE_CANVAS, NULL);
+}
+
+/**
+ * ld_canvas_set_diagram:
+ * @self: An #LdCanvas object.
+ * @diagram: The #LdDiagram to be assigned to the canvas.
+ *
+ * Assign an #LdDiagram object to the canvas.
+ */
+void
+ld_canvas_set_diagram (LdCanvas *self, LdDiagram *diagram)
+{
+ g_return_if_fail (LD_IS_CANVAS (self));
+ g_return_if_fail (LD_IS_DIAGRAM (diagram));
+
+ if (self->priv->diagram)
+ {
+ diagram_disconnect_signals (self);
+ g_object_unref (self->priv->diagram);
+ }
+
+ self->priv->diagram = diagram;
+ diagram_connect_signals (self);
+ g_object_ref (diagram);
+
+ g_object_notify (G_OBJECT (self), "diagram");
+}
+
+/**
+ * ld_canvas_get_diagram:
+ * @self: An #LdCanvas object.
+ *
+ * Get the #LdDiagram object assigned to this canvas.
+ * The reference count on the diagram is not incremented.
+ */
+LdDiagram *
+ld_canvas_get_diagram (LdCanvas *self)
+{
+ g_return_val_if_fail (LD_IS_CANVAS (self), NULL);
+ return self->priv->diagram;
+}
+
+static void
+diagram_connect_signals (LdCanvas *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self->priv->diagram));
+
+ g_signal_connect_swapped (self->priv->diagram, "changed",
+ G_CALLBACK (gtk_widget_queue_draw), self);
+ g_signal_connect_swapped (self->priv->diagram, "selection-changed",
+ G_CALLBACK (gtk_widget_queue_draw), self);
+}
+
+static void
+diagram_disconnect_signals (LdCanvas *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self->priv->diagram));
+
+ g_signal_handlers_disconnect_matched (self->priv->diagram,
+ G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL,
+ gtk_widget_queue_draw, self);
+}
+
+/**
+ * ld_canvas_set_library:
+ * @self: An #LdCanvas object.
+ * @library: The #LdLibrary to be assigned to the canvas.
+ *
+ * Assign an #LdLibrary object to the canvas.
+ */
+void
+ld_canvas_set_library (LdCanvas *self, LdLibrary *library)
+{
+ g_return_if_fail (LD_IS_CANVAS (self));
+ g_return_if_fail (LD_IS_LIBRARY (library));
+
+ if (self->priv->library)
+ g_object_unref (self->priv->library);
+
+ self->priv->library = library;
+ g_object_ref (library);
+
+ g_object_notify (G_OBJECT (self), "library");
+}
+
+/**
+ * ld_canvas_get_library:
+ * @self: An #LdCanvas object.
+ *
+ * Get the #LdLibrary object assigned to this canvas.
+ * The reference count on the library is not incremented.
+ */
+LdLibrary *
+ld_canvas_get_library (LdCanvas *self)
+{
+ g_return_val_if_fail (LD_IS_CANVAS (self), NULL);
+ return self->priv->library;
+}
+
+/*
+ * ld_canvas_get_base_unit_in_px:
+ * @self: A #GtkWidget object to retrieve DPI from (indirectly).
+ *
+ * Return value: Length of the base unit in pixels.
+ */
+static gdouble
+ld_canvas_get_base_unit_in_px (GtkWidget *self)
+{
+ gdouble resolution;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (self), 1);
+
+ resolution = gdk_screen_get_resolution (gtk_widget_get_screen (self));
+ if (resolution == -1)
+ resolution = DEFAULT_SCREEN_RESOLUTION;
+
+ /* XXX: It might look better if the unit was rounded to a whole number. */
+ return resolution / MM_PER_INCH * LD_CANVAS_BASE_UNIT_LENGTH;
+}
+
+/*
+ * ld_canvas_get_scale_in_px:
+ * @self: An #LdCanvas object.
+ *
+ * Return value: Displayed length of the base unit in pixels.
+ */
+static gdouble
+ld_canvas_get_scale_in_px (LdCanvas *self)
+{
+ g_return_val_if_fail (LD_IS_CANVAS (self), 1);
+
+ return ld_canvas_get_base_unit_in_px (GTK_WIDGET (self))
+ * self->priv->zoom;
+}
+
+/**
+ * ld_canvas_widget_to_diagram_coords:
+ * @self: An #LdCanvas object.
+ * @wx: The X coordinate to be translated.
+ * @wy: The Y coordinate to be translated.
+ * @dx: (out): The translated X coordinate.
+ * @dy: (out): The translated Y coordinate.
+ *
+ * Translate coordinates located inside the canvas window
+ * into diagram coordinates.
+ */
+void
+ld_canvas_widget_to_diagram_coords (LdCanvas *self,
+ gdouble wx, gdouble wy, gdouble *dx, gdouble *dy)
+{
+ GtkWidget *widget;
+ gdouble scale;
+
+ g_return_if_fail (LD_IS_CANVAS (self));
+ g_return_if_fail (dx != NULL);
+ g_return_if_fail (dy != NULL);
+
+ widget = GTK_WIDGET (self);
+ scale = ld_canvas_get_scale_in_px (self);
+
+ /* We know diagram coordinates of the center of the canvas, so we may
+ * translate the given X and Y coordinates to this center and then scale
+ * them by dividing them by the current scale.
+ */
+ *dx = self->priv->x + (wx - (widget->allocation.width * 0.5)) / scale;
+ *dy = self->priv->y + (wy - (widget->allocation.height * 0.5)) / scale;
+}
+
+/**
+ * ld_canvas_diagram_to_widget_coords:
+ * @self: An #LdCanvas object.
+ * @dx: The X coordinate to be translated.
+ * @dy: The Y coordinate to be translated.
+ * @wx: (out): The translated X coordinate.
+ * @wy: (out): The translated Y coordinate.
+ *
+ * Translate diagram coordinates into canvas coordinates.
+ */
+void
+ld_canvas_diagram_to_widget_coords (LdCanvas *self,
+ gdouble dx, gdouble dy, gdouble *wx, gdouble *wy)
+{
+ GtkWidget *widget;
+ gdouble scale;
+
+ g_return_if_fail (LD_IS_CANVAS (self));
+ g_return_if_fail (wx != NULL);
+ g_return_if_fail (wy != NULL);
+
+ widget = GTK_WIDGET (self);
+ scale = ld_canvas_get_scale_in_px (self);
+
+ /* Just the reversal of ld_canvas_widget_to_diagram_coords(). */
+ *wx = scale * (dx - self->priv->x) + 0.5 * widget->allocation.width;
+ *wy = scale * (dy - self->priv->y) + 0.5 * widget->allocation.height;
+}
+
+/**
+ * ld_canvas_get_zoom:
+ * @self: An #LdCanvas object.
+ *
+ * Return value: Zoom of the canvas.
+ */
+gdouble
+ld_canvas_get_zoom (LdCanvas *self)
+{
+ g_return_val_if_fail (LD_IS_CANVAS (self), -1);
+ return self->priv->zoom;
+}
+
+/**
+ * ld_canvas_set_zoom:
+ * @self: An #LdCanvas object.
+ * @zoom: The zoom.
+ *
+ * Set zoom of the canvas.
+ */
+void
+ld_canvas_set_zoom (LdCanvas *self, gdouble zoom)
+{
+ gdouble clamped_zoom;
+
+ g_return_if_fail (LD_IS_CANVAS (self));
+
+ clamped_zoom = CLAMP (zoom, ZOOM_MIN, ZOOM_MAX);
+ if (self->priv->zoom == clamped_zoom)
+ return;
+
+ self->priv->zoom = clamped_zoom;
+
+ simulate_motion (self);
+ update_adjustments (self);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ g_object_notify (G_OBJECT (self), "zoom");
+}
+
+
+/* ===== Operations ======================================================== */
+
+static void
+ld_canvas_real_cancel_operation (LdCanvas *self)
+{
+ g_return_if_fail (LD_IS_CANVAS (self));
+
+ if (self->priv->operation)
+ {
+ if (self->priv->operation_end)
+ self->priv->operation_end (self);
+ self->priv->operation = OPER_0;
+ self->priv->operation_end = NULL;
+ }
+}
+
+/**
+ * ld_canvas_add_object_begin:
+ * @self: An #LdCanvas object.
+ * @object: (transfer full): The object to be added to the diagram.
+ *
+ * Begin an operation for adding an object into the diagram.
+ */
+void
+ld_canvas_add_object_begin (LdCanvas *self, LdDiagramObject *object)
+{
+ AddObjectData *data;
+
+ g_return_if_fail (LD_IS_CANVAS (self));
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object));
+
+ ld_canvas_real_cancel_operation (self);
+
+ self->priv->operation = OPER_ADD_OBJECT;
+ self->priv->operation_end = ld_canvas_add_object_end;
+
+ data = &OPER_DATA (self, add_object);
+ data->object = object;
+}
+
+static void
+ld_canvas_add_object_end (LdCanvas *self)
+{
+ AddObjectData *data;
+
+ data = &OPER_DATA (self, add_object);
+ if (data->object)
+ {
+ queue_object_draw (self, data->object);
+ g_object_unref (data->object);
+ data->object = NULL;
+ }
+}
+
+
+/* ===== Events, rendering ================================================= */
+
+static void
+ld_canvas_color_set (LdCanvasColor *color,
+ gdouble r, gdouble g, gdouble b, gdouble a)
+{
+ color->r = r;
+ color->g = g;
+ color->b = b;
+ color->a = a;
+}
+
+static void
+ld_canvas_color_apply (LdCanvasColor *color, cairo_t *cr)
+{
+ cairo_set_source_rgba (cr, color->r, color->g, color->b, color->a);
+}
+
+static void
+move_object_to_coords (LdCanvas *self, LdDiagramObject *object,
+ gdouble x, gdouble y)
+{
+ gdouble dx, dy;
+
+ ld_canvas_widget_to_diagram_coords (self, x, y, &dx, &dy);
+ ld_diagram_object_set_x (object, floor (dx + 0.5));
+ ld_diagram_object_set_y (object, floor (dy + 0.5));
+}
+
+static LdDiagramObject *
+get_object_at_coords (LdCanvas *self, gdouble x, gdouble y)
+{
+ GList *objects, *iter;
+
+ /* Iterate from the top object downwards. */
+ objects = (GList *) ld_diagram_get_objects (self->priv->diagram);
+ for (iter = objects; iter; iter = g_list_next (iter))
+ {
+ LdDiagramObject *object;
+
+ object = LD_DIAGRAM_OBJECT (iter->data);
+ if (object_hit_test (self, object, x, y))
+ return object;
+ }
+ return NULL;
+}
+
+static gboolean
+is_object_selected (LdCanvas *self, LdDiagramObject *object)
+{
+ return g_list_find (ld_diagram_get_selection (self->priv->diagram),
+ object) != NULL;
+}
+
+static LdSymbol *
+resolve_diagram_symbol (LdCanvas *self, LdDiagramSymbol *diagram_symbol)
+{
+ if (!self->priv->library)
+ return NULL;
+
+ return ld_library_find_symbol (self->priv->library,
+ ld_diagram_symbol_get_class (diagram_symbol));
+}
+
+static gboolean
+get_symbol_area (LdCanvas *self, LdDiagramSymbol *symbol, LdRectangle *rect)
+{
+ LdDiagramObject *object;
+ gdouble object_x, object_y;
+ LdSymbol *library_symbol;
+ LdRectangle area;
+ gdouble x1, x2;
+ gdouble y1, y2;
+
+ object = LD_DIAGRAM_OBJECT (symbol);
+ object_x = ld_diagram_object_get_x (object);
+ object_y = ld_diagram_object_get_y (object);
+
+ library_symbol = resolve_diagram_symbol (self, symbol);
+ if (library_symbol)
+ ld_symbol_get_area (library_symbol, &area);
+ else
+ return FALSE;
+
+ /* TODO: Rotate the rectangle for other orientations. */
+ ld_canvas_diagram_to_widget_coords (self,
+ object_x + area.x,
+ object_y + area.y,
+ &x1, &y1);
+ ld_canvas_diagram_to_widget_coords (self,
+ object_x + area.x + area.width,
+ object_y + area.y + area.height,
+ &x2, &y2);
+
+ rect->x = x1;
+ rect->y = y1;
+ rect->width = x2 - x1;
+ rect->height = y2 - y1;
+ return TRUE;
+}
+
+static gboolean
+get_symbol_clip_area (LdCanvas *self, LdDiagramSymbol *symbol,
+ LdRectangle *rect)
+{
+ LdRectangle object_rect;
+
+ if (!get_object_area (self, LD_DIAGRAM_OBJECT (symbol), &object_rect))
+ return FALSE;
+
+ *rect = object_rect;
+ ld_rectangle_extend (rect, SYMBOL_CLIP_TOLERANCE);
+ return TRUE;
+}
+
+static gboolean
+get_object_area (LdCanvas *self, LdDiagramObject *object, LdRectangle *rect)
+{
+ if (LD_IS_DIAGRAM_SYMBOL (object))
+ return get_symbol_area (self, LD_DIAGRAM_SYMBOL (object), rect);
+ return FALSE;
+}
+
+static gboolean
+object_hit_test (LdCanvas *self, LdDiagramObject *object, gdouble x, gdouble y)
+{
+ LdRectangle rect;
+
+ if (!get_object_area (self, object, &rect))
+ return FALSE;
+ ld_rectangle_extend (&rect, OBJECT_BORDER_TOLERANCE);
+ return ld_rectangle_contains (&rect, x, y);
+}
+
+static void
+check_terminals (LdCanvas *self, gdouble x, gdouble y)
+{
+ GList *objects, *iter;
+ LdDiagramSymbol *closest_symbol = NULL;
+ gdouble closest_distance = TERMINAL_HOVER_TOLERANCE;
+ LdPoint closest_terminal;
+
+ objects = (GList *) ld_diagram_get_objects (self->priv->diagram);
+ for (iter = objects; iter; iter = g_list_next (iter))
+ {
+ LdDiagramObject *diagram_object;
+ gdouble object_x, object_y;
+ LdDiagramSymbol *diagram_symbol;
+ LdSymbol *symbol;
+ const LdPointArray *terminals;
+ gint i;
+
+ if (!LD_IS_DIAGRAM_SYMBOL (iter->data))
+ continue;
+
+ diagram_symbol = LD_DIAGRAM_SYMBOL (iter->data);
+ symbol = resolve_diagram_symbol (self, diagram_symbol);
+ if (!symbol)
+ continue;
+
+ diagram_object = LD_DIAGRAM_OBJECT (iter->data);
+ object_x = ld_diagram_object_get_x (diagram_object);
+ object_y = ld_diagram_object_get_y (diagram_object);
+
+ terminals = ld_symbol_get_terminals (symbol);
+
+ for (i = 0; i < terminals->num_points; i++)
+ {
+ LdPoint cur_term;
+ gdouble distance;
+
+ cur_term = terminals->points[i];
+ cur_term.x += object_x;
+ cur_term.y += object_y;
+ ld_canvas_diagram_to_widget_coords (self,
+ cur_term.x, cur_term.y, &cur_term.x, &cur_term.y);
+
+ distance = ld_point_distance (&cur_term, x, y);
+ if (distance <= closest_distance)
+ {
+ closest_symbol = diagram_symbol;
+ closest_distance = distance;
+ closest_terminal = cur_term;
+ }
+ }
+ }
+
+ hide_terminals (self);
+
+ if (closest_symbol)
+ {
+ self->priv->terminal_highlighted = TRUE;
+ self->priv->terminal = closest_terminal;
+ queue_terminal_draw (self, &closest_terminal);
+ }
+}
+
+static void
+hide_terminals (LdCanvas *self)
+{
+ if (self->priv->terminal_highlighted)
+ {
+ self->priv->terminal_highlighted = FALSE;
+ queue_terminal_draw (self, &self->priv->terminal);
+ }
+}
+
+static void
+queue_draw (LdCanvas *self, LdRectangle *rect)
+{
+ LdRectangle area;
+
+ area = *rect;
+ ld_rectangle_extend (&area, QUEUE_DRAW_EXTEND);
+ gtk_widget_queue_draw_area (GTK_WIDGET (self),
+ area.x, area.y, area.width, area.height);
+}
+
+static void
+queue_object_draw (LdCanvas *self, LdDiagramObject *object)
+{
+ if (LD_IS_DIAGRAM_SYMBOL (object))
+ {
+ LdRectangle rect;
+
+ if (!get_symbol_clip_area (self, LD_DIAGRAM_SYMBOL (object), &rect))
+ return;
+ queue_draw (self, &rect);
+ }
+}
+
+static void
+queue_terminal_draw (LdCanvas *self, LdPoint *terminal)
+{
+ LdRectangle rect;
+
+ rect.x = terminal->x - TERMINAL_RADIUS;
+ rect.y = terminal->y - TERMINAL_RADIUS;
+ rect.width = 2 * TERMINAL_RADIUS;
+ rect.height = 2 * TERMINAL_RADIUS;
+ queue_draw (self, &rect);
+}
+
+static void
+simulate_motion (LdCanvas *self)
+{
+ GdkEventMotion event;
+ GtkWidget *widget;
+ gint x, y;
+ GdkModifierType state;
+
+ widget = GTK_WIDGET (self);
+
+ if (gdk_window_get_pointer (widget->window, &x, &y, &state)
+ != widget->window)
+ return;
+
+ memset (&event, 0, sizeof (event));
+ event.type = GDK_MOTION_NOTIFY;
+ event.window = widget->window;
+ event.x = x;
+ event.y = y;
+ event.state = state;
+
+ on_motion_notify (widget, &event, NULL);
+}
+
+static gboolean
+on_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (widget);
+ switch (self->priv->operation)
+ {
+ AddObjectData *data;
+
+ case OPER_ADD_OBJECT:
+ data = &OPER_DATA (self, add_object);
+ data->visible = TRUE;
+
+ queue_object_draw (self, data->object);
+ move_object_to_coords (self, data->object, event->x, event->y);
+ queue_object_draw (self, data->object);
+ break;
+ case OPER_0:
+ check_terminals (self, event->x, event->y);
+ break;
+ }
+ return FALSE;
+}
+
+static gboolean
+on_leave_notify (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+ LdCanvas *self;
+
+ self = LD_CANVAS (widget);
+ switch (self->priv->operation)
+ {
+ AddObjectData *data;
+
+ case OPER_ADD_OBJECT:
+ data = &OPER_DATA (self, add_object);
+ data->visible = FALSE;
+
+ queue_object_draw (self, data->object);
+ break;
+ }
+ return FALSE;
+}
+
+static gboolean
+on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+ LdCanvas *self;
+
+ if (!gtk_widget_has_focus (widget))
+ gtk_widget_grab_focus (widget);
+
+ self = LD_CANVAS (widget);
+ switch (self->priv->operation)
+ {
+ AddObjectData *data;
+
+ case OPER_ADD_OBJECT:
+ data = &OPER_DATA (self, add_object);
+
+ queue_object_draw (self, data->object);
+ move_object_to_coords (self, data->object, event->x, event->y);
+
+ if (self->priv->diagram)
+ ld_diagram_insert_object (self->priv->diagram, data->object, 0);
+
+ /* XXX: "cancel" causes confusion. */
+ ld_canvas_real_cancel_operation (self);
+ break;
+ case OPER_0:
+ if (self->priv->diagram)
+ {
+ LdDiagramObject *object;
+
+ if (event->state != GDK_SHIFT_MASK)
+ ld_diagram_unselect_all (self->priv->diagram);
+
+ object = get_object_at_coords (self, event->x, event->y);
+ if (object)
+ ld_diagram_selection_add (self->priv->diagram, object, 0);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static gboolean
+on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+ return FALSE;
+}
+
+static gboolean
+on_scroll (GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
+{
+ gdouble prev_x, prev_y;
+ gdouble new_x, new_y;
+ LdCanvas *self;
+
+ self = LD_CANVAS (widget);
+
+ ld_canvas_widget_to_diagram_coords (self,
+ event->x, event->y, &prev_x, &prev_y);
+
+ switch (event->direction)
+ {
+ case GDK_SCROLL_UP:
+ ld_canvas_set_zoom (self, self->priv->zoom * ZOOM_WHEEL_STEP);
+ break;
+ case GDK_SCROLL_DOWN:
+ ld_canvas_set_zoom (self, self->priv->zoom / ZOOM_WHEEL_STEP);
+ break;
+ default:
+ return FALSE;
+ }
+
+ ld_canvas_widget_to_diagram_coords (self,
+ event->x, event->y, &new_x, &new_y);
+
+ /* Focus on the point under the cursor. */
+ self->priv->x += prev_x - new_x;
+ self->priv->y += prev_y - new_y;
+
+ check_terminals (self, event->x, event->y);
+ return TRUE;
+}
+
+static gboolean
+on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
+{
+ DrawData data;
+
+ data.cr = gdk_cairo_create (widget->window);
+ data.self = LD_CANVAS (widget);
+ data.scale = ld_canvas_get_scale_in_px (data.self);
+ data.exposed_rect.x = event->area.x;
+ data.exposed_rect.y = event->area.y;
+ data.exposed_rect.width = event->area.width;
+ data.exposed_rect.height = event->area.height;
+
+ gdk_cairo_rectangle (data.cr, &event->area);
+ cairo_clip (data.cr);
+
+ ld_canvas_color_apply (COLOR_GET (data.self, COLOR_BASE), data.cr);
+ cairo_paint (data.cr);
+
+ draw_grid (widget, &data);
+ draw_diagram (widget, &data);
+ draw_terminal (widget, &data);
+
+ cairo_destroy (data.cr);
+ return FALSE;
+}
+
+static void
+draw_grid (GtkWidget *widget, DrawData *data)
+{
+ gdouble grid_step;
+ gdouble x_init, y_init;
+ gdouble x, y;
+
+ grid_step = data->scale;
+ while (grid_step < 5)
+ grid_step *= 5;
+
+ ld_canvas_color_apply (COLOR_GET (data->self, COLOR_GRID), data->cr);
+ cairo_set_line_width (data->cr, 1);
+ cairo_set_line_cap (data->cr, CAIRO_LINE_CAP_ROUND);
+
+ /* Get coordinates of the top-left point. */
+ ld_canvas_widget_to_diagram_coords (data->self,
+ data->exposed_rect.x, data->exposed_rect.y, &x_init, &y_init);
+ ld_canvas_diagram_to_widget_coords (data->self,
+ ceil (x_init), ceil (y_init), &x_init, &y_init);
+
+ /* Iterate over all the points. */
+ for (x = x_init; x <= data->exposed_rect.x + data->exposed_rect.width;
+ x += grid_step)
+ {
+ for (y = y_init; y <= data->exposed_rect.y + data->exposed_rect.height;
+ y += grid_step)
+ {
+ cairo_move_to (data->cr, x, y);
+ cairo_line_to (data->cr, x, y);
+ }
+ }
+ cairo_stroke (data->cr);
+}
+
+static void
+draw_terminal (GtkWidget *widget, DrawData *data)
+{
+ LdCanvasPrivate *priv;
+
+ priv = data->self->priv;
+ if (!priv->terminal_highlighted)
+ return;
+
+ ld_canvas_color_apply (COLOR_GET (data->self, COLOR_TERMINAL), data->cr);
+ cairo_set_line_width (data->cr, 1);
+
+ cairo_new_path (data->cr);
+ cairo_arc (data->cr, priv->terminal.x, priv->terminal.y,
+ TERMINAL_RADIUS, 0, 2 * G_PI);
+ cairo_stroke (data->cr);
+}
+
+static void
+draw_diagram (GtkWidget *widget, DrawData *data)
+{
+ GList *objects, *iter;
+
+ if (!data->self->priv->diagram)
+ return;
+
+ cairo_save (data->cr);
+ cairo_set_line_width (data->cr, 1 / data->scale);
+
+ /* Draw objects from the diagram, from bottom to top. */
+ objects = (GList *) ld_diagram_get_objects (data->self->priv->diagram);
+ for (iter = g_list_last (objects); iter; iter = g_list_previous (iter))
+ draw_object (LD_DIAGRAM_OBJECT (iter->data), data);
+
+ switch (data->self->priv->operation)
+ {
+ AddObjectData *op_data;
+
+ case OPER_ADD_OBJECT:
+ op_data = &OPER_DATA (data->self, add_object);
+ if (op_data->visible)
+ draw_object (op_data->object, data);
+ break;
+ }
+
+ cairo_restore (data->cr);
+}
+
+static void
+draw_object (LdDiagramObject *diagram_object, DrawData *data)
+{
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (diagram_object));
+ g_return_if_fail (data != NULL);
+
+ if (is_object_selected (data->self, diagram_object))
+ ld_canvas_color_apply (COLOR_GET (data->self,
+ COLOR_SELECTION), data->cr);
+ else
+ ld_canvas_color_apply (COLOR_GET (data->self,
+ COLOR_OBJECT), data->cr);
+
+ if (LD_IS_DIAGRAM_SYMBOL (diagram_object))
+ draw_symbol (LD_DIAGRAM_SYMBOL (diagram_object), data);
+}
+
+static void
+draw_symbol (LdDiagramSymbol *diagram_symbol, DrawData *data)
+{
+ LdSymbol *symbol;
+ LdRectangle clip_rect;
+ gdouble x, y;
+
+ symbol = resolve_diagram_symbol (data->self, diagram_symbol);
+
+ /* TODO: Resolve this better; draw a cross or whatever. */
+ if (!symbol)
+ {
+ g_warning ("Cannot find symbol %s in the library.",
+ ld_diagram_symbol_get_class (diagram_symbol));
+ return;
+ }
+
+ if (!get_symbol_clip_area (data->self, diagram_symbol, &clip_rect)
+ || !ld_rectangle_intersects (&clip_rect, &data->exposed_rect))
+ return;
+
+ cairo_save (data->cr);
+
+ cairo_rectangle (data->cr, clip_rect.x, clip_rect.y,
+ clip_rect.width, clip_rect.height);
+ cairo_clip (data->cr);
+
+ /* TODO: Rotate the space for other orientations. */
+ ld_canvas_diagram_to_widget_coords (data->self,
+ ld_diagram_object_get_x (LD_DIAGRAM_OBJECT (diagram_symbol)),
+ ld_diagram_object_get_y (LD_DIAGRAM_OBJECT (diagram_symbol)),
+ &x, &y);
+ cairo_translate (data->cr, x, y);
+ cairo_scale (data->cr, data->scale, data->scale);
+ ld_symbol_draw (symbol, data->cr);
+
+ cairo_restore (data->cr);
+}
diff --git a/liblogdiag/ld-canvas.h b/liblogdiag/ld-canvas.h
new file mode 100644
index 0000000..702f2fe
--- /dev/null
+++ b/liblogdiag/ld-canvas.h
@@ -0,0 +1,91 @@
+/*
+ * ld-canvas.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_CANVAS_H__
+#define __LD_CANVAS_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_CANVAS (ld_canvas_get_type ())
+#define LD_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_CANVAS, LdCanvas))
+#define LD_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_CANVAS, LdCanvasClass))
+#define LD_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_CANVAS))
+#define LD_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_CANVAS))
+#define LD_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_CANVAS, LdCanvasClass))
+
+typedef struct _LdCanvas LdCanvas;
+typedef struct _LdCanvasPrivate LdCanvasPrivate;
+typedef struct _LdCanvasClass LdCanvasClass;
+
+
+/**
+ * LdCanvas:
+ */
+struct _LdCanvas
+{
+/*< private >*/
+ GtkDrawingArea parent_instance;
+ LdCanvasPrivate *priv;
+
+/*< public >*/
+};
+
+struct _LdCanvasClass
+{
+/*< private >*/
+ GtkDrawingAreaClass parent_class;
+
+ guint cancel_operation_signal;
+
+ void (*set_scroll_adjustments) (LdCanvas *self,
+ GtkAdjustment *horizontal, GtkAdjustment *vertical);
+ void (*cancel_operation) (LdCanvas *self);
+};
+
+
+/**
+ * LD_CANVAS_BASE_UNIT:
+ *
+ * Length of the base unit in milimetres.
+ */
+#define LD_CANVAS_BASE_UNIT_LENGTH 2.5
+
+
+GType ld_canvas_get_type (void) G_GNUC_CONST;
+
+LdCanvas *ld_canvas_new (void);
+
+void ld_canvas_set_diagram (LdCanvas *self, LdDiagram *diagram);
+LdDiagram *ld_canvas_get_diagram (LdCanvas *self);
+void ld_canvas_set_library (LdCanvas *self, LdLibrary *library);
+LdLibrary *ld_canvas_get_library (LdCanvas *self);
+
+void ld_canvas_widget_to_diagram_coords (LdCanvas *self,
+ gdouble wx, gdouble wy, gdouble *dx, gdouble *dy);
+void ld_canvas_diagram_to_widget_coords (LdCanvas *self,
+ gdouble dx, gdouble dy, gdouble *wx, gdouble *wy);
+
+gdouble ld_canvas_get_zoom (LdCanvas *self);
+void ld_canvas_set_zoom (LdCanvas *self, gdouble zoom);
+
+void ld_canvas_add_object_begin (LdCanvas *self, LdDiagramObject *object);
+
+/* TODO: The rest of the interface. */
+
+
+G_END_DECLS
+
+#endif /* ! __LD_CANVAS_H__ */
diff --git a/liblogdiag/ld-diagram-object.c b/liblogdiag/ld-diagram-object.c
new file mode 100644
index 0000000..f43e620
--- /dev/null
+++ b/liblogdiag/ld-diagram-object.c
@@ -0,0 +1,186 @@
+/*
+ * ld-diagram-object.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-diagram-object
+ * @short_description: A diagram object.
+ * @see_also: #LdDiagram, #LdCanvas
+ *
+ * #LdDiagramObject represents an object in an #LdDiagram.
+ */
+
+/*
+ * LdDiagramObjectPrivate:
+ * @x: The X coordinate of this object.
+ * @y: The Y coordinate of this object.
+ */
+struct _LdDiagramObjectPrivate
+{
+ gdouble x;
+ gdouble y;
+};
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y
+};
+
+static void ld_diagram_object_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_diagram_object_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+
+
+G_DEFINE_ABSTRACT_TYPE (LdDiagramObject, ld_diagram_object, G_TYPE_OBJECT);
+
+static void
+ld_diagram_object_class_init (LdDiagramObjectClass *klass)
+{
+ GObjectClass *object_class;
+ GParamSpec *pspec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_diagram_object_get_property;
+ object_class->set_property = ld_diagram_object_set_property;
+
+/**
+ * LdDiagramObject:x:
+ *
+ * The X coordinate of the object.
+ */
+ pspec = g_param_spec_double ("x", "X",
+ "The X coordinate of this object.",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_X, pspec);
+
+/**
+ * LdDiagramObject:y:
+ *
+ * The Y coordinate of the object.
+ */
+ pspec = g_param_spec_double ("y", "Y",
+ "The Y coordinate of this object.",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_Y, pspec);
+
+ g_type_class_add_private (klass, sizeof (LdDiagramObjectPrivate));
+}
+
+static void
+ld_diagram_object_init (LdDiagramObject *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_DIAGRAM_OBJECT, LdDiagramObjectPrivate);
+}
+
+static void
+ld_diagram_object_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdDiagramObject *self;
+
+ self = LD_DIAGRAM_OBJECT (object);
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_double (value, ld_diagram_object_get_x (self));
+ break;
+ case PROP_Y:
+ g_value_set_double (value, ld_diagram_object_get_y (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_diagram_object_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ LdDiagramObject *self;
+
+ self = LD_DIAGRAM_OBJECT (object);
+ switch (property_id)
+ {
+ case PROP_X:
+ ld_diagram_object_set_x (self, g_value_get_double (value));
+ break;
+ case PROP_Y:
+ ld_diagram_object_set_y (self, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+
+/**
+ * ld_diagram_object_get_x:
+ * @self: An #LdDiagramObject object.
+ *
+ * Return value: The X coordinate of the object.
+ */
+gdouble
+ld_diagram_object_get_x (LdDiagramObject *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM_OBJECT (self), 0);
+ return self->priv->x;
+}
+
+/**
+ * ld_diagram_object_get_y:
+ * @self: An #LdDiagramObject object.
+ *
+ * Return value: The Y coordinate of the object.
+ */
+gdouble
+ld_diagram_object_get_y (LdDiagramObject *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM_OBJECT (self), 0);
+ return self->priv->y;
+}
+
+/**
+ * ld_diagram_object_set_x:
+ * @self: An #LdDiagramObject object.
+ * @x: The new X coordinate.
+ *
+ * Set the X coordinate of the object.
+ */
+void
+ld_diagram_object_set_x (LdDiagramObject *self, gdouble x)
+{
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self));
+ self->priv->x = x;
+
+ g_object_notify (G_OBJECT (self), "x");
+}
+
+/**
+ * ld_diagram_object_set_y:
+ * @self: An #LdDiagramObject object.
+ * @y: The new Y coordinate.
+ *
+ * Set the Y coordinate of the object.
+ */
+void
+ld_diagram_object_set_y (LdDiagramObject *self, gdouble y)
+{
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self));
+ self->priv->y = y;
+
+ g_object_notify (G_OBJECT (self), "y");
+}
diff --git a/liblogdiag/ld-diagram-object.h b/liblogdiag/ld-diagram-object.h
new file mode 100644
index 0000000..c727602
--- /dev/null
+++ b/liblogdiag/ld-diagram-object.h
@@ -0,0 +1,65 @@
+/*
+ * ld-diagram-object.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_DIAGRAM_OBJECT_H__
+#define __LD_DIAGRAM_OBJECT_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_DIAGRAM_OBJECT (ld_diagram_object_get_type ())
+#define LD_DIAGRAM_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_DIAGRAM_OBJECT, LdDiagramObject))
+#define LD_DIAGRAM_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_DIAGRAM_OBJECT, LdDiagramObjectClass))
+#define LD_IS_DIAGRAM_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_DIAGRAM_OBJECT))
+#define LD_IS_DIAGRAM_OBJECT_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_DIAGRAM_OBJECT))
+#define LD_DIAGRAM_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_DIAGRAM_OBJECT, LdDiagramObjectClass))
+
+typedef struct _LdDiagramObject LdDiagramObject;
+typedef struct _LdDiagramObjectPrivate LdDiagramObjectPrivate;
+typedef struct _LdDiagramObjectClass LdDiagramObjectClass;
+
+
+/**
+ * LdDiagramObject:
+ */
+struct _LdDiagramObject
+{
+/*< private >*/
+ GObject parent_instance;
+ LdDiagramObjectPrivate *priv;
+};
+
+/**
+ * LdDiagramObjectClass:
+ */
+struct _LdDiagramObjectClass
+{
+/*< private >*/
+ GObjectClass parent_class;
+};
+
+
+GType ld_diagram_object_get_type (void) G_GNUC_CONST;
+
+gdouble ld_diagram_object_get_x (LdDiagramObject *self);
+gdouble ld_diagram_object_get_y (LdDiagramObject *self);
+void ld_diagram_object_set_x (LdDiagramObject *self, gdouble x);
+void ld_diagram_object_set_y (LdDiagramObject *self, gdouble y);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_DIAGRAM_OBJECT_H__ */
+
diff --git a/liblogdiag/ld-diagram-symbol.c b/liblogdiag/ld-diagram-symbol.c
new file mode 100644
index 0000000..3308dbb
--- /dev/null
+++ b/liblogdiag/ld-diagram-symbol.c
@@ -0,0 +1,171 @@
+/*
+ * ld-diagram-symbol.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-diagram-symbol
+ * @short_description: A symbol object.
+ * @see_also: #LdDiagramObject
+ *
+ * #LdDiagramSymbol is an implementation of #LdDiagramObject.
+ */
+
+/*
+ * LdDiagramSymbolPrivate:
+ * @klass: The class of this symbol.
+ */
+struct _LdDiagramSymbolPrivate
+{
+ gchar *klass;
+};
+
+enum
+{
+ PROP_0,
+ PROP_CLASS
+};
+
+static void ld_diagram_symbol_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_diagram_symbol_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+static void ld_diagram_symbol_finalize (GObject *gobject);
+
+
+G_DEFINE_TYPE (LdDiagramSymbol, ld_diagram_symbol, LD_TYPE_DIAGRAM_OBJECT);
+
+static void
+ld_diagram_symbol_class_init (LdDiagramSymbolClass *klass)
+{
+ GObjectClass *object_class;
+ GParamSpec *pspec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_diagram_symbol_get_property;
+ object_class->set_property = ld_diagram_symbol_set_property;
+ object_class->finalize = ld_diagram_symbol_finalize;
+
+/**
+ * LdDiagramSymbol:class:
+ *
+ * The class of this symbol.
+ */
+ pspec = g_param_spec_string ("class", "Class",
+ "The class of this symbol.",
+ "", G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_CLASS, pspec);
+
+ g_type_class_add_private (klass, sizeof (LdDiagramSymbolPrivate));
+}
+
+static void
+ld_diagram_symbol_init (LdDiagramSymbol *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_DIAGRAM_SYMBOL, LdDiagramSymbolPrivate);
+}
+
+static void
+ld_diagram_symbol_finalize (GObject *gobject)
+{
+ LdDiagramSymbol *self;
+
+ self = LD_DIAGRAM_SYMBOL (gobject);
+
+ if (self->priv->klass)
+ g_free (self->priv->klass);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_diagram_symbol_parent_class)->finalize (gobject);
+}
+
+static void
+ld_diagram_symbol_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdDiagramSymbol *self;
+
+ self = LD_DIAGRAM_SYMBOL (object);
+ switch (property_id)
+ {
+ case PROP_CLASS:
+ g_value_set_string (value, ld_diagram_symbol_get_class (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_diagram_symbol_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ LdDiagramSymbol *self;
+
+ self = LD_DIAGRAM_SYMBOL (object);
+ switch (property_id)
+ {
+ case PROP_CLASS:
+ ld_diagram_symbol_set_class (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+
+/**
+ * ld_diagram_symbol_new:
+ * @klass: The class of the new symbol.
+ *
+ * Return value: A new #LdDiagramSymbol object.
+ */
+LdDiagramSymbol *
+ld_diagram_symbol_new (const gchar *klass)
+{
+ LdDiagramSymbol *self;
+
+ self = g_object_new (LD_TYPE_DIAGRAM_SYMBOL, NULL);
+ ld_diagram_symbol_set_class (self, klass);
+ return self;
+}
+
+/**
+ * ld_diagram_symbol_get_class:
+ * @self: An #LdDiagramSymbol object.
+ *
+ * Return value: The class of the symbol.
+ */
+const gchar *
+ld_diagram_symbol_get_class (LdDiagramSymbol *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM_SYMBOL (self), NULL);
+ return self->priv->klass;
+}
+
+/**
+ * ld_diagram_symbol_get_class:
+ * @self: An #LdDiagramSymbol object.
+ * @klass: The class.
+ *
+ * Set the class of the symbol.
+ */
+void
+ld_diagram_symbol_set_class (LdDiagramSymbol *self, const gchar *klass)
+{
+ g_return_if_fail (LD_IS_DIAGRAM_SYMBOL (self));
+
+ if (self->priv->klass)
+ g_free (self->priv->klass);
+ self->priv->klass = g_strdup (klass);
+}
diff --git a/liblogdiag/ld-diagram-symbol.h b/liblogdiag/ld-diagram-symbol.h
new file mode 100644
index 0000000..09d8739
--- /dev/null
+++ b/liblogdiag/ld-diagram-symbol.h
@@ -0,0 +1,64 @@
+/*
+ * ld-diagram-symbol.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_DIAGRAM_SYMBOL_H__
+#define __LD_DIAGRAM_SYMBOL_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_DIAGRAM_SYMBOL (ld_diagram_symbol_get_type ())
+#define LD_DIAGRAM_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_DIAGRAM_SYMBOL, LdDiagramSymbol))
+#define LD_DIAGRAM_SYMBOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_DIAGRAM_SYMBOL, LdDiagramSymbolClass))
+#define LD_IS_DIAGRAM_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_DIAGRAM_SYMBOL))
+#define LD_IS_DIAGRAM_SYMBOL_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_DIAGRAM_SYMBOL))
+#define LD_DIAGRAM_SYMBOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_DIAGRAM_SYMBOL, LdDiagramSymbolClass))
+
+typedef struct _LdDiagramSymbol LdDiagramSymbol;
+typedef struct _LdDiagramSymbolPrivate LdDiagramSymbolPrivate;
+typedef struct _LdDiagramSymbolClass LdDiagramSymbolClass;
+
+
+/**
+ * LdDiagramSymbol:
+ */
+struct _LdDiagramSymbol
+{
+/*< private >*/
+ LdDiagramObject parent_instance;
+ LdDiagramSymbolPrivate *priv;
+};
+
+/**
+ * LdDiagramSymbolClass:
+ */
+struct _LdDiagramSymbolClass
+{
+/*< private >*/
+ LdDiagramObjectClass parent_class;
+};
+
+
+GType ld_diagram_symbol_get_type (void) G_GNUC_CONST;
+
+LdDiagramSymbol *ld_diagram_symbol_new (const gchar *klass);
+const gchar *ld_diagram_symbol_get_class (LdDiagramSymbol *self);
+void ld_diagram_symbol_set_class (LdDiagramSymbol *self, const gchar *klass);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_DIAGRAM_SYMBOL_H__ */
+
diff --git a/liblogdiag/ld-diagram.c b/liblogdiag/ld-diagram.c
new file mode 100644
index 0000000..0129e2b
--- /dev/null
+++ b/liblogdiag/ld-diagram.c
@@ -0,0 +1,550 @@
+/*
+ * ld-diagram.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-diagram
+ * @short_description: A diagram object.
+ * @see_also: #LdCanvas
+ *
+ * #LdDiagram is a model used for storing diagrams.
+ */
+
+/*
+ * LdDiagramPrivate:
+ * @modified: Whether the diagram has been modified.
+ * @objects: All objects in the diagram.
+ * @selection: All currently selected objects.
+ * @connections: Connections between objects.
+ */
+struct _LdDiagramPrivate
+{
+ gboolean modified;
+
+ GList *objects;
+ GList *selection;
+ GList *connections;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MODIFIED
+};
+
+static void ld_diagram_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_diagram_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+static void ld_diagram_dispose (GObject *gobject);
+static void ld_diagram_finalize (GObject *gobject);
+
+static gboolean write_signature (GOutputStream *stream, GError **error);
+
+static void ld_diagram_real_changed (LdDiagram *self);
+static void ld_diagram_clear_internal (LdDiagram *self);
+static void ld_diagram_unselect_all_internal (LdDiagram *self);
+
+
+G_DEFINE_TYPE (LdDiagram, ld_diagram, G_TYPE_OBJECT);
+
+static void
+ld_diagram_class_init (LdDiagramClass *klass)
+{
+ GObjectClass *object_class;
+ GParamSpec *pspec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_diagram_get_property;
+ object_class->set_property = ld_diagram_set_property;
+ object_class->dispose = ld_diagram_dispose;
+ object_class->finalize = ld_diagram_finalize;
+
+ klass->changed = ld_diagram_real_changed;
+
+/**
+ * LdDiagram:modified:
+ *
+ * Whether the diagram has been modified.
+ */
+ pspec = g_param_spec_boolean ("modified", "Modified",
+ "Whether the diagram has been modified.",
+ FALSE, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MODIFIED, pspec);
+
+/**
+ * LdDiagram::changed:
+ * @diagram: The diagram object.
+ *
+ * Contents of the diagram have changed.
+ */
+ klass->changed_signal = g_signal_new
+ ("changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LdDiagramClass, changed), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+/**
+ * LdDiagram::selection-changed:
+ * @diagram: The diagram object.
+ *
+ * The current selection has changed.
+ */
+ klass->selection_changed_signal = g_signal_new
+ ("selection-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LdDiagramClass, selection_changed), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LdDiagramPrivate));
+}
+
+static void
+ld_diagram_init (LdDiagram *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_DIAGRAM, LdDiagramPrivate);
+}
+
+static void
+ld_diagram_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdDiagram *self;
+
+ self = LD_DIAGRAM (object);
+ switch (property_id)
+ {
+ case PROP_MODIFIED:
+ g_value_set_boolean (value, ld_diagram_get_modified (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_diagram_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ LdDiagram *self;
+
+ self = LD_DIAGRAM (object);
+ switch (property_id)
+ {
+ case PROP_MODIFIED:
+ ld_diagram_set_modified (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_diagram_dispose (GObject *gobject)
+{
+ LdDiagram *self;
+
+ self = LD_DIAGRAM (gobject);
+ ld_diagram_clear_internal (self);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_diagram_parent_class)->dispose (gobject);
+}
+
+static void
+ld_diagram_finalize (GObject *gobject)
+{
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_diagram_parent_class)->finalize (gobject);
+}
+
+static void
+ld_diagram_real_changed (LdDiagram *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+
+ ld_diagram_set_modified (self, TRUE);
+}
+
+
+/**
+ * ld_diagram_new:
+ *
+ * Create an instance.
+ */
+LdDiagram *
+ld_diagram_new (void)
+{
+ return g_object_new (LD_TYPE_DIAGRAM, NULL);
+}
+
+/**
+ * ld_diagram_clear:
+ * @self: An #LdDiagram object.
+ *
+ * Clear the whole diagram with it's objects and selection.
+ */
+void
+ld_diagram_clear (LdDiagram *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+
+ ld_diagram_clear_internal (self);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
+}
+
+/*
+ * ld_diagram_clear_internal:
+ * @self: An #LdDiagram object.
+ *
+ * Do the same as ld_diagram_clear() does but don't emit signals.
+ */
+static void
+ld_diagram_clear_internal (LdDiagram *self)
+{
+ ld_diagram_unselect_all (self);
+
+ g_list_free (self->priv->connections);
+ self->priv->connections = NULL;
+
+ g_list_foreach (self->priv->objects, (GFunc) g_object_unref, NULL);
+ g_list_free (self->priv->objects);
+ self->priv->objects = NULL;
+}
+
+/**
+ * ld_diagram_load_from_file:
+ * @self: An #LdDiagram object.
+ * @filename: A filename.
+ * @error: Return location for a GError, or NULL.
+ *
+ * Load a file into the diagram.
+ *
+ * Return value: TRUE if the file could be loaded, FALSE otherwise.
+ */
+gboolean
+ld_diagram_load_from_file (LdDiagram *self,
+ const gchar *filename, GError **error)
+{
+ JsonParser *parser;
+ GError *json_error;
+
+ g_return_val_if_fail (LD_IS_DIAGRAM (self), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ /* TODO: Implement loading for real. This is just a stub. */
+ parser = json_parser_new ();
+
+ json_error = NULL;
+ json_parser_load_from_file (parser, filename, &json_error);
+ if (json_error)
+ {
+ g_propagate_error (error, json_error);
+ g_object_unref (parser);
+ return FALSE;
+ }
+
+ ld_diagram_clear (self);
+ g_object_unref (parser);
+ return TRUE;
+}
+
+/**
+ * ld_diagram_save_to_file:
+ * @self: An #LdDiagram object.
+ * @filename: A filename.
+ * @error: Return location for a GError, or NULL.
+ *
+ * Save the diagram into a file.
+ *
+ * Return value: TRUE if the diagram could be saved, FALSE otherwise.
+ */
+gboolean
+ld_diagram_save_to_file (LdDiagram *self,
+ const gchar *filename, GError **error)
+{
+ GFile *file;
+ GFileOutputStream *file_stream;
+ JsonGenerator *generator;
+ JsonNode *root;
+ GError *local_error;
+
+ g_return_val_if_fail (LD_IS_DIAGRAM (self), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ file = g_file_new_for_path (filename);
+
+ local_error = NULL;
+ file_stream = g_file_replace (file, NULL, FALSE,
+ G_FILE_CREATE_NONE, NULL, &local_error);
+ g_object_unref (file);
+
+ if (local_error)
+ {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ local_error = NULL;
+ write_signature (G_OUTPUT_STREAM (file_stream), &local_error);
+ if (local_error)
+ {
+ g_object_unref (file_stream);
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ /* TODO: Implement saving for real. This is just a stub. */
+ generator = json_generator_new ();
+ g_object_set (generator, "pretty", TRUE, NULL);
+
+ /* XXX: json-glib dislikes empty objects. */
+ root = json_node_new (JSON_NODE_OBJECT);
+ json_generator_set_root (generator, root);
+ json_node_free (root);
+
+ local_error = NULL;
+ json_generator_to_stream (generator, G_OUTPUT_STREAM (file_stream),
+ NULL, &local_error);
+ g_object_unref (file_stream);
+ g_object_unref (generator);
+
+ if (local_error)
+ {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+write_signature (GOutputStream *stream, GError **error)
+{
+ static const gchar signature[] = "/* logdiag diagram */\n";
+ GError *local_error = NULL;
+
+ g_output_stream_write (stream, signature, sizeof (signature) - 1,
+ NULL, &local_error);
+ if (local_error)
+ {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * ld_diagram_get_modified:
+ * @self: An #LdDiagram object.
+ *
+ * Return value: The modification status of diagram.
+ */
+gboolean
+ld_diagram_get_modified (LdDiagram *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM (self), FALSE);
+ return self->priv->modified;
+}
+
+/**
+ * ld_diagram_set_modified:
+ * @self: An #LdDiagram object.
+ * @value: Whether the diagram has been modified.
+ *
+ * Set the modification status of diagram.
+ */
+void
+ld_diagram_set_modified (LdDiagram *self, gboolean value)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+ self->priv->modified = value;
+
+ g_object_notify (G_OBJECT (self), "modified");
+}
+
+/**
+ * ld_diagram_get_objects:
+ * @self: An #LdDiagram object.
+ *
+ * Get a list of objects in the diagram. Do not modify.
+ */
+GList *
+ld_diagram_get_objects (LdDiagram *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM (self), NULL);
+ return self->priv->objects;
+}
+
+/**
+ * ld_diagram_insert_object:
+ * @self: An #LdDiagram object.
+ * @object: The object to be inserted.
+ * @pos: The position at which the object is to be inserted.
+ * Negative values will append to the end.
+ *
+ * Insert an object into the diagram.
+ */
+void
+ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object));
+
+ if (!g_list_find (self->priv->objects, object))
+ {
+ self->priv->objects =
+ g_list_insert (self->priv->objects, object, pos);
+ g_object_ref (object);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
+ }
+}
+
+/**
+ * ld_diagram_remove_object:
+ * @self: An #LdDiagram object.
+ * @object: The object to be removed.
+ *
+ * Remove an object from the diagram.
+ */
+void
+ld_diagram_remove_object (LdDiagram *self, LdDiagramObject *object)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object));
+
+ if (g_list_find (self->priv->objects, object))
+ {
+ ld_diagram_selection_remove (self, object);
+
+ self->priv->objects = g_list_remove (self->priv->objects, object);
+ g_object_unref (object);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
+ }
+}
+
+/**
+ * ld_diagram_get_selection:
+ * @self: An #LdDiagram object.
+ *
+ * Get a list of objects that are currently selected in the diagram.
+ * Do not modify.
+ */
+GList *
+ld_diagram_get_selection (LdDiagram *self)
+{
+ g_return_val_if_fail (LD_IS_DIAGRAM (self), NULL);
+ return self->priv->selection;
+}
+
+/**
+ * ld_diagram_selection_add:
+ * @self: An #LdDiagram object.
+ * @object: The object to be added to the selection.
+ * @pos: The position at which the object is to be inserted.
+ * Negative values will append to the end.
+ *
+ * Add an object to selection.
+ */
+void
+ld_diagram_selection_add (LdDiagram *self, LdDiagramObject *object, gint pos)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object));
+
+ g_return_if_fail (g_list_find (self->priv->objects, object) != NULL);
+
+ if (!g_list_find (self->priv->selection, object))
+ {
+ self->priv->selection =
+ g_list_insert (self->priv->selection, object, pos);
+ g_object_ref (object);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->selection_changed_signal, 0);
+ }
+}
+
+/**
+ * ld_diagram_selection_remove:
+ * @self: An #LdDiagram object.
+ * @object: The object to be removed from the selection.
+ *
+ * Remove an object from the selection.
+ */
+void
+ld_diagram_selection_remove (LdDiagram *self, LdDiagramObject *object)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+ g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object));
+
+ if (g_list_find (self->priv->selection, object))
+ {
+ self->priv->selection = g_list_remove (self->priv->selection, object);
+ g_object_unref (object);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->selection_changed_signal, 0);
+ }
+}
+
+/**
+ * ld_diagram_select_all:
+ * @self: An #LdDiagram object.
+ *
+ * Include all objects in the document to the selection.
+ */
+void
+ld_diagram_select_all (LdDiagram *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+
+ ld_diagram_unselect_all_internal (self);
+
+ self->priv->selection = g_list_copy (self->priv->objects);
+ g_list_foreach (self->priv->selection, (GFunc) g_object_ref, NULL);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->selection_changed_signal, 0);
+}
+
+/**
+ * ld_diagram_unselect_all:
+ * @self: An #LdDiagram object.
+ *
+ * Remove all objects from the current selection.
+ */
+void
+ld_diagram_unselect_all (LdDiagram *self)
+{
+ g_return_if_fail (LD_IS_DIAGRAM (self));
+
+ ld_diagram_unselect_all_internal (self);
+
+ g_signal_emit (self,
+ LD_DIAGRAM_GET_CLASS (self)->selection_changed_signal, 0);
+}
+
+static void
+ld_diagram_unselect_all_internal (LdDiagram *self)
+{
+ g_list_foreach (self->priv->selection, (GFunc) g_object_unref, NULL);
+ g_list_free (self->priv->selection);
+ self->priv->selection = NULL;
+}
diff --git a/liblogdiag/ld-diagram.h b/liblogdiag/ld-diagram.h
new file mode 100644
index 0000000..f364189
--- /dev/null
+++ b/liblogdiag/ld-diagram.h
@@ -0,0 +1,98 @@
+/*
+ * ld-diagram.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_DIAGRAM_H__
+#define __LD_DIAGRAM_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_DIAGRAM (ld_diagram_get_type ())
+#define LD_DIAGRAM(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_DIAGRAM, LdDiagram))
+#define LD_DIAGRAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_DIAGRAM, LdDiagramClass))
+#define LD_IS_DIAGRAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_DIAGRAM))
+#define LD_IS_DIAGRAM_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_DIAGRAM))
+#define LD_DIAGRAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_DIAGRAM, LdDiagramClass))
+
+typedef struct _LdDiagram LdDiagram;
+typedef struct _LdDiagramClass LdDiagramClass;
+typedef struct _LdDiagramPrivate LdDiagramPrivate;
+
+
+/**
+ * LdDiagram:
+ *
+ * A diagram object.
+ */
+struct _LdDiagram
+{
+/*< private >*/
+ GObject parent_instance;
+ LdDiagramPrivate *priv;
+};
+
+struct _LdDiagramClass
+{
+/*< private >*/
+ GObjectClass parent_class;
+
+ guint changed_signal;
+ guint selection_changed_signal;
+
+ void (*changed) (LdDiagram *self);
+ void (*selection_changed) (LdDiagram *self);
+};
+
+
+GType ld_diagram_get_type (void) G_GNUC_CONST;
+
+LdDiagram *ld_diagram_new (void);
+void ld_diagram_clear (LdDiagram *self);
+gboolean ld_diagram_load_from_file (LdDiagram *self,
+ const gchar *filename, GError **error);
+gboolean ld_diagram_save_to_file (LdDiagram *self,
+ const gchar *filename, GError **error);
+
+gboolean ld_diagram_get_modified (LdDiagram *self);
+void ld_diagram_set_modified (LdDiagram *self, gboolean value);
+
+GList *ld_diagram_get_objects (LdDiagram *self);
+void ld_diagram_insert_object (LdDiagram *self,
+ LdDiagramObject *object, gint pos);
+void ld_diagram_remove_object (LdDiagram *self,
+ LdDiagramObject *object);
+
+GList *ld_diagram_get_selection (LdDiagram *self);
+void ld_diagram_selection_add (LdDiagram *self,
+ LdDiagramObject *object, gint pos);
+void ld_diagram_selection_remove (LdDiagram *self,
+ LdDiagramObject *object);
+
+void ld_diagram_select_all (LdDiagram *self);
+void ld_diagram_unselect_all (LdDiagram *self);
+
+/*
+GList *ld_diagram_get_connections (LdDiagram *self);
+void ld_diagram_connection_add (LdDiagram *self,
+ LdConnection *connection, gint pos);
+void ld_diagram_connection_remove (LdDiagram *self,
+ LdConnection *connection);
+*/
+
+
+G_END_DECLS
+
+#endif /* ! __LD_DIAGRAM_H__ */
+
diff --git a/liblogdiag/ld-library.c b/liblogdiag/ld-library.c
new file mode 100644
index 0000000..37e2bc9
--- /dev/null
+++ b/liblogdiag/ld-library.c
@@ -0,0 +1,524 @@
+/*
+ * ld-library.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include <string.h>
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-library
+ * @short_description: A symbol library.
+ * @see_also: #LdSymbol, #LdSymbolCategory
+ *
+ * #LdLibrary is used for loading symbols from their files.
+ */
+
+/*
+ * LdLibraryPrivate:
+ * @lua: State of the scripting language.
+ * @children: Child objects of the library.
+ */
+struct _LdLibraryPrivate
+{
+ LdLua *lua;
+ GSList *children;
+};
+
+static void ld_library_finalize (GObject *gobject);
+
+static LdSymbolCategory *load_category (LdLibrary *self,
+ const gchar *path, const gchar *name);
+static gboolean load_category_cb (const gchar *base,
+ const gchar *filename, gpointer userdata);
+static void load_category_symbol_cb (LdSymbol *symbol, gpointer user_data);
+
+static gchar *read_human_name_from_file (const gchar *filename);
+
+static gboolean foreach_dir (const gchar *path,
+ gboolean (*callback) (const gchar *, const gchar *, gpointer),
+ gpointer userdata, GError **error);
+static gboolean ld_library_load_cb
+ (const gchar *base, const gchar *filename, gpointer userdata);
+
+
+G_DEFINE_TYPE (LdLibrary, ld_library, G_TYPE_OBJECT);
+
+static void
+ld_library_class_init (LdLibraryClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ld_library_finalize;
+
+/**
+ * LdLibrary::changed:
+ * @library: The library object.
+ *
+ * Contents of the library have changed.
+ */
+ klass->changed_signal = g_signal_new
+ ("changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LdLibraryPrivate));
+}
+
+static void
+ld_library_init (LdLibrary *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_LIBRARY, LdLibraryPrivate);
+
+ self->priv->lua = ld_lua_new ();
+ self->priv->children = NULL;
+}
+
+static void
+ld_library_finalize (GObject *gobject)
+{
+ LdLibrary *self;
+
+ self = LD_LIBRARY (gobject);
+
+ g_object_unref (self->priv->lua);
+
+ g_slist_foreach (self->priv->children, (GFunc) g_object_unref, NULL);
+ g_slist_free (self->priv->children);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_library_parent_class)->finalize (gobject);
+}
+
+/**
+ * ld_library_new:
+ *
+ * Create an instance.
+ */
+LdLibrary *
+ld_library_new (void)
+{
+ return g_object_new (LD_TYPE_LIBRARY, NULL);
+}
+
+/*
+ * foreach_dir:
+ *
+ * Call a user-defined function for each file within a directory.
+ */
+static gboolean
+foreach_dir (const gchar *path,
+ gboolean (*callback) (const gchar *, const gchar *, gpointer),
+ gpointer userdata, GError **error)
+{
+ GDir *dir;
+ const gchar *item;
+
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (callback != NULL, FALSE);
+
+ dir = g_dir_open (path, 0, error);
+ if (!dir)
+ return FALSE;
+
+ while ((item = g_dir_read_name (dir)))
+ {
+ gchar *filename;
+
+ filename = g_build_filename (path, item, NULL);
+ if (!callback (item, filename, userdata))
+ break;
+ g_free (filename);
+ }
+ g_dir_close (dir);
+ return TRUE;
+}
+
+/*
+ * LoadCategoryData:
+ *
+ * Data shared between load_category() and load_category_cb().
+ */
+typedef struct
+{
+ LdLibrary *self;
+ LdSymbolCategory *cat;
+}
+LoadCategoryData;
+
+/*
+ * load_category:
+ * @self: An #LdLibrary object.
+ * @path: The path to the category.
+ * @name: The default name of the category.
+ *
+ * Loads a category into the library.
+ */
+static LdSymbolCategory *
+load_category (LdLibrary *self, const gchar *path, const gchar *name)
+{
+ LdSymbolCategory *cat;
+ gchar *icon_file, *category_file;
+ gchar *human_name;
+ LoadCategoryData data;
+
+ g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (!g_file_test (path, G_FILE_TEST_IS_DIR))
+ goto load_category_fail_1;
+
+ icon_file = g_build_filename (path, "icon.svg", NULL);
+ if (!g_file_test (icon_file, G_FILE_TEST_IS_REGULAR))
+ {
+ g_warning ("The category in %s has no icon.", path);
+ goto load_category_fail_2;
+ }
+
+ category_file = g_build_filename (path, "category.json", NULL);
+ human_name = read_human_name_from_file (category_file);
+ if (!human_name)
+ human_name = g_strdup (name);
+
+ cat = ld_symbol_category_new (name, human_name);
+ ld_symbol_category_set_image_path (cat, icon_file);
+
+ data.self = self;
+ data.cat = cat;
+ foreach_dir (path, load_category_cb, &data, NULL);
+
+ g_free (human_name);
+ g_free (category_file);
+ g_free (icon_file);
+ return cat;
+
+load_category_fail_2:
+ g_free (icon_file);
+load_category_fail_1:
+ return NULL;
+}
+
+/*
+ * load_category_cb:
+ *
+ * Load script files from a directory into a symbol category.
+ */
+static gboolean
+load_category_cb (const gchar *base, const gchar *filename, gpointer userdata)
+{
+ LoadCategoryData *data;
+
+ g_return_val_if_fail (base != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (userdata != NULL, FALSE);
+
+ data = (LoadCategoryData *) userdata;
+
+ if (ld_lua_check_file (data->self->priv->lua, filename))
+ ld_lua_load_file (data->self->priv->lua, filename,
+ load_category_symbol_cb, data->cat);
+ return TRUE;
+}
+
+/*
+ * load_category_symbol_cb:
+ *
+ * Insert newly registered symbols into the category.
+ */
+static void
+load_category_symbol_cb (LdSymbol *symbol, gpointer user_data)
+{
+ const gchar *name;
+ LdSymbolCategory *cat;
+ const GSList *children, *iter;
+
+ g_return_if_fail (LD_IS_SYMBOL (symbol));
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (user_data));
+
+ cat = LD_SYMBOL_CATEGORY (user_data);
+ name = ld_symbol_get_name (symbol);
+
+ /* Check for name collisions with other symbols. */
+ children = ld_symbol_category_get_children (cat);
+ for (iter = children; iter; iter = iter->next)
+ {
+ if (!LD_IS_SYMBOL (iter->data))
+ continue;
+ if (!strcmp (name, ld_symbol_get_name (LD_SYMBOL (iter->data))))
+ {
+ g_warning ("Attempted to insert multiple '%s' symbols into"
+ " category '%s'.", name, ld_symbol_category_get_name (cat));
+ return;
+ }
+ }
+ ld_symbol_category_insert_child (cat, G_OBJECT (symbol), -1);
+}
+
+/*
+ * read_human_name_from_file:
+ * @filename: Location of the JSON file.
+ *
+ * Read the human name of the processed category.
+ */
+static gchar *
+read_human_name_from_file (const gchar *filename)
+{
+ const gchar *const *lang;
+ JsonParser *parser;
+ JsonNode *root;
+ JsonObject *object;
+ GError *error;
+
+ g_return_val_if_fail (filename != NULL, NULL);
+
+ parser = json_parser_new ();
+ error = NULL;
+ if (!json_parser_load_from_file (parser, filename, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ goto read_human_name_from_file_end;
+ }
+
+ root = json_parser_get_root (parser);
+ if (!JSON_NODE_HOLDS_OBJECT (root))
+ {
+ g_warning ("Failed to parse '%s': %s", filename,
+ "The root node is not an object.");
+ goto read_human_name_from_file_end;
+ }
+
+ object = json_node_get_object (root);
+ for (lang = g_get_language_names (); *lang; lang++)
+ {
+ const gchar *member;
+
+ if (!json_object_has_member (object, *lang))
+ continue;
+ member = json_object_get_string_member (object, *lang);
+
+ if (member != NULL)
+ {
+ gchar *result;
+
+ result = g_strdup (member);
+ g_object_unref (parser);
+ return result;
+ }
+ }
+
+read_human_name_from_file_end:
+ g_object_unref (parser);
+ return NULL;
+}
+
+/*
+ * LibraryLoadData:
+ *
+ * Data shared between ld_library_load() and ld_library_load_cb().
+ */
+typedef struct
+{
+ LdLibrary *self;
+ gboolean changed;
+}
+LibraryLoadData;
+
+/**
+ * ld_library_load:
+ * @self: An #LdLibrary object.
+ * @directory: A directory to be loaded.
+ *
+ * Load the contents of a directory into the library.
+ */
+gboolean
+ld_library_load (LdLibrary *self, const gchar *directory)
+{
+ LibraryLoadData data;
+
+ g_return_val_if_fail (LD_IS_LIBRARY (self), FALSE);
+ g_return_val_if_fail (directory != NULL, FALSE);
+
+ data.self = self;
+ data.changed = FALSE;
+ foreach_dir (directory, ld_library_load_cb, &data, NULL);
+
+ if (data.changed)
+ g_signal_emit (self, LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
+
+ return TRUE;
+}
+
+/*
+ * ld_library_load_cb:
+ *
+ * A callback that's called for each file in the root directory.
+ */
+static gboolean
+ld_library_load_cb (const gchar *base, const gchar *filename, gpointer userdata)
+{
+ LdSymbolCategory *cat;
+ LibraryLoadData *data;
+
+ g_return_val_if_fail (base != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (userdata != NULL, FALSE);
+
+ data = (LibraryLoadData *) userdata;
+
+ cat = load_category (data->self, filename, base);
+ if (cat)
+ ld_library_insert_child (data->self, G_OBJECT (cat), -1);
+
+ data->changed = TRUE;
+ return TRUE;
+}
+
+/**
+ * ld_library_find_symbol:
+ * @self: An #LdLibrary object.
+ * @identifier: An identifier of the symbol to be searched for.
+ *
+ * Search for a symbol in the library.
+ *
+ * Return value: A symbol object if found, NULL otherwise.
+ */
+/* XXX: With this level of indentation, this function is really ugly. */
+LdSymbol *
+ld_library_find_symbol (LdLibrary *self, const gchar *identifier)
+{
+ gchar **id_el_start, **id_el;
+ const GSList *list, *list_el;
+
+ g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
+ g_return_val_if_fail (identifier != NULL, NULL);
+
+ id_el_start = g_strsplit (identifier, LD_LIBRARY_IDENTIFIER_SEPARATOR, 0);
+ if (!id_el_start)
+ return NULL;
+
+ list = ld_library_get_children (self);
+ for (id_el = id_el_start; id_el[0]; id_el++)
+ {
+ LdSymbolCategory *cat;
+ LdSymbol *symbol;
+ gboolean found = FALSE;
+
+ for (list_el = list; list_el; list_el = g_slist_next (list_el))
+ {
+ /* If the current identifier element is a category (not last)
+ * and this list element is a category.
+ */
+ if (id_el[1] && LD_IS_SYMBOL_CATEGORY (list_el->data))
+ {
+ cat = LD_SYMBOL_CATEGORY (list_el->data);
+ if (strcmp (id_el[0], ld_symbol_category_get_name (cat)))
+ continue;
+
+ list = ld_symbol_category_get_children (cat);
+ found = TRUE;
+ break;
+ }
+ /* If the current identifier element is a symbol (last)
+ * and this list element is a symbol.
+ */
+ else if (!id_el[1] && LD_IS_SYMBOL (list_el->data))
+ {
+ symbol = LD_SYMBOL (list_el->data);
+ if (strcmp (id_el[0], ld_symbol_get_name (symbol)))
+ continue;
+
+ g_strfreev (id_el_start);
+ return symbol;
+ }
+ }
+
+ if (!found)
+ break;
+ }
+ g_strfreev (id_el_start);
+ return NULL;
+}
+
+/**
+ * ld_library_clear:
+ * @self: An #LdLibrary object.
+ *
+ * Clear all the contents.
+ */
+void
+ld_library_clear (LdLibrary *self)
+{
+ g_return_if_fail (LD_IS_LIBRARY (self));
+
+ g_slist_foreach (self->priv->children, (GFunc) g_object_unref, NULL);
+ g_slist_free (self->priv->children);
+ self->priv->children = NULL;
+
+ g_signal_emit (self,
+ LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
+}
+
+/**
+ * ld_library_insert_child:
+ * @self: An #LdLibrary object.
+ * @child: The child to be inserted.
+ * @pos: The position at which the child will be inserted.
+ * Negative values will append to the end of list.
+ *
+ * Insert a child into the library.
+ */
+void
+ld_library_insert_child (LdLibrary *self, GObject *child, gint pos)
+{
+ g_return_if_fail (LD_IS_LIBRARY (self));
+ g_return_if_fail (G_IS_OBJECT (child));
+
+ g_object_ref (child);
+ self->priv->children = g_slist_insert (self->priv->children, child, pos);
+}
+
+/**
+ * ld_library_remove_child:
+ * @self: An #LdLibrary object.
+ * @child: The child to be removed.
+ *
+ * Remove a child from the library.
+ */
+void
+ld_library_remove_child (LdLibrary *self, GObject *child)
+{
+ g_return_if_fail (LD_IS_LIBRARY (self));
+ g_return_if_fail (G_IS_OBJECT (child));
+
+ if (g_slist_find (self->priv->children, child))
+ {
+ g_object_unref (child);
+ self->priv->children = g_slist_remove (self->priv->children, child);
+ }
+}
+
+/**
+ * ld_library_get_children:
+ * @self: An #LdLibrary object.
+ *
+ * Return value: The internal list of children. Do not modify.
+ */
+const GSList *
+ld_library_get_children (LdLibrary *self)
+{
+ g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
+ return self->priv->children;
+}
+
diff --git a/liblogdiag/ld-library.h b/liblogdiag/ld-library.h
new file mode 100644
index 0000000..26521ed
--- /dev/null
+++ b/liblogdiag/ld-library.h
@@ -0,0 +1,73 @@
+/*
+ * ld-library.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_LIBRARY_H__
+#define __LD_LIBRARY_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_LIBRARY (ld_library_get_type ())
+#define LD_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_LIBRARY, LdLibrary))
+#define LD_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_LIBRARY, LdLibraryClass))
+#define LD_IS_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_LIBRARY))
+#define LD_IS_LIBRARY_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_LIBRARY))
+#define LD_LIBRARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_LIBRARY, LdLibraryClass))
+
+typedef struct _LdLibrary LdLibrary;
+typedef struct _LdLibraryPrivate LdLibraryPrivate;
+typedef struct _LdLibraryClass LdLibraryClass;
+
+
+struct _LdLibrary
+{
+/*< private >*/
+ GObject parent_instance;
+ LdLibraryPrivate *priv;
+};
+
+struct _LdLibraryClass
+{
+/*< private >*/
+ GObjectClass parent_class;
+
+ guint changed_signal;
+};
+
+
+/**
+ * LD_LIBRARY_IDENTIFIER_SEPARATOR:
+ *
+ * Defines a string that separates categories and symbols in identifiers.
+ */
+#define LD_LIBRARY_IDENTIFIER_SEPARATOR "/"
+
+
+GType ld_library_get_type (void) G_GNUC_CONST;
+
+LdLibrary *ld_library_new (void);
+gboolean ld_library_load (LdLibrary *self, const gchar *directory);
+LdSymbol *ld_library_find_symbol (LdLibrary *self, const gchar *identifier);
+void ld_library_clear (LdLibrary *self);
+
+void ld_library_insert_child (LdLibrary *self, GObject *child, gint pos);
+void ld_library_remove_child (LdLibrary *self, GObject *child);
+const GSList *ld_library_get_children (LdLibrary *self);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_LIBRARY_H__ */
+
diff --git a/liblogdiag/ld-lua-private.h b/liblogdiag/ld-lua-private.h
new file mode 100644
index 0000000..7d6943a
--- /dev/null
+++ b/liblogdiag/ld-lua-private.h
@@ -0,0 +1,26 @@
+/*
+ * ld-lua-private.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_LUA_PRIVATE_H__
+#define __LD_LUA_PRIVATE_H__
+
+G_BEGIN_DECLS
+
+
+/*< private_header >*/
+
+void ld_lua_private_unregister (LdLua *self, LdLuaSymbol *symbol);
+void ld_lua_private_draw (LdLua *self, LdLuaSymbol *symbol, cairo_t *cr);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_LUA_PRIVATE_H__ */
+
diff --git a/liblogdiag/ld-lua-symbol-private.h b/liblogdiag/ld-lua-symbol-private.h
new file mode 100644
index 0000000..83bc646
--- /dev/null
+++ b/liblogdiag/ld-lua-symbol-private.h
@@ -0,0 +1,40 @@
+/*
+ * ld-lua-symbol-private.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_LUA_SYMBOL_PRIVATE_H__
+#define __LD_LUA_SYMBOL_PRIVATE_H__
+
+G_BEGIN_DECLS
+
+
+/*< private_header >*/
+
+/*
+ * LdLuaSymbolPrivate:
+ * @lua: Parent #LdLua object.
+ * @name: Name of this symbol.
+ * @human_name: Localized human name of this symbol.
+ * @area: Area of this symbol.
+ * @terminals: Terminals of this symbol.
+ */
+struct _LdLuaSymbolPrivate
+{
+ LdLua *lua;
+ gchar *name;
+ gchar *human_name;
+ LdRectangle area;
+ LdPointArray *terminals;
+};
+
+
+G_END_DECLS
+
+#endif /* ! __LD_LUA_SYMBOL_PRIVATE_H__ */
+
diff --git a/liblogdiag/ld-lua-symbol.c b/liblogdiag/ld-lua-symbol.c
new file mode 100644
index 0000000..27a6279
--- /dev/null
+++ b/liblogdiag/ld-lua-symbol.c
@@ -0,0 +1,138 @@
+/*
+ * ld-lua-symbol.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+#include "ld-lua-private.h"
+#include "ld-lua-symbol-private.h"
+
+
+/**
+ * SECTION:ld-lua-symbol
+ * @short_description: A symbol.
+ * @see_also: #LdSymbol
+ *
+ * #LdLuaSymbol is an implementation of #LdSymbol.
+ */
+
+static void ld_lua_symbol_finalize (GObject *gobject);
+
+static const gchar *ld_lua_symbol_real_get_name (LdSymbol *symbol);
+static const gchar *ld_lua_symbol_real_get_human_name (LdSymbol *symbol);
+static void ld_lua_symbol_real_get_area (LdSymbol *symbol, LdRectangle *area);
+static const LdPointArray *ld_lua_symbol_real_get_terminals (LdSymbol *symbol);
+static void ld_lua_symbol_real_draw (LdSymbol *symbol, cairo_t *cr);
+
+
+G_DEFINE_TYPE (LdLuaSymbol, ld_lua_symbol, LD_TYPE_SYMBOL);
+
+static void
+ld_lua_symbol_class_init (LdLuaSymbolClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ld_lua_symbol_finalize;
+
+ klass->parent_class.get_name = ld_lua_symbol_real_get_name;
+ klass->parent_class.get_human_name = ld_lua_symbol_real_get_human_name;
+ klass->parent_class.get_area = ld_lua_symbol_real_get_area;
+ klass->parent_class.get_terminals = ld_lua_symbol_real_get_terminals;
+ klass->parent_class.draw = ld_lua_symbol_real_draw;
+
+ g_type_class_add_private (klass, sizeof (LdLuaSymbolPrivate));
+}
+
+static void
+ld_lua_symbol_init (LdLuaSymbol *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_LUA_SYMBOL, LdLuaSymbolPrivate);
+}
+
+static void
+ld_lua_symbol_finalize (GObject *gobject)
+{
+ LdLuaSymbol *self;
+
+ self = LD_LUA_SYMBOL (gobject);
+
+ if (self->priv->lua)
+ {
+ ld_lua_private_unregister (self->priv->lua, self);
+ g_object_unref (self->priv->lua);
+ }
+
+ if (self->priv->name)
+ g_free (self->priv->name);
+ if (self->priv->human_name)
+ g_free (self->priv->human_name);
+
+ if (self->priv->terminals)
+ ld_point_array_free (self->priv->terminals);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_lua_symbol_parent_class)->finalize (gobject);
+}
+
+
+static const gchar *
+ld_lua_symbol_real_get_name (LdSymbol *symbol)
+{
+ g_return_val_if_fail (LD_IS_LUA_SYMBOL (symbol), NULL);
+ return LD_LUA_SYMBOL (symbol)->priv->name;
+}
+
+static const gchar *
+ld_lua_symbol_real_get_human_name (LdSymbol *symbol)
+{
+ g_return_val_if_fail (LD_IS_LUA_SYMBOL (symbol), NULL);
+ return LD_LUA_SYMBOL (symbol)->priv->human_name;
+}
+
+static void
+ld_lua_symbol_real_get_area (LdSymbol *symbol, LdRectangle *area)
+{
+ LdLuaSymbol *self;
+
+ g_return_if_fail (LD_IS_LUA_SYMBOL (symbol));
+ g_return_if_fail (area != NULL);
+
+ self = LD_LUA_SYMBOL (symbol);
+ *area = self->priv->area;
+}
+
+static const LdPointArray *
+ld_lua_symbol_real_get_terminals (LdSymbol *symbol)
+{
+ LdLuaSymbol *self;
+
+ g_return_val_if_fail (LD_IS_LUA_SYMBOL (symbol), NULL);
+
+ self = LD_LUA_SYMBOL (symbol);
+ return self->priv->terminals;
+}
+
+static void
+ld_lua_symbol_real_draw (LdSymbol *symbol, cairo_t *cr)
+{
+ LdLuaSymbol *self;
+
+ g_return_if_fail (LD_IS_LUA_SYMBOL (symbol));
+ g_return_if_fail (cr != NULL);
+
+ self = LD_LUA_SYMBOL (symbol);
+
+ cairo_save (cr);
+ ld_lua_private_draw (self->priv->lua, self, cr);
+ cairo_restore (cr);
+}
+
diff --git a/liblogdiag/ld-lua-symbol.h b/liblogdiag/ld-lua-symbol.h
new file mode 100644
index 0000000..5f68b59
--- /dev/null
+++ b/liblogdiag/ld-lua-symbol.h
@@ -0,0 +1,60 @@
+/*
+ * ld-lua-symbol.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_LUA_SYMBOL_H__
+#define __LD_LUA_SYMBOL_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_LUA_SYMBOL (ld_lua_symbol_get_type ())
+#define LD_LUA_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_LUA_SYMBOL, LdLuaSymbol))
+#define LD_LUA_SYMBOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_LUA_SYMBOL, LdLuaSymbolClass))
+#define LD_IS_LUA_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_LUA_SYMBOL))
+#define LD_IS_LUA_SYMBOL_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_LUA_SYMBOL))
+#define LD_LUA_SYMBOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_LUA_SYMBOL, LdLuaSymbolClass))
+
+typedef struct _LdLuaSymbol LdLuaSymbol;
+typedef struct _LdLuaSymbolPrivate LdLuaSymbolPrivate;
+typedef struct _LdLuaSymbolClass LdLuaSymbolClass;
+
+
+/**
+ * LdLuaSymbol:
+ */
+struct _LdLuaSymbol
+{
+/*< private >*/
+ LdSymbol parent_instance;
+ LdLuaSymbolPrivate *priv;
+};
+
+/**
+ * LdLuaSymbolClass:
+ * @parent_class: The parent class.
+ */
+struct _LdLuaSymbolClass
+{
+ LdSymbolClass parent_class;
+};
+
+
+GType ld_lua_symbol_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* ! __LD_LUA_SYMBOL_H__ */
+
diff --git a/liblogdiag/ld-lua.c b/liblogdiag/ld-lua.c
new file mode 100644
index 0000000..47a41b5
--- /dev/null
+++ b/liblogdiag/ld-lua.c
@@ -0,0 +1,808 @@
+/*
+ * ld-lua.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "liblogdiag.h"
+#include "config.h"
+
+#include "ld-lua-private.h"
+#include "ld-lua-symbol-private.h"
+
+
+/**
+ * SECTION:ld-lua
+ * @short_description: Lua symbol engine.
+ * @see_also: #LdLuaSymbol
+ *
+ * #LdLua is a symbol engine that uses Lua scripts to manage symbols.
+ */
+
+/*
+ * LdLuaPrivate:
+ * @L: Lua state.
+ *
+ * The library contains the real function for rendering.
+ */
+struct _LdLuaPrivate
+{
+ lua_State *L;
+};
+
+/* registry.logdiag_symbols
+ * -> A table indexed by pointers to LdLuaSymbol objects
+ * registry.logdiag_symbols.object.render(cr)
+ * -> The rendering function
+ */
+
+#define LD_LUA_LIBRARY_NAME "logdiag"
+#define LD_LUA_DATA_INDEX LD_LUA_LIBRARY_NAME "_data"
+#define LD_LUA_SYMBOLS_INDEX LD_LUA_LIBRARY_NAME "_symbols"
+
+/*
+ * LdLuaData:
+ * @self: A reference to self.
+ * @load_callback: A callback for newly registered symbols.
+ * @load_user_data: User data to be passed to the callback.
+ *
+ * Full user data to be stored in Lua registry.
+ */
+typedef struct _LdLuaData LdLuaData;
+
+struct _LdLuaData
+{
+ LdLua *self;
+ LdLuaLoadCallback load_callback;
+ gpointer load_user_data;
+};
+
+typedef struct _LdLuaDrawData LdLuaDrawData;
+
+struct _LdLuaDrawData
+{
+ LdLuaSymbol *symbol;
+ cairo_t *cr;
+ unsigned save_count;
+};
+
+static void ld_lua_finalize (GObject *gobject);
+
+static void *ld_lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize);
+
+static int ld_lua_private_draw_cb (lua_State *L);
+static int ld_lua_private_unregister_cb (lua_State *L);
+
+static int ld_lua_logdiag_register (lua_State *L);
+static int process_registration (lua_State *L);
+static gchar *get_translation (lua_State *L, int index);
+static gboolean read_symbol_area (lua_State *L, int index, LdRectangle *area);
+static gboolean read_terminals (lua_State *L, int index,
+ LdPointArray **terminals);
+
+static void push_cairo_object (lua_State *L, LdLuaDrawData *draw_data);
+static gdouble get_cairo_scale (cairo_t *cr);
+static int ld_lua_cairo_save (lua_State *L);
+static int ld_lua_cairo_restore (lua_State *L);
+static int ld_lua_cairo_get_line_width (lua_State *L);
+static int ld_lua_cairo_set_line_width (lua_State *L);
+static int ld_lua_cairo_move_to (lua_State *L);
+static int ld_lua_cairo_line_to (lua_State *L);
+static int ld_lua_cairo_curve_to (lua_State *L);
+static int ld_lua_cairo_arc (lua_State *L);
+static int ld_lua_cairo_arc_negative (lua_State *L);
+static int ld_lua_cairo_new_path (lua_State *L);
+static int ld_lua_cairo_new_sub_path (lua_State *L);
+static int ld_lua_cairo_close_path (lua_State *L);
+static int ld_lua_cairo_stroke (lua_State *L);
+static int ld_lua_cairo_stroke_preserve (lua_State *L);
+static int ld_lua_cairo_fill (lua_State *L);
+static int ld_lua_cairo_fill_preserve (lua_State *L);
+static int ld_lua_cairo_clip (lua_State *L);
+static int ld_lua_cairo_clip_preserve (lua_State *L);
+
+
+static luaL_Reg ld_lua_logdiag_lib[] =
+{
+ {"register", ld_lua_logdiag_register},
+ {NULL, NULL}
+};
+
+static luaL_Reg ld_lua_cairo_table[] =
+{
+ {"save", ld_lua_cairo_save},
+ {"restore", ld_lua_cairo_restore},
+ {"get_line_width", ld_lua_cairo_get_line_width},
+ {"set_line_width", ld_lua_cairo_set_line_width},
+ {"move_to", ld_lua_cairo_move_to},
+ {"line_to", ld_lua_cairo_line_to},
+ {"curve_to", ld_lua_cairo_curve_to},
+ {"arc", ld_lua_cairo_arc},
+ {"arc_negative", ld_lua_cairo_arc_negative},
+ {"new_path", ld_lua_cairo_new_path},
+ {"new_sub_path", ld_lua_cairo_new_sub_path},
+ {"close_path", ld_lua_cairo_close_path},
+ {"stroke", ld_lua_cairo_stroke},
+ {"stroke_preserve", ld_lua_cairo_stroke_preserve},
+ {"fill", ld_lua_cairo_fill},
+ {"fill_preserve", ld_lua_cairo_fill_preserve},
+ {"clip", ld_lua_cairo_clip},
+ {"clip_preserve", ld_lua_cairo_clip_preserve},
+ {NULL, NULL}
+};
+
+
+/* ===== Generic =========================================================== */
+
+G_DEFINE_TYPE (LdLua, ld_lua, G_TYPE_OBJECT);
+
+static void
+ld_lua_class_init (LdLuaClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ld_lua_finalize;
+
+ g_type_class_add_private (klass, sizeof (LdLuaPrivate));
+}
+
+static void
+ld_lua_init (LdLua *self)
+{
+ lua_State *L;
+ LdLuaData *ud;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_LUA, LdLuaPrivate);
+
+ L = self->priv->L = lua_newstate (ld_lua_alloc, NULL);
+ g_return_if_fail (L != NULL);
+
+ /* TODO: lua_atpanic () */
+
+ /* Load some safe libraries. */
+ lua_pushcfunction (L, luaopen_string);
+ lua_call (L, 0, 0);
+
+ lua_pushcfunction (L, luaopen_table);
+ lua_call (L, 0, 0);
+
+ lua_pushcfunction (L, luaopen_math);
+ lua_call (L, 0, 0);
+
+ /* Load the application library. */
+ luaL_register (L, LD_LUA_LIBRARY_NAME, ld_lua_logdiag_lib);
+
+ /* Store user data to the registry. */
+ ud = lua_newuserdata (L, sizeof (LdLuaData));
+ ud->self = self;
+ ud->load_callback = NULL;
+ ud->load_user_data = NULL;
+
+ lua_setfield (L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX);
+
+ /* Create an empty symbol table. */
+ lua_newtable (L);
+ lua_setfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX);
+}
+
+static void
+ld_lua_finalize (GObject *gobject)
+{
+ LdLua *self;
+
+ self = LD_LUA (gobject);
+ lua_close (self->priv->L);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_lua_parent_class)->finalize (gobject);
+}
+
+/**
+ * ld_lua_new:
+ *
+ * Create an instance of #LdLua.
+ */
+LdLua *
+ld_lua_new (void)
+{
+ return g_object_new (LD_TYPE_LUA, NULL);
+}
+
+static void *
+ld_lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize)
+{
+ if (!nsize)
+ {
+ g_free (ptr);
+ return NULL;
+ }
+ else
+ return g_try_realloc (ptr, nsize);
+}
+
+/**
+ * ld_lua_check_file:
+ * @self: An #LdLua object.
+ * @filename: The file to be checked.
+ *
+ * Check if the given filename can be loaded by #LdLua.
+ */
+gboolean
+ld_lua_check_file (LdLua *self, const gchar *filename)
+{
+ g_return_val_if_fail (LD_IS_LUA (self), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ return g_str_has_suffix (filename, ".lua")
+ && g_file_test (filename, G_FILE_TEST_IS_REGULAR);
+}
+
+/**
+ * ld_lua_load_file:
+ * @self: An #LdLua object.
+ * @filename: The file to be loaded.
+ * @callback: A callback for newly registered symbols.
+ * The callee is responsible for referencing the symbol.
+ * @user_data: User data to be passed to the callback.
+ *
+ * Loads a file and creates #LdLuaSymbol objects for contained symbols.
+ *
+ * Returns: TRUE if no error has occured, FALSE otherwise.
+ */
+gboolean
+ld_lua_load_file (LdLua *self, const gchar *filename,
+ LdLuaLoadCallback callback, gpointer user_data)
+{
+ gint retval;
+ LdLuaData *ud;
+
+ g_return_val_if_fail (LD_IS_LUA (self), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (callback != NULL, FALSE);
+
+ /* XXX: If something from the following fails, Lua will call exit(). */
+ lua_getfield (self->priv->L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX);
+ ud = lua_touserdata (self->priv->L, -1);
+ lua_pop (self->priv->L, 1);
+ g_return_val_if_fail (ud != NULL, FALSE);
+
+ ud->load_callback = callback;
+ ud->load_user_data = user_data;
+
+ retval = luaL_loadfile (self->priv->L, filename);
+ if (retval)
+ goto ld_lua_lftc_fail;
+
+ retval = lua_pcall (self->priv->L, 0, 0, 0);
+ if (retval)
+ goto ld_lua_lftc_fail;
+
+ ud->load_callback = NULL;
+ ud->load_user_data = NULL;
+ return TRUE;
+
+ld_lua_lftc_fail:
+ g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1));
+ lua_remove (self->priv->L, -1);
+
+ ud->load_callback = NULL;
+ ud->load_user_data = NULL;
+ return FALSE;
+}
+
+/* ===== LdLuaSymbol callbacks ============================================= */
+
+/**
+ * ld_lua_private_draw:
+ * @self: An #LdLua object.
+ * @symbol: A symbol to be drawn.
+ * @cr: A Cairo context to be drawn onto.
+ *
+ * Draw a symbol onto a Cairo context.
+ */
+void
+ld_lua_private_draw (LdLua *self, LdLuaSymbol *symbol, cairo_t *cr)
+{
+ LdLuaDrawData data;
+
+ g_return_if_fail (LD_IS_LUA (self));
+ g_return_if_fail (LD_IS_LUA_SYMBOL (symbol));
+ g_return_if_fail (cr != NULL);
+
+ data.symbol = symbol;
+ data.cr = cr;
+ data.save_count = 0;
+
+ if (lua_cpcall (self->priv->L, ld_lua_private_draw_cb, &data))
+ {
+ g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1));
+ lua_pop (self->priv->L, 1);
+ }
+
+ while (data.save_count--)
+ cairo_restore (cr);
+}
+
+static int
+ld_lua_private_draw_cb (lua_State *L)
+{
+ LdLuaDrawData *data;
+
+ data = lua_touserdata (L, -1);
+
+ /* Retrieve the function for rendering from the registry. */
+ lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX);
+ lua_pushlightuserdata (L, data->symbol);
+ lua_gettable (L, -2);
+
+ luaL_checktype (L, -1, LUA_TTABLE);
+ lua_getfield (L, -1, "render");
+ luaL_checktype (L, -1, LUA_TFUNCTION);
+
+ /* Call the function do draw the symbol. */
+ push_cairo_object (L, data);
+ lua_pcall (L, 1, 0, 0);
+ return 0;
+}
+
+/**
+ * ld_lua_private_unregister:
+ * @self: An #LdLua object.
+ * @symbol: A symbol to be unregistered.
+ *
+ * Unregister a symbol from the internal Lua state.
+ */
+void
+ld_lua_private_unregister (LdLua *self, LdLuaSymbol *symbol)
+{
+ g_return_if_fail (LD_IS_LUA (self));
+ g_return_if_fail (LD_IS_LUA_SYMBOL (symbol));
+
+ if (lua_cpcall (self->priv->L, ld_lua_private_unregister_cb, symbol))
+ {
+ g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1));
+ lua_pop (self->priv->L, 1);
+ }
+}
+
+static int
+ld_lua_private_unregister_cb (lua_State *L)
+{
+ /* Set the entry in the symbol table to nil. */
+ lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX);
+ lua_insert (L, -2);
+ lua_pushnil (L);
+ lua_settable (L, -3);
+ return 0;
+}
+
+/* ===== Application library =============================================== */
+
+static int
+ld_lua_logdiag_register (lua_State *L)
+{
+ LdLuaData *ud;
+ LdLuaSymbol *symbol;
+
+ lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX);
+ ud = lua_touserdata (L, -1);
+ lua_pop (L, 1);
+ g_return_val_if_fail (ud != NULL, 0);
+
+ /* Use a protected environment, so script errors won't cause leaking
+ * of the symbol object. Only a failure of the last three function calls
+ * before lua_pcall() may cause the symbol to leak.
+ */
+ lua_checkstack (L, 3);
+ symbol = g_object_new (LD_TYPE_LUA_SYMBOL, NULL);
+
+ lua_pushlightuserdata (L, symbol);
+ lua_pushcclosure (L, process_registration, 1);
+ lua_insert (L, 1);
+
+ /* On the stack, there are function arguments plus the function itself. */
+ if (lua_pcall (L, lua_gettop (L) - 1, 0, 0))
+ {
+ luaL_where (L, 1);
+ lua_insert (L, -2);
+ lua_concat (L, 2);
+
+ g_warning ("Lua symbol registration failed: %s",
+ lua_tostring (L, -1));
+ lua_pushboolean (L, FALSE);
+ }
+ else
+ {
+ /* We don't want an extra LdLua reference either. */
+ symbol->priv->lua = ud->self;
+ g_object_ref (ud->self);
+
+ ud->load_callback (LD_SYMBOL (symbol), ud->load_user_data);
+ lua_pushboolean (L, TRUE);
+ }
+ g_object_unref (symbol);
+
+ return 1;
+}
+
+/*
+ * process_registration:
+ * @L: A Lua state.
+ *
+ * Parse arguments, write them to a symbol object and register the object.
+ */
+static int
+process_registration (lua_State *L)
+{
+ LdLuaSymbol *symbol;
+ gchar *human_name;
+
+ int i, type, types[] =
+ {LUA_TSTRING, LUA_TTABLE, LUA_TTABLE, LUA_TTABLE, LUA_TFUNCTION};
+ int n_args_needed = sizeof (types) / sizeof (int);
+
+ if (lua_gettop (L) < n_args_needed)
+ return luaL_error (L, "Too few arguments.");
+
+ for (i = 0; i < n_args_needed; i++)
+ if ((type = lua_type (L, i + 1)) != types[i])
+ return luaL_error (L, "Bad type of argument #%d."
+ " Expected %s, got %s.", i + 1,
+ lua_typename (L, types[i]), lua_typename (L, type));
+
+ symbol = LD_LUA_SYMBOL (lua_touserdata (L, lua_upvalueindex (1)));
+ symbol->priv->name = g_strdup (lua_tostring (L, 1));
+
+ human_name = get_translation (L, 2);
+ if (!human_name)
+ human_name = g_strdup (symbol->priv->name);
+ symbol->priv->human_name = human_name;
+
+ if (!read_symbol_area (L, 3, &symbol->priv->area))
+ return luaL_error (L, "Malformed symbol area array.");
+ if (!read_terminals (L, 4, &symbol->priv->terminals))
+ return luaL_error (L, "Malformed terminals array.");
+
+ lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX);
+ lua_pushlightuserdata (L, symbol);
+
+ lua_newtable (L);
+ lua_pushvalue (L, 5);
+ lua_setfield (L, -2, "render");
+
+ lua_settable (L, -3);
+ return 0;
+}
+
+/*
+ * get_translation:
+ * @L: A Lua state.
+ * @index: Stack index of the table.
+ *
+ * Select an applicable translation from a table.
+ * The return value has to be freed with g_free().
+ *
+ * Return value: The translation, if found. If none was found, returns NULL.
+ */
+static gchar *
+get_translation (lua_State *L, int index)
+{
+ const gchar *const *lang;
+ gchar *result;
+
+ for (lang = g_get_language_names (); *lang; lang++)
+ {
+ lua_getfield (L, 2, *lang);
+ if (lua_isstring (L, -1))
+ {
+ result = g_strdup (lua_tostring (L, -1));
+ lua_pop (L, 1);
+ return result;
+ }
+ lua_pop (L, 1);
+ }
+ return NULL;
+}
+
+/*
+ * read_symbol_area:
+ * @L: A Lua state.
+ * @index: Stack index of the table.
+ * @area: Where the area will be returned.
+ *
+ * Read a symbol area from a Lua table.
+ *
+ * Return value: TRUE on success, FALSE on failure.
+ */
+static gboolean
+read_symbol_area (lua_State *L, int index, LdRectangle *area)
+{
+ lua_Number x1, x2, y1, y2;
+
+ if (lua_objlen (L, index) != 4)
+ return FALSE;
+
+ lua_rawgeti (L, index, 1);
+ if (!lua_isnumber (L, -1))
+ return FALSE;
+ x1 = lua_tonumber (L, -1);
+
+ lua_rawgeti (L, index, 2);
+ if (!lua_isnumber (L, -1))
+ return FALSE;
+ y1 = lua_tonumber (L, -1);
+
+ lua_rawgeti (L, index, 3);
+ if (!lua_isnumber (L, -1))
+ return FALSE;
+ x2 = lua_tonumber (L, -1);
+
+ lua_rawgeti (L, index, 4);
+ if (!lua_isnumber (L, -1))
+ return FALSE;
+ y2 = lua_tonumber (L, -1);
+
+ area->x = MIN (x1, x2);
+ area->y = MIN (y1, y2);
+ area->width = ABS (x2 - x1);
+ area->height = ABS (y2 - y1);
+
+ lua_pop (L, 4);
+ return TRUE;
+}
+
+/*
+ * read_terminals:
+ * @L: A Lua state.
+ * @index: Stack index of the table.
+ * @area: Where the point array will be returned.
+ *
+ * Read symbol terminals from a Lua table.
+ *
+ * Return value: TRUE on success, FALSE on failure.
+ */
+static gboolean
+read_terminals (lua_State *L, int index, LdPointArray **terminals)
+{
+ LdPointArray *points;
+ size_t num_points;
+ unsigned i = 0;
+
+ num_points = lua_objlen (L, index);
+ points = ld_point_array_new (num_points);
+
+ lua_pushnil (L);
+ while (lua_next (L, index) != 0)
+ {
+ g_assert (i < num_points);
+
+ if (!lua_istable (L, -1) || lua_objlen (L, -1) != 2)
+ goto read_terminals_fail;
+
+ lua_rawgeti (L, -1, 1);
+ if (!lua_isnumber (L, -1))
+ goto read_terminals_fail;
+ points->points[i].x = lua_tonumber (L, -1);
+ lua_pop (L, 1);
+
+ lua_rawgeti (L, -1, 2);
+ if (!lua_isnumber (L, -1))
+ goto read_terminals_fail;
+ points->points[i].y = lua_tonumber (L, -1);
+
+ lua_pop (L, 2);
+ i++;
+ }
+ *terminals = points;
+ return TRUE;
+
+read_terminals_fail:
+ ld_point_array_free (points);
+ *terminals = NULL;
+ return FALSE;
+}
+
+
+/* ===== Cairo ============================================================= */
+
+static void
+push_cairo_object (lua_State *L, LdLuaDrawData *draw_data)
+{
+ luaL_Reg *fn;
+
+ /* Create a table. */
+ lua_newtable (L);
+
+ /* Add methods. */
+ /* XXX: The light user data pointer gets invalid after the end of
+ * "render" function invocation. If the script stores the "cr" object
+ * in some global variable and then tries to reuse it the next time,
+ * the application may go SIGSEGV.
+ *
+ * The solution is creating a full user data instead, referencing
+ * the cairo object and dereferencing it upon garbage collection
+ * of the user data object.
+ */
+ for (fn = ld_lua_cairo_table; fn->name; fn++)
+ {
+ lua_pushlightuserdata (L, draw_data);
+ lua_pushcclosure (L, fn->func, 1);
+ lua_setfield (L, -2, fn->name);
+ }
+}
+
+static gdouble
+get_cairo_scale (cairo_t *cr)
+{
+ double dx = 1, dy = 0;
+
+ cairo_user_to_device_distance (cr, &dx, &dy);
+ return dx;
+}
+
+#define LD_LUA_CAIRO_TRIVIAL(name) \
+static int \
+ld_lua_cairo_ ## name (lua_State *L) \
+{ \
+ LdLuaDrawData *data; \
+ data = lua_touserdata (L, lua_upvalueindex (1)); \
+ cairo_ ## name (data->cr); \
+ return 0; \
+}
+
+LD_LUA_CAIRO_TRIVIAL (new_path)
+LD_LUA_CAIRO_TRIVIAL (new_sub_path)
+LD_LUA_CAIRO_TRIVIAL (close_path)
+
+LD_LUA_CAIRO_TRIVIAL (stroke)
+LD_LUA_CAIRO_TRIVIAL (stroke_preserve)
+LD_LUA_CAIRO_TRIVIAL (fill)
+LD_LUA_CAIRO_TRIVIAL (fill_preserve)
+LD_LUA_CAIRO_TRIVIAL (clip)
+LD_LUA_CAIRO_TRIVIAL (clip_preserve)
+
+static int
+ld_lua_cairo_save (lua_State *L)
+{
+ LdLuaDrawData *data;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+ if (data->save_count + 1)
+ {
+ data->save_count++;
+ cairo_save (data->cr);
+ }
+ return 0;
+}
+
+static int
+ld_lua_cairo_restore (lua_State *L)
+{
+ LdLuaDrawData *data;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+ if (data->save_count)
+ {
+ data->save_count--;
+ cairo_restore (data->cr);
+ }
+ return 0;
+}
+
+static int
+ld_lua_cairo_get_line_width (lua_State *L)
+{
+ LdLuaDrawData *data;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+ lua_pushnumber (L, cairo_get_line_width (data->cr)
+ * get_cairo_scale (data->cr));
+ return 1;
+}
+
+static int
+ld_lua_cairo_set_line_width (lua_State *L)
+{
+ LdLuaDrawData *data;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+ cairo_set_line_width (data->cr, luaL_checknumber (L, 1)
+ / get_cairo_scale (data->cr));
+ return 0;
+}
+
+static int
+ld_lua_cairo_move_to (lua_State *L)
+{
+ LdLuaDrawData *data;
+ lua_Number x, y;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+
+ x = luaL_checknumber (L, 1);
+ y = luaL_checknumber (L, 2);
+
+ cairo_move_to (data->cr, x, y);
+ return 0;
+}
+
+static int
+ld_lua_cairo_line_to (lua_State *L)
+{
+ LdLuaDrawData *data;
+ lua_Number x, y;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+
+ x = luaL_checknumber (L, 1);
+ y = luaL_checknumber (L, 2);
+
+ cairo_line_to (data->cr, x, y);
+ return 0;
+}
+
+static int
+ld_lua_cairo_curve_to (lua_State *L)
+{
+ LdLuaDrawData *data;
+ lua_Number x1, y1, x2, y2, x3, y3;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+
+ x1 = luaL_checknumber (L, 1);
+ y1 = luaL_checknumber (L, 2);
+ x2 = luaL_checknumber (L, 3);
+ y2 = luaL_checknumber (L, 4);
+ x3 = luaL_checknumber (L, 5);
+ y3 = luaL_checknumber (L, 6);
+
+ cairo_curve_to (data->cr, x1, y1, x2, y2, x3, y3);
+ return 0;
+}
+
+static int
+ld_lua_cairo_arc (lua_State *L)
+{
+ LdLuaDrawData *data;
+ lua_Number xc, yc, radius, angle1, angle2;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+
+ xc = luaL_checknumber (L, 1);
+ yc = luaL_checknumber (L, 2);
+ radius = luaL_checknumber (L, 3);
+ angle1 = luaL_checknumber (L, 4);
+ angle2 = luaL_checknumber (L, 5);
+
+ cairo_arc (data->cr, xc, yc, radius, angle1, angle2);
+ return 0;
+}
+
+static int
+ld_lua_cairo_arc_negative (lua_State *L)
+{
+ LdLuaDrawData *data;
+ lua_Number xc, yc, radius, angle1, angle2;
+
+ data = lua_touserdata (L, lua_upvalueindex (1));
+
+ xc = luaL_checknumber (L, 1);
+ yc = luaL_checknumber (L, 2);
+ radius = luaL_checknumber (L, 3);
+ angle1 = luaL_checknumber (L, 4);
+ angle2 = luaL_checknumber (L, 5);
+
+ cairo_arc_negative (data->cr, xc, yc, radius, angle1, angle2);
+ return 0;
+}
+
diff --git a/liblogdiag/ld-lua.h b/liblogdiag/ld-lua.h
new file mode 100644
index 0000000..b207d66
--- /dev/null
+++ b/liblogdiag/ld-lua.h
@@ -0,0 +1,70 @@
+/*
+ * ld-lua.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_LUA_H__
+#define __LD_LUA_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_LUA (ld_lua_get_type ())
+#define LD_LUA(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_LUA, LdLua))
+#define LD_LUA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_LUA, LdLuaClass))
+#define LD_IS_LUA(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_LUA))
+#define LD_IS_LUA_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_LUA))
+#define LD_LUA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_LUA, LdLuaClass))
+
+typedef struct _LdLua LdLua;
+typedef struct _LdLuaPrivate LdLuaPrivate;
+typedef struct _LdLuaClass LdLuaClass;
+
+
+struct _LdLua
+{
+/*< private >*/
+ GObject parent_instance;
+ LdLuaPrivate *priv;
+};
+
+/* TODO: A virtual superclass, so other engines can be used. */
+struct _LdLuaClass
+{
+/*< private >*/
+ GObjectClass parent_class;
+};
+
+
+/**
+ * LdLuaLoadCallback:
+ * @symbol: The symbol that has been created.
+ * @user_data: User data passed to ld_lua_load_file().
+ *
+ * A callback function that is called when a symbol is created.
+ */
+typedef void (*LdLuaLoadCallback) (LdSymbol *symbol, gpointer user_data);
+
+
+GType ld_lua_get_type (void) G_GNUC_CONST;
+
+LdLua *ld_lua_new (void);
+gboolean ld_lua_check_file (LdLua *self, const gchar *filename);
+gboolean ld_lua_load_file (LdLua *self, const gchar *filename,
+ LdLuaLoadCallback callback, gpointer user_data);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_LUA_H__ */
+
diff --git a/liblogdiag/ld-marshal.c b/liblogdiag/ld-marshal.c
new file mode 100644
index 0000000..ac88836
--- /dev/null
+++ b/liblogdiag/ld-marshal.c
@@ -0,0 +1,88 @@
+
+#include <glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v) g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v) g_value_get_int (v)
+#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
+#define g_marshal_value_peek_long(v) g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
+#define g_marshal_value_peek_float(v) g_value_get_float (v)
+#define g_marshal_value_peek_double(v) g_value_get_double (v)
+#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v) g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v) g_value_get_object (v)
+#define g_marshal_value_peek_variant(v) g_value_get_variant (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ * Do not access GValues directly in your code. Instead, use the
+ * g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
+#define g_marshal_value_peek_char(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v) (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v) (v)->data[0].v_float
+#define g_marshal_value_peek_double(v) (v)->data[0].v_double
+#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:OBJECT,OBJECT (ld-marshal.list:1) */
+void
+g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
+ gpointer arg_1,
+ gpointer arg_2,
+ gpointer data2);
+ register GMarshalFunc_VOID__OBJECT_OBJECT callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_object (param_values + 2),
+ data2);
+}
+
diff --git a/liblogdiag/ld-marshal.h b/liblogdiag/ld-marshal.h
new file mode 100644
index 0000000..545735a
--- /dev/null
+++ b/liblogdiag/ld-marshal.h
@@ -0,0 +1,20 @@
+
+#ifndef __g_cclosure_user_marshal_MARSHAL_H__
+#define __g_cclosure_user_marshal_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:OBJECT,OBJECT (ld-marshal.list:1) */
+extern void g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+G_END_DECLS
+
+#endif /* __g_cclosure_user_marshal_MARSHAL_H__ */
+
diff --git a/liblogdiag/ld-marshal.list b/liblogdiag/ld-marshal.list
new file mode 100644
index 0000000..38076d6
--- /dev/null
+++ b/liblogdiag/ld-marshal.list
@@ -0,0 +1 @@
+VOID:OBJECT,OBJECT
diff --git a/liblogdiag/ld-symbol-category.c b/liblogdiag/ld-symbol-category.c
new file mode 100644
index 0000000..e4b86a9
--- /dev/null
+++ b/liblogdiag/ld-symbol-category.c
@@ -0,0 +1,339 @@
+/*
+ * ld-symbol-category.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-symbol-category
+ * @short_description: A category of symbols.
+ * @see_also: #LdSymbol, #LdLibrary
+ *
+ * #LdSymbolCategory represents a category of #LdSymbol objects.
+ */
+
+/*
+ * LdSymbolCategoryPrivate:
+ * @name: The name of this category.
+ * @image_path: Path to the image for this category.
+ * @children: Children of this category.
+ */
+struct _LdSymbolCategoryPrivate
+{
+ gchar *name;
+ gchar *human_name;
+ gchar *image_path;
+ GSList *children;
+};
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_HUMAN_NAME,
+ PROP_IMAGE_PATH
+};
+
+static void ld_symbol_category_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_symbol_category_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+static void ld_symbol_category_finalize (GObject *gobject);
+
+
+G_DEFINE_TYPE (LdSymbolCategory, ld_symbol_category, G_TYPE_OBJECT);
+
+static void
+ld_symbol_category_class_init (LdSymbolCategoryClass *klass)
+{
+ GObjectClass *object_class;
+ GParamSpec *pspec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_symbol_category_get_property;
+ object_class->set_property = ld_symbol_category_set_property;
+ object_class->finalize = ld_symbol_category_finalize;
+
+/**
+ * LdSymbolCategory:name:
+ *
+ * The name of this symbol category.
+ */
+ pspec = g_param_spec_string ("name", "Name",
+ "The name of this symbol category.",
+ "", G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_NAME, pspec);
+
+/**
+ * LdSymbolCategory:human-name:
+ *
+ * The localized human name of this symbol category.
+ */
+ pspec = g_param_spec_string ("human-name", "Human name",
+ "The localized human name of this symbol category.",
+ "", G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_HUMAN_NAME, pspec);
+
+/**
+ * LdSymbolCategory:image-path:
+ *
+ * Path to an image file representing this category.
+ */
+ pspec = g_param_spec_string ("image-path", "Image path",
+ "Path to an image file representing this category.",
+ "", G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_IMAGE_PATH, pspec);
+
+ g_type_class_add_private (klass, sizeof (LdSymbolCategoryPrivate));
+}
+
+static void
+ld_symbol_category_init (LdSymbolCategory *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE
+ (self, LD_TYPE_SYMBOL_CATEGORY, LdSymbolCategoryPrivate);
+}
+
+static void
+ld_symbol_category_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdSymbolCategory *self;
+
+ self = LD_SYMBOL_CATEGORY (object);
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, ld_symbol_category_get_name (self));
+ break;
+ case PROP_HUMAN_NAME:
+ g_value_set_string (value, ld_symbol_category_get_human_name (self));
+ break;
+ case PROP_IMAGE_PATH:
+ g_value_set_string (value, ld_symbol_category_get_image_path (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_symbol_category_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ LdSymbolCategory *self;
+
+ self = LD_SYMBOL_CATEGORY (object);
+ switch (property_id)
+ {
+ case PROP_NAME:
+ ld_symbol_category_set_name (self, g_value_get_string (value));
+ break;
+ case PROP_HUMAN_NAME:
+ ld_symbol_category_set_human_name (self, g_value_get_string (value));
+ break;
+ case PROP_IMAGE_PATH:
+ ld_symbol_category_set_image_path (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_symbol_category_finalize (GObject *gobject)
+{
+ LdSymbolCategory *self;
+
+ self = LD_SYMBOL_CATEGORY (gobject);
+
+ if (self->priv->name)
+ g_free (self->priv->name);
+ if (self->priv->human_name)
+ g_free (self->priv->human_name);
+ if (self->priv->image_path)
+ g_free (self->priv->image_path);
+
+ g_slist_foreach (self->priv->children, (GFunc) g_object_unref, NULL);
+ g_slist_free (self->priv->children);
+
+ /* Chain up to the parent class. */
+ G_OBJECT_CLASS (ld_symbol_category_parent_class)->finalize (gobject);
+}
+
+
+/**
+ * ld_symbol_category_new:
+ * @name: The name of the new category.
+ * @human_name: The localized human name of the new category.
+ *
+ * Create an instance.
+ */
+LdSymbolCategory *
+ld_symbol_category_new (const gchar *name, const gchar *human_name)
+{
+ LdSymbolCategory *cat;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (human_name != NULL, NULL);
+
+ cat = g_object_new (LD_TYPE_SYMBOL_CATEGORY, NULL);
+ cat->priv->name = g_strdup (name);
+ cat->priv->human_name = g_strdup (human_name);
+
+ return cat;
+}
+
+/**
+ * ld_symbol_category_set_name:
+ * @self: An #LdSymbolCategory object.
+ * @name: The new name for this category.
+ */
+void
+ld_symbol_category_set_name (LdSymbolCategory *self, const gchar *name)
+{
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (self));
+ g_return_if_fail (name != NULL);
+
+ if (self->priv->name)
+ g_free (self->priv->name);
+ self->priv->name = g_strdup (name);
+
+ g_object_notify (G_OBJECT (self), "name");
+}
+
+/**
+ * ld_symbol_category_get_name:
+ * @self: An #LdSymbolCategory object.
+ *
+ * Return the name of this category.
+ */
+const gchar *
+ld_symbol_category_get_name (LdSymbolCategory *self)
+{
+ g_return_val_if_fail (LD_IS_SYMBOL_CATEGORY (self), NULL);
+ return self->priv->name;
+}
+
+/**
+ * ld_symbol_category_set_human_name:
+ * @self: An #LdSymbolCategory object.
+ * @human_name: The new localized human name for this category.
+ */
+void
+ld_symbol_category_set_human_name (LdSymbolCategory *self,
+ const gchar *human_name)
+{
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (self));
+ g_return_if_fail (human_name != NULL);
+
+ if (self->priv->human_name)
+ g_free (self->priv->human_name);
+ self->priv->human_name = g_strdup (human_name);
+
+ g_object_notify (G_OBJECT (self), "human-name");
+}
+
+/**
+ * ld_symbol_category_get_human_name:
+ * @self: An #LdSymbolCategory object.
+ *
+ * Return the localized human name of this category.
+ */
+const gchar *
+ld_symbol_category_get_human_name (LdSymbolCategory *self)
+{
+ g_return_val_if_fail (LD_IS_SYMBOL_CATEGORY (self), NULL);
+ return self->priv->human_name;
+}
+
+/**
+ * ld_symbol_category_set_image_path:
+ * @self: An #LdSymbolCategory object.
+ * @image_path: The new path to the image for this category. May be NULL.
+ */
+void
+ld_symbol_category_set_image_path (LdSymbolCategory *self,
+ const gchar *image_path)
+{
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (self));
+
+ if (self->priv->image_path)
+ g_free (self->priv->image_path);
+ self->priv->image_path = g_strdup (image_path);
+
+ g_object_notify (G_OBJECT (self), "image-path");
+}
+
+/**
+ * ld_symbol_category_get_image_path:
+ * @self: An #LdSymbolCategory object.
+ *
+ * Return the filesystem path to the image for this category. May be NULL.
+ */
+const gchar *
+ld_symbol_category_get_image_path (LdSymbolCategory *self)
+{
+ g_return_val_if_fail (LD_IS_SYMBOL_CATEGORY (self), NULL);
+ return self->priv->image_path;
+}
+
+/**
+ * ld_symbol_category_insert_child:
+ * @self: An #LdSymbolCategory object.
+ * @child: The child to be inserted.
+ * @pos: The position at which the child will be inserted.
+ * Negative values will append to the end of list.
+ *
+ * Insert a child into the category.
+ */
+void
+ld_symbol_category_insert_child (LdSymbolCategory *self,
+ GObject *child, gint pos)
+{
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (self));
+ g_return_if_fail (G_IS_OBJECT (child));
+
+ g_object_ref (child);
+ self->priv->children = g_slist_insert (self->priv->children, child, pos);
+}
+
+/**
+ * ld_symbol_category_remove_child:
+ * @self: An #LdSymbolCategory object.
+ * @child: The child to be removed.
+ *
+ * Removes a child from the category.
+ */
+void
+ld_symbol_category_remove_child (LdSymbolCategory *self,
+ GObject *child)
+{
+ g_return_if_fail (LD_IS_SYMBOL_CATEGORY (self));
+ g_return_if_fail (G_IS_OBJECT (child));
+
+ g_object_unref (child);
+ self->priv->children = g_slist_remove (self->priv->children, child);
+}
+
+/**
+ * ld_symbol_category_get_children:
+ * @self: An #LdSymbolCategory object.
+ *
+ * Return value: The internal list of children. Do not modify.
+ */
+const GSList *
+ld_symbol_category_get_children (LdSymbolCategory *self)
+{
+ g_return_val_if_fail (LD_IS_SYMBOL_CATEGORY (self), NULL);
+ return self->priv->children;
+}
+
diff --git a/liblogdiag/ld-symbol-category.h b/liblogdiag/ld-symbol-category.h
new file mode 100644
index 0000000..3b1f05b
--- /dev/null
+++ b/liblogdiag/ld-symbol-category.h
@@ -0,0 +1,79 @@
+/*
+ * ld-symbol-category.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_SYMBOL_CATEGORY_H__
+#define __LD_SYMBOL_CATEGORY_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_SYMBOL_CATEGORY (ld_symbol_category_get_type ())
+#define LD_SYMBOL_CATEGORY(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_SYMBOL_CATEGORY, LdSymbolCategory))
+#define LD_SYMBOL_CATEGORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_SYMBOL_CATEGORY, LdSymbolCategoryClass))
+#define LD_IS_SYMBOL_CATEGORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_SYMBOL_CATEGORY))
+#define LD_IS_SYMBOL_CATEGORY_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_SYMBOL_CATEGORY))
+#define LD_SYMBOL_CATEGORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_SYMBOL_CATEGORY, LdSymbolCategoryClass))
+
+typedef struct _LdSymbolCategory LdSymbolCategory;
+typedef struct _LdSymbolCategoryPrivate LdSymbolCategoryPrivate;
+typedef struct _LdSymbolCategoryClass LdSymbolCategoryClass;
+
+
+/**
+ * LdSymbolCategory:
+ */
+struct _LdSymbolCategory
+{
+/*< private >*/
+ GObject parent_instance;
+ LdSymbolCategoryPrivate *priv;
+};
+
+/* TODO: If required sometime, categories (and maybe symbols) should implement
+ * a "changed" signal. This can be somewhat tricky. The library might be
+ * a good candidate for what they call a proxy. See GtkUIManager.
+ */
+struct _LdSymbolCategoryClass
+{
+/*< private >*/
+ GObjectClass parent_class;
+};
+
+
+GType ld_symbol_category_get_type (void) G_GNUC_CONST;
+
+LdSymbolCategory *ld_symbol_category_new (const gchar *name,
+ const gchar *human_name);
+
+void ld_symbol_category_set_name (LdSymbolCategory *self, const gchar *name);
+const gchar *ld_symbol_category_get_name (LdSymbolCategory *self);
+void ld_symbol_category_set_human_name (LdSymbolCategory *self,
+ const gchar *human_name);
+const gchar *ld_symbol_category_get_human_name (LdSymbolCategory *self);
+void ld_symbol_category_set_image_path (LdSymbolCategory *self,
+ const gchar *image_path);
+const gchar *ld_symbol_category_get_image_path (LdSymbolCategory *self);
+
+void ld_symbol_category_insert_child (LdSymbolCategory *self,
+ GObject *child, gint pos);
+void ld_symbol_category_remove_child (LdSymbolCategory *self,
+ GObject *child);
+const GSList *ld_symbol_category_get_children (LdSymbolCategory *self);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_SYMBOL_CATEGORY_H__ */
+
diff --git a/liblogdiag/ld-symbol.c b/liblogdiag/ld-symbol.c
new file mode 100644
index 0000000..fafa9ab
--- /dev/null
+++ b/liblogdiag/ld-symbol.c
@@ -0,0 +1,232 @@
+/*
+ * ld-symbol.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+/**
+ * SECTION:ld-symbol
+ * @short_description: A symbol.
+ * @see_also: #LdDiagramSymbol, #LdCanvas
+ *
+ * #LdSymbol represents a symbol to be drawn onto a #LdCanvas.
+ *
+ * All implementations of this abstract class are required to use
+ * cairo_save() and cairo_restore() when drawing to store the state.
+ */
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_HUMAN_NAME,
+ PROP_AREA,
+ PROP_TERMINALS
+};
+
+static void ld_symbol_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void ld_symbol_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+
+
+G_DEFINE_ABSTRACT_TYPE (LdSymbol, ld_symbol, G_TYPE_OBJECT);
+
+static void
+ld_symbol_class_init (LdSymbolClass *klass)
+{
+ GObjectClass *object_class;
+ GParamSpec *pspec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = ld_symbol_get_property;
+ object_class->set_property = ld_symbol_set_property;
+
+/**
+ * LdSymbol:name:
+ *
+ * The name of this symbol.
+ */
+ pspec = g_param_spec_string ("name", "Name",
+ "The name of this symbol.",
+ "", G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_NAME, pspec);
+
+/**
+ * LdSymbol:human-name:
+ *
+ * The localized human name of this symbol.
+ */
+ pspec = g_param_spec_string ("human-name", "Human name",
+ "The localized human name of this symbol.",
+ "", G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_HUMAN_NAME, pspec);
+
+/**
+ * LdSymbol:area:
+ *
+ * The area of this symbol.
+ */
+ pspec = g_param_spec_boxed ("area", "Area",
+ "The area of this symbol.",
+ LD_TYPE_RECTANGLE, G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_AREA, pspec);
+
+/**
+ * LdSymbol:terminals:
+ *
+ * A point array that specifies terminals of this symbol.
+ */
+ pspec = g_param_spec_boxed ("terminals", "Terminals",
+ "A point array that specifies terminals of this symbol.",
+ LD_TYPE_POINT_ARRAY, G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_TERMINALS, pspec);
+}
+
+static void
+ld_symbol_init (LdSymbol *self)
+{
+}
+
+static void
+ld_symbol_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ LdSymbol *self;
+
+ self = LD_SYMBOL (object);
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, ld_symbol_get_name (self));
+ break;
+ case PROP_HUMAN_NAME:
+ g_value_set_string (value, ld_symbol_get_human_name (self));
+ break;
+ case PROP_AREA:
+ {
+ LdRectangle area;
+
+ ld_symbol_get_area (self, &area);
+ g_value_set_boxed (value, &area);
+ }
+ break;
+ case PROP_TERMINALS:
+ g_value_set_boxed (value, ld_symbol_get_terminals (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ld_symbol_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+
+/**
+ * ld_symbol_get_name:
+ * @self: An #LdSymbol object.
+ *
+ * Return value: The name of the symbol.
+ */
+const gchar *
+ld_symbol_get_name (LdSymbol *self)
+{
+ LdSymbolClass *klass;
+
+ g_return_val_if_fail (LD_IS_SYMBOL (self), NULL);
+
+ klass = LD_SYMBOL_GET_CLASS (self);
+ g_return_val_if_fail (klass->get_name != NULL, NULL);
+ return klass->get_name (self);
+}
+
+/**
+ * ld_symbol_get_human_name:
+ * @self: An #LdSymbol object.
+ *
+ * Return value: The localised human name of the symbol.
+ */
+const gchar *
+ld_symbol_get_human_name (LdSymbol *self)
+{
+ LdSymbolClass *klass;
+
+ g_return_val_if_fail (LD_IS_SYMBOL (self), NULL);
+
+ klass = LD_SYMBOL_GET_CLASS (self);
+ g_return_val_if_fail (klass->get_human_name != NULL, NULL);
+ return klass->get_human_name (self);
+}
+
+/**
+ * ld_symbol_get_area:
+ * @self: An #LdSymbol object.
+ * @area: Where the area of the symbol will be returned.
+ *
+ * Get the area of the symbol.
+ */
+void
+ld_symbol_get_area (LdSymbol *self, LdRectangle *area)
+{
+ LdSymbolClass *klass;
+
+ g_return_if_fail (LD_IS_SYMBOL (self));
+ g_return_if_fail (area != NULL);
+
+ klass = LD_SYMBOL_GET_CLASS (self);
+ g_return_if_fail (klass->get_area != NULL);
+ klass->get_area (self, area);
+}
+
+/**
+ * ld_symbol_get_terminals:
+ * @self: An #LdSymbol object.
+ *
+ * Get a list of symbol terminals.
+ *
+ * Return value: An #LdPointArray structure.
+ */
+const LdPointArray *
+ld_symbol_get_terminals (LdSymbol *self)
+{
+ LdSymbolClass *klass;
+
+ g_return_val_if_fail (LD_IS_SYMBOL (self), NULL);
+
+ klass = LD_SYMBOL_GET_CLASS (self);
+ g_return_val_if_fail (klass->get_terminals != NULL, NULL);
+ return klass->get_terminals (self);
+}
+
+/**
+ * ld_symbol_draw:
+ * @self: An #LdSymbol object.
+ * @cr: A cairo surface to be drawn on.
+ *
+ * Draw the symbol onto a Cairo surface.
+ */
+void
+ld_symbol_draw (LdSymbol *self, cairo_t *cr)
+{
+ LdSymbolClass *klass;
+
+ g_return_if_fail (LD_IS_SYMBOL (self));
+ g_return_if_fail (cr != NULL);
+
+ klass = LD_SYMBOL_GET_CLASS (self);
+ g_return_if_fail (klass->draw != NULL);
+ klass->draw (self, cr);
+}
diff --git a/liblogdiag/ld-symbol.h b/liblogdiag/ld-symbol.h
new file mode 100644
index 0000000..409eba5
--- /dev/null
+++ b/liblogdiag/ld-symbol.h
@@ -0,0 +1,74 @@
+/*
+ * ld-symbol.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_SYMBOL_H__
+#define __LD_SYMBOL_H__
+
+G_BEGIN_DECLS
+
+
+#define LD_TYPE_SYMBOL (ld_symbol_get_type ())
+#define LD_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), LD_TYPE_SYMBOL, LdSymbol))
+#define LD_SYMBOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \
+ ((klass), LD_TYPE_SYMBOL, LdSymbolClass))
+#define LD_IS_SYMBOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), LD_TYPE_SYMBOL))
+#define LD_IS_SYMBOL_CLASS(klass) (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((klass), LD_TYPE_SYMBOL))
+#define LD_SYMBOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), LD_SYMBOL, LdSymbolClass))
+
+typedef struct _LdSymbol LdSymbol;
+typedef struct _LdSymbolPrivate LdSymbolPrivate;
+typedef struct _LdSymbolClass LdSymbolClass;
+
+
+struct _LdSymbol
+{
+/*< private >*/
+ GObject parent_instance;
+ LdSymbolPrivate *priv;
+};
+
+/**
+ * LdSymbolClass:
+ * @parent_class: The parent class.
+ * @get_name: Get the name of the symbol.
+ * @get_human_name: Get the localized human name of the symbol.
+ * @get_area: Get the area of the symbol.
+ * @get_terminals: Get a list of symbol terminals.
+ * @draw: Draw the symbol on a Cairo surface.
+ */
+struct _LdSymbolClass
+{
+ GObjectClass parent_class;
+
+ const gchar *(*get_name) (LdSymbol *self);
+ const gchar *(*get_human_name) (LdSymbol *self);
+ void (*get_area) (LdSymbol *self, LdRectangle *area);
+ const LdPointArray *(*get_terminals) (LdSymbol *self);
+ void (*draw) (LdSymbol *self, cairo_t *cr);
+};
+
+
+GType ld_symbol_get_type (void) G_GNUC_CONST;
+
+const gchar *ld_symbol_get_name (LdSymbol *self);
+const gchar *ld_symbol_get_human_name (LdSymbol *self);
+void ld_symbol_get_area (LdSymbol *self, LdRectangle *area);
+const LdPointArray *ld_symbol_get_terminals (LdSymbol *self);
+void ld_symbol_draw (LdSymbol *self, cairo_t *cr);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_SYMBOL_H__ */
+
diff --git a/liblogdiag/ld-types.c b/liblogdiag/ld-types.c
new file mode 100644
index 0000000..cde3da6
--- /dev/null
+++ b/liblogdiag/ld-types.c
@@ -0,0 +1,221 @@
+/*
+ * ld-types.c
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#include <math.h>
+
+#include "liblogdiag.h"
+#include "config.h"
+
+
+#define DEFINE_BOXED_TYPE(TypeName, type_name) \
+GType \
+type_name ## _get_type (void) \
+{ \
+ static GType our_type = 0; \
+ if (our_type == 0) \
+ our_type = g_boxed_type_register_static \
+ (g_intern_static_string (#TypeName), \
+ (GBoxedCopyFunc) type_name ## _copy, \
+ (GBoxedFreeFunc) type_name ## _free); \
+ return our_type; \
+}
+
+DEFINE_BOXED_TYPE (LdPoint, ld_point)
+DEFINE_BOXED_TYPE (LdPointArray, ld_point_array)
+DEFINE_BOXED_TYPE (LdRectangle, ld_rectangle)
+
+#define DEFINE_BOXED_TRIVIAL_COPY(TypeName, type_name) \
+TypeName * \
+type_name ## _copy (const TypeName *self) \
+{ \
+ TypeName *new_copy; \
+ g_return_val_if_fail (self != NULL, NULL); \
+ new_copy = g_slice_new (TypeName); \
+ *new_copy = *self; \
+ return new_copy; \
+}
+
+#define DEFINE_BOXED_TRIVIAL_FREE(TypeName, type_name) \
+void \
+type_name ## _free (TypeName *self) \
+{ \
+ g_return_if_fail (self != NULL); \
+ g_slice_free (TypeName, self); \
+}
+
+/**
+ * ld_point_copy:
+ * @self: An #LdPoint structure.
+ *
+ * Makes a copy of the structure.
+ * The result must be freed by ld_point_free().
+ *
+ * Return value: A copy of @self.
+ */
+DEFINE_BOXED_TRIVIAL_COPY (LdPoint, ld_point)
+
+/**
+ * ld_point_free:
+ * @self: An #LdPoint structure.
+ *
+ * Frees the structure created with ld_point_copy().
+ */
+DEFINE_BOXED_TRIVIAL_FREE (LdPoint, ld_point)
+
+/**
+ * ld_point_distance:
+ * @self: An #LdPoint structure.
+ * @x: The X coordinate of the second point.
+ * @y: The Y coordinate of the second point.
+ *
+ * Compute the distance between two points.
+ */
+gdouble
+ld_point_distance (LdPoint *self, gdouble x, gdouble y)
+{
+ gdouble dx, dy;
+
+ g_return_val_if_fail (self != NULL, -1);
+
+ dx = self->x - x;
+ dy = self->y - y;
+ return sqrt (dx * dx + dy * dy);
+}
+
+/**
+ * ld_point_array_new:
+ * @num_points: The number of points the array can store.
+ *
+ * Create a new array of points and initialize.
+ *
+ * Return value: An #LdPointArray structure.
+ */
+LdPointArray *
+ld_point_array_new (gint num_points)
+{
+ LdPointArray *new_array;
+
+ g_return_val_if_fail (num_points >= 1, NULL);
+
+ new_array = g_slice_new (LdPointArray);
+ new_array->num_points = num_points;
+ new_array->points = g_malloc0 (num_points * sizeof (LdPoint));
+ return new_array;
+}
+
+/**
+ * ld_point_array_copy:
+ * @self: An #LdPointArray structure.
+ *
+ * Makes a copy of the structure.
+ * The result must be freed by ld_point_array_free().
+ *
+ * Return value: A copy of @self.
+ */
+LdPointArray *
+ld_point_array_copy (const LdPointArray *self)
+{
+ LdPointArray *new_array;
+
+ g_return_val_if_fail (self != NULL, NULL);
+
+ new_array = g_slice_new (LdPointArray);
+ new_array->num_points = self->num_points;
+ new_array->points = g_memdup (self->points,
+ self->num_points * sizeof (LdPoint));
+ return new_array;
+}
+
+/**
+ * ld_point_array_free:
+ * @self: An #LdPointArray structure.
+ *
+ * Frees the structure created with ld_point_array_copy().
+ */
+void
+ld_point_array_free (LdPointArray *self)
+{
+ g_return_if_fail (self != NULL);
+
+ g_free (self->points);
+ g_slice_free (LdPointArray, self);
+}
+
+/**
+ * ld_rectangle_copy:
+ * @self: An #LdRectangle structure.
+ *
+ * Makes a copy of the structure.
+ * The result must be freed by ld_rectangle_free().
+ *
+ * Return value: A copy of @self.
+ */
+DEFINE_BOXED_TRIVIAL_COPY (LdRectangle, ld_rectangle)
+
+/**
+ * ld_rectangle_free:
+ * @self: An #LdRectangle structure.
+ *
+ * Frees the structure created with ld_rectangle_copy().
+ */
+DEFINE_BOXED_TRIVIAL_FREE (LdRectangle, ld_rectangle)
+
+/**
+ * ld_rectangle_contains:
+ * @self: An #LdRectangle structure.
+ * @x: The X coordinate of the point to be checked.
+ * @y: The Y coordinate of the point to be checked.
+ *
+ * Return value: TRUE if the rectangle contains the specified point.
+ */
+gboolean
+ld_rectangle_contains (LdRectangle *self, gdouble x, gdouble y)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ return (x >= self->x && x <= self->x + self->width
+ && y >= self->y && y <= self->y + self->height);
+}
+
+/**
+ * ld_rectangle_intersects:
+ * @self: An #LdRectangle structure.
+ * @rect: An #LdRectangle to be checked for intersection.
+ *
+ * Return value: TRUE if the two rectangles intersect.
+ */
+gboolean
+ld_rectangle_intersects (LdRectangle *self, LdRectangle *rect)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (rect != NULL, FALSE);
+
+ return !(self->x > rect->x + rect->width
+ || self->y > rect->y + rect->height
+ || self->x + self->width < rect->x
+ || self->y + self->height < rect->y);
+}
+
+/**
+ * ld_rectangle_extend:
+ * @self: An #LdRectangle structure.
+ * @border: The border by which the rectangle should be extended.
+ *
+ * Extend a rectangle on all sides.
+ */
+void
+ld_rectangle_extend (LdRectangle *self, gdouble border)
+{
+ g_return_if_fail (self != NULL);
+
+ self->x -= border;
+ self->y -= border;
+ self->width += 2 * border;
+ self->height += 2 * border;
+}
diff --git a/liblogdiag/ld-types.h b/liblogdiag/ld-types.h
new file mode 100644
index 0000000..61a1a7d
--- /dev/null
+++ b/liblogdiag/ld-types.h
@@ -0,0 +1,101 @@
+/*
+ * ld-types.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LD_TYPES_H__
+#define __LD_TYPES_H__
+
+G_BEGIN_DECLS
+
+
+/**
+ * SECTION:ld-types
+ * @short_description: Simple data types.
+ *
+ * #LdPoint defines coordinates of a point.
+ *
+ * #LdRectangle defines the position and size of a rectangle.
+ */
+
+#define LD_TYPE_POINT (ld_point_get_type ())
+#define LD_TYPE_POINT_ARRAY (ld_point_array_get_type ())
+#define LD_TYPE_RECTANGLE (ld_rectangle_get_type ())
+
+typedef struct _LdPoint LdPoint;
+typedef struct _LdPointArray LdPointArray;
+typedef struct _LdRectangle LdRectangle;
+
+
+/**
+ * LdPoint:
+ * @x: The X coordinate.
+ * @y: The Y coordinate.
+ *
+ * Defines a point.
+ */
+struct _LdPoint
+{
+ gdouble x, y;
+};
+
+GType ld_point_get_type (void) G_GNUC_CONST;
+
+LdPoint *ld_point_copy (const LdPoint *self);
+void ld_point_free (LdPoint *self);
+gdouble ld_point_distance (LdPoint *self, gdouble x, gdouble y);
+
+
+/**
+ * LdPointArray:
+ * @points: An array of #LdPoint structures.
+ * @num_points: Count of points in @points.
+ *
+ * Moves quickly.
+ */
+struct _LdPointArray
+{
+ LdPoint *points;
+ gint num_points;
+};
+
+GType ld_point_array_get_type (void) G_GNUC_CONST;
+
+LdPointArray *ld_point_array_new (gint num_points);
+LdPointArray *ld_point_array_copy (const LdPointArray *self);
+void ld_point_array_free (LdPointArray *self);
+
+
+/**
+ * LdRectangle:
+ * @x: Left-top X coordinate.
+ * @y: Left-top Y coordinate.
+ * @width: Width of the area, must be positive.
+ * @height: Height of the area, must be positive.
+ *
+ * Defines a rectangle.
+ */
+struct _LdRectangle
+{
+ gdouble x, y;
+ gdouble width, height;
+};
+
+GType ld_rectangle_get_type (void) G_GNUC_CONST;
+
+LdRectangle *ld_rectangle_copy (const LdRectangle *self);
+void ld_rectangle_free (LdRectangle *self);
+gboolean ld_rectangle_contains (LdRectangle *self, gdouble x, gdouble y);
+gboolean ld_rectangle_intersects (LdRectangle *self, LdRectangle *rect);
+void ld_rectangle_extend (LdRectangle *self, gdouble border);
+
+
+G_END_DECLS
+
+#endif /* ! __LD_TYPES_H__ */
+
diff --git a/liblogdiag/liblogdiag.h b/liblogdiag/liblogdiag.h
new file mode 100644
index 0000000..800826f
--- /dev/null
+++ b/liblogdiag/liblogdiag.h
@@ -0,0 +1,34 @@
+/*
+ * liblogdiag.h
+ *
+ * This file is a part of logdiag.
+ * Copyright Přemysl Janouch 2011. All rights reserved.
+ *
+ * See the file LICENSE for licensing information.
+ *
+ */
+
+#ifndef __LIBLOGDIAG_H__
+#define __LIBLOGDIAG_H__
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "ld-marshal.h"
+#include "ld-types.h"
+
+#include "ld-symbol.h"
+#include "ld-symbol-category.h"
+#include "ld-library.h"
+
+#include "ld-diagram-object.h"
+#include "ld-diagram-symbol.h"
+#include "ld-diagram.h"
+
+#include "ld-canvas.h"
+
+#include "ld-lua.h"
+#include "ld-lua-symbol.h"
+
+#endif /* ! __LIBLOGDIAG_H__ */
+