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