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(SPDesktop *desktop, Inkscape::XML::Node * in_repr)
139 {
140 //create canvasitem
141 // 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.
142 canvasitem = INKSCAPE_GRID_CANVASITEM( sp_canvas_item_new(sp_desktop_grid(desktop), INKSCAPE_TYPE_GRID_CANVASITEM, NULL) );
143 gtk_object_ref(GTK_OBJECT(canvasitem)); // since we're keeping a copy, we need to bump up the ref count
144 canvasitem->grid = this;
146 snapenabled = false;
147 visible = false;
149 // sp_canvas_item_hide(canvasitem);
151 repr = in_repr;
152 if (repr) {
153 repr->addListener (&_repr_events, this);
154 }
156 namedview = sp_desktop_namedview(desktop);
157 }
159 CanvasGrid::~CanvasGrid()
160 {
161 if (repr) {
162 repr->removeListenerByData (this);
163 }
165 sp_canvas_item_hide(canvasitem);
166 // deref canvasitem
167 gtk_object_unref(GTK_OBJECT(canvasitem));
168 g_free(canvasitem);
169 }
171 /*
172 * writes an <inkscape:grid> child to repr.
173 */
174 void
175 CanvasGrid::writeNewGridToRepr(Inkscape::XML::Node * repr, const char * gridtype)
176 {
177 if (!repr) return;
178 if (!gridtype) return;
180 // 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.
182 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sp_desktop_document(SP_ACTIVE_DESKTOP));
183 Inkscape::XML::Node *newnode;
184 newnode = xml_doc->createElement("inkscape:grid");
185 newnode->setAttribute("type",gridtype);
187 repr->appendChild(newnode);
189 // FIXME: add this to history?
190 // sp_document_done(current_document, SP_VERB_DIALOG_XML_EDITOR,
191 // _("Create new element node"));
192 }
194 /*
195 * Creates a new CanvasGrid object of type gridtype
196 */
197 CanvasGrid*
198 CanvasGrid::NewGrid(SPDesktop *desktop, Inkscape::XML::Node * in_repr, const char * gridtype)
199 {
200 if (!desktop) return NULL;
201 if (!in_repr) return NULL;
202 if (!gridtype) return NULL;
204 if (!strcmp(gridtype,"xygrid")) {
205 return (CanvasGrid*) new CanvasXYGrid(desktop, in_repr);
206 } else if (!strcmp(gridtype,"axonometric")) {
207 return (CanvasGrid*) new CanvasAxonomGrid(desktop, in_repr);
208 }
210 return NULL;
211 }
214 void
215 CanvasGrid::hide()
216 {
217 sp_canvas_item_hide(canvasitem);
218 visible = false;
219 disable_snapping(); // temporary hack, because at the moment visibilty and snapping are linked
220 }
222 void
223 CanvasGrid::show()
224 {
225 sp_canvas_item_show(canvasitem);
226 visible = true;
227 enable_snapping(); // temporary hack, because at the moment visibilty and snapping are linked
228 }
230 void
231 CanvasGrid::set_visibility(bool visible)
232 {
233 this->visible = visible;
234 if(visible) {
235 show();
236 } else {
237 hide();
238 }
239 }
241 void
242 CanvasGrid::toggle_visibility()
243 {
244 visible = !visible;
245 set_visibility(visible);
246 }
248 void
249 CanvasGrid::on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data)
250 {
251 if (!data)
252 return;
254 ((CanvasGrid*) data)->onReprAttrChanged(repr, key, oldval, newval, is_interactive);
255 }
259 // ##########################################################
260 // CanvasXYGrid
262 static void grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba);
263 static void grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba);
266 /**
267 * A DIRECT COPY-PASTE FROM DOCUMENT-PROPERTIES.CPP TO QUICKLY GET RESULTS
268 *
269 * Helper function that attachs widgets in a 3xn table. The widgets come in an
270 * array that has two entries per table row. The two entries code for four
271 * possible cases: (0,0) means insert space in first column; (0, non-0) means
272 * widget in columns 2-3; (non-0, 0) means label in columns 1-3; and
273 * (non-0, non-0) means two widgets in columns 2 and 3.
274 **/
275 #define SPACE_SIZE_X 15
276 #define SPACE_SIZE_Y 10
277 static inline void
278 attach_all (Gtk::Table &table, const Gtk::Widget *arr[], unsigned size, int start = 0)
279 {
280 for (unsigned i=0, r=start; i<size/sizeof(Gtk::Widget*); i+=2)
281 {
282 if (arr[i] && arr[i+1])
283 {
284 table.attach (const_cast<Gtk::Widget&>(*arr[i]), 1, 2, r, r+1,
285 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
286 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 2, 3, r, r+1,
287 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
288 }
289 else
290 {
291 if (arr[i+1])
292 table.attach (const_cast<Gtk::Widget&>(*arr[i+1]), 1, 3, r, r+1,
293 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
294 else if (arr[i])
295 {
296 Gtk::Label& label = reinterpret_cast<Gtk::Label&> (const_cast<Gtk::Widget&>(*arr[i]));
297 label.set_alignment (0.0);
298 table.attach (label, 0, 3, r, r+1,
299 Gtk::FILL|Gtk::EXPAND, (Gtk::AttachOptions)0,0,0);
300 }
301 else
302 {
303 Gtk::HBox *space = manage (new Gtk::HBox);
304 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
305 table.attach (*space, 0, 1, r, r+1,
306 (Gtk::AttachOptions)0, (Gtk::AttachOptions)0,0,0);
307 }
308 }
309 ++r;
310 }
311 }
313 CanvasXYGrid::CanvasXYGrid (SPDesktop *desktop, Inkscape::XML::Node * in_repr)
314 : CanvasGrid(desktop, in_repr), table(1, 1)
315 {
316 origin[NR::X] = origin[NR::Y] = 0.0;
317 // nv->gridcolor = (nv->gridcolor & 0xff) | (DEFAULTGRIDCOLOR & 0xffffff00);
318 // case SP_ATTR_GRIDOPACITY:
319 // nv->gridcolor = (nv->gridcolor & 0xffffff00) | (DEFAULTGRIDCOLOR & 0xff);
320 color = 0xff3f3f20;
321 empcolor = 0xFF3F3F40;
322 empspacing = 5;
323 spacing[NR::X] = spacing[NR::Y] = 8.0;
324 gridunit = &sp_unit_get_by_id(SP_UNIT_PX);
326 snapper = new CanvasXYGridSnapper(this, namedview, 0);
328 // initialize widgets:
329 vbox.set_border_width(2);
330 table.set_spacings(2);
331 vbox.pack_start(table, false, false, 0);
333 _rumg.init (_("Grid _units:"), "units", _wr, repr);
334 _rsu_ox.init (_("_Origin X:"), _("X coordinate of grid origin"),
335 "originx", _rumg, _wr, repr);
336 _rsu_oy.init (_("O_rigin Y:"), _("Y coordinate of grid origin"),
337 "originy", _rumg, _wr, repr);
338 _rsu_sx.init (_("Spacing _X:"), _("Distance between vertical grid lines"),
339 "spacingx", _rumg, _wr, repr);
340 _rsu_sy.init (_("Spacing _Y:"), _("Distance between horizontal grid lines"),
341 "spacingy", _rumg, _wr, repr);
342 _rcp_gcol.init (_("Grid line _color:"), _("Grid line color"),
343 _("Color of grid lines"), "color", "opacity", _wr, repr);
344 _rcp_gmcol.init (_("Ma_jor grid line color:"), _("Major grid line color"),
345 _("Color of the major (highlighted) grid lines"),
346 "empcolor", "empopacity", _wr, repr);
347 _rsi.init (_("_Major grid line every:"), _("lines"), "empspacing", _wr, repr);
349 const Gtk::Widget* widget_array[] =
350 {
351 0, _rcbgrid._button,
352 _rumg._label, _rumg._sel,
353 0, _rsu_ox.getSU(),
354 0, _rsu_oy.getSU(),
355 0, _rsu_sx.getSU(),
356 0, _rsu_sy.getSU(),
357 _rcp_gcol._label, _rcp_gcol._cp,
358 0, 0,
359 _rcp_gmcol._label, _rcp_gmcol._cp,
360 _rsi._label, &_rsi._hbox,
361 };
363 attach_all (table, widget_array, sizeof(widget_array));
365 vbox.show();
367 if (repr) readRepr();
368 updateWidgets();
369 }
371 CanvasXYGrid::~CanvasXYGrid ()
372 {
373 if (snapper) delete snapper;
374 }
377 /* fixme: Collect all these length parsing methods and think common sane API */
379 static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit)
380 {
381 if (!str) {
382 return FALSE;
383 }
385 gchar *u;
386 gdouble v = g_ascii_strtod(str, &u);
387 if (!u) {
388 return FALSE;
389 }
390 while (isspace(*u)) {
391 u += 1;
392 }
394 if (!*u) {
395 /* No unit specified - keep default */
396 *val = v;
397 return TRUE;
398 }
400 if (base & SP_UNIT_DEVICE) {
401 if (u[0] && u[1] && !isalnum(u[2]) && !strncmp(u, "px", 2)) {
402 *unit = &sp_unit_get_by_id(SP_UNIT_PX);
403 *val = v;
404 return TRUE;
405 }
406 }
408 if (base & SP_UNIT_ABSOLUTE) {
409 if (!strncmp(u, "pt", 2)) {
410 *unit = &sp_unit_get_by_id(SP_UNIT_PT);
411 } else if (!strncmp(u, "mm", 2)) {
412 *unit = &sp_unit_get_by_id(SP_UNIT_MM);
413 } else if (!strncmp(u, "cm", 2)) {
414 *unit = &sp_unit_get_by_id(SP_UNIT_CM);
415 } else if (!strncmp(u, "m", 1)) {
416 *unit = &sp_unit_get_by_id(SP_UNIT_M);
417 } else if (!strncmp(u, "in", 2)) {
418 *unit = &sp_unit_get_by_id(SP_UNIT_IN);
419 } else {
420 return FALSE;
421 }
422 *val = v;
423 return TRUE;
424 }
426 return FALSE;
427 }
429 static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color)
430 {
431 if (!str) {
432 return FALSE;
433 }
435 gchar *u;
436 gdouble v = g_ascii_strtod(str, &u);
437 if (!u) {
438 return FALSE;
439 }
440 v = CLAMP(v, 0.0, 1.0);
442 *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
444 return TRUE;
445 }
449 void
450 CanvasXYGrid::readRepr()
451 {
452 gchar const* value;
453 if ( (value = repr->attribute("originx")) ) {
454 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::X], &gridunit);
455 origin[NR::X] = sp_units_get_pixels(origin[NR::X], *(gridunit));
456 }
457 if ( (value = repr->attribute("originy")) ) {
458 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &origin[NR::Y], &gridunit);
459 origin[NR::Y] = sp_units_get_pixels(origin[NR::Y], *(gridunit));
460 }
462 if ( (value = repr->attribute("spacingx")) ) {
463 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::X], &gridunit);
464 spacing[NR::X] = sp_units_get_pixels(spacing[NR::X], *(gridunit));
465 }
466 if ( (value = repr->attribute("spacingy")) ) {
467 sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &spacing[NR::Y], &gridunit);
468 spacing[NR::Y] = sp_units_get_pixels(spacing[NR::Y], *(gridunit));
469 }
471 if ( (value = repr->attribute("color")) ) {
472 color = (color & 0xff) | sp_svg_read_color(value, color);
473 }
475 if ( (value = repr->attribute("empcolor")) ) {
476 empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
477 }
479 if ( (value = repr->attribute("opacity")) ) {
480 sp_nv_read_opacity(value, &color);
481 }
482 if ( (value = repr->attribute("empopacity")) ) {
483 sp_nv_read_opacity(value, &empcolor);
484 }
486 if ( (value = repr->attribute("empspacing")) ) {
487 empspacing = atoi(value);
488 }
490 sp_canvas_item_request_update (canvasitem);
492 return;
493 }
495 /**
496 * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
497 */
498 void
499 CanvasXYGrid::onReprAttrChanged (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive)
500 {
501 readRepr();
503 if ( ! (_wr.isUpdating()) )
504 updateWidgets();
505 }
510 Gtk::Widget &
511 CanvasXYGrid::getWidget()
512 {
513 return vbox;
514 }
517 /**
518 * Update dialog widgets from object's values.
519 */
520 void
521 CanvasXYGrid::updateWidgets()
522 {
523 if (_wr.isUpdating()) return;
525 _wr.setUpdating (true);
527 // _rrb_gridtype.setValue (nv->gridtype);
528 _rumg.setUnit (gridunit);
530 gdouble val;
531 val = origin[NR::X];
532 val = sp_pixels_get_units (val, *(gridunit));
533 _rsu_ox.setValue (val);
534 val = origin[NR::Y];
535 val = sp_pixels_get_units (val, *(gridunit));
536 _rsu_oy.setValue (val);
537 val = spacing[NR::X];
538 double gridx = sp_pixels_get_units (val, *(gridunit));
539 _rsu_sx.setValue (gridx);
540 val = spacing[NR::Y];
541 double gridy = sp_pixels_get_units (val, *(gridunit));
542 _rsu_sy.setValue (gridy);
544 _rcp_gcol.setRgba32 (color);
545 _rcp_gmcol.setRgba32 (empcolor);
546 _rsi.setValue (empspacing);
548 _wr.setUpdating (false);
550 return;
551 }
555 void
556 CanvasXYGrid::Update (NR::Matrix const &affine, unsigned int flags)
557 {
558 ow = origin * affine;
559 sw = spacing * affine;
560 sw -= NR::Point(affine[4], affine[5]);
562 for(int dim = 0; dim < 2; dim++) {
563 gint scaling_factor = 5; //empspacing;
565 if (scaling_factor <= 1)
566 scaling_factor = 5;
568 scaled[dim] = FALSE;
569 sw[dim] = fabs (sw[dim]);
570 while (sw[dim] < 8.0) {
571 scaled[dim] = TRUE;
572 sw[dim] *= scaling_factor;
573 /* First pass, go up to the major line spacing, then
574 keep increasing by two. */
575 scaling_factor = 2;
576 }
577 }
578 }
580 void
581 CanvasXYGrid::Render (SPCanvasBuf *buf)
582 {
583 const gdouble sxg = floor ((buf->rect.x0 - ow[NR::X]) / sw[NR::X]) * sw[NR::X] + ow[NR::X];
584 const gint xlinestart = (gint) Inkscape::round((sxg - ow[NR::X]) / sw[NR::X]);
585 const gdouble syg = floor ((buf->rect.y0 - ow[NR::Y]) / sw[NR::Y]) * sw[NR::Y] + ow[NR::Y];
586 const gint ylinestart = (gint) Inkscape::round((syg - ow[NR::Y]) / sw[NR::Y]);
588 gint ylinenum;
589 gdouble y;
590 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += sw[NR::Y], ylinenum++) {
591 const gint y0 = (gint) Inkscape::round(y);
593 if (!scaled[NR::Y] && (ylinenum % 5 /*empspacing*/) == 0) {
594 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, empcolor);
595 } else {
596 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, color);
597 }
598 }
600 gint xlinenum;
601 gdouble x;
602 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += sw[NR::X], xlinenum++) {
603 const gint ix = (gint) Inkscape::round(x);
604 if (!scaled[NR::X] && (xlinenum % 5 /*empspacing*/) == 0) {
605 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, empcolor);
606 } else {
607 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, color);
608 }
609 }
610 }
623 /**
624 * \return x rounded to the nearest multiple of c1 plus c0.
625 *
626 * \note
627 * If c1==0 (and c0 is finite), then returns +/-inf. This makes grid spacing of zero
628 * mean "ignore the grid in this dimention". We're currently discussing "good" semantics
629 * for guide/grid snapping.
630 */
632 /* FIXME: move this somewhere else, perhaps */
633 static double round_to_nearest_multiple_plus(double x, double const c1, double const c0)
634 {
635 return floor((x - c0) / c1 + .5) * c1 + c0;
636 }
638 CanvasXYGridSnapper::CanvasXYGridSnapper(CanvasXYGrid *grid, SPNamedView const *nv, NR::Coord const d) : LineSnapper(nv, d)
639 {
640 this->grid = grid;
641 }
643 LineSnapper::LineList
644 CanvasXYGridSnapper::_getSnapLines(NR::Point const &p) const
645 {
646 LineList s;
648 if ( grid == NULL ) {
649 return s;
650 }
652 for (unsigned int i = 0; i < 2; ++i) {
654 /* This is to make sure we snap to only visible grid lines */
655 double scaled_spacing = grid->sw[i]; // this is spacing of visible lines if screen pixels
657 // convert screen pixels to px
658 // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
659 if (SP_ACTIVE_DESKTOP) {
660 scaled_spacing /= SP_ACTIVE_DESKTOP->current_zoom();
661 }
663 NR::Coord const rounded = round_to_nearest_multiple_plus(p[i],
664 scaled_spacing,
665 grid->origin[i]);
667 s.push_back(std::make_pair(NR::Dim2(i), rounded));
668 }
670 return s;
671 }
703 enum {
704 ARG_0,
705 ARG_ORIGINX,
706 ARG_ORIGINY,
707 ARG_SPACINGX,
708 ARG_SPACINGY,
709 ARG_COLOR,
710 ARG_EMPCOLOR,
711 ARG_EMPSPACING
712 };
715 static void cxygrid_class_init (CXYGridClass *klass);
716 static void cxygrid_init (CXYGrid *grid);
717 static void cxygrid_destroy (GtkObject *object);
718 static void cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
720 static void cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
721 static void cxygrid_render (SPCanvasItem *item, SPCanvasBuf *buf);
723 //static SPCanvasItemClass * parent_class;
725 GtkType
726 cxygrid_get_type (void)
727 {
728 static GtkType cxygrid_type = 0;
730 if (!cxygrid_type) {
731 GtkTypeInfo cxygrid_info = {
732 "CXYGrid",
733 sizeof (CXYGrid),
734 sizeof (CXYGridClass),
735 (GtkClassInitFunc) cxygrid_class_init,
736 (GtkObjectInitFunc) cxygrid_init,
737 NULL, NULL,
738 (GtkClassInitFunc) NULL
739 };
740 cxygrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cxygrid_info);
741 }
742 return cxygrid_type;
743 }
745 static void
746 cxygrid_class_init (CXYGridClass *klass)
747 {
748 GtkObjectClass *object_class;
749 SPCanvasItemClass *item_class;
751 object_class = (GtkObjectClass *) klass;
752 item_class = (SPCanvasItemClass *) klass;
754 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
756 gtk_object_add_arg_type ("CXYGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);
757 gtk_object_add_arg_type ("CXYGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);
758 gtk_object_add_arg_type ("CXYGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX);
759 gtk_object_add_arg_type ("CXYGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);
760 gtk_object_add_arg_type ("CXYGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);
761 gtk_object_add_arg_type ("CXYGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);
762 gtk_object_add_arg_type ("CXYGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);
764 object_class->destroy = cxygrid_destroy;
765 object_class->set_arg = cxygrid_set_arg;
767 item_class->update = cxygrid_update;
768 item_class->render = cxygrid_render;
769 }
771 static void
772 cxygrid_init (CXYGrid *grid)
773 {
774 grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;
775 grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0;
776 grid->color = 0x0000ff7f;
777 grid->empcolor = 0x3F3FFF40;
778 grid->empspacing = 5;
779 }
781 static void
782 cxygrid_destroy (GtkObject *object)
783 {
784 g_return_if_fail (object != NULL);
785 g_return_if_fail (INKSCAPE_IS_CXYGRID (object));
787 if (GTK_OBJECT_CLASS (parent_class)->destroy)
788 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
789 }
791 static void
792 cxygrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
793 {
794 SPCanvasItem *item = SP_CANVAS_ITEM (object);
795 CXYGrid *grid = INKSCAPE_CXYGRID (object);
797 switch (arg_id) {
798 case ARG_ORIGINX:
799 grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);
800 sp_canvas_item_request_update (item);
801 break;
802 case ARG_ORIGINY:
803 grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);
804 sp_canvas_item_request_update (item);
805 break;
806 case ARG_SPACINGX:
807 grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg);
808 if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01;
809 sp_canvas_item_request_update (item);
810 break;
811 case ARG_SPACINGY:
812 grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg);
813 if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01;
814 sp_canvas_item_request_update (item);
815 break;
816 case ARG_COLOR:
817 grid->color = GTK_VALUE_INT (* arg);
818 sp_canvas_item_request_update (item);
819 break;
820 case ARG_EMPCOLOR:
821 grid->empcolor = GTK_VALUE_INT (* arg);
822 sp_canvas_item_request_update (item);
823 break;
824 case ARG_EMPSPACING:
825 grid->empspacing = GTK_VALUE_INT (* arg);
826 // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;
827 sp_canvas_item_request_update (item);
828 break;
829 default:
830 break;
831 }
832 }
834 static void
835 grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba)
836 {
837 if ((y >= buf->rect.y0) && (y < buf->rect.y1)) {
838 guint r, g, b, a;
839 gint x0, x1, x;
840 guchar *p;
841 r = NR_RGBA32_R (rgba);
842 g = NR_RGBA32_G (rgba);
843 b = NR_RGBA32_B (rgba);
844 a = NR_RGBA32_A (rgba);
845 x0 = MAX (buf->rect.x0, xs);
846 x1 = MIN (buf->rect.x1, xe + 1);
847 p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
848 for (x = x0; x < x1; x++) {
849 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
850 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
851 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
852 p += 3;
853 }
854 }
855 }
857 static void
858 grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)
859 {
860 if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {
861 guint r, g, b, a;
862 gint y0, y1, y;
863 guchar *p;
864 r = NR_RGBA32_R(rgba);
865 g = NR_RGBA32_G (rgba);
866 b = NR_RGBA32_B (rgba);
867 a = NR_RGBA32_A (rgba);
868 y0 = MAX (buf->rect.y0, ys);
869 y1 = MIN (buf->rect.y1, ye + 1);
870 p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
871 for (y = y0; y < y1; y++) {
872 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);
873 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);
874 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);
875 p += buf->buf_rowstride;
876 }
877 }
878 }
880 /**
881 \brief This function renders the grid on a particular canvas buffer
882 \param item The grid to render on the buffer
883 \param buf The buffer to render the grid on
885 This function gets called a touch more than you might believe,
886 about once per tile. This means that it could probably be optimized
887 and help things out.
889 Basically this function has to determine where in the canvas it is,
890 and how that associates with the grid. It does this first by looking
891 at the bounding box of the buffer, and then calculates where the grid
892 starts in that buffer. It will then step through grid lines until
893 it is outside of the buffer.
895 For each grid line it is drawn using the function \c sp_grid_hline
896 or \c sp_grid_vline. These are convience functions for the sake
897 of making the function easier to read.
899 Also, there are emphisized lines on the grid. While the \c syg and
900 \c sxg variable track grid positioning, the \c xlinestart and \c
901 ylinestart variables track the 'count' of what lines they are. If
902 that count is a multiple of the line seperation between emphisis
903 lines, then that line is drawn in the emphisis color.
904 */
905 static void
906 cxygrid_render (SPCanvasItem * item, SPCanvasBuf * buf)
907 {
908 CXYGrid *grid = INKSCAPE_CXYGRID (item);
910 sp_canvas_prepare_buffer (buf);
912 const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X];
913 const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]);
914 const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y];
915 const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]);
917 gint ylinenum;
918 gdouble y;
919 for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) {
920 const gint y0 = (gint) Inkscape::round(y);
922 if (!grid->scaled[NR::Y] && (ylinenum % grid->empspacing) == 0) {
923 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor);
924 } else {
925 grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color);
926 }
927 }
929 gint xlinenum;
930 gdouble x;
931 for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) {
932 const gint ix = (gint) Inkscape::round(x);
933 if (!grid->scaled[NR::X] && (xlinenum % grid->empspacing) == 0) {
934 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->empcolor);
935 } else {
936 grid_vline (buf, ix, buf->rect.y0, buf->rect.y1, grid->color);
937 }
938 }
939 }
941 static void
942 cxygrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
943 {
944 CXYGrid *grid = INKSCAPE_CXYGRID (item);
946 if (parent_class->update)
947 (* parent_class->update) (item, affine, flags);
949 grid->ow = grid->origin * affine;
950 grid->sw = grid->spacing * affine;
951 grid->sw -= NR::Point(affine[4], affine[5]);
953 for(int dim = 0; dim < 2; dim++) {
954 gint scaling_factor = grid->empspacing;
956 if (scaling_factor <= 1)
957 scaling_factor = 5;
959 grid->scaled[dim] = FALSE;
960 grid->sw[dim] = fabs (grid->sw[dim]);
961 while (grid->sw[dim] < 8.0) {
962 grid->scaled[dim] = TRUE;
963 grid->sw[dim] *= scaling_factor;
964 /* First pass, go up to the major line spacing, then
965 keep increasing by two. */
966 scaling_factor = 2;
967 }
968 }
970 if (grid->empspacing == 0) {
971 grid->scaled[NR::Y] = TRUE;
972 grid->scaled[NR::X] = TRUE;
973 }
975 sp_canvas_request_redraw (item->canvas,
976 -1000000, -1000000,
977 1000000, 1000000);
979 item->x1 = item->y1 = -1000000;
980 item->x2 = item->y2 = 1000000;
981 }
984 }; /* namespace Inkscape */
986 /*
987 Local Variables:
988 mode:c++
989 c-file-style:"stroustrup"
990 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
991 indent-tabs-mode:nil
992 fill-column:99
993 End:
994 */
995 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :