Non-numeric Properties are not automatable
[ardour.git] / libs / canvas / rectangle.cc
1 /*
2     Copyright (C) 2011-2013 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
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.
9
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.
14
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.
18 */
19
20 #include <iostream>
21 #include <cairomm/context.h>
22 #include "pbd/stacktrace.h"
23 #include "pbd/compose.h"
24
25 #include "canvas/canvas.h"
26 #include "canvas/rectangle.h"
27 #include "canvas/debug.h"
28
29 using namespace std;
30 using namespace ArdourCanvas;
31
32 Rectangle::Rectangle (Canvas* c)
33         : Item (c)
34         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
35 {
36 }
37
38 Rectangle::Rectangle (Canvas* c, Rect const & rect)
39         : Item (c)
40         , _rect (rect)
41         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
42 {
43 }
44
45 Rectangle::Rectangle (Item* parent)
46         : Item (parent)
47         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
48 {
49 }
50
51 Rectangle::Rectangle (Item* parent, Rect const & rect)
52         : Item (parent)
53         , _rect (rect)
54         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
55 {
56 }
57
58 Rect
59 Rectangle::get_self_for_render () const
60 {
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.
65         */
66
67         return item_to_window (_rect.translate (_position), false);
68 }
69
70 void
71 Rectangle::render_self (Rect const & area, Cairo::RefPtr<Cairo::Context> context, Rect self) const
72 {
73         Rect r = self.intersection (area);
74
75         if (!r) {
76                 return;
77         }
78
79         Rect draw = r;
80
81         if (_fill && !_transparent) {
82                 if (_stops.empty()) {
83                         setup_fill_context (context);
84                 } else {
85                         setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
86                 }
87
88                 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
89                 context->fill ();
90         }
91
92         if (_outline && _outline_width && _outline_what) {
93
94                 setup_outline_context (context);
95
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).
104                  *
105                  * See the Cairo FAQ on single pixel lines for more
106                  * detail.
107                  */
108
109                 if (fmod (_outline_width, 2.0)  != 0.0) {
110                         const double shift = _outline_width * 0.5;
111                         self = self.translate (Duple (shift, shift));
112                 }
113
114                 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
115
116                         context->rectangle (self.x0, self.y0, self.width(), self.height());
117
118                 } else {
119
120                         if (_outline_what & LEFT) {
121                                 context->move_to (self.x0, self.y0);
122                                 context->line_to (self.x0, self.y1);
123                         }
124
125                         if (_outline_what & TOP) {
126                                 context->move_to (self.x0, self.y0);
127                                 context->line_to (self.x1, self.y0);
128                         }
129
130                         if (_outline_what & BOTTOM) {
131                                 context->move_to (self.x0, self.y1);
132                                 context->line_to (self.x1, self.y1);
133                         }
134
135                         if (_outline_what & RIGHT) {
136                                 context->move_to (self.x1, self.y0);
137                                 context->line_to (self.x1, self.y1);
138                         }
139                 }
140
141                 context->stroke ();
142         }
143 }
144
145 void
146 Rectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
147 {
148         render_self (area, context, get_self_for_render ());
149 }
150
151 void
152 Rectangle::compute_bounding_box () const
153 {
154         if (!_rect.empty()) {
155                 Rect r = _rect.fix ();
156
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.
160
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
167                    inside).
168
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).
173
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.
178
179                    hence ... the bounding box is width * 0.5 larger
180                    than the rectangle itself.
181                 */
182
183                 _bounding_box = r.expand (1.0 + _outline_width * 0.5);
184         }
185
186         _bounding_box_dirty = false;
187 }
188
189 void
190 Rectangle::set (Rect const & r)
191 {
192         /* We don't update the bounding box here; it's just
193            as cheap to do it when asked.
194         */
195
196         if (r != _rect) {
197
198                 begin_change ();
199
200                 _rect = r;
201
202                 _bounding_box_dirty = true;
203                 end_change ();
204         }
205 }
206
207 void
208 Rectangle::set_x0 (Coord x0)
209 {
210         if (x0 != _rect.x0) {
211                 begin_change ();
212
213                 _rect.x0 = x0;
214
215                 _bounding_box_dirty = true;
216                 end_change ();
217         }
218 }
219
220 void
221 Rectangle::set_y0 (Coord y0)
222 {
223         if (y0 != _rect.y0) {
224                 begin_change ();
225
226                 _rect.y0 = y0;
227
228                 _bounding_box_dirty = true;
229                 end_change();
230         }
231 }
232
233 void
234 Rectangle::set_x1 (Coord x1)
235 {
236         if (x1 != _rect.x1) {
237                 begin_change ();
238
239                 _rect.x1 = x1;
240
241                 _bounding_box_dirty = true;
242                 end_change ();
243         }
244 }
245
246 void
247 Rectangle::set_y1 (Coord y1)
248 {
249         if (y1 != _rect.y1) {
250                 begin_change ();
251
252                 _rect.y1 = y1;
253
254                 _bounding_box_dirty = true;
255                 end_change ();
256         }
257 }
258
259 void
260 Rectangle::set_outline_what (What what)
261 {
262         if (what != _outline_what) {
263                 begin_visual_change ();
264                 _outline_what = what;
265                 end_visual_change ();
266         }
267 }
268
269 double
270 Rectangle::vertical_fraction (double y) const
271 {
272         /* y is in canvas coordinates */
273
274         Duple i (canvas_to_item (Duple (0, y)));
275         Rect r = bounding_box();
276         if (!r) {
277                 return 0; /* not really correct, but what else can we do? */
278         }
279
280         Rect bbox (r);
281
282         if (i.y < bbox.y0 || i.y >= bbox.y1) {
283                 return 0;
284         }
285
286         /* convert to fit Cairo origin model (origin at upper left)
287          */
288
289         return 1.0 - ((i.y - bbox.y0) / bbox.height());
290 }