]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
T405 - Respect setting bracket-visibility property.
[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);          // Flag, print/don't print tuplet bracket.
286   /*
287     FIXME: The type of this prop is sucky.
288   */
289   SCM bracket_vis_prop = me->get_property ("bracket-visibility");
290   bool bracket_prop = ly_scm2bool (bracket_vis_prop);             // Flag, user has set bracket-visibility prop.
291   bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
292   if (scm_is_bool (bracket_vis_prop))
293     bracket_visibility = bracket_prop;
294   else if (bracket)
295     bracket_visibility = !par_beam;
296
297   /*
298     Don't print a tuplet bracket and number if
299     no control-points were calculated
300   */
301   SCM cpoints = me->get_property ("control-points");
302   if (scm_ilength (cpoints) < 2)
303     {
304       me->suicide ();
305       return SCM_EOL;
306     }
307   /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
308       the bracket, but still let the number be displayed.
309       Only do this if the user has not explicitly specified bracket-visibility = #t.
310   */
311   if (!bracket_prop)
312   {
313       if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
314           == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
315       {
316           bracket_visibility = false;
317       }
318   }
319   Drul_array<Offset> points;
320   points[LEFT] = ly_scm2offset (scm_car (cpoints));
321   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
322
323   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
324   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
325
326   Output_def *pap = me->layout ();
327
328   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
329
330   /*
331     Don't print the bracket when it would be smaller than the number.
332     ...Unless the user has coded bracket-visibility = #t, that is.
333   */
334   Real gap = 0.;
335   if (bracket_visibility && number_grob)
336     {
337       Interval ext = number_grob->extent (number_grob, X_AXIS);
338       if (!ext.is_empty ())
339         {
340           gap = ext.length () + 1.0;
341
342           if ((0.75 * x_span.length () < gap) && !bracket_prop)
343             bracket_visibility = false;
344         }
345     }
346
347   if (bracket_visibility)
348     {
349       Drul_array<Real> zero (0, 0);
350       Real ss = Staff_symbol_referencer::staff_space (me);
351       Drul_array<Real> height
352         = robust_scm2drul (me->get_property ("edge-height"), zero);
353       Drul_array<Real> flare
354         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
355       Drul_array<Real> shorten
356         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
357       Drul_array<Stencil> edge_stencils;
358
359       Direction dir = get_grob_direction (me);
360
361       scale_drul (&height, -ss * dir);
362       scale_drul (&flare, ss);
363       scale_drul (&shorten, ss);
364
365       Drul_array<bool> connect_to_other =
366         robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
367                              Drul_array<bool> (false, false));
368
369       Direction d = LEFT;
370       do
371         {
372           if (connect_to_other[d])
373             {
374               height[d] = 0.0;
375               flare[d] = 0.0;
376               shorten[d] = 0.0;
377
378               SCM edge_text = me->get_property ("edge-text");
379
380               if (scm_is_pair (edge_text))
381                 {
382                   SCM properties = Font_interface::text_font_alist_chain (me);
383                   SCM text = index_get_cell (edge_text, d);
384                   if (Text_interface::is_markup (text))
385                     {
386                       SCM t
387                         = Text_interface::interpret_markup (pap->self_scm (),
388                                                             properties, text);
389
390                       Stencil *edge_text = unsmob_stencil (t);
391                       edge_text->translate_axis (x_span[d] - x_span[LEFT],
392                                                  X_AXIS);
393                       edge_stencils[d] = *edge_text;
394                     }
395                 }
396             }
397         }
398       while (flip (&d) != LEFT);
399
400       Stencil brack = make_bracket (me, Y_AXIS,
401                                     points[RIGHT] - points[LEFT],
402                                     height,
403                                     /*
404                                       0.1 = more space at right due to italics
405                                       TODO: use italic correction of font.
406                                     */
407                                     Interval (-0.5, 0.5) * gap + 0.1,
408                                     flare, shorten);
409
410       do
411         {
412           if (!edge_stencils[d].is_empty ())
413             brack.add_stencil (edge_stencils[d]);
414         }
415       while (flip (&d) != LEFT);
416
417       mol.add_stencil (brack);
418     }
419
420   mol.translate (points[LEFT]);
421   return mol.smobbed_copy ();
422 }
423
424 /*
425   should move to lookup?
426
427   TODO: this will fail for very short (shorter than the flare)
428   brackets.
429 */
430 Stencil
431 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
432                               Axis protrusion_axis,
433                               Offset dz,
434                               Drul_array<Real> height,
435                               Interval gap,
436                               Drul_array<Real> flare,
437                               Drul_array<Real> shorten)
438 {
439   Drul_array<Offset> corners (Offset (0, 0), dz);
440
441   Real length = dz.length ();
442   Drul_array<Offset> gap_corners;
443
444   Axis bracket_axis = other_axis (protrusion_axis);
445
446   Drul_array<Offset> straight_corners = corners;
447
448   Direction d = LEFT;
449   do
450     straight_corners[d] += -d * shorten[d] / length * dz;
451   while (flip (&d) != LEFT);
452
453   if (!gap.is_empty ())
454     {
455       do
456         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
457       while (flip (&d) != LEFT);
458     }
459
460   Drul_array<Offset> flare_corners = straight_corners;
461   do
462     {
463       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
464       flare_corners[d][protrusion_axis] += height[d];
465       straight_corners[d][bracket_axis] += -d * flare[d];
466     }
467   while (flip (&d) != LEFT);
468
469   Stencil m;
470   do
471     {
472       if (!gap.is_empty ())
473         m.add_stencil (Line_interface::line (me, straight_corners[d],
474                                              gap_corners[d]));
475
476       m.add_stencil (Line_interface::line (me, straight_corners[d],
477                                            flare_corners[d]));
478     }
479
480   while (flip (&d) != LEFT);
481
482   if (gap.is_empty ())
483     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
484                                          straight_corners[RIGHT]));
485
486   return m;
487 }
488
489 void
490 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
491 {
492   extract_grob_set (me, "note-columns", columns);
493   vsize l = 0;
494   while (l < columns.size () && Note_column::has_rests (columns[l]))
495     l++;
496
497   vsize r = columns.size ();
498   while (r > l && Note_column::has_rests (columns[r-1]))
499     r--;
500
501   *left = *right = 0;
502
503   if (l < r)
504     {
505       *left = columns[l];
506       *right = columns[r-1];
507     }
508 }
509
510 /*
511   use first -> last note for slope, and then correct for disturbing
512   notes in between.  */
513 void
514 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
515 {
516   Spanner *me = dynamic_cast<Spanner *> (me_grob);
517
518   extract_grob_set (me, "note-columns", columns);
519   extract_grob_set (me, "tuplets", tuplets);
520
521   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
522   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
523   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
524     commony = st->common_refpoint (commony, Y_AXIS);
525   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
526
527   Grob *commonx = get_common_x (me);
528   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
529
530   Interval staff;
531   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
532
533   /* staff-padding doesn't work correctly on cross-staff tuplets
534      because it only considers one staff symbol. Until this works,
535      disable it. */
536   if (st && !to_boolean (me->get_property ("cross-staff")))
537     {
538       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
539       if  (pad >= 0.0)
540         {
541           staff = st->extent (commony, Y_AXIS) - my_offset;
542           staff.widen (pad);
543         }
544     }
545
546   Direction dir = get_grob_direction (me);
547
548   bool equally_long = false;
549   Grob *par_beam = parallel_beam (me, columns, &equally_long);
550
551   Item *lgr = get_x_bound_item (me, LEFT, dir);
552   Item *rgr = get_x_bound_item (me, RIGHT, dir);
553   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
554   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
555   bool follow_beam = par_beam
556     && get_grob_direction (par_beam) == dir
557     && ! to_boolean (par_beam->get_property ("knee"));
558
559   vector<Offset> points;
560   if (columns.size ()
561       && follow_beam
562       && Note_column::get_stem (columns[0])
563       && Note_column::get_stem (columns.back ()))
564     {
565       /*
566         trigger set_stem_ends
567        */
568       (void) par_beam->get_property ("quantized-positions");
569
570       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
571                                 Note_column::get_stem (columns.back ()));
572
573       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
574       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
575         + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
576       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
577         + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
578
579       *dy = rp - lp;
580       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
581       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
582     }
583   else
584     {
585       /*
586         Use outer non-rest columns to determine slope
587       */
588       Grob *left_col = 0;
589       Grob *right_col = 0;
590       get_bounds (me, &left_col, &right_col);
591       if (left_col && right_col)
592         {
593           Interval rv = Note_column::cross_staff_extent (right_col, commony);
594           Interval lv = Note_column::cross_staff_extent (left_col, commony);
595           rv.unite (staff);
596           lv.unite (staff);
597
598           Real graphical_dy = rv[dir] - lv[dir];
599
600           Slice ls = Note_column::head_positions_interval (left_col);
601           Slice rs = Note_column::head_positions_interval (right_col);
602
603           Interval musical_dy;
604           musical_dy[UP] = rs[UP] - ls[UP];
605           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
606           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
607             *dy = 0.0;
608           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
609             *dy = 0.0;
610           else
611             *dy = graphical_dy;
612         }
613       else
614         *dy = 0;
615
616       for (vsize i = 0; i < columns.size (); i++)
617         {
618           Interval note_ext = Note_column::cross_staff_extent (columns[i],
619                                                                commony);
620           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
621
622           points.push_back (Offset (x, note_ext[dir]));
623         }
624     }
625
626   if (!follow_beam)
627     {
628       points.push_back (Offset (x0 - x0, staff[dir]));
629       points.push_back (Offset (x1 - x0, staff[dir]));
630     }
631
632   /*
633     This is a slight hack. We compute two encompass points from the
634     bbox of the smaller tuplets.
635
636     We assume that the smaller bracket is 1.0 space high.
637   */
638   Real ss = Staff_symbol_referencer::staff_space (me);
639   for (vsize i = 0; i < tuplets.size (); i++)
640     {
641       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
642       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
643
644       if (!tuplets[i]->is_live ())
645         continue;
646
647       Direction d = LEFT;
648       Drul_array<Real> positions
649         = robust_scm2interval (tuplets[i]->get_property ("positions"),
650                                Interval (0,0));
651
652       Real other_dy = positions[RIGHT] - positions[LEFT];
653
654       do
655         {
656           Real y
657             = tuplet_y.linear_combination (d * sign (other_dy));
658
659           /*
660             We don't take padding into account for nested tuplets.
661             the edges can come very close to the stems, likewise for
662             nested tuplets?
663           */
664
665           points.push_back (Offset (tuplet_x[d] - x0, y));
666         }
667       while (flip (&d) != LEFT);
668     }
669
670   *offset = -dir * infinity_f;
671   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
672   for (vsize i = 0; i < points.size (); i++)
673     {
674       Real x = points[i][X_AXIS];
675       Real tuplety = (*dy) * x * factor + my_offset;
676
677       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
678         *offset = points[i][Y_AXIS] - tuplety;
679     }
680
681   *offset += scm_to_double (me->get_property ("padding")) * dir;
682
683   /*
684     horizontal brackets should not collide with staff lines.
685
686     Kind of pointless since we put them outside the staff anyway, but
687     let's leave code for the future when possibly allow them to move
688     into the staff once again.
689
690     This doesn't seem to support cross-staff tuplets atm.
691   */
692   if (*dy == 0
693       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
694     {
695       // quantize, then do collision check.
696       *offset *= 2 / ss;
697
698       *offset = rint (*offset);
699       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
700         *offset += dir;
701
702       *offset *= 0.5 * ss;
703     }
704 }
705
706 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
707 SCM
708 Tuplet_bracket::calc_direction (SCM smob)
709 {
710   Grob *me = unsmob_grob (smob);
711   Direction dir = Tuplet_bracket::get_default_dir (me);
712   return scm_from_int (dir);
713 }
714
715 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
716 SCM
717 Tuplet_bracket::calc_positions (SCM smob)
718 {
719   Spanner *me = unsmob_spanner (smob);
720
721   Real dy = 0.0;
722   Real offset = 0.0;
723   calc_position_and_height (me, &offset, &dy);
724
725   SCM x = scm_cons (scm_from_double (offset),
726                     scm_from_double (offset + dy));
727
728   return x;
729 }
730
731 /*
732   similar to beam ?
733 */
734 Direction
735 Tuplet_bracket::get_default_dir (Grob *me)
736 {
737   Drul_array<int> dirs (0, 0);
738   extract_grob_set (me, "note-columns", columns);
739   for (vsize i = 0; i < columns.size (); i++)
740     {
741       Grob *nc = columns[i];
742       Direction d = Note_column::dir (nc);
743       if (d)
744         dirs[d]++;
745     }
746
747   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
748 }
749
750 void
751 Tuplet_bracket::add_column (Grob *me, Item *n)
752 {
753   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
754   add_bound_item (dynamic_cast<Spanner *> (me), n);
755 }
756
757 void
758 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
759 {
760   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
761 }
762
763 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
764 SCM
765 Tuplet_bracket::calc_cross_staff (SCM smob)
766 {
767   Grob *me = unsmob_grob (smob);
768   extract_grob_set (me, "note-columns", cols);
769   extract_grob_set (me, "tuplets", tuplets);
770
771   Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
772   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
773   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
774     commony = st->common_refpoint (commony, Y_AXIS);
775   if (me->check_cross_staff (commony))
776     return SCM_BOOL_T;
777
778   bool equally_long = false;
779   Grob *par_beam = parallel_beam (me, cols, &equally_long);
780
781   if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
782     return SCM_BOOL_T;
783
784   for (vsize i = 0; i < cols.size (); i++)
785     {
786       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
787       if (stem && to_boolean (stem->get_property ("cross-staff")))
788         return SCM_BOOL_T;
789     }
790
791   return SCM_BOOL_F;
792 }
793
794 ADD_INTERFACE (Tuplet_bracket,
795                "A bracket with a number in the middle, used for tuplets."
796                "  When the bracket spans a line break, the value of"
797                " @code{break-overshoot} determines how far it extends"
798                " beyond the staff.  At a line break, the markups in the"
799                " @code{edge-text} are printed at the edges.",
800
801                /* properties */
802                "bracket-flare "
803                "bracket-visibility "
804                "break-overshoot "
805                "connect-to-neighbor "
806                "control-points "
807                "direction "
808                "edge-height "
809                "edge-text "
810                "full-length-padding "
811                "full-length-to-extent "
812                "gap "
813                "positions "
814                "note-columns "
815                "padding "
816                "tuplet-number "
817                "shorten-pair "
818                "staff-padding "
819                "thickness "
820                "tuplets "
821                );