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(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
));
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 */