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