Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / lily / tuplet-bracket.cc
blob3bf2d1b49e8487815b79d1ec8af0b89a526803b2
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2011 Jan Nieuwenhuizen <janneke@gnu.org>
5 Han-Wen Nienhuys <hanwen@xs4all.nl>
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.
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.
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/>.
22 TODO:
24 - tuplet bracket should probably be subject to the same rules as
25 beam sloping/quanting.
27 - There is no support for kneed brackets, or nested brackets.
29 - number placement for parallel beams should be much more advanced:
30 for sloped beams some extra horizontal offset must be introduced.
32 - number placement is usually done over the center note, not the
33 graphical center.
37 TODO: quantise, we don't want to collide with staff lines.
38 (or should we be above staff?)
40 todo: handle breaking elegantly.
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"
60 static Item *
61 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
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);
70 return g;
73 void
74 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
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;
81 me->set_property (sym, ly_interval2scm (pair));
85 Return beam that encompasses the span of the tuplet bracket.
87 Grob *
88 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
89 bool *equally_long)
91 Spanner *me = dynamic_cast<Spanner *> (me_grob);
93 if (me->get_bound (LEFT)->break_status_dir ()
94 || me->get_bound (RIGHT)->break_status_dir ())
95 return 0;
97 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
98 Note_column::get_stem (cols.back ()));
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;
106 Drul_array<Grob *> beams;
107 Direction d = LEFT;
109 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
110 while (flip (&d) != LEFT);
112 *equally_long = false;
113 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
114 return 0;
116 extract_grob_set (beams[LEFT], "stems", beam_stems);
117 if (beam_stems.size () == 0)
119 programming_error ("beam under tuplet bracket has no stems");
120 *equally_long = 0;
121 return 0;
124 *equally_long
125 = (beam_stems[0] == stems[LEFT]
126 && beam_stems.back () == stems[RIGHT]);
127 return beams[LEFT];
130 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
132 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
134 Spanner *me = unsmob_spanner (smob);
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));
140 Drul_array<bool> connect_to_other (false, false);
141 Direction d = LEFT;
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 ())
151 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
153 /* trigger possible suicide*/
154 (void) neighbor->get_property ("positions");
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 ());
162 while (flip (&d) != LEFT);
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]));
168 return SCM_EOL;
171 Grob *
172 Tuplet_bracket::get_common_x (Spanner *me)
174 extract_grob_set (me, "note-columns", columns);
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);
180 return commonx;
183 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points, 1)
185 Tuplet_bracket::calc_control_points (SCM smob)
187 Spanner *me = unsmob_spanner (smob);
189 extract_grob_set (me, "note-columns", columns);
191 SCM scm_positions = me->get_property ("positions");
192 if (!me->is_live ())
193 return SCM_EOL;
195 if (!scm_is_pair (scm_positions))
196 programming_error ("Positions should be number pair");
198 Drul_array<Real> positions
199 = robust_scm2drul (scm_positions, Drul_array<Real> (0, 0));
201 Grob *commonx = get_common_x (me);
202 Direction dir = get_grob_direction (me);
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);
208 Drul_array<bool> connect_to_other
209 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
210 Drul_array<bool> (false, false));
212 Interval x_span;
213 Direction d = LEFT;
216 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
218 if (connect_to_other[d])
220 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
221 Interval (-0.5, 0.0)));
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];
231 else if (d == RIGHT
232 && (columns.empty ()
233 || (bounds[d]->get_column ()
234 != dynamic_cast<Item *> (columns.back ())->get_column ())))
237 We're connecting to a column, for the last bit of a broken
238 fullLength bracket.
240 Real padding
241 = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
243 if (bounds[d]->break_status_dir ())
244 padding = 0.0;
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];
250 coord = max (coord, x_span[LEFT]);
252 x_span[d] = coord - padding;
255 while (flip (&d) != LEFT);
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])));
263 TODO:
265 in the case that there is no bracket, but there is a (single) beam,
266 follow beam precisely for determining tuplet number location.
268 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
270 Tuplet_bracket::print (SCM smob)
272 Spanner *me = unsmob_spanner (smob);
273 Stencil mol;
275 extract_grob_set (me, "note-columns", columns);
276 bool equally_long = false;
277 Grob *par_beam = parallel_beam (me, columns, &equally_long);
279 bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
281 FIXME: The type of this prop is sucky.
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;
292 Don't print a tuplet bracket and number if
293 no control-points were calculated
295 SCM cpoints = me->get_property ("control-points");
296 if (scm_ilength (cpoints) < 2)
298 me->suicide ();
299 return SCM_EOL;
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.
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;
310 Drul_array<Offset> points;
311 points[LEFT] = ly_scm2offset (scm_car (cpoints));
312 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
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]);
317 Output_def *pap = me->layout ();
319 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
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.
325 Real gap = 0.;
326 if (bracket_visibility && number_grob)
328 Interval ext = number_grob->extent (number_grob, X_AXIS);
329 if (!ext.is_empty ())
331 gap = ext.length () + 1.0;
333 if ((0.75 * x_span.length () < gap) && !bracket_prop)
334 bracket_visibility = false;
338 if (bracket_visibility)
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;
350 Direction dir = get_grob_direction (me);
352 scale_drul (&height, -ss * dir);
353 scale_drul (&flare, ss);
354 scale_drul (&shorten, ss);
356 Drul_array<bool> connect_to_other
357 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
358 Drul_array<bool> (false, false));
360 Direction d = LEFT;
363 if (connect_to_other[d])
365 height[d] = 0.0;
366 flare[d] = 0.0;
367 shorten[d] = 0.0;
369 SCM edge_text = me->get_property ("edge-text");
371 if (scm_is_pair (edge_text))
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))
377 SCM t
378 = Text_interface::interpret_markup (pap->self_scm (),
379 properties, text);
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;
389 while (flip (&d) != LEFT);
391 Stencil brack = make_bracket (me, Y_AXIS,
392 points[RIGHT] - points[LEFT],
393 height,
395 0.1 = more space at right due to italics
396 TODO: use italic correction of font.
398 Interval (-0.5, 0.5) * gap + 0.1,
399 flare, shorten);
403 if (!edge_stencils[d].is_empty ())
404 brack.add_stencil (edge_stencils[d]);
406 while (flip (&d) != LEFT);
408 mol.add_stencil (brack);
411 mol.translate (points[LEFT]);
412 return mol.smobbed_copy ();
416 should move to lookup?
418 TODO: this will fail for very short (shorter than the flare)
419 brackets.
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)
430 Drul_array<Offset> corners (Offset (0, 0), dz);
432 Real length = dz.length ();
433 Drul_array<Offset> gap_corners;
435 Axis bracket_axis = other_axis (protrusion_axis);
437 Drul_array<Offset> straight_corners = corners;
439 Direction d = LEFT;
441 straight_corners[d] += -d * shorten[d] / length * dz;
442 while (flip (&d) != LEFT);
444 if (!gap.is_empty ())
447 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
448 while (flip (&d) != LEFT);
451 Drul_array<Offset> flare_corners = straight_corners;
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];
458 while (flip (&d) != LEFT);
460 Stencil m;
463 if (!gap.is_empty ())
464 m.add_stencil (Line_interface::line (me, straight_corners[d],
465 gap_corners[d]));
467 m.add_stencil (Line_interface::line (me, straight_corners[d],
468 flare_corners[d]));
471 while (flip (&d) != LEFT);
473 if (gap.is_empty ())
474 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
475 straight_corners[RIGHT]));
477 return m;
480 void
481 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
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++;
488 vsize r = columns.size ();
489 while (r > l && Note_column::has_rests (columns[r - 1]))
490 r--;
492 *left = *right = 0;
494 if (l < r)
496 *left = columns[l];
497 *right = columns[r - 1];
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)
507 Spanner *me = dynamic_cast<Spanner *> (me_grob);
509 extract_grob_set (me, "note-columns", columns);
510 extract_grob_set (me, "tuplets", tuplets);
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);
518 Grob *commonx = get_common_x (me);
519 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
521 Interval staff;
522 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
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")))
529 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
530 if (pad >= 0.0)
532 staff = st->extent (commony, Y_AXIS) - my_offset;
533 staff.widen (pad);
537 Direction dir = get_grob_direction (me);
539 bool equally_long = false;
540 Grob *par_beam = parallel_beam (me, columns, &equally_long);
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"));
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 ()))
557 trigger set_stem_ends
559 (void) par_beam->get_property ("quantized-positions");
561 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
562 Note_column::get_stem (columns.back ()));
564 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
565 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
566 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
567 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
568 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
570 *dy = rp - lp;
571 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
572 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
574 else
577 Use outer non-rest columns to determine slope
579 Grob *left_col = 0;
580 Grob *right_col = 0;
581 get_bounds (me, &left_col, &right_col);
582 if (left_col && right_col)
584 Interval rv = Note_column::cross_staff_extent (right_col, commony);
585 Interval lv = Note_column::cross_staff_extent (left_col, commony);
586 rv.unite (staff);
587 lv.unite (staff);
589 Real graphical_dy = rv[dir] - lv[dir];
591 Slice ls = Note_column::head_positions_interval (left_col);
592 Slice rs = Note_column::head_positions_interval (right_col);
594 Interval musical_dy;
595 musical_dy[UP] = rs[UP] - ls[UP];
596 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
597 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
598 *dy = 0.0;
599 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
600 *dy = 0.0;
601 else
602 *dy = graphical_dy;
604 else
605 *dy = 0;
607 for (vsize i = 0; i < columns.size (); i++)
609 Interval note_ext = Note_column::cross_staff_extent (columns[i],
610 commony);
611 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
613 points.push_back (Offset (x, note_ext[dir]));
617 if (!follow_beam)
619 points.push_back (Offset (x0 - x0, staff[dir]));
620 points.push_back (Offset (x1 - x0, staff[dir]));
624 This is a slight hack. We compute two encompass points from the
625 bbox of the smaller tuplets.
627 We assume that the smaller bracket is 1.0 space high.
629 Real ss = Staff_symbol_referencer::staff_space (me);
630 for (vsize i = 0; i < tuplets.size (); i++)
632 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
633 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
635 if (!tuplets[i]->is_live ())
636 continue;
638 Direction d = LEFT;
639 Drul_array<Real> positions
640 = robust_scm2interval (tuplets[i]->get_property ("positions"),
641 Interval (0,0));
643 Real other_dy = positions[RIGHT] - positions[LEFT];
647 Real y
648 = tuplet_y.linear_combination (d * sign (other_dy));
651 We don't take padding into account for nested tuplets.
652 the edges can come very close to the stems, likewise for
653 nested tuplets?
656 points.push_back (Offset (tuplet_x[d] - x0, y));
658 while (flip (&d) != LEFT);
661 *offset = -dir * infinity_f;
662 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
663 for (vsize i = 0; i < points.size (); i++)
665 Real x = points[i][X_AXIS];
666 Real tuplety = (*dy) * x * factor + my_offset;
668 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
669 *offset = points[i][Y_AXIS] - tuplety;
672 *offset += scm_to_double (me->get_property ("padding")) * dir;
675 horizontal brackets should not collide with staff lines.
677 Kind of pointless since we put them outside the staff anyway, but
678 let's leave code for the future when possibly allow them to move
679 into the staff once again.
681 This doesn't seem to support cross-staff tuplets atm.
683 if (*dy == 0
684 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
686 // quantize, then do collision check.
687 *offset *= 2 / ss;
689 *offset = rint (*offset);
690 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
691 *offset += dir;
693 *offset *= 0.5 * ss;
697 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
699 Tuplet_bracket::calc_direction (SCM smob)
701 Grob *me = unsmob_grob (smob);
702 Direction dir = Tuplet_bracket::get_default_dir (me);
703 return scm_from_int (dir);
706 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
708 Tuplet_bracket::calc_positions (SCM smob)
710 Spanner *me = unsmob_spanner (smob);
712 Real dy = 0.0;
713 Real offset = 0.0;
714 calc_position_and_height (me, &offset, &dy);
716 SCM x = scm_cons (scm_from_double (offset),
717 scm_from_double (offset + dy));
719 return x;
723 similar to beam ?
725 Direction
726 Tuplet_bracket::get_default_dir (Grob *me)
728 Drul_array<int> dirs (0, 0);
729 extract_grob_set (me, "note-columns", columns);
730 for (vsize i = 0; i < columns.size (); i++)
732 Grob *nc = columns[i];
733 Direction d = Note_column::dir (nc);
734 if (d)
735 dirs[d]++;
738 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
741 void
742 Tuplet_bracket::add_column (Grob *me, Item *n)
744 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
745 add_bound_item (dynamic_cast<Spanner *> (me), n);
748 void
749 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
751 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
754 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
756 Tuplet_bracket::calc_cross_staff (SCM smob)
758 Grob *me = unsmob_grob (smob);
759 extract_grob_set (me, "note-columns", cols);
760 extract_grob_set (me, "tuplets", tuplets);
762 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
763 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
764 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
765 commony = st->common_refpoint (commony, Y_AXIS);
766 if (me->check_cross_staff (commony))
767 return SCM_BOOL_T;
769 bool equally_long = false;
770 Grob *par_beam = parallel_beam (me, cols, &equally_long);
772 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
773 return SCM_BOOL_T;
775 for (vsize i = 0; i < cols.size (); i++)
777 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
778 if (stem && to_boolean (stem->get_property ("cross-staff")))
779 return SCM_BOOL_T;
782 return SCM_BOOL_F;
785 ADD_INTERFACE (Tuplet_bracket,
786 "A bracket with a number in the middle, used for tuplets."
787 " When the bracket spans a line break, the value of"
788 " @code{break-overshoot} determines how far it extends"
789 " beyond the staff. At a line break, the markups in the"
790 " @code{edge-text} are printed at the edges.",
792 /* properties */
793 "bracket-flare "
794 "bracket-visibility "
795 "break-overshoot "
796 "connect-to-neighbor "
797 "control-points "
798 "direction "
799 "edge-height "
800 "edge-text "
801 "full-length-padding "
802 "full-length-to-extent "
803 "gap "
804 "positions "
805 "note-columns "
806 "padding "
807 "tuplet-number "
808 "shorten-pair "
809 "staff-padding "
810 "thickness "
811 "tuplets "