Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / lily / side-position-interface.cc
blob4fe16e510b46ad871fd8e59a01e0b2f668889e2d
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1998--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "side-position-interface.hh"
22 #include <cmath> // ceil.
23 #include <algorithm>
25 using namespace std;
27 #include "axis-group-interface.hh"
28 #include "directional-element-interface.hh"
29 #include "grob.hh"
30 #include "grob-array.hh"
31 #include "main.hh"
32 #include "misc.hh"
33 #include "note-head.hh"
34 #include "pointer-group-interface.hh"
35 #include "staff-symbol-referencer.hh"
36 #include "staff-symbol.hh"
37 #include "stem.hh"
38 #include "string-convert.hh"
39 #include "system.hh"
40 #include "warn.hh"
42 void
43 Side_position_interface::add_support (Grob *me, Grob *e)
45 Pointer_group_interface::add_unordered_grob (me, ly_symbol2scm ("side-support-elements"), e);
48 /* Put the element next to the support, optionally taking in
49 account the extent of the support.
51 Does not take into account the extent of ME.
53 SCM
54 Side_position_interface::general_side_position (Grob *me, Axis a, bool use_extents,
55 bool include_my_extent,
56 bool pure, int start, int end,
57 Real *current_offset)
59 Real ss = Staff_symbol_referencer::staff_space (me);
61 extract_grob_set (me, "side-support-elements", support);
63 Grob *common = common_refpoint_of_array (support, me->get_parent (a), a);
64 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
65 bool include_staff =
66 staff_symbol
67 && a == Y_AXIS
68 && scm_is_number (me->get_property ("staff-padding"))
69 && !to_boolean (me->get_property ("quantize-position"));
71 Interval dim;
72 Interval staff_extents;
73 if (include_staff)
75 common = staff_symbol->common_refpoint (common, Y_AXIS);
76 staff_extents = staff_symbol->maybe_pure_extent (common, Y_AXIS, pure, start, end);
78 if (include_staff)
79 dim.unite (staff_extents);
82 Direction dir = get_grob_direction (me);
84 for (vsize i = 0; i < support.size (); i++)
86 Grob *e = support[i];
88 // In the case of a stem, we will find a note head as well
89 // ignoring the stem solves cyclic dependencies if the stem is
90 // attached to a cross-staff beam.
91 if (a == Y_AXIS
92 && Stem::has_interface (e)
93 && dir == - get_grob_direction (e))
94 continue;
96 if (e)
98 if (use_extents)
99 dim.unite (e->maybe_pure_extent (common, a, pure, start, end));
100 else
102 Real x = e->maybe_pure_coordinate (common, a, pure, start, end);
103 dim.unite (Interval (x, x));
108 if (dim.is_empty ())
109 dim = Interval (0, 0);
111 Real off = me->get_parent (a)->maybe_pure_coordinate (common, a, pure, start, end);
112 Real minimum_space = ss * robust_scm2double (me->get_property ("minimum-space"), -1);
114 Real total_off = dim.linear_combination (dir) - off;
115 total_off += dir * ss * robust_scm2double (me->get_property ("padding"), 0);
117 if (minimum_space >= 0
118 && dir
119 && total_off * dir < minimum_space)
120 total_off = minimum_space * dir;
122 if (include_my_extent)
124 Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
125 if (!iv.is_empty ())
127 if (!dir)
129 programming_error ("direction unknown, but aligned-side wanted");
130 dir = DOWN;
132 total_off += -iv[-dir];
136 if (current_offset)
137 total_off = dir * max (dir * total_off,
138 dir * (*current_offset));
141 /* FIXME: 1000 should relate to paper size. */
142 if (fabs (total_off) > 1000)
144 string msg
145 = String_convert::form_string ("Improbable offset for grob %s: %f",
146 me->name ().c_str (), total_off);
148 programming_error (msg);
149 if (strict_infinity_checking)
150 scm_misc_error (__FUNCTION__, "Improbable offset.", SCM_EOL);
152 return scm_from_double (total_off);
156 MAKE_SCHEME_CALLBACK (Side_position_interface, y_aligned_on_support_refpoints, 1);
158 Side_position_interface::y_aligned_on_support_refpoints (SCM smob)
160 return general_side_position (unsmob_grob (smob), Y_AXIS, false, false, false, 0, 0, 0);
163 MAKE_SCHEME_CALLBACK (Side_position_interface, pure_y_aligned_on_support_refpoints, 3);
165 Side_position_interface::pure_y_aligned_on_support_refpoints (SCM smob, SCM start, SCM end)
167 return general_side_position (unsmob_grob (smob), Y_AXIS, false, false,
168 true, scm_to_int (start), scm_to_int (end), 0);
173 Position next to support, taking into account my own dimensions and padding.
176 axis_aligned_side_helper (SCM smob, Axis a, bool pure, int start, int end, SCM current_off_scm)
178 Real r;
179 Real *current_off_ptr = 0;
180 if (scm_is_number (current_off_scm))
182 r = scm_to_double (current_off_scm);
183 current_off_ptr = &r;
186 return Side_position_interface::aligned_side (unsmob_grob (smob), a, pure, start, end, current_off_ptr);
190 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, x_aligned_side, 2, 1, "");
192 Side_position_interface::x_aligned_side (SCM smob, SCM current_off)
194 return axis_aligned_side_helper (smob, X_AXIS, false, 0, 0, current_off);
197 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, y_aligned_side, 2, 1, "");
199 Side_position_interface::y_aligned_side (SCM smob, SCM current_off)
201 return axis_aligned_side_helper (smob, Y_AXIS, false, 0, 0, current_off);
204 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, pure_y_aligned_side, 4, 1, "");
206 Side_position_interface::pure_y_aligned_side (SCM smob, SCM start, SCM end, SCM cur_off)
208 return axis_aligned_side_helper (smob, Y_AXIS, true,
209 scm_to_int (start),
210 scm_to_int (end),
211 cur_off);
214 MAKE_SCHEME_CALLBACK (Side_position_interface, calc_cross_staff, 1)
216 Side_position_interface::calc_cross_staff (SCM smob)
218 Grob *me = unsmob_grob (smob);
219 extract_grob_set (me, "side-support-elements", elts);
221 for (vsize i = 0; i < elts.size (); i++)
222 if (to_boolean (elts[i]->get_property ("cross-staff")))
223 return SCM_BOOL_T;
225 Grob *common = common_refpoint_of_array (elts, me->get_parent (Y_AXIS), Y_AXIS);
226 return scm_from_bool (common != me->get_parent (Y_AXIS));
230 Side_position_interface::aligned_side (Grob *me, Axis a, bool pure, int start, int end,
231 Real *current_off)
233 Direction dir = get_grob_direction (me);
235 Real o = scm_to_double (general_side_position (me, a, true, true, pure, start, end, current_off));
238 Maintain a minimum distance to the staff. This is similar to side
239 position with padding, but it will put adjoining objects on a row if
240 stuff sticks out of the staff a little.
242 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
243 if (staff && a == Y_AXIS)
245 if (to_boolean (me->get_property ("quantize-position")))
247 Grob *common = me->common_refpoint (staff, Y_AXIS);
248 Real my_off = me->get_parent (Y_AXIS)->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
249 Real staff_off = staff->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
250 Real ss = Staff_symbol::staff_space (staff);
251 Real position = 2 * (my_off + o - staff_off) / ss;
252 Real rounded = directed_round (position, dir);
253 Grob *head = me->get_parent (X_AXIS);
255 if (fabs (position) <= 2 * Staff_symbol_referencer::staff_radius (me) + 1
256 /* In case of a ledger lines, quantize even if we're outside the staff. */
257 || (Note_head::has_interface (head)
259 && abs (Staff_symbol_referencer::get_position (head)) > abs (position)))
261 o += (rounded - position) * 0.5 * ss;
262 if (Staff_symbol_referencer::on_line (me, int (rounded)))
263 o += dir * 0.5 * ss;
266 else if (scm_is_number (me->get_property ("staff-padding")) && dir)
268 Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
270 Real padding
271 = Staff_symbol_referencer::staff_space (me)
272 * scm_to_double (me->get_property ("staff-padding"));
274 Grob *common = me->common_refpoint (staff, Y_AXIS);
276 Interval staff_size = staff->maybe_pure_extent (common, Y_AXIS, pure, start, end);
277 Real diff = dir*staff_size[dir] + padding - dir * (o + iv[-dir]);
278 o += dir * max (diff, 0.0);
281 return scm_from_double (o);
284 void
285 Side_position_interface::set_axis (Grob *me, Axis a)
287 if (!scm_is_number (me->get_property ("side-axis")))
289 me->set_property ("side-axis", scm_from_int (a));
290 chain_offset_callback (me,
291 (a==X_AXIS)
292 ? x_aligned_side_proc
293 : y_aligned_side_proc,
298 Axis
299 Side_position_interface::get_axis (Grob *me)
301 if (scm_is_number (me->get_property ("side-axis")))
302 return Axis (scm_to_int (me->get_property ("side-axis")));
304 string msg = String_convert::form_string ("side-axis not set for grob %s.",
305 me->name ().c_str ());
306 me->programming_error (msg);
307 return NO_AXES;
310 MAKE_SCHEME_CALLBACK (Side_position_interface, move_to_extremal_staff, 1);
312 Side_position_interface::move_to_extremal_staff (SCM smob)
314 Grob *me = unsmob_grob (smob);
315 System *sys = dynamic_cast<System*> (me->get_system ());
316 Direction dir = get_grob_direction (me);
317 if (dir != DOWN)
318 dir = UP;
320 Interval iv = me->extent (sys, X_AXIS);
321 iv.widen (1.0);
322 Grob *top_staff = sys->get_extremal_staff (dir, iv);
324 if (!top_staff)
325 return SCM_BOOL_F;
327 // Only move this grob if it is a direct child of the system. We
328 // are not interested in moving marks from other staves to the top
329 // staff; we only want to move marks from the system to the top
330 // staff.
331 if (sys != me->get_parent (Y_AXIS))
332 return SCM_BOOL_F;
334 me->set_parent (top_staff, Y_AXIS);
335 me->flush_extent_cache (Y_AXIS);
336 Axis_group_interface::add_element (top_staff, me);
338 // Remove any cross-staff side-support dependencies
339 Grob_array *ga = unsmob_grob_array (me->get_object ("side-support-elements"));
340 if (ga)
342 vector<Grob*> const& elts = ga->array ();
343 vector<Grob*> new_elts;
344 for (vsize i = 0; i < elts.size (); ++i)
346 if (me->common_refpoint (elts[i], Y_AXIS) == top_staff)
347 new_elts.push_back (elts[i]);
349 ga->set_array (new_elts);
351 return SCM_BOOL_T;
355 ADD_INTERFACE (Side_position_interface,
356 "Position a victim object (this one) next to other objects"
357 " (the support). The property @code{direction} signifies where"
358 " to put the victim object relative to the support (left or"
359 " right, up or down?)\n"
360 "\n"
361 "The routine also takes the size of the staff into account if"
362 " @code{staff-padding} is set. If undefined, the staff symbol"
363 " is ignored.",
365 /* properties */
366 "direction "
367 "minimum-space "
368 "padding "
369 "quantize-position "
370 "side-axis "
371 "side-support-elements "
372 "slur-padding "
373 "staff-padding "