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