]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Merge commit '2a2f4f5'
[lilypond.git] / lily / tuplet-bracket.cc
1 /*
2   tuplet-bracket.cc -- implement Tuplet_bracket
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2007 Jan Nieuwenhuizen <janneke@gnu.org>
7   Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
9
10 /*
11   TODO:
12
13   - tuplet bracket should probably be subject to the same rules as
14   beam sloping/quanting.
15
16   - There is no support for kneed brackets, or nested brackets.
17
18   - number placement for parallel beams should be much more advanced:
19   for sloped beams some extra horizontal offset must be introduced.
20
21   - number placement is usually done over the center note, not the
22   graphical center.
23 */
24
25 /*
26   TODO: quantise, we don't want to collide with staff lines.
27   (or should we be above staff?)
28
29   todo: handle breaking elegantly.
30 */
31
32
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
35 #include "beam.hh"
36 #include "warn.hh"
37 #include "output-def.hh"
38 #include "font-interface.hh"
39 #include "text-interface.hh"
40 #include "stem.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
44 #include "spanner.hh"
45 #include "staff-symbol-referencer.hh"
46 #include "lookup.hh"
47 #include "paper-column.hh"
48 #include "moment.hh"
49
50 static Item *
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
52 {
53   Spanner *me = dynamic_cast<Spanner *> (me_grob);
54   Item *g = me->get_bound (hdir);
55   if (Note_column::has_interface (g)
56       && Note_column::get_stem (g)
57       && Note_column::dir (g) == my_dir)
58     g = Note_column::get_stem (g);
59
60   return g;
61 }
62
63
64 void
65 flatten_number_pair_property (Grob *me,
66                               Direction xdir,  SCM sym)
67 {
68   Drul_array<Real> zero (0, 0);
69   Drul_array<Real> pair
70     = robust_scm2drul (me->internal_get_property (sym), zero);
71   pair[xdir] = 0.0;
72   
73   me->set_property (sym, ly_interval2scm (pair));
74 }
75
76
77 /*
78   Return beam that encompasses the span of the tuplet bracket.
79 */
80
81 Grob *
82 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
83                                bool *equally_long)
84 {
85   Spanner *me = dynamic_cast<Spanner *> (me_grob);
86
87   if (me->get_bound (LEFT)->break_status_dir ()
88       || me->get_bound (RIGHT)->break_status_dir ())
89     return 0;
90
91   Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
92                            Note_column::get_stem (cols.back ()));
93
94   if (!stems[RIGHT]
95       || !stems[LEFT]
96       || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
97           != me->get_bound (RIGHT)->get_column ()))
98     return 0;
99
100   Drul_array<Grob*> beams;
101   Direction d = LEFT;
102   do {
103     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
104   } while (flip (&d) != LEFT);
105   
106   *equally_long = false;
107   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
108     return 0;
109
110   extract_grob_set (beams[LEFT], "stems", beam_stems);
111   if (beam_stems.size () == 0)
112     {
113       programming_error ("beam under tuplet bracket has no stems");
114       *equally_long = 0;
115       return 0;
116     }
117
118   *equally_long =
119     (beam_stems[0] == stems[LEFT]
120      && beam_stems.back () == stems[RIGHT]);
121   return beams[LEFT];
122 }
123
124
125 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
126 SCM
127 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
128 {
129   Spanner *me = unsmob_spanner (smob);
130
131   Direction dir = get_grob_direction (me); 
132   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
133                              get_x_bound_item (me, RIGHT, dir));
134   
135   Drul_array<bool> connect_to_other (false, false);
136   Direction d = LEFT;
137   do
138     {
139       Direction break_dir = bounds[d]->break_status_dir ();
140       Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
141       vsize neighbor_idx = me->get_break_index () - break_dir;
142       if (break_dir
143           && d == RIGHT
144           && neighbor_idx < orig_spanner->broken_intos_.size ())
145         {
146           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
147
148           /* trigger possible suicide*/
149           (void) neighbor->get_property ("positions");
150         }
151
152       connect_to_other[d]
153         = (break_dir
154            && neighbor_idx < orig_spanner->broken_intos_.size ()
155            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
156     }
157   while (flip (&d) != LEFT);
158
159
160   if (connect_to_other[LEFT] || connect_to_other[RIGHT])
161     return scm_cons (scm_from_bool (connect_to_other[LEFT]),
162                      scm_from_bool (connect_to_other[RIGHT]));
163                      
164   return SCM_EOL;
165 }
166
167 Grob* 
168 Tuplet_bracket::get_common_x (Spanner *me)
169 {
170   extract_grob_set (me, "note-columns", columns);
171
172   Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
173   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
174   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
175
176   return commonx;
177 }
178   
179 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
180 SCM
181 Tuplet_bracket::calc_control_points (SCM smob)
182 {
183   Spanner *me = unsmob_spanner (smob);
184
185   extract_grob_set (me, "note-columns", columns);
186
187   SCM scm_positions = me->get_property ("positions");
188   if (!me->is_live ())
189     return SCM_EOL;
190   
191   if (!scm_is_pair (scm_positions))
192     programming_error ("Positions should be number pair");
193     
194   Drul_array<Real> positions
195     = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
196
197   Grob *commonx = get_common_x (me);
198   Direction dir = get_grob_direction (me);
199
200   Drul_array<Item *> bounds;
201   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
202   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
203
204   Drul_array<bool> connect_to_other =
205     robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
206                          Drul_array<bool> (false, false));
207   
208     
209   Interval x_span;
210   Direction d = LEFT;
211   do
212     {
213       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
214
215       if (connect_to_other[d])
216         {
217           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
218                                                Interval (-0.5, 0.0)));
219
220           if (d == RIGHT)
221             x_span[d] += d * overshoot[d];
222           else
223             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
224               - overshoot[LEFT];
225         }
226       
227       else if (d == RIGHT
228                && (columns.empty ()
229                    || (bounds[d]->get_column ()
230                        != dynamic_cast<Item *> (columns.back ())->get_column ())))
231         {
232           /*
233             We're connecting to a column, for the last bit of a broken
234             fullLength bracket.
235             
236             TODO: make padding tunable?
237           */
238           Real padding = 1.0;
239
240           if (bounds[d]->break_status_dir ())
241             padding = 0.0;
242           
243           x_span[d]
244             = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
245             - padding;
246         }
247     }
248   while (flip (&d) != LEFT);
249
250   
251   
252   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
253   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
254                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
255 }
256
257 /*
258   TODO:
259
260   in the case that there is no bracket, but there is a (single) beam,
261   follow beam precisely for determining tuplet number location.
262 */
263 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
264 SCM
265 Tuplet_bracket::print (SCM smob)
266 {
267   Spanner *me = unsmob_spanner (smob);
268   Stencil mol;
269
270   extract_grob_set (me, "note-columns", columns);
271   bool equally_long = false;
272   Grob *par_beam = parallel_beam (me, columns, &equally_long);
273   
274   bool bracket_visibility = !(par_beam && equally_long);
275   /*
276     Fixme: the type of this prop is sucky.
277   */
278   SCM bracket = me->get_property ("bracket-visibility");
279   if (scm_is_bool (bracket))
280     bracket_visibility = ly_scm2bool (bracket);
281   else if (bracket == ly_symbol2scm ("if-no-beam"))
282     bracket_visibility = !par_beam;
283   
284   SCM cpoints =  me->get_property ("control-points");
285   if (scm_ilength (cpoints) < 2)
286     {
287       me->suicide ();
288       return SCM_EOL;
289     }
290   
291   Drul_array<Offset> points;
292   points[LEFT] = ly_scm2offset (scm_car (cpoints));
293   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
294   
295   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
296   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
297
298   Output_def *pap = me->layout ();
299
300   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
301   
302   /*
303     No bracket when it would be smaller than the number.
304   */
305   Real gap = 0.;
306   if (bracket_visibility && number_grob)
307     {
308       Interval ext = number_grob->extent (number_grob, X_AXIS);
309       if (!ext.is_empty ())
310         {
311           gap = ext.length () + 1.0;
312       
313           if (0.75 * x_span.length () < gap)
314             bracket_visibility = false;
315         }
316     }
317
318   if (bracket_visibility)
319     {
320       Drul_array<Real> zero (0, 0);
321       Real ss = Staff_symbol_referencer::staff_space (me);
322       Drul_array<Real> height
323         = robust_scm2drul (me->get_property ("edge-height"), zero);
324       Drul_array<Real> flare
325         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
326       Drul_array<Real> shorten
327         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
328       Drul_array<Stencil> edge_stencils;
329
330       Direction dir = get_grob_direction (me);
331       
332       scale_drul (&height, -ss * dir);
333       scale_drul (&flare, ss);
334       scale_drul (&shorten, ss);
335
336       Drul_array<bool> connect_to_other =
337         robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
338                              Drul_array<bool> (false, false));
339
340       Direction d = LEFT;
341       do
342         {
343           if (connect_to_other[d])
344             {
345               height[d] = 0.0;
346               flare[d] = 0.0;
347               shorten[d] = 0.0;
348
349               SCM edge_text = me->get_property ("edge-text");
350
351               if (scm_is_pair (edge_text))
352                 {
353                   SCM properties = Font_interface::text_font_alist_chain (me);
354                   SCM text = index_get_cell (edge_text, d);
355                   if (Text_interface::is_markup (text))
356                     {
357                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
358                                                                 properties, text);
359
360                       Stencil *edge_text = unsmob_stencil (t);
361                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
362                       edge_stencils[d] = *edge_text;
363                     }
364                 }
365             }
366         }
367       while (flip (&d) != LEFT);
368
369       Stencil brack = make_bracket (me, Y_AXIS,
370                                     points[RIGHT] - points[LEFT],
371                                     height,
372                                     /*
373                                       0.1 = more space at right due to italics
374                                       TODO: use italic correction of font.
375                                     */
376                                     Interval (-0.5, 0.5) * gap + 0.1,
377                                     flare, shorten);
378
379       do
380         {
381           if (!edge_stencils[d].is_empty ())
382             brack.add_stencil (edge_stencils[d]);
383         }
384       while (flip (&d) != LEFT);
385
386       mol.add_stencil (brack);
387     }
388
389   mol.translate (points[LEFT]);
390   return mol.smobbed_copy ();
391 }
392
393 /*
394   should move to lookup?
395
396   TODO: this will fail for very short (shorter than the flare)
397   brackets.
398 */
399 Stencil
400 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
401                               Axis protusion_axis,
402                               Offset dz,
403                               Drul_array<Real> height,
404                               Interval gap,
405                               Drul_array<Real> flare,
406                               Drul_array<Real> shorten)
407 {
408   Drul_array<Offset> corners (Offset (0, 0), dz);
409
410   Real length = dz.length ();
411   Drul_array<Offset> gap_corners;
412
413   Axis bracket_axis = other_axis (protusion_axis);
414
415   Drul_array<Offset> straight_corners = corners;
416
417   Direction d = LEFT;
418   do
419     straight_corners[d] += -d * shorten[d] / length * dz;
420   while (flip (&d) != LEFT);
421
422   if (!gap.is_empty ())
423     {
424       do
425         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
426       while (flip (&d) != LEFT);
427     }
428
429   Drul_array<Offset> flare_corners = straight_corners;
430   do
431     {
432       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
433       flare_corners[d][protusion_axis] += height[d];
434       straight_corners[d][bracket_axis] += -d * flare[d];
435     }
436   while (flip (&d) != LEFT);
437
438   Stencil m;
439   do
440     {
441       if (!gap.is_empty ())
442         m.add_stencil (Line_interface::line (me, straight_corners[d],
443                                              gap_corners[d]));
444
445       m.add_stencil (Line_interface::line (me, straight_corners[d],
446                                            flare_corners[d]));
447     }
448
449   while (flip (&d) != LEFT);
450
451   if (gap.is_empty ())
452     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
453                                          straight_corners[RIGHT]));
454   
455   return m;
456 }
457
458 void
459 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
460 {
461   extract_grob_set (me, "note-columns", columns);
462   vsize l = 0;
463   while (l < columns.size () && Note_column::has_rests (columns[l]))
464     l++;
465
466   vsize r = columns.size ();
467   while (r > l && Note_column::has_rests (columns[r-1]))
468     r--;
469
470   *left = *right = 0;
471
472   if (l < r)
473     {
474       *left = columns[l];
475       *right = columns[r-1];
476     }
477 }
478
479 /*
480   use first -> last note for slope, and then correct for disturbing
481   notes in between.  */
482 void
483 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
484 {
485   Spanner *me = dynamic_cast<Spanner *> (me_grob);
486
487   extract_grob_set (me, "note-columns", columns);
488   extract_grob_set (me, "tuplets", tuplets);
489
490   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
491   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
492   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
493     commony = st->common_refpoint (commony, Y_AXIS);
494   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
495
496   Grob *commonx = get_common_x (me);
497   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
498
499   Interval staff;
500   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
501     {
502       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
503       if  (pad >= 0.0)
504         {
505           staff = st->extent (commony, Y_AXIS) - my_offset;
506           staff.widen (pad);
507         }
508     }
509   
510   Direction dir = get_grob_direction (me);
511
512   bool equally_long = false;
513   Grob *par_beam = parallel_beam (me, columns, &equally_long);
514   
515
516
517   Item *lgr = get_x_bound_item (me, LEFT, dir);
518   Item *rgr = get_x_bound_item (me, RIGHT, dir);
519   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
520   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
521   bool follow_beam = par_beam
522     && ((get_grob_direction (par_beam) == dir) || to_boolean (par_beam->get_property ("knee")));
523
524   vector<Offset> points;
525
526   if (columns.size ()
527       && follow_beam
528       && Note_column::get_stem (columns[0])
529       && Note_column::get_stem (columns.back ()))
530     {
531       /*
532         trigger set_stem_ends
533        */
534       (void) par_beam->get_property ("quantized-positions");
535
536
537       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
538                                 Note_column::get_stem (columns.back ()));
539
540       
541       
542
543       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
544       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
545         + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
546       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
547         + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
548
549       *dy = rp - lp;
550       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
551       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
552     }
553   else
554     {
555       /*
556         Use outer non-rest columns to determine slope
557       */
558       Grob *left_col = 0;
559       Grob *right_col = 0;
560       get_bounds (me, &left_col, &right_col);
561       if (left_col && right_col)
562         {
563           Interval rv = right_col->extent (commony, Y_AXIS);
564           Interval lv = left_col->extent (commony, Y_AXIS);
565           rv.unite (staff);
566           lv.unite (staff);
567           Real graphical_dy = rv[dir] - lv[dir];
568
569           Slice ls = Note_column::head_positions_interval (left_col);
570           Slice rs = Note_column::head_positions_interval (right_col);
571
572           Interval musical_dy;
573           musical_dy[UP] = rs[UP] - ls[UP];
574           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
575           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
576             *dy = 0.0;
577           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
578             *dy = 0.0;
579           else
580             *dy = graphical_dy;
581         }
582       else
583         *dy = 0;
584
585       for (vsize i = 0; i < columns.size (); i++)
586         {
587           Interval note_ext = columns[i]->extent (commony, Y_AXIS);
588           Real notey = note_ext[dir] - my_offset;
589
590           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
591           points.push_back (Offset (x, notey));
592         }
593     }
594
595   if (!follow_beam)
596     {
597       points.push_back (Offset (x0 - x0, staff[dir]));
598       points.push_back (Offset (x1 - x0, staff[dir]));
599     }
600   
601   /*
602     This is a slight hack. We compute two encompass points from the
603     bbox of the smaller tuplets.
604
605     We assume that the smaller bracket is 1.0 space high.
606   */
607   Real ss = Staff_symbol_referencer::staff_space (me);
608   for (vsize i = 0; i < tuplets.size (); i++)
609     {
610       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
611       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
612
613       if (!tuplets[i]->is_live ())
614         continue;
615       
616       Direction d = LEFT;
617       Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
618                                                         Interval (0,0));
619
620       
621       Real other_dy = positions[RIGHT] - positions[LEFT];
622
623       do
624         {
625           Real y
626             = tuplet_y.linear_combination (d * sign (other_dy));
627
628           /*
629             We don't take padding into account for nested tuplets.
630             the edges can come very close to the stems, likewise for
631             nested tuplets?
632           */
633
634           points.push_back (Offset (tuplet_x[d] - x0, y));
635         }
636       while (flip (&d) != LEFT);
637     }
638
639   *offset = -dir * infinity_f;
640   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
641   for (vsize i = 0; i < points.size (); i++)
642     {
643       Real x = points[i][X_AXIS];
644       Real tuplety = (*dy) * x * factor;
645
646       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
647         *offset = points[i][Y_AXIS] - tuplety;
648     }
649
650   *offset += scm_to_double (me->get_property ("padding")) * dir;
651
652   /*
653     horizontal brackets should not collide with staff lines.
654
655     Kind of pointless since we put them outside the staff anyway, but
656     let's leave code for the future when possibly allow them to move
657     into the staff once again.
658   
659     This doesn't seem to support cross-staff tuplets atm.
660   */
661   if (*dy == 0
662       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
663     {
664       // quantize, then do collision check.
665       *offset *= 2 / ss;
666
667       *offset = rint (*offset);
668       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
669         *offset += dir;
670
671       *offset *= 0.5 * ss;
672     }
673 }
674
675
676 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
677 SCM
678 Tuplet_bracket::calc_direction (SCM smob)
679 {
680   Grob *me = unsmob_grob (smob);
681   Direction dir = Tuplet_bracket::get_default_dir (me);
682   return scm_from_int (dir);
683 }
684
685 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
686 SCM
687 Tuplet_bracket::calc_positions (SCM smob)
688 {
689   Spanner *me = unsmob_spanner (smob);
690
691   /*
692     Don't print if it doesn't span time.
693    */
694   if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
695       == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
696     {
697       me->suicide ();
698       return SCM_EOL;
699     }
700
701   Real dy = 0.0;
702   Real offset = 0.0;
703   calc_position_and_height (me, &offset, &dy);
704   
705   SCM x = scm_cons (scm_from_double (offset),
706                     scm_from_double (offset + dy));
707   
708   return x;
709 }
710
711 /*
712   similar to beam ?
713 */
714 Direction
715 Tuplet_bracket::get_default_dir (Grob *me)
716 {
717   Drul_array<int> dirs (0, 0);
718   extract_grob_set (me, "note-columns", columns);
719   for (vsize i = 0; i < columns.size (); i++)
720     {
721       Grob *nc = columns[i];
722       Direction d = Note_column::dir (nc);
723       if (d)
724         dirs[d]++;
725     }
726
727   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
728 }
729
730 void
731 Tuplet_bracket::add_column (Grob *me, Item *n)
732 {
733   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
734   add_bound_item (dynamic_cast<Spanner *> (me), n);
735 }
736
737 void
738 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
739 {
740   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
741 }
742
743 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
744 SCM
745 Tuplet_bracket::calc_cross_staff (SCM smob)
746 {
747   Grob *me = unsmob_grob (smob);
748   Grob *staff_symbol = 0;
749   extract_grob_set (me, "note-columns", cols);
750   bool equally_long = false;
751   Grob *par_beam = parallel_beam (me, cols, &equally_long);
752
753   if (par_beam)
754     return par_beam->get_property ("cross-staff");
755
756   for (vsize i = 0; i < cols.size (); i++)
757     {
758       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
759       if (!stem)
760         continue;
761       
762       if (to_boolean (stem->get_property ("cross-staff")))
763         return SCM_BOOL_T;
764
765       Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
766       if (staff_symbol && (stem_staff != staff_symbol))
767         return SCM_BOOL_T;
768       staff_symbol = stem_staff;
769     }
770   return SCM_BOOL_F;
771 }
772
773 ADD_INTERFACE (Tuplet_bracket,
774                "A bracket with a number in the middle, used for tuplets. "
775                "When the bracket spans  a line break, the value of "
776                "@code{break-overshoot} determines how far it extends "
777                "beyond the staff. "
778                "At a line break, the markups in the @code{edge-text} are printed "
779                "at the edges. ",
780
781
782                /* properties */
783                "bracket-flare "
784                "bracket-visibility "
785                "break-overshoot "
786                "connect-to-neighbor "
787                "control-points "
788                "direction "
789                "edge-height "
790                "edge-text "
791                "gap "
792                "positions "
793                "note-columns "
794                "padding "
795                "tuplet-number "
796                "shorten-pair "
797                "staff-padding "
798                "thickness "
799                "tuplets ");
800
801