]> 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   axis-group-interface.cc -- implement Axis_group_interface
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "axis-group-interface.hh"
10
11 #include "align-interface.hh"
12 #include "directional-element-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "grob-array.hh"
15 #include "hara-kiri-group-spanner.hh"
16 #include "international.hh"
17 #include "lookup.hh"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
21 #include "stencil.hh"
22 #include "system.hh"
23 #include "warn.hh"
24
25 void
26 Axis_group_interface::add_element (Grob *me, Grob *e)
27 {
28   SCM axes = me->get_property ("axes");
29   if (!scm_is_pair (axes))
30     programming_error ("axes should be nonempty");
31
32   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
33     {
34       Axis a = (Axis) scm_to_int (scm_car (ax));
35
36       if (!e->get_parent (a))
37         e->set_parent (me, a);
38
39       e->set_object ((a == X_AXIS)
40                      ? ly_symbol2scm ("axis-group-parent-X")
41                      : ly_symbol2scm ("axis-group-parent-Y"),
42                      me->self_scm ());
43     }
44
45   /* must be ordered, because Align_interface also uses
46      Axis_group_interface  */
47   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
48 }
49
50 bool
51 Axis_group_interface::has_axis (Grob *me, Axis a)
52 {
53   SCM axes = me->get_property ("axes");
54
55   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
56 }
57
58 Interval
59 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
60                                              Grob *common, Axis a)
61 {
62   Interval r;
63   for (vsize i = 0; i < elts.size (); i++)
64     {
65       Grob *se = elts[i];
66       if (!to_boolean (se->get_property ("cross-staff")))
67         {
68           Interval dims = se->extent (common, a);
69           if (!dims.is_empty ())
70             r.unite (dims);
71         }
72     }
73   return r;
74 }
75
76
77 /*
78   FIXME: pure extent handling has a lot of ad-hoc caching.
79   This should be done with grob property callbacks.
80
81   --hwn
82 */
83
84 Interval
85 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
86 {
87   Paper_score *ps = get_root_system (me)->paper_score ();
88   vector<vsize> breaks = ps->get_break_indices ();
89   vector<Grob*> cols = ps->get_columns ();
90
91   SCM extents = me->get_property ("adjacent-pure-heights");
92
93   if (!scm_is_vector (extents))
94     return Interval (0, 0);
95
96   Interval ext;
97   for (vsize i = 0; i + 1 < breaks.size (); i++)
98     {
99       int r = Paper_column::get_rank (cols[breaks[i]]);
100       if (r > end)
101         break;
102
103       if (r >= start)
104         ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
105     }
106
107   return ext;
108 }
109
110 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
111 SCM
112 Axis_group_interface::adjacent_pure_heights (SCM smob)
113 {
114   Grob *me = unsmob_grob (smob);
115
116   Grob *common = calc_pure_elts_and_common (me);
117   extract_grob_set (me, "pure-relevant-items", items);
118   extract_grob_set (me, "pure-relevant-spanners", spanners);
119
120   Paper_score *ps = get_root_system (me)->paper_score ();
121   vector<vsize> breaks = ps->get_break_indices ();
122   vector<Grob*> cols = ps->get_columns ();
123
124   SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
125   vsize it_index = 0;
126   for (vsize i = 0; i + 1 < breaks.size (); i++)
127     {
128       int start = Paper_column::get_rank (cols[breaks[i]]);
129       int end = Paper_column::get_rank (cols[breaks[i+1]]);
130       Interval iv;
131
132       for (vsize j = it_index; j < items.size (); j++)
133         {
134           Item *it = dynamic_cast<Item*> (items[j]);
135           int rank = it->get_column ()->get_rank ();
136
137           if (rank <= end && it->pure_is_visible (start, end)
138               && !to_boolean (it->get_property ("cross-staff")))
139             {
140               Interval dims = items[j]->pure_height (common, start, end);
141               if (!dims.is_empty ())
142                 iv.unite (dims);
143             }
144
145           if (rank < end)
146             it_index++;
147           else if (rank > end)
148             break;
149         }
150
151       for (vsize j = 0; j < spanners.size (); j++)
152         {
153           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
154           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
155               && !to_boolean (spanners[j]->get_property ("cross-staff")))
156             {
157               Interval dims = spanners[j]->pure_height (common, start, end);
158               if (!dims.is_empty ())
159                 iv.unite (dims);
160             }
161         }
162
163       scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
164     }
165   return ret;
166 }
167
168 Interval
169 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
170 {
171   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
172      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
173      Unfortunately, it isn't always true, particularly if there is a
174      VerticalAlignment somewhere in the descendants.
175
176      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
177      count, the only VerticalAlignment comes from Score. This makes it
178      reasonably safe to assume that if our parent is a VerticalAlignment,
179      we can assume additivity and cache things nicely. */
180   Grob *p = me->get_parent (Y_AXIS);
181   if (p && Align_interface::has_interface (p))
182     return Axis_group_interface::cached_pure_height (me, start, end);
183
184   Grob *common = calc_pure_elts_and_common (me);
185   extract_grob_set (me, "pure-relevant-items", items);
186   extract_grob_set (me, "pure-relevant-spanners", spanners);
187
188   Interval r;
189
190   for (vsize i = 0; i < items.size (); i++)
191     {
192       Item *it = dynamic_cast<Item*> (items[i]);
193       int rank = it->get_column ()->get_rank ();
194
195       if (rank > end)
196         break;
197       else if (rank >= start && it->pure_is_visible (start, end))
198         {
199           Interval dims = it->pure_height (common, start, end);
200           if (!dims.is_empty ())
201             r.unite (dims);
202         }
203     }
204
205   for (vsize i = 0; i < spanners.size (); i++)
206     {
207       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
208       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
209         {
210           Interval dims = spanners[i]->pure_height (common, start, end);
211           if (!dims.is_empty ())
212             r.unite (dims);
213         }
214     }
215   return r;
216 }
217
218 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
219 SCM
220 Axis_group_interface::width (SCM smob)
221 {
222   Grob *me = unsmob_grob (smob);
223   return generic_group_extent (me, X_AXIS);
224 }
225
226 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
227 SCM
228 Axis_group_interface::height (SCM smob)
229 {
230   Grob *me = unsmob_grob (smob);
231   return generic_group_extent (me, Y_AXIS);
232 }
233
234 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
235 SCM
236 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
237 {
238   int start = robust_scm2int (start_scm, 0);
239   int end = robust_scm2int (end_scm, INT_MAX);
240   Grob *me = unsmob_grob (smob);
241
242   /* Maybe we are in the second pass of a two-pass spacing run. In that
243      case, the Y-extent of a system is already given to us */
244   System *system = dynamic_cast<System*> (me);
245   if (system)
246     {
247       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
248       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
249       if (scm_is_pair (system_y_extent))
250         return scm_cdr (system_y_extent);
251     }
252
253   return ly_interval2scm (pure_group_height (me, start, end));
254 }
255
256 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
257 SCM
258 Axis_group_interface::calc_skylines (SCM smob)
259 {
260   Grob *me = unsmob_grob (smob);
261   extract_grob_set (me, "elements", elts);
262   Skyline_pair skylines = skyline_spacing (me, elts);
263
264   /* add a minimum-Y-extent-sized box to the skyline */
265   SCM min_y_extent = me->get_property ("minimum-Y-extent");
266   if (is_number_pair (min_y_extent))
267     {
268       Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
269       skylines.insert (b, 0, X_AXIS);
270     }
271   return skylines.smobbed_copy ();
272 }
273
274 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
275    visible children, combine_skylines is designed for axis-groups whose only
276    children are other axis-groups (ie. VerticalAlignment). Rather than
277    calculating all the skylines from scratch, we just merge the skylines
278    of the children.
279 */
280 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
281 SCM
282 Axis_group_interface::combine_skylines (SCM smob)
283 {
284   Grob *me = unsmob_grob (smob);
285   extract_grob_set (me, "elements", elements);
286   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
287   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
288
289   if (y_common != me)
290     programming_error ("combining skylines that don't belong to me");
291
292   Skyline_pair ret;
293   for (vsize i = 0; i < elements.size (); i++)
294     {
295       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
296       if (Skyline_pair::unsmob (skyline_scm))
297         {
298           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
299           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
300           other.raise (offset);
301           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
302           ret.merge (other);
303         }
304     }
305   return ret.smobbed_copy ();
306 }
307   
308 SCM
309 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
310 {
311   /* trigger the callback to do skyline-spacing on the children */
312   if (a == Y_AXIS)
313     (void) me->get_property ("vertical-skylines");
314
315   extract_grob_set (me, "elements", elts);
316   Grob *common = common_refpoint_of_array (elts, me, a);
317
318   Real my_coord = me->relative_coordinate (common, a);
319   Interval r (relative_group_extent (elts, common, a));
320
321   return ly_interval2scm (r - my_coord);
322 }
323
324
325 Grob *
326 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
327 {
328   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
329     return c;
330   
331   extract_grob_set (me, "elements", elts);
332
333   vector<Grob*> relevant_items;
334   vector<Grob*> relevant_spanners;
335   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
336
337   for (vsize i = 0; i < elts.size (); i++)
338     {
339       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
340         {
341           if (dynamic_cast<Item*> (elts[i]))
342             relevant_items.push_back (elts[i]);
343           else if (dynamic_cast<Spanner*> (elts[i]))
344             relevant_spanners.push_back (elts[i]);
345         }
346             
347
348       Item *it = dynamic_cast<Item*> (elts[i]);
349       Direction d = LEFT;
350       if (it)
351         do
352           {
353             Item *piece = it->find_prebroken_piece (d);
354             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
355               relevant_items.push_back (piece);
356           }
357         while (flip (&d) != LEFT);
358     }
359   vector_sort (relevant_items, Item::less);
360
361   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
362   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
363
364   me->set_object ("pure-Y-common", common->self_scm ());
365   
366   SCM items_scm = Grob_array::make_array ();
367   SCM spanners_scm = Grob_array::make_array ();
368
369   unsmob_grob_array (items_scm)->set_array (relevant_items);
370   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
371   me->set_object ("pure-relevant-items", items_scm);
372   me->set_object ("pure-relevant-spanners", spanners_scm);
373
374   return common;
375 }
376
377 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
378 SCM
379 Axis_group_interface::calc_x_common (SCM grob)
380 {
381   Grob *me = unsmob_grob (grob);
382
383   extract_grob_set (me, "elements", elts);
384   Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
385   return common->self_scm ();
386 }
387
388 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
389 SCM
390 Axis_group_interface::calc_y_common (SCM grob)
391 {
392   Grob *me = unsmob_grob (grob);
393
394   extract_grob_set (me, "elements", elts);
395   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
396   return common->self_scm ();
397 }
398
399 Interval
400 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
401 {
402   Grob *common = calc_pure_elts_and_common (me);
403         
404   Real my_coord = me->relative_coordinate (common, Y_AXIS);
405   Interval r (relative_pure_height (me, start, end));
406
407   return r - my_coord;
408 }
409
410 void
411 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
412 {
413   found->push_back (me);
414
415   if (!has_interface (me))
416     return;
417
418   extract_grob_set (me, "elements", elements);
419   for (vsize i = 0; i < elements.size (); i++)
420     {
421       Grob *e = elements[i];
422       Axis_group_interface::get_children (e, found);
423     }
424 }
425
426 bool
427 staff_priority_less (Grob * const &g1, Grob * const &g2)
428 {
429   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
430   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
431
432   if (priority_1 < priority_2)
433     return true;
434   else if (priority_1 > priority_2)
435     return false;
436
437   /* if neither grob has an outside-staff priority, the ordering will have no
438      effect -- we just need to choose a consistent ordering. We do this to
439      avoid the side-effect of calculating extents. */
440   if (isinf (priority_1))
441     return g1 < g2;
442
443   /* if there is no preference in staff priority, choose the left-most one */
444   Grob *common = g1->common_refpoint (g2, X_AXIS);
445   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
446   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
447   return start_1 < start_2;
448 }
449
450 static void
451 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
452 {
453   /* if a child has skylines, use them instead of the extent box */
454   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
455     {
456       Skyline_pair s = *pair;
457       s.shift (me->relative_coordinate (x_common, X_AXIS));
458       s.raise (me->relative_coordinate (y_common, Y_AXIS));
459       skylines->merge (s);
460     }
461   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
462     {
463       for (vsize i = 0; i < elements->size (); i++)
464         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
465     }
466   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
467            && !to_boolean (me->get_property ("cross-staff")))
468     {
469       boxes->push_back (Box (me->extent (x_common, X_AXIS),
470                              me->extent (y_common, Y_AXIS)));
471     }
472 }
473
474 /* We want to avoid situations like this:
475            still more text
476       more text
477    text
478    -------------------
479    staff
480    -------------------
481
482    The point is that "still more text" should be positioned under
483    "more text".  In order to achieve this, we place the grobs in several
484    passes.  We keep track of the right-most horizontal position that has been
485    affected by the current pass so far (actually we keep track of 2
486    positions, one for above the staff, one for below).
487
488    In each pass, we loop through the unplaced grobs from left to right.
489    If the grob doesn't overlap the right-most affected position, we place it
490    (and then update the right-most affected position to point to the right
491    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
492 */
493 static void
494 add_grobs_of_one_priority (Skyline_pair *const skylines,
495                            vector<Grob*> elements,
496                            Grob *x_common,
497                            Grob *y_common)
498 {
499   vector<Box> boxes;
500   Drul_array<Real> last_affected_position;
501
502   reverse (elements);
503   while (!elements.empty ())
504     {
505       last_affected_position[UP] = -infinity_f;
506       last_affected_position[DOWN] = -infinity_f;
507       /* do one pass */
508       for (vsize i = elements.size (); i--;)
509         {
510           Direction dir = get_grob_direction (elements[i]);
511           if (dir == CENTER)
512             {
513               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
514               dir = UP;
515             }
516
517           Box b (elements[i]->extent (x_common, X_AXIS),
518                  elements[i]->extent (y_common, Y_AXIS));
519           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
520           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
521
522           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
523             continue;
524
525           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
526             {
527               boxes.clear ();
528               boxes.push_back (b);
529               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
530               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
531               Real dist = (*skylines)[dir].distance (other) + padding;
532
533               if (dist > 0)
534                 {
535                   b.translate (Offset (0, dir*dist));
536                   elements[i]->translate_axis (dir*dist, Y_AXIS);
537                 }
538               (*skylines)[dir].insert (b, 0, X_AXIS);
539               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
540               last_affected_position[dir] = b[X_AXIS][RIGHT];
541             }
542
543           /*
544             Ugh: quadratic. --hwn
545            */
546           elements.erase (elements.begin () + i);
547         }
548     }
549 }
550
551 Skyline_pair
552 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
553 {
554   vector_sort (elements, staff_priority_less);
555   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
556   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
557
558   assert (y_common == me);
559
560   vsize i = 0;
561   vector<Box> boxes;
562
563   Skyline_pair skylines;
564   for (i = 0; i < elements.size ()
565          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
566     add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
567
568   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
569   Real padding = robust_scm2double (padding_scm, 0.1);
570   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
571   for (; i < elements.size (); i++)
572     {
573       SCM priority = elements[i]->get_property ("outside-staff-priority");
574       vector<Grob*> current_elts;
575       current_elts.push_back (elements[i]);
576       while (i + 1 < elements.size () 
577              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
578         current_elts.push_back (elements[++i]);
579
580       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
581     }
582   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
583   return skylines;
584 }
585
586 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
587 SCM
588 Axis_group_interface::calc_max_stretch (SCM smob)
589 {
590   Grob *me = unsmob_grob (smob);
591   Real ret = 0;
592   extract_grob_set (me, "elements", elts);
593
594   for (vsize i = 0; i < elts.size (); i++)
595     if (Axis_group_interface::has_interface (elts[i]))
596       ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
597
598   return scm_from_double (ret);
599 }
600
601 extern bool debug_skylines;
602 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
603 SCM
604 Axis_group_interface::print (SCM smob)
605 {
606   if (!debug_skylines)
607     return SCM_BOOL_F;
608
609   Grob *me = unsmob_grob (smob);
610   Stencil ret;
611   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
612     {
613       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
614       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
615     }
616   return ret.smobbed_copy ();
617 }
618
619 ADD_INTERFACE (Axis_group_interface,
620
621                "An object that groups other layout objects.",
622
623                /* properties */
624                "X-common "
625                "Y-common "
626                "adjacent-pure-heights "
627                "axes "
628                "elements "
629                "keep-fixed-while-stretching "
630                "max-stretch "
631                "no-alignment "
632                "pure-Y-common "
633                "pure-relevant-items "
634                "pure-relevant-spanners "
635                "vertical-skylines "
636                );