2 Copyright (C) 2011-2013 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <cairomm/context.h>
22 #include "pbd/stacktrace.h"
23 #include "pbd/compose.h"
25 #include "canvas/canvas.h"
26 #include "canvas/rectangle.h"
27 #include "canvas/debug.h"
30 using namespace ArdourCanvas;
32 Rectangle::Rectangle (Canvas* c)
34 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
38 Rectangle::Rectangle (Canvas* c, Rect const & rect)
41 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
45 Rectangle::Rectangle (Item* parent)
47 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
51 Rectangle::Rectangle (Item* parent, Rect const & rect)
54 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
59 Rectangle::get_self_for_render () const
61 /* In general, a Rectangle will have a _position of (0,0) within its
62 parent, and its extent is actually defined by _rect. But in the
63 unusual case that _position is set to something other than (0,0),
64 we should take that into account when rendering.
67 return item_to_window (_rect.translate (_position), false);
71 Rectangle::render_self (Rect const & area, Cairo::RefPtr<Cairo::Context> context, Rect self) const
73 Rect r = self.intersection (area);
81 if (_fill && !_transparent) {
83 setup_fill_context (context);
85 setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
88 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
92 if (_outline && _outline_width && _outline_what) {
94 setup_outline_context (context);
96 /* the goal here is that if the border is 1 pixel
97 * thick, it will precisely align with the corner
98 * coordinates of the rectangle. So if the rectangle
99 * has a left edge at 0 and a right edge at 10, then
100 * the left edge must span 0..1, the right edge
101 * must span 10..11 because the first and final pixels
102 * to be colored are actually "at" 0.5 and 10.5 (midway
103 * between the integer coordinates).
105 * See the Cairo FAQ on single pixel lines for more
109 if (fmod (_outline_width, 2.0) != 0.0) {
110 const double shift = _outline_width * 0.5;
111 self = self.translate (Duple (shift, shift));
114 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
116 context->rectangle (self.x0, self.y0, self.width(), self.height());
120 if (_outline_what & LEFT) {
121 context->move_to (self.x0, self.y0);
122 context->line_to (self.x0, self.y1);
125 if (_outline_what & TOP) {
126 context->move_to (self.x0, self.y0);
127 context->line_to (self.x1, self.y0);
130 if (_outline_what & BOTTOM) {
131 context->move_to (self.x0, self.y1);
132 context->line_to (self.x1, self.y1);
135 if (_outline_what & RIGHT) {
136 context->move_to (self.x1, self.y0);
137 context->line_to (self.x1, self.y1);
146 Rectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
148 render_self (area, context, get_self_for_render ());
152 Rectangle::compute_bounding_box () const
154 if (!_rect.empty()) {
155 Rect r = _rect.fix ();
157 /* if the outline is 1 pixel, then the actual
158 bounding box is 0.5 pixels outside the stated
159 corners of the rectangle.
161 if the outline is 2 pixels, then the actual
162 bounding box is 1.0 pixels outside the stated
163 corners of the rectangle (so that the middle
164 of the 2 pixel wide border passes through
165 the corners, alternatively described as 1 row
166 of pixels outside of the corners, and 1 row
169 if the outline is 3 pixels, then the actual
170 bounding box is 1.5 outside the stated corners
171 of the rectangle (so that the middle row of
172 pixels of the border passes through the corners).
174 if the outline is 4 pixels, then the actual bounding
175 box is 2.0 pixels outside the stated corners
176 of the rectangle, so that the border consists
177 of 2 pixels outside the corners and 2 pixels inside.
179 hence ... the bounding box is width * 0.5 larger
180 than the rectangle itself.
183 _bounding_box = r.expand (1.0 + _outline_width * 0.5);
186 _bounding_box_dirty = false;
190 Rectangle::set (Rect const & r)
192 /* We don't update the bounding box here; it's just
193 as cheap to do it when asked.
202 _bounding_box_dirty = true;
208 Rectangle::set_x0 (Coord x0)
210 if (x0 != _rect.x0) {
215 _bounding_box_dirty = true;
221 Rectangle::set_y0 (Coord y0)
223 if (y0 != _rect.y0) {
228 _bounding_box_dirty = true;
234 Rectangle::set_x1 (Coord x1)
236 if (x1 != _rect.x1) {
241 _bounding_box_dirty = true;
247 Rectangle::set_y1 (Coord y1)
249 if (y1 != _rect.y1) {
254 _bounding_box_dirty = true;
260 Rectangle::set_outline_what (What what)
262 if (what != _outline_what) {
263 begin_visual_change ();
264 _outline_what = what;
265 end_visual_change ();
270 Rectangle::vertical_fraction (double y) const
272 /* y is in canvas coordinates */
274 Duple i (canvas_to_item (Duple (0, y)));
275 Rect r = bounding_box();
277 return 0; /* not really correct, but what else can we do? */
282 if (i.y < bbox.y0 || i.y >= bbox.y1) {
286 /* convert to fit Cairo origin model (origin at upper left)
289 return 1.0 - ((i.y - bbox.y0) / bbox.height());