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