1 #define INKSCAPE_CANVAS_GRID_C
3 /*
4 *
5 * Copyright (C) Johan Engelen 2006-2007 <johan@shouraizou.nl>
6 * Copyright (C) Lauris Kaplinski 2000
7 *
8 */
11 #include "sp-canvas-util.h"
12 #include "display-forward.h"
13 #include <libnr/nr-pixops.h>
14 #include "desktop-handles.h"
15 #include "helper/units.h"
16 #include "svg/svg-color.h"
17 #include "xml/node-event-vector.h"
18 #include "sp-object.h"
20 #include "sp-namedview.h"
21 #include "inkscape.h"
22 #include "desktop.h"
24 #include "../document.h"
26 #include "canvas-grid.h"
27 #include "canvas-axonomgrid.h"
29 namespace Inkscape {
31 static void grid_canvasitem_class_init (GridCanvasItemClass *klass);
32 static void grid_canvasitem_init (GridCanvasItem *grid);
33 static void grid_canvasitem_destroy (GtkObject *object);
35 static void grid_canvasitem_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
36 static void grid_canvasitem_render (SPCanvasItem *item, SPCanvasBuf *buf);
38 static SPCanvasItemClass * parent_class;
40 GtkType
41 grid_canvasitem_get_type (void)
42 {
43 static GtkType grid_canvasitem_type = 0;
45 if (!grid_canvasitem_type) {
46 GtkTypeInfo grid_canvasitem_info = {
47 "GridCanvasItem",
48 sizeof (GridCanvasItem),
49 sizeof (GridCanvasItemClass),
50 (GtkClassInitFunc) grid_canvasitem_class_init,
51 (GtkObjectInitFunc) grid_canvasitem_init,
52 NULL, NULL,
53 (GtkClassInitFunc) NULL
54 };
55 grid_canvasitem_type = gtk_type_unique (sp_canvas_item_get_type (), &grid_canvasitem_info);
56 }
57 return grid_canvasitem_type;
58 }
60 static void
61 grid_canvasitem_class_init (GridCanvasItemClass *klass)
62 {
63 GtkObjectClass *object_class;
64 SPCanvasItemClass *item_class;
66 object_class = (GtkObjectClass *) klass;
67 item_class = (SPCanvasItemClass *) klass;
69 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
71 object_class->destroy = grid_canvasitem_destroy;
73 item_class->update = grid_canvasitem_update;
74 item_class->render = grid_canvasitem_render;
75 }
77 static void
78 grid_canvasitem_init (GridCanvasItem *griditem)
79 {
80 griditem->grid = NULL;
81 }
83 static void
84 grid_canvasitem_destroy (GtkObject *object)
85 {
86 g_return_if_fail (object != NULL);
87 g_return_if_fail (INKSCAPE_IS_GRID_CANVASITEM (object));
89 if (GTK_OBJECT_CLASS (parent_class)->destroy)
90 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
91 }
93 /**
94 */
95 static void
96 grid_canvasitem_render (SPCanvasItem * item, SPCanvasBuf * buf)
97 {
98 GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
100 sp_canvas_prepare_buffer (buf);
102 if (gridcanvasitem->grid) gridcanvasitem->grid->Render(buf);
103 }
105 static void
106 grid_canvasitem_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
107 {
108 GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
110 if (parent_class->update)
111 (* parent_class->update) (item, affine, flags);
113 if (gridcanvasitem->grid) {
114 gridcanvasitem->grid->Update(affine, flags);
116 sp_canvas_request_redraw (item->canvas,
117 -1000000, -1000000,
118 1000000, 1000000);
120 item->x1 = item->y1 = -1000000;
121 item->x2 = item->y2 = 1000000;
122 }
123 }
127 // ##########################################################
128 // CanvasGrid
130 static Inkscape::XML::NodeEventVector const _repr_events = {
131 NULL, /* child_added */
132 NULL, /* child_removed */
133 CanvasGrid::on_repr_attr_changed,
134 NULL, /* content_changed */
135 NULL /* order_changed */
136 };
138 CanvasGrid::CanvasGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr)
139 {
140 repr = in_repr;
141 if (repr) {
142 repr->addListener (&_repr_events, this);
143 }
145 namedview = nv;
146 canvasitems = NULL;
147 }
149 CanvasGrid::~CanvasGrid()
150 {
151 if (repr) {
152 repr->removeListenerByData (this);
153 }
155 while (canvasitems) {
156 gtk_object_destroy(GTK_OBJECT(canvasitems->data));
157 canvasitems = g_slist_remove(canvasitems, canvasitems->data);
158 }
159 }
161 /*
162 * writes an <inkscape:grid> child to repr.
163 */
164 void
165 CanvasGrid::writeNewGridToRepr(Inkscape::XML::Node * repr, const char * gridtype)
166 {
167 if (!repr) return;
168 if (!gridtype) return;
170 // first create the child xml node, then hook it to repr. This order is important, to not set off listeners to repr before the new node is complete.
172 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sp_desktop_document(SP_ACTIVE_DESKTOP));
173 Inkscape::XML::Node *newnode;
174 newnode = xml_doc->createElement("inkscape:grid");
175 newnode->setAttribute("type",gridtype);
177 repr->appendChild(newnode);
179 // FIXME: add this to history?
180 // sp_document_done(current_document, SP_VERB_DIALOG_XML_EDITOR,
181 // _("Create new element node"));
182 }
184 /*
185 * Creates a new CanvasGrid object of type gridtype
186 */
187 CanvasGrid*
188 CanvasGrid::NewGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, const char * gridtype)
189 {
190 if (!in_repr) return NULL;
191 if (!gridtype) return NULL;
193 if (!strcmp(gridtype,"xygrid")) {
194 return (CanvasGrid*) new CanvasXYGrid(nv, in_repr);
195 } else if (!strcmp(gridtype,"axonometric")) {
196 return (CanvasGrid*) new CanvasAxonomGrid(nv, in_repr);
197 }
199 return NULL;
200 }
203 /**
204 * creates a new grid canvasitem for the SPDesktop given as parameter. Keeps a link to this canvasitem in the canvasitems list.
205 */
206 GridCanvasItem *
207 CanvasGrid::createCanvasItem(SPDesktop * desktop)
208 {
209 if (!desktop) return NULL;
210 //Johan: I think for multiple desktops it is best if each has their own canvasitem, but share the same CanvasGrid object; that is what this function is for.
212 // check if there is already a canvasitem on this desktop linking to this grid
213 for (GSList *l = canvasitems; l != NULL; l = l->next) {
214 if ( sp_desktop_gridgroup(desktop) == SP_CANVAS_GROUP(SP_CANVAS_ITEM(l->data)->parent) ) {
215 return NULL;
216 }
217 }
219 GridCanvasItem * item = INKSCAPE_GRID_CANVASITEM( sp_canvas_item_new(sp_desktop_gridgroup(desktop), INKSCAPE_TYPE_GRID_CANVASITEM, NULL) );
220 item->grid = this;
221 sp_canvas_item_show(SP_CANVAS_ITEM(item));
223 gtk_object_ref(GTK_OBJECT(item)); // since we're keeping a link to this item, we need to bump up the ref count
224 canvasitems = g_slist_prepend(canvasitems, item);
226 return item;
227 }
229 void
230 CanvasGrid::on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data)
231 {
232 if (!data)
233 return;
235 ((CanvasGrid*) data)->onReprAttrChanged(repr, key, oldval, newval, is_interactive);
236 }
240 // ##########################################################
241 // CanvasXYGrid
243 static void grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba);
244 static void grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba);
247 /**
248 * A DIRECT COPY-PASTE FROM DOCUMENT-PROPERTIES.CPP TO QUICKLY GET RESULTS
249 *
250 * Helper function that attachs widgets in a 3xn table. The widgets come in an
251 * array that has two entries per table row. The two entries code for four
252 * possible cases: (0,0) means insert space in first column; (0, non-0) means
253 * widget in columns 2-3; (non-0, 0) means label in columns 1-3; and
254 * (non-0, non-0) means two widgets in columns 2 and 3.
255 **/
256 #define SPACE_SIZE_X 15
257 #define SPACE_SIZE_Y 10
258 static inline void
259 attach_all (Gtk::Table &table, const Gtk::Widget *arr[], unsigned size, int start = 0)
260 {
261 for (unsigned i=0, r=start; i<size/sizeof(Gtk::Widget*); i+=2)
262 {
263 if (arr[i] && arr[i+1])
264 {
265 table.attach (const_cast<Gtk::Widget&>(*arr[i]), 1, 2, r, r+1,
266 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
267 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 2, 3, r, r+1,
268 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
269 }
270 else
271 {
272 if (arr[i+1])
273 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 1, 3, r, r+1,
274 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
275 else if (arr[i])
276 {
277 Gtk::Label& label = reinterpret_cast<Gtk::Label&> (const_cast<Gtk::Widget&>(*arr[i]));
278 label.set_alignment (0.0);
279 table.attach (label, 0, 3, r, r+1,
280 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
281 }
282 else
283 {
284 Gtk::HBox *space = manage (new Gtk::HBox);
285 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
286 table.attach (*space, 0, 1, r, r+1,
287 (Gtk::AttachOptions)0, (Gtk::AttachOptions)0,0,0);
288 }
289 }
290 ++r;
291 }
292 }
294 CanvasXYGrid::CanvasXYGrid (SPNamedView * nv, Inkscape::XML::Node * in_repr)
295 : CanvasGrid(nv, in_repr), table(1, 1)
296 {
297 origin[NR::X] = origin[NR::Y] = 0.0;
298 // nv->gridcolor = (nv->gridcolor & 0xff) | (DEFAULTGRIDCOLOR & 0xffffff00);
299 // case SP_ATTR_GRIDOPACITY:
300 // nv->gridcolor = (nv->gridcolor & 0xffffff00) | (DEFAULTGRIDCOLOR & 0xff);
301 color = 0xff3f3f20;
302 empcolor = 0xFF3F3F40;
303 empspacing = 5;
304 spacing[NR::X] = spacing[NR::Y] = 8.0;
305 gridunit = &sp_unit_get_by_id(SP_UNIT_PX);
307 snapper = new CanvasXYGridSnapper(this, namedview, 0);
309 // initialize widgets:
310 vbox.set_border_width(2);
311 table.set_spacings(2);
312 vbox.pack_start(table, false, false, 0);
314 _rumg.init (_("Grid _units:"), "units", _wr, repr);
315 _rsu_ox.init (_("_Origin X:"), _("X coordinate of grid origin"),
316 "originx", _rumg, _wr, repr);
317 _rsu_oy.init (_("O_rigin Y:"), _("Y coordinate of grid origin"),
318 "originy", _rumg, _wr, repr);
319 _rsu_sx.init (_("Spacing _X:"), _("Distance between vertical grid lines"),
320 "spacingx", _rumg, _wr, repr);
321 _rsu_sy.init (_("Spacing _Y:"), _("Distance between horizontal grid lines"),
322 "spacingy", _rumg, _wr, repr);
323 _rcp_gcol.init (_("Grid line _color:"), _("Grid line color"),
324 _("Color of grid lines"), "color", "opacity", _wr, repr);
325 _rcp_gmcol.init (_("Ma_jor grid line color:"), _("Major grid line color"),
326 _("Color of the major (highlighted) grid lines"),
327 "empcolor", "empopacity", _wr, repr);
328 _rsi.init (_("_Major grid line every:"), _("lines"), "empspacing", _wr, repr);
330 const Gtk::Widget* widget_array[] =
331 {
332 0, _rcbgrid._button,
333 _rumg._label, _rumg._sel,
334 0, _rsu_ox.getSU(),
335 0, _rsu_oy.getSU(),
336 0, _rsu_sx.getSU(),
337 0, _rsu_sy.getSU(),
338 _rcp_gcol._label, _rcp_gcol._cp,
339 0, 0,
340 _rcp_gmcol._label, _rcp_gmcol._cp,
341 _rsi._label, &_rsi._hbox,
342 };
344 attach_all (table, widget_array, sizeof(widget_array));
346 vbox.show();
348 if (repr) readRepr();
349 updateWidgets();
350 }
352 CanvasXYGrid::~CanvasXYGrid ()
353 {
354 if (snapper) delete snapper;
355 }
358 /* fixme: Collect all these length parsing methods and think common sane API */
360 static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit)
361 {
362 if (!str) {
363 return FALSE;
364 }
366 gchar *u;
367 gdouble v = g_ascii_strtod(str, &u);
368 if (!u) {
369 return FALSE;
370 }
371 while (isspace(*u)) {
372 u += 1;
373 }
375 if (!*u) {
376 /* No unit specified - keep default */
377 *val = v;
378 return TRUE;
379 }
381 if (base & SP_UNIT_DEVICE) {
382 if (u[0] && u[1] && !isalnum(u[2]) && !strncmp(u, "px", 2)) {
383 *unit = &sp_unit_get_by_id(SP_UNIT_PX);
384 *val = v;
385 return TRUE;
386 }
387 }
389 if (base & SP_UNIT_ABSOLUTE) {
390 if (!strncmp(u, "pt", 2)) {
391 *unit = &sp_unit_get_by_id(SP_UNIT_PT);
392 } else if (!strncmp(u, "mm", 2)) {
393 *unit = &sp_unit_get_by_id(SP_UNIT_MM);
394 } else if (!strncmp(u, "cm", 2)) {
395 *unit = &sp_unit_get_by_id(SP_UNIT_CM);
396 } else if (!strncmp(u, "m", 1)) {
397 *unit = &sp_unit_get_by_id(SP_UNIT_M);
398 } else if (!strncmp(u, "in", 2)) {
399 *unit = &sp_unit_get_by_id(SP_UNIT_IN);
400 } else {
401 return FALSE;
402 }
403 *val = v;
404 return TRUE;
405 }
407 return FALSE;
408 }
410 static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color)
411 {
412 if (!str) {
413 return FALSE;
414 }
416 gchar *u;
417 gdouble v = g_ascii_strtod(str, &u);
418 if (!u) {
419 return FALSE;
420 }
421 v = CLAMP(v, 0.0, 1.0);
423 *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
425 return TRUE;
426 }
430 void
431 CanvasXYGrid::readRepr()
432 {
433 gchar const* value;
434 if ( (value = repr->attribute("originx")) ) {
435 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::X], &gridunit);
436 origin[NR::X] = sp_units_get_pixels(origin[NR::X], *(gridunit));
437 }
438 if ( (value = repr->attribute("originy")) ) {
439 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::Y], &gridunit);
440 origin[NR::Y] = sp_units_get_pixels(origin[NR::Y], *(gridunit));
441 }
443 if ( (value = repr->attribute("spacingx")) ) {
444 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::X], &gridunit);
445 spacing[NR::X] = sp_units_get_pixels(spacing[NR::X], *(gridunit));
446 }
447 if ( (value = repr->attribute("spacingy")) ) {
448 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::Y], &gridunit);
449 spacing[NR::Y] = sp_units_get_pixels(spacing[NR::Y], *(gridunit));
450 }
452 if ( (value = repr->attribute("color")) ) {
453 color = (color & 0xff) | sp_svg_read_color(value, color);
454 }
456 if ( (value = repr->attribute("empcolor")) ) {
457 empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
458 }
460 if ( (value = repr->attribute("opacity")) ) {
461 sp_nv_read_opacity(value, &color);
462 }
463 if ( (value = repr->attribute("empopacity")) ) {
464 sp_nv_read_opacity(value, &empcolor);
465 }
467 if ( (value = repr->attribute("empspacing")) ) {
468 empspacing = atoi(value);
469 }
471 for (GSList *l = canvasitems; l != NULL; l = l->next) {
472 sp_canvas_item_request_update ( SP_CANVAS_ITEM(l->data) );
473 }
475 return;
476 }
478 /**
479 * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
480 */
481 void
482 CanvasXYGrid::onReprAttrChanged (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive)
483 {
484 readRepr();
486 if ( ! (_wr.isUpdating()) )
487 updateWidgets();
488 }
493 Gtk::Widget &
494 CanvasXYGrid::getWidget()
495 {
496 return vbox;
497 }
500 /**
501 * Update dialog widgets from object's values.
502 */
503 void
504 CanvasXYGrid::updateWidgets()
505 {
506 if (_wr.isUpdating()) return;
508 _wr.setUpdating (true);
510 // _rrb_gridtype.setValue (nv->gridtype);
511 _rumg.setUnit (gridunit);
513 gdouble val;
514 val = origin[NR::X];
515 val = sp_pixels_get_units (val, *(gridunit));
516 _rsu_ox.setValue (val);
517 val = origin[NR::Y];
518 val = sp_pixels_get_units (val, *(gridunit));
519 _rsu_oy.setValue (val);
520 val = spacing[NR::X];
521 double gridx = sp_pixels_get_units (val, *(gridunit));
522 _rsu_sx.setValue (gridx);
523 val = spacing[NR::Y];
524 double gridy = sp_pixels_get_units (val, *(gridunit));
525 _rsu_sy.setValue (gridy);
527 _rcp_gcol.setRgba32 (color);
528 _rcp_gmcol.setRgba32 (empcolor);
529 _rsi.setValue (empspacing);
531 _wr.setUpdating (false);
533 return;
534 }
538 void
539 CanvasXYGrid::Update (NR::Matrix const &affine, unsigned int flags)
540 {
541 ow = origin * affine;
542 sw = spacing * affine;
543 sw -= NR::Point(affine[4], affine[5]);
545 for(int dim = 0; dim < 2; dim++) {
546 gint scaling_factor = 5; //empspacing;
548 if (scaling_factor <= 1)
549 scaling_factor = 5;
551 scaled[dim] = FALSE;
552 sw[dim] = fabs (sw[dim]);
553 while (sw[dim] < 8.0) {
554 scaled[dim] = TRUE;
555 sw[dim] *= scaling_factor;
556 /* First pass, go up to the major line spacing, then
557 keep increasing by two. */
558 scaling_factor = 2;
559 }
560 }
561 }
563 void
564 CanvasXYGrid::Render (SPCanvasBuf *buf)
565 {
566 const gdouble sxg = floor ((buf->rect.x0 - ow[NR::X]) / sw[NR::X]) * sw[NR::X] + ow[NR::X];
567 const gint xlinestart = (gint) Inkscape::round((sxg - ow[NR::X]) / sw[NR::X]);
568 const gdouble syg = floor ((buf->rect.y0 - ow[NR::Y]) / sw[NR::Y]) * sw[NR::Y] + ow[NR::Y];
569 const gint ylinestart = (gint) Inkscape::round((syg - ow[NR::Y]) / sw[NR::Y]);
571 gint ylinenum;
572 gdouble y;
573 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += sw[NR::Y], ylinenum++) {
574 const gint y0 = (gint) Inkscape::round(y);
576 if (!scaled[NR::Y] && (ylinenum % 5 /*empspacing*/) == 0) {
577 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, empcolor);
578 } else {
579 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, color);
580 }
581 }
583 gint xlinenum;
584 gdouble x;
585 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += sw[NR::X], xlinenum++) {
586 const gint ix = (gint) Inkscape::round(x);
587 if (!scaled[NR::X] && (xlinenum % 5 /*empspacing*/) == 0) {
588 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, empcolor);
589 } else {
590 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, color);
591 }
592 }
593 }
606 /**
607 * \return x rounded to the nearest multiple of c1 plus c0.
608 *
609 * \note
610 * If c1==0 (and c0 is finite), then returns +/-inf. This makes grid spacing of zero
611 * mean "ignore the grid in this dimention". We're currently discussing "good" semantics
612 * for guide/grid snapping.
613 */
615 /* FIXME: move this somewhere else, perhaps */
616 static double round_to_nearest_multiple_plus(double x, double const c1, double const c0)
617 {
618 return floor((x - c0) / c1 + .5) * c1 + c0;
619 }
621 CanvasXYGridSnapper::CanvasXYGridSnapper(CanvasXYGrid *grid, SPNamedView const *nv, NR::Coord const d) : LineSnapper(nv, d)
622 {
623 this->grid = grid;
624 }
626 LineSnapper::LineList
627 CanvasXYGridSnapper::_getSnapLines(NR::Point const &p) const
628 {
629 LineList s;
631 if ( grid == NULL ) {
632 return s;
633 }
635 for (unsigned int i = 0; i < 2; ++i) {
637 /* This is to make sure we snap to only visible grid lines */
638 double scaled_spacing = grid->sw[i]; // this is spacing of visible lines if screen pixels
640 // convert screen pixels to px
641 // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
642 if (SP_ACTIVE_DESKTOP) {
643 scaled_spacing /= SP_ACTIVE_DESKTOP->current_zoom();
644 }
646 NR::Coord const rounded = round_to_nearest_multiple_plus(p[i],
647 scaled_spacing,
648 grid->origin[i]);
650 s.push_back(std::make_pair(NR::Dim2(i), rounded));
651 }
653 return s;
654 }
686 enum {
687 ARG_0,
688 ARG_ORIGINX,
689 ARG_ORIGINY,
690 ARG_SPACINGX,
691 ARG_SPACINGY,
692 ARG_COLOR,
693 ARG_EMPCOLOR,
694 ARG_EMPSPACING
695 };
698 static void cxygrid_class_init (CXYGridClass *klass);
699 static void cxygrid_init (CXYGrid *grid);
700 static void cxygrid_destroy (GtkObject *object);
701 static void cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
703 static void cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
704 static void cxygrid_render (SPCanvasItem *item, SPCanvasBuf *buf);
706 //static SPCanvasItemClass * parent_class;
708 GtkType
709 cxygrid_get_type (void)
710 {
711 static GtkType cxygrid_type = 0;
713 if (!cxygrid_type) {
714 GtkTypeInfo cxygrid_info = {
715 "CXYGrid",
716 sizeof (CXYGrid),
717 sizeof (CXYGridClass),
718 (GtkClassInitFunc) cxygrid_class_init,
719 (GtkObjectInitFunc) cxygrid_init,
720 NULL, NULL,
721 (GtkClassInitFunc) NULL
722 };
723 cxygrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cxygrid_info);
724 }
725 return cxygrid_type;
726 }
728 static void
729 cxygrid_class_init (CXYGridClass *klass)
730 {
731 GtkObjectClass *object_class;
732 SPCanvasItemClass *item_class;
734 object_class = (GtkObjectClass *) klass;
735 item_class = (SPCanvasItemClass *) klass;
737 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
739 gtk_object_add_arg_type ("CXYGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);
740 gtk_object_add_arg_type ("CXYGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);
741 gtk_object_add_arg_type ("CXYGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX);
742 gtk_object_add_arg_type ("CXYGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);
743 gtk_object_add_arg_type ("CXYGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);
744 gtk_object_add_arg_type ("CXYGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);
745 gtk_object_add_arg_type ("CXYGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);
747 object_class->destroy = cxygrid_destroy;
748 object_class->set_arg = cxygrid_set_arg;
750 item_class->update = cxygrid_update;
751 item_class->render = cxygrid_render;
752 }
754 static void
755 cxygrid_init (CXYGrid *grid)
756 {
757 grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;
758 grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0;
759 grid->color = 0x0000ff7f;
760 grid->empcolor = 0x3F3FFF40;
761 grid->empspacing = 5;
762 }
764 static void
765 cxygrid_destroy (GtkObject *object)
766 {
767 g_return_if_fail (object != NULL);
768 g_return_if_fail (INKSCAPE_IS_CXYGRID (object));
770 if (GTK_OBJECT_CLASS (parent_class)->destroy)
771 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
772 }
774 static void
775 cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
776 {
777 SPCanvasItem *item = SP_CANVAS_ITEM (object);
778 CXYGrid *grid = INKSCAPE_CXYGRID (object);
780 switch (arg_id) {
781 case ARG_ORIGINX:
782 grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);
783 sp_canvas_item_request_update (item);
784 break;
785 case ARG_ORIGINY:
786 grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);
787 sp_canvas_item_request_update (item);
788 break;
789 case ARG_SPACINGX:
790 grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg);
791 if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01;
792 sp_canvas_item_request_update (item);
793 break;
794 case ARG_SPACINGY:
795 grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg);
796 if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01;
797 sp_canvas_item_request_update (item);
798 break;
799 case ARG_COLOR:
800 grid->color = GTK_VALUE_INT (* arg);
801 sp_canvas_item_request_update (item);
802 break;
803 case ARG_EMPCOLOR:
804 grid->empcolor = GTK_VALUE_INT (* arg);
805 sp_canvas_item_request_update (item);
806 break;
807 case ARG_EMPSPACING:
808 grid->empspacing = GTK_VALUE_INT (* arg);
809 // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;
810 sp_canvas_item_request_update (item);
811 break;
812 default:
813 break;
814 }
815 }
817 static void
818 grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba)
819 {
820 if ((y >= buf->rect.y0) && (y < buf->rect.y1)) {
821 guint r, g, b, a;
822 gint x0, x1, x;
823 guchar *p;
824 r = NR_RGBA32_R (rgba);
825 g = NR_RGBA32_G (rgba);
826 b = NR_RGBA32_B (rgba);
827 a = NR_RGBA32_A (rgba);
828 x0 = MAX (buf->rect.x0, xs);
829 x1 = MIN (buf->rect.x1, xe + 1);
830 p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
831 for (x = x0; x < x1; x++) {
832 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
833 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
834 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
835 p += 3;
836 }
837 }
838 }
840 static void
841 grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)
842 {
843 if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {
844 guint r, g, b, a;
845 gint y0, y1, y;
846 guchar *p;
847 r = NR_RGBA32_R(rgba);
848 g = NR_RGBA32_G (rgba);
849 b = NR_RGBA32_B (rgba);
850 a = NR_RGBA32_A (rgba);
851 y0 = MAX (buf->rect.y0, ys);
852 y1 = MIN (buf->rect.y1, ye + 1);
853 p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
854 for (y = y0; y < y1; y++) {
855 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
856 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
857 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
858 p += buf->buf_rowstride;
859 }
860 }
861 }
863 /**
864 \brief This function renders the grid on a particular canvas buffer
865 \param item The grid to render on the buffer
866 \param buf The buffer to render the grid on
868 This function gets called a touch more than you might believe,
869 about once per tile. This means that it could probably be optimized
870 and help things out.
872 Basically this function has to determine where in the canvas it is,
873 and how that associates with the grid. It does this first by looking
874 at the bounding box of the buffer, and then calculates where the grid
875 starts in that buffer. It will then step through grid lines until
876 it is outside of the buffer.
878 For each grid line it is drawn using the function \c sp_grid_hline
879 or \c sp_grid_vline. These are convience functions for the sake
880 of making the function easier to read.
882 Also, there are emphisized lines on the grid. While the \c syg and
883 \c sxg variable track grid positioning, the \c xlinestart and \c
884 ylinestart variables track the 'count' of what lines they are. If
885 that count is a multiple of the line seperation between emphisis
886 lines, then that line is drawn in the emphisis color.
887 */
888 static void
889 cxygrid_render (SPCanvasItem * item, SPCanvasBuf * buf)
890 {
891 CXYGrid *grid = INKSCAPE_CXYGRID (item);
893 sp_canvas_prepare_buffer (buf);
895 const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X];
896 const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]);
897 const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y];
898 const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]);
900 gint ylinenum;
901 gdouble y;
902 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) {
903 const gint y0 = (gint) Inkscape::round(y);
905 if (!grid->scaled[NR::Y] && (ylinenum % grid->empspacing) == 0) {
906 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor);
907 } else {
908 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color);
909 }
910 }
912 gint xlinenum;
913 gdouble x;
914 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) {
915 const gint ix = (gint) Inkscape::round(x);
916 if (!grid->scaled[NR::X] && (xlinenum % grid->empspacing) == 0) {
917 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->empcolor);
918 } else {
919 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->color);
920 }
921 }
922 }
924 static void
925 cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
926 {
927 CXYGrid *grid = INKSCAPE_CXYGRID (item);
929 if (parent_class->update)
930 (* parent_class->update) (item, affine, flags);
932 grid->ow = grid->origin * affine;
933 grid->sw = grid->spacing * affine;
934 grid->sw -= NR::Point(affine[4], affine[5]);
936 for(int dim = 0; dim < 2; dim++) {
937 gint scaling_factor = grid->empspacing;
939 if (scaling_factor <= 1)
940 scaling_factor = 5;
942 grid->scaled[dim] = FALSE;
943 grid->sw[dim] = fabs (grid->sw[dim]);
944 while (grid->sw[dim] < 8.0) {
945 grid->scaled[dim] = TRUE;
946 grid->sw[dim] *= scaling_factor;
947 /* First pass, go up to the major line spacing, then
948 keep increasing by two. */
949 scaling_factor = 2;
950 }
951 }
953 if (grid->empspacing == 0) {
954 grid->scaled[NR::Y] = TRUE;
955 grid->scaled[NR::X] = TRUE;
956 }
958 sp_canvas_request_redraw (item->canvas,
959 -1000000, -1000000,
960 1000000, 1000000);
962 item->x1 = item->y1 = -1000000;
963 item->x2 = item->y2 = 1000000;
964 }
967 }; /* namespace Inkscape */
969 /*
970 Local Variables:
971 mode:c++
972 c-file-style:"stroustrup"
973 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
974 indent-tabs-mode:nil
975 fill-column:99
976 End:
977 */
978 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :