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