]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Merge branch 'jneeman' of git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond into jneeman
[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--2006 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 "paper-column.hh"
18 #include "paper-score.hh"
19 #include "separation-item.hh"
20 #include "system.hh"
21 #include "warn.hh"
22
23 void
24 Axis_group_interface::add_element (Grob *me, Grob *e)
25 {
26   SCM axes = me->get_property ("axes");
27   if (!scm_is_pair (axes))
28     programming_error ("axes should be nonempty");
29
30   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
31     {
32       Axis a = (Axis) scm_to_int (scm_car (ax));
33
34       if (!e->get_parent (a))
35         e->set_parent (me, a);
36
37       e->set_object ((a == X_AXIS)
38                      ? ly_symbol2scm ("axis-group-parent-X")
39                      : ly_symbol2scm ("axis-group-parent-Y"),
40                      me->self_scm ());
41     }
42
43   /* must be ordered, because Align_interface also uses
44      Axis_group_interface  */
45   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
46 }
47
48 bool
49 Axis_group_interface::has_axis (Grob *me, Axis a)
50 {
51   SCM axes = me->get_property ("axes");
52
53   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
54 }
55
56 Interval
57 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
58                                              Grob *common, Axis a)
59 {
60   Interval r;
61   for (vsize i = 0; i < elts.size (); i++)
62     {
63       Grob *se = elts[i];
64       Interval dims = se->extent (common, a);
65       if (!dims.is_empty ())
66         r.unite (dims);
67     }
68   return r;
69 }
70
71 Interval
72 Axis_group_interface::cached_pure_height (Grob *me,
73                                           vector<Grob*> const &elts,
74                                           Grob *common,
75                                           int start, int end)
76 {
77   Paper_score *ps = get_root_system (me)->paper_score ();
78   vector<vsize> breaks = ps->get_break_indices ();
79   vector<Grob*> cols = ps->get_columns ();
80   vsize start_index = VPOS;
81   vsize end_index = VPOS;
82
83   for (vsize i = 0; i < breaks.size (); i++)
84     {
85       int r = Paper_column::get_rank (cols[breaks[i]]);
86       if (start == r)
87         start_index = i;
88       if (end == r)
89         end_index = i;
90     }
91   if (end == INT_MAX)
92     end_index = breaks.size () - 1;
93
94   if (start_index == VPOS || end_index == VPOS)
95     {
96       programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
97       return Interval (0, 0);
98     }
99
100   SCM extents = me->get_property ("cached-pure-extents");
101   if (!scm_is_vector (extents))
102     {
103       extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
104       for (vsize i = 0; i < breaks.size () - 1; i++)
105         {
106           int st = Paper_column::get_rank (cols[breaks[i]]);
107           int ed = Paper_column::get_rank (cols[breaks[i+1]]);
108           Interval iv = relative_pure_height (me, elts, common, st, ed, false);
109           scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
110         }
111       me->set_property ("cached-pure-extents", extents);
112     }
113
114   Interval ext (0, 0);
115   for (vsize i = start_index; i < end_index; i++)
116     ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
117   return ext;
118 }
119
120 Interval
121 Axis_group_interface::relative_pure_height (Grob *me,
122                                             vector<Grob*> const &elts,
123                                             Grob *common,
124                                             int start, int end,
125                                             bool use_cache)
126 {
127   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
128      (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
129      Unfortunately, it isn't always true, particularly if there is a
130      VerticalAlignment somewhere in the descendants.
131
132      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
133      count, the only VerticalAlignment comes from Score. This makes it
134      reasonably safe to assume that if our parent is a VerticalAlignment,
135      we can assume additivity and cache things nicely. */
136   Grob *p = me->get_parent (Y_AXIS);
137   if (use_cache && p && Align_interface::has_interface (p))
138     return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
139
140   Interval r;
141
142   for (vsize i = 0; i < elts.size (); i++)
143     {
144       Interval_t<int> rank_span = elts[i]->spanned_rank_iv ();
145       Item *it = dynamic_cast<Item*> (elts[i]);
146       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
147         {
148           Interval dims = elts[i]->pure_height (common, start, end);
149           if (!dims.is_empty ())
150             r.unite (dims);
151         }
152     }
153   return r;
154 }
155
156 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
157 SCM
158 Axis_group_interface::width (SCM smob)
159 {
160   Grob *me = unsmob_grob (smob);
161   return generic_group_extent (me, X_AXIS);
162 }
163
164 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
165 SCM
166 Axis_group_interface::height (SCM smob)
167 {
168   Grob *me = unsmob_grob (smob);
169   return generic_group_extent (me, Y_AXIS);
170 }
171
172 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
173 SCM
174 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
175 {
176   int start = robust_scm2int (start_scm, 0);
177   int end = robust_scm2int (end_scm, INT_MAX);
178   Grob *me = unsmob_grob (smob);
179
180   return pure_group_height (me, start, end);
181 }
182
183 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
184 SCM
185 Axis_group_interface::calc_skylines (SCM smob)
186 {
187   Grob *me = unsmob_grob (smob);
188   extract_grob_set (me, "elements", elts);
189   return skyline_spacing (me, elts).smobbed_copy ();
190 }
191
192 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
193    visible children, combine_skylines is designed for axis-groups whose only
194    children are other axis-groups (ie. VerticalAlignment). Rather than
195    calculating all the skylines from scratch, we just merge the skylines
196    of the children.
197 */
198 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
199 SCM
200 Axis_group_interface::combine_skylines (SCM smob)
201 {
202   Grob *me = unsmob_grob (smob);
203   extract_grob_set (me, "elements", elements);
204   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
205
206   assert (y_common == me);
207
208   Skyline_pair ret;
209   for (vsize i = 0; i < elements.size (); i++)
210     {
211       SCM skyline_scm = elements[i]->get_property ("skylines");
212       if (Skyline_pair::unsmob (skyline_scm))
213         {
214           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
215           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
216           other.raise (offset);
217           ret.merge (other);
218         }
219     }
220   return ret.smobbed_copy ();
221 }
222   
223 SCM
224 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
225 {
226   /* trigger the callback to do skyline-spacing on the children */
227   (void) me->get_property ("skylines");
228
229   extract_grob_set (me, "elements", elts);
230   Grob *common = common_refpoint_of_array (elts, me, a);
231
232   Real my_coord = me->relative_coordinate (common, a);
233   Interval r (relative_group_extent (elts, common, a));
234
235   return ly_interval2scm (r - my_coord);
236 }
237
238 SCM
239 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
240 {
241   Grob *common = unsmob_grob (me->get_object ("common-refpoint-of-elements"));
242
243   if (!common)
244     {
245       extract_grob_set (me, "elements", elts);
246
247       vector<Grob*> relevant_elts;
248       SCM is_relevant = ly_lily_module_constant ("pure-relevant");
249
250       for (vsize i = 0; i < elts.size (); i++)
251         {
252           if (to_boolean (scm_apply_1 (is_relevant, elts[i]->self_scm (), SCM_EOL)))
253             relevant_elts.push_back (elts[i]);
254
255           Item *it = dynamic_cast<Item*> (elts[i]);
256           Direction d = LEFT;
257           if (it)
258             do
259               {
260                 Item *piece = it->find_prebroken_piece (d);
261                 if (piece && to_boolean (scm_apply_1 (is_relevant, piece->self_scm (), SCM_EOL)))
262                   relevant_elts.push_back (piece);
263               }
264             while (flip (&d) != LEFT);
265         }
266
267       common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
268       me->set_object ("common-refpoint-of-elements", common->self_scm ());
269
270       SCM ga_scm = Grob_array::make_array ();
271       Grob_array *ga = unsmob_grob_array (ga_scm);
272       ga->set_array (relevant_elts);
273       me->set_object ("pure-relevant-elements", ga_scm);
274     }
275
276   extract_grob_set (me, "pure-relevant-elements", elts);
277   Real my_coord = me->relative_coordinate (common, Y_AXIS);
278   Interval r (relative_pure_height (me, elts, common, start, end, true));
279
280   return ly_interval2scm (r - my_coord);
281 }
282
283 void
284 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
285 {
286   found->push_back (me);
287
288   if (!has_interface (me))
289     return;
290
291   extract_grob_set (me, "elements", elements);
292   for (vsize i = 0; i < elements.size (); i++)
293     {
294       Grob *e = elements[i];
295       Axis_group_interface::get_children (e, found);
296     }
297 }
298
299 bool
300 staff_priority_less (Grob * const &g1, Grob * const &g2)
301 {
302   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
303   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
304
305   if (priority_1 < priority_2)
306     return true;
307   else if (priority_1 > priority_2)
308     return false;
309
310   /* if there is no preference in staff priority, choose the left-most one */
311   Grob *common = g1->common_refpoint (g2, X_AXIS);
312   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
313   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
314   return start_1 < start_2;
315 }
316
317 static void
318 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
319 {
320   /* if we are a parent, consider the children's boxes instead of mine */
321   if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
322     {
323       for (vsize i = 0; i < elements->size (); i++)
324         add_boxes (elements->grob (i), x_common, y_common, boxes);
325     }
326   else if (!scm_is_number (me->get_property ("outside-staff-priority")))
327     boxes->push_back (Box (me->extent (x_common, X_AXIS),
328                            me->extent (y_common, Y_AXIS)));
329 }
330
331 /* We want to avoid situations like this:
332            still more text
333       more text
334    text
335    -------------------
336    staff
337    -------------------
338
339    The point is that "still more text" should be positioned under
340    "more text".  In order to achieve this, we place the grobs in several
341    passes.  We keep track of the right-most horizontal position that has been
342    affected by the current pass so far (actually we keep track of 2
343    positions, one for above the staff, one for below).
344
345    In each pass, we loop through the unplaced grobs from left to right.
346    If the grob overlaps the right-most affected position, we place it
347    (and then update the right-most affected position to point to the right
348    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
349 */
350 static void
351 add_grobs_of_one_priority (Skyline_pair *const skylines,
352                            vector<Grob*> elements,
353                            Grob *x_common,
354                            Grob *y_common)
355 {
356   vector<Box> boxes;
357   Drul_array<Real> last_affected_position;
358
359   reverse (elements);
360   while (!elements.empty ())
361     {
362       last_affected_position[UP] = -infinity_f;
363       last_affected_position[DOWN] = -infinity_f;
364       /* do one pass */
365       for (vsize i = elements.size (); i--;)
366         {
367           Direction dir = get_grob_direction (elements[i]);
368           if (dir == CENTER)
369             {
370               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
371               dir = UP;
372             }
373
374           Box b (elements[i]->extent (x_common, X_AXIS),
375                  elements[i]->extent (y_common, Y_AXIS));
376           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
377           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
378
379           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
380             continue;
381
382           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
383             warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
384           else
385             {
386               boxes.clear ();
387               boxes.push_back (b);
388               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
389               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
390               Real dist = (*skylines)[dir].distance (other) + padding;
391
392               if (dist > 0)
393                 {
394                   b.translate (Offset (0, dir*dist));
395                   elements[i]->translate_axis (dir*dist, Y_AXIS);
396                 }
397               (*skylines)[dir].insert (b, 0, X_AXIS);
398               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
399               last_affected_position[dir] = b[X_AXIS][RIGHT];
400             }
401           elements.erase (elements.begin () + i);
402         }
403     }
404 }
405
406 Skyline_pair
407 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
408 {
409   vector_sort (elements, staff_priority_less);
410   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
411   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
412
413   assert (y_common == me);
414
415   vsize i = 0;
416   vector<Box> boxes;
417
418   for (i = 0; i < elements.size ()
419          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
420     add_boxes (elements[i], x_common, y_common, &boxes);
421
422   Skyline_pair skylines (boxes, 0, X_AXIS);
423   for (; i < elements.size (); i++)
424     {
425       SCM priority = elements[i]->get_property ("outside-staff-priority");
426       vector<Grob*> current_elts;
427       current_elts.push_back (elements[i]);
428       while (i < elements.size () - 1
429              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
430         current_elts.push_back (elements[++i]);
431
432       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
433     }
434   return skylines;
435 }
436
437 ADD_INTERFACE (Axis_group_interface,
438
439                "An object that groups other layout objects.",
440
441                /* properties */
442                "axes "
443                "elements "
444                "common-refpoint-of-elements "
445                "pure-relevant-elements "
446                "skylines "
447                "cached-pure-extents "
448                );