]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Cache common refpoint of VerticalAxisGroup using object-callback.
[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
239 Grob *
240 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
241 {
242   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
243     return c;
244   
245   extract_grob_set (me, "elements", elts);
246
247   vector<Grob*> relevant_elts;
248   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
249
250   for (vsize i = 0; i < elts.size (); i++)
251     {
252       if (to_boolean (scm_apply_1 (pure_relevant_p, 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 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
262               relevant_elts.push_back (piece);
263           }
264         while (flip (&d) != LEFT);
265     }
266
267   Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
268   me->set_object ("pure-Y-common", 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   return common;
276 }
277
278 MAKE_SCHEME_CALLBACK(Axis_group_interface,calc_y_common, 1);
279 SCM
280 Axis_group_interface::calc_y_common (SCM grob)
281 {
282   Grob *me = unsmob_grob (grob);
283
284   extract_grob_set (me, "elements", elts);
285   return common_refpoint_of_array (elts, me, Y_AXIS)->self_scm ();
286 }
287
288 SCM
289 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
290 {
291   Grob *common = calc_pure_elts_and_common (me);
292         
293   extract_grob_set (me, "pure-relevant-elements", elts);
294   Real my_coord = me->relative_coordinate (common, Y_AXIS);
295   Interval r (relative_pure_height (me, elts, common, start, end, true));
296
297   return ly_interval2scm (r - my_coord);
298 }
299
300 void
301 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
302 {
303   found->push_back (me);
304
305   if (!has_interface (me))
306     return;
307
308   extract_grob_set (me, "elements", elements);
309   for (vsize i = 0; i < elements.size (); i++)
310     {
311       Grob *e = elements[i];
312       Axis_group_interface::get_children (e, found);
313     }
314 }
315
316 bool
317 staff_priority_less (Grob * const &g1, Grob * const &g2)
318 {
319   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
320   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
321
322   if (priority_1 < priority_2)
323     return true;
324   else if (priority_1 > priority_2)
325     return false;
326
327   /* if there is no preference in staff priority, choose the left-most one */
328   Grob *common = g1->common_refpoint (g2, X_AXIS);
329   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
330   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
331   return start_1 < start_2;
332 }
333
334 static void
335 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
336 {
337   /* if we are a parent, consider the children's boxes instead of mine */
338   if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
339     {
340       for (vsize i = 0; i < elements->size (); i++)
341         add_boxes (elements->grob (i), x_common, y_common, boxes);
342     }
343   else if (!scm_is_number (me->get_property ("outside-staff-priority")))
344     boxes->push_back (Box (me->extent (x_common, X_AXIS),
345                            me->extent (y_common, Y_AXIS)));
346 }
347
348 /* We want to avoid situations like this:
349            still more text
350       more text
351    text
352    -------------------
353    staff
354    -------------------
355
356    The point is that "still more text" should be positioned under
357    "more text".  In order to achieve this, we place the grobs in several
358    passes.  We keep track of the right-most horizontal position that has been
359    affected by the current pass so far (actually we keep track of 2
360    positions, one for above the staff, one for below).
361
362    In each pass, we loop through the unplaced grobs from left to right.
363    If the grob overlaps the right-most affected position, we place it
364    (and then update the right-most affected position to point to the right
365    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
366 */
367 static void
368 add_grobs_of_one_priority (Skyline_pair *const skylines,
369                            vector<Grob*> elements,
370                            Grob *x_common,
371                            Grob *y_common)
372 {
373   vector<Box> boxes;
374   Drul_array<Real> last_affected_position;
375
376   reverse (elements);
377   while (!elements.empty ())
378     {
379       last_affected_position[UP] = -infinity_f;
380       last_affected_position[DOWN] = -infinity_f;
381       /* do one pass */
382       for (vsize i = elements.size (); i--;)
383         {
384           Direction dir = get_grob_direction (elements[i]);
385           if (dir == CENTER)
386             {
387               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
388               dir = UP;
389             }
390
391           Box b (elements[i]->extent (x_common, X_AXIS),
392                  elements[i]->extent (y_common, Y_AXIS));
393           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
394           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
395
396           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
397             continue;
398
399           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
400             warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
401           else
402             {
403               boxes.clear ();
404               boxes.push_back (b);
405               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
406               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
407               Real dist = (*skylines)[dir].distance (other) + padding;
408
409               if (dist > 0)
410                 {
411                   b.translate (Offset (0, dir*dist));
412                   elements[i]->translate_axis (dir*dist, Y_AXIS);
413                 }
414               (*skylines)[dir].insert (b, 0, X_AXIS);
415               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
416               last_affected_position[dir] = b[X_AXIS][RIGHT];
417             }
418
419           /*
420             Ugh: quadratic. --hwn
421            */
422           elements.erase (elements.begin () + i);
423         }
424     }
425 }
426
427 Skyline_pair
428 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
429 {
430   vector_sort (elements, staff_priority_less);
431   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
432   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
433
434   assert (y_common == me);
435
436   vsize i = 0;
437   vector<Box> boxes;
438
439   for (i = 0; i < elements.size ()
440          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
441     add_boxes (elements[i], x_common, y_common, &boxes);
442
443   Skyline_pair skylines (boxes, 0, X_AXIS);
444   for (; i < elements.size (); i++)
445     {
446       SCM priority = elements[i]->get_property ("outside-staff-priority");
447       vector<Grob*> current_elts;
448       current_elts.push_back (elements[i]);
449       while (i < elements.size () - 1
450              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
451         current_elts.push_back (elements[++i]);
452
453       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
454     }
455   return skylines;
456 }
457
458 ADD_INTERFACE (Axis_group_interface,
459
460                "An object that groups other layout objects.",
461
462                /* properties */
463                "X-common "
464                "Y-common "
465                "axes "
466                "elements "
467                "pure-Y-common "
468                "pure-relevant-elements "
469                "skylines "
470                "cached-pure-extents "
471                );