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