]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/tuplet-bracket.cc
Web-it: add news-front.itexi, add links to italian SVG files of Text input, plus...
[lilypond.git] / lily / tuplet-bracket.cc
index bb55ca2d018b7ac3776cc53b2e4ec3fac8ee8f6d..9935e87735bac3b19da290bad07010e0b1367b34 100644 (file)
@@ -1,10 +1,21 @@
 /*
 /*
-  tuplet-bracket.cc -- implement Tuplet_bracket
+  This file is part of LilyPond, the GNU music typesetter.
 
 
-  source file of the GNU LilyPond music typesetter
-
-  (c) 1997--2005 Jan Nieuwenhuizen <janneke@gnu.org>
+  Copyright (C) 1997--2010 Jan Nieuwenhuizen <janneke@gnu.org>
   Han-Wen Nienhuys <hanwen@xs4all.nl>
   Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+  LilyPond is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  LilyPond is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 /*
 */
 
 /*
   todo: handle breaking elegantly.
 */
 
   todo: handle breaking elegantly.
 */
 
-#include <math.h>
-
 #include "tuplet-bracket.hh"
 #include "line-interface.hh"
 #include "beam.hh"
 #include "warn.hh"
 #include "tuplet-bracket.hh"
 #include "line-interface.hh"
 #include "beam.hh"
 #include "warn.hh"
-#include "font-interface.hh"
 #include "output-def.hh"
 #include "output-def.hh"
+#include "font-interface.hh"
 #include "text-interface.hh"
 #include "stem.hh"
 #include "note-column.hh"
 #include "text-interface.hh"
 #include "stem.hh"
 #include "note-column.hh"
 #include "spanner.hh"
 #include "staff-symbol-referencer.hh"
 #include "lookup.hh"
 #include "spanner.hh"
 #include "staff-symbol-referencer.hh"
 #include "lookup.hh"
+#include "paper-column.hh"
+#include "moment.hh"
 
 static Item *
 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
 {
 
 static Item *
 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
 {
-  Spanner *me = dynamic_cast<Spanner*> (me_grob);
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
   Item *g = me->get_bound (hdir);
   if (Note_column::has_interface (g)
       && Note_column::get_stem (g)
       && Note_column::dir (g) == my_dir)
   Item *g = me->get_bound (hdir);
   if (Note_column::has_interface (g)
       && Note_column::get_stem (g)
       && Note_column::dir (g) == my_dir)
-    {
-      g = Note_column::get_stem (g);
-    }
-  
+    g = Note_column::get_stem (g);
+
   return g;
 }
 
   return g;
 }
 
+void
+flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
+{
+  Drul_array<Real> zero (0, 0);
+  Drul_array<Real> pair
+    = robust_scm2drul (me->internal_get_property (sym), zero);
+  pair[xdir] = 0.0;
+
+  me->set_property (sym, ly_interval2scm (pair));
+}
+
+/*
+  Return beam that encompasses the span of the tuplet bracket.
+*/
 Grob *
 Grob *
-Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> const &cols, bool *equally_long)
+Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
+                              bool *equally_long)
 {
 {
-  /*
-    ugh: code dup.
-  */
-  Grob *s1 = Note_column::get_stem (cols[0]);
-  Grob *s2 = Note_column::get_stem (cols.top ());
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
+
+  if (me->get_bound (LEFT)->break_status_dir ()
+      || me->get_bound (RIGHT)->break_status_dir ())
+    return 0;
 
 
-  Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
-  Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
+  Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
+                           Note_column::get_stem (cols.back ()));
 
 
-  Spanner *sp = dynamic_cast<Spanner *> (me);
+  if (!stems[RIGHT]
+      || !stems[LEFT]
+      || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
+         != me->get_bound (RIGHT)->get_column ()))
+    return 0;
+
+  Drul_array<Grob *> beams;
+  Direction d = LEFT;
+  do
+    beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
+  while (flip (&d) != LEFT);
 
   *equally_long = false;
 
   *equally_long = false;
-  if (! (b1 && (b1 == b2) && !sp->is_broken ()))
+  if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
     return 0;
 
     return 0;
 
-  extract_grob_set (b1, "stems", beam_stems);
+  extract_grob_set (beams[LEFT], "stems", beam_stems);
   if (beam_stems.size () == 0)
     {
       programming_error ("beam under tuplet bracket has no stems");
   if (beam_stems.size () == 0)
     {
       programming_error ("beam under tuplet bracket has no stems");
@@ -87,140 +121,223 @@ Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> const &cols, bool *equ
       return 0;
     }
 
       return 0;
     }
 
-  *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
-  return b1;
+  *equally_long
+    = (beam_stems[0] == stems[LEFT]
+       && beam_stems.back () == stems[RIGHT]);
+  return beams[LEFT];
 }
 
 }
 
-/*
-  TODO:
-
-  in the case that there is no bracket, but there is a (single) beam,
-  follow beam precisely for determining tuplet number location.
-*/
-MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
 SCM
 SCM
-Tuplet_bracket::print (SCM smob)
+Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
 {
 {
-  Grob *me = unsmob_grob (smob);
-  Stencil mol;
-  extract_grob_set (me, "note-columns", columns);
+  Spanner *me = unsmob_spanner (smob);
 
 
-  if (!columns.size ())
-    return mol.smobbed_copy ();
+  Direction dir = get_grob_direction (me);
+  Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
+                            get_x_bound_item (me, RIGHT, dir));
 
 
-  {
-    SCM lp = me->get_property ("left-position");
-    SCM rp = me->get_property ("right-position");
+  Drul_array<bool> connect_to_other (false, false);
+  Direction d = LEFT;
+  do
+    {
+      Direction break_dir = bounds[d]->break_status_dir ();
+      Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
+      vsize neighbor_idx = me->get_break_index () - break_dir;
+      if (break_dir
+         && d == RIGHT
+         && neighbor_idx < orig_spanner->broken_intos_.size ())
+       {
+         Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
 
 
-    if (!scm_is_number (rp) || !scm_is_number (lp))
-      {
-       /*
-         UGH. dependency tracking!
-        */
-       extract_grob_set (me, "tuplets", tuplets);
-       for (int i = 0; i < tuplets.size (); i++)
-         Tuplet_bracket::print (tuplets[i]->self_scm());
+         /* trigger possible suicide*/
+         (void) neighbor->get_property ("positions");
+       }
 
 
-       after_line_breaking (smob);
-      }
-  }
+      connect_to_other[d]
+       = (break_dir
+          && neighbor_idx < orig_spanner->broken_intos_.size ()
+          && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
+    }
+  while (flip (&d) != LEFT);
 
 
-  Real ly = robust_scm2double (me->get_property ("left-position"), 0);
-  Real ry = robust_scm2double (me->get_property ("right-position"), 0);
+  if (connect_to_other[LEFT] || connect_to_other[RIGHT])
+    return scm_cons (scm_from_bool (connect_to_other[LEFT]),
+                    scm_from_bool (connect_to_other[RIGHT]));
 
 
-  bool equally_long = false;
-  Grob *par_beam = parallel_beam (me, columns, &equally_long);
+  return SCM_EOL;
+}
 
 
-  Spanner *sp = dynamic_cast<Spanner *> (me);
+Grob *
+Tuplet_bracket::get_common_x (Spanner *me)
+{
+  extract_grob_set (me, "note-columns", columns);
 
 
-  bool bracket_visibility = !(par_beam && equally_long);
-  bool number_visibility = true;
+  Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
+  commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
+  commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
 
 
-  /*
-    Fixme: the type of this prop is sucky.
-  */
-  SCM bracket = me->get_property ("bracket-visibility");
-  if (scm_is_bool (bracket))
-    {
-      bracket_visibility = ly_scm2bool (bracket);
-    }
-  else if (bracket == ly_symbol2scm ("if-no-beam"))
-    bracket_visibility = !par_beam;
+  return commonx;
+}
 
 
-  SCM numb = me->get_property ("number-visibility");
-  if (scm_is_bool (numb))
-    {
-      number_visibility = ly_scm2bool (numb);
-    }
-  else if (numb == ly_symbol2scm ("if-no-beam"))
-    number_visibility = !par_beam;
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points, 1)
+SCM
+Tuplet_bracket::calc_control_points (SCM smob)
+{
+  Spanner *me = unsmob_spanner (smob);
+
+  extract_grob_set (me, "note-columns", columns);
+
+  SCM scm_positions = me->get_property ("positions");
+  if (!me->is_live ())
+    return SCM_EOL;
 
 
-  Grob *commonx = columns[0]->common_refpoint (columns.top (), X_AXIS);
+  if (!scm_is_pair (scm_positions))
+    programming_error ("Positions should be number pair");
 
 
-  
-  commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
-  commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
+  Drul_array<Real> positions
+    = robust_scm2drul (scm_positions, Drul_array<Real> (0, 0));
 
 
+  Grob *commonx = get_common_x (me);
   Direction dir = get_grob_direction (me);
 
   Drul_array<Item *> bounds;
   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
 
   Direction dir = get_grob_direction (me);
 
   Drul_array<Item *> bounds;
   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
 
+  Drul_array<bool> connect_to_other
+    = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
+                          Drul_array<bool> (false, false));
+
   Interval x_span;
   Direction d = LEFT;
   do
     {
       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
 
   Interval x_span;
   Direction d = LEFT;
   do
     {
       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
 
-      if (bounds[d]->break_status_dir())
+      if (connect_to_other[d])
        {
          Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
        {
          Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
-                                              Interval (0,0)));
+                                              Interval (-0.5, 0.0)));
+
+         if (d == RIGHT)
+           x_span[d] += d * overshoot[d];
+         else
+           x_span[d] = robust_relative_extent (bounds[d],
+                                               commonx, X_AXIS)[RIGHT]
+             - overshoot[LEFT];
+       }
 
 
-         x_span[d] += d * overshoot[d];
+      else if (d == RIGHT
+              && (columns.empty ()
+                  || (bounds[d]->get_column ()
+                      != dynamic_cast<Item *> (columns.back ())->get_column ())))
+       {
+         /*
+           We're connecting to a column, for the last bit of a broken
+           fullLength bracket.
+         */
+         Real padding
+           = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
+
+         if (bounds[d]->break_status_dir ())
+           padding = 0.0;
+
+         Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
+         if (to_boolean (me->get_property ("full-length-to-extent")))
+           coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
+
+         coord = max (coord, x_span[LEFT]);
+
+         x_span[d] = coord - padding;
        }
     }
   while (flip (&d) != LEFT);
 
        }
     }
   while (flip (&d) != LEFT);
 
