1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Authors:
4 * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
5 *
6 * Copyright (C) 2006-2012 Authors
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10 /*
11 * Current limits are: one axis (y-axis) is always vertical. The other two
12 * axes are bound to a certain range of angles. The z-axis always has an angle
13 * smaller than 90 degrees (measured from horizontal, 0 degrees being a line extending
14 * to the right). The x-axis will always have an angle between 0 and 90 degrees.
15 */
16
17 #include <gtkmm/box.h>
18 #include <gtkmm/label.h>
19 #include <gtkmm/grid.h>
20
21 #include <glibmm/i18n.h>
22
23 #include <2geom/angle.h>
24 #include <2geom/line.h>
25
26 #include "canvas-axonomgrid.h"
27 #include "canvas-grid.h"
28 #include "canvas-item-grid.h"
29
30 #include "desktop.h"
31 #include "document.h"
32 #include "inkscape.h"
33 #include "preferences.h"
34
35 #include "display/cairo-utils.h"
36
37 #include "helper/mathfns.h"
38
39 #include "object/sp-namedview.h"
40 #include "object/sp-object.h"
41 #include "object/sp-root.h"
42
43 #include "svg/svg-color.h"
44
45 #include "ui/widget/canvas.h"
46 #include "ui/widget/registered-widget.h"
47
48 #include "util/units.h"
49
50 using Inkscape::Util::unit_table;
51
52 enum Dim3 { X=0, Y, Z };
53
54 /**
55 * This function calls Cairo to render a line on a particular canvas buffer.
56 * Coordinates are interpreted as SCREENcoordinates
57 */
58 static void
sp_caxonomgrid_drawline(Inkscape::CanvasItemBuffer * buf,gint x0,gint y0,gint x1,gint y1,guint32 rgba)59 sp_caxonomgrid_drawline (Inkscape::CanvasItemBuffer *buf, gint x0, gint y0, gint x1, gint y1, guint32 rgba)
60 {
61 buf->cr->move_to(0.5 + x0, 0.5 + y0);
62 buf->cr->line_to(0.5 + x1, 0.5 + y1);
63 buf->cr->set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba),
64 SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
65 buf->cr->stroke();
66 }
67
68 static void
sp_grid_vline(Inkscape::CanvasItemBuffer * buf,gint x,gint ys,gint ye,guint32 rgba)69 sp_grid_vline (Inkscape::CanvasItemBuffer *buf, gint x, gint ys, gint ye, guint32 rgba)
70 {
71 if ((x < buf->rect.left()) || (x >= buf->rect.right()))
72 return;
73
74 buf->cr->move_to(0.5 + x, 0.5 + ys);
75 buf->cr->line_to(0.5 + x, 0.5 + ye);
76 buf->cr->set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba),
77 SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
78 buf->cr->stroke();
79 }
80
81 namespace Inkscape {
82
83
CanvasAxonomGrid(SPNamedView * nv,Inkscape::XML::Node * in_repr,SPDocument * in_doc)84 CanvasAxonomGrid::CanvasAxonomGrid (SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument * in_doc)
85 : CanvasGrid(nv, in_repr, in_doc, GRID_AXONOMETRIC)
86 {
87 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
88 gridunit = unit_table.getUnit(prefs->getString("/options/grids/axonom/units"));
89 if (!gridunit) {
90 gridunit = unit_table.getUnit("px");
91 }
92 origin[Geom::X] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/origin_x", 0.0), gridunit, "px");
93 origin[Geom::Y] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/origin_y", 0.0), gridunit, "px");
94 color = prefs->getInt("/options/grids/axonom/color", GRID_DEFAULT_COLOR);
95 empcolor = prefs->getInt("/options/grids/axonom/empcolor", GRID_DEFAULT_EMPCOLOR);
96 empspacing = prefs->getInt("/options/grids/axonom/empspacing", 5);
97 lengthy = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/spacing_y", 1.0), gridunit, "px");
98 angle_deg[X] = prefs->getDouble("/options/grids/axonom/angle_x", 30.0);
99 angle_deg[Z] = prefs->getDouble("/options/grids/axonom/angle_z", 30.0);
100 angle_deg[Y] = 0;
101
102 angle_rad[X] = Geom::rad_from_deg(angle_deg[X]);
103 tan_angle[X] = tan(angle_rad[X]);
104 angle_rad[Z] = Geom::rad_from_deg(angle_deg[Z]);
105 tan_angle[Z] = tan(angle_rad[Z]);
106
107 snapper = new CanvasAxonomGridSnapper(this, &namedview->snap_manager, 0);
108
109 if (repr) readRepr();
110 }
111
~CanvasAxonomGrid()112 CanvasAxonomGrid::~CanvasAxonomGrid ()
113 {
114 if (snapper) delete snapper;
115 }
116
sp_nv_read_opacity(gchar const * str,guint32 * color)117 static gboolean sp_nv_read_opacity(gchar const *str, guint32 *color)
118 {
119 if (!str) {
120 return FALSE;
121 }
122
123 gchar *u;
124 gdouble v = g_ascii_strtod(str, &u);
125 if (!u) {
126 return FALSE;
127 }
128 v = CLAMP(v, 0.0, 1.0);
129
130 *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
131
132 return TRUE;
133 }
134
135
136
137 void
readRepr()138 CanvasAxonomGrid::readRepr()
139 {
140 SPRoot *root = doc->getRoot();
141 double scale_x = 1.0;
142 double scale_y = 1.0;
143 if( root->viewBox_set ) {
144 scale_x = root->width.computed / root->viewBox.width();
145 scale_y = root->height.computed / root->viewBox.height();
146 if (Geom::are_near(scale_x / scale_y, 1.0, Geom::EPSILON)) {
147 // scaling is uniform, try to reduce numerical error
148 scale_x = (scale_x + scale_y)/2.0;
149 double scale_none = Inkscape::Util::Quantity::convert(1, doc->getDisplayUnit(), "px");
150 if (Geom::are_near(scale_x / scale_none, 1.0, Geom::EPSILON))
151 scale_x = scale_none; // objects are same size, reduce numerical error
152 scale_y = scale_x;
153 }
154 }
155
156 gchar const *value;
157
158 if ( (value = repr->attribute("originx")) ) {
159
160 Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
161
162 if( q.unit->type == UNIT_TYPE_LINEAR ) {
163 // Legacy grid not in 'user units'
164 origin[Geom::X] = q.value("px");
165 } else {
166 // Grid in 'user units'
167 origin[Geom::X] = q.quantity * scale_x;
168 }
169 }
170
171 if ( (value = repr->attribute("originy")) ) {
172
173 Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
174
175 if( q.unit->type == UNIT_TYPE_LINEAR ) {
176 // Legacy grid not in 'user units'
177 origin[Geom::Y] = q.value("px");
178 } else {
179 // Grid in 'user units'
180 origin[Geom::Y] = q.quantity * scale_y;
181 }
182 }
183
184 if ( (value = repr->attribute("spacingy")) ) {
185
186 Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
187
188 if( q.unit->type == UNIT_TYPE_LINEAR ) {
189 // Legacy grid not in 'user units'
190 lengthy = q.value("px");
191 } else {
192 // Grid in 'user units'
193 lengthy = q.quantity * scale_y; // We do not handle scale_x != scale_y
194 }
195 if (lengthy < 0.0500) lengthy = 0.0500;
196 }
197
198 if ( (value = repr->attribute("gridanglex")) ) {
199 angle_deg[X] = g_ascii_strtod(value, nullptr);
200 if (angle_deg[X] < 0.) angle_deg[X] = 0.;
201 if (angle_deg[X] > 89.0) angle_deg[X] = 89.0;
202 angle_rad[X] = Geom::rad_from_deg(angle_deg[X]);
203 tan_angle[X] = tan(angle_rad[X]);
204 }
205
206 if ( (value = repr->attribute("gridanglez")) ) {
207 angle_deg[Z] = g_ascii_strtod(value, nullptr);
208 if (angle_deg[Z] < 0.) angle_deg[Z] = 0.;
209 if (angle_deg[Z] > 89.0) angle_deg[Z] = 89.0;
210 angle_rad[Z] = Geom::rad_from_deg(angle_deg[Z]);
211 tan_angle[Z] = tan(angle_rad[Z]);
212 }
213
214 if ( (value = repr->attribute("color")) ) {
215 color = (color & 0xff) | sp_svg_read_color(value, color);
216 }
217
218 if ( (value = repr->attribute("empcolor")) ) {
219 empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
220 }
221
222 if ( (value = repr->attribute("opacity")) ) {
223 sp_nv_read_opacity(value, &color);
224 }
225 if ( (value = repr->attribute("empopacity")) ) {
226 sp_nv_read_opacity(value, &empcolor);
227 }
228
229 if ( (value = repr->attribute("empspacing")) ) {
230 empspacing = atoi(value);
231 }
232
233 if ( (value = repr->attribute("visible")) ) {
234 visible = (strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
235 }
236
237 if ( (value = repr->attribute("enabled")) ) {
238 g_assert(snapper != nullptr);
239 snapper->setEnabled(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
240 }
241
242 if ( (value = repr->attribute("snapvisiblegridlinesonly")) ) {
243 g_assert(snapper != nullptr);
244 snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
245 }
246
247 if ( (value = repr->attribute("units")) ) {
248 gridunit = unit_table.getUnit(value); // Display unit identifier in grid menu
249 }
250
251 for (auto grid : canvas_item_grids) {
252 grid->request_update();
253 }
254
255 return;
256 }
257
258 /**
259 * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
260 */
261 void
onReprAttrChanged(Inkscape::XML::Node *,gchar const *,gchar const *,gchar const *,bool)262 CanvasAxonomGrid::onReprAttrChanged(Inkscape::XML::Node */*repr*/, gchar const */*key*/, gchar const */*oldval*/, gchar const */*newval*/, bool /*is_interactive*/)
263 {
264 readRepr();
265
266 if ( ! (_wr.isUpdating()) )
267 updateWidgets();
268 }
269
270 Gtk::Widget *
newSpecificWidget()271 CanvasAxonomGrid::newSpecificWidget()
272 {
273 _rumg = Gtk::manage( new Inkscape::UI::Widget::RegisteredUnitMenu(
274 _("Grid _units:"), "units", _wr, repr, doc) );
275 _rsu_ox = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
276 _("_Origin X:"), _("X coordinate of grid origin"), "originx",
277 *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_x) );
278 _rsu_oy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
279 _("O_rigin Y:"), _("Y coordinate of grid origin"), "originy",
280 *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
281 _rsu_sy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
282 _("Spacing _Y:"), _("Base length of z-axis"), "spacingy",
283 *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
284 _rsu_ax = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalar(
285 _("Angle X:"), _("Angle of x-axis"), "gridanglex", _wr, repr, doc ) );
286 _rsu_az = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalar(
287 _("Angle Z:"), _("Angle of z-axis"), "gridanglez", _wr, repr, doc ) );
288
289 _rcp_gcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
290 _("Minor grid line _color:"), _("Minor grid line color"), _("Color of the minor grid lines"),
291 "color", "opacity", _wr, repr, doc));
292 _rcp_gmcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
293 _("Ma_jor grid line color:"), _("Major grid line color"),
294 _("Color of the major (highlighted) grid lines"),
295 "empcolor", "empopacity", _wr, repr, doc));
296
297 _rsi = Gtk::manage( new Inkscape::UI::Widget::RegisteredSuffixedInteger(
298 _("_Major grid line every:"), "", _("lines"), "empspacing", _wr, repr, doc ) );
299
300 _rumg->set_hexpand();
301 _rsu_ox->set_hexpand();
302 _rsu_oy->set_hexpand();
303 _rsu_sy->set_hexpand();
304 _rsu_ax->set_hexpand();
305 _rsu_az->set_hexpand();
306 _rcp_gcol->set_hexpand();
307 _rcp_gmcol->set_hexpand();
308 _rsi->set_hexpand();
309
310 // set widget values
311 _wr.setUpdating (true);
312
313 _rsu_ox->setDigits(5);
314 _rsu_ox->setIncrements(0.1, 1.0);
315
316 _rsu_oy->setDigits(5);
317 _rsu_oy->setIncrements(0.1, 1.0);
318
319 _rsu_sy->setDigits(5);
320 _rsu_sy->setIncrements(0.1, 1.0);
321
322 _rumg->setUnit (gridunit->abbr);
323
324 gdouble val;
325 val = origin[Geom::X];
326 val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
327 _rsu_ox->setValue (val);
328 val = origin[Geom::Y];
329 val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
330 _rsu_oy->setValue (val);
331 val = lengthy;
332 double gridy = Inkscape::Util::Quantity::convert(val, "px", gridunit);
333 _rsu_sy->setValue (gridy);
334
335 _rsu_ax->setValue(angle_deg[X]);
336 _rsu_az->setValue(angle_deg[Z]);
337
338 _rcp_gcol->setRgba32 (color);
339 _rcp_gmcol->setRgba32 (empcolor);
340 _rsi->setValue (empspacing);
341
342 _wr.setUpdating (false);
343
344 _rsu_ox->setProgrammatically = false;
345 _rsu_oy->setProgrammatically = false;
346
347 Gtk::Box *column = new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4);
348 column->pack_start(*_rumg, true, false);
349 column->pack_start(*_rsu_ox, true, false);
350 column->pack_start(*_rsu_oy, true, false);
351 column->pack_start(*_rsu_sy, true, false);
352 column->pack_start(*_rsu_ax, true, false);
353 column->pack_start(*_rsu_az, true, false);
354 column->pack_start(*_rcp_gcol, true, false);
355 column->pack_start(*_rcp_gmcol, true, false);
356 column->pack_start(*_rsi, true, false);
357
358 return column;
359 }
360
361
362 /**
363 * Update dialog widgets from object's values.
364 */
365 void
updateWidgets()366 CanvasAxonomGrid::updateWidgets()
367 {
368 if (_wr.isUpdating()) return;
369
370 //no widgets (grid created with the document, not with the dialog)
371 if (!_rcb_visible) return;
372
373 _wr.setUpdating (true);
374
375 _rcb_visible->setActive(visible);
376 if (snapper != nullptr) {
377 _rcb_enabled->setActive(snapper->getEnabled());
378 _rcb_snap_visible_only->setActive(snapper->getSnapVisibleOnly());
379 }
380
381 _rumg->setUnit (gridunit->abbr);
382
383 gdouble val;
384
385 val = origin[Geom::X];
386 val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
387 _rsu_ox->setValue (val);
388
389 val = origin[Geom::Y];
390 val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
391 _rsu_oy->setValue (val);
392
393 val = lengthy;
394 val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
395 _rsu_sy->setValue (val);
396
397 _rsu_ax->setValue(angle_deg[X]);
398 _rsu_az->setValue(angle_deg[Z]);
399
400 _rsu_ox->setProgrammatically = false;
401 _rsu_oy->setProgrammatically = false;
402 _rsu_sy->setProgrammatically = false;
403 _rsu_ax->setProgrammatically = false;
404 _rsu_az->setProgrammatically = false;
405
406 _rcp_gcol->setRgba32 (color);
407 _rcp_gmcol->setRgba32 (empcolor);
408 _rsi->setValue (empspacing);
409
410 _wr.setUpdating (false);
411 }
412
413
414
415 void
Update(Geom::Affine const & affine,unsigned int)416 CanvasAxonomGrid::Update (Geom::Affine const &affine, unsigned int /*flags*/)
417 {
418 ow = origin * affine;
419 sw = Geom::Point(fabs(affine[0]),fabs(affine[3]));
420 sw *= lengthy;
421
422 scaled = false;
423
424 for(int dim = 0; dim < 2; dim++) {
425 gint scaling_factor = empspacing;
426
427 if (scaling_factor <= 1)
428 scaling_factor = 5;
429
430 int watchdog = 0;
431 while ( (sw[dim] < 8.0) & (watchdog < 100) ) {
432 scaled = true;
433 sw[dim] *= scaling_factor;
434 // First pass, go up to the major line spacing, then
435 // keep increasing by two.
436 scaling_factor = 2;
437 watchdog++;
438 }
439
440 }
441
442 spacing_ylines = sw[Geom::X] /(tan_angle[X] + tan_angle[Z]);
443 lyw = sw[Geom::Y];
444 lxw_x = Geom::are_near(tan_angle[X],0.) ? Geom::infinity() : sw[Geom::X] / tan_angle[X];
445 lxw_z = Geom::are_near(tan_angle[Z],0.) ? Geom::infinity() : sw[Geom::X] / tan_angle[Z];
446
447 if (empspacing == 0) {
448 scaled = true;
449 }
450 }
451
452 void
Render(Inkscape::CanvasItemBuffer * buf)453 CanvasAxonomGrid::Render (Inkscape::CanvasItemBuffer *buf)
454 {
455 //set correct coloring, depending preference (when zoomed out, always major coloring or minor coloring)
456 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
457 guint32 _empcolor;
458 guint32 _color = color;
459 bool preference = prefs->getBool("/options/grids/no_emphasize_when_zoomedout", false);
460 if( scaled && preference ) {
461 _empcolor = color;
462 } else {
463 _empcolor = empcolor;
464 }
465
466 bool xrayactive = prefs->getBool("/desktop/xrayactive", false);
467 if (xrayactive) { //this allow good looking on xray zones
468 guint32 bg = namedview->pagecolor;
469 _color = SP_RGBA32_F_COMPOSE(
470 CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_R_F(_color)), 0.0, 1.0),
471 CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_G_F(_color)), 0.0, 1.0),
472 CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_B_F(_color)), 0.0, 1.0),
473 1.0);
474 _empcolor = SP_RGBA32_F_COMPOSE(
475 CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_R_F(_empcolor)), 0.0, 1.0),
476 CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_G_F(_empcolor)), 0.0, 1.0),
477 CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_B_F(_empcolor)), 0.0, 1.0),
478 1.0);
479 }
480
481 buf->cr->save();
482 buf->cr->translate(-buf->rect.left(), -buf->rect.top());
483 buf->cr->set_line_width(1.0);
484 buf->cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
485
486 // gc = gridcoordinates (the coordinates calculated from the grids origin 'grid->ow'.
487 // sc = screencoordinates ( for example "buf->rect.left()" is in screencoordinates )
488 // bc = buffer patch coordinates (x=0 on left side of page, y=0 on bottom of page)
489
490 // tl = topleft ; br = bottomright
491 Geom::Point buf_tl_gc;
492 Geom::Point buf_br_gc;
493 buf_tl_gc[Geom::X] = buf->rect.left() - ow[Geom::X];
494 buf_tl_gc[Geom::Y] = buf->rect.top() - ow[Geom::Y];
495 buf_br_gc[Geom::X] = buf->rect.right() - ow[Geom::X];
496 buf_br_gc[Geom::Y] = buf->rect.bottom() - ow[Geom::Y];
497
498 // render the three separate line groups representing the main-axes
499
500 // x-axis always goes from topleft to bottomright. (0,0) - (1,1)
501 gdouble const xintercept_y_bc = (buf_tl_gc[Geom::X] * tan_angle[X]) - buf_tl_gc[Geom::Y] ;
502 gdouble const xstart_y_sc = ( xintercept_y_bc - floor(xintercept_y_bc/lyw)*lyw ) + buf->rect.top();
503 gint const xlinestart = round( (xstart_y_sc - buf_tl_gc[Geom::X]*tan_angle[X] - ow[Geom::Y]) / lyw );
504 gint xlinenum = xlinestart;
505
506 // lines starting on left side.
507
508 for (gdouble y = xstart_y_sc; y < buf->rect.bottom(); y += lyw, xlinenum++) {
509 gint const x0 = buf->rect.left();
510 gint const y0 = round(y);
511 gint x1 = x0 + round( (buf->rect.bottom() - y) / tan_angle[X] );
512 gint y1 = buf->rect.bottom();
513 if ( Geom::are_near(tan_angle[X],0.) ) {
514 x1 = buf->rect.right();
515 y1 = y0;
516 }
517
518 if (!scaled && (xlinenum % empspacing) != 0) {
519 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
520 } else {
521 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
522 }
523 }
524 // lines starting from top side
525 if (!Geom::are_near(tan_angle[X],0.))
526 {
527 gdouble const xstart_x_sc = buf->rect.left() + (lxw_x - (xstart_y_sc - buf->rect.top()) / tan_angle[X]) ;
528 xlinenum = xlinestart-1;
529 for (gdouble x = xstart_x_sc; x < buf->rect.right(); x += lxw_x, xlinenum--) {
530 gint const y0 = buf->rect.top();
531 gint const y1 = buf->rect.bottom();
532 gint const x0 = round(x);
533 gint const x1 = x0 + round( (y1 - y0) / tan_angle[X] );
534
535 if (!scaled && (xlinenum % empspacing) != 0) {
536 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
537 } else {
538 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
539 }
540 }
541 }
542
543 // y-axis lines (vertical)
544 gdouble const ystart_x_sc = floor (buf_tl_gc[Geom::X] / spacing_ylines) * spacing_ylines + ow[Geom::X];
545 gint const ylinestart = round((ystart_x_sc - ow[Geom::X]) / spacing_ylines);
546 gint ylinenum = ylinestart;
547 for (gdouble x = ystart_x_sc; x < buf->rect.right(); x += spacing_ylines, ylinenum++) {
548 gint const x0 = floor(x); // sp_grid_vline will add 0.5 again, so we'll pre-emptively use floor()
549 // instead of round() to avoid biasing the vertical lines to the right by half a pixel; see
550 // CanvasXYGrid::Render() for more details
551 if (!scaled && (ylinenum % empspacing) != 0) {
552 sp_grid_vline (buf, x0, buf->rect.top(), buf->rect.bottom() - 1, _color);
553 } else {
554 sp_grid_vline (buf, x0, buf->rect.top(), buf->rect.bottom() - 1, _empcolor);
555 }
556 }
557
558 // z-axis always goes from bottomleft to topright. (0,1) - (1,0)
559 gdouble const zintercept_y_bc = (buf_tl_gc[Geom::X] * -tan_angle[Z]) - buf_tl_gc[Geom::Y] ;
560 gdouble const zstart_y_sc = ( zintercept_y_bc - floor(zintercept_y_bc/lyw)*lyw ) + buf->rect.top();
561 gint const zlinestart = round( (zstart_y_sc + buf_tl_gc[Geom::X]*tan_angle[Z] - ow[Geom::Y]) / lyw );
562 gint zlinenum = zlinestart;
563 // lines starting from left side
564 gdouble next_y = zstart_y_sc;
565 for (gdouble y = zstart_y_sc; y < buf->rect.bottom(); y += lyw, zlinenum++, next_y = y) {
566 gint const x0 = buf->rect.left();
567 gint const y0 = round(y);
568 gint x1 = x0 + round( (y - buf->rect.top() ) / tan_angle[Z] );
569 gint y1 = buf->rect.top();
570 if ( Geom::are_near(tan_angle[Z],0.) ) {
571 x1 = buf->rect.right();
572 y1 = y0;
573 }
574
575 if (!scaled && (zlinenum % empspacing) != 0) {
576 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
577 } else {
578 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
579 }
580 }
581 // draw lines from bottom-up
582 if (!Geom::are_near(tan_angle[Z],0.))
583 {
584 gdouble const zstart_x_sc = buf->rect.left() + (next_y - buf->rect.bottom()) / tan_angle[Z] ;
585 for (gdouble x = zstart_x_sc; x < buf->rect.right(); x += lxw_z, zlinenum++) {
586 gint const y0 = buf->rect.bottom();
587 gint const y1 = buf->rect.top();
588 gint const x0 = round(x);
589 gint const x1 = x0 + round(buf->rect.height() / tan_angle[Z] );
590
591 if (!scaled && (zlinenum % empspacing) != 0) {
592 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
593 } else {
594 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
595 }
596 }
597 }
598
599 buf->cr->restore();
600 }
601
CanvasAxonomGridSnapper(CanvasAxonomGrid * grid,SnapManager * sm,Geom::Coord const d)602 CanvasAxonomGridSnapper::CanvasAxonomGridSnapper(CanvasAxonomGrid *grid, SnapManager *sm, Geom::Coord const d) : LineSnapper(sm, d)
603 {
604 this->grid = grid;
605 }
606
607 /**
608 * \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels
609 */
getSnapperTolerance() const610 Geom::Coord CanvasAxonomGridSnapper::getSnapperTolerance() const
611 {
612 SPDesktop const *dt = _snapmanager->getDesktop();
613 double const zoom = dt ? dt->current_zoom() : 1;
614 return _snapmanager->snapprefs.getGridTolerance() / zoom;
615 }
616
getSnapperAlwaysSnap() const617 bool CanvasAxonomGridSnapper::getSnapperAlwaysSnap() const
618 {
619 return _snapmanager->snapprefs.getGridTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp
620 }
621
622 LineSnapper::LineList
_getSnapLines(Geom::Point const & p) const623 CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const
624 {
625 LineList s;
626
627 if ( grid == nullptr ) {
628 return s;
629 }
630
631 SPDesktop const *dt = _snapmanager->getDesktop();
632 double ta_x = grid->tan_angle[X];
633 double ta_z = grid->tan_angle[Z];
634
635 if (dt && dt->is_yaxisdown()) {
636 std::swap(ta_x, ta_z);
637 }
638
639 double spacing_h;
640 double spacing_v;
641
642 if (getSnapVisibleOnly()) {
643 // Only snapping to visible grid lines
644 spacing_h = grid->spacing_ylines; // this is the spacing of the visible grid lines measured in screen pixels
645 spacing_v = grid->lyw; // vertical
646 // convert screen pixels to px
647 // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
648 if (dt) {
649 spacing_h /= dt->current_zoom();
650 spacing_v /= dt->current_zoom();
651 }
652 } else {
653 // Snapping to any grid line, whether it's visible or not
654 spacing_h = grid->lengthy /(grid->tan_angle[X] + grid->tan_angle[Z]);
655 spacing_v = grid->lengthy;
656
657 }
658
659 // In an axonometric grid, any point will be surrounded by 6 grid lines:
660 // - 2 vertical grid lines, one left and one right from the point
661 // - 2 angled z grid lines, one above and one below the point
662 // - 2 angled x grid lines, one above and one below the point
663
664 // Calculate the x coordinate of the vertical grid lines
665 Geom::Coord x_max = Inkscape::Util::round_to_upper_multiple_plus(p[Geom::X], spacing_h, grid->origin[Geom::X]);
666 Geom::Coord x_min = Inkscape::Util::round_to_lower_multiple_plus(p[Geom::X], spacing_h, grid->origin[Geom::X]);
667
668 // Calculate the y coordinate of the intersection of the angled grid lines with the y-axis
669 double y_proj_along_z = p[Geom::Y] - ta_z * (p[Geom::X] - grid->origin[Geom::X]);
670 double y_proj_along_x = p[Geom::Y] + ta_x * (p[Geom::X] - grid->origin[Geom::X]);
671 double y_proj_along_z_max = Inkscape::Util::round_to_upper_multiple_plus(y_proj_along_z, spacing_v, grid->origin[Geom::Y]);
672 double y_proj_along_z_min = Inkscape::Util::round_to_lower_multiple_plus(y_proj_along_z, spacing_v, grid->origin[Geom::Y]);
673 double y_proj_along_x_max = Inkscape::Util::round_to_upper_multiple_plus(y_proj_along_x, spacing_v, grid->origin[Geom::Y]);
674 double y_proj_along_x_min = Inkscape::Util::round_to_lower_multiple_plus(y_proj_along_x, spacing_v, grid->origin[Geom::Y]);
675
676 // Calculate the versor for the angled grid lines
677 Geom::Point vers_x = Geom::Point(1, -ta_x);
678 Geom::Point vers_z = Geom::Point(1, ta_z);
679
680 // Calculate the normal for the angled grid lines
681 Geom::Point norm_x = Geom::rot90(vers_x);
682 Geom::Point norm_z = Geom::rot90(vers_z);
683
684 // The four angled grid lines form a parallelogram, enclosing the point
685 // One of the two vertical grid lines divides this parallelogram in two triangles
686 // We will now try to find out in which half (i.e. triangle) our point is, and return
687 // only the three grid lines defining that triangle
688
689 // The vertical grid line is at the intersection of two angled grid lines.
690 // Now go find that intersection!
691 Geom::Point p_x(0, y_proj_along_x_max);
692 Geom::Line line_x(p_x, p_x + vers_x);
693 Geom::Point p_z(0, y_proj_along_z_max);
694 Geom::Line line_z(p_z, p_z + vers_z);
695
696 Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default
697 try
698 {
699 inters = Geom::intersection(line_x, line_z);
700 }
701 catch (Geom::InfiniteSolutions &e)
702 {
703 // We're probably dealing with parallel lines; this is useless!
704 return s;
705 }
706
707 // Determine which half of the parallelogram to use
708 bool use_left_half = true;
709 bool use_right_half = true;
710
711 if (inters) {
712 Geom::Point inters_pt = line_x.pointAt((*inters).ta);
713 use_left_half = (p[Geom::X] - grid->origin[Geom::X]) < inters_pt[Geom::X];
714 use_right_half = !use_left_half;
715 }
716
717 // Return the three grid lines which define the triangle that encloses our point
718 // If we didn't find an intersection above, all 6 grid lines will be returned
719 if (use_left_half) {
720 s.push_back(std::make_pair(norm_z, Geom::Point(grid->origin[Geom::X], y_proj_along_z_max)));
721 s.push_back(std::make_pair(norm_x, Geom::Point(grid->origin[Geom::X], y_proj_along_x_min)));
722 s.push_back(std::make_pair(Geom::Point(1, 0), Geom::Point(x_max, 0)));
723 }
724
725 if (use_right_half) {
726 s.push_back(std::make_pair(norm_z, Geom::Point(grid->origin[Geom::X], y_proj_along_z_min)));
727 s.push_back(std::make_pair(norm_x, Geom::Point(grid->origin[Geom::X], y_proj_along_x_max)));
728 s.push_back(std::make_pair(Geom::Point(1, 0), Geom::Point(x_min, 0)));
729 }
730
731 return s;
732 }
733
_addSnappedLine(IntermSnapResults & isr,Geom::Point const & snapped_point,Geom::Coord const & snapped_distance,SnapSourceType const & source,long source_num,Geom::Point const & normal_to_line,Geom::Point const & point_on_line) const734 void CanvasAxonomGridSnapper::_addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) const
735 {
736 SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line);
737 isr.grid_lines.push_back(dummy);
738 }
739
_addSnappedPoint(IntermSnapResults & isr,Geom::Point const & snapped_point,Geom::Coord const & snapped_distance,SnapSourceType const & source,long source_num,bool constrained_snap) const740 void CanvasAxonomGridSnapper::_addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
741 {
742 SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
743 isr.points.push_back(dummy);
744 }
745
_addSnappedLinePerpendicularly(IntermSnapResults & isr,Geom::Point const & snapped_point,Geom::Coord const & snapped_distance,SnapSourceType const & source,long source_num,bool constrained_snap) const746 void CanvasAxonomGridSnapper::_addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
747 {
748 SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID_PERPENDICULAR, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
749 isr.points.push_back(dummy);
750 }
751
ThisSnapperMightSnap() const752 bool CanvasAxonomGridSnapper::ThisSnapperMightSnap() const
753 {
754 return _snap_enabled && _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_GRID);
755 }
756
757
758 }; // namespace Inkscape
759
760
761 /*
762 Local Variables:
763 mode:c++
764 c-file-style:"stroustrup"
765 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
766 indent-tabs-mode:nil
767 fill-column:99
768 End:
769 */
770 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
771