]> git.donarmstrong.com Git - lilypond.git/blob - lily/dot-column.cc
Merge branch 'master' into translation
[lilypond.git] / lily / dot-column.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "dot-column.hh"
21
22 #include <cstdio>
23 #include <cmath>
24 #include <map>
25 #include <set>
26
27 using namespace std;
28
29 #include "axis-group-interface.hh"
30 #include "directional-element-interface.hh"
31 #include "dot-column.hh"
32 #include "dot-configuration.hh"
33 #include "dot-formatting-problem.hh"
34 #include "dots.hh"
35 #include "grob.hh"
36 #include "note-head.hh"
37 #include "pointer-group-interface.hh"
38 #include "rest.hh"
39 #include "rhythmic-head.hh"
40 #include "side-position-interface.hh"
41 #include "staff-symbol-referencer.hh"
42 #include "stem.hh"
43
44 MAKE_SCHEME_CALLBACK (Dot_column, calc_positioning_done, 1);
45 SCM
46 Dot_column::calc_positioning_done (SCM smob)
47 {
48   Grob *me = unsmob_grob (smob);
49
50   /*
51     Trigger note collision resolution first, since that may kill off
52     dots when merging.
53   */
54   if (Grob *collision = unsmob_grob (me->get_object ("note-collision")))
55     (void) collision->get_property ("positioning-done");
56
57   me->set_property ("positioning-done", SCM_BOOL_T);
58
59   vector<Grob *> dots
60     = extract_grob_array (me, "dots");
61
62   vector<Grob *> main_heads;
63   vector<Interval> allowed_y_positions;
64   Real ss = 0;
65
66   Grob *commonx = me;
67   for (vsize i = 0; i < dots.size (); i++)
68     {
69       Grob *n = dots[i]->get_parent (Y_AXIS);
70       commonx = n->common_refpoint (commonx, X_AXIS);
71
72       if (Grob *stem = unsmob_grob (n->get_object ("stem")))
73         {
74           commonx = stem->common_refpoint (commonx, X_AXIS);
75
76           if (Stem::first_head (stem) == n)
77             {
78               main_heads.push_back (n);
79
80               // Get vertical interval of the chord's notehead positions.
81               // We widen this interval since dots always sit between staff
82               // lines.  Be careful to make it work also for unusual
83               // overrides of `NoteHead.Y-offset'.
84               //
85               // Possible solutions to improve this code (namely, to handle
86               // the `staff-position' property also) -- in case there is
87               // ever the desire or necessity to do so -- can be found in
88               // the Rietveld comments at
89               //
90               //   https://codereview.appspot.com/7319049
91
92               Interval hp = Stem::head_positions (stem);
93
94               int top = int (ceil (hp[UP]));
95               if (Staff_symbol_referencer::on_line (stem, top))
96                 top += 1;
97               hp[UP] = top;
98
99               int bottom = int (floor (hp[DOWN]));
100               if (Staff_symbol_referencer::on_line (stem, bottom))
101                 bottom -= 1;
102               hp[DOWN] = bottom;
103
104               allowed_y_positions.push_back (hp);
105             }
106         }
107     }
108
109   vector<Box> boxes;
110   set<Grob *> stems;
111
112   extract_grob_set (me, "side-support-elements", support);
113
114   Interval base_x;
115   for (vsize i = 0; i < main_heads.size (); i++)
116     base_x.unite (main_heads[i]->extent (commonx, X_AXIS));
117
118   for (vsize i = 0; i < support.size (); i++)
119     {
120       Grob *s = support[i];
121       if (!ss)
122         ss = Staff_symbol_referencer::staff_space (s);
123
124       /* can't inspect Y extent of rest.
125
126          Rest collisions should wait after line breaking.
127       */
128       Interval y;
129       if (Rest::has_interface (s))
130         {
131           base_x.unite (s->extent (commonx, X_AXIS));
132           continue;
133         }
134       else if (Stem::has_interface (s))
135         {
136           Real y1 = Stem::head_positions (s)[-get_grob_direction (s)];
137           Real y2 = y1 + get_grob_direction (s) * 7;
138
139           y.add_point (y1);
140           y.add_point (y2);
141
142           stems.insert (s);
143         }
144       else if (Note_head::has_interface (s))
145         y = Interval (-1.1, 1.1);
146       else
147         {
148           programming_error ("unknown grob in dot col support");
149           continue;
150         }
151
152       y += Staff_symbol_referencer::get_position (s);
153
154       Box b (s->extent (commonx, X_AXIS), y);
155       boxes.push_back (b);
156
157       if (Grob *stem = unsmob_grob (s->get_object ("stem")))
158         stems.insert (stem);
159     }
160
161   for (set<Grob *>::const_iterator i (stems.begin ());
162        i != stems.end (); i++)
163     {
164       Grob *stem = (*i);
165       Grob *flag = Stem::flag (stem);
166       if (flag)
167         {
168           Grob *commony = stem->common_refpoint (flag, Y_AXIS);
169           Interval y = flag->extent (commony, Y_AXIS) * (2 / ss);
170           Interval x = flag->extent (commonx, X_AXIS);
171
172           boxes.push_back (Box (x, y));
173         }
174     }
175
176   /*
177     The use of pure_position_less and pure_get_rounded_position below
178     are due to the fact that this callback is called before line breaking
179     occurs.  Because dots' actual Y posiitons may be linked to that of
180     beams (dots are attached to rests, which are shifted to avoid beams),
181     we instead must use their pure Y positions.
182   */
183   vector_sort (dots, pure_position_less);
184   for (vsize i = dots.size (); i--;)
185     {
186       if (!dots[i]->is_live ())
187         dots.erase (dots.begin () + i);
188       else
189         // Undo any fake translations that were done in add_head.
190         dots[i]->translate_axis (-dots[i]->relative_coordinate (me, X_AXIS), X_AXIS);
191     }
192
193   Dot_formatting_problem problem (boxes, base_x);
194
195   Dot_configuration cfg (problem);
196   for (vsize i = 0; i < dots.size (); i++)
197     {
198       Dot_position dp;
199       dp.dot_ = dots[i];
200
201       Grob *note = dots[i]->get_parent (Y_AXIS);
202       if (note)
203         {
204           if (Note_head::has_interface (note))
205             dp.dir_ = to_dir (dp.dot_->get_property ("direction"));
206
207           dp.x_extent_ = note->extent (commonx, X_AXIS);
208         }
209
210       int p = Staff_symbol_referencer::pure_get_rounded_position (dp.dot_);
211
212       /* icky, since this should go via a Staff_symbol_referencer
213          offset callback but adding a dot overwrites Y-offset. */
214       p += (int) robust_scm2double (dp.dot_->get_property ("staff-position"), 0.0);
215       dp.pos_ = p;
216
217       cfg.remove_collision (p);
218       cfg[p] = dp;
219       if (Staff_symbol_referencer::on_line (dp.dot_, p) &&
220           dp.dot_->get_property ("style") != ly_symbol2scm ("kievan"))
221         cfg.remove_collision (p);
222     }
223
224   problem.register_configuration (cfg);
225
226   // If in a chord, remove dots which have vertical positions above or below
227   // the topmost or bottommost note, respectively ([Gould], p. 56).
228   // Note that a dot configuration can contain more than a single chord or
229   // rest (the latter gets ignored).
230   //
231   // The dot positioning algorithm vertically shifts dots; it thus can
232   // happen that, say, a dot of the upper voice's chord is positioned
233   // beneath a note head of the lower voice's chord, while the dots of the
234   // lower voice's chord are shifted down even more.  We thus check all
235   // vertical ranges for valid positions and not only the range of the dot's
236   // parent chord.
237   //
238   // Do nothing if there is either no staff line, or no note head, or the
239   // `chord-dots' property not set.
240   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
241   vsize num_positions = allowed_y_positions.size ();
242   bool chord_dots = to_boolean (me->get_property ("chord-dots"));
243
244   if (st && num_positions && chord_dots)
245     {
246       for (Dot_configuration::const_iterator i (cfg.begin ());
247            i != cfg.end (); i++)
248         {
249           vsize j;
250
251           for (j = 0; j < num_positions; j++)
252             if (allowed_y_positions[j].contains (i->first))
253               break;
254
255           if (j == num_positions)
256             {
257               Grob *dot = i->second.dot_;
258               Grob *n = dot->get_parent (Y_AXIS);
259               if (n && Note_head::has_interface (n))
260                 dot->suicide ();
261             }
262         }
263     }
264
265   for (Dot_configuration::const_iterator i (cfg.begin ());
266        i != cfg.end (); i++)
267     {
268       /*
269         Junkme?
270        */
271       Staff_symbol_referencer::pure_set_position (i->second.dot_, i->first);
272     }
273
274   me->translate_axis (cfg.x_offset () - me->relative_coordinate (commonx, X_AXIS),
275                       X_AXIS);
276   return SCM_BOOL_T;
277 }
278
279 void
280 Dot_column::add_head (Grob *me, Grob *head)
281 {
282   Grob *d = unsmob_grob (head->get_object ("dot"));
283   if (d)
284     {
285       Side_position_interface::add_support (me, head);
286
287       Pointer_group_interface::add_grob (me, ly_symbol2scm ("dots"), d);
288       d->set_property ("Y-offset", Grob::x_parent_positioning_proc);
289       // Dot formatting requests the Y-offset, which for rests may
290       // trigger post-linebreak callbacks.  On the other hand, we need the
291       // correct X-offset of the dots for horizontal collision avoidance.
292       // The translation here is undone in calc_positioning_done, where we
293       // do the X-offset properly.
294       if (Rest::has_interface (head))
295         d->translate_axis (head->extent (head, X_AXIS).length (), X_AXIS);
296       else
297         d->set_property ("X-offset", Grob::x_parent_positioning_proc);
298       Axis_group_interface::add_element (me, d);
299     }
300 }
301
302 ADD_INTERFACE (Dot_column,
303                "Group dot objects so they form a column, and position"
304                " dots so they do not clash with staff lines.",
305
306                /* properties */
307                "chord-dots "
308                "direction "
309                "dots "
310                "note-collision "
311                "positioning-done "
312               );
313