diff options
Diffstat (limited to 'src/ld-canvas.c')
-rw-r--r-- | src/ld-canvas.c | 1417 |
1 files changed, 0 insertions, 1417 deletions
diff --git a/src/ld-canvas.c b/src/ld-canvas.c deleted file mode 100644 index 9523d9d..0000000 --- a/src/ld-canvas.c +++ /dev/null @@ -1,1417 +0,0 @@ -/* - * 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); -} |