]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Fix 1062.
[lilypond.git] / lily / axis-group-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2010 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 = unsmob_grob (me->get_object ("pure-Y-common"));
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
173       // Take grobs that are visible with respect to a slightly longer line.
174       // Otherwise, we will never include grobs at breakpoints which aren't
175       // end-of-line-visible.
176       int visibility_end = i + 2 < breaks.size () ?
177         Paper_column::get_rank (cols[breaks[i+2]]) : end;
178
179       Interval begin_line_iv;
180       Interval mid_line_iv;
181
182       for (vsize j = it_index; j < items.size (); j++)
183         {
184           Item *it = dynamic_cast<Item*> (items[j]);
185           int rank = it->get_column ()->get_rank ();
186
187           if (rank <= end && it->pure_is_visible (start, visibility_end)
188               && !to_boolean (it->get_property ("cross-staff")))
189             {
190               Interval dims = items[j]->pure_height (common, start, end);
191               Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
192
193               if (!dims.is_empty ())
194                 target_iv.unite (dims);
195             }
196
197           if (rank < end)
198             it_index++;
199           else if (rank > end)
200             break;
201         }
202
203       for (vsize j = 0; j < spanners.size (); j++)
204         {
205           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
206           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
207               && !to_boolean (spanners[j]->get_property ("cross-staff")))
208             {
209               Interval dims = spanners[j]->pure_height (common, start, end);
210
211               if (!dims.is_empty ())
212                 {
213                   mid_line_iv.unite (dims);
214                   if (rank_span[LEFT] <= start)
215                     begin_line_iv.unite (dims);
216                 }
217             }
218         }
219
220       scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
221       scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
222     }
223   return scm_cons (begin_line_heights, mid_line_heights);
224 }
225
226 Interval
227 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
228 {
229   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
230      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
231      Unfortunately, it isn't always true, particularly if there is a
232      VerticalAlignment somewhere in the descendants.
233
234      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
235      count, the only VerticalAlignment comes from Score. This makes it
236      reasonably safe to assume that if our parent is a VerticalAlignment,
237      we can assume additivity and cache things nicely. */
238   Grob *p = me->get_parent (Y_AXIS);
239   if (p && Align_interface::has_interface (p))
240     return Axis_group_interface::cached_pure_height (me, start, end);
241
242   Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
243   extract_grob_set (me, "pure-relevant-items", items);
244   extract_grob_set (me, "pure-relevant-spanners", spanners);
245
246   Interval r;
247
248   for (vsize i = 0; i < items.size (); i++)
249     {
250       Item *it = dynamic_cast<Item*> (items[i]);
251       int rank = it->get_column ()->get_rank ();
252
253       if (rank > end)
254         break;
255       else if (rank >= start && it->pure_is_visible (start, end)
256                && !to_boolean (it->get_property ("cross-staff")))
257         {
258           Interval dims = it->pure_height (common, start, end);
259           if (!dims.is_empty ())
260             r.unite (dims);
261         }
262     }
263
264   for (vsize i = 0; i < spanners.size (); i++)
265     {
266       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
267       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
268           && !to_boolean (spanners[i]->get_property ("cross-staff")))
269         {
270           Interval dims = spanners[i]->pure_height (common, start, end);
271           if (!dims.is_empty ())
272             r.unite (dims);
273         }
274     }
275   return r;
276 }
277
278 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
279 SCM
280 Axis_group_interface::width (SCM smob)
281 {
282   Grob *me = unsmob_grob (smob);
283   return generic_group_extent (me, X_AXIS);
284 }
285
286 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
287 SCM
288 Axis_group_interface::height (SCM smob)
289 {
290   Grob *me = unsmob_grob (smob);
291   return generic_group_extent (me, Y_AXIS);
292 }
293
294 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
295 SCM
296 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
297 {
298   int start = robust_scm2int (start_scm, 0);
299   int end = robust_scm2int (end_scm, INT_MAX);
300   Grob *me = unsmob_grob (smob);
301
302   /* Maybe we are in the second pass of a two-pass spacing run. In that
303      case, the Y-extent of a system is already given to us */
304   System *system = dynamic_cast<System*> (me);
305   if (system)
306     {
307       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
308       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
309       if (scm_is_pair (system_y_extent))
310         return scm_cdr (system_y_extent);
311     }
312
313   return ly_interval2scm (pure_group_height (me, start, end));
314 }
315
316 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
317 SCM
318 Axis_group_interface::calc_skylines (SCM smob)
319 {
320   Grob *me = unsmob_grob (smob);
321   extract_grob_set (me, "elements", elts);
322   Skyline_pair skylines = skyline_spacing (me, elts);
323
324   return skylines.smobbed_copy ();
325 }
326
327 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
328    visible children, combine_skylines is designed for axis-groups whose only
329    children are other axis-groups (ie. VerticalAlignment). Rather than
330    calculating all the skylines from scratch, we just merge the skylines
331    of the children.
332 */
333 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
334 SCM
335 Axis_group_interface::combine_skylines (SCM smob)
336 {
337   Grob *me = unsmob_grob (smob);
338   extract_grob_set (me, "elements", elements);
339   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
340   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
341
342   if (y_common != me)
343     programming_error ("combining skylines that don't belong to me");
344
345   Skyline_pair ret;
346   for (vsize i = 0; i < elements.size (); i++)
347     {
348       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
349       if (Skyline_pair::unsmob (skyline_scm))
350         {
351           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
352           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
353           other.raise (offset);
354           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
355           ret.merge (other);
356         }
357     }
358   return ret.smobbed_copy ();
359 }
360   
361 SCM
362 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
363 {
364   /* trigger the callback to do skyline-spacing on the children */
365   if (a == Y_AXIS)
366     (void) me->get_property ("vertical-skylines");
367
368   extract_grob_set (me, "elements", elts);
369   Grob *common = common_refpoint_of_array (elts, me, a);
370
371   Real my_coord = me->relative_coordinate (common, a);
372   Interval r (relative_group_extent (elts, common, a));
373
374   return ly_interval2scm (r - my_coord);
375 }
376
377 /* This is like generic_group_extent, but it only counts the grobs that
378    are children of some other axis-group. This is uncached; if it becomes
379    commonly used, it may be necessary to cache it somehow. */
380 Interval
381 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
382 {
383   extract_grob_set (me, "elements", elts);
384   vector<Grob*> new_elts;
385
386   for (vsize i = 0; i < elts.size (); i++)
387     if (elts[i]->common_refpoint (staff, parent_a) == staff)
388       new_elts.push_back (elts[i]);
389
390   return relative_group_extent (new_elts, refp, ext_a);
391 }
392
393
394 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_grobs, 1);
395 SCM
396 Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
397 {
398   Grob *me = unsmob_grob (smob);
399   
400   extract_grob_set (me, "elements", elts);
401
402   vector<Grob*> relevant_grobs;
403   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
404
405   for (vsize i = 0; i < elts.size (); i++)
406     {
407       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
408         relevant_grobs.push_back (elts[i]);
409
410       if (Item *it = dynamic_cast<Item*> (elts[i]))
411         {
412           Direction d = LEFT;
413           do
414             {
415               Item *piece = it->find_prebroken_piece (d);
416               if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
417                 relevant_grobs.push_back (piece);
418             }
419           while (flip (&d) != LEFT);
420         }
421     }
422
423   SCM grobs_scm = Grob_array::make_array ();
424   unsmob_grob_array (grobs_scm)->set_array (relevant_grobs);
425
426   return grobs_scm;
427 }
428
429 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_items, 1);
430 SCM
431 Axis_group_interface::calc_pure_relevant_items (SCM smob)
432 {
433   Grob *me = unsmob_grob (smob);
434
435   extract_grob_set (me, "pure-relevant-grobs", elts);
436
437   vector<Grob*> relevant_items;
438   for (vsize i = 0; i < elts.size (); i++)
439     if (Item *it = dynamic_cast<Item*> (elts[i]))
440       relevant_items.push_back (it);
441
442   vector_sort (relevant_items, Item::less);
443
444   SCM items_scm = Grob_array::make_array ();
445   unsmob_grob_array (items_scm)->set_array (relevant_items);
446   return items_scm;
447 }
448
449 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_spanners, 1);
450 SCM
451 Axis_group_interface::calc_pure_relevant_spanners (SCM smob)
452 {
453   Grob *me = unsmob_grob (smob);
454
455   extract_grob_set (me, "pure-relevant-grobs", elts);
456
457   vector<Grob*> relevant_spanners;
458   for (vsize i = 0; i < elts.size (); i++)
459     if (dynamic_cast<Spanner*> (elts[i]))
460       relevant_spanners.push_back (elts[i]);
461
462   SCM spanners_scm = Grob_array::make_array ();
463   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
464   return spanners_scm;
465 }
466
467 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_y_common, 1);
468 SCM
469 Axis_group_interface::calc_pure_y_common (SCM smob)
470 {
471   Grob *me = unsmob_grob (smob);
472
473   extract_grob_set (me, "pure-relevant-grobs", elts);
474   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
475   if (!common)
476     {
477       me->programming_error ("No common parent found in calc_pure_y_common.");
478       return SCM_EOL;
479     }
480
481   return common->self_scm ();
482 }
483
484 SCM
485 Axis_group_interface::calc_common (Grob *me, Axis axis)
486 {
487   extract_grob_set (me, "elements", elts);
488   Grob *common = common_refpoint_of_array (elts, me, axis);
489   if (!common)
490     {
491       me->programming_error ("No common parent found in calc_common axis.");
492       return SCM_EOL;
493     }
494
495   return common->self_scm ();
496 }
497
498
499 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
500 SCM
501 Axis_group_interface::calc_x_common (SCM grob)
502 {
503   return calc_common (unsmob_grob (grob), X_AXIS);
504 }
505
506 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
507 SCM
508 Axis_group_interface::calc_y_common (SCM grob)
509 {
510   return calc_common (unsmob_grob (grob), Y_AXIS);
511 }
512
513 Interval
514 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
515 {
516   Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
517
518   if (!common)
519     {
520       programming_error ("no pure Y common refpoint");
521       return Interval ();
522     }
523   Real my_coord = me->relative_coordinate (common, Y_AXIS);
524   Interval r (relative_pure_height (me, start, end));
525
526   return r - my_coord;
527 }
528
529 void
530 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
531 {
532   found->push_back (me);
533
534   if (!has_interface (me))
535     return;
536
537   extract_grob_set (me, "elements", elements);
538   for (vsize i = 0; i < elements.size (); i++)
539     {
540       Grob *e = elements[i];
541       Axis_group_interface::get_children (e, found);
542     }
543 }
544
545 bool
546 staff_priority_less (Grob * const &g1, Grob * const &g2)
547 {
548   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
549   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
550
551   if (priority_1 < priority_2)
552     return true;
553   else if (priority_1 > priority_2)
554     return false;
555
556   /* if neither grob has an outside-staff priority, the ordering will have no
557      effect -- we just need to choose a consistent ordering. We do this to
558      avoid the side-effect of calculating extents. */
559   if (isinf (priority_1))
560     return g1 < g2;
561
562   /* if there is no preference in staff priority, choose the left-most one */
563   Grob *common = g1->common_refpoint (g2, X_AXIS);
564   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
565   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
566   return start_1 < start_2;
567 }
568
569 static void
570 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
571 {
572   /* if a child has skylines, use them instead of the extent box */
573   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
574     {
575       Skyline_pair s = *pair;
576       s.shift (me->relative_coordinate (x_common, X_AXIS));
577       s.raise (me->relative_coordinate (y_common, Y_AXIS));
578       skylines->merge (s);
579     }
580   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
581     {
582       for (vsize i = 0; i < elements->size (); i++)
583         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
584     }
585   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
586            && !to_boolean (me->get_property ("cross-staff")))
587     {
588       boxes->push_back (Box (me->extent (x_common, X_AXIS),
589                              me->extent (y_common, Y_AXIS)));
590     }
591 }
592
593 /* We want to avoid situations like this:
594            still more text
595       more text
596    text
597    -------------------
598    staff
599    -------------------
600
601    The point is that "still more text" should be positioned under
602    "more text".  In order to achieve this, we place the grobs in several
603    passes.  We keep track of the right-most horizontal position that has been
604    affected by the current pass so far (actually we keep track of 2
605    positions, one for above the staff, one for below).
606
607    In each pass, we loop through the unplaced grobs from left to right.
608    If the grob doesn't overlap the right-most affected position, we place it
609    (and then update the right-most affected position to point to the right
610    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
611 */
612 static void
613 add_grobs_of_one_priority (Skyline_pair *const skylines,
614                            vector<Grob*> elements,
615                            Grob *x_common,
616                            Grob *y_common)
617 {
618   vector<Box> boxes;
619   Drul_array<Real> last_affected_position;
620
621   reverse (elements);
622   while (!elements.empty ())
623     {
624       last_affected_position[UP] = -infinity_f;
625       last_affected_position[DOWN] = -infinity_f;
626       /* do one pass */
627       for (vsize i = elements.size (); i--;)
628         {
629           Direction dir = get_grob_direction (elements[i]);
630           if (dir == CENTER)
631             {
632               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
633               dir = UP;
634             }
635
636           Box b (elements[i]->extent (x_common, X_AXIS),
637                  elements[i]->extent (y_common, Y_AXIS));
638           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
639           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
640
641           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
642             continue;
643
644           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
645             {
646               boxes.clear ();
647               boxes.push_back (b);
648               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
649               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
650               Real dist = (*skylines)[dir].distance (other) + padding;
651
652               if (dist > 0)
653                 {
654                   b.translate (Offset (0, dir*dist));
655                   elements[i]->translate_axis (dir*dist, Y_AXIS);
656                 }
657               skylines->insert (b, 0, X_AXIS);
658               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
659               last_affected_position[dir] = b[X_AXIS][RIGHT];
660             }
661
662           /*
663             Ugh: quadratic. --hwn
664            */
665           elements.erase (elements.begin () + i);
666         }
667     }
668 }
669
670 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
671 // For example, cross-staff beams cannot be formatted until the distance between
672 // staves is known and therefore any grobs that depend on the beam cannot be placed
673 // until the skylines are known. On the other hand, the distance between staves should
674 // really depend on position of the cross-staff grobs that lie between them.
675 // Currently, we just leave cross-staff grobs out of the
676 // skyline altogether, but this could mean that staves are placed so close together
677 // that there is no room for the cross-staff grob. It also means, of course, that
678 // we don't get the benefits of skyline placement for cross-staff grobs.
679 Skyline_pair
680 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
681 {
682   /* For grobs with an outside-staff-priority, the sorting function might
683      call extent and cause suicide. This breaks the contract that is required
684      for the STL sort function. To avoid this, we make sure that any suicides
685      are triggered beforehand.
686   */
687   for (vsize i = 0; i < elements.size (); i++)
688     if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
689       elements[i]->extent (elements[i], X_AXIS);
690
691   vector_sort (elements, staff_priority_less);
692   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
693   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
694
695   assert (y_common == me);
696
697   vsize i = 0;
698   vector<Box> boxes;
699
700   Skyline_pair skylines;
701   for (i = 0; i < elements.size ()
702          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
703     if (!to_boolean (elements[i]->get_property ("cross-staff")))
704       add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
705
706   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
707   Real padding = robust_scm2double (padding_scm, 0.1);
708   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
709   for (; i < elements.size (); i++)
710     {
711       if (to_boolean (elements[i]->get_property ("cross-staff")))
712         continue;
713
714       SCM priority = elements[i]->get_property ("outside-staff-priority");
715       vector<Grob*> current_elts;
716       current_elts.push_back (elements[i]);
717       while (i + 1 < elements.size () 
718              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
719         {
720           if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
721             current_elts.push_back (elements[i+1]);
722           ++i;
723         }
724
725       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
726     }
727   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
728   return skylines;
729 }
730
731 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
732 SCM
733 Axis_group_interface::print (SCM smob)
734 {
735   if (!debug_skylines)
736     return SCM_BOOL_F;
737
738   Grob *me = unsmob_grob (smob);
739   Stencil ret;
740   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
741     {
742       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
743                        .in_color (255, 0, 255));
744       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
745                        .in_color (0, 255, 255));
746     }
747   return ret.smobbed_copy ();
748 }
749
750 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_next_staff_spacing, 3)
751 SCM
752 Axis_group_interface::calc_pure_next_staff_spacing (SCM smob, SCM start, SCM end)
753 {
754   return calc_maybe_pure_next_staff_spacing (unsmob_grob (smob),
755                                              true,
756                                              scm_to_int (start),
757                                              scm_to_int (end));
758 }
759
760 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
761 SCM
762 Axis_group_interface::calc_next_staff_spacing (SCM smob)
763 {
764   return calc_maybe_pure_next_staff_spacing (unsmob_grob (smob),
765                                              false,
766                                              0,
767                                              INT_MAX);
768 }
769
770 SCM
771 Axis_group_interface::calc_maybe_pure_next_staff_spacing (Grob *me, bool pure, int start, int end)
772 {
773   Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
774
775   if (grouper)
776     {
777       Grob *last_in_group = Staff_grouper_interface::get_maybe_pure_last_grob (grouper, pure, start, end);
778       if (me == last_in_group)
779         return grouper->get_maybe_pure_property ("after-last-staff-spacing", pure, start, end);
780       else
781         return grouper->get_maybe_pure_property ("between-staff-spacing", pure, start, end);
782     }
783   return me->get_maybe_pure_property ("default-next-staff-spacing", pure, start, end);
784 }
785
786 Real
787 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
788 {
789   SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
790
791   Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
792   Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
793   if (s1 && s2)
794     return (*s1)[DOWN].distance ((*s2)[UP]);
795   return 0;
796 }
797
798 ADD_INTERFACE (Axis_group_interface,
799                "An object that groups other layout objects.",
800
801                // TODO: some of these properties are specific to
802                // VerticalAxisGroup. We should split off a
803                // vertical-axis-group-interface.
804                /* properties */
805                "X-common "
806                "Y-common "
807                "adjacent-pure-heights "
808                "axes "
809                "default-next-staff-spacing "
810                "elements "
811                "inter-loose-line-spacing "
812                "inter-staff-spacing "
813                "keep-fixed-while-stretching "
814                "max-stretch "
815                "non-affinity-spacing "
816                "next-staff-spacing "
817                "no-alignment "
818                "pure-Y-common "
819                "pure-relevant-grobs "
820                "pure-relevant-items "
821                "pure-relevant-spanners "
822                "staff-affinity "
823                "staff-grouper "
824                "system-Y-offset "
825                "vertical-skylines "
826                );