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