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 ())
73 FIXME: pure extent handling has a lot of ad-hoc caching.
74 This should be done with grob property callbacks.
80 Axis_group_interface::cached_pure_height (Grob *me,
81 vector<Grob*> const &elts,
85 Paper_score *ps = get_root_system (me)->paper_score ();
86 vector<vsize> breaks = ps->get_break_indices ();
87 vector<Grob*> cols = ps->get_columns ();
88 vsize start_index = VPOS;
89 vsize end_index = VPOS;
91 for (vsize i = 0; i < breaks.size (); i++)
93 int r = Paper_column::get_rank (cols[breaks[i]]);
100 end_index = breaks.size () - 1;
102 if (start_index == VPOS || end_index == VPOS)
104 programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
105 return Interval (0, 0);
108 SCM extents = me->get_property ("cached-pure-extents");
109 if (!scm_is_vector (extents))
111 extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
112 for (vsize i = 0; i < breaks.size () - 1; i++)
114 int st = Paper_column::get_rank (cols[breaks[i]]);
115 int ed = Paper_column::get_rank (cols[breaks[i+1]]);
116 Interval iv = relative_pure_height (me, elts, common, st, ed, false);
117 scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
119 me->set_property ("cached-pure-extents", extents);
123 for (vsize i = start_index; i < end_index; i++)
124 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
129 Axis_group_interface::relative_pure_height (Grob *me,
130 vector<Grob*> const &elts,
135 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
136 (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
137 Unfortunately, it isn't always true, particularly if there is a
138 VerticalAlignment somewhere in the descendants.
140 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
141 count, the only VerticalAlignment comes from Score. This makes it
142 reasonably safe to assume that if our parent is a VerticalAlignment,
143 we can assume additivity and cache things nicely. */
144 Grob *p = me->get_parent (Y_AXIS);
145 if (use_cache && p && Align_interface::has_interface (p))
146 return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
150 for (vsize i = 0; i < elts.size (); i++)
152 Interval_t<int> rank_span = elts[i]->spanned_rank_iv ();
153 Item *it = dynamic_cast<Item*> (elts[i]);
154 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
156 Interval dims = elts[i]->pure_height (common, start, end);
157 if (!dims.is_empty ())
164 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
166 Axis_group_interface::width (SCM smob)
168 Grob *me = unsmob_grob (smob);
169 return generic_group_extent (me, X_AXIS);
172 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
174 Axis_group_interface::height (SCM smob)
176 Grob *me = unsmob_grob (smob);
177 return generic_group_extent (me, Y_AXIS);
180 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
182 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
184 int start = robust_scm2int (start_scm, 0);
185 int end = robust_scm2int (end_scm, INT_MAX);
186 Grob *me = unsmob_grob (smob);
188 return pure_group_height (me, start, end);
191 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
193 Axis_group_interface::calc_skylines (SCM smob)
195 Grob *me = unsmob_grob (smob);
196 extract_grob_set (me, "elements", elts);
197 return skyline_spacing (me, elts).smobbed_copy ();
200 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
201 visible children, combine_skylines is designed for axis-groups whose only
202 children are other axis-groups (ie. VerticalAlignment). Rather than
203 calculating all the skylines from scratch, we just merge the skylines
206 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
208 Axis_group_interface::combine_skylines (SCM smob)
210 Grob *me = unsmob_grob (smob);
211 extract_grob_set (me, "elements", elements);
212 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
214 assert (y_common == me);
217 for (vsize i = 0; i < elements.size (); i++)
219 SCM skyline_scm = elements[i]->get_property ("skylines");
220 if (Skyline_pair::unsmob (skyline_scm))
222 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
223 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
224 other.raise (offset);
228 return ret.smobbed_copy ();
232 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
234 /* trigger the callback to do skyline-spacing on the children */
235 (void) me->get_property ("skylines");
237 extract_grob_set (me, "elements", elts);
238 Grob *common = common_refpoint_of_array (elts, me, a);
240 Real my_coord = me->relative_coordinate (common, a);
241 Interval r (relative_group_extent (elts, common, a));
243 return ly_interval2scm (r - my_coord);
248 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
250 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
253 extract_grob_set (me, "elements", elts);
255 vector<Grob*> relevant_elts;
256 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
258 for (vsize i = 0; i < elts.size (); i++)
260 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
261 relevant_elts.push_back (elts[i]);
263 Item *it = dynamic_cast<Item*> (elts[i]);
268 Item *piece = it->find_prebroken_piece (d);
269 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
270 relevant_elts.push_back (piece);
272 while (flip (&d) != LEFT);
275 Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
276 me->set_object ("pure-Y-common", common->self_scm ());
278 SCM ga_scm = Grob_array::make_array ();
279 Grob_array *ga = unsmob_grob_array (ga_scm);
280 ga->set_array (relevant_elts);
281 me->set_object ("pure-relevant-elements", ga_scm);
286 MAKE_SCHEME_CALLBACK(Axis_group_interface,calc_y_common, 1);
288 Axis_group_interface::calc_y_common (SCM grob)
290 Grob *me = unsmob_grob (grob);
292 extract_grob_set (me, "elements", elts);
293 return common_refpoint_of_array (elts, me, Y_AXIS)->self_scm ();
297 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
299 Grob *common = calc_pure_elts_and_common (me);
301 extract_grob_set (me, "pure-relevant-elements", elts);
302 Real my_coord = me->relative_coordinate (common, Y_AXIS);
303 Interval r (relative_pure_height (me, elts, common, start, end, true));
305 return ly_interval2scm (r - my_coord);
309 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
311 found->push_back (me);
313 if (!has_interface (me))
316 extract_grob_set (me, "elements", elements);
317 for (vsize i = 0; i < elements.size (); i++)
319 Grob *e = elements[i];
320 Axis_group_interface::get_children (e, found);
325 staff_priority_less (Grob * const &g1, Grob * const &g2)
327 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
328 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
330 if (priority_1 < priority_2)
332 else if (priority_1 > priority_2)
335 /* if there is no preference in staff priority, choose the left-most one */
336 Grob *common = g1->common_refpoint (g2, X_AXIS);
337 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
338 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
339 return start_1 < start_2;
343 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
345 /* if we are a parent, consider the children's boxes instead of mine */
346 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
348 for (vsize i = 0; i < elements->size (); i++)
349 add_boxes (elements->grob (i), x_common, y_common, boxes);
351 else if (!scm_is_number (me->get_property ("outside-staff-priority")))
352 boxes->push_back (Box (me->extent (x_common, X_AXIS),
353 me->extent (y_common, Y_AXIS)));
356 /* We want to avoid situations like this:
364 The point is that "still more text" should be positioned under
365 "more text". In order to achieve this, we place the grobs in several
366 passes. We keep track of the right-most horizontal position that has been
367 affected by the current pass so far (actually we keep track of 2
368 positions, one for above the staff, one for below).
370 In each pass, we loop through the unplaced grobs from left to right.
371 If the grob overlaps the right-most affected position, we place it
372 (and then update the right-most affected position to point to the right
373 edge of the just-placed grob). Otherwise, we skip it until the next pass.
376 add_grobs_of_one_priority (Skyline_pair *const skylines,
377 vector<Grob*> elements,
382 Drul_array<Real> last_affected_position;
385 while (!elements.empty ())
387 last_affected_position[UP] = -infinity_f;
388 last_affected_position[DOWN] = -infinity_f;
390 for (vsize i = elements.size (); i--;)
392 Direction dir = get_grob_direction (elements[i]);
395 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
399 Box b (elements[i]->extent (x_common, X_AXIS),
400 elements[i]->extent (y_common, Y_AXIS));
401 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
402 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
404 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
407 if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
408 warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
413 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
414 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
415 Real dist = (*skylines)[dir].distance (other) + padding;
419 b.translate (Offset (0, dir*dist));
420 elements[i]->translate_axis (dir*dist, Y_AXIS);
422 (*skylines)[dir].insert (b, 0, X_AXIS);
423 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
424 last_affected_position[dir] = b[X_AXIS][RIGHT];
428 Ugh: quadratic. --hwn
430 elements.erase (elements.begin () + i);
436 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
438 vector_sort (elements, staff_priority_less);
439 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
440 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
442 assert (y_common == me);
447 for (i = 0; i < elements.size ()
448 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
449 add_boxes (elements[i], x_common, y_common, &boxes);
451 Skyline_pair skylines (boxes, 0, X_AXIS);
452 for (; i < elements.size (); i++)
454 SCM priority = elements[i]->get_property ("outside-staff-priority");
455 vector<Grob*> current_elts;
456 current_elts.push_back (elements[i]);
457 while (i < elements.size () - 1
458 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
459 current_elts.push_back (elements[++i]);
461 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
466 ADD_INTERFACE (Axis_group_interface,
468 "An object that groups other layout objects.",
476 "pure-relevant-elements "
478 "cached-pure-extents "