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.
19 #include "rgb_macros.h"
21 #include <libgnomecanvas/libgnomecanvas.h>
22 #include <libgnomecanvasmm/group.h>
23 #include <libgnomecanvasmm/canvas.h>
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)
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)
52 Lineset::Line::set_color(uint32_t color)
54 UINT_TO_RGBA (color, &r, &g, &b, &a);
61 class_init_func_ = &LinesetClass::class_init_function;
62 register_derived_type(Item::get_type());
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())
78 , x1(*this, "x1", 0.0)
79 , y1(*this, "y1", 0.0)
80 , x2(*this, "x2", 0.0)
81 , y2(*this, "y2", 0.0)
85 , bounds_changed(false)
86 , covered1(1.0) // covered1 > covered2 ==> nothing's covered
90 item_construct(parent);
92 property_x1().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update));
93 property_y1().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update));
94 property_x2().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update));
95 property_y2().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update));
103 Lineset::line_compare(const Line& a, const Line& b)
105 return a.coord < b.coord;
109 Lineset::print_lines()
111 for (Lines::iterator it = lines.begin(); it != lines.end(); ++it)
113 cerr << " " << it->coord << " " << it->width << " " << (int)it->r << " " << (int)it->g << " " << (int)it->b << " " << (int)it->a << endl;
118 Lineset::move_line(double coord, double dest)
124 Lines::iterator it = line_at(coord);
126 if (it != lines.end()) {
128 double width = it->width;
131 Lines::iterator ins = lower_bound(lines.begin(), lines.end(), *it, line_compare);
133 lines.insert(ins, *it);
137 region_needs_update(dest, coord + width);
139 region_needs_update(coord, dest + width);
145 Lineset::change_line_width(double coord, double width)
147 Lines::iterator it = line_at(coord);
149 if (it != lines.end()) {
153 if (it != lines.end()) {
154 if (l.coord + width > it->coord) {
155 //cerr << overlap_error_str << endl;
161 region_needs_update(coord, coord + width);
166 Lineset::change_line_color(double coord, uint32_t color)
168 Lines::iterator it = line_at(coord);
170 if (it != lines.end()) {
171 it->set_color(color);
172 region_needs_update(it->coord, it->coord + it->width);
177 Lineset::add_line(double coord, double width, uint32_t color)
179 Line l(coord, width, color);
181 Lines::iterator it = std::lower_bound(lines.begin(), lines.end(), l, line_compare);
183 /* overlap checking */
184 if (it != lines.end()) {
185 if (l.coord + l.width > it->coord) {
186 //cerr << overlap_error_str << endl;
190 if (it != lines.begin()) {
192 if (l.coord < it->coord + it->width) {
193 //cerr << overlap_error_str << endl;
200 region_needs_update(coord, coord + width);
204 Lineset::remove_line(double coord)
206 Lines::iterator it = line_at(coord);
208 if (it != lines.end()) {
209 double start = it->coord;
210 double end = start + it->width;
214 region_needs_update(start, end);
219 Lineset::remove_lines(double c1, double c2)
221 if (!lines.empty()) {
222 region_needs_update(c1, c2);
227 Lineset::remove_until(double coord)
229 if (!lines.empty()) {
230 double first = lines.front().coord;
234 region_needs_update(first, coord);
239 Lineset::remove_from(double coord)
241 if (!lines.empty()) {
242 double last = lines.back().coord + lines.back().width;
246 region_needs_update(coord, last);
253 if (!lines.empty()) {
254 double coord1 = lines.front().coord;
255 double coord2 = lines.back().coord + lines.back().width;
258 region_needs_update(coord1, coord2);
263 * this function is optimized to work faster if we access elements that are adjacent to each other.
264 * so if a large number of lines are modified, it is wise to modify them in sorted order.
266 Lineset::Lines::iterator
267 Lineset::line_at(double coord)
269 if (cached_pos != lines.end()) {
270 if (coord < cached_pos->coord) {
271 /* backward search */
272 while(--cached_pos != lines.end()) {
273 if (cached_pos->coord <= coord) {
274 if (cached_pos->coord + cached_pos->width < coord) {
275 /* coord is between two lines */
284 while(cached_pos != lines.end()) {
285 if (cached_pos->coord > coord) {
286 /* we searched past the line that we want, so now see
287 if the previous line includes the coordinate */
289 if (cached_pos->coord + cached_pos->width >= coord) {
299 /* initialize the cached position */
302 cached_pos = lower_bound(lines.begin(), lines.end(), dummy, line_compare);
304 /* The iterator found should point to the element after the one we want. */
307 if (cached_pos != lines.end()) {
308 if (cached_pos->coord <= coord) {
309 if (cached_pos->coord + cached_pos->width >= coord) {
326 Lineset::redraw_request(ArtIRect& r)
328 get_canvas()->request_redraw(r.x0, r.y0, r.x1, r.y1);
332 Lineset::redraw_request(ArtDRect& r)
335 Canvas& cv = *get_canvas();
337 //cerr << "redraw request: " << r.x0 << " " << r.y0 << " " << r.x1 << " " << r.y1 << endl;
339 cv.w2c(r.x0, r.y0, x0, y0);
340 cv.w2c(r.x1, r.y1, x1, y1);
341 cv.request_redraw(x0, y0, x1, y1);
345 Lineset::update_lines(bool need_redraw)
347 //cerr << "update_lines need_redraw=" << need_redraw << endl;
349 update_region1 = 1.0;
350 update_region2 = 0.0;
354 if (update_region2 > update_region1) {
356 Lineset::bounds_vfunc(&redraw.x0, &redraw.y0, &redraw.x1, &redraw.y1);
357 i2w(redraw.x0, redraw.y0);
358 i2w(redraw.x1, redraw.y1);
360 if (orientation == Vertical) {
361 redraw.x1 = redraw.x0 + update_region2;
362 redraw.x0 += update_region1;
364 redraw.y1 = redraw.y0 + update_region2;
365 redraw.y0 += update_region1;
367 redraw_request(redraw);
368 update_region1 = 1.0;
369 update_region2 = 0.0;
372 // if we need to calculate what becomes visible, use some of this
373 //cv.c2w (0, 0, world_v[X1], world_v[Y1]);
374 //cv.c2w (cv.get_width(), cv.get_height(), world_v[X2], world_v[Y2]);
378 * return false if a full redraw request has been made.
379 * return true if nothing or only parts of the rect area has been requested for redraw
382 Lineset::update_bounds()
384 GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj());
388 Canvas& cv = *get_canvas();
390 /* store the old bounding box */
395 Lineset::bounds_vfunc(&new_b.x0, &new_b.y0, &new_b.x1, &new_b.y1);
397 i2w(new_b.x0, new_b.y0);
398 i2w(new_b.x1, new_b.y1);
405 /* Update bounding box used in rendering function */
406 cv.w2c(new_b.x0, new_b.y0, bbox.x0, bbox.y0);
407 cv.w2c(new_b.x1, new_b.y1, bbox.x1, bbox.y1);
410 * if the first primary axis property (x1 for Vertical, y1 for Horizontal) changed, we must redraw everything,
411 * because lines are positioned relative to this coordinate. Please excuse the confusion resulting from
412 * gnome canvas coordinate numbering (1, 2) and libart's (0, 1).
414 if (orientation == Vertical) {
415 if (new_b.x0 == old_b.x0) {
416 /* No need to update everything */
417 if (new_b.y0 != old_b.y0) {
418 redraw.x0 = old_b.x0;
419 redraw.y0 = min(old_b.y0, new_b.y0);
420 redraw.x1 = old_b.x1;
421 redraw.y1 = max(old_b.y0, new_b.y0);
422 redraw_request(redraw);
424 if (new_b.y1 != old_b.y1) {
425 redraw.x0 = old_b.x0;
426 redraw.y0 = min(old_b.y1, new_b.y1);
427 redraw.x1 = old_b.x1;
428 redraw.y1 = max(old_b.y1, new_b.y1);
429 redraw_request(redraw);
432 if (new_b.x1 > old_b.x1) {
433 // we have a larger area ==> possibly more lines
434 request_lines(old_b.x1, new_b.x1);
435 redraw.x0 = old_b.x1;
436 redraw.y0 = min(old_b.y0, new_b.y0);
437 redraw.x1 = new_b.x1;
438 redraw.y1 = max(old_b.y1, new_b.y1);
439 redraw_request(redraw);
440 } else if (new_b.x1 < old_b.x1) {
441 remove_lines(new_b.x1, old_b.x1);
442 redraw.x0 = new_b.x1;
443 redraw.y0 = min(old_b.y0, new_b.y0);
444 redraw.x1 = old_b.x1;
445 redraw.y1 = max(old_b.y1, new_b.y1);
446 redraw_request(redraw);
450 /* update everything */
451 //cerr << "update everything" << endl;
452 art_drect_union(&redraw, &old_b, &new_b);
453 redraw_request(redraw);
457 if (new_b.y0 == old_b.y0) {
458 /* No need to update everything */
459 if (new_b.x0 != old_b.x0) {
460 redraw.y0 = old_b.y0;
461 redraw.x0 = min(old_b.x0, new_b.x0);
462 redraw.y1 = old_b.y1;
463 redraw.x1 = max(old_b.x0, new_b.x0);
464 redraw_request(redraw);
466 if (new_b.x1 != old_b.x1) {
467 redraw.y0 = old_b.y0;
468 redraw.x0 = min(old_b.x1, new_b.x1);
469 redraw.y1 = old_b.y1;
470 redraw.x1 = max(old_b.x1, new_b.x1);
471 redraw_request(redraw);
474 if (new_b.y1 > old_b.y1) {
475 // we have a larger area ==> possibly more lines
476 request_lines(old_b.y1, new_b.y1);
477 redraw.y0 = old_b.y1;
478 redraw.x0 = min(old_b.x0, new_b.x0);
479 redraw.y1 = new_b.y1;
480 redraw.x1 = max(old_b.x1, new_b.x1);
481 redraw_request(redraw);
482 } else if (new_b.y1 < old_b.y1) {
483 remove_lines(new_b.y1, old_b.y1);
484 redraw.y0 = new_b.y1;
485 redraw.x0 = min(old_b.x0, new_b.x0);
486 redraw.y1 = old_b.y1;
487 redraw.x1 = max(old_b.x1, new_b.x1);
488 redraw_request(redraw);
492 /* update everything */
493 art_drect_union(&redraw, &old_b, &new_b);
494 redraw_request(redraw);
502 * 1. find out if any line data has been modified since last update.
503 * N. find out if the item moved. if it moved, the old bbox and the new bbox need to be updated.
506 Lineset::update_vfunc(double* affine, ArtSVP* clip_path, int flags)
508 GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj());
509 bool lines_need_redraw = true;
512 * need to call gnome_canvas_item_update here, to unset the need_update flag.
513 * but a call to Gnome::Canvas::Item::update_vfunc results in infinite recursion.
514 * that function is declared in gnome_canvas.c so no way to call it directly:
515 * Item::update_vfunc(affine, clip_path, flags);
516 * So just copy the code from that function. This has to be a bug or
517 * something I haven't figured out.
519 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_UPDATE);
520 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_AFFINE);
521 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_CLIP);
522 GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_VIS);
524 //cerr << "update {" << endl;
527 // ahh. We must update bounds no matter what. If the group position changed,
528 // there is no way that we are notified of that.
530 //if (bounds_changed) {
531 lines_need_redraw = update_bounds();
532 bounds_changed = false;
535 update_lines(lines_need_redraw);
538 //cerr << "}" << endl;
542 Lineset::draw_vfunc(const Glib::RefPtr<Gdk::Drawable>& drawable, int x, int y, int width, int height)
544 cerr << "please don't use the GnomeCanvasLineset item in a non-aa Canvas" << endl;
549 Lineset::paint_vert(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2)
551 if (line.width == 1.0) {
552 PAINT_VERTA(buf, line.r, line.g, line.b, line.a, x1, y1, y2);
554 PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2);
559 Lineset::paint_horiz(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2)
561 if (line.width == 1.0) {
562 PAINT_HORIZA(buf, line.r, line.g, line.b, line.a, x1, x2, y1);
564 PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2);
569 Lineset::render_vfunc(GnomeCanvasBuf* buf)
572 int pos0, pos1, offset;
575 gnome_canvas_buf_ensure_buf (buf);
579 /* get the rect that we are rendering to */
580 art_irect_intersect(&rect, &bbox, &buf->rect);
583 /* DEBUG render bounding box for this region. should result in the full
584 bounding box when all rendering regions are finished */
585 PAINT_BOX(buf, 0xaa, 0xaa, 0xff, 0xbb, rect.x0, rect.y0, rect.x1, rect.y1);
589 /* harlequin debugging, shows the rect that is actually drawn, distinct from
590 rects from other render cycles */
595 PAINT_BOX(buf, r, g, b, 0x33, rect.x0, rect.y0, rect.x1, rect.y1);
602 Lines::iterator it = lines.begin();
603 Lines::iterator end = --lines.end();
606 * The first and the last line in this render have to be handled separately from those in between, because those lines
607 * may be cut off at the ends.
610 if (orientation == Vertical) {
613 // skip parts of lines that are to the right of the buffer, and paint the last line visible
614 for (; end != lines.end(); --end) {
615 pos0 = ((int) floor(end->coord)) + offset;
617 if (pos0 < rect.x1) {
618 pos1 = min((pos0 + (int) floor(end->width)), rect.x1);
619 if (pos0 < rect.x0 && pos1 < rect.x0) {
623 paint_vert(buf, *end, pos0, rect.y0, pos1, rect.y1);
628 if (end == lines.end()) {
632 // skip parts of lines that are to the left of the buffer
633 for (; it != end; ++it) {
634 pos0 = ((int) floor(it->coord)) + offset;
635 pos1 = pos0 + ((int) floor(it->width));
637 if (pos1 > rect.x0) {
638 pos0 = max(pos0, rect.x0);
639 paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1);
645 // render what's between the first and last lines
646 for (; it != end; ++it) {
647 pos0 = ((int) floor(it->coord)) + offset;
648 pos1 = pos0 + ((int) floor(it->width));
650 paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1);
655 // skip parts of lines that are to the right of the buffer, and paint the last line visible
656 for (; end != lines.end(); --end) {
657 pos0 = ((int) floor(end->coord)) + offset;
659 if (pos0 < rect.y1) {
660 pos1 = min((pos0 + (int) floor(end->width)), rect.y1);
661 if (pos0 < rect.y0 && pos1 < rect.y0) {
665 paint_horiz(buf, *end, rect.x0, pos0, rect.x1, pos1);
670 if (end == lines.end()) {
674 // skip parts of lines that are to the left of the buffer
675 for (; it != end; ++it) {
676 pos0 = ((int) floor(it->coord)) + offset;
677 pos1 = pos0 + ((int) floor(it->width));
679 if (pos1 > rect.y0) {
680 pos0 = max(pos0, rect.y0);
681 paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1);
687 // render what's between the first and last lines
688 for (; it != end; ++it) {
689 pos0 = ((int) floor(it->coord)) + offset;
690 pos1 = pos0 + ((int) floor(it->width));
691 paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1);
697 Lineset::bounds_vfunc(double* _x1, double* _y1, double* _x2, double* _y2)
707 Lineset::point_vfunc(double x, double y, int cx, int cy, GnomeCanvasItem** actual_item)
709 double x1, y1, x2, y2;
712 Lineset::bounds_vfunc(&x1, &y1, &x2, &y2);
714 *actual_item = gobj();
737 return sqrt (dx * dx + dy * dy);
740 /* If not overrided emit the signal */
742 Lineset::request_lines(double c1, double c2)
744 signal_request_lines(*this, c1, c2);
748 Lineset::bounds_need_update()
750 bounds_changed = true;
758 Lineset::region_needs_update(double coord1, double coord2)
760 if (update_region1 > update_region2) {
761 update_region1 = coord1;
762 update_region2 = coord2;
764 update_region1 = min(update_region1, coord1);
765 update_region2 = max(update_region2, coord2);
774 * These have been defined to avoid endless recursion with gnomecanvasmm.
775 * Don't know why this happens
777 bool Lineset::on_event(GdkEvent* p1)
782 void Lineset::realize_vfunc() { }
783 void Lineset::unrealize_vfunc() { }
784 void Lineset::map_vfunc() { }
785 void Lineset::unmap_vfunc() { }
787 } /* namespace Canvas */
788 } /* namespace Gnome */