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