]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Doc-es: Update of Input.
[lilypond.git] / lily / axis-group-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
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.
10
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.
15
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/>.
18 */
19
20 #include "axis-group-interface.hh"
21
22 #include "align-interface.hh"
23 #include "directional-element-interface.hh"
24 #include "grob-array.hh"
25 #include "hara-kiri-group-spanner.hh"
26 #include "international.hh"
27 #include "lookup.hh"
28 #include "paper-column.hh"
29 #include "paper-score.hh"
30 #include "pointer-group-interface.hh"
31 #include "separation-item.hh"
32 #include "skyline-pair.hh"
33 #include "staff-grouper-interface.hh"
34 #include "stencil.hh"
35 #include "system.hh"
36 #include "warn.hh"
37
38 void
39 Axis_group_interface::add_element (Grob *me, Grob *e)
40 {
41   SCM axes = me->get_property ("axes");
42   if (!scm_is_pair (axes))
43     programming_error ("axes should be nonempty");
44
45   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
46     {
47       Axis a = (Axis) scm_to_int (scm_car (ax));
48
49       if (!e->get_parent (a))
50         e->set_parent (me, a);
51
52       e->set_object ((a == X_AXIS)
53                      ? ly_symbol2scm ("axis-group-parent-X")
54                      : ly_symbol2scm ("axis-group-parent-Y"),
55                      me->self_scm ());
56     }
57
58   /* must be ordered, because Align_interface also uses
59      Axis_group_interface  */
60   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
61 }
62
63 bool
64 Axis_group_interface::has_axis (Grob *me, Axis a)
65 {
66   SCM axes = me->get_property ("axes");
67
68   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
69 }
70
71 Interval
72 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
73                                              Grob *common, Axis a)
74 {
75   Interval r;
76   for (vsize i = 0; i < elts.size (); i++)
77     {
78       Grob *se = elts[i];
79       if (!to_boolean (se->get_property ("cross-staff")))
80         {
81           Interval dims = se->extent (common, a);
82           if (!dims.is_empty ())
83             r.unite (dims);
84         }
85     }
86   return r;
87 }
88
89 Interval
90 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
91 {
92   Interval iv = begin_of_line_pure_height (me, start);
93   iv.unite (rest_of_line_pure_height (me, start, end));
94
95   return iv;
96 }
97
98 Interval
99 Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
100 {
101   SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
102
103   if (!scm_is_pair (adjacent_pure_heights)
104       || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
105     return Interval (0, 0);
106
107   return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
108 }
109
110 Interval
111 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
112 {
113   SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
114
115   if (!scm_is_pair (adjacent_pure_heights)
116       || !scm_is_vector (scm_car (adjacent_pure_heights)))
117     return Interval (0, 0);
118
119   return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
120 }
121
122 Interval
123 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
124 {
125   Paper_score *ps = get_root_system (me)->paper_score ();
126   vector<vsize> breaks = ps->get_break_indices ();
127   vector<Grob*> cols = ps->get_columns ();
128
129   Interval ext;
130   for (vsize i = 0; i + 1 < breaks.size (); i++)
131     {
132       int r = Paper_column::get_rank (cols[breaks[i]]);
133       if (r >= end)
134         break;
135
136       if (r >= start)
137         ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
138     }
139
140   return ext;
141 }
142
143 // adjacent-pure-heights is a pair of vectors, each of which has one element
144 // for every measure in the score. The first vector stores, for each measure,
145 // the combined height of the elements that are present only when the bar
146 // is at the beginning of a line. The second vector stores, for each measure,
147 // the combined height of the elements that are present only when the bar
148 // is not at the beginning of a line.
149
150 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
151 SCM
152 Axis_group_interface::adjacent_pure_heights (SCM smob)
153 {
154   Grob *me = unsmob_grob (smob);
155
156   Grob *common = calc_pure_elts_and_common (me);
157   extract_grob_set (me, "pure-relevant-items", items);
158   extract_grob_set (me, "pure-relevant-spanners", spanners);
159
160   Paper_score *ps = get_root_system (me)->paper_score ();
161   vector<vsize> breaks = ps->get_break_indices ();
162   vector<Grob*> cols = ps->get_columns ();
163
164   SCM begin_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
165   SCM mid_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
166
167   vsize it_index = 0;
168   for (vsize i = 0; i + 1 < breaks.size (); i++)
169     {
170       int start = Paper_column::get_rank (cols[breaks[i]]);
171       int end = Paper_column::get_rank (cols[breaks[i+1]]);
172       Interval begin_line_iv;
173       Interval mid_line_iv;
174
175       for (vsize j = it_index; j < items.size (); j++)
176         {
177           Item *it = dynamic_cast<Item*> (items[j]);
178           int rank = it->get_column ()->get_rank ();
179
180           if (rank <= end && it->pure_is_visible (start, end)
181               && !to_boolean (it->get_property ("cross-staff")))
182             {
183               Interval dims = items[j]->pure_height (common, start, end);
184               Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
185
186               if (!dims.is_empty ())
187                 target_iv.unite (dims);
188             }
189
190           if (rank < end)
191             it_index++;
192           else if (rank > end)
193             break;
194         }
195
196       for (vsize j = 0; j < spanners.size (); j++)
197         {
198           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
199           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
200               && !to_boolean (spanners[j]->get_property ("cross-staff")))
201             {
202               Interval dims = spanners[j]->pure_height (common, start, end);
203
204               if (!dims.is_empty ())
205                 mid_line_iv.unite (dims);
206             }
207         }
208
209       scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
210       scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
211     }
212   return scm_cons (begin_line_heights, mid_line_heights);
213 }
214
215 Interval
216 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
217 {
218   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
219      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
220      Unfortunately, it isn't always true, particularly if there is a
221      VerticalAlignment somewhere in the descendants.
222
223      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
224      count, the only VerticalAlignment comes from Score. This makes it
225      reasonably safe to assume that if our parent is a VerticalAlignment,
226      we can assume additivity and cache things nicely. */
227   Grob *p = me->get_parent (Y_AXIS);
228   if (p && Align_interface::has_interface (p))
229     return Axis_group_interface::cached_pure_height (me, start, end);
230
231   Grob *common = calc_pure_elts_and_common (me);
232   extract_grob_set (me, "pure-relevant-items", items);
233   extract_grob_set (me, "pure-relevant-spanners", spanners);
234
235   Interval r;
236
237   for (vsize i = 0; i < items.size (); i++)
238     {
239       Item *it = dynamic_cast<Item*> (items[i]);
240       int rank = it->get_column ()->get_rank ();
241
242       if (rank > end)
243         break;
244       else if (rank >= start && it->pure_is_visible (start, end)
245                && !to_boolean (it->get_property ("cross-staff")))
246         {
247           Interval dims = it->pure_height (common, start, end);
248           if (!dims.is_empty ())
249             r.unite (dims);
250         }
251     }
252
253   for (vsize i = 0; i < spanners.size (); i++)
254     {
255       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
256       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
257           && !to_boolean (spanners[i]->get_property ("cross-staff")))
258         {
259           Interval dims = spanners[i]->pure_height (common, start, end);
260           if (!dims.is_empty ())
261             r.unite (dims);
262         }
263     }
264   return r;
265 }
266
267 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
268 SCM
269 Axis_group_interface::width (SCM smob)
270 {
271   Grob *me = unsmob_grob (smob);
272   return generic_group_extent (me, X_AXIS);
273 }
274
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
276 SCM
277 Axis_group_interface::height (SCM smob)
278 {
279   Grob *me = unsmob_grob (smob);
280   return generic_group_extent (me, Y_AXIS);
281 }
282
283 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
284 SCM
285 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
286 {
287   int start = robust_scm2int (start_scm, 0);
288   int end = robust_scm2int (end_scm, INT_MAX);
289   Grob *me = unsmob_grob (smob);
290
291   /* Maybe we are in the second pass of a two-pass spacing run. In that
292      case, the Y-extent of a system is already given to us */
293   System *system = dynamic_cast<System*> (me);
294   if (system)
295     {
296       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
297       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
298       if (scm_is_pair (system_y_extent))
299         return scm_cdr (system_y_extent);
300     }
301
302   return ly_interval2scm (pure_group_height (me, start, end));
303 }
304
305 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
306 SCM
307 Axis_group_interface::calc_skylines (SCM smob)
308 {
309   Grob *me = unsmob_grob (smob);
310   extract_grob_set (me, "elements", elts);
311   Skyline_pair skylines = skyline_spacing (me, elts);
312
313   return skylines.smobbed_copy ();
314 }
315
316 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
317    visible children, combine_skylines is designed for axis-groups whose only
318    children are other axis-groups (ie. VerticalAlignment). Rather than
319    calculating all the skylines from scratch, we just merge the skylines
320    of the children.
321 */
322 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
323 SCM
324 Axis_group_interface::combine_skylines (SCM smob)
325 {
326   Grob *me = unsmob_grob (smob);
327   extract_grob_set (me, "elements", elements);
328   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
329   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
330
331   if (y_common != me)
332     programming_error ("combining skylines that don't belong to me");
333
334   Skyline_pair ret;
335   for (vsize i = 0; i < elements.size (); i++)
336     {
337       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
338       if (Skyline_pair::unsmob (skyline_scm))
339         {
340           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
341           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
342           other.raise (offset);
343           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
344           ret.merge (other);
345         }
346     }
347   return ret.smobbed_copy ();
348 }
349   
350 SCM
351 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
352 {
353   /* trigger the callback to do skyline-spacing on the children */
354   if (a == Y_AXIS)
355     (void) me->get_property ("vertical-skylines");
356
357   extract_grob_set (me, "elements", elts);
358   Grob *common = common_refpoint_of_array (elts, me, a);
359
360   Real my_coord = me->relative_coordinate (common, a);
361   Interval r (relative_group_extent (elts, common, a));
362
363   return ly_interval2scm (r - my_coord);
364 }
365
366 /* This is like generic_group_extent, but it only counts the grobs that
367    are children of some other axis-group. This is uncached; if it becomes
368    commonly used, it may be necessary to cache it somehow. */
369 Interval
370 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
371 {
372   extract_grob_set (me, "elements", elts);
373   vector<Grob*> new_elts;
374
375   for (vsize i = 0; i < elts.size (); i++)
376     if (elts[i]->common_refpoint (staff, parent_a) == staff)
377       new_elts.push_back (elts[i]);
378
379   return relative_group_extent (new_elts, refp, ext_a);
380 }
381
382
383 Grob *
384 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
385 {
386   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
387     return c;
388   
389   extract_grob_set (me, "elements", elts);
390
391   vector<Grob*> relevant_items;
392   vector<Grob*> relevant_spanners;
393   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
394
395   for (vsize i = 0; i < elts.size (); i++)
396     {
397       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
398         {
399           if (dynamic_cast<Item*> (elts[i]))
400             relevant_items.push_back (elts[i]);
401           else if (dynamic_cast<Spanner*> (elts[i]))
402             relevant_spanners.push_back (elts[i]);
403         }
404             
405
406       Item *it = dynamic_cast<Item*> (elts[i]);
407       Direction d = LEFT;
408       if (it)
409         do
410           {
411             Item *piece = it->find_prebroken_piece (d);
412             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
413               relevant_items.push_back (piece);
414           }
415         while (flip (&d) != LEFT);
416     }
417   vector_sort (relevant_items, Item::less);
418
419   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
420   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
421
422   me->set_object ("pure-Y-common", common->self_scm ());
423   
424   SCM items_scm = Grob_array::make_array ();
425   SCM spanners_scm = Grob_array::make_array ();
426
427   unsmob_grob_array (items_scm)->set_array (relevant_items);
428   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
429   me->set_object ("pure-relevant-items", items_scm);
430   me->set_object ("pure-relevant-spanners", spanners_scm);
431
432   return common;
433 }
434
435 SCM
436 Axis_group_interface::calc_common (Grob *me, Axis axis)
437 {
438   extract_grob_set (me, "elements", elts);
439   Grob *common = common_refpoint_of_array (elts, me, axis);
440   if (!common)
441     {
442       me->programming_error ("No common parent found in calc_common axis.");
443       return SCM_EOL;
444     }
445   
446   return common->self_scm ();
447 }
448
449
450 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
451 SCM
452 Axis_group_interface::calc_x_common (SCM grob)
453 {
454   return calc_common (unsmob_grob (grob), X_AXIS);
455 }
456
457 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
458 SCM
459 Axis_group_interface::calc_y_common (SCM grob)
460 {
461   return calc_common (unsmob_grob (grob), Y_AXIS);
462 }
463
464 Interval
465 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
466 {
467   Grob *common = calc_pure_elts_and_common (me);
468         
469   Real my_coord = me->relative_coordinate (common, Y_AXIS);
470   Interval r (relative_pure_height (me, start, end));
471
472   return r - my_coord;
473 }
474
475 void
476 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
477 {
478   found->push_back (me);
479
480   if (!has_interface (me))
481     return;
482
483   extract_grob_set (me, "elements", elements);
484   for (vsize i = 0; i < elements.size (); i++)
485     {
486       Grob *e = elements[i];
487       Axis_group_interface::get_children (e, found);
488     }
489 }
490
491 bool
492 staff_priority_less (Grob * const &g1, Grob * const &g2)
493 {
494   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
495   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
496
497   if (priority_1 < priority_2)
498     return true;
499   else if (priority_1 > priority_2)
500     return false;
501
502   /* if neither grob has an outside-staff priority, the ordering will have no
503      effect -- we just need to choose a consistent ordering. We do this to
504      avoid the side-effect of calculating extents. */
505   if (isinf (priority_1))
506     return g1 < g2;
507
508   /* if there is no preference in staff priority, choose the left-most one */
509   Grob *common = g1->common_refpoint (g2, X_AXIS);
510   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
511   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
512   return start_1 < start_2;
513 }
514
515 static void
516 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
517 {
518   /* if a child has skylines, use them instead of the extent box */
519   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
520     {
521       Skyline_pair s = *pair;
522       s.shift (me->relative_coordinate (x_common, X_AXIS));
523       s.raise (me->relative_coordinate (y_common, Y_AXIS));
524       skylines->merge (s);
525     }
526   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
527     {
528       for (vsize i = 0; i < elements->size (); i++)
529         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
530     }
531   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
532            && !to_boolean (me->get_property ("cross-staff")))
533     {
534       boxes->push_back (Box (me->extent (x_common, X_AXIS),
535                              me->extent (y_common, Y_AXIS)));
536     }
537 }
538
539 /* We want to avoid situations like this:
540            still more text
541       more text
542    text
543    -------------------
544    staff
545    -------------------
546
547    The point is that "still more text" should be positioned under
548    "more text".  In order to achieve this, we place the grobs in several
549    passes.  We keep track of the right-most horizontal position that has been
550    affected by the current pass so far (actually we keep track of 2
551    positions, one for above the staff, one for below).
552
553    In each pass, we loop through the unplaced grobs from left to right.
554    If the grob doesn't overlap the right-most affected position, we place it
555    (and then update the right-most affected position to point to the right
556    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
557 */
558 static void
559 add_grobs_of_one_priority (Skyline_pair *const skylines,
560                            vector<Grob*> elements,
561                            Grob *x_common,
562                            Grob *y_common)
563 {
564   vector<Box> boxes;
565   Drul_array<Real> last_affected_position;
566
567   reverse (elements);
568   while (!elements.empty ())
569     {
570       last_affected_position[UP] = -infinity_f;
571       last_affected_position[DOWN] = -infinity_f;
572       /* do one pass */
573       for (vsize i = elements.size (); i--;)
574         {
575           Direction dir = get_grob_direction (elements[i]);
576           if (dir == CENTER)
577             {
578               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
579               dir = UP;
580             }
581
582           Box b (elements[i]->extent (x_common, X_AXIS),
583                  elements[i]->extent (y_common, Y_AXIS));
584           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
585           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
586
587           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
588             continue;
589
590           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
591             {
592               boxes.clear ();
593               boxes.push_back (b);
594               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
595               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
596               Real dist = (*skylines)[dir].distance (other) + padding;
597
598               if (dist > 0)
599                 {
600                   b.translate (Offset (0, dir*dist));
601                   elements[i]->translate_axis (dir*dist, Y_AXIS);
602                 }
603               skylines->insert (b, 0, X_AXIS);
604               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
605               last_affected_position[dir] = b[X_AXIS][RIGHT];
606             }
607
608           /*
609             Ugh: quadratic. --hwn
610            */
611           elements.erase (elements.begin () + i);
612         }
613     }
614 }
615
616 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
617 // For example, cross-staff beams cannot be formatted until the distance between
618 // staves is known and therefore any grobs that depend on the beam cannot be placed
619 // until the skylines are known. On the other hand, the distance between staves should
620 // really depend on position of the cross-staff grobs that lie between them.
621 // Currently, we just leave cross-staff grobs out of the
622 // skyline altogether, but this could mean that staves are placed so close together
623 // that there is no room for the cross-staff grob. It also means, of course, that
624 // we don't get the benefits of skyline placement for cross-staff grobs.
625 Skyline_pair
626 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
627 {
628   /* For grobs with an outside-staff-priority, the sorting function might
629      call extent and cause suicide. This breaks the contract that is required
630      for the STL sort function. To avoid this, we make sure that any suicides
631      are triggered beforehand.
632   */
633   for (vsize i = 0; i < elements.size (); i++)
634     if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
635       elements[i]->extent (elements[i], X_AXIS);
636
637   vector_sort (elements, staff_priority_less);
638   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
639   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
640
641   assert (y_common == me);
642
643   vsize i = 0;
644   vector<Box> boxes;
645
646   Skyline_pair skylines;
647   for (i = 0; i < elements.size ()
648          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
649     if (!to_boolean (elements[i]->get_property ("cross-staff")))
650       add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
651
652   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
653   Real padding = robust_scm2double (padding_scm, 0.1);
654   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
655   for (; i < elements.size (); i++)
656     {
657       if (to_boolean (elements[i]->get_property ("cross-staff")))
658         continue;
659
660       SCM priority = elements[i]->get_property ("outside-staff-priority");
661       vector<Grob*> current_elts;
662       current_elts.push_back (elements[i]);
663       while (i + 1 < elements.size () 
664              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
665         {
666           if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
667             current_elts.push_back (elements[i+1]);
668           ++i;
669         }
670
671       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
672     }
673   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
674   return skylines;
675 }
676
677 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
678 SCM
679 Axis_group_interface::print (SCM smob)
680 {
681   if (!debug_skylines)
682     return SCM_BOOL_F;
683
684   Grob *me = unsmob_grob (smob);
685   Stencil ret;
686   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
687     {
688       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
689                        .in_color (255, 0, 255));
690       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
691                        .in_color (0, 255, 255));
692     }
693   return ret.smobbed_copy ();
694 }
695
696 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
697 SCM
698 Axis_group_interface::calc_next_staff_spacing (SCM smob)
699 {
700   Grob *me = unsmob_grob (smob);
701   Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
702
703   if (grouper)
704     {
705       Grob *last_in_group = Staff_grouper_interface::get_last_grob (grouper);
706       if (me == last_in_group)
707         return grouper->get_property ("after-last-staff-spacing");
708       else
709         return grouper->get_property ("between-staff-spacing");
710     }
711   return me->get_property ("default-next-staff-spacing");
712 }
713
714 Real
715 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
716 {
717   SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
718
719   Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
720   Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
721   if (s1 && s2)
722     return (*s1)[DOWN].distance ((*s2)[UP]);
723   return 0;
724 }
725
726 ADD_INTERFACE (Axis_group_interface,
727                "An object that groups other layout objects.",
728
729                // TODO: some of these properties are specific to
730                // VerticalAxisGroup. We should split off a
731                // vertical-axis-group-interface.
732                /* properties */
733                "X-common "
734                "Y-common "
735                "adjacent-pure-heights "
736                "axes "
737                "default-next-staff-spacing "
738                "elements "
739                "inter-loose-line-spacing "
740                "inter-staff-spacing "
741                "keep-fixed-while-stretching "
742                "max-stretch "
743                "non-affinity-spacing "
744                "next-staff-spacing "
745                "no-alignment "
746                "pure-Y-common "
747                "pure-relevant-items "
748                "pure-relevant-spanners "
749                "staff-affinity "
750                "staff-grouper "
751                "system-Y-offset "
752                "vertical-skylines "
753                );