From 3419680f2523414d4ac7349232eb92b627dd993a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Tue, 7 Dec 2010 00:59:05 +0100 Subject: [PATCH] Implement more of LdCanvas. 1. Add methods for coordinate translation between cairo/GtkWidget coordinates and LdDocument coordinates. 2. Draw the grid correctly (also make it significantly faster). 3. Place the canvas into a GtkScrolledWindow and register the set_scroll_adjustments signal in the GtkWidget base class. 4. Allow scrolling inside of an area of 200 x 200 units (for now). In the future, it should be possible to go to infinity. --- src/ld-canvas.c | 368 ++++++++++++++++++++++++++++++++++++------- src/ld-canvas.h | 19 ++- src/ld-window-main.c | 10 +- 3 files changed, 337 insertions(+), 60 deletions(-) diff --git a/src/ld-canvas.c b/src/ld-canvas.c index 3414ae6..7aed0c7 100644 --- a/src/ld-canvas.c +++ b/src/ld-canvas.c @@ -8,6 +8,7 @@ * */ +#include #include #include "config.h" @@ -28,14 +29,30 @@ * #LdCanvas is used for displaying #LdDocument objects. */ +/* Milimetres per inch. */ +#define MM_PER_INCH 25.4 + /* * LdCanvasPrivate: * @document: A document 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. */ struct _LdCanvasPrivate { LdDocument *document; LdLibrary *library; + + GtkAdjustment *adjustment_h; + GtkAdjustment *adjustment_v; + + gdouble x; + gdouble y; + gdouble zoom; }; G_DEFINE_TYPE (LdCanvas, ld_canvas, GTK_TYPE_DRAWING_AREA); @@ -47,26 +64,43 @@ enum PROP_LIBRARY }; -static void -ld_canvas_get_property (GObject *object, guint property_id, +typedef struct _DrawData DrawData; + +/* + * 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 document unit in pixels. + */ +struct _DrawData +{ + LdCanvas *self; + cairo_t *cr; + cairo_rectangle_t exposed_rect; + gdouble scale; +}; + +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, +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_finalize (GObject *gobject); +static void on_adjustment_value_changed + (GtkAdjustment *adjustment, LdCanvas *self); +static void ld_canvas_set_scroll_adjustments + (LdCanvas *self, GtkAdjustment *horizontal, GtkAdjustment *vertical); +static gdouble ld_canvas_get_base_unit_in_px (GtkWidget *self); +static gdouble ld_canvas_get_scale_in_px (LdCanvas *self); -static gboolean -on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data); - -static void -draw_grid (GtkWidget *widget, cairo_t *cr); - -static void -canvas_paint (GtkWidget *widget, cairo_t *cr); +static void on_size_allocate (GtkWidget *widget, GtkAllocation *allocation, + gpointer user_data); +static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, + gpointer user_data); +static void draw_grid (GtkWidget *widget, DrawData *data); +static void draw_document (GtkWidget *widget, DrawData *data); static void @@ -76,6 +110,8 @@ ld_canvas_class_init (LdCanvasClass *klass) GtkWidgetClass *widget_class; 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; @@ -101,16 +137,15 @@ ld_canvas_class_init (LdCanvasClass *klass) LD_TYPE_LIBRARY, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_DOCUMENT, pspec); - widget_class = GTK_WIDGET_CLASS (klass); + klass->set_scroll_adjustments = ld_canvas_set_scroll_adjustments; -/* TODO: Scrolling support; make the comment bellow a gtk-doc comment then. */ -/* +/** * LdCanvas::set-scroll-adjustments: - * @canvas: The canvas object. + * @horizontal: The horizontal #GtkAdjustment. + * @vertical: The vertical #GtkAdjustment. * - * Contents of the library have changed. + * 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, @@ -118,7 +153,7 @@ ld_canvas_class_init (LdCanvasClass *klass) NULL, NULL, g_cclosure_user_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); -*/ + g_type_class_add_private (klass, sizeof (LdCanvasPrivate)); } @@ -128,7 +163,14 @@ ld_canvas_init (LdCanvas *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, LD_TYPE_CANVAS, LdCanvasPrivate); - g_signal_connect (self, "expose-event", G_CALLBACK (on_expose_event), NULL); + self->priv->x = 0; + self->priv->y = 0; + self->priv->zoom = 1; + + g_signal_connect (self, "expose-event", + G_CALLBACK (on_expose_event), NULL); + g_signal_connect (self, "size-allocate", + G_CALLBACK (on_size_allocate), NULL); } static void @@ -138,6 +180,8 @@ ld_canvas_finalize (GObject *gobject) self = LD_CANVAS (gobject); + ld_canvas_set_scroll_adjustments (self, NULL, NULL); + if (self->priv->document) g_object_unref (self->priv->document); if (self->priv->library) @@ -187,6 +231,128 @@ ld_canvas_set_property (GObject *object, guint property_id, } } +static void +ld_canvas_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; + gdouble scale; + + self = LD_CANVAS (widget); + scale = ld_canvas_get_scale_in_px (self); + + /* 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 just so large that we must see more, + * let's disable the scrollbars in question. + */ + if (self->priv->adjustment_h) + { + self->priv->adjustment_h->page_size = 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 = 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); + } +} + /** * ld_canvas_new: * @@ -258,58 +424,154 @@ ld_canvas_get_library (LdCanvas *self) return self->priv->library; } +/* + * ld_canvas_get_base_unit_in_px: + * @self: A #GtkWidget object to retrieve DPI from (indirectly). + * + * Return value: The length of the base unit in pixels. + */ +static gdouble +ld_canvas_get_base_unit_in_px (GtkWidget *self) +{ + g_return_val_if_fail (GTK_IS_WIDGET (self), 1); + + /* XXX: It might look better if the unit was rounded to a whole number. */ + return gdk_screen_get_resolution (gtk_widget_get_screen (self)) + / MM_PER_INCH * LD_CANVAS_BASE_UNIT_LENGTH; +} + +/* + * ld_canvas_get_scale_in_px: + * @self: An #LdCanvas object. + * + * Return value: The 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_translate_canvas_coordinates: + * @self: An #LdCanvas object. + * @x: The X coordinate to be translated. + * @y: The Y coordinate to be translated. + * + * Translate coordinates located inside the canvas window + * into document coordinates. + */ +void +ld_canvas_translate_canvas_coordinates (LdCanvas *self, gdouble *x, gdouble *y) +{ + GtkWidget *widget; + gdouble scale; + + g_return_if_fail (LD_IS_CANVAS (self)); + + widget = GTK_WIDGET (self); + scale = ld_canvas_get_scale_in_px (self); + + /* We know document 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 length of the base unit in pixels + * times zoom of the canvas. + */ + *x = self->priv->x + (*x - (widget->allocation.width * 0.5)) / scale; + *y = self->priv->y + (*y - (widget->allocation.height * 0.5)) / scale; +} + +/** + * ld_canvas_translate_document_coordinates: + * @self: An #LdCanvas object. + * @x: The X coordinate to be translated. + * @y: The Y coordinate to be translated. + * + * Translate document coordinates into canvas coordinates. + */ +void +ld_canvas_translate_document_coordinates (LdCanvas *self, + gdouble *x, gdouble *y) +{ + GtkWidget *widget; + gdouble scale; + + g_return_if_fail (LD_IS_CANVAS (self)); + + widget = GTK_WIDGET (self); + scale = ld_canvas_get_scale_in_px (self); + + /* Just the reversal of ld_canvas_translate_canvas_coordinates(). */ + *x = scale * (*x - self->priv->x) + 0.5 * widget->allocation.width; + *y = scale * (*y - self->priv->y) + 0.5 * widget->allocation.height; +} + static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { - cairo_t *cr; + DrawData data; - cr = gdk_cairo_create (widget->window); + 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; - cairo_rectangle (cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_clip (cr); + cairo_rectangle (data.cr, data.exposed_rect.x, data.exposed_rect.y, + data.exposed_rect.width, data.exposed_rect.height); + cairo_clip (data.cr); - canvas_paint (widget, cr); + /* Paint the background white. */ + cairo_set_source_rgb (data.cr, 1, 1, 1); + cairo_paint (data.cr); - cairo_destroy (cr); + draw_grid (widget, &data); + draw_document (widget, &data); + cairo_destroy (data.cr); return FALSE; } static void -draw_grid (GtkWidget *widget, cairo_t *cr) +draw_grid (GtkWidget *widget, DrawData *data) { - int x, y; + gdouble x_top, y_top; + gdouble x, y; - /* Drawing points: - * http://lists.freedesktop.org/archives/cairo/2009-June/017459.html - */ - cairo_set_source_rgb (cr, 0.5, 0.5, 0.5); - cairo_set_line_width (cr, 1); - cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_source_rgb (data->cr, 0.5, 0.5, 0.5); + cairo_set_line_width (data->cr, 1); + cairo_set_line_cap (data->cr, CAIRO_LINE_CAP_ROUND); - /* FIXME: Invariable grid size; it's slow because it draws whole area. */ - for (x = 0; x < widget->allocation.width; x += 20) + /* Get coordinates of the most top-left grid point. */ + x_top = data->exposed_rect.x; + y_top = data->exposed_rect.y; + ld_canvas_translate_canvas_coordinates (data->self, &x_top, &y_top); + x_top = ceil (x_top); + y_top = ceil (y_top); + ld_canvas_translate_document_coordinates (data->self, &x_top, &y_top); + + /* Iterate over all the points. */ + for (x = x_top; x <= data->exposed_rect.x + data->exposed_rect.width; + x += data->scale) { - for (y = 0; y < widget->allocation.height; y += 20) + for (y = y_top; y <= data->exposed_rect.y + data->exposed_rect.height; + y += data->scale) { - /* Drawing sharp lines (also applies to these dots): - * http://www.cairographics.org/FAQ/#sharp_lines - */ - cairo_move_to (cr, x + 0.5, y + 0.5); - cairo_close_path (cr); - cairo_stroke (cr); + cairo_move_to (data->cr, x, y); + cairo_line_to (data->cr, x, y); } } + cairo_stroke (data->cr); } static void -canvas_paint (GtkWidget *widget, cairo_t *cr) +draw_document (GtkWidget *widget, DrawData *data) { - /* Paint a white background. */ - cairo_set_source_rgb (cr, 1, 1, 1); - cairo_paint (cr); - - draw_grid (widget, cr); + /* TODO: Draw symbols from the document. */ } diff --git a/src/ld-canvas.h b/src/ld-canvas.h index a31b2b7..037e727 100644 --- a/src/ld-canvas.h +++ b/src/ld-canvas.h @@ -48,22 +48,33 @@ struct _LdCanvasClass /*< private >*/ GtkDrawingAreaClass parent_class; -/* - void (*set_scroll_adjustments) (GtkAdjustment *x, GtkAdjustment *y); -*/ + void (*set_scroll_adjustments) (LdCanvas *self, + GtkAdjustment *horizontal, GtkAdjustment *vertical); }; +/** + * LD_CANVAS_BASE_UNIT: + * + * The 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_document (LdCanvas *self, LdDocument *document); LdDocument *ld_canvas_get_document (LdCanvas *self); - void ld_canvas_set_library (LdCanvas *self, LdLibrary *library); LdLibrary *ld_canvas_get_library (LdCanvas *self); +void ld_canvas_translate_canvas_coordinates (LdCanvas *self, + gdouble *x, gdouble *y); +void ld_canvas_translate_document_coordinates (LdCanvas *self, + gdouble *x, gdouble *y); + /* TODO: The rest of the interface. */ diff --git a/src/ld-window-main.c b/src/ld-window-main.c index bcacf3f..99910d8 100644 --- a/src/ld-window-main.c +++ b/src/ld-window-main.c @@ -97,6 +97,8 @@ struct _LdWindowMainPrivate GtkWidget *toolbar; LdLibrary *library; + + GtkWidget *canvas_window; LdCanvas *canvas; GtkWidget *statusbar; @@ -270,10 +272,9 @@ ld_window_main_init (LdWindowMain *self) /* TODO in the future: GtkHPaned */ /* Canvas. */ - /* TODO: Put it into a GtkScrolledWindow. */ priv->canvas = ld_canvas_new (); - /* TODO: To be able to draw a symbol menu over the canvas, we may: + /* XXX: To be able to draw a symbol menu over the canvas, we may: * 1. Hook the expose-event and button-{press,release}-event signals. * 2. Create a hook mechanism in the LdCanvas object. * + The cairo context would not have to be created twice. @@ -297,7 +298,10 @@ ld_window_main_init (LdWindowMain *self) g_signal_handler_block (priv->canvas, priv->symbol_menu.button_release_handler); - gtk_box_pack_start (GTK_BOX (priv->hbox), GTK_WIDGET (priv->canvas), + priv->canvas_window = gtk_scrolled_window_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (priv->canvas_window), + GTK_WIDGET (priv->canvas)); + gtk_box_pack_start (GTK_BOX (priv->hbox), GTK_WIDGET (priv->canvas_window), TRUE, TRUE, 0); priv->statusbar = gtk_statusbar_new ();