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);
184 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
186 extract_grob_set (me, "elements", elts);
187 if (a == Y_AXIS && to_boolean (me->get_property ("skyline-spacing")))
188 skyline_spacing (me, elts);
189 Grob *common = common_refpoint_of_array (elts, me, a);
191 Real my_coord = me->relative_coordinate (common, a);
192 Interval r (relative_group_extent (elts, common, a));
194 return ly_interval2scm (r - my_coord);
198 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
200 Grob *common = unsmob_grob (me->get_object ("common-refpoint-of-elements"));
204 extract_grob_set (me, "elements", elts);
206 vector<Grob*> relevant_elts;
207 SCM is_relevant = ly_lily_module_constant ("pure-relevant");
209 for (vsize i = 0; i < elts.size (); i++)
211 if (to_boolean (scm_apply_1 (is_relevant, elts[i]->self_scm (), SCM_EOL)))
212 relevant_elts.push_back (elts[i]);
214 Item *it = dynamic_cast<Item*> (elts[i]);
219 Item *piece = it->find_prebroken_piece (d);
220 if (piece && to_boolean (scm_apply_1 (is_relevant, piece->self_scm (), SCM_EOL)))
221 relevant_elts.push_back (piece);
223 while (flip (&d) != LEFT);
226 common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
227 me->set_object ("common-refpoint-of-elements", common->self_scm ());
229 SCM ga_scm = Grob_array::make_array ();
230 Grob_array *ga = unsmob_grob_array (ga_scm);
231 ga->set_array (relevant_elts);
232 me->set_object ("pure-relevant-elements", ga_scm);
235 extract_grob_set (me, "pure-relevant-elements", elts);
236 Real my_coord = me->relative_coordinate (common, Y_AXIS);
237 Interval r (relative_pure_height (me, elts, common, start, end, true));
239 return ly_interval2scm (r - my_coord);
243 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
245 found->push_back (me);
247 if (!has_interface (me))
250 extract_grob_set (me, "elements", elements);
251 for (vsize i = 0; i < elements.size (); i++)
253 Grob *e = elements[i];
254 Axis_group_interface::get_children (e, found);
259 staff_priority_less (Grob * const &g1, Grob * const &g2)
261 int priority_1 = robust_scm2int (g1->get_property ("outside-staff-priority"), INT_MIN);
262 int priority_2 = robust_scm2int (g2->get_property ("outside-staff-priority"), INT_MIN);
264 if (priority_1 < priority_2)
266 else if (priority_1 > priority_2)
269 /* if there is no preference in staff priority, choose the left-most one */
270 Grob *common = g1->common_refpoint (g2, X_AXIS);
271 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
272 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
273 return start_1 < start_2;
277 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
279 /* if we are a parent, consider the children's boxes instead of mine */
280 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
282 for (vsize i = 0; i < elements->size (); i++)
283 add_boxes (elements->grob (i), x_common, y_common, boxes);
285 else if (!scm_is_number (me->get_property ("outside-staff-priority")))
286 boxes->push_back (Box (me->extent (x_common, X_AXIS),
287 me->extent (y_common, Y_AXIS)));
290 /* We want to avoid situations like this:
298 The point is that "still more text" should be positioned under
299 "more text". In order to achieve this, we place the grobs in several
300 passes. We keep track of the right-most horizontal position that has been
301 affected by the current pass so far (actually we keep track of 2
302 positions, one for above the staff, one for below).
304 In each pass, we loop through the unplaced grobs from left to right.
305 If the grob overlaps the right-most affected position, we place it
306 (and then update the right-most affected position to point to the right
307 edge of the just-placed grob). Otherwise, we skip it until the next pass.
310 add_grobs_of_one_priority (Drul_array<Skyline> *const skylines,
311 vector<Grob*> elements,
316 Drul_array<Real> last_affected_position;
319 while (!elements.empty ())
321 last_affected_position[UP] = -infinity_f;
322 last_affected_position[DOWN] = -infinity_f;
324 for (vsize i = elements.size (); i--;)
326 Direction dir = get_grob_direction (elements[i]);
329 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
333 Box b (elements[i]->extent (x_common, X_AXIS),
334 elements[i]->extent (y_common, Y_AXIS));
335 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
336 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
338 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
341 if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
342 warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
347 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
348 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
349 Real dist = (*skylines)[dir].distance (other) + padding;
353 b.translate (Offset (0, dir*dist));
354 elements[i]->translate_axis (dir*dist, Y_AXIS);
356 (*skylines)[dir].insert (b, 0, X_AXIS);
357 elements[i]->del_property ("outside-staff-padding");
358 last_affected_position[dir] = b[X_AXIS][RIGHT];
360 elements.erase (elements.begin () + i);
366 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
368 vector_sort (elements, staff_priority_less);
369 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
370 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
375 for (i = 0; i < elements.size ()
376 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
377 add_boxes (elements[i], x_common, y_common, &boxes);
379 Drul_array<Skyline> skylines (Skyline (boxes, 0, X_AXIS, DOWN),
380 Skyline (boxes, 0, X_AXIS, UP));
381 for (; i < elements.size (); i++)
383 SCM priority = elements[i]->get_property ("outside-staff-priority");
384 vector<Grob*> current_elts;
385 current_elts.push_back (elements[i]);
386 while (i < elements.size () - 1
387 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
388 current_elts.push_back (elements[++i]);
390 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
394 ADD_INTERFACE (Axis_group_interface,
396 "An object that groups other layout objects.",
401 "common-refpoint-of-elements "
402 "pure-relevant-elements "
404 "cached-pure-extents "