2 axis-group-interface.cc -- implement Axis_group_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "axis-group-interface.hh"
11 #include "align-interface.hh"
12 #include "directional-element-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "grob-array.hh"
15 #include "hara-kiri-group-spanner.hh"
16 #include "international.hh"
17 #include "paper-column.hh"
18 #include "paper-score.hh"
19 #include "separation-item.hh"
24 Axis_group_interface::add_element (Grob *me, Grob *e)
26 SCM axes = me->get_property ("axes");
27 if (!scm_is_pair (axes))
28 programming_error ("axes should be nonempty");
30 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
32 Axis a = (Axis) scm_to_int (scm_car (ax));
34 if (!e->get_parent (a))
35 e->set_parent (me, a);
37 e->set_object ((a == X_AXIS)
38 ? ly_symbol2scm ("axis-group-parent-X")
39 : ly_symbol2scm ("axis-group-parent-Y"),
43 /* must be ordered, because Align_interface also uses
44 Axis_group_interface */
45 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
49 Axis_group_interface::has_axis (Grob *me, Axis a)
51 SCM axes = me->get_property ("axes");
53 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
57 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
61 for (vsize i = 0; i < elts.size (); i++)
64 Interval dims = se->extent (common, a);
65 if (!dims.is_empty ())
72 Axis_group_interface::cached_pure_height (Grob *me,
73 vector<Grob*> const &elts,
77 Paper_score *ps = get_root_system (me)->paper_score ();
78 vector<vsize> breaks = ps->get_break_indices ();
79 vector<Grob*> cols = ps->get_columns ();
80 vsize start_index = VPOS;
81 vsize end_index = VPOS;
83 for (vsize i = 0; i < breaks.size (); i++)
85 int r = Paper_column::get_rank (cols[breaks[i]]);
92 end_index = breaks.size () - 1;
94 if (start_index == VPOS || end_index == VPOS)
96 programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
97 return Interval (0, 0);
100 SCM extents = me->get_property ("cached-pure-extents");
101 if (!scm_is_vector (extents))
103 extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
104 for (vsize i = 0; i < breaks.size () - 1; i++)
106 int st = Paper_column::get_rank (cols[breaks[i]]);
107 int ed = Paper_column::get_rank (cols[breaks[i+1]]);
108 Interval iv = relative_pure_height (me, elts, common, st, ed, false);
109 scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
111 me->set_property ("cached-pure-extents", extents);
115 for (vsize i = start_index; i < end_index; i++)
116 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
121 Axis_group_interface::relative_pure_height (Grob *me,
122 vector<Grob*> const &elts,
127 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
128 (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
129 Unfortunately, it isn't always true, particularly if there is a
130 VerticalAlignment somewhere in the descendants.
132 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
133 count, the only VerticalAlignment comes from Score. This makes it
134 reasonably safe to assume that if our parent is a VerticalAlignment,
135 we can assume additivity and cache things nicely. */
136 Grob *p = me->get_parent (Y_AXIS);
137 if (use_cache && p && Align_interface::has_interface (p))
138 return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
142 for (vsize i = 0; i < elts.size (); i++)
144 Interval_t<int> rank_span = elts[i]->spanned_rank_iv ();
145 Item *it = dynamic_cast<Item*> (elts[i]);
146 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
148 Interval dims = elts[i]->pure_height (common, start, end);
149 if (!dims.is_empty ())
156 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
158 Axis_group_interface::width (SCM smob)
160 Grob *me = unsmob_grob (smob);
161 return generic_group_extent (me, X_AXIS);
164 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
166 Axis_group_interface::height (SCM smob)
168 Grob *me = unsmob_grob (smob);
169 return generic_group_extent (me, Y_AXIS);
172 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
174 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
176 int start = robust_scm2int (start_scm, 0);
177 int end = robust_scm2int (end_scm, INT_MAX);
178 Grob *me = unsmob_grob (smob);
180 return pure_group_height (me, start, end);
183 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
185 Axis_group_interface::calc_skylines (SCM smob)
187 Grob *me = unsmob_grob (smob);
188 extract_grob_set (me, "elements", elts);
189 return skyline_spacing (me, elts).smobbed_copy ();
192 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
193 visible children, combine_skylines is designed for axis-groups whose only
194 children are other axis-groups (ie. VerticalAlignment). Rather than
195 calculating all the skylines from scratch, we just merge the skylines
198 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
200 Axis_group_interface::combine_skylines (SCM smob)
202 Grob *me = unsmob_grob (smob);
203 extract_grob_set (me, "elements", elements);
204 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
206 assert (y_common == me);
209 for (vsize i = 0; i < elements.size (); i++)
211 SCM skyline_scm = elements[i]->get_property ("skylines");
212 if (Skyline_pair::unsmob (skyline_scm))
214 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
215 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
216 other.raise (offset);
220 return ret.smobbed_copy ();
224 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
226 /* trigger the callback to do skyline-spacing on the children */
227 (void) me->get_property ("skylines");
229 extract_grob_set (me, "elements", elts);
230 Grob *common = common_refpoint_of_array (elts, me, a);
232 Real my_coord = me->relative_coordinate (common, a);
233 Interval r (relative_group_extent (elts, common, a));
235 return ly_interval2scm (r - my_coord);
239 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
241 Grob *common = unsmob_grob (me->get_object ("common-refpoint-of-elements"));
245 extract_grob_set (me, "elements", elts);
247 vector<Grob*> relevant_elts;
248 SCM is_relevant = ly_lily_module_constant ("pure-relevant");
250 for (vsize i = 0; i < elts.size (); i++)
252 if (to_boolean (scm_apply_1 (is_relevant, elts[i]->self_scm (), SCM_EOL)))
253 relevant_elts.push_back (elts[i]);
255 Item *it = dynamic_cast<Item*> (elts[i]);
260 Item *piece = it->find_prebroken_piece (d);
261 if (piece && to_boolean (scm_apply_1 (is_relevant, piece->self_scm (), SCM_EOL)))
262 relevant_elts.push_back (piece);
264 while (flip (&d) != LEFT);
267 common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
268 me->set_object ("common-refpoint-of-elements", common->self_scm ());
270 SCM ga_scm = Grob_array::make_array ();
271 Grob_array *ga = unsmob_grob_array (ga_scm);
272 ga->set_array (relevant_elts);
273 me->set_object ("pure-relevant-elements", ga_scm);
276 extract_grob_set (me, "pure-relevant-elements", elts);
277 Real my_coord = me->relative_coordinate (common, Y_AXIS);
278 Interval r (relative_pure_height (me, elts, common, start, end, true));
280 return ly_interval2scm (r - my_coord);
284 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
286 found->push_back (me);
288 if (!has_interface (me))
291 extract_grob_set (me, "elements", elements);
292 for (vsize i = 0; i < elements.size (); i++)
294 Grob *e = elements[i];
295 Axis_group_interface::get_children (e, found);
300 staff_priority_less (Grob * const &g1, Grob * const &g2)
302 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
303 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
305 if (priority_1 < priority_2)
307 else if (priority_1 > priority_2)
310 /* if there is no preference in staff priority, choose the left-most one */
311 Grob *common = g1->common_refpoint (g2, X_AXIS);
312 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
313 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
314 return start_1 < start_2;
318 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
320 /* if we are a parent, consider the children's boxes instead of mine */
321 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
323 for (vsize i = 0; i < elements->size (); i++)
324 add_boxes (elements->grob (i), x_common, y_common, boxes);
326 else if (!scm_is_number (me->get_property ("outside-staff-priority")))
327 boxes->push_back (Box (me->extent (x_common, X_AXIS),
328 me->extent (y_common, Y_AXIS)));
331 /* We want to avoid situations like this:
339 The point is that "still more text" should be positioned under
340 "more text". In order to achieve this, we place the grobs in several
341 passes. We keep track of the right-most horizontal position that has been
342 affected by the current pass so far (actually we keep track of 2
343 positions, one for above the staff, one for below).
345 In each pass, we loop through the unplaced grobs from left to right.
346 If the grob overlaps the right-most affected position, we place it
347 (and then update the right-most affected position to point to the right
348 edge of the just-placed grob). Otherwise, we skip it until the next pass.
351 add_grobs_of_one_priority (Skyline_pair *const skylines,
352 vector<Grob*> elements,
357 Drul_array<Real> last_affected_position;
360 while (!elements.empty ())
362 last_affected_position[UP] = -infinity_f;
363 last_affected_position[DOWN] = -infinity_f;
365 for (vsize i = elements.size (); i--;)
367 Direction dir = get_grob_direction (elements[i]);
370 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
374 Box b (elements[i]->extent (x_common, X_AXIS),
375 elements[i]->extent (y_common, Y_AXIS));
376 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
377 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
379 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
382 if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
383 warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
388 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
389 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
390 Real dist = (*skylines)[dir].distance (other) + padding;
394 b.translate (Offset (0, dir*dist));
395 elements[i]->translate_axis (dir*dist, Y_AXIS);
397 (*skylines)[dir].insert (b, 0, X_AXIS);
398 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
399 last_affected_position[dir] = b[X_AXIS][RIGHT];
401 elements.erase (elements.begin () + i);
407 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
409 vector_sort (elements, staff_priority_less);
410 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
411 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
413 assert (y_common == me);
418 for (i = 0; i < elements.size ()
419 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
420 add_boxes (elements[i], x_common, y_common, &boxes);
422 Skyline_pair skylines (boxes, 0, X_AXIS);
423 for (; i < elements.size (); i++)
425 SCM priority = elements[i]->get_property ("outside-staff-priority");
426 vector<Grob*> current_elts;
427 current_elts.push_back (elements[i]);
428 while (i < elements.size () - 1
429 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
430 current_elts.push_back (elements[++i]);
432 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
437 ADD_INTERFACE (Axis_group_interface,
439 "An object that groups other layout objects.",
444 "common-refpoint-of-elements "
445 "pure-relevant-elements "
447 "cached-pure-extents "