Update Midnam DTD URI, use midi.org
[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 void
59 Rectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) 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         Rect self (item_to_window (_rect.translate (_position), false));
68         const Rect draw = self.intersection (area);
69
70         if (!draw) {
71                 return;
72         }
73
74         if (_fill && !_transparent) {
75                 if (_stops.empty()) {
76                         setup_fill_context (context);
77                 } else {
78                         setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
79                 }
80
81                 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
82                 context->fill ();
83         }
84
85         if (_outline && _outline_width && _outline_what) {
86
87                 setup_outline_context (context);
88
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).
97                  *
98                  * See the Cairo FAQ on single pixel lines for more
99                  * detail.
100                  */
101
102                 if (fmod (_outline_width, 2.0)  != 0.0) {
103                         const double shift = _outline_width * 0.5;
104                         self = self.translate (Duple (shift, shift));
105                 }
106
107                 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
108
109                         context->rectangle (self.x0, self.y0, self.width(), self.height());
110
111                 } else {
112
113                         if (_outline_what & LEFT) {
114                                 context->move_to (self.x0, self.y0);
115                                 context->line_to (self.x0, self.y1);
116                         }
117
118                         if (_outline_what & TOP) {
119                                 context->move_to (self.x0, self.y0);
120                                 context->line_to (self.x1, self.y0);
121                         }
122
123                         if (_outline_what & BOTTOM) {
124                                 context->move_to (self.x0, self.y1);
125                                 context->line_to (self.x1, self.y1);
126                         }
127
128                         if (_outline_what & RIGHT) {
129                                 context->move_to (self.x1, self.y0);
130                                 context->line_to (self.x1, self.y1);
131                         }
132                 }
133
134                 context->stroke ();
135         }
136 }
137
138 void
139 Rectangle::compute_bounding_box () const
140 {
141         if (!_rect.empty()) {
142                 Rect r = _rect.fix ();
143
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.
147
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
154                    inside).
155
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).
160
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.
165
166                    hence ... the bounding box is width * 0.5 larger
167                    than the rectangle itself.
168                 */
169
170                 _bounding_box = r.expand (1.0 + _outline_width * 0.5);
171         }
172
173         _bounding_box_dirty = false;
174 }
175
176 void
177 Rectangle::set (Rect const & r)
178 {
179         /* We don't update the bounding box here; it's just
180            as cheap to do it when asked.
181         */
182
183         if (r != _rect) {
184
185                 begin_change ();
186
187                 _rect = r;
188
189                 _bounding_box_dirty = true;
190                 end_change ();
191         }
192 }
193
194 void
195 Rectangle::set_x0 (Coord x0)
196 {
197         if (x0 != _rect.x0) {
198                 begin_change ();
199
200                 _rect.x0 = x0;
201
202                 _bounding_box_dirty = true;
203                 end_change ();
204         }
205 }
206
207 void
208 Rectangle::set_y0 (Coord y0)
209 {
210         if (y0 != _rect.y0) {
211                 begin_change ();
212
213                 _rect.y0 = y0;
214
215                 _bounding_box_dirty = true;
216                 end_change();
217         }
218 }
219
220 void
221 Rectangle::set_x1 (Coord x1)
222 {
223         if (x1 != _rect.x1) {
224                 begin_change ();
225
226                 _rect.x1 = x1;
227
228                 _bounding_box_dirty = true;
229                 end_change ();
230         }
231 }
232
233 void
234 Rectangle::set_y1 (Coord y1)
235 {
236         if (y1 != _rect.y1) {
237                 begin_change ();
238
239                 _rect.y1 = y1;
240
241                 _bounding_box_dirty = true;
242                 end_change ();
243         }
244 }
245
246 void
247 Rectangle::set_outline_what (What what)
248 {
249         if (what != _outline_what) {
250                 begin_visual_change ();
251                 _outline_what = what;
252                 end_visual_change ();
253         }
254 }
255
256 double
257 Rectangle::vertical_fraction (double y) const
258 {
259         /* y is in canvas coordinates */
260
261         Duple i (canvas_to_item (Duple (0, y)));
262         Rect r = bounding_box();
263         if (!r) {
264                 return 0; /* not really correct, but what else can we do? */
265         }
266
267         Rect bbox (r);
268
269         if (i.y < bbox.y0 || i.y >= bbox.y1) {
270                 return 0;
271         }
272
273         /* convert to fit Cairo origin model (origin at upper left)
274          */
275
276         return 1.0 - ((i.y - bbox.y0) / bbox.height());
277 }