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 "canvas-grid.h"
13 #include "display-forward.h"
14 #include <libnr/nr-pixops.h>
15 #include "desktop-handles.h"
16 #include "helper/units.h"
17 #include "svg/svg-color.h"
18 #include "xml/node-event-vector.h"
19 #include "sp-object.h"
21 #include "sp-namedview.h"
22 #include "inkscape.h"
23 #include "desktop.h"
24 #include "display/canvas-grid.h"
25 #include "display/canvas-axonomgrid.h"
26 #include "../document.h"
30 namespace Inkscape {
32 static void grid_canvasitem_class_init (GridCanvasItemClass *klass);
33 static void grid_canvasitem_init (GridCanvasItem *grid);
34 static void grid_canvasitem_destroy (GtkObject *object);
36 static void grid_canvasitem_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
37 static void grid_canvasitem_render (SPCanvasItem *item, SPCanvasBuf *buf);
39 static SPCanvasItemClass * parent_class;
41 GtkType
42 grid_canvasitem_get_type (void)
43 {
44 static GtkType grid_canvasitem_type = 0;
46 if (!grid_canvasitem_type) {
47 GtkTypeInfo grid_canvasitem_info = {
48 "GridCanvasItem",
49 sizeof (GridCanvasItem),
50 sizeof (GridCanvasItemClass),
51 (GtkClassInitFunc) grid_canvasitem_class_init,
52 (GtkObjectInitFunc) grid_canvasitem_init,
53 NULL, NULL,
54 (GtkClassInitFunc) NULL
55 };
56 grid_canvasitem_type = gtk_type_unique (sp_canvas_item_get_type (), &grid_canvasitem_info);
57 }
58 return grid_canvasitem_type;
59 }
61 static void
62 grid_canvasitem_class_init (GridCanvasItemClass *klass)
63 {
64 GtkObjectClass *object_class;
65 SPCanvasItemClass *item_class;
67 object_class = (GtkObjectClass *) klass;
68 item_class = (SPCanvasItemClass *) klass;
70 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
72 object_class->destroy = grid_canvasitem_destroy;
74 item_class->update = grid_canvasitem_update;
75 item_class->render = grid_canvasitem_render;
76 }
78 static void
79 grid_canvasitem_init (GridCanvasItem *griditem)
80 {
81 griditem->grid = NULL;
82 }
84 static void
85 grid_canvasitem_destroy (GtkObject *object)
86 {
87 g_return_if_fail (object != NULL);
88 g_return_if_fail (INKSCAPE_IS_GRID_CANVASITEM (object));
90 if (GTK_OBJECT_CLASS (parent_class)->destroy)
91 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
92 }
94 /**
95 */
96 static void
97 grid_canvasitem_render (SPCanvasItem * item, SPCanvasBuf * buf)
98 {
99 GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
101 sp_canvas_prepare_buffer (buf);
103 if (gridcanvasitem->grid) gridcanvasitem->grid->Render(buf);
104 }
106 static void
107 grid_canvasitem_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
108 {
109 GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
111 if (parent_class->update)
112 (* parent_class->update) (item, affine, flags);
114 if (gridcanvasitem->grid) {
115 gridcanvasitem->grid->Update(affine, flags);
117 sp_canvas_request_redraw (item->canvas,
118 -1000000, -1000000,
119 1000000, 1000000);
121 item->x1 = item->y1 = -1000000;
122 item->x2 = item->y2 = 1000000;
123 }
124 }
128 // ##########################################################
129 // CanvasGrid
131 static Inkscape::XML::NodeEventVector const _repr_events = {
132 NULL, /* child_added */
133 NULL, /* child_removed */
134 CanvasGrid::on_repr_attr_changed,
135 NULL, /* content_changed */
136 NULL /* order_changed */
137 };
139 CanvasGrid::CanvasGrid(SPDesktop *desktop, Inkscape::XML::Node * in_repr)
140 {
141 //create canvasitem
142 // FIXME: probably this creation has to be done on demand. I think for multiple desktops it is best if each has their own canvasitem, but share the same CanvasGrid object.
143 canvasitem = INKSCAPE_GRID_CANVASITEM( sp_canvas_item_new(sp_desktop_grid(desktop), INKSCAPE_TYPE_GRID_CANVASITEM, NULL) );
144 gtk_object_ref(GTK_OBJECT(canvasitem)); // since we're keeping a copy, we need to bump up the ref count
145 canvasitem->grid = this;
147 enabled = false;
148 visible = false;
150 // sp_canvas_item_hide(canvasitem);
152 repr = in_repr;
153 if (repr) {
154 repr->addListener (&_repr_events, this);
155 }
157 namedview = sp_desktop_namedview(desktop);
158 }
160 CanvasGrid::~CanvasGrid()
161 {
162 if (repr) {
163 repr->removeListenerByData (this);
164 }
166 sp_canvas_item_hide(canvasitem);
167 // deref canvasitem
168 gtk_object_unref(GTK_OBJECT(canvasitem));
169 g_free(canvasitem);
170 g_message("~CanvasGrid");
171 }
173 /*
174 * writes an <inkscape:grid> child to repr.
175 */
176 void
177 CanvasGrid::writeNewGridToRepr(Inkscape::XML::Node * repr, const char * gridtype)
178 {
179 if (!repr) return;
180 if (!gridtype) return;
182 // 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.
184 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sp_desktop_document(SP_ACTIVE_DESKTOP));
185 Inkscape::XML::Node *newnode;
186 newnode = xml_doc->createElement("inkscape:grid");
187 if (!strcmp(gridtype,"xygrid")) {
188 newnode->setAttribute("type","xygrid");
189 }
191 repr->appendChild(newnode);
193 // FIXME: add this to history?
194 // sp_document_done(current_document, SP_VERB_DIALOG_XML_EDITOR,
195 // _("Create new element node"));
196 }
198 /*
199 * Creates a new CanvasGrid object of type gridtype
200 */
201 CanvasGrid*
202 CanvasGrid::NewGrid(SPDesktop *desktop, Inkscape::XML::Node * in_repr, const char * gridtype)
203 {
204 if (!desktop) return NULL;
205 if (!in_repr) return NULL;
206 if (!gridtype) return NULL;
208 if (!strcmp(gridtype,"xygrid")) {
209 return (CanvasGrid*) new CanvasXYGrid(desktop, in_repr);
210 }
212 return NULL;
213 }
216 void
217 CanvasGrid::hide()
218 {
219 sp_canvas_item_hide(canvasitem);
220 visible = false;
221 }
223 void
224 CanvasGrid::show()
225 {
226 sp_canvas_item_show(canvasitem);
227 visible = true;
228 }
230 void
231 CanvasGrid::on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data)
232 {
233 if (!data)
234 return;
236 ((CanvasGrid*) data)->onReprAttrChanged(repr, key, oldval, newval, is_interactive);
237 }
241 // ##########################################################
242 // CanvasXYGrid
244 static void grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba);
245 static void grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba);
248 /**
249 * A DIRECT COPY-PASTE FROM DOCUMENT-PROPERTIES.CPP TO QUICKLY GET RESULTS
250 *
251 * Helper function that attachs widgets in a 3xn table. The widgets come in an
252 * array that has two entries per table row. The two entries code for four
253 * possible cases: (0,0) means insert space in first column; (0, non-0) means
254 * widget in columns 2-3; (non-0, 0) means label in columns 1-3; and
255 * (non-0, non-0) means two widgets in columns 2 and 3.
256 **/
257 #define SPACE_SIZE_X 15
258 #define SPACE_SIZE_Y 10
259 static inline void
260 attach_all (Gtk::Table &table, const Gtk::Widget *arr[], unsigned size, int start = 0)
261 {
262 for (unsigned i=0, r=start; i<size/sizeof(Gtk::Widget*); i+=2)
263 {
264 if (arr[i] && arr[i+1])
265 {
266 table.attach (const_cast<Gtk::Widget&>(*arr[i]), 1, 2, r, r+1,
267 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
268 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 2, 3, r, r+1,
269 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
270 }
271 else
272 {
273 if (arr[i+1])
274 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 1, 3, r, r+1,
275 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
276 else if (arr[i])
277 {
278 Gtk::Label& label = reinterpret_cast<Gtk::Label&> (const_cast<Gtk::Widget&>(*arr[i]));
279 label.set_alignment (0.0);
280 table.attach (label, 0, 3, r, r+1,
281 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
282 }
283 else
284 {
285 Gtk::HBox *space = manage (new Gtk::HBox);
286 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
287 table.attach (*space, 0, 1, r, r+1,
288 (Gtk::AttachOptions)0, (Gtk::AttachOptions)0,0,0);
289 }
290 }
291 ++r;
292 }
293 }
295 CanvasXYGrid::CanvasXYGrid (SPDesktop *desktop, Inkscape::XML::Node * in_repr)
296 : CanvasGrid(desktop, in_repr), table(1, 1)
297 {
298 origin[NR::X] = origin[NR::Y] = 0.0;
299 // nv->gridcolor = (nv->gridcolor & 0xff) | (DEFAULTGRIDCOLOR & 0xffffff00);
300 // case SP_ATTR_GRIDOPACITY:
301 // nv->gridcolor = (nv->gridcolor & 0xffffff00) | (DEFAULTGRIDCOLOR & 0xff);
302 color = 0xff3f3f20;
303 empcolor = 0xFF3F3F40;
304 empspacing = 5;
305 spacing[NR::X] = spacing[NR::Y] = 8.0;
306 gridunit = &sp_unit_get_by_id(SP_UNIT_PX);
308 snapper = new CanvasXYGridSnapper(this, namedview, 0);
310 // initialize widgets:
311 vbox.set_border_width(2);
312 table.set_spacings(2);
313 vbox.pack_start(table, false, false, 0);
315 _rumg.init (_("Grid _units:"), "units", _wr, repr);
316 _rsu_ox.init (_("_Origin X:"), _("X coordinate of grid origin"),
317 "originx", _rumg, _wr, repr);
318 _rsu_oy.init (_("O_rigin Y:"), _("Y coordinate of grid origin"),
319 "originy", _rumg, _wr, repr);
320 _rsu_sx.init (_("Spacing _X:"), _("Distance between vertical grid lines"),
321 "spacingx", _rumg, _wr, repr);
322 _rsu_sy.init (_("Spacing _Y:"), _("Distance between horizontal grid lines"),
323 "spacingy", _rumg, _wr, repr);
324 _rcp_gcol.init (_("Grid line _color:"), _("Grid line color"),
325 _("Color of grid lines"), "color", "opacity", _wr, repr);
326 _rcp_gmcol.init (_("Ma_jor grid line color:"), _("Major grid line color"),
327 _("Color of the major (highlighted) grid lines"),
328 "empcolor", "empopacity", _wr, repr);
329 _rsi.init (_("_Major grid line every:"), _("lines"), "empspacing", _wr, repr);
331 const Gtk::Widget* widget_array[] =
332 {
333 0, _rcbgrid._button,
334 0, _rrb_gridtype._hbox,
335 _rumg._label, _rumg._sel,
336 0, _rsu_ox.getSU(),
337 0, _rsu_oy.getSU(),
338 0, _rsu_sx.getSU(),
339 0, _rsu_sy.getSU(),
340 _rcp_gcol._label, _rcp_gcol._cp,
341 0, 0,
342 _rcp_gmcol._label, _rcp_gmcol._cp,
343 _rsi._label, &_rsi._hbox,
344 };
346 attach_all (table, widget_array, sizeof(widget_array));
348 vbox.show();
350 if (repr) readRepr();
351 updateWidgets();
352 }
354 CanvasXYGrid::~CanvasXYGrid ()
355 {
356 if (snapper) delete snapper;
357 }
360 /* fixme: Collect all these length parsing methods and think common sane API */
362 static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit)
363 {
364 if (!str) {
365 return FALSE;
366 }
368 gchar *u;
369 gdouble v = g_ascii_strtod(str, &u);
370 if (!u) {
371 return FALSE;
372 }
373 while (isspace(*u)) {
374 u += 1;
375 }
377 if (!*u) {
378 /* No unit specified - keep default */
379 *val = v;
380 return TRUE;
381 }
383 if (base & SP_UNIT_DEVICE) {
384 if (u[0] && u[1] && !isalnum(u[2]) && !strncmp(u, "px", 2)) {
385 *unit = &sp_unit_get_by_id(SP_UNIT_PX);
386 *val = v;
387 return TRUE;
388 }
389 }
391 if (base & SP_UNIT_ABSOLUTE) {
392 if (!strncmp(u, "pt", 2)) {
393 *unit = &sp_unit_get_by_id(SP_UNIT_PT);
394 } else if (!strncmp(u, "mm", 2)) {
395 *unit = &sp_unit_get_by_id(SP_UNIT_MM);
396 } else if (!strncmp(u, "cm", 2)) {
397 *unit = &sp_unit_get_by_id(SP_UNIT_CM);
398 } else if (!strncmp(u, "m", 1)) {
399 *unit = &sp_unit_get_by_id(SP_UNIT_M);
400 } else if (!strncmp(u, "in", 2)) {
401 *unit = &sp_unit_get_by_id(SP_UNIT_IN);
402 } else {
403 return FALSE;
404 }
405 *val = v;
406 return TRUE;
407 }
409 return FALSE;
410 }
412 static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color)
413 {
414 if (!str) {
415 return FALSE;
416 }
418 gchar *u;
419 gdouble v = g_ascii_strtod(str, &u);
420 if (!u) {
421 return FALSE;
422 }
423 v = CLAMP(v, 0.0, 1.0);
425 *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
427 return TRUE;
428 }
432 void
433 CanvasXYGrid::readRepr()
434 {
435 gchar const* value;
436 if ( (value = repr->attribute("originx")) ) {
437 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::X], &gridunit);
438 origin[NR::X] = sp_units_get_pixels(origin[NR::X], *(gridunit));
439 }
440 if ( (value = repr->attribute("originy")) ) {
441 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::Y], &gridunit);
442 origin[NR::Y] = sp_units_get_pixels(origin[NR::Y], *(gridunit));
443 }
445 if ( (value = repr->attribute("spacingx")) ) {
446 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::X], &gridunit);
447 spacing[NR::X] = sp_units_get_pixels(spacing[NR::X], *(gridunit));
448 }
449 if ( (value = repr->attribute("spacingy")) ) {
450 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::Y], &gridunit);
451 spacing[NR::Y] = sp_units_get_pixels(spacing[NR::Y], *(gridunit));
452 }
454 if ( (value = repr->attribute("color")) ) {
455 color = (color & 0xff) | sp_svg_read_color(value, color);
456 }
458 if ( (value = repr->attribute("empcolor")) ) {
459 empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
460 }
462 if ( (value = repr->attribute("opacity")) ) {
463 sp_nv_read_opacity(value, &color);
464 }
465 if ( (value = repr->attribute("empopacity")) ) {
466 sp_nv_read_opacity(value, &empcolor);
467 }
469 if ( (value = repr->attribute("empspacing")) ) {
470 empspacing = atoi(value);
471 }
473 sp_canvas_item_request_update (canvasitem);
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 :