Fix a couple of minor typos.
[ardour2.git] / gtk2_ardour / lineset.cc
blobc1761c539836318f41b5606d20b61bcd29fcd3a6
1 /*
2 Copyright (C) 2007 Paul Davis
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 #include "lineset.h"
19 #include "rgb_macros.h"
21 #include <libgnomecanvas/libgnomecanvas.h>
22 #include <libgnomecanvasmm/group.h>
23 #include <libgnomecanvasmm/canvas.h>
25 #include <algorithm>
26 #include <cmath>
27 #include <iostream>
29 using namespace std;
31 namespace Gnome {
32 namespace Canvas {
34 LineSetClass LineSet::lineset_class;
36 //static const char* overlap_error_str = "LineSet error: Line overlap";
38 LineSet::Line::Line(double c, double w, uint32_t color)
39 : coord(c)
40 , width(w)
42 UINT_TO_RGBA (color, &r, &g, &b, &a);
45 /* Constructor for dummy lines that are used only with the coordinate */
46 LineSet::Line::Line(double c)
47 : coord(c)
51 void
52 LineSet::Line::set_color(uint32_t color)
54 UINT_TO_RGBA (color, &r, &g, &b, &a);
57 const Glib::Class&
58 LineSetClass::init()
60 if (!gtype_) {
61 class_init_func_ = &LineSetClass::class_init_function;
62 register_derived_type(Item::get_type());
65 return *this;
68 void
69 LineSetClass::class_init_function(void* /*g_class*/, void* /*class_data*/)
73 LineSet::LineSet(Group& parent, Orientation o)
74 : Glib::ObjectBase("GnomeCanvasLineSet")
75 , Item(Glib::ConstructParams(lineset_class.init()))
76 , cached_pos(lines.end())
77 , orientation(o)
78 , x1(*this, "x1", 0.0)
79 , y1(*this, "y1", 0.0)
80 , x2(*this, "x2", 0.0)
81 , y2(*this, "y2", 0.0)
82 , in_update(false)
83 , update_region1(1.0)
84 , update_region2(0.0)
85 , bounds_changed(false)
86 , covered1(1.0) // covered1 > covered2 ==> nothing's covered
87 , covered2(0.0)
90 item_construct(parent);
92 property_x1().signal_changed().connect(sigc::mem_fun(*this, &LineSet::bounds_need_update));
93 property_y1().signal_changed().connect(sigc::mem_fun(*this, &LineSet::bounds_need_update));
94 property_x2().signal_changed().connect(sigc::mem_fun(*this, &LineSet::bounds_need_update));
95 property_y2().signal_changed().connect(sigc::mem_fun(*this, &LineSet::bounds_need_update));
98 LineSet::~LineSet()
102 bool
103 LineSet::line_compare(const Line& a, const Line& b)
105 return a.coord < b.coord;
108 void
109 LineSet::print_lines()
111 for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) {
112 cerr << " " << it->coord << " " << it->width << " " << (int)it->r << " " << (int)it->g << " " << (int)it->b << " " << (int)it->a << endl;
116 void
117 LineSet::move_line(double coord, double dest)
119 if (coord == dest) {
120 return;
123 Lines::iterator it = line_at(coord);
125 if (it != lines.end()) {
127 double width = it->width;
128 it->coord = dest;
130 Lines::iterator ins = lower_bound(lines.begin(), lines.end(), *it, line_compare);
132 lines.insert(ins, *it);
133 lines.erase(it);
135 if (coord > dest) {
136 region_needs_update(dest, coord + width);
137 } else {
138 region_needs_update(coord, dest + width);
143 void
144 LineSet::change_line_width(double coord, double width)
146 Lines::iterator it = line_at(coord);
148 if (it != lines.end()) {
149 Line& l = *it;
150 ++it;
152 if (it != lines.end()) {
153 if (l.coord + width > it->coord) {
154 //cerr << overlap_error_str << endl;
155 return;
159 l.width = width;
160 region_needs_update(coord, coord + width);
164 void
165 LineSet::change_line_color(double coord, uint32_t color)
167 Lines::iterator it = line_at(coord);
169 if (it != lines.end()) {
170 it->set_color(color);
171 region_needs_update(it->coord, it->coord + it->width);
175 void
176 LineSet::add_line(double coord, double width, uint32_t color)
178 Line l(coord, width, color);
180 Lines::iterator it = std::lower_bound(lines.begin(), lines.end(), l, line_compare);
182 /* overlap checking */
183 if (it != lines.end()) {
184 if (l.coord + l.width > it->coord) {
185 //cerr << overlap_error_str << endl;
186 return;
189 if (it != lines.begin()) {
190 --it;
191 if (l.coord < it->coord + it->width) {
192 //cerr << overlap_error_str << endl;
193 return;
195 ++it;
198 lines.insert(it, l);
199 region_needs_update(coord, coord + width);
202 void
203 LineSet::remove_line(double coord)
205 Lines::iterator it = line_at(coord);
207 if (it != lines.end()) {
208 double start = it->coord;
209 double end = start + it->width;
211 lines.erase(it);
213 region_needs_update(start, end);
217 void
218 LineSet::remove_lines(double c1, double c2)
220 if (!lines.empty()) {
221 region_needs_update(c1, c2);
225 void
226 LineSet::remove_until(double coord)
228 if (!lines.empty()) {
229 double first = lines.front().coord;
231 // code
233 region_needs_update(first, coord);
237 void
238 LineSet::remove_from(double coord)
240 if (!lines.empty()) {
241 double last = lines.back().coord + lines.back().width;
243 // code
245 region_needs_update(coord, last);
249 void
250 LineSet::clear()
252 if (!lines.empty()) {
253 double coord1 = lines.front().coord;
254 double coord2 = lines.back().coord + lines.back().width;
256 lines.clear();
257 region_needs_update(coord1, coord2);
262 * this function is optimized to work faster if we access elements that are adjacent to each other.
263 * so if a large number of lines are modified, it is wise to modify them in sorted order.
265 LineSet::Lines::iterator
266 LineSet::line_at(double coord)
268 if (cached_pos != lines.end()) {
269 if (coord < cached_pos->coord) {
270 /* backward search */
271 while (--cached_pos != lines.end()) {
272 if (cached_pos->coord <= coord) {
273 if (cached_pos->coord + cached_pos->width < coord) {
274 /* coord is between two lines */
275 return lines.end();
276 } else {
277 return cached_pos;
281 } else {
282 /* forward search */
283 while (cached_pos != lines.end()) {
284 if (cached_pos->coord > coord) {
285 /* we searched past the line that we want, so now see
286 if the previous line includes the coordinate */
287 --cached_pos;
288 if (cached_pos->coord + cached_pos->width >= coord) {
289 return cached_pos;
290 } else {
291 return lines.end();
294 ++cached_pos;
297 } else {
298 /* initialize the cached position */
299 Line dummy(coord);
301 cached_pos = lower_bound(lines.begin(), lines.end(), dummy, line_compare);
303 /* The iterator found should point to the element after the one we want. */
304 --cached_pos;
306 if (cached_pos != lines.end()) {
307 if (cached_pos->coord <= coord) {
308 if (cached_pos->coord + cached_pos->width >= coord) {
309 return cached_pos;
310 } else {
311 return lines.end();
313 } else {
314 return lines.end();
316 } else {
317 return lines.end();
321 return lines.end();
324 void
325 LineSet::redraw_request (ArtDRect const & r)
327 int x0, y0, x1, y1;
328 Canvas& cv = *get_canvas();
330 //cerr << "redraw request: " << r.x0 << " " << r.y0 << " " << r.x1 << " " << r.y1 << endl;
332 double fx0 = r.x0;
333 if (fx0 > INT_MAX) {
334 fx0 = INT_MAX;
337 double fx1 = r.x1;
338 if (fx1 > INT_MAX) {
339 fx1 = INT_MAX;
342 cv.w2c (fx0, r.y0, x0, y0);
343 cv.w2c (fx1, r.y1, x1, y1);
345 cv.request_redraw(x0, y0, x1, y1);
348 void
349 LineSet::update_lines(bool need_redraw)
351 //cerr << "update_lines need_redraw=" << need_redraw << endl;
352 if (!need_redraw) {
353 update_region1 = 1.0;
354 update_region2 = 0.0;
355 return;
358 if (update_region2 > update_region1) {
359 ArtDRect redraw;
360 LineSet::bounds_vfunc(&redraw.x0, &redraw.y0, &redraw.x1, &redraw.y1);
361 i2w(redraw.x0, redraw.y0);
362 i2w(redraw.x1, redraw.y1);
364 if (orientation == Vertical) {
365 redraw.x1 = redraw.x0 + update_region2;
366 redraw.x0 += update_region1;
367 } else {
368 redraw.y1 = redraw.y0 + update_region2;
369 redraw.y0 += update_region1;
371 redraw_request(redraw);
372 update_region1 = 1.0;
373 update_region2 = 0.0;
376 // if we need to calculate what becomes visible, use some of this
377 //cv.c2w (0, 0, world_v[X1], world_v[Y1]);
378 //cv.c2w (cv.get_width(), cv.get_height(), world_v[X2], world_v[Y2]);
382 * return false if a full redraw request has been made.
383 * return true if nothing or only parts of the rect area has been requested for redraw
385 bool
386 LineSet::update_bounds()
388 GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj());
389 ArtDRect old_b;
390 ArtDRect new_b;
391 ArtDRect redraw;
392 Canvas& cv = *get_canvas();
394 /* store the old bounding box */
395 old_b.x0 = item->x1;
396 old_b.y0 = item->y1;
397 old_b.x1 = item->x2;
398 old_b.y1 = item->y2;
399 LineSet::bounds_vfunc(&new_b.x0, &new_b.y0, &new_b.x1, &new_b.y1);
401 i2w(new_b.x0, new_b.y0);
402 i2w(new_b.x1, new_b.y1);
404 item->x1 = new_b.x0;
405 item->y1 = new_b.y0;
406 item->x2 = new_b.x1;
407 item->y2 = new_b.y1;
409 /* Update bounding box used in rendering function */
411 double fx0 = new_b.x0;
412 if (fx0 > INT_MAX) {
413 fx0 = INT_MAX;
416 double fx1 = new_b.x1;
417 if (fx1 > INT_MAX) {
418 fx1 = INT_MAX;
421 cv.w2c (fx0, new_b.y0, bbox.x0, bbox.y0);
422 cv.w2c (fx1, new_b.y1, bbox.x1, bbox.y1);
425 * if the first primary axis property (x1 for Vertical, y1 for Horizontal) changed, we must redraw everything,
426 * because lines are positioned relative to this coordinate. Please excuse the confusion resulting from
427 * gnome canvas coordinate numbering (1, 2) and libart's (0, 1).
429 if (orientation == Vertical) {
430 if (new_b.x0 == old_b.x0) {
431 /* No need to update everything */
432 if (new_b.y0 != old_b.y0) {
433 redraw.x0 = old_b.x0;
434 redraw.y0 = min(old_b.y0, new_b.y0);
435 redraw.x1 = old_b.x1;
436 redraw.y1 = max(old_b.y0, new_b.y0);
437 redraw_request(redraw);
439 if (new_b.y1 != old_b.y1) {
440 redraw.x0 = old_b.x0;
441 redraw.y0 = min(old_b.y1, new_b.y1);
442 redraw.x1 = old_b.x1;
443 redraw.y1 = max(old_b.y1, new_b.y1);
444 redraw_request(redraw);
447 if (new_b.x1 > old_b.x1) {
448 // we have a larger area ==> possibly more lines
449 request_lines(old_b.x1, new_b.x1);
450 redraw.x0 = old_b.x1;
451 redraw.y0 = min(old_b.y0, new_b.y0);
452 redraw.x1 = new_b.x1;
453 redraw.y1 = max(old_b.y1, new_b.y1);
454 redraw_request(redraw);
455 } else if (new_b.x1 < old_b.x1) {
456 remove_lines(new_b.x1, old_b.x1);
457 redraw.x0 = new_b.x1;
458 redraw.y0 = min(old_b.y0, new_b.y0);
459 redraw.x1 = old_b.x1;
460 redraw.y1 = max(old_b.y1, new_b.y1);
461 redraw_request(redraw);
463 return true;
464 } else {
465 /* update everything */
466 //cerr << "update everything" << endl;
467 art_drect_union(&redraw, &old_b, &new_b);
468 redraw_request(redraw);
469 return false;
471 } else {
472 if (new_b.y0 == old_b.y0) {
473 /* No need to update everything */
474 if (new_b.x0 != old_b.x0) {
475 redraw.y0 = old_b.y0;
476 redraw.x0 = min(old_b.x0, new_b.x0);
477 redraw.y1 = old_b.y1;
478 redraw.x1 = max(old_b.x0, new_b.x0);
479 redraw_request(redraw);
481 if (new_b.x1 != old_b.x1) {
482 redraw.y0 = old_b.y0;
483 redraw.x0 = min(old_b.x1, new_b.x1);
484 redraw.y1 = old_b.y1;
485 redraw.x1 = max(old_b.x1, new_b.x1);
486 redraw_request(redraw);
489 if (new_b.y1 > old_b.y1) {
490 // we have a larger area ==> possibly more lines
491 request_lines(old_b.y1, new_b.y1);
492 redraw.y0 = old_b.y1;
493 redraw.x0 = min(old_b.x0, new_b.x0);
494 redraw.y1 = new_b.y1;
495 redraw.x1 = max(old_b.x1, new_b.x1);
496 redraw_request(redraw);
497 } else if (new_b.y1 < old_b.y1) {
498 remove_lines(new_b.y1, old_b.y1);
499 redraw.y0 = new_b.y1;
500 redraw.x0 = min(old_b.x0, new_b.x0);
501 redraw.y1 = old_b.y1;
502 redraw.x1 = max(old_b.x1, new_b.x1);
503 redraw_request(redraw);
505 return true;
506 } else {
507 /* update everything */
508 art_drect_union(&redraw, &old_b, &new_b);
509 redraw_request(redraw);
510 return false;
516 * what to do here?
517 * 1. find out if any line data has been modified since last update.
518 * N. find out if the item moved. if it moved, the old bbox and the new bbox need to be updated.
520 void
521 LineSet::update_vfunc(double* /*affine*/, ArtSVP* /*clip_path*/, int /*flags*/)
523 GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj());
524 bool lines_need_redraw = true;
527 * need to call gnome_canvas_item_update here, to unset the need_update flag.
528 * but a call to Gnome::Canvas::Item::update_vfunc results in infinite recursion.
529 * that function is declared in gnome_canvas.c so no way to call it directly:
530 * Item::update_vfunc(affine, clip_path, flags);
531 * So just copy the code from that function. This has to be a bug or
532 * something I haven't figured out.
534 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_UPDATE);
535 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_AFFINE);
536 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_CLIP);
537 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_VIS);
539 //cerr << "update {" << endl;
540 in_update = true;
542 // ahh. We must update bounds no matter what. If the group position changed,
543 // there is no way that we are notified of that.
545 //if (bounds_changed) {
546 lines_need_redraw = update_bounds();
547 bounds_changed = false;
550 update_lines(lines_need_redraw);
552 in_update = false;
553 //cerr << "}" << endl;
556 void
557 LineSet::draw_vfunc(const Glib::RefPtr<Gdk::Drawable>& /*drawable*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/)
559 cerr << "please don't use the GnomeCanvasLineSet item in a non-aa Canvas" << endl;
560 abort();
563 inline void
564 LineSet::paint_vert(GnomeCanvasBuf* buf, LineSet::Line& line, int x1, int y1, int x2, int y2)
566 if (line.width == 1.0) {
567 PAINT_VERTA(buf, line.r, line.g, line.b, line.a, x1, y1, y2);
568 } else {
569 PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2);
573 inline void
574 LineSet::paint_horiz(GnomeCanvasBuf* buf, LineSet::Line& line, int x1, int y1, int x2, int y2)
576 if (line.width == 1.0) {
577 PAINT_HORIZA(buf, line.r, line.g, line.b, line.a, x1, x2, y1);
578 } else {
579 PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2);
583 void
584 LineSet::render_vfunc(GnomeCanvasBuf* buf)
586 ArtIRect rect;
587 int pos0, pos1, offset;
589 if (buf->is_bg) {
590 gnome_canvas_buf_ensure_buf (buf);
591 buf->is_bg = FALSE;
594 /* get the rect that we are rendering to */
595 art_irect_intersect(&rect, &bbox, &buf->rect);
597 #if 0
598 /* DEBUG render bounding box for this region. should result in the full
599 bounding box when all rendering regions are finished */
600 PAINT_BOX(buf, 0xaa, 0xaa, 0xff, 0xbb, rect.x0, rect.y0, rect.x1, rect.y1);
601 #endif
603 #if 0
604 /* harlequin debugging, shows the rect that is actually drawn, distinct from
605 rects from other render cycles */
606 gint r, g, b, a;
607 r = random() % 0xff;
608 g = random() % 0xff;
609 b = random() % 0xff;
610 PAINT_BOX(buf, r, g, b, 0x33, rect.x0, rect.y0, rect.x1, rect.y1);
611 #endif
613 if (lines.empty()) {
614 return;
617 Lines::iterator it = lines.begin();
618 Lines::iterator end = --lines.end();
621 * The first and the last line in this render have to be handled separately from those in between, because those lines
622 * may be cut off at the ends.
625 if (orientation == Vertical) {
626 offset = bbox.x0;
628 // skip parts of lines that are to the right of the buffer, and paint the last line visible
629 for (; end != lines.end(); --end) {
630 pos0 = ((int) floor(end->coord)) + offset;
632 if (pos0 < rect.x1) {
633 pos1 = min((pos0 + (int) floor(end->width)), rect.x1);
634 if (pos0 < rect.x0 && pos1 < rect.x0) {
635 return;
638 paint_vert(buf, *end, pos0, rect.y0, pos1, rect.y1);
639 break;
643 if (end == lines.end()) {
644 return;
647 // skip parts of lines that are to the left of the buffer
648 for (; it != end; ++it) {
649 pos0 = ((int) floor(it->coord)) + offset;
650 pos1 = pos0 + ((int) floor(it->width));
652 if (pos1 > rect.x0) {
653 pos0 = max(pos0, rect.x0);
654 paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1);
655 ++it;
656 break;
660 // render what's between the first and last lines
661 for (; it != end; ++it) {
662 pos0 = ((int) floor(it->coord)) + offset;
663 pos1 = pos0 + ((int) floor(it->width));
665 paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1);
667 } else {
668 offset = bbox.y0;
670 // skip parts of lines that are to the right of the buffer, and paint the last line visible
671 for (; end != lines.end(); --end) {
672 pos0 = ((int) floor(end->coord)) + offset;
674 if (pos0 < rect.y1) {
675 pos1 = min((pos0 + (int) floor(end->width)), rect.y1);
676 if (pos0 < rect.y0 && pos1 < rect.y0) {
677 return;
680 paint_horiz(buf, *end, rect.x0, pos0, rect.x1, pos1);
681 break;
685 if (end == lines.end()) {
686 return;
689 // skip parts of lines that are to the left of the buffer
690 for (; it != end; ++it) {
691 pos0 = ((int) floor(it->coord)) + offset;
692 pos1 = pos0 + ((int) floor(it->width));
694 if (pos1 > rect.y0) {
695 pos0 = max(pos0, rect.y0);
696 paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1);
697 ++it;
698 break;
702 // render what's between the first and last lines
703 for (; it != end; ++it) {
704 pos0 = ((int) floor(it->coord)) + offset;
705 pos1 = pos0 + ((int) floor(it->width));
706 paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1);
711 void
712 LineSet::bounds_vfunc(double* _x1, double* _y1, double* _x2, double* _y2)
714 *_x1 = x1;
715 *_y1 = y1;
716 *_x2 = x2 + 1;
717 *_y2 = y2 + 1;
721 double
722 LineSet::point_vfunc(double x, double y, int /*cx*/, int /*cy*/, GnomeCanvasItem** actual_item)
724 double x1, y1, x2, y2;
725 double dx, dy;
727 LineSet::bounds_vfunc(&x1, &y1, &x2, &y2);
729 *actual_item = gobj();
731 if (x < x1) {
732 dx = x1 - x;
733 } else if (x > x2) {
734 dx = x - x2;
735 } else {
736 dx = 0.0;
739 if (y < y1) {
740 dy = y1 - y;
741 } else if (y > y2) {
742 dy = y - y2;
743 } else {
744 if (dx == 0.0) {
745 // point is inside
746 return 0.0;
747 } else {
748 dy = 0.0;
752 return sqrt (dx * dx + dy * dy);
755 /* If not overrided emit the signal */
756 void
757 LineSet::request_lines(double c1, double c2)
759 signal_request_lines(*this, c1, c2);
762 void
763 LineSet::bounds_need_update()
765 bounds_changed = true;
767 if (!in_update) {
768 request_update();
772 void
773 LineSet::region_needs_update(double coord1, double coord2)
775 if (update_region1 > update_region2) {
776 update_region1 = coord1;
777 update_region2 = coord2;
778 } else {
779 update_region1 = min(update_region1, coord1);
780 update_region2 = max(update_region2, coord2);
783 if (!in_update) {
784 request_update();
789 * These have been defined to avoid endless recursion with gnomecanvasmm.
790 * Don't know why this happens
792 bool LineSet::on_event(GdkEvent* /*p1*/)
794 return false;
797 void LineSet::realize_vfunc() { }
798 void LineSet::unrealize_vfunc() { }
799 void LineSet::map_vfunc() { }
800 void LineSet::unmap_vfunc() { }
802 } /* namespace Canvas */
803 } /* namespace Gnome */