]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
codingstyle nits: space before ( and after ,
[lilypond.git] / lily / line-spanner.cc
1 /*
2   line-spanner.cc -- implement Line_spanner
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2000--2007 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include "spanner.hh"
10 #include "output-def.hh"
11 #include "item.hh"
12 #include "staff-symbol-referencer.hh"
13 #include "font-interface.hh"
14 #include "warn.hh"
15 #include "align-interface.hh"
16 #include "line-interface.hh"
17 #include "moment.hh"
18
19 #include "lily-proto.hh"
20 #include "grob-interface.hh"
21 #include "text-interface.hh"
22
23 class Line_spanner
24 {
25 public:
26   DECLARE_SCHEME_CALLBACK (print, (SCM));
27   DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
28   DECLARE_SCHEME_CALLBACK (calc_left_bound_info, (SCM));
29   DECLARE_SCHEME_CALLBACK (calc_left_bound_info_and_text, (SCM));
30   DECLARE_SCHEME_CALLBACK (calc_right_bound_info, (SCM));
31   DECLARE_SCHEME_CALLBACK (calc_bound_info, (SCM, Direction));
32   DECLARE_GROB_INTERFACE ();
33 };
34
35
36 static Grob *
37 line_spanner_common_parent (Grob *me)
38 {
39   Grob *common = find_fixed_alignment_parent (me);
40   if (!common)
41     {
42       common = Staff_symbol_referencer::get_staff_symbol (me);
43       if (common)
44         common = common->get_parent (Y_AXIS);
45       else
46         common = me->get_parent (Y_AXIS);
47     }
48
49   return common;
50 }
51
52 SCM
53 Line_spanner::calc_bound_info (SCM smob, Direction dir)
54 {
55   Spanner *me = unsmob_spanner (smob);
56
57   Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
58   commonx = me->common_refpoint (commonx, X_AXIS);
59
60   SCM bound_details = me->get_property ("bound-details");
61
62   SCM details = SCM_BOOL_F;
63   if (details == SCM_BOOL_F)
64     details = ly_assoc_get ((dir == LEFT)
65                             ? ly_symbol2scm ("left")
66                             : ly_symbol2scm ("right"), bound_details, SCM_BOOL_F);
67
68   if (me->get_bound (dir)->break_status_dir ())
69     {
70       SCM extra = ly_assoc_get ((dir == LEFT)
71                                 ? ly_symbol2scm ("left-broken")
72                                 : ly_symbol2scm ("right-broken"), bound_details, SCM_EOL);
73
74       for (SCM s = extra; scm_is_pair (s); s = scm_cdr (s))
75         details = scm_cons (scm_car (s), details);
76     }
77   
78   if (details == SCM_BOOL_F)
79     details = ly_assoc_get (ly_symbol2scm ("default"), bound_details, SCM_EOL);
80
81   SCM text = ly_assoc_get (ly_symbol2scm ("text"), details, SCM_BOOL_F);
82   if (Text_interface::is_markup (text))
83     {
84       Output_def *layout = me->layout ();
85       SCM properties = Font_interface::text_font_alist_chain (me);
86       details = scm_acons (ly_symbol2scm ("stencil"),
87                            Text_interface::interpret_markup (layout->self_scm (),
88                                                              properties, text),
89                            details);
90     }
91   
92   if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("X"), details, SCM_BOOL_F)))
93     {
94       Direction attach = (Direction)
95         robust_scm2int (ly_assoc_get (ly_symbol2scm ("attach-dir"),
96                                                      details, SCM_BOOL_F),
97                         CENTER);
98
99       details = scm_acons (ly_symbol2scm ("X"),
100                            scm_from_double (me->get_bound (dir)->extent (commonx, X_AXIS)
101                                             .linear_combination (attach)),
102                            details);
103     }
104   
105
106   if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("Y"), details, SCM_BOOL_F)))
107     {
108       Real y = 0.0;
109
110       Real extra_dy = robust_scm2double (me->get_property ("extra-dy"),
111                                          0.0);
112          
113       if (me->get_bound (dir)->break_status_dir ())
114         {
115           /*
116             This is hairy. For the normal case, we simply find common
117             parents, and draw a line between the bounds. When two note
118             heads are on different systems, there is no common parent
119             anymore. We have to find the piano-staff object.
120           */
121
122           Spanner *next_sp = me->broken_neighbor (dir);
123           Item *next_bound = next_sp->get_bound (dir);
124
125           if (next_bound->break_status_dir ())
126             {
127               programming_error ("no note heads for the line spanner on neighbor line?"
128                                  " Confused.");
129               me->suicide ();
130               return SCM_EOL;
131             }
132
133           Grob *next_common_y = line_spanner_common_parent (next_bound);
134           Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
135
136           y = next_ext.center ();
137         }
138       else
139         {
140           Grob *commony = me->common_refpoint (me->get_bound (dir), Y_AXIS);
141           y = me->get_bound (dir)->extent (commony, Y_AXIS).center ();
142           details = scm_acons (ly_symbol2scm ("common-Y"), commony->self_scm (), details);
143         }
144
145       y += dir * extra_dy / 2; 
146       details = scm_acons (ly_symbol2scm ("Y"), scm_from_double (y), details);
147     }
148
149   return details;
150 }
151
152 MAKE_SCHEME_CALLBACK (Line_spanner, calc_right_bound_info, 1);
153 SCM
154 Line_spanner::calc_right_bound_info (SCM smob)
155 {
156   return Line_spanner::calc_bound_info (smob, RIGHT);
157 }
158
159 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info, 1);
160 SCM
161 Line_spanner::calc_left_bound_info (SCM smob)
162 {
163   return Line_spanner::calc_bound_info (smob, LEFT);
164 }
165
166 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info_and_text, 1);
167 SCM
168 Line_spanner::calc_left_bound_info_and_text (SCM smob)
169 {
170   SCM alist = Line_spanner::calc_bound_info (smob, LEFT);
171   Spanner *me = unsmob_spanner (smob);
172
173   SCM text = me->get_property ("text");
174   if (Text_interface::is_markup (text)
175       && me->get_bound (LEFT)->break_status_dir () == CENTER
176       && ly_assoc_get (ly_symbol2scm ("stencil"), alist, SCM_BOOL_F) == SCM_BOOL_F)
177     {
178       Output_def *layout = me->layout ();
179       SCM properties = Font_interface::text_font_alist_chain (me);
180       alist = scm_acons (ly_symbol2scm ("stencil"),
181                          Text_interface::interpret_markup (layout->self_scm (),
182                                                            properties, text),
183                          alist);
184     }
185   
186   return alist;
187 }
188
189 MAKE_SCHEME_CALLBACK (Line_spanner, print, 1);
190 SCM
191 Line_spanner::print (SCM smob)
192 {
193   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
194
195   Interval_t<Moment> moments = me->spanned_time ();
196   /*
197     We remove the line at the start of the line.  For piano voice
198     indicators, it makes no sense to have them at the start of the
199     line.
200
201     I'm not sure what the official rules for glissandi are, but
202     usually the 2nd note of the glissando is "exact", so when playing
203     from the start of the line, there is no need to glide.
204
205     From a typographical p.o.v. this makes sense, since the amount of
206     space left of a note at the start of a line is very small.
207
208     --hwn.
209
210   */
211   if (moments.length () == Moment (0,0))
212     return SCM_EOL;
213   
214   Drul_array<SCM> bounds (me->get_property ("left-bound-info"),
215                           me->get_property ("right-bound-info"));
216
217   
218   Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
219   commonx = me->common_refpoint (commonx, X_AXIS);
220
221   Drul_array<Offset> span_points;
222
223   Direction d =  LEFT;
224   do
225     {
226       Offset z (robust_scm2double (ly_assoc_get (ly_symbol2scm ("X"),
227                                                  bounds[d], SCM_BOOL_F), 0.0)
228                 + commonx->relative_coordinate (commonx, X_AXIS),
229                 robust_scm2double (ly_assoc_get (ly_symbol2scm ("Y"),
230                                                  bounds[d], SCM_BOOL_F), 0.0));
231       
232       span_points[d] = z;
233     }
234   while (flip (&d) != LEFT);
235
236   Drul_array<Real> gaps (0, 0);
237   Drul_array<bool> arrows (0, 0);
238   Drul_array<Stencil*> stencils (0,0);
239   Drul_array<Grob*> common_y (0, 0);
240   do
241     {
242       gaps[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("padding"),
243                                                  bounds[d], SCM_BOOL_F), 0.0);
244       arrows[d] = to_boolean (ly_assoc_get (ly_symbol2scm ("arrow"),
245                                             bounds[d], SCM_BOOL_F));
246       stencils[d] = unsmob_stencil (ly_assoc_get (ly_symbol2scm ("stencil"),
247                                                   bounds[d], SCM_BOOL_F));
248       common_y[d] = unsmob_grob (ly_assoc_get (ly_symbol2scm ("common-Y"),
249                                                bounds[d], SCM_BOOL_F));
250       if (!common_y[d])
251         common_y[d] = me; 
252     }
253   while (flip (&d) != LEFT);
254
255   Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
256   do
257     span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
258   while (flip (&d) != LEFT);
259
260   Offset dz = (span_points[RIGHT] - span_points[LEFT]);
261   Offset dz_dir = dz.direction ();
262   if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
263     {
264       return SCM_EOL;
265     }
266
267   Stencil line;
268   do
269     {
270       if (stencils[d])
271         {
272          Stencil s = stencils[d]->translated (span_points[d]);
273          SCM align = ly_assoc_get (ly_symbol2scm ("stencil-align-dir-y"),
274                                    bounds[d], SCM_BOOL_F);
275          SCM off = ly_assoc_get (ly_symbol2scm ("stencil-offset"),
276                                    bounds[d], SCM_BOOL_F);
277
278          if (scm_is_number (align)) 
279            s.align_to (Y_AXIS, scm_to_double (align));
280
281          /*
282            todo: should use font-size.
283           */
284          if (is_number_pair (off))
285            s.translate (ly_scm2offset (off));
286          
287          line.add_stencil (s);
288         }
289     }
290   while (flip (&d) != LEFT);
291
292   do
293     {
294       if (stencils[d])
295         span_points[d] += dz_dir *
296           (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
297       
298       span_points[d] += -d * gaps[d] *  dz.direction ();
299     }
300   while (flip (&d) != LEFT);
301
302   line.add_stencil (Line_interface::line (me, 
303                                           span_points[LEFT],
304                                           span_points[RIGHT]));
305
306   line.add_stencil (Line_interface::arrows (me,
307                                             span_points[LEFT],
308                                             span_points[RIGHT],
309                                             arrows[LEFT],
310                                             arrows[RIGHT]));
311
312   line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
313                           -me->relative_coordinate (my_common_y, Y_AXIS)));
314                           
315     
316   return line.smobbed_copy ();
317 }
318
319 ADD_INTERFACE (Line_spanner,
320                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
321                "The property @code{style} can be @code{line}, "
322                "@code{dashed-line}, @code{trill}, \n"
323                "@code{dotted-line} or @code{zigzag}.\n"
324                "\n",
325
326                "extra-dy "
327                "gap "
328                "thickness "
329                "bound-details " 
330                "left-bound-info " 
331                "right-bound-info " 
332                );
333