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::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) 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 Rect self (item_to_window (_rect.translate (_position), false));
68 const Rect draw = self.intersection (area);
74 if (_fill && !_transparent) {
76 setup_fill_context (context);
78 setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
81 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
85 if (_outline && _outline_width && _outline_what) {
87 setup_outline_context (context);
89 /* the goal here is that if the border is 1 pixel
90 * thick, it will precisely align with the corner
91 * coordinates of the rectangle. So if the rectangle
92 * has a left edge at 0 and a right edge at 10, then
93 * the left edge must span 0..1, the right edge
94 * must span 10..11 because the first and final pixels
95 * to be colored are actually "at" 0.5 and 10.5 (midway
96 * between the integer coordinates).
98 * See the Cairo FAQ on single pixel lines for more
102 if (fmod (_outline_width, 2.0) != 0.0) {
103 const double shift = _outline_width * 0.5;
104 self = self.translate (Duple (shift, shift));
107 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
109 context->rectangle (self.x0, self.y0, self.width(), self.height());
113 if (_outline_what & LEFT) {
114 context->move_to (self.x0, self.y0);
115 context->line_to (self.x0, self.y1);
118 if (_outline_what & TOP) {
119 context->move_to (self.x0, self.y0);
120 context->line_to (self.x1, self.y0);
123 if (_outline_what & BOTTOM) {
124 context->move_to (self.x0, self.y1);
125 context->line_to (self.x1, self.y1);
128 if (_outline_what & RIGHT) {
129 context->move_to (self.x1, self.y0);
130 context->line_to (self.x1, self.y1);
139 Rectangle::compute_bounding_box () const
141 if (!_rect.empty()) {
142 Rect r = _rect.fix ();
144 /* if the outline is 1 pixel, then the actual
145 bounding box is 0.5 pixels outside the stated
146 corners of the rectangle.
148 if the outline is 2 pixels, then the actual
149 bounding box is 1.0 pixels outside the stated
150 corners of the rectangle (so that the middle
151 of the 2 pixel wide border passes through
152 the corners, alternatively described as 1 row
153 of pixels outside of the corners, and 1 row
156 if the outline is 3 pixels, then the actual
157 bounding box is 1.5 outside the stated corners
158 of the rectangle (so that the middle row of
159 pixels of the border passes through the corners).
161 if the outline is 4 pixels, then the actual bounding
162 box is 2.0 pixels outside the stated corners
163 of the rectangle, so that the border consists
164 of 2 pixels outside the corners and 2 pixels inside.
166 hence ... the bounding box is width * 0.5 larger
167 than the rectangle itself.
170 _bounding_box = r.expand (1.0 + _outline_width * 0.5);
173 _bounding_box_dirty = false;
177 Rectangle::set (Rect const & r)
179 /* We don't update the bounding box here; it's just
180 as cheap to do it when asked.
189 _bounding_box_dirty = true;
195 Rectangle::set_x0 (Coord x0)
197 if (x0 != _rect.x0) {
202 _bounding_box_dirty = true;
208 Rectangle::set_y0 (Coord y0)
210 if (y0 != _rect.y0) {
215 _bounding_box_dirty = true;
221 Rectangle::set_x1 (Coord x1)
223 if (x1 != _rect.x1) {
228 _bounding_box_dirty = true;
234 Rectangle::set_y1 (Coord y1)
236 if (y1 != _rect.y1) {
241 _bounding_box_dirty = true;
247 Rectangle::set_outline_what (What what)
249 if (what != _outline_what) {
250 begin_visual_change ();
251 _outline_what = what;
252 end_visual_change ();
257 Rectangle::vertical_fraction (double y) const
259 /* y is in canvas coordinates */
261 Duple i (canvas_to_item (Duple (0, y)));
262 Rect r = bounding_box();
264 return 0; /* not really correct, but what else can we do? */
269 if (i.y < bbox.y0 || i.y >= bbox.y1) {
273 /* convert to fit Cairo origin model (origin at upper left)
276 return 1.0 - ((i.y - bbox.y0) / bbox.height());