]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Better pure height approximations for stems.
[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 "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 = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
285   bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
286   if (scm_is_bool (bracket_vis_prop))
287     bracket_visibility = bracket_prop;
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 (!to_boolean (bracket_vis_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.;
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 void
505 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
506 {
507   Spanner *me = dynamic_cast<Spanner *> (me_grob);
508
509   extract_grob_set (me, "note-columns", columns);
510   extract_grob_set (me, "tuplets", tuplets);
511
512   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
513   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
514   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
515     commony = st->common_refpoint (commony, Y_AXIS);
516   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
517
518   Grob *commonx = get_common_x (me);
519   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
520
521   Interval staff;
522   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
523
524   /* staff-padding doesn't work correctly on cross-staff tuplets
525      because it only considers one staff symbol. Until this works,
526      disable it. */
527   if (st && !to_boolean (me->get_property ("cross-staff")))
528     {
529       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
530       if (pad >= 0.0)
531         {
532           staff = st->extent (commony, Y_AXIS) - my_offset;
533           staff.widen (pad);
534         }
535     }
536
537   Direction dir = get_grob_direction (me);
538
539   bool equally_long = false;
540   Grob *par_beam = parallel_beam (me, columns, &equally_long);
541
542   Item *lgr = get_x_bound_item (me, LEFT, dir);
543   Item *rgr = get_x_bound_item (me, RIGHT, dir);
544   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
545   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
546   bool follow_beam = par_beam
547                      && get_grob_direction (par_beam) == dir
548                      && !to_boolean (par_beam->get_property ("knee"));
549
550   vector<Offset> points;
551   if (columns.size ()
552       && follow_beam
553       && Note_column::get_stem (columns[0])
554       && Note_column::get_stem (columns.back ()))
555     {
556       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
557                                 Note_column::get_stem (columns.back ()));
558
559       Real lp = stems[LEFT]->extent (stems[LEFT], Y_AXIS)[get_grob_direction (stems[LEFT])]
560                 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
561       Real rp = stems[RIGHT]->extent (stems[RIGHT], Y_AXIS)[get_grob_direction (stems[RIGHT])]
562                 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
563
564       *dy = rp - lp;
565       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
566       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
567     }
568   else
569     {
570       /*
571         Use outer non-rest columns to determine slope
572       */
573       Grob *left_col = 0;
574       Grob *right_col = 0;
575       get_bounds (me, &left_col, &right_col);
576       if (left_col && right_col)
577         {
578           Interval rv = Note_column::cross_staff_extent (right_col, commony);
579           Interval lv = Note_column::cross_staff_extent (left_col, commony);
580           rv.unite (staff);
581           lv.unite (staff);
582
583           Real graphical_dy = rv[dir] - lv[dir];
584
585           Slice ls = Note_column::head_positions_interval (left_col);
586           Slice rs = Note_column::head_positions_interval (right_col);
587
588           Interval musical_dy;
589           musical_dy[UP] = rs[UP] - ls[UP];
590           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
591           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
592             *dy = 0.0;
593           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
594             *dy = 0.0;
595           else
596             *dy = graphical_dy;
597         }
598       else
599         *dy = 0;
600
601       for (vsize i = 0; i < columns.size (); i++)
602         {
603           Interval note_ext = Note_column::cross_staff_extent (columns[i],
604                                                                commony);
605           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
606
607           points.push_back (Offset (x, note_ext[dir]));
608         }
609     }
610
611   if (!follow_beam)
612     {
613       points.push_back (Offset (x0 - x0, staff[dir]));
614       points.push_back (Offset (x1 - x0, staff[dir]));
615     }
616
617   /*
618     This is a slight hack. We compute two encompass points from the
619     bbox of the smaller tuplets.
620
621     We assume that the smaller bracket is 1.0 space high.
622   */
623   Real ss = Staff_symbol_referencer::staff_space (me);
624   for (vsize i = 0; i < tuplets.size (); i++)
625     {
626       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
627       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
628
629       if (!tuplets[i]->is_live ())
630         continue;
631
632       Direction d = LEFT;
633       Drul_array<Real> positions
634         = robust_scm2interval (tuplets[i]->get_property ("positions"),
635                                Interval (0, 0));
636
637       Real other_dy = positions[RIGHT] - positions[LEFT];
638
639       do
640         {
641           Real y
642             = tuplet_y.linear_combination (d * sign (other_dy));
643
644           /*
645             We don't take padding into account for nested tuplets.
646             the edges can come very close to the stems, likewise for
647             nested tuplets?
648           */
649
650           points.push_back (Offset (tuplet_x[d] - x0, y));
651         }
652       while (flip (&d) != LEFT);
653     }
654
655   *offset = -dir * infinity_f;
656   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
657   for (vsize i = 0; i < points.size (); i++)
658     {
659       Real x = points[i][X_AXIS];
660       Real tuplety = (*dy) * x * factor + my_offset;
661
662       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
663         *offset = points[i][Y_AXIS] - tuplety;
664     }
665
666   *offset += scm_to_double (me->get_property ("padding")) * dir;
667
668   /*
669     horizontal brackets should not collide with staff lines.
670
671     Kind of pointless since we put them outside the staff anyway, but
672     let's leave code for the future when possibly allow them to move
673     into the staff once again.
674
675     This doesn't seem to support cross-staff tuplets atm.
676   */
677   if (*dy == 0
678       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
679     {
680       // quantize, then do collision check.
681       *offset *= 2 / ss;
682
683       *offset = rint (*offset);
684       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
685         *offset += dir;
686
687       *offset *= 0.5 * ss;
688     }
689 }
690
691 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
692 SCM
693 Tuplet_bracket::calc_direction (SCM smob)
694 {
695   Grob *me = unsmob_grob (smob);
696   Direction dir = Tuplet_bracket::get_default_dir (me);
697   return scm_from_int (dir);
698 }
699
700 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
701 SCM
702 Tuplet_bracket::calc_positions (SCM smob)
703 {
704   Spanner *me = unsmob_spanner (smob);
705
706   Real dy = 0.0;
707   Real offset = 0.0;
708   calc_position_and_height (me, &offset, &dy);
709
710   SCM x = scm_cons (scm_from_double (offset),
711                     scm_from_double (offset + dy));
712
713   return x;
714 }
715
716 /*
717   similar to beam ?
718 */
719 Direction
720 Tuplet_bracket::get_default_dir (Grob *me)
721 {
722   Drul_array<int> dirs (0, 0);
723   extract_grob_set (me, "note-columns", columns);
724   for (vsize i = 0; i < columns.size (); i++)
725     {
726       Grob *nc = columns[i];
727       if (Note_column::has_rests (nc))
728         continue;
729       Direction d = Note_column::dir (nc);
730       if (d)
731         dirs[d]++;
732     }
733
734   if (dirs[UP] == dirs[DOWN])
735     {
736       if (dirs[UP] == 0)
737         return UP;
738       Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
739       if (!staff)
740         return UP;
741       Interval staff_extent = staff->extent (staff, Y_AXIS);
742       Interval extremal_positions;
743       extremal_positions.set_empty ();
744       for (vsize i = 0; i < columns.size (); i++)
745         {
746           Direction d = Note_column::dir (columns[i]);
747           extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
748         }
749       Direction d = LEFT;
750       do
751         extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
752       while (flip (&d) != LEFT);
753
754       return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
755     }
756
757   return dirs[UP] > dirs[DOWN] ? UP : DOWN;
758 }
759
760 void
761 Tuplet_bracket::add_column (Grob *me, Item *n)
762 {
763   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
764   add_bound_item (dynamic_cast<Spanner *> (me), n);
765 }
766
767 void
768 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
769 {
770   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
771 }
772
773 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
774 SCM
775 Tuplet_bracket::calc_cross_staff (SCM smob)
776 {
777   Grob *me = unsmob_grob (smob);
778   extract_grob_set (me, "note-columns", cols);
779   extract_grob_set (me, "tuplets", tuplets);
780
781   Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
782   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
783   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
784     commony = st->common_refpoint (commony, Y_AXIS);
785   if (me->check_cross_staff (commony))
786     return SCM_BOOL_T;
787
788   bool equally_long = false;
789   Grob *par_beam = parallel_beam (me, cols, &equally_long);
790
791   if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
792     return SCM_BOOL_T;
793
794   for (vsize i = 0; i < cols.size (); i++)
795     {
796       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
797       if (stem && to_boolean (stem->get_property ("cross-staff")))
798         return SCM_BOOL_T;
799     }
800
801   return SCM_BOOL_F;
802 }
803
804 ADD_INTERFACE (Tuplet_bracket,
805                "A bracket with a number in the middle, used for tuplets."
806                "  When the bracket spans a line break, the value of"
807                " @code{break-overshoot} determines how far it extends"
808                " beyond the staff.  At a line break, the markups in the"
809                " @code{edge-text} are printed at the edges.",
810
811                /* properties */
812                "bracket-flare "
813                "bracket-visibility "
814                "break-overshoot "
815                "connect-to-neighbor "
816                "control-points "
817                "direction "
818                "edge-height "
819                "edge-text "
820                "full-length-padding "
821                "full-length-to-extent "
822                "gap "
823                "positions "
824                "note-columns "
825                "padding "
826                "tuplet-number "
827                "shorten-pair "
828                "staff-padding "
829                "thickness "
830                "tuplets "
831               );