]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Merge branch 'master' into lilypond/translation
[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 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
670 // For example, cross-staff beams cannot be formatted until the distance between
671 // staves is known and therefore any grobs that depend on the beam cannot be placed
672 // until the skylines are known. On the other hand, the distance between staves should
673 // really depend on position of the cross-staff grobs that lie between them.
674 // Currently, we just leave cross-staff grobs out of the
675 // skyline altogether, but this could mean that staves are placed so close together
676 // that there is no room for the cross-staff grob. It also means, of course, that
677 // we don't get the benefits of skyline placement for cross-staff grobs.
678 Skyline_pair
679 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
680 {
681   /* For grobs with an outside-staff-priority, the sorting function might
682      call extent and cause suicide. This breaks the contract that is required
683      for the STL sort function. To avoid this, we make sure that any suicides
684      are triggered beforehand.
685   */
686   for (vsize i = 0; i < elements.size (); i++)
687     if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
688       elements[i]->extent (elements[i], X_AXIS);
689
690   vector_sort (elements, staff_priority_less);
691   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
692   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
693
694   assert (y_common == me);
695
696   vsize i = 0;
697   vector<Box> boxes;
698
699   Skyline_pair skylines;
700   for (i = 0; i < elements.size ()
701          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
702     if (!to_boolean (elements[i]->get_property ("cross-staff")))
703       add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
704
705   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
706   Real padding = robust_scm2double (padding_scm, 0.1);
707   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
708   for (; i < elements.size (); i++)
709     {
710       if (to_boolean (elements[i]->get_property ("cross-staff")))
711         continue;
712
713       SCM priority = elements[i]->get_property ("outside-staff-priority");
714       vector<Grob*> current_elts;
715       current_elts.push_back (elements[i]);
716       while (i + 1 < elements.size ()
717              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
718         {
719           if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
720             current_elts.push_back (elements[i+1]);
721           ++i;
722         }
723
724       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
725     }
726   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
727   return skylines;
728 }
729
730 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
731 SCM
732 Axis_group_interface::print (SCM smob)
733 {
734   if (!debug_skylines)
735     return SCM_BOOL_F;
736
737   Grob *me = unsmob_grob (smob);
738   Stencil ret;
739   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
740     {
741       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
742                        .in_color (255, 0, 255));
743       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
744                        .in_color (0, 255, 255));
745     }
746   return ret.smobbed_copy ();
747 }
748
749 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_staff_staff_spacing, 3)
750 SCM
751 Axis_group_interface::calc_pure_staff_staff_spacing (SCM smob, SCM start, SCM end)
752 {
753   return calc_maybe_pure_staff_staff_spacing (unsmob_grob (smob),
754                                               true,
755                                               scm_to_int (start),
756                                               scm_to_int (end));
757 }
758
759 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_staff_staff_spacing, 1)
760 SCM
761 Axis_group_interface::calc_staff_staff_spacing (SCM smob)
762 {
763   return calc_maybe_pure_staff_staff_spacing (unsmob_grob (smob),
764                                               false,
765                                               0,
766                                               INT_MAX);
767 }
768
769 SCM
770 Axis_group_interface::calc_maybe_pure_staff_staff_spacing (Grob *me, bool pure, int start, int end)
771 {
772   Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
773
774   if (grouper)
775     {
776       Grob *last_in_group = Staff_grouper_interface::get_maybe_pure_last_grob (grouper, pure, start, end);
777       if (me == last_in_group)
778         return grouper->get_maybe_pure_property ("staffgroup-staff-spacing", pure, start, end);
779       else
780         return grouper->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
781     }
782   return me->get_maybe_pure_property ("default-staff-staff-spacing", pure, start, end);
783 }
784
785 Real
786 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
787 {
788   SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
789
790   Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
791   Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
792   if (s1 && s2)
793     return (*s1)[DOWN].distance ((*s2)[UP]);
794   return 0;
795 }
796
797 ADD_INTERFACE (Axis_group_interface,
798                "An object that groups other layout objects.",
799
800                // TODO: some of these properties are specific to
801                // VerticalAxisGroup. We should split off a
802                // vertical-axis-group-interface.
803                /* properties */
804                "adjacent-pure-heights "
805                "axes "
806                "default-staff-staff-spacing "
807                "elements "
808                "max-stretch "
809                "no-alignment "
810                "nonstaff-nonstaff-spacing "
811                "nonstaff-relatedstaff-spacing "
812                "nonstaff-unrelatedstaff-spacing "
813                "pure-relevant-grobs "
814                "pure-relevant-items "
815                "pure-relevant-spanners "
816                "pure-Y-common "
817                "staff-affinity "
818                "staff-grouper "
819                "staff-staff-spacing "
820                "system-Y-offset "
821                "vertical-skylines "
822                "X-common "
823                "Y-common "
824                );