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"
28 #include "canvas/utils.h"
31 using namespace ArdourCanvas;
33 Rectangle::Rectangle (Canvas* c)
35 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
39 Rectangle::Rectangle (Canvas* c, Rect const & rect)
42 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
46 Rectangle::Rectangle (Item* parent)
48 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
52 Rectangle::Rectangle (Item* parent, Rect const & rect)
55 , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
60 Rectangle::get_self_for_render () const
62 /* In general, a Rectangle will have a _position of (0,0) within its
63 parent, and its extent is actually defined by _rect. But in the
64 unusual case that _position is set to something other than (0,0),
65 we should take that into account when rendering.
68 return item_to_window (_rect.translate (_position), false);
72 Rectangle::render_self (Rect const & area, Cairo::RefPtr<Cairo::Context> context, Rect self) const
74 Rect r = self.intersection (area);
82 if (_fill && !_transparent) {
84 setup_fill_context (context);
86 setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
89 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
93 if (_outline && _outline_width && _outline_what) {
95 setup_outline_context (context);
97 /* the goal here is that if the border is 1 pixel
98 * thick, it will precisely align with the corner
99 * coordinates of the rectangle. So if the rectangle
100 * has a left edge at 0 and a right edge at 10, then
101 * the left edge must span 0..1, the right edge
102 * must span 10..11 because the first and final pixels
103 * to be colored are actually "at" 0.5 and 10.5 (midway
104 * between the integer coordinates).
106 * See the Cairo FAQ on single pixel lines for more
110 if (fmod (_outline_width, 2.0) != 0.0) {
111 const double shift = _outline_width * 0.5;
112 self = self.translate (Duple (shift, shift));
115 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
117 context->rectangle (self.x0, self.y0, self.width(), self.height());
121 if (_outline_what & LEFT) {
122 context->move_to (self.x0, self.y0);
123 context->line_to (self.x0, self.y1);
126 if (_outline_what & TOP) {
127 context->move_to (self.x0, self.y0);
128 context->line_to (self.x1, self.y0);
131 if (_outline_what & BOTTOM) {
132 context->move_to (self.x0, self.y1);
133 context->line_to (self.x1, self.y1);
136 if (_outline_what & RIGHT) {
137 context->move_to (self.x1, self.y0);
138 context->line_to (self.x1, self.y1);
147 Rectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
149 render_self (area, context, get_self_for_render ());
153 Rectangle::compute_bounding_box () const
155 if (!_rect.empty()) {
156 Rect r = _rect.fix ();
158 /* if the outline is 1 pixel, then the actual
159 bounding box is 0.5 pixels outside the stated
160 corners of the rectangle.
162 if the outline is 2 pixels, then the actual
163 bounding box is 1.0 pixels outside the stated
164 corners of the rectangle (so that the middle
165 of the 2 pixel wide border passes through
166 the corners, alternatively described as 1 row
167 of pixels outside of the corners, and 1 row
170 if the outline is 3 pixels, then the actual
171 bounding box is 1.5 outside the stated corners
172 of the rectangle (so that the middle row of
173 pixels of the border passes through the corners).
175 if the outline is 4 pixels, then the actual bounding
176 box is 2.0 pixels outside the stated corners
177 of the rectangle, so that the border consists
178 of 2 pixels outside the corners and 2 pixels inside.
180 hence ... the bounding box is width * 0.5 larger
181 than the rectangle itself.
184 _bounding_box = r.expand (1.0 + _outline_width * 0.5);
187 _bounding_box_dirty = false;
191 Rectangle::set (Rect const & r)
193 /* We don't update the bounding box here; it's just
194 as cheap to do it when asked.
203 _bounding_box_dirty = true;
209 Rectangle::set_x0 (Coord x0)
211 if (x0 != _rect.x0) {
216 _bounding_box_dirty = true;
222 Rectangle::set_y0 (Coord y0)
224 if (y0 != _rect.y0) {
229 _bounding_box_dirty = true;
235 Rectangle::set_x1 (Coord x1)
237 if (x1 != _rect.x1) {
242 _bounding_box_dirty = true;
248 Rectangle::set_y1 (Coord y1)
250 if (y1 != _rect.y1) {
255 _bounding_box_dirty = true;
261 Rectangle::set_outline_what (What what)
263 if (what != _outline_what) {
264 begin_visual_change ();
265 _outline_what = what;
266 end_visual_change ();
271 Rectangle::vertical_fraction (double y) const
273 /* y is in canvas coordinates */
275 Duple i (canvas_to_item (Duple (0, y)));
276 Rect r = bounding_box();
278 return 0; /* not really correct, but what else can we do? */
283 if (i.y < bbox.y0 || i.y >= bbox.y1) {
287 /* convert to fit Cairo origin model (origin at upper left)
290 return 1.0 - ((i.y - bbox.y0) / bbox.height());