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