-/*
- staff-side.cc -- implement Staff_side_element
-
- source file of the GNU LilyPond music typesetter
-
- (c) 1998--1999 Han-Wen Nienhuys <hanwen@cs.uu.nl>
-
- */
-#include <math.h> // ceil.
+/*
+ This file is part of LilyPond, the GNU music typesetter.
+
+ Copyright (C) 1998--2010 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/>.
+*/
#include "side-position-interface.hh"
-#include "staff-symbol.hh"
-#include "debug.hh"
-#include "warn.hh"
-#include "dimensions.hh"
-#include "dimension-cache.hh"
-#include "staff-symbol-referencer.hh"
-Side_position_interface::Side_position_interface (Score_element const *e)
-{
- elt_l_ = (Score_element*)e;
-}
+#include <cmath> // ceil.
+#include <algorithm>
+
+using namespace std;
+#include "axis-group-interface.hh"
+#include "directional-element-interface.hh"
+#include "grob.hh"
+#include "grob-array.hh"
+#include "main.hh"
+#include "misc.hh"
+#include "note-head.hh"
+#include "pointer-group-interface.hh"
+#include "staff-symbol-referencer.hh"
+#include "staff-symbol.hh"
+#include "stem.hh"
+#include "string-convert.hh"
+#include "system.hh"
+#include "warn.hh"
void
-Side_position_interface::add_support (Score_element*e)
+Side_position_interface::add_support (Grob *me, Grob *e)
{
- SCM sup = elt_l_->get_elt_property ("side-support");
- elt_l_->set_elt_property ("side-support",
- gh_cons (e->self_scm_,sup));
+ Pointer_group_interface::add_unordered_grob (me, ly_symbol2scm ("side-support-elements"), e);
}
+/* Put the element next to the support, optionally taking in
+ account the extent of the support.
-
-Direction
-Side_position_interface::get_direction () const
+ Does not take into account the extent of ME.
+*/
+SCM
+Side_position_interface::general_side_position (Grob *me, Axis a, bool use_extents,
+ bool include_my_extent,
+ bool pure, int start, int end,
+ Real *current_offset)
{
- SCM d = elt_l_->get_elt_property ("direction");
- if (isdir_b (d))
- return to_dir (d) ? to_dir (d) : DOWN;
+ Real ss = Staff_symbol_referencer::staff_space (me);
- Direction relative_dir = UP;
- SCM reldir = elt_l_->get_elt_property ("side-relative-direction"); // should use a lambda.
- if (isdir_b (reldir))
- {
- relative_dir = to_dir (reldir);
- }
-
- SCM other_elt = elt_l_->get_elt_property ("direction-source");
- Score_element * e = unsmob_element(other_elt);
- if (e)
- {
- return (Direction)(relative_dir * Side_position_interface (e).get_direction ());
- }
-
- return DOWN;
-}
-
-/**
- Callback that does the aligning.
- */
-Real
-Side_position_interface::side_position (Dimension_cache const * c)
-{
- Score_element * me = dynamic_cast<Score_element*> (c->element_l ());
+ extract_grob_set (me, "side-support-elements", support);
+
+ Grob *common = common_refpoint_of_array (support, me->get_parent (a), a);
+ Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
+ bool include_staff =
+ staff_symbol
+ && a == Y_AXIS
+ && scm_is_number (me->get_property ("staff-padding"))
+ && !to_boolean (me->get_property ("quantize-position"));
Interval dim;
- Axis axis = c->axis ();
- Score_element *common = me->parent_l (axis);
- SCM support = me->get_elt_property ("side-support");
- for (SCM s = support; s != SCM_EOL; s = gh_cdr (s))
+ Interval staff_extents;
+ if (include_staff)
{
- Score_element * e = unsmob_element ( gh_car (s));
- if (e)
- common = common->common_refpoint (e, axis);
+ common = staff_symbol->common_refpoint (common, Y_AXIS);
+ staff_extents = staff_symbol->maybe_pure_extent (common, Y_AXIS, pure, start, end);
+
+ if (include_staff)
+ dim.unite (staff_extents);
}
-
- for (SCM s = support; s != SCM_EOL; s = gh_cdr (s))
- {
- Score_element * e = unsmob_element ( gh_car (s));
+ Direction dir = get_grob_direction (me);
+
+ for (vsize i = 0; i < support.size (); i++)
+ {
+ Grob *e = support[i];
+
+ // In the case of a stem, we will find a note head as well
+ // ignoring the stem solves cyclic dependencies if the stem is
+ // attached to a cross-staff beam.
+ if (a == Y_AXIS
+ && Stem::has_interface (e)
+ && dir == - get_grob_direction (e))
+ continue;
+
if (e)
{
- Real coord = e->relative_coordinate (common, axis);
-
- dim.unite (coord + e->extent (axis));
+ if (use_extents)
+ dim.unite (e->maybe_pure_extent (common, a, pure, start, end));
+ else
+ {
+ Real x = e->maybe_pure_coordinate (common, a, pure, start, end);
+ dim.unite (Interval (x, x));
+ }
}
}
- if (dim.empty_b ())
- {
- dim = Interval(0,0);
- }
-
- Real off = me->parent_l (axis)->relative_coordinate (common, axis);
+ if (dim.is_empty ())
+ dim = Interval (0, 0);
+ Real off = me->get_parent (a)->maybe_pure_coordinate (common, a, pure, start, end);
+ Real minimum_space = ss * robust_scm2double (me->get_property ("minimum-space"), -1);
- Direction dir = Side_position_interface (me).get_direction ();
-
- SCM pad = me->remove_elt_property ("padding");
- if (gh_number_p (pad))
- {
- off += gh_scm2double (pad) * dir;
- }
- Real total_off = dim[dir] + off;
+ Real total_off = dim.linear_combination (dir) - off;
+ total_off += dir * ss * robust_scm2double (me->get_property ("padding"), 0);
- if (fabs (total_off) > 100 CM)
- programming_error ("Huh ? Improbable staff side dim.");
+ if (minimum_space >= 0
+ && dir
+ && total_off * dir < minimum_space)
+ total_off = minimum_space * dir;
- return total_off;
-}
-
-Real
-Side_position_interface::self_alignment (Dimension_cache const *c)
-{
- String s ("self-alignment-");
- Axis ax = c->axis ();
- s += (ax == X_AXIS) ? "X" : "Y";
- Score_element *elm = dynamic_cast<Score_element*> (c->element_l ());
- SCM align (elm->get_elt_property (s));
- if (isdir_b (align))
+ if (include_my_extent)
{
- Direction d = to_dir (align);
- Interval ext(elm->extent (ax));
- if (d)
+ Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
+ if (!iv.is_empty ())
{
- return - ext[d];
+ if (!dir)
+ {
+ programming_error ("direction unknown, but aligned-side wanted");
+ dir = DOWN;
+ }
+ total_off += -iv[-dir];
}
- return - ext.center ();
}
- else
- return 0.0;
+
+ if (current_offset)
+ total_off = dir * max (dir * total_off,
+ dir * (*current_offset));
+
+
+ /* FIXME: 1000 should relate to paper size. */
+ if (fabs (total_off) > 1000)
+ {
+ string msg
+ = String_convert::form_string ("Improbable offset for grob %s: %f",
+ me->name ().c_str (), total_off);
+
+ programming_error (msg);
+ if (strict_infinity_checking)
+ scm_misc_error (__FUNCTION__, "Improbable offset.", SCM_EOL);
+ }
+ return scm_from_double (total_off);
}
-Real
-directed_round (Real f, Direction d)
+MAKE_SCHEME_CALLBACK (Side_position_interface, y_aligned_on_support_refpoints, 1);
+SCM
+Side_position_interface::y_aligned_on_support_refpoints (SCM smob)
{
- if (d < 0)
- return floor (f);
- else
- return ceil (f);
+ return general_side_position (unsmob_grob (smob), Y_AXIS, false, false, false, 0, 0, 0);
}
-Real
-Side_position_interface::quantised_position (Dimension_cache const *c)
+MAKE_SCHEME_CALLBACK (Side_position_interface, pure_y_aligned_on_support_refpoints, 3);
+SCM
+Side_position_interface::pure_y_aligned_on_support_refpoints (SCM smob, SCM start, SCM end)
{
- Score_element * me = dynamic_cast<Score_element*> (c->element_l ());
- Side_position_interface s(me);
- Direction d = s.get_direction ();
- Staff_symbol_referencer_interface si (me);
+ return general_side_position (unsmob_grob (smob), Y_AXIS, false, false,
+ true, scm_to_int (start), scm_to_int (end), 0);
+}
+
- if (si.has_interface_b ())
+/*
+ Position next to support, taking into account my own dimensions and padding.
+*/
+SCM
+axis_aligned_side_helper (SCM smob, Axis a, bool pure, int start, int end, SCM current_off_scm)
+{
+ Real r;
+ Real *current_off_ptr = 0;
+ if (scm_is_number (current_off_scm))
{
- Real p = si.position_f ();
- Real rp = directed_round (p, d);
+ r = scm_to_double (current_off_scm);
+ current_off_ptr = &r;
+ }
+
+ return Side_position_interface::aligned_side (unsmob_grob (smob), a, pure, start, end, current_off_ptr);
+}
- int ip = int (rp);
- if ((ip % 2) == 0)
- {
- ip += d;
- rp += d;
- }
- return (rp - p) * si.staff_space () / 2.0;
- }
- return 0.0;
+MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, x_aligned_side, 2, 1, "");
+SCM
+Side_position_interface::x_aligned_side (SCM smob, SCM current_off)
+{
+ return axis_aligned_side_helper (smob, X_AXIS, false, 0, 0, current_off);
}
-Real
-Side_position_interface::aligned_side (Dimension_cache const *c)
+MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, y_aligned_side, 2, 1, "");
+SCM
+Side_position_interface::y_aligned_side (SCM smob, SCM current_off)
{
- Score_element * me = dynamic_cast<Score_element*> (c->element_l ());
- Side_position_interface s(me);
- Direction d = s.get_direction ();
- Axis ax = c->axis ();
- Real o = side_position (c);
+ return axis_aligned_side_helper (smob, Y_AXIS, false, 0, 0, current_off);
+}
- Interval iv = me->extent (ax);
+MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, pure_y_aligned_side, 4, 1, "");
+SCM
+Side_position_interface::pure_y_aligned_side (SCM smob, SCM start, SCM end, SCM cur_off)
+{
+ return axis_aligned_side_helper (smob, Y_AXIS, true,
+ scm_to_int (start),
+ scm_to_int (end),
+ cur_off);
+}
- if (!iv.empty_b ())
- {
- o += - iv[-d];
+MAKE_SCHEME_CALLBACK (Side_position_interface, calc_cross_staff, 1)
+SCM
+Side_position_interface::calc_cross_staff (SCM smob)
+{
+ Grob *me = unsmob_grob (smob);
+ extract_grob_set (me, "side-support-elements", elts);
- SCM pad = me->get_elt_property ("padding");
- if (gh_number_p (pad))
- o += d *gh_scm2double (pad) ;
- }
- return o;
+ for (vsize i = 0; i < elts.size (); i++)
+ if (to_boolean (elts[i]->get_property ("cross-staff")))
+ return SCM_BOOL_T;
+
+ Grob *common = common_refpoint_of_array (elts, me->get_parent (Y_AXIS), Y_AXIS);
+ return scm_from_bool (common != me->get_parent (Y_AXIS));
}
+SCM
+Side_position_interface::aligned_side (Grob *me, Axis a, bool pure, int start, int end,
+ Real *current_off)
+{
+ Direction dir = get_grob_direction (me);
+ Real o = scm_to_double (general_side_position (me, a, true, true, pure, start, end, current_off));
+ /*
+ Maintain a minimum distance to the staff. This is similar to side
+ position with padding, but it will put adjoining objects on a row if
+ stuff sticks out of the staff a little.
+ */
+ Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
+ if (staff && a == Y_AXIS)
+ {
+ if (to_boolean (me->get_property ("quantize-position")))
+ {
+ Grob *common = me->common_refpoint (staff, Y_AXIS);
+ Real my_off = me->get_parent (Y_AXIS)->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
+ Real staff_off = staff->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
+ Real ss = Staff_symbol::staff_space (staff);
+ Real position = 2 * (my_off + o - staff_off) / ss;
+ Real rounded = directed_round (position, dir);
+ Grob *head = me->get_parent (X_AXIS);
+
+ if (fabs (position) <= 2 * Staff_symbol_referencer::staff_radius (me) + 1
+ /* In case of a ledger lines, quantize even if we're outside the staff. */
+ || (Note_head::has_interface (head)
+
+ && abs (Staff_symbol_referencer::get_position (head)) > abs (position)))
+ {
+ o += (rounded - position) * 0.5 * ss;
+ if (Staff_symbol_referencer::on_line (me, int (rounded)))
+ o += dir * 0.5 * ss;
+ }
+ }
+ else if (scm_is_number (me->get_property ("staff-padding")) && dir)
+ {
+ Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
+
+ Real padding
+ = Staff_symbol_referencer::staff_space (me)
+ * scm_to_double (me->get_property ("staff-padding"));
-void
-Side_position_interface::set_axis (Axis a)
-{
- // prop transparent ?
- if (elt_l_->get_elt_property ("side-support") == SCM_UNDEFINED)
- elt_l_->set_elt_property ("side-support" ,SCM_EOL);
+ Grob *common = me->common_refpoint (staff, Y_AXIS);
- elt_l_->dim_cache_[a]->off_callbacks_.push (aligned_side);
+ Interval staff_size = staff->maybe_pure_extent (common, Y_AXIS, pure, start, end);
+ Real diff = dir*staff_size[dir] + padding - dir * (o + iv[-dir]);
+ o += dir * max (diff, 0.0);
+ }
+ }
+ return scm_from_double (o);
}
-
void
-Side_position_interface::set_quantised (Axis a)
+Side_position_interface::set_axis (Grob *me, Axis a)
{
- Dimension_cache * c = elt_l_->dim_cache_[a];
-
- c->off_callbacks_.push (quantised_position);
+ if (!scm_is_number (me->get_property ("side-axis")))
+ {
+ me->set_property ("side-axis", scm_from_int (a));
+ chain_offset_callback (me,
+ (a==X_AXIS)
+ ? x_aligned_side_proc
+ : y_aligned_side_proc,
+ a);
+ }
}
Axis
-Side_position_interface::get_axis () const
+Side_position_interface::get_axis (Grob *me)
{
- Dimension_cache * c = elt_l_->dim_cache_[X_AXIS];
- for (int i=0 ; i < c->off_callbacks_.size();i ++)
- if (c->off_callbacks_[i] == side_position
- ||c->off_callbacks_[i] == aligned_side)
- return X_AXIS;
-
+ if (scm_is_number (me->get_property ("side-axis")))
+ return Axis (scm_to_int (me->get_property ("side-axis")));
- return Y_AXIS;
+ string msg = String_convert::form_string ("side-axis not set for grob %s.",
+ me->name ().c_str ());
+ me->programming_error (msg);
+ return NO_AXES;
}
-void
-Side_position_interface::set_direction (Direction d)
+MAKE_SCHEME_CALLBACK (Side_position_interface, move_to_extremal_staff, 1);
+SCM
+Side_position_interface::move_to_extremal_staff (SCM smob)
{
- elt_l_->set_elt_property ("direction", gh_int2scm (d));
+ Grob *me = unsmob_grob (smob);
+ System *sys = dynamic_cast<System*> (me->get_system ());
+ Direction dir = get_grob_direction (me);
+ if (dir != DOWN)
+ dir = UP;
+
+ Interval iv = me->extent (sys, X_AXIS);
+ iv.widen (1.0);
+ Grob *top_staff = sys->get_extremal_staff (dir, iv);
+
+ if (!top_staff)
+ return SCM_BOOL_F;
+
+ // Only move this grob if it is a direct child of the system. We
+ // are not interested in moving marks from other staves to the top
+ // staff; we only want to move marks from the system to the top
+ // staff.
+ if (sys != me->get_parent (Y_AXIS))
+ return SCM_BOOL_F;
+
+ me->set_parent (top_staff, Y_AXIS);
+ me->flush_extent_cache (Y_AXIS);
+ Axis_group_interface::add_element (top_staff, me);
+
+ // Remove any cross-staff side-support dependencies
+ Grob_array *ga = unsmob_grob_array (me->get_object ("side-support-elements"));
+ if (ga)
+ {
+ vector<Grob*> const& elts = ga->array ();
+ vector<Grob*> new_elts;
+ for (vsize i = 0; i < elts.size (); ++i)
+ {
+ if (me->common_refpoint (elts[i], Y_AXIS) == top_staff)
+ new_elts.push_back (elts[i]);
+ }
+ ga->set_array (new_elts);
+ }
+ return SCM_BOOL_T;
}
-bool
-Side_position_interface::has_interface_b () const
-{
- return elt_l_->get_elt_property ("side-support") != SCM_UNDEFINED;
-}
-bool
-Side_position_interface::supported_b () const
-{
- SCM s =elt_l_->get_elt_property ("side-support");
- return s != SCM_UNDEFINED && s != SCM_EOL;
-}
+ADD_INTERFACE (Side_position_interface,
+ "Position a victim object (this one) next to other objects"
+ " (the support). The property @code{direction} signifies where"
+ " to put the victim object relative to the support (left or"
+ " right, up or down?)\n"
+ "\n"
+ "The routine also takes the size of the staff into account if"
+ " @code{staff-padding} is set. If undefined, the staff symbol"
+ " is ignored.",
+
+ /* properties */
+ "direction "
+ "minimum-space "
+ "padding "
+ "quantize-position "
+ "side-axis "
+ "side-support-elements "
+ "slur-padding "
+ "staff-padding "
+ );