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