]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
use the new spacing-interface stuff in staff-spacing
[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 (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
95       != me->get_bound (RIGHT)->get_column ())
96     return 0;
97
98   Drul_array<Grob*> beams;
99   Direction d = LEFT;
100   do {
101     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
102   } while (flip (&d) != LEFT);
103   
104   *equally_long = false;
105   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
106     return 0;
107
108   extract_grob_set (beams[LEFT], "stems", beam_stems);
109   if (beam_stems.size () == 0)
110     {
111       programming_error ("beam under tuplet bracket has no stems");
112       *equally_long = 0;
113       return 0;
114     }
115
116   *equally_long =
117     (beam_stems[0] == stems[LEFT]
118      && beam_stems.back () == stems[RIGHT]);
119   return beams[LEFT];
120 }
121
122
123 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
124 SCM
125 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
126 {
127   Spanner *me = unsmob_spanner (smob);
128
129   Direction dir = get_grob_direction (me); 
130   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
131                              get_x_bound_item (me, RIGHT, dir));
132   
133   Drul_array<bool> connect_to_other (false, false);
134   Direction d = LEFT;
135   do
136     {
137       Direction break_dir = bounds[d]->break_status_dir ();
138       
139       Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
140
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   
285   SCM cpoints =  me->get_property ("control-points");
286   if (scm_ilength (cpoints) < 2)
287     {
288       me->suicide ();
289       return SCM_EOL;
290     }
291   
292   Drul_array<Offset> points;
293   points[LEFT] = ly_scm2offset (scm_car (cpoints));
294   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
295   
296   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
297   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
298
299
300
301   Output_def *pap = me->layout ();
302
303   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
304   
305   /*
306     No bracket when it would be smaller than the number.
307   */
308   Real gap = 0.;
309   if (bracket_visibility && number_grob)
310     {
311       Interval ext = number_grob->extent (number_grob, X_AXIS);
312       if (!ext.is_empty ())
313         {
314           gap = ext.length () + 1.0;
315       
316           if (0.75 * x_span.length () < gap)
317             bracket_visibility = false;
318         }
319     }
320
321   if (bracket_visibility)
322     {
323       Drul_array<Real> zero (0, 0);
324       Real ss = Staff_symbol_referencer::staff_space (me);
325       Drul_array<Real> height
326         = robust_scm2drul (me->get_property ("edge-height"), zero);
327       Drul_array<Real> flare
328         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
329       Drul_array<Real> shorten
330         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
331       Drul_array<Stencil> edge_stencils;
332
333       Direction dir = get_grob_direction (me);
334       
335       scale_drul (&height, -ss * dir);
336       scale_drul (&flare, ss);
337       scale_drul (&shorten, ss);
338
339       Drul_array<bool> connect_to_other =
340         robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
341                              Drul_array<bool> (false, false));
342
343       Direction d = LEFT;
344       do
345         {
346           if (connect_to_other[d])
347             {
348               height[d] = 0.0;
349               flare[d] = 0.0;
350               shorten[d] = 0.0;
351
352               SCM edge_text = me->get_property ("edge-text");
353
354               if (scm_is_pair (edge_text))
355                 {
356                   SCM properties = Font_interface::text_font_alist_chain (me);
357                   SCM text = index_get_cell (edge_text, d);
358                   if (Text_interface::is_markup (text))
359                     {
360                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
361                                                                 properties, text);
362
363                       Stencil *edge_text = unsmob_stencil (t);
364                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
365                       edge_stencils[d] = *edge_text;
366                     }
367                 }
368             }
369         }
370       while (flip (&d) != LEFT);
371
372       Stencil brack = make_bracket (me, Y_AXIS,
373                                     points[RIGHT] - points[LEFT],
374                                     height,
375                                     /*
376                                       0.1 = more space at right due to italics
377                                       TODO: use italic correction of font.
378                                     */
379                                     Interval (-0.5, 0.5) * gap + 0.1,
380                                     flare, shorten);
381
382       do
383         {
384           if (!edge_stencils[d].is_empty ())
385             brack.add_stencil (edge_stencils[d]);
386         }
387       while (flip (&d) != LEFT);
388
389       mol.add_stencil (brack);
390     }
391
392   mol.translate (points[LEFT]);
393   return mol.smobbed_copy ();
394 }
395
396 /*
397   should move to lookup?
398
399   TODO: this will fail for very short (shorter than the flare)
400   brackets.
401 */
402 Stencil
403 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
404                               Axis protusion_axis,
405                               Offset dz,
406                               Drul_array<Real> height,
407                               Interval gap,
408                               Drul_array<Real> flare,
409                               Drul_array<Real> shorten)
410 {
411   Drul_array<Offset> corners (Offset (0, 0), dz);
412
413   Real length = dz.length ();
414   Drul_array<Offset> gap_corners;
415
416   Axis bracket_axis = other_axis (protusion_axis);
417
418   Drul_array<Offset> straight_corners = corners;
419
420   Direction d = LEFT;
421   do
422     straight_corners[d] += -d * shorten[d] / length * dz;
423   while (flip (&d) != LEFT);
424
425   if (!gap.is_empty ())
426     {
427       do
428         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
429       while (flip (&d) != LEFT);
430     }
431
432   Drul_array<Offset> flare_corners = straight_corners;
433   do
434     {
435       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
436       flare_corners[d][protusion_axis] += height[d];
437       straight_corners[d][bracket_axis] += -d * flare[d];
438     }
439   while (flip (&d) != LEFT);
440
441   Stencil m;
442   do
443     {
444       if (!gap.is_empty ())
445         m.add_stencil (Line_interface::line (me, straight_corners[d],
446                                              gap_corners[d]));
447
448       m.add_stencil (Line_interface::line (me, straight_corners[d],
449                                            flare_corners[d]));
450     }
451
452   while (flip (&d) != LEFT);
453
454   if (gap.is_empty ())
455     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
456                                          straight_corners[RIGHT]));
457   
458   return m;
459 }
460
461 void
462 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
463 {
464   extract_grob_set (me, "note-columns", columns);
465   vsize l = 0;
466   while (l < columns.size () && Note_column::has_rests (columns[l]))
467     l++;
468
469   vsize r = columns.size ();
470   while (r > l && Note_column::has_rests (columns[r-1]))
471     r--;
472
473   *left = *right = 0;
474
475   if (l < r)
476     {
477       *left = columns[l];
478       *right = columns[r-1];
479     }
480 }
481
482 /*
483   use first -> last note for slope, and then correct for disturbing
484   notes in between.  */
485 void
486 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
487 {
488   Spanner *me = dynamic_cast<Spanner *> (me_grob);
489
490   extract_grob_set (me, "note-columns", columns);
491   extract_grob_set (me, "tuplets", tuplets);
492
493   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
494   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
495   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
496     commony = st->common_refpoint (commony, Y_AXIS);
497   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
498
499   Grob *commonx = get_common_x (me);
500   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
501
502   Interval staff;
503   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
504     {
505       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
506       if  (pad >= 0.0)
507         {
508           staff = st->extent (commony, Y_AXIS) - my_offset;
509           staff.widen (pad);
510         }
511     }
512   
513   Direction dir = get_grob_direction (me);
514
515   bool equally_long = false;
516   Grob *par_beam = parallel_beam (me, columns, &equally_long);
517   
518
519
520   Item *lgr = get_x_bound_item (me, LEFT, dir);
521   Item *rgr = get_x_bound_item (me, RIGHT, dir);
522   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
523   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
524   bool follow_beam = par_beam
525     && ((get_grob_direction (par_beam) == dir) || to_boolean (par_beam->get_property ("knee")));
526
527   vector<Offset> points;
528
529   if (columns.size ()
530       && follow_beam
531       && Note_column::get_stem (columns[0])
532       && Note_column::get_stem (columns.back ()))
533     {
534       /*
535         trigger set_stem_ends
536        */
537       (void) par_beam->get_property ("quantized-positions");
538
539
540       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
541                                 Note_column::get_stem (columns.back ()));
542
543       
544       
545
546       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
547       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
548         + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
549       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
550         + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
551
552       *dy = rp - lp;
553       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
554       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
555     }
556   else
557     {
558       /*
559         Use outer non-rest columns to determine slope
560       */
561       Grob *left_col = 0;
562       Grob *right_col = 0;
563       get_bounds (me, &left_col, &right_col);
564       if (left_col && right_col)
565         {
566           Interval rv = right_col->extent (commony, Y_AXIS);
567           Interval lv = left_col->extent (commony, Y_AXIS);
568           rv.unite (staff);
569           lv.unite (staff);
570           Real graphical_dy = rv[dir] - lv[dir];
571
572           Slice ls = Note_column::head_positions_interval (left_col);
573           Slice rs = Note_column::head_positions_interval (right_col);
574
575           Interval musical_dy;
576           musical_dy[UP] = rs[UP] - ls[UP];
577           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
578           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
579             *dy = 0.0;
580           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
581             *dy = 0.0;
582           else
583             *dy = graphical_dy;
584         }
585       else
586         *dy = 0;
587
588       for (vsize i = 0; i < columns.size (); i++)
589         {
590           Interval note_ext = columns[i]->extent (commony, Y_AXIS);
591           Real notey = note_ext[dir] - my_offset;
592
593           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
594           points.push_back (Offset (x, notey));
595         }
596     }
597
598   if (!follow_beam)
599     {
600       points.push_back (Offset (x0 - x0, staff[dir]));
601       points.push_back (Offset (x1 - x0, staff[dir]));
602     }
603   
604   /*
605     This is a slight hack. We compute two encompass points from the
606     bbox of the smaller tuplets.
607
608     We assume that the smaller bracket is 1.0 space high.
609   */
610   Real ss = Staff_symbol_referencer::staff_space (me);
611   for (vsize i = 0; i < tuplets.size (); i++)
612     {
613       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
614       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
615
616       if (!tuplets[i]->is_live ())
617         continue;
618       
619       Direction d = LEFT;
620       Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
621                                                         Interval (0,0));
622
623       
624       Real other_dy = positions[RIGHT] - positions[LEFT];
625
626       do
627         {
628           Real y
629             = tuplet_y.linear_combination (d * sign (other_dy));
630
631           /*
632             We don't take padding into account for nested tuplets.
633             the edges can come very close to the stems, likewise for
634             nested tuplets?
635           */
636
637           points.push_back (Offset (tuplet_x[d] - x0, y));
638         }
639       while (flip (&d) != LEFT);
640     }
641
642   *offset = -dir * infinity_f;
643   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
644   for (vsize i = 0; i < points.size (); i++)
645     {
646       Real x = points[i][X_AXIS];
647       Real tuplety = (*dy) * x * factor;
648
649       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
650         *offset = points[i][Y_AXIS] - tuplety;
651     }
652
653   *offset += scm_to_double (me->get_property ("padding")) * dir;
654
655   /*
656     horizontal brackets should not collide with staff lines.
657
658     Kind of pointless since we put them outside the staff anyway, but
659     let's leave code for the future when possibly allow them to move
660     into the staff once again.
661   
662     This doesn't seem to support cross-staff tuplets atm.
663   */
664   if (*dy == 0
665       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
666     {
667       // quantize, then do collision check.
668       *offset *= 2 / ss;
669
670       *offset = rint (*offset);
671       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
672         *offset += dir;
673
674       *offset *= 0.5 * ss;
675     }
676 }
677
678
679 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
680 SCM
681 Tuplet_bracket::calc_direction (SCM smob)
682 {
683   Grob *me = unsmob_grob (smob);
684   Direction dir = Tuplet_bracket::get_default_dir (me);
685   return scm_from_int (dir);
686 }
687
688 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
689 SCM
690 Tuplet_bracket::calc_positions (SCM smob)
691 {
692   Spanner *me = unsmob_spanner (smob);
693
694   /*
695     Don't print if it doesn't span time.
696    */
697   if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
698       == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
699     {
700       me->suicide ();
701       return SCM_EOL;
702     }
703
704   Real dy = 0.0;
705   Real offset = 0.0;
706   calc_position_and_height (me, &offset, &dy);
707   
708   SCM x = scm_cons (scm_from_double (offset),
709                     scm_from_double (offset + dy));
710   
711   return x;
712 }
713
714 /*
715   similar to beam ?
716 */
717 Direction
718 Tuplet_bracket::get_default_dir (Grob *me)
719 {
720   Drul_array<int> dirs (0, 0);
721   extract_grob_set (me, "note-columns", columns);
722   for (vsize i = 0; i < columns.size (); i++)
723     {
724       Grob *nc = columns[i];
725       Direction d = Note_column::dir (nc);
726       if (d)
727         dirs[d]++;
728     }
729
730   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
731 }
732
733 void
734 Tuplet_bracket::add_column (Grob *me, Item *n)
735 {
736   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
737   add_bound_item (dynamic_cast<Spanner *> (me), n);
738 }
739
740 void
741 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
742 {
743   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
744 }
745
746 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
747 SCM
748 Tuplet_bracket::calc_cross_staff (SCM smob)
749 {
750   Grob *me = unsmob_grob (smob);
751   Grob *staff_symbol = 0;
752   extract_grob_set (me, "note-columns", cols);
753   bool equally_long = false;
754   Grob *par_beam = parallel_beam (me, cols, &equally_long);
755
756   if (par_beam)
757     return par_beam->get_property ("cross-staff");
758
759   for (vsize i = 0; i < cols.size (); i++)
760     {
761       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
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