]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Add .en.html symlink hack.
[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 SCM
184 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
185 {
186   extract_grob_set (me, "elements", elts);
187   if (a == Y_AXIS && to_boolean (me->get_property ("skyline-spacing")))
188     skyline_spacing (me, elts);
189   Grob *common = common_refpoint_of_array (elts, me, a);
190
191   Real my_coord = me->relative_coordinate (common, a);
192   Interval r (relative_group_extent (elts, common, a));
193
194   return ly_interval2scm (r - my_coord);
195 }
196
197 SCM
198 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
199 {
200   Grob *common = unsmob_grob (me->get_object ("common-refpoint-of-elements"));
201
202   if (!common)
203     {
204       extract_grob_set (me, "elements", elts);
205
206       vector<Grob*> relevant_elts;
207       SCM is_relevant = ly_lily_module_constant ("pure-relevant");
208
209       for (vsize i = 0; i < elts.size (); i++)
210         {
211           if (to_boolean (scm_apply_1 (is_relevant, elts[i]->self_scm (), SCM_EOL)))
212             relevant_elts.push_back (elts[i]);
213
214           Item *it = dynamic_cast<Item*> (elts[i]);
215           Direction d = LEFT;
216           if (it)
217             do
218               {
219                 Item *piece = it->find_prebroken_piece (d);
220                 if (piece && to_boolean (scm_apply_1 (is_relevant, piece->self_scm (), SCM_EOL)))
221                   relevant_elts.push_back (piece);
222               }
223             while (flip (&d) != LEFT);
224         }
225
226       common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
227       me->set_object ("common-refpoint-of-elements", common->self_scm ());
228
229       SCM ga_scm = Grob_array::make_array ();
230       Grob_array *ga = unsmob_grob_array (ga_scm);
231       ga->set_array (relevant_elts);
232       me->set_object ("pure-relevant-elements", ga_scm);
233     }
234
235   extract_grob_set (me, "pure-relevant-elements", elts);
236   Real my_coord = me->relative_coordinate (common, Y_AXIS);
237   Interval r (relative_pure_height (me, elts, common, start, end, true));
238
239   return ly_interval2scm (r - my_coord);
240 }
241
242 void
243 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
244 {
245   found->push_back (me);
246
247   if (!has_interface (me))
248     return;
249
250   extract_grob_set (me, "elements", elements);
251   for (vsize i = 0; i < elements.size (); i++)
252     {
253       Grob *e = elements[i];
254       Axis_group_interface::get_children (e, found);
255     }
256 }
257
258 bool
259 staff_priority_less (Grob * const &g1, Grob * const &g2)
260 {
261   int priority_1 = robust_scm2int (g1->get_property ("outside-staff-priority"), INT_MIN);
262   int priority_2 = robust_scm2int (g2->get_property ("outside-staff-priority"), INT_MIN);
263
264   if (priority_1 < priority_2)
265     return true;
266   else if (priority_1 > priority_2)
267     return false;
268
269   /* if there is no preference in staff priority, choose the left-most one */
270   Grob *common = g1->common_refpoint (g2, X_AXIS);
271   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
272   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
273   return start_1 < start_2;
274 }
275
276 static void
277 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
278 {
279   /* if we are a parent, consider the children's boxes instead of mine */
280   if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
281     {
282       for (vsize i = 0; i < elements->size (); i++)
283         add_boxes (elements->grob (i), x_common, y_common, boxes);
284     }
285   else if (!scm_is_number (me->get_property ("outside-staff-priority")))
286     boxes->push_back (Box (me->extent (x_common, X_AXIS),
287                            me->extent (y_common, Y_AXIS)));
288 }
289
290 /* We want to avoid situations like this:
291            still more text
292       more text
293    text
294    -------------------
295    staff
296    -------------------
297
298    The point is that "still more text" should be positioned under
299    "more text".  In order to achieve this, we place the grobs in several
300    passes.  We keep track of the right-most horizontal position that has been
301    affected by the current pass so far (actually we keep track of 2
302    positions, one for above the staff, one for below).
303
304    In each pass, we loop through the unplaced grobs from left to right.
305    If the grob overlaps the right-most affected position, we place it
306    (and then update the right-most affected position to point to the right
307    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
308 */
309 static void
310 add_grobs_of_one_priority (Drul_array<Skyline> *const skylines,
311                            vector<Grob*> elements,
312                            Grob *x_common,
313                            Grob *y_common)
314 {
315   vector<Box> boxes;
316   Drul_array<Real> last_affected_position;
317
318   reverse (elements);
319   while (!elements.empty ())
320     {
321       last_affected_position[UP] = -infinity_f;
322       last_affected_position[DOWN] = -infinity_f;
323       /* do one pass */
324       for (vsize i = elements.size (); i--;)
325         {
326           Direction dir = get_grob_direction (elements[i]);
327           if (dir == CENTER)
328             {
329               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
330               dir = UP;
331             }
332
333           Box b (elements[i]->extent (x_common, X_AXIS),
334                  elements[i]->extent (y_common, Y_AXIS));
335           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
336           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
337
338           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
339             continue;
340
341           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
342             warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
343           else
344             {
345               boxes.clear ();
346               boxes.push_back (b);
347               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
348               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
349               Real dist = (*skylines)[dir].distance (other) + padding;
350
351               if (dist > 0)
352                 {
353                   b.translate (Offset (0, dir*dist));
354                   elements[i]->translate_axis (dir*dist, Y_AXIS);
355                 }
356               (*skylines)[dir].insert (b, 0, X_AXIS);
357               elements[i]->del_property ("outside-staff-padding");
358               last_affected_position[dir] = b[X_AXIS][RIGHT];
359             }
360           elements.erase (elements.begin () + i);
361         }
362     }
363 }
364
365 void
366 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
367 {
368   vector_sort (elements, staff_priority_less);
369   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
370   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
371
372   vsize i = 0;
373   vector<Box> boxes;
374
375   for (i = 0; i < elements.size ()
376          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
377     add_boxes (elements[i], x_common, y_common, &boxes);
378
379   Drul_array<Skyline> skylines (Skyline (boxes, 0, X_AXIS, DOWN),
380                                 Skyline (boxes, 0, X_AXIS, UP));
381   for (; i < elements.size (); i++)
382     {
383       SCM priority = elements[i]->get_property ("outside-staff-priority");
384       vector<Grob*> current_elts;
385       current_elts.push_back (elements[i]);
386       while (i < elements.size () - 1
387              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
388         current_elts.push_back (elements[++i]);
389
390       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
391     }
392 }
393
394 ADD_INTERFACE (Axis_group_interface,
395
396                "An object that groups other layout objects.",
397
398                /* properties */
399                "axes "
400                "elements "
401                "common-refpoint-of-elements "
402                "pure-relevant-elements "
403                "skyline-spacing "
404                "cached-pure-extents "
405                );