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