-  Real w = x_span.length();
-  SCM number = me->get_property ("text");
+  x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
+  return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
+                    ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
+}
 
 
-  Output_def *pap = me->get_layout ();
-  Stencil num;
-  if (scm_is_string (number) && number_visibility)
-    {
-      SCM properties = Font_interface::text_font_alist_chain (me);
-      SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
-      num = *unsmob_stencil (snum);
-      num.align_to (X_AXIS, CENTER);
-      num.translate_axis (w / 2, X_AXIS);
-      num.align_to (Y_AXIS, CENTER);
+/*
+  TODO:
 
 
-      num.translate_axis ((ry - ly) / 2, Y_AXIS);
+  in the case that there is no bracket, but there is a (single) beam,
+  follow beam precisely for determining tuplet number location.
+*/
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
+SCM
+Tuplet_bracket::print (SCM smob)
+{
+  Spanner *me = unsmob_spanner (smob);
+  Stencil mol;
 
 
-      mol.add_stencil (num);
-    }
+  extract_grob_set (me, "note-columns", columns);
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
 
 
+  bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
   /*
   /*
-    No bracket when it would be smaller than the number.
+    FIXME: The type of this prop is sucky.
+  */
+  SCM bracket_vis_prop = me->get_property ("bracket-visibility");
+  bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
+  bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
+  if (scm_is_bool (bracket_vis_prop))
+    bracket_visibility = bracket_prop;
+  else if (bracket)
+    bracket_visibility = !par_beam;
 
 
-    TODO: should use GAP in calculation too.
+  /*
+    Don't print a tuplet bracket and number if
+    no control-points were calculated
   */
   */
-  if (bracket_visibility && number_visibility
-      && mol.extent (X_AXIS).length () > w)
+  SCM cpoints = me->get_property ("control-points");
+  if (scm_ilength (cpoints) < 2)
     {
     {
-      bracket_visibility = false;
+      me->suicide ();
+      return SCM_EOL;
     }
     }
+  /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
+      the bracket, but still let the number be displayed.
+      Only do this if the user has not explicitly specified bracket-visibility = #t.
+  */
+  if (!to_boolean (bracket_vis_prop)
+      && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
+         == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
+    bracket_visibility = false;
 
 
-  if (bracket_visibility)
+  Drul_array<Offset> points;
+  points[LEFT] = ly_scm2offset (scm_car (cpoints));
+  points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
+
+  Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
+  Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
+
+  Output_def *pap = me->layout ();
+
+  Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
+
+  /*
+    Don't print the bracket when it would be smaller than the number.
+    ...Unless the user has coded bracket-visibility = #t, that is.
+  */
+  Real gap = 0.;
+  if (bracket_visibility && number_grob)
     {
     {
-      Real gap = 0.;
+      Interval ext = number_grob->extent (number_grob, X_AXIS);
+      if (!ext.is_empty ())
+       {
+         gap = ext.length () + 1.0;
 
 
-      if (!num.extent (X_AXIS).is_empty ())
-       gap = num.extent (X_AXIS).length () + 1.0;
+         if ((0.75 * x_span.length () < gap) && !bracket_prop)
+           bracket_visibility = false;
+       }
+    }
 
 
-      Drul_array<Real> zero (0,0);
+  if (bracket_visibility)
+    {
+      Drul_array<Real> zero (0, 0);
       Real ss = Staff_symbol_referencer::staff_space (me);
       Drul_array<Real> height
        = robust_scm2drul (me->get_property ("edge-height"), zero);
       Real ss = Staff_symbol_referencer::staff_space (me);
       Drul_array<Real> height
        = robust_scm2drul (me->get_property ("edge-height"), zero);
@@ -228,26 +345,51 @@ Tuplet_bracket::print (SCM smob)
        = robust_scm2drul (me->get_property ("bracket-flare"), zero);
       Drul_array<Real> shorten
        = robust_scm2drul (me->get_property ("shorten-pair"), zero);
        = robust_scm2drul (me->get_property ("bracket-flare"), zero);
       Drul_array<Real> shorten
        = robust_scm2drul (me->get_property ("shorten-pair"), zero);
+      Drul_array<Stencil> edge_stencils;
+
+      Direction dir = get_grob_direction (me);
 
       scale_drul (&height, -ss * dir);
       scale_drul (&flare, ss);
       scale_drul (&shorten, ss);
 
 
       scale_drul (&height, -ss * dir);
       scale_drul (&flare, ss);
       scale_drul (&shorten, ss);
 
-      
+      Drul_array<bool> connect_to_other
+       = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
+                              Drul_array<bool> (false, false));
+
+      Direction d = LEFT;
       do
        {
       do
        {
-         if (bounds[d]->break_status_dir ())
+         if (connect_to_other[d])
            {
              height[d] = 0.0;
              flare[d] = 0.0;
              shorten[d] = 0.0;
            {
              height[d] = 0.0;
              flare[d] = 0.0;
              shorten[d] = 0.0;
+
+             SCM edge_text = me->get_property ("edge-text");
+
+             if (scm_is_pair (edge_text))
+               {
+                 SCM properties = Font_interface::text_font_alist_chain (me);
+                 SCM text = index_get_cell (edge_text, d);
+                 if (Text_interface::is_markup (text))
+                   {
+                     SCM t
+                       = Text_interface::interpret_markup (pap->self_scm (),
+                                                           properties, text);
+
+                     Stencil *edge_text = unsmob_stencil (t);
+                     edge_text->translate_axis (x_span[d] - x_span[LEFT],
+                                                X_AXIS);
+                     edge_stencils[d] = *edge_text;
+                   }
+               }
            }
        }
       while (flip (&d) != LEFT);
 
            }
        }
       while (flip (&d) != LEFT);
 
       Stencil brack = make_bracket (me, Y_AXIS,
       Stencil brack = make_bracket (me, Y_AXIS,
-                                   Offset (w, ry - ly),
+                                   points[RIGHT] - points[LEFT],
                                    height,
                                    /*
                                      0.1 = more space at right due to italics
                                    height,
                                    /*
                                      0.1 = more space at right due to italics
@@ -258,32 +400,15 @@ Tuplet_bracket::print (SCM smob)
 
       do
        {
 
       do
        {
-         if (bounds[d]->break_status_dir ())
-           {
-             SCM properties = Font_interface::text_font_alist_chain (me);
-             SCM edge_text = me->get_property ("edge-text");
-             
-             SCM text = index_get_cell (edge_text, d);
-             if (Text_interface::is_markup (text))
-               {
-                 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
-                                                           text);
-                 
-                 Stencil *edge_text = unsmob_stencil (t);
-                 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
-                 mol.add_stencil (*edge_text);
-               }
-           }
+         if (!edge_stencils[d].is_empty ())
+           brack.add_stencil (edge_stencils[d]);
        }
       while (flip (&d) != LEFT);
 
        }
       while (flip (&d) != LEFT);
 
-      
       mol.add_stencil (brack);
     }
 
       mol.add_stencil (brack);
     }
 
-  mol.translate_axis (ly, Y_AXIS);
-  mol.translate_axis (x_span[LEFT]
-                     - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
+  mol.translate (points[LEFT]);
   return mol.smobbed_copy ();
 }
 
   return mol.smobbed_copy ();
 }
 
@@ -295,7 +420,7 @@ Tuplet_bracket::print (SCM smob)
 */
 Stencil
 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
 */
 Stencil
 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
-                             Axis protusion_axis,
+                             Axis protrusion_axis,
                              Offset dz,
                              Drul_array<Real> height,
                              Interval gap,
                              Offset dz,
                              Drul_array<Real> height,
                              Interval gap,
@@ -307,30 +432,27 @@ Tuplet_bracket::make_bracket (Grob *me, // for line properties.
   Real length = dz.length ();
   Drul_array<Offset> gap_corners;
 
   Real length = dz.length ();
   Drul_array<Offset> gap_corners;
 
-  Axis bracket_axis = other_axis (protusion_axis);
+  Axis bracket_axis = other_axis (protrusion_axis);
 
   Drul_array<Offset> straight_corners = corners;
 
   Direction d = LEFT;
   do
 
   Drul_array<Offset> straight_corners = corners;
 
   Direction d = LEFT;
   do
-    {
-      straight_corners[d] += -d * shorten[d] / length * dz;
-    }
+    straight_corners[d] += -d * shorten[d] / length * dz;
   while (flip (&d) != LEFT);
 
   while (flip (&d) != LEFT);
 
-  if (gap.is_empty ())
-    gap = Interval (0, 0);
-  do
+  if (!gap.is_empty ())
     {
     {
-      gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
+      do
+       gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
+      while (flip (&d) != LEFT);
     }
     }
-  while (flip (&d) != LEFT);
 
   Drul_array<Offset> flare_corners = straight_corners;
   do
     {
       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
 
   Drul_array<Offset> flare_corners = straight_corners;
   do
     {
       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
-      flare_corners[d][protusion_axis] += height[d];
+      flare_corners[d][protrusion_axis] += height[d];
       straight_corners[d][bracket_axis] += -d * flare[d];
     }
   while (flip (&d) != LEFT);
       straight_corners[d][bracket_axis] += -d * flare[d];
     }
   while (flip (&d) != LEFT);
@@ -338,14 +460,20 @@ Tuplet_bracket::make_bracket (Grob *me, // for line properties.
   Stencil m;
   do
     {
   Stencil m;
   do
     {
-      m.add_stencil (Line_interface::line (me, straight_corners[d],
-                                          gap_corners[d]));
+      if (!gap.is_empty ())
+       m.add_stencil (Line_interface::line (me, straight_corners[d],
+                                            gap_corners[d]));
 
       m.add_stencil (Line_interface::line (me, straight_corners[d],
                                           flare_corners[d]));
     }
 
       m.add_stencil (Line_interface::line (me, straight_corners[d],
                                           flare_corners[d]));
     }
+
   while (flip (&d) != LEFT);
 
   while (flip (&d) != LEFT);
 
+  if (gap.is_empty ())
+    m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
+                                        straight_corners[RIGHT]));
+
   return m;
 }
 
   return m;
 }
 
@@ -353,157 +481,194 @@ void
 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
 {
   extract_grob_set (me, "note-columns", columns);
 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
 {
   extract_grob_set (me, "note-columns", columns);
-  int l = 0;
+  vsize l = 0;
   while (l < columns.size () && Note_column::has_rests (columns[l]))
     l++;
 
   while (l < columns.size () && Note_column::has_rests (columns[l]))
     l++;
 
-  int r = columns.size ()- 1;
-  while (r >= l && Note_column::has_rests (columns[r]))
+  vsize r = columns.size ();
+  while (r > l && Note_column::has_rests (columns[r - 1]))
     r--;
 
   *left = *right = 0;
 
     r--;
 
   *left = *right = 0;
 
-  if (l <= r)
+  if (l < r)
     {
       *left = columns[l];
     {
       *left = columns[l];
-      *right = columns[r];
+      *right = columns[r - 1];
     }
 }
 
     }
 }
 
-
 /*
   use first -> last note for slope, and then correct for disturbing
   notes in between.  */
 void
 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
 {
 /*
   use first -> last note for slope, and then correct for disturbing
   notes in between.  */
 void
 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
 {
-  Spanner *me = dynamic_cast<Spanner*> (me_grob);
-  
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
+
   extract_grob_set (me, "note-columns", columns);
   extract_grob_set (me, "tuplets", tuplets);
   extract_grob_set (me, "note-columns", columns);
   extract_grob_set (me, "tuplets", tuplets);
-  
+
   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
-  Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
-  commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
-  commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
-  commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
+  if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
+    commony = st->common_refpoint (commony, Y_AXIS);
+  Real my_offset = me->relative_coordinate (commony, Y_AXIS);
 
 
+  Grob *commonx = get_common_x (me);
+  commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
 
   Interval staff;
 
   Interval staff;
-  if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
-    staff = st->extent (commony, Y_AXIS);
+  Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
 
 
-  Direction dir = get_grob_direction (me);
-
-  /*
-    Use outer non-rest columns to determine slope
-  */
-  Grob *left_col = 0;
-  Grob *right_col = 0;
-  get_bounds (me, &left_col, &right_col);
-  if (left_col && right_col)
+  /* staff-padding doesn't work correctly on cross-staff tuplets
+     because it only considers one staff symbol. Until this works,
+     disable it. */
+  if (st && !to_boolean (me->get_property ("cross-staff")))
     {
     {
-      Interval rv = right_col->extent (commony, Y_AXIS);
-      Interval lv = left_col->extent (commony, Y_AXIS);
-      rv.unite (staff);
-      lv.unite (staff);
-      Real graphical_dy = rv[dir] - lv[dir];
-
-      Slice ls = Note_column::head_positions_interval (left_col);
-      Slice rs = Note_column::head_positions_interval (right_col);
-
-      Interval musical_dy;
-      musical_dy[UP] = rs[UP] - ls[UP];
-      musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
-      if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
-       *dy = 0.0;
-      else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
-       *dy = 0.0;
-      else
-       *dy = graphical_dy;
+      Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
+      if (pad >= 0.0)
+       {
+         staff = st->extent (commony, Y_AXIS) - my_offset;
+         staff.widen (pad);
+       }
     }
     }
-  else
-    *dy = 0;
 
 
-  *offset = -dir * infinity_f;
+  Direction dir = get_grob_direction (me);
 
 
-  if (!columns.size ())
-    return;
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
 
   Item *lgr = get_x_bound_item (me, LEFT, dir);
   Item *rgr = get_x_bound_item (me, RIGHT, dir);
   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
 
   Item *lgr = get_x_bound_item (me, LEFT, dir);
   Item *rgr = get_x_bound_item (me, RIGHT, dir);
   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
+  bool follow_beam = par_beam
+    && get_grob_direction (par_beam) == dir
+    && !to_boolean (par_beam->get_property ("knee"));
+
+  vector<Offset> points;
+  if (columns.size ()
+      && follow_beam
+      && Note_column::get_stem (columns[0])
+      && Note_column::get_stem (columns.back ()))
+    {
+      /*
+       trigger set_stem_ends
+      */
+      (void) par_beam->get_property ("quantized-positions");
 
 
-  /*
-    offset
-  */
-  Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
+      Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
+                               Note_column::get_stem (columns.back ()));
+
+      Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
+      Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
+       + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
+      Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
+       + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
 
 
-  Array<Offset> points;
-  for (int i = 0; i < columns.size (); i++)
+      *dy = rp - lp;
+      points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
+      points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
+    }
+  else
     {
     {
-      Interval note_ext = columns[i]->extent (commony, Y_AXIS);
-      note_ext.unite (staff);
-      Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
+      /*
+       Use outer non-rest columns to determine slope
+      */
+      Grob *left_col = 0;
+      Grob *right_col = 0;
+      get_bounds (me, &left_col, &right_col);
+      if (left_col && right_col)
+       {
+         Interval rv = Note_column::cross_staff_extent (right_col, commony);
+         Interval lv = Note_column::cross_staff_extent (left_col, commony);
+         rv.unite (staff);
+         lv.unite (staff);
+
+         Real graphical_dy = rv[dir] - lv[dir];
+
+         Slice ls = Note_column::head_positions_interval (left_col);
+         Slice rs = Note_column::head_positions_interval (right_col);
+
+         Interval musical_dy;
+         musical_dy[UP] = rs[UP] - ls[UP];
+         musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
+         if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
+           *dy = 0.0;
+         else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
+           *dy = 0.0;
+         else
+           *dy = graphical_dy;
+       }
+      else
+       *dy = 0;
+
+      for (vsize i = 0; i < columns.size (); i++)
+       {
+         Interval note_ext = Note_column::cross_staff_extent (columns[i],
+                                                              commony);
+         Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
 
 
-      Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
-      points.push (Offset (x, notey));
+         points.push_back (Offset (x, note_ext[dir]));
+       }
+    }
+
+  if (!follow_beam)
+    {
+      points.push_back (Offset (x0 - x0, staff[dir]));
+      points.push_back (Offset (x1 - x0, staff[dir]));
     }
     }
-  
+
   /*
     This is a slight hack. We compute two encompass points from the
     bbox of the smaller tuplets.
   /*
     This is a slight hack. We compute two encompass points from the
     bbox of the smaller tuplets.
-    
+
     We assume that the smaller bracket is 1.0 space high.
   */
     We assume that the smaller bracket is 1.0 space high.
   */
-  
   Real ss = Staff_symbol_referencer::staff_space (me);
   Real ss = Staff_symbol_referencer::staff_space (me);
-  for (int i = 0; i < tuplets.size (); i++)
+  for (vsize i = 0; i < tuplets.size (); i++)
     {
       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
 
     {
       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
 
+      if (!tuplets[i]->is_live ())
+       continue;
+
       Direction d = LEFT;
       Direction d = LEFT;
-      Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
-      Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
-      Real other_dy = rp - lp;
+      Drul_array<Real> positions
+       = robust_scm2interval (tuplets[i]->get_property ("positions"),
+                              Interval (0,0));
+
+      Real other_dy = positions[RIGHT] - positions[LEFT];
 
       do
        {
 
       do
        {
-         Real y =
-           tuplet_y.linear_combination (d * sign (other_dy));
+         Real y
+           tuplet_y.linear_combination (d * sign (other_dy));
 
 
-#if 0
          /*
          /*
-           Let's not take padding into account for nested tuplets.
+           We don't take padding into account for nested tuplets.
            the edges can come very close to the stems, likewise for
            nested tuplets?
            the edges can come very close to the stems, likewise for
            nested tuplets?
-          */
-         Drul_array<Real> my_height
-           = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
-         if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
-             ==  me->get_bound (d))
-           {
-             y += dir * my_height[d];
-           }
-#endif
-         
-         points.push (Offset (tuplet_x[d] - x0, y));
+         */
+
+         points.push_back (Offset (tuplet_x[d] - x0, y));
        }
       while (flip (&d) != LEFT);
     }
 
        }
       while (flip (&d) != LEFT);
     }
 
-  for (int i = 0; i < points.size (); i++)
+  *offset = -dir * infinity_f;
+  Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
+  for (vsize i = 0; i < points.size (); i++)
     {
       Real x = points[i][X_AXIS];
     {
       Real x = points[i][X_AXIS];
-      Real tuplety = *dy * x * factor;
+      Real tuplety = (*dy) * x * factor + my_offset;
 
       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
        *offset = points[i][Y_AXIS] - tuplety;
     }
 
       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
        *offset = points[i][Y_AXIS] - tuplety;
     }
-                 
+
   *offset += scm_to_double (me->get_property ("padding")) * dir;
 
   /*
   *offset += scm_to_double (me->get_property ("padding")) * dir;
 
   /*
@@ -512,116 +677,46 @@ Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
     Kind of pointless since we put them outside the staff anyway, but
     let's leave code for the future when possibly allow them to move
     into the staff once again.
     Kind of pointless since we put them outside the staff anyway, but
     let's leave code for the future when possibly allow them to move
     into the staff once again.
+
+    This doesn't seem to support cross-staff tuplets atm.
   */
   */
-  if (*dy == 0 &&
-      fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
+  if (*dy == 0
+      && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
     {
       // quantize, then do collision check.
       *offset *= 2 / ss;
 
       *offset = rint (*offset);
     {
       // quantize, then do collision check.
       *offset *= 2 / ss;
 
       *offset = rint (*offset);
-      if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
+      if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
        *offset += dir;
 
       *offset *= 0.5 * ss;
     }
 }
 
        *offset += dir;
 
       *offset *= 0.5 * ss;
     }
 }
 
-/*
-  We depend on the beams if there are any.
-*/
-MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
 SCM
 SCM
-Tuplet_bracket::before_line_breaking (SCM smob)
+Tuplet_bracket::calc_direction (SCM smob)
 {
   Grob *me = unsmob_grob (smob);
 {
   Grob *me = unsmob_grob (smob);
-  extract_grob_set (me, "note-columns", columns);
-
-  for (int i = columns.size (); i--;)
-    {
-      Grob *s = Note_column::get_stem (columns[i]);
-      Grob *b = s ? Stem::get_beam (s) : 0;
-      if (b)
-       me->add_dependency (b);
-    }
-  return SCM_UNSPECIFIED;
+  Direction dir = Tuplet_bracket::get_default_dir (me);
+  return scm_from_int (dir);
 }
 
 }
 
-MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
-
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
 SCM
 SCM
-Tuplet_bracket::after_line_breaking (SCM smob)
+Tuplet_bracket::calc_positions (SCM smob)
 {
 {
-  Grob *me = unsmob_grob (smob);
-  extract_grob_set (me, "note-columns", columns);
+  Spanner *me = unsmob_spanner (smob);
 
 
-  if (!columns.size ())
-    {
-      me->suicide ();
-      return SCM_UNSPECIFIED;
-    }
-  if (dynamic_cast<Spanner *> (me)->is_broken ())
-    {
-      me->warning (_ ("removing tuplet bracket across linebreak"));
-      me->suicide ();
-      return SCM_UNSPECIFIED;
-    }
-
-  Direction dir = get_grob_direction (me);
-  if (!dir)
-    {
-      dir = Tuplet_bracket::get_default_dir (me);
-      set_grob_direction (me, dir);
-    }
-
-  bool equally_long = false;
-  Grob *par_beam = parallel_beam (me, columns, &equally_long);
-
-  /*
-    We follow the beam only if there is one, and we are next to it.
-  */
   Real dy = 0.0;
   Real offset = 0.0;
   Real dy = 0.0;
   Real offset = 0.0;
-  if (!par_beam
-      || get_grob_direction (par_beam) != dir)
-    {
-      calc_position_and_height (me, &offset, &dy);
-    }
-  else
-    {
-      SCM ps = par_beam->get_property ("positions");
-
-      Real lp = scm_to_double (scm_car (ps));
-      Real rp = scm_to_double (scm_cdr (ps));
-
-      /*
-       duh. magic.
-      */
-      offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
-      dy = rp- lp;
-    }
+  calc_position_and_height (me, &offset, &dy);
 
 
-  SCM lp = me->get_property ("left-position");
-  SCM rp = me->get_property ("right-position");
+  SCM x = scm_cons (scm_from_double (offset),
+                   scm_from_double (offset + dy));
 
 
-  if (scm_is_number (lp) && !scm_is_number (rp))
-    {
-      rp = scm_from_double (scm_to_double (lp) + dy);
-    }
-  else if (scm_is_number (rp) && !scm_is_number (lp))
-    {
-      lp = scm_from_double (scm_to_double (rp) - dy);
-    }
-  else if (!scm_is_number (rp) && !scm_is_number (lp))
-    {
-      lp = scm_from_double (offset);
-      rp = scm_from_double (offset + dy);
-    }
-
-  me->set_property ("left-position", lp);
-  me->set_property ("right-position", rp);
-
-  return SCM_UNSPECIFIED;
+  return x;
 }
 
 /*
 }
 
 /*
@@ -632,7 +727,7 @@ Tuplet_bracket::get_default_dir (Grob *me)
 {
   Drul_array<int> dirs (0, 0);
   extract_grob_set (me, "note-columns", columns);
 {
   Drul_array<int> dirs (0, 0);
   extract_grob_set (me, "note-columns", columns);
-  for (int i = 0 ; i < columns.size (); i++)
+  for (vsize i = 0; i < columns.size (); i++)
     {
       Grob *nc = columns[i];
       Direction d = Note_column::dir (nc);
     {
       Grob *nc = columns[i];
       Direction d = Note_column::dir (nc);
@@ -647,8 +742,6 @@ void
 Tuplet_bracket::add_column (Grob *me, Item *n)
 {
   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
 Tuplet_bracket::add_column (Grob *me, Item *n)
 {
   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
-  me->add_dependency (n);
-
   add_bound_item (dynamic_cast<Spanner *> (me), n);
 }
 
   add_bound_item (dynamic_cast<Spanner *> (me), n);
 }
 
@@ -656,22 +749,64 @@ void
 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
 {
   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
 {
   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
-  me->add_dependency (bracket);
 }
 
 }
 
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
+SCM
+Tuplet_bracket::calc_cross_staff (SCM smob)
+{
+  Grob *me = unsmob_grob (smob);
+  extract_grob_set (me, "note-columns", cols);
+  extract_grob_set (me, "tuplets", tuplets);
 
 
+  Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
+  commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
+  if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
+    commony = st->common_refpoint (commony, Y_AXIS);
+  if (me->check_cross_staff (commony))
+    return SCM_BOOL_T;
 
 
-ADD_INTERFACE (Tuplet_bracket,
-              "tuplet-bracket-interface",
-              "A bracket with a number in the middle, used for tuplets. "
-              "When the bracket spans  a line break, the value of "
-              "@code{break-overshoot} determines how far it extends "
-              "beyond the staff. "
-              "At a line break, the markups in the @code{edge-text} are printed "
-              "at the edges. ",
-
-              "note-columns bracket-flare edge-height shorten-pair "
-              "tuplets edge-text break-overshoot "
-              "padding left-position right-position bracket-visibility "
-              "number-visibility thickness direction");
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, cols, &equally_long);
 
 
+  if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
+    return SCM_BOOL_T;
+
+  for (vsize i = 0; i < cols.size (); i++)
+    {
+      Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
+      if (stem && to_boolean (stem->get_property ("cross-staff")))
+       return SCM_BOOL_T;
+    }
+
+  return SCM_BOOL_F;
+}
+
+ADD_INTERFACE (Tuplet_bracket,
+              "A bracket with a number in the middle, used for tuplets."
+              "  When the bracket spans a line break, the value of"
+              " @code{break-overshoot} determines how far it extends"
+              " beyond the staff.  At a line break, the markups in the"
+              " @code{edge-text} are printed at the edges.",
+
+              /* properties */
+              "bracket-flare "
+              "bracket-visibility "
+              "break-overshoot "
+              "connect-to-neighbor "
+              "control-points "
+              "direction "
+              "edge-height "
+              "edge-text "
+              "full-length-padding "
+              "full-length-to-extent "
+              "gap "
+              "positions "
+              "note-columns "
+              "padding "
+              "tuplet-number "
+              "shorten-pair "
+              "staff-padding "
+              "thickness "
+              "tuplets "
+              );