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 snapenabled = true;
141 visible = true;
143 repr = in_repr;
144 if (repr) {
145 repr->addListener (&_repr_events, this);
146 }
148 namedview = nv;
149 canvasitems = NULL;
150 }
152 CanvasGrid::~CanvasGrid()
153 {
154 if (repr) {
155 repr->removeListenerByData (this);
156 }
158 while (canvasitems) {
159 gtk_object_destroy(GTK_OBJECT(canvasitems->data));
160 canvasitems = g_slist_remove(canvasitems, canvasitems->data);
161 }
162 }
164 /*
165 * writes an <inkscape:grid> child to repr.
166 */
167 void
168 CanvasGrid::writeNewGridToRepr(Inkscape::XML::Node * repr, const char * gridtype)
169 {
170 if (!repr) return;
171 if (!gridtype) return;
173 // 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.
175 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sp_desktop_document(SP_ACTIVE_DESKTOP));
176 Inkscape::XML::Node *newnode;
177 newnode = xml_doc->createElement("inkscape:grid");
178 newnode->setAttribute("type",gridtype);
180 repr->appendChild(newnode);
182 // FIXME: add this to history?
183 // sp_document_done(current_document, SP_VERB_DIALOG_XML_EDITOR,
184 // _("Create new element node"));
185 }
187 /*
188 * Creates a new CanvasGrid object of type gridtype
189 */
190 CanvasGrid*
191 CanvasGrid::NewGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, const char * gridtype)
192 {
193 if (!in_repr) return NULL;
194 if (!gridtype) return NULL;
196 if (!strcmp(gridtype,"xygrid")) {
197 return (CanvasGrid*) new CanvasXYGrid(nv, in_repr);
198 } else if (!strcmp(gridtype,"axonometric")) {
199 return (CanvasGrid*) new CanvasAxonomGrid(nv, in_repr);
200 }
202 return NULL;
203 }
206 /**
207 * creates a new grid canvasitem for the SPDesktop given as parameter. Keeps a link to this canvasitem in the canvasitems list.
208 */
209 GridCanvasItem *
210 CanvasGrid::createCanvasItem(SPDesktop * desktop)
211 {
212 if (!desktop) return NULL;
213 //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.
215 // check if there is already a canvasitem on this desktop linking to this grid
216 for (GSList *l = canvasitems; l != NULL; l = l->next) {
217 if ( sp_desktop_gridgroup(desktop) == SP_CANVAS_GROUP(SP_CANVAS_ITEM(l->data)->parent) ) {
218 return NULL;
219 }
220 }
222 GridCanvasItem * item = INKSCAPE_GRID_CANVASITEM( sp_canvas_item_new(sp_desktop_gridgroup(desktop), INKSCAPE_TYPE_GRID_CANVASITEM, NULL) );
223 item->grid = this;
224 if (desktop->gridsEnabled()) {
225 sp_canvas_item_show(SP_CANVAS_ITEM(item));
226 } else {
227 sp_canvas_item_hide(SP_CANVAS_ITEM(item));
228 }
230 gtk_object_ref(GTK_OBJECT(item)); // since we're keeping a link to this item, we need to bump up the ref count
231 canvasitems = g_slist_prepend(canvasitems, item);
233 return item;
234 }
237 void
238 CanvasGrid::hide()
239 {
240 for (GSList *l = canvasitems; l != NULL; l = l->next) {
241 sp_canvas_item_hide ( SP_CANVAS_ITEM(l->data) );
242 }
243 visible = false;
244 disable_snapping(); // temporary hack, because at the moment visibilty and snapping are linked
245 }
247 void
248 CanvasGrid::show()
249 {
250 for (GSList *l = canvasitems; l != NULL; l = l->next) {
251 sp_canvas_item_show ( SP_CANVAS_ITEM(l->data) );
252 }
253 visible = true;
254 enable_snapping(); // temporary hack, because at the moment visibilty and snapping are linked
255 }
257 void
258 CanvasGrid::set_visibility(bool visible)
259 {
260 this->visible = visible;
261 if(visible) {
262 show();
263 } else {
264 hide();
265 }
266 }
268 void
269 CanvasGrid::toggle_visibility()
270 {
271 visible = !visible;
272 set_visibility(visible);
273 }
275 void
276 CanvasGrid::on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data)
277 {
278 if (!data)
279 return;
281 ((CanvasGrid*) data)->onReprAttrChanged(repr, key, oldval, newval, is_interactive);
282 }
286 // ##########################################################
287 // CanvasXYGrid
289 static void grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba);
290 static void grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba);
293 /**
294 * A DIRECT COPY-PASTE FROM DOCUMENT-PROPERTIES.CPP TO QUICKLY GET RESULTS
295 *
296 * Helper function that attachs widgets in a 3xn table. The widgets come in an
297 * array that has two entries per table row. The two entries code for four
298 * possible cases: (0,0) means insert space in first column; (0, non-0) means
299 * widget in columns 2-3; (non-0, 0) means label in columns 1-3; and
300 * (non-0, non-0) means two widgets in columns 2 and 3.
301 **/
302 #define SPACE_SIZE_X 15
303 #define SPACE_SIZE_Y 10
304 static inline void
305 attach_all (Gtk::Table &table, const Gtk::Widget *arr[], unsigned size, int start = 0)
306 {
307 for (unsigned i=0, r=start; i<size/sizeof(Gtk::Widget*); i+=2)
308 {
309 if (arr[i] && arr[i+1])
310 {
311 table.attach (const_cast<Gtk::Widget&>(*arr[i]), 1, 2, r, r+1,
312 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
313 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 2, 3, r, r+1,
314 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
315 }
316 else
317 {
318 if (arr[i+1])
319 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 1, 3, r, r+1,
320 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
321 else if (arr[i])
322 {
323 Gtk::Label& label = reinterpret_cast<Gtk::Label&> (const_cast<Gtk::Widget&>(*arr[i]));
324 label.set_alignment (0.0);
325 table.attach (label, 0, 3, r, r+1,
326 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
327 }
328 else
329 {
330 Gtk::HBox *space = manage (new Gtk::HBox);
331 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
332 table.attach (*space, 0, 1, r, r+1,
333 (Gtk::AttachOptions)0, (Gtk::AttachOptions)0,0,0);
334 }
335 }
336 ++r;
337 }
338 }
340 CanvasXYGrid::CanvasXYGrid (SPNamedView * nv, Inkscape::XML::Node * in_repr)
341 : CanvasGrid(nv, in_repr), table(1, 1)
342 {
343 origin[NR::X] = origin[NR::Y] = 0.0;
344 // nv->gridcolor = (nv->gridcolor & 0xff) | (DEFAULTGRIDCOLOR & 0xffffff00);
345 // case SP_ATTR_GRIDOPACITY:
346 // nv->gridcolor = (nv->gridcolor & 0xffffff00) | (DEFAULTGRIDCOLOR & 0xff);
347 color = 0xff3f3f20;
348 empcolor = 0xFF3F3F40;
349 empspacing = 5;
350 spacing[NR::X] = spacing[NR::Y] = 8.0;
351 gridunit = &sp_unit_get_by_id(SP_UNIT_PX);
353 snapper = new CanvasXYGridSnapper(this, namedview, 0);
355 // initialize widgets:
356 vbox.set_border_width(2);
357 table.set_spacings(2);
358 vbox.pack_start(table, false, false, 0);
360 _rumg.init (_("Grid _units:"), "units", _wr, repr);
361 _rsu_ox.init (_("_Origin X:"), _("X coordinate of grid origin"),
362 "originx", _rumg, _wr, repr);
363 _rsu_oy.init (_("O_rigin Y:"), _("Y coordinate of grid origin"),
364 "originy", _rumg, _wr, repr);
365 _rsu_sx.init (_("Spacing _X:"), _("Distance between vertical grid lines"),
366 "spacingx", _rumg, _wr, repr);
367 _rsu_sy.init (_("Spacing _Y:"), _("Distance between horizontal grid lines"),
368 "spacingy", _rumg, _wr, repr);
369 _rcp_gcol.init (_("Grid line _color:"), _("Grid line color"),
370 _("Color of grid lines"), "color", "opacity", _wr, repr);
371 _rcp_gmcol.init (_("Ma_jor grid line color:"), _("Major grid line color"),
372 _("Color of the major (highlighted) grid lines"),
373 "empcolor", "empopacity", _wr, repr);
374 _rsi.init (_("_Major grid line every:"), _("lines"), "empspacing", _wr, repr);
376 const Gtk::Widget* widget_array[] =
377 {
378 0, _rcbgrid._button,
379 _rumg._label, _rumg._sel,
380 0, _rsu_ox.getSU(),
381 0, _rsu_oy.getSU(),
382 0, _rsu_sx.getSU(),
383 0, _rsu_sy.getSU(),
384 _rcp_gcol._label, _rcp_gcol._cp,
385 0, 0,
386 _rcp_gmcol._label, _rcp_gmcol._cp,
387 _rsi._label, &_rsi._hbox,
388 };
390 attach_all (table, widget_array, sizeof(widget_array));
392 vbox.show();
394 if (repr) readRepr();
395 updateWidgets();
396 }
398 CanvasXYGrid::~CanvasXYGrid ()
399 {
400 if (snapper) delete snapper;
401 }
404 /* fixme: Collect all these length parsing methods and think common sane API */
406 static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit)
407 {
408 if (!str) {
409 return FALSE;
410 }
412 gchar *u;
413 gdouble v = g_ascii_strtod(str, &u);
414 if (!u) {
415 return FALSE;
416 }
417 while (isspace(*u)) {
418 u += 1;
419 }
421 if (!*u) {
422 /* No unit specified - keep default */
423 *val = v;
424 return TRUE;
425 }
427 if (base & SP_UNIT_DEVICE) {
428 if (u[0] && u[1] && !isalnum(u[2]) && !strncmp(u, "px", 2)) {
429 *unit = &sp_unit_get_by_id(SP_UNIT_PX);
430 *val = v;
431 return TRUE;
432 }
433 }
435 if (base & SP_UNIT_ABSOLUTE) {
436 if (!strncmp(u, "pt", 2)) {
437 *unit = &sp_unit_get_by_id(SP_UNIT_PT);
438 } else if (!strncmp(u, "mm", 2)) {
439 *unit = &sp_unit_get_by_id(SP_UNIT_MM);
440 } else if (!strncmp(u, "cm", 2)) {
441 *unit = &sp_unit_get_by_id(SP_UNIT_CM);
442 } else if (!strncmp(u, "m", 1)) {
443 *unit = &sp_unit_get_by_id(SP_UNIT_M);
444 } else if (!strncmp(u, "in", 2)) {
445 *unit = &sp_unit_get_by_id(SP_UNIT_IN);
446 } else {
447 return FALSE;
448 }
449 *val = v;
450 return TRUE;
451 }
453 return FALSE;
454 }
456 static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color)
457 {
458 if (!str) {
459 return FALSE;
460 }
462 gchar *u;
463 gdouble v = g_ascii_strtod(str, &u);
464 if (!u) {
465 return FALSE;
466 }
467 v = CLAMP(v, 0.0, 1.0);
469 *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
471 return TRUE;
472 }
476 void
477 CanvasXYGrid::readRepr()
478 {
479 gchar const* value;
480 if ( (value = repr->attribute("originx")) ) {
481 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::X], &gridunit);
482 origin[NR::X] = sp_units_get_pixels(origin[NR::X], *(gridunit));
483 }
484 if ( (value = repr->attribute("originy")) ) {
485 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::Y], &gridunit);
486 origin[NR::Y] = sp_units_get_pixels(origin[NR::Y], *(gridunit));
487 }
489 if ( (value = repr->attribute("spacingx")) ) {
490 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::X], &gridunit);
491 spacing[NR::X] = sp_units_get_pixels(spacing[NR::X], *(gridunit));
492 }
493 if ( (value = repr->attribute("spacingy")) ) {
494 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::Y], &gridunit);
495 spacing[NR::Y] = sp_units_get_pixels(spacing[NR::Y], *(gridunit));
496 }
498 if ( (value = repr->attribute("color")) ) {
499 color = (color & 0xff) | sp_svg_read_color(value, color);
500 }
502 if ( (value = repr->attribute("empcolor")) ) {
503 empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
504 }
506 if ( (value = repr->attribute("opacity")) ) {
507 sp_nv_read_opacity(value, &color);
508 }
509 if ( (value = repr->attribute("empopacity")) ) {
510 sp_nv_read_opacity(value, &empcolor);
511 }
513 if ( (value = repr->attribute("empspacing")) ) {
514 empspacing = atoi(value);
515 }
517 for (GSList *l = canvasitems; l != NULL; l = l->next) {
518 sp_canvas_item_request_update ( SP_CANVAS_ITEM(l->data) );
519 }
521 return;
522 }
524 /**
525 * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
526 */
527 void
528 CanvasXYGrid::onReprAttrChanged (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive)
529 {
530 readRepr();
532 if ( ! (_wr.isUpdating()) )
533 updateWidgets();
534 }
539 Gtk::Widget &
540 CanvasXYGrid::getWidget()
541 {
542 return vbox;
543 }
546 /**
547 * Update dialog widgets from object's values.
548 */
549 void
550 CanvasXYGrid::updateWidgets()
551 {
552 if (_wr.isUpdating()) return;
554 _wr.setUpdating (true);
556 // _rrb_gridtype.setValue (nv->gridtype);
557 _rumg.setUnit (gridunit);
559 gdouble val;
560 val = origin[NR::X];
561 val = sp_pixels_get_units (val, *(gridunit));
562 _rsu_ox.setValue (val);
563 val = origin[NR::Y];
564 val = sp_pixels_get_units (val, *(gridunit));
565 _rsu_oy.setValue (val);
566 val = spacing[NR::X];
567 double gridx = sp_pixels_get_units (val, *(gridunit));
568 _rsu_sx.setValue (gridx);
569 val = spacing[NR::Y];
570 double gridy = sp_pixels_get_units (val, *(gridunit));
571 _rsu_sy.setValue (gridy);
573 _rcp_gcol.setRgba32 (color);
574 _rcp_gmcol.setRgba32 (empcolor);
575 _rsi.setValue (empspacing);
577 _wr.setUpdating (false);
579 return;
580 }
584 void
585 CanvasXYGrid::Update (NR::Matrix const &affine, unsigned int flags)
586 {
587 ow = origin * affine;
588 sw = spacing * affine;
589 sw -= NR::Point(affine[4], affine[5]);
591 for(int dim = 0; dim < 2; dim++) {
592 gint scaling_factor = 5; //empspacing;
594 if (scaling_factor <= 1)
595 scaling_factor = 5;
597 scaled[dim] = FALSE;
598 sw[dim] = fabs (sw[dim]);
599 while (sw[dim] < 8.0) {
600 scaled[dim] = TRUE;
601 sw[dim] *= scaling_factor;
602 /* First pass, go up to the major line spacing, then
603 keep increasing by two. */
604 scaling_factor = 2;
605 }
606 }
607 }
609 void
610 CanvasXYGrid::Render (SPCanvasBuf *buf)
611 {
612 const gdouble sxg = floor ((buf->rect.x0 - ow[NR::X]) / sw[NR::X]) * sw[NR::X] + ow[NR::X];
613 const gint xlinestart = (gint) Inkscape::round((sxg - ow[NR::X]) / sw[NR::X]);
614 const gdouble syg = floor ((buf->rect.y0 - ow[NR::Y]) / sw[NR::Y]) * sw[NR::Y] + ow[NR::Y];
615 const gint ylinestart = (gint) Inkscape::round((syg - ow[NR::Y]) / sw[NR::Y]);
617 gint ylinenum;
618 gdouble y;
619 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += sw[NR::Y], ylinenum++) {
620 const gint y0 = (gint) Inkscape::round(y);
622 if (!scaled[NR::Y] && (ylinenum % 5 /*empspacing*/) == 0) {
623 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, empcolor);
624 } else {
625 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, color);
626 }
627 }
629 gint xlinenum;
630 gdouble x;
631 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += sw[NR::X], xlinenum++) {
632 const gint ix = (gint) Inkscape::round(x);
633 if (!scaled[NR::X] && (xlinenum % 5 /*empspacing*/) == 0) {
634 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, empcolor);
635 } else {
636 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, color);
637 }
638 }
639 }
652 /**
653 * \return x rounded to the nearest multiple of c1 plus c0.
654 *
655 * \note
656 * If c1==0 (and c0 is finite), then returns +/-inf. This makes grid spacing of zero
657 * mean "ignore the grid in this dimention". We're currently discussing "good" semantics
658 * for guide/grid snapping.
659 */
661 /* FIXME: move this somewhere else, perhaps */
662 static double round_to_nearest_multiple_plus(double x, double const c1, double const c0)
663 {
664 return floor((x - c0) / c1 + .5) * c1 + c0;
665 }
667 CanvasXYGridSnapper::CanvasXYGridSnapper(CanvasXYGrid *grid, SPNamedView const *nv, NR::Coord const d) : LineSnapper(nv, d)
668 {
669 this->grid = grid;
670 }
672 LineSnapper::LineList
673 CanvasXYGridSnapper::_getSnapLines(NR::Point const &p) const
674 {
675 LineList s;
677 if ( grid == NULL ) {
678 return s;
679 }
681 for (unsigned int i = 0; i < 2; ++i) {
683 /* This is to make sure we snap to only visible grid lines */
684 double scaled_spacing = grid->sw[i]; // this is spacing of visible lines if screen pixels
686 // convert screen pixels to px
687 // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
688 if (SP_ACTIVE_DESKTOP) {
689 scaled_spacing /= SP_ACTIVE_DESKTOP->current_zoom();
690 }
692 NR::Coord const rounded = round_to_nearest_multiple_plus(p[i],
693 scaled_spacing,
694 grid->origin[i]);
696 s.push_back(std::make_pair(NR::Dim2(i), rounded));
697 }
699 return s;
700 }
732 enum {
733 ARG_0,
734 ARG_ORIGINX,
735 ARG_ORIGINY,
736 ARG_SPACINGX,
737 ARG_SPACINGY,
738 ARG_COLOR,
739 ARG_EMPCOLOR,
740 ARG_EMPSPACING
741 };
744 static void cxygrid_class_init (CXYGridClass *klass);
745 static void cxygrid_init (CXYGrid *grid);
746 static void cxygrid_destroy (GtkObject *object);
747 static void cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
749 static void cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
750 static void cxygrid_render (SPCanvasItem *item, SPCanvasBuf *buf);
752 //static SPCanvasItemClass * parent_class;
754 GtkType
755 cxygrid_get_type (void)
756 {
757 static GtkType cxygrid_type = 0;
759 if (!cxygrid_type) {
760 GtkTypeInfo cxygrid_info = {
761 "CXYGrid",
762 sizeof (CXYGrid),
763 sizeof (CXYGridClass),
764 (GtkClassInitFunc) cxygrid_class_init,
765 (GtkObjectInitFunc) cxygrid_init,
766 NULL, NULL,
767 (GtkClassInitFunc) NULL
768 };
769 cxygrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cxygrid_info);
770 }
771 return cxygrid_type;
772 }
774 static void
775 cxygrid_class_init (CXYGridClass *klass)
776 {
777 GtkObjectClass *object_class;
778 SPCanvasItemClass *item_class;
780 object_class = (GtkObjectClass *) klass;
781 item_class = (SPCanvasItemClass *) klass;
783 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
785 gtk_object_add_arg_type ("CXYGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);
786 gtk_object_add_arg_type ("CXYGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);
787 gtk_object_add_arg_type ("CXYGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX);
788 gtk_object_add_arg_type ("CXYGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);
789 gtk_object_add_arg_type ("CXYGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);
790 gtk_object_add_arg_type ("CXYGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);
791 gtk_object_add_arg_type ("CXYGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);
793 object_class->destroy = cxygrid_destroy;
794 object_class->set_arg = cxygrid_set_arg;
796 item_class->update = cxygrid_update;
797 item_class->render = cxygrid_render;
798 }
800 static void
801 cxygrid_init (CXYGrid *grid)
802 {
803 grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;
804 grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0;
805 grid->color = 0x0000ff7f;
806 grid->empcolor = 0x3F3FFF40;
807 grid->empspacing = 5;
808 }
810 static void
811 cxygrid_destroy (GtkObject *object)
812 {
813 g_return_if_fail (object != NULL);
814 g_return_if_fail (INKSCAPE_IS_CXYGRID (object));
816 if (GTK_OBJECT_CLASS (parent_class)->destroy)
817 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
818 }
820 static void
821 cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
822 {
823 SPCanvasItem *item = SP_CANVAS_ITEM (object);
824 CXYGrid *grid = INKSCAPE_CXYGRID (object);
826 switch (arg_id) {
827 case ARG_ORIGINX:
828 grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);
829 sp_canvas_item_request_update (item);
830 break;
831 case ARG_ORIGINY:
832 grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);
833 sp_canvas_item_request_update (item);
834 break;
835 case ARG_SPACINGX:
836 grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg);
837 if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01;
838 sp_canvas_item_request_update (item);
839 break;
840 case ARG_SPACINGY:
841 grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg);
842 if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01;
843 sp_canvas_item_request_update (item);
844 break;
845 case ARG_COLOR:
846 grid->color = GTK_VALUE_INT (* arg);
847 sp_canvas_item_request_update (item);
848 break;
849 case ARG_EMPCOLOR:
850 grid->empcolor = GTK_VALUE_INT (* arg);
851 sp_canvas_item_request_update (item);
852 break;
853 case ARG_EMPSPACING:
854 grid->empspacing = GTK_VALUE_INT (* arg);
855 // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;
856 sp_canvas_item_request_update (item);
857 break;
858 default:
859 break;
860 }
861 }
863 static void
864 grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba)
865 {
866 if ((y >= buf->rect.y0) && (y < buf->rect.y1)) {
867 guint r, g, b, a;
868 gint x0, x1, x;
869 guchar *p;
870 r = NR_RGBA32_R (rgba);
871 g = NR_RGBA32_G (rgba);
872 b = NR_RGBA32_B (rgba);
873 a = NR_RGBA32_A (rgba);
874 x0 = MAX (buf->rect.x0, xs);
875 x1 = MIN (buf->rect.x1, xe + 1);
876 p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
877 for (x = x0; x < x1; x++) {
878 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
879 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
880 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
881 p += 3;
882 }
883 }
884 }
886 static void
887 grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)
888 {
889 if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {
890 guint r, g, b, a;
891 gint y0, y1, y;
892 guchar *p;
893 r = NR_RGBA32_R(rgba);
894 g = NR_RGBA32_G (rgba);
895 b = NR_RGBA32_B (rgba);
896 a = NR_RGBA32_A (rgba);
897 y0 = MAX (buf->rect.y0, ys);
898 y1 = MIN (buf->rect.y1, ye + 1);
899 p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
900 for (y = y0; y < y1; y++) {
901 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
902 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
903 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
904 p += buf->buf_rowstride;
905 }
906 }
907 }
909 /**
910 \brief This function renders the grid on a particular canvas buffer
911 \param item The grid to render on the buffer
912 \param buf The buffer to render the grid on
914 This function gets called a touch more than you might believe,
915 about once per tile. This means that it could probably be optimized
916 and help things out.
918 Basically this function has to determine where in the canvas it is,
919 and how that associates with the grid. It does this first by looking
920 at the bounding box of the buffer, and then calculates where the grid
921 starts in that buffer. It will then step through grid lines until
922 it is outside of the buffer.
924 For each grid line it is drawn using the function \c sp_grid_hline
925 or \c sp_grid_vline. These are convience functions for the sake
926 of making the function easier to read.
928 Also, there are emphisized lines on the grid. While the \c syg and
929 \c sxg variable track grid positioning, the \c xlinestart and \c
930 ylinestart variables track the 'count' of what lines they are. If
931 that count is a multiple of the line seperation between emphisis
932 lines, then that line is drawn in the emphisis color.
933 */
934 static void
935 cxygrid_render (SPCanvasItem * item, SPCanvasBuf * buf)
936 {
937 CXYGrid *grid = INKSCAPE_CXYGRID (item);
939 sp_canvas_prepare_buffer (buf);
941 const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X];
942 const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]);
943 const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y];
944 const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]);
946 gint ylinenum;
947 gdouble y;
948 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) {
949 const gint y0 = (gint) Inkscape::round(y);
951 if (!grid->scaled[NR::Y] && (ylinenum % grid->empspacing) == 0) {
952 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor);
953 } else {
954 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color);
955 }
956 }
958 gint xlinenum;
959 gdouble x;
960 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) {
961 const gint ix = (gint) Inkscape::round(x);
962 if (!grid->scaled[NR::X] && (xlinenum % grid->empspacing) == 0) {
963 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->empcolor);
964 } else {
965 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->color);
966 }
967 }
968 }
970 static void
971 cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
972 {
973 CXYGrid *grid = INKSCAPE_CXYGRID (item);
975 if (parent_class->update)
976 (* parent_class->update) (item, affine, flags);
978 grid->ow = grid->origin * affine;
979 grid->sw = grid->spacing * affine;
980 grid->sw -= NR::Point(affine[4], affine[5]);
982 for(int dim = 0; dim < 2; dim++) {
983 gint scaling_factor = grid->empspacing;
985 if (scaling_factor <= 1)
986 scaling_factor = 5;
988 grid->scaled[dim] = FALSE;
989 grid->sw[dim] = fabs (grid->sw[dim]);
990 while (grid->sw[dim] < 8.0) {
991 grid->scaled[dim] = TRUE;
992 grid->sw[dim] *= scaling_factor;
993 /* First pass, go up to the major line spacing, then
994 keep increasing by two. */
995 scaling_factor = 2;
996 }
997 }
999 if (grid->empspacing == 0) {
1000 grid->scaled[NR::Y] = TRUE;
1001 grid->scaled[NR::X] = TRUE;
1002 }
1004 sp_canvas_request_redraw (item->canvas,
1005 -1000000, -1000000,
1006 1000000, 1000000);
1008 item->x1 = item->y1 = -1000000;
1009 item->x2 = item->y2 = 1000000;
1010 }
1013 }; /* namespace Inkscape */
1015 /*
1016 Local Variables:
1017 mode:c++
1018 c-file-style:"stroustrup"
1019 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1020 indent-tabs-mode:nil
1021 fill-column:99
1022 End:
1023 */
1024 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :