2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
13 #include "align-interface.hh"
15 #include "international.hh"
20 #include "output-def.hh"
21 #include "pointer-group-interface.hh"
23 #include "stream-event.hh"
27 #include "ly-smobs.icc"
32 return new Grob (*this);
35 Grob::Grob (SCM basicprops)
38 /* FIXME: default should be no callback. */
42 interfaces_ = SCM_EOL;
43 immutable_property_alist_ = basicprops;
44 mutable_property_alist_ = SCM_EOL;
45 object_alist_ = SCM_EOL;
47 /* We do smobify_self () as the first step. Since the object lives
48 on the heap, none of its SCM variables are protected from
49 GC. After smobify_self (), they are. */
52 SCM meta = get_property ("meta");
53 if (scm_is_pair (meta))
55 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
57 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
58 if (scm_is_pair (object_cbs))
60 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
61 set_object (scm_caar (s), scm_cdar (s));
65 if (get_property_data ("X-extent") == SCM_EOL)
66 set_property ("X-extent", Grob::stencil_width_proc);
67 if (get_property_data ("Y-extent") == SCM_EOL)
68 set_property ("Y-extent", Grob::stencil_height_proc);
71 Grob::Grob (Grob const &s)
72 : dim_cache_ (s.dim_cache_)
74 original_ = (Grob *) & s;
77 immutable_property_alist_ = s.immutable_property_alist_;
78 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
79 interfaces_ = s.interfaces_;
80 object_alist_ = SCM_EOL;
90 /****************************************************************
92 ****************************************************************/
95 Grob::get_stencil () const
100 SCM stil = get_property ("stencil");
101 return unsmob_stencil (stil);
105 Grob::get_print_stencil () const
107 SCM stil = get_property ("stencil");
110 if (Stencil *m = unsmob_stencil (stil))
113 if (to_boolean (get_property ("transparent")))
114 retval = Stencil (m->extent_box (), SCM_EOL);
117 SCM expr = m->expr ();
118 expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
121 retval = Stencil (m->extent_box (), expr);
124 SCM rot = get_property ("rotation");
125 if (scm_is_pair (rot))
127 Real angle = scm_to_double (scm_car (rot));
128 Real x = scm_to_double (scm_cadr (rot));
129 Real y = scm_to_double (scm_caddr (rot));
131 retval.rotate_degrees (angle, Offset (x, y));
134 /* color support... see interpret_stencil_expression () for more... */
135 SCM color = get_property ("color");
136 if (scm_is_pair (color))
138 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
142 retval = Stencil (retval.extent_box (), expr);
150 /****************************************************************
152 ****************************************************************/
154 Grob::do_break_processing ()
159 Grob::discretionary_processing ()
164 Grob::get_system () const
171 Grob::handle_broken_dependencies ()
173 Spanner *sp = dynamic_cast<Spanner *> (this);
174 if (original () && sp)
178 /* THIS, SP is the original spanner. We use a special function
179 because some Spanners have enormously long lists in their
180 properties, and a special function fixes FOO */
182 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
183 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
185 System *system = get_system ();
189 && common_refpoint (system, X_AXIS)
190 && common_refpoint (system, Y_AXIS))
191 substitute_object_links (system->self_scm (), object_alist_);
192 else if (dynamic_cast<System *> (this))
193 substitute_object_links (SCM_UNDEFINED, object_alist_);
195 /* THIS element is `invalid'; it has been removed from all
196 dependencies, so let's junk the element itself.
198 Do not do this for System, since that would remove references
199 to the originals of score-grobs, which get then GC'd (a bad
204 /* Note that we still want references to this element to be
205 rearranged, and not silently thrown away, so we keep pointers like
206 {broken_into_{drul, array}, original}
214 for (int a = X_AXIS; a < NO_AXES; a++)
215 dim_cache_[a].clear ();
217 mutable_property_alist_ = SCM_EOL;
218 object_alist_ = SCM_EOL;
219 immutable_property_alist_ = SCM_EOL;
220 interfaces_ = SCM_EOL;
224 Grob::handle_prebroken_dependencies ()
226 /* Don't do this in the derived method, since we want to keep access to
227 object_alist_ centralized. */
230 Item *it = dynamic_cast<Item *> (this);
231 substitute_object_links (scm_from_int (it->break_status_dir ()),
232 original ()->object_alist_);
237 Grob::find_broken_piece (System *) const
242 /****************************************************************
244 ****************************************************************/
247 Grob::translate_axis (Real y, Axis a)
249 if (isinf (y) || isnan (y))
251 programming_error (_ ("Infinity or NaN encountered"));
255 if (!dim_cache_[a].offset_)
256 dim_cache_[a].offset_ = new Real (y);
258 *dim_cache_[a].offset_ += y;
261 /* Find the offset relative to D. If D equals THIS, then it is 0.
262 Otherwise, it recursively defd as
264 OFFSET_ + PARENT_L_->relative_coordinate (D) */
266 Grob::relative_coordinate (Grob const *refp, Axis a) const
271 /* We catch PARENT_L_ == nil case with this, but we crash if we did
272 not ask for the absolute coordinate (ie. REFP == nil.) */
273 Real off = get_offset (a);
274 if (refp == dim_cache_[a].parent_)
277 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
283 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
290 if (dim_cache_[Y_AXIS].offset_)
292 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
293 programming_error ("cyclic chain in pure-Y-offset callbacks");
295 off = *dim_cache_[Y_AXIS].offset_;
299 SCM proc = get_property_data ("Y-offset");
301 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
302 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
303 off = robust_scm2double (call_pure_function (proc,
304 scm_list_1 (self_scm ()),
307 del_property ("pure-Y-offset-in-progress");
308 delete dim_cache_[Y_AXIS].offset_;
309 dim_cache_[Y_AXIS].offset_ = 0;
312 /* we simulate positioning-done if we are the child of a VerticalAlignment,
313 but only if we don't have a cached offset. If we do have a cached offset,
314 it probably means that the Alignment was fixed and it has already been
317 if (Grob *p = get_parent (Y_AXIS))
320 if (Align_interface::has_interface (p) && !dim_cache_[Y_AXIS].offset_)
321 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
323 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
328 /* Invoke callbacks to get offset relative to parent. */
330 Grob::get_offset (Axis a) const
332 if (dim_cache_[a].offset_)
333 return *dim_cache_[a].offset_;
335 Grob *me = (Grob *) this;
337 SCM sym = axis_offset_symbol (a);
338 me->dim_cache_[a].offset_ = new Real (0.0);
341 UGH: can't fold next 2 statements together. Apparently GCC thinks
342 dim_cache_[a].offset_ is unaliased.
344 Real off = robust_scm2double (internal_get_property (sym), 0.0);
345 if (me->dim_cache_[a].offset_)
347 *me->dim_cache_[a].offset_ += off;
348 me->del_property (sym);
349 return *me->dim_cache_[a].offset_;
356 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
358 if (pure && a != Y_AXIS)
359 programming_error ("tried to get pure X-offset");
360 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
361 : relative_coordinate (refp, a);
364 /****************************************************************
366 ****************************************************************/
369 Grob::flush_extent_cache (Axis axis)
371 if (dim_cache_[axis].extent_)
374 Ugh, this is not accurate; will flush property, causing
375 callback to be called if.
377 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
378 delete dim_cache_[axis].extent_;
379 dim_cache_[axis].extent_ = 0;
380 if (get_parent (axis))
381 get_parent (axis)->flush_extent_cache (axis);
387 Grob::extent (Grob *refp, Axis a) const
389 Real offset = relative_coordinate (refp, a);
391 if (dim_cache_[a].extent_)
393 real_ext = *dim_cache_[a].extent_;
398 Order is significant: ?-extent may trigger suicide.
402 ? ly_symbol2scm ("X-extent")
403 : ly_symbol2scm ("Y-extent");
405 SCM ext = internal_get_property (ext_sym);
406 if (is_number_pair (ext))
407 real_ext.unite (ly_scm2interval (ext));
411 ? ly_symbol2scm ("minimum-X-extent")
412 : ly_symbol2scm ("minimum-Y-extent");
413 SCM min_ext = internal_get_property (min_ext_sym);
414 if (is_number_pair (min_ext))
415 real_ext.unite (ly_scm2interval (min_ext));
417 ((Grob*)this)->dim_cache_[a].extent_ = new Interval (real_ext);
420 real_ext.translate (offset);
426 Grob::pure_height (Grob *refp, int start, int end)
428 SCM proc = get_property_data (ly_symbol2scm ("Y-extent"));
429 SCM iv_scm = call_pure_function (proc,
430 scm_list_1 (self_scm ()),
432 Interval iv = robust_scm2interval (iv_scm, Interval (0, 0));
433 Real offset = pure_relative_y_coordinate (refp, start, end);
435 SCM min_ext = get_property ("minimum-Y-extent");
437 /* we don't add minimum-Y-extent if the extent is empty. This solves
438 a problem with Hara-kiri spanners. They would request_suicide and
439 return empty extents, but we would force them here to be large. */
440 if (!iv.is_empty () && is_number_pair (min_ext))
441 iv.unite (ly_scm2interval (min_ext));
444 iv.translate (offset);
449 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
451 if (pure && a != Y_AXIS)
452 programming_error ("tried to get pure width");
453 return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
457 Grob::spanned_rank_interval () const
459 return Interval_t<int> (-1, 0);
462 /****************************************************************
464 ****************************************************************/
466 /* Find the group-element which has both #this# and #s# */
468 Grob::common_refpoint (Grob const *s, Axis a) const
470 /* I don't like the quadratic aspect of this code, but I see no
471 other way. The largest chain of parents might be 10 high or so,
472 so it shouldn't be a real issue. */
473 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
474 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
482 Grob::set_parent (Grob *g, Axis a)
484 dim_cache_[a].parent_ = g;
488 Grob::get_parent (Axis a) const
490 return dim_cache_[a].parent_;
495 Grob::fixup_refpoint ()
497 for (int a = X_AXIS; a < NO_AXES; a++)
500 Grob *parent = get_parent (ax);
505 if (parent->get_system () != get_system () && get_system ())
507 Grob *newparent = parent->find_broken_piece (get_system ());
508 set_parent (newparent, ax);
511 if (Item *i = dynamic_cast<Item *> (this))
513 Item *parenti = dynamic_cast<Item *> (parent);
517 Direction my_dir = i->break_status_dir ();
518 if (my_dir != parenti->break_status_dir ())
520 Item *newparent = parenti->find_prebroken_piece (my_dir);
521 set_parent (newparent, ax);
529 /****************************************************************
531 ****************************************************************/
533 Grob::warning (string s) const
535 SCM cause = self_scm ();
536 while (Grob *g = unsmob_grob (cause))
537 cause = g->get_property ("cause");
539 /* ES TODO: cause can't be Music*/
540 if (Music *m = unsmob_music (cause))
541 m->origin ()->warning (s);
542 else if (Stream_event *ev = unsmob_stream_event (cause))
543 ev->origin ()->warning (s);
552 SCM meta = get_property ("meta");
553 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
554 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
555 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
559 Grob::programming_error (string s) const
561 SCM cause = self_scm ();
562 while (Grob *g = unsmob_grob (cause))
563 cause = g->get_property ("cause");
565 s = _f ("programming error: %s", s);
567 /* ES TODO: cause can't be Music*/
568 if (Music *m = unsmob_music (cause))
569 m->origin ()->message (s);
570 else if (Stream_event *ev = unsmob_stream_event (cause))
571 ev->origin ()->warning (s);
578 "A grob represents a piece of music notation\n"
580 "All grobs have an X and Y-position on the page. These X and Y positions\n"
581 "are stored in a relative format, so they can easily be combined by\n"
582 "stacking them, hanging one grob to the side of another, and coupling\n"
583 "them into a grouping objects.\n"
585 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
586 "is stored relative to that reference point. For example the X-reference\n"
587 "point of a staccato dot usually is the note head that it applies\n"
588 "to. When the note head is moved, the staccato dot moves along\n"
591 "A grob is often associated with a symbol, but some grobs do not print\n"
592 "any symbols. They take care of grouping objects. For example, there is a\n"
593 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
594 "is also an abstract grob: it only moves around chords, but doesn't print\n"
597 "Grobs have a properties: Scheme variables, that can be read and set. "
598 "They have two types. Immutable variables "
599 "define the default style and behavior. They are shared between many objects. "
600 "They can be changed using @code{\\override} and @code{\\revert}. "
602 "Mutable properties are variables that are specific to one grob. Typically, "
603 "lists of other objects, or results from computations are stored in"
604 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
605 "sets a mutable property. "
607 "The properties @code{after-line-breaking} and @code{before-line-breaking} "
608 "are dummies that are not user-serviceable. "
617 "after-line-breaking "
619 "axis-group-parent-X "
620 "axis-group-parent-Y "
621 "before-line-breaking "
633 "outside-staff-horizontal-padding "
634 "outside-staff-padding "
635 "outside-staff-priority "
636 "pure-Y-offset-in-progress "
644 /****************************************************************
646 ****************************************************************/
649 grob_stencil_extent (Grob *me, Axis a)
651 Stencil *m = me->get_stencil ();
655 return ly_interval2scm (e);
659 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
661 Grob::stencil_height (SCM smob)
663 Grob *me = unsmob_grob (smob);
664 return grob_stencil_extent (me, Y_AXIS);
667 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
669 Grob::y_parent_positioning (SCM smob)
671 Grob *me = unsmob_grob (smob);
672 Grob *par = me->get_parent (Y_AXIS);
674 (void) par->get_property ("positioning-done");
676 return scm_from_double (0.0);
680 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
682 Grob::x_parent_positioning (SCM smob)
684 Grob *me = unsmob_grob (smob);
686 Grob *par = me->get_parent (X_AXIS);
688 (void) par->get_property ("positioning-done");
690 return scm_from_double (0.0);
693 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
695 Grob::stencil_width (SCM smob)
697 Grob *me = unsmob_grob (smob);
698 return grob_stencil_extent (me, X_AXIS);
704 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
706 for (; scm_is_pair (elist); elist = scm_cdr (elist))
707 if (Grob *s = unsmob_grob (scm_car (elist)))
710 common = common->common_refpoint (s, a);
719 common_refpoint_of_array (vector<Grob*> const &arr, Grob *common, Axis a)
721 for (vsize i = arr.size (); i--;)
722 if (Grob *s = arr[i])
725 common = common->common_refpoint (s, a);
734 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
736 Interval ext = me->extent (refpoint, a);
738 ext.add_point (me->relative_coordinate (refpoint, a));