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