]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[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 "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 + 1 < breaks.size (); 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   /* Maybe we are in the second pass of a two-pass spacing run. In that
189      case, the Y-extent of a system is already given to us */
190   System *system = dynamic_cast<System*> (me);
191   if (system)
192     {
193       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
194       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
195       if (scm_is_pair (system_y_extent))
196         return scm_cdr (system_y_extent);
197     }
198
199   return pure_group_height (me, start, end);
200 }
201
202 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
203 SCM
204 Axis_group_interface::calc_skylines (SCM smob)
205 {
206   Grob *me = unsmob_grob (smob);
207   extract_grob_set (me, "elements", elts);
208   return skyline_spacing (me, elts).smobbed_copy ();
209 }
210
211 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
212    visible children, combine_skylines is designed for axis-groups whose only
213    children are other axis-groups (ie. VerticalAlignment). Rather than
214    calculating all the skylines from scratch, we just merge the skylines
215    of the children.
216 */
217 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
218 SCM
219 Axis_group_interface::combine_skylines (SCM smob)
220 {
221   Grob *me = unsmob_grob (smob);
222   extract_grob_set (me, "elements", elements);
223   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
224
225   assert (y_common == me);
226
227   Skyline_pair ret;
228   for (vsize i = 0; i < elements.size (); i++)
229     {
230       SCM skyline_scm = elements[i]->get_property ("skylines");
231       if (Skyline_pair::unsmob (skyline_scm))
232         {
233           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
234           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
235           other.raise (offset);
236           ret.merge (other);
237         }
238     }
239   return ret.smobbed_copy ();
240 }
241   
242 SCM
243 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
244 {
245   /* trigger the callback to do skyline-spacing on the children */
246   (void) me->get_property ("skylines");
247
248   extract_grob_set (me, "elements", elts);
249   Grob *common = common_refpoint_of_array (elts, me, a);
250
251   Real my_coord = me->relative_coordinate (common, a);
252   Interval r (relative_group_extent (elts, common, a));
253
254   return ly_interval2scm (r - my_coord);
255 }
256
257
258 Grob *
259 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
260 {
261   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
262     return c;
263   
264   extract_grob_set (me, "elements", elts);
265
266   vector<Grob*> relevant_elts;
267   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
268
269   for (vsize i = 0; i < elts.size (); i++)
270     {
271       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
272         relevant_elts.push_back (elts[i]);
273
274       Item *it = dynamic_cast<Item*> (elts[i]);
275       Direction d = LEFT;
276       if (it)
277         do
278           {
279             Item *piece = it->find_prebroken_piece (d);
280             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
281               relevant_elts.push_back (piece);
282           }
283         while (flip (&d) != LEFT);
284     }
285
286   Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
287   me->set_object ("pure-Y-common", common->self_scm ());
288   
289   SCM ga_scm = Grob_array::make_array ();
290   Grob_array *ga = unsmob_grob_array (ga_scm);
291   ga->set_array (relevant_elts);
292   me->set_object ("pure-relevant-elements", ga_scm);
293
294   return common;
295 }
296
297 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
298 SCM
299 Axis_group_interface::calc_x_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, X_AXIS);
305   return common->self_scm ();
306 }
307
308 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
309 SCM
310 Axis_group_interface::calc_y_common (SCM grob)
311 {
312   Grob *me = unsmob_grob (grob);
313
314   extract_grob_set (me, "elements", elts);
315   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
316   return common->self_scm ();
317 }
318
319 SCM
320 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
321 {
322   Grob *common = calc_pure_elts_and_common (me);
323         
324   extract_grob_set (me, "pure-relevant-elements", elts);
325   Real my_coord = me->relative_coordinate (common, Y_AXIS);
326   Interval r (relative_pure_height (me, elts, common, start, end, true));
327
328   return ly_interval2scm (r - my_coord);
329 }
330
331 void
332 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
333 {
334   found->push_back (me);
335
336   if (!has_interface (me))
337     return;
338
339   extract_grob_set (me, "elements", elements);
340   for (vsize i = 0; i < elements.size (); i++)
341     {
342       Grob *e = elements[i];
343       Axis_group_interface::get_children (e, found);
344     }
345 }
346
347 bool
348 staff_priority_less (Grob * const &g1, Grob * const &g2)
349 {
350   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
351   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
352
353   if (priority_1 < priority_2)
354     return true;
355   else if (priority_1 > priority_2)
356     return false;
357
358   /* if there is no preference in staff priority, choose the left-most one */
359   Grob *common = g1->common_refpoint (g2, X_AXIS);
360   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
361   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
362   return start_1 < start_2;
363 }
364
365 static void
366 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
367 {
368   /* if we are a parent, consider the children's boxes instead of mine */
369   if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
370     {
371       for (vsize i = 0; i < elements->size (); i++)
372         add_boxes (elements->grob (i), x_common, y_common, boxes);
373     }
374   else if (!scm_is_number (me->get_property ("outside-staff-priority")))
375     boxes->push_back (Box (me->extent (x_common, X_AXIS),
376                            me->extent (y_common, Y_AXIS)));
377 }
378
379 /* We want to avoid situations like this:
380            still more text
381       more text
382    text
383    -------------------
384    staff
385    -------------------
386
387    The point is that "still more text" should be positioned under
388    "more text".  In order to achieve this, we place the grobs in several
389    passes.  We keep track of the right-most horizontal position that has been
390    affected by the current pass so far (actually we keep track of 2
391    positions, one for above the staff, one for below).
392
393    In each pass, we loop through the unplaced grobs from left to right.
394    If the grob overlaps the right-most affected position, we place it
395    (and then update the right-most affected position to point to the right
396    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
397 */
398 static void
399 add_grobs_of_one_priority (Skyline_pair *const skylines,
400                            vector<Grob*> elements,
401                            Grob *x_common,
402                            Grob *y_common)
403 {
404   vector<Box> boxes;
405   Drul_array<Real> last_affected_position;
406
407   reverse (elements);
408   while (!elements.empty ())
409     {
410       last_affected_position[UP] = -infinity_f;
411       last_affected_position[DOWN] = -infinity_f;
412       /* do one pass */
413       for (vsize i = elements.size (); i--;)
414         {
415           Direction dir = get_grob_direction (elements[i]);
416           if (dir == CENTER)
417             {
418               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
419               dir = UP;
420             }
421
422           Box b (elements[i]->extent (x_common, X_AXIS),
423                  elements[i]->extent (y_common, Y_AXIS));
424           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
425           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
426
427           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
428             continue;
429
430           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
431             warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
432           else
433             {
434               boxes.clear ();
435               boxes.push_back (b);
436               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
437               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
438               Real dist = (*skylines)[dir].distance (other) + padding;
439
440               if (dist > 0)
441                 {
442                   b.translate (Offset (0, dir*dist));
443                   elements[i]->translate_axis (dir*dist, Y_AXIS);
444                 }
445               (*skylines)[dir].insert (b, 0, X_AXIS);
446               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
447               last_affected_position[dir] = b[X_AXIS][RIGHT];
448             }
449
450           /*
451             Ugh: quadratic. --hwn
452            */
453           elements.erase (elements.begin () + i);
454         }
455     }
456 }
457
458 Skyline_pair
459 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
460 {
461   vector_sort (elements, staff_priority_less);
462   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
463   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
464
465   assert (y_common == me);
466
467   vsize i = 0;
468   vector<Box> boxes;
469
470   for (i = 0; i < elements.size ()
471          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
472     add_boxes (elements[i], x_common, y_common, &boxes);
473
474   Skyline_pair skylines (boxes, 0, X_AXIS);
475   for (; i < elements.size (); i++)
476     {
477       SCM priority = elements[i]->get_property ("outside-staff-priority");
478       vector<Grob*> current_elts;
479       current_elts.push_back (elements[i]);
480       while (i + 1 < elements.size () 
481              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
482         current_elts.push_back (elements[++i]);
483
484       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
485     }
486   return skylines;
487 }
488
489 ADD_INTERFACE (Axis_group_interface,
490
491                "An object that groups other layout objects.",
492
493                /* properties */
494                "X-common "
495                "Y-common "
496                "axes "
497                "elements "
498                "pure-Y-common "
499                "pure-relevant-elements "
500                "skylines "
501                "cached-pure-extents "
502                );