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_x_common, 1);
288 Axis_group_interface::calc_x_common (SCM grob)
290 Grob *me = unsmob_grob (grob);
292 extract_grob_set (me, "elements", elts);
293 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
294 return common->self_scm ();
297 MAKE_SCHEME_CALLBACK(Axis_group_interface,calc_y_common, 1);
299 Axis_group_interface::calc_y_common (SCM grob)
301 Grob *me = unsmob_grob (grob);
303 extract_grob_set (me, "elements", elts);
304 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
305 return common->self_scm ();
309 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
311 Grob *common = calc_pure_elts_and_common (me);
313 extract_grob_set (me, "pure-relevant-elements", elts);
314 Real my_coord = me->relative_coordinate (common, Y_AXIS);
315 Interval r (relative_pure_height (me, elts, common, start, end, true));
317 return ly_interval2scm (r - my_coord);
321 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
323 found->push_back (me);
325 if (!has_interface (me))
328 extract_grob_set (me, "elements", elements);
329 for (vsize i = 0; i < elements.size (); i++)
331 Grob *e = elements[i];
332 Axis_group_interface::get_children (e, found);
337 staff_priority_less (Grob * const &g1, Grob * const &g2)
339 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
340 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
342 if (priority_1 < priority_2)
344 else if (priority_1 > priority_2)
347 /* if there is no preference in staff priority, choose the left-most one */
348 Grob *common = g1->common_refpoint (g2, X_AXIS);
349 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
350 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
351 return start_1 < start_2;
355 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
357 /* if we are a parent, consider the children's boxes instead of mine */
358 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
360 for (vsize i = 0; i < elements->size (); i++)
361 add_boxes (elements->grob (i), x_common, y_common, boxes);
363 else if (!scm_is_number (me->get_property ("outside-staff-priority")))
364 boxes->push_back (Box (me->extent (x_common, X_AXIS),
365 me->extent (y_common, Y_AXIS)));
368 /* We want to avoid situations like this:
376 The point is that "still more text" should be positioned under
377 "more text". In order to achieve this, we place the grobs in several
378 passes. We keep track of the right-most horizontal position that has been
379 affected by the current pass so far (actually we keep track of 2
380 positions, one for above the staff, one for below).
382 In each pass, we loop through the unplaced grobs from left to right.
383 If the grob overlaps the right-most affected position, we place it
384 (and then update the right-most affected position to point to the right
385 edge of the just-placed grob). Otherwise, we skip it until the next pass.
388 add_grobs_of_one_priority (Skyline_pair *const skylines,
389 vector<Grob*> elements,
394 Drul_array<Real> last_affected_position;
397 while (!elements.empty ())
399 last_affected_position[UP] = -infinity_f;
400 last_affected_position[DOWN] = -infinity_f;
402 for (vsize i = elements.size (); i--;)
404 Direction dir = get_grob_direction (elements[i]);
407 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
411 Box b (elements[i]->extent (x_common, X_AXIS),
412 elements[i]->extent (y_common, Y_AXIS));
413 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
414 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
416 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
419 if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
420 warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
425 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
426 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
427 Real dist = (*skylines)[dir].distance (other) + padding;
431 b.translate (Offset (0, dir*dist));
432 elements[i]->translate_axis (dir*dist, Y_AXIS);
434 (*skylines)[dir].insert (b, 0, X_AXIS);
435 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
436 last_affected_position[dir] = b[X_AXIS][RIGHT];
440 Ugh: quadratic. --hwn
442 elements.erase (elements.begin () + i);
448 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
450 vector_sort (elements, staff_priority_less);
451 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
452 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
454 assert (y_common == me);
459 for (i = 0; i < elements.size ()
460 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
461 add_boxes (elements[i], x_common, y_common, &boxes);
463 Skyline_pair skylines (boxes, 0, X_AXIS);
464 for (; i < elements.size (); i++)
466 SCM priority = elements[i]->get_property ("outside-staff-priority");
467 vector<Grob*> current_elts;
468 current_elts.push_back (elements[i]);
469 while (i < elements.size () - 1
470 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
471 current_elts.push_back (elements[++i]);
473 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
478 ADD_INTERFACE (Axis_group_interface,
480 "An object that groups other layout objects.",
488 "pure-relevant-elements "
490 "cached-pure-extents "