2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
15 #include "input-smob.hh"
17 #include "pointer-group-interface.hh"
19 #include "paper-score.hh"
28 #include "paper-score.hh"
29 #include "ly-smobs.icc"
30 #include "output-def.hh"
33 Grob::clone (int count) const
35 return new Grob (*this, count);
40 - remove dynamic_cast<Spanner, Item> and put this code into respective
44 #define INFINITY_MSG "Infinity or NaN encountered"
46 Grob::Grob (SCM basicprops,
47 Object_key const *key)
50 /* FIXME: default should be no callback. */
55 interfaces_ = SCM_EOL;
56 immutable_property_alist_ = basicprops;
57 mutable_property_alist_ = SCM_EOL;
58 object_alist_ = SCM_EOL;
60 /* We do smobify_self () as the first step. Since the object lives
61 on the heap, none of its SCM variables are protected from
62 GC. After smobify_self (), they are. */
66 We always get a new key object for a new grob.
70 ((Object_key *)key_)->unprotect ();
73 SCM meta = get_property ("meta");
74 if (scm_is_pair (meta))
76 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
81 - destill this into a function, so we can re-init the immutable
82 properties with a new BASICPROPS value after
83 creation. Convenient eg. when using \override with
86 SCM off_callbacks[] = {
87 get_property ("X-offset-callbacks"),
88 get_property ("Y-offset-callbacks")
91 get_property ("X-extent"),
92 get_property ("Y-extent")
94 SCM extent_callbacks[] = {
95 get_property ("X-extent-callback"),
96 get_property ("Y-extent-callback")
99 for (int a = X_AXIS; a <= Y_AXIS; a++)
101 SCM l = off_callbacks[a];
103 if (scm_ilength (l) >= 0)
105 dim_cache_[a].offset_callbacks_ = l;
106 dim_cache_[a].offsets_left_ = scm_ilength (l);
109 programming_error ("[XY]-offset-callbacks must be a list");
111 SCM cb = extent_callbacks[a];
112 if (cb == SCM_BOOL_F)
113 dim_cache_[a].dimension_ = SCM_BOOL_F;
116 if (is_number_pair (xt))
117 dim_cache_[a].dimension_ = xt;
118 else if (ly_is_procedure (cb))
119 dim_cache_[a].dimension_callback_ = cb;
120 else if (cb == SCM_EOL
121 && ly_is_procedure (get_property ("print-function")))
122 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
126 Grob::Grob (Grob const &s, int copy_index)
127 : dim_cache_ (s.dim_cache_)
129 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
130 original_ = (Grob *) & s;
133 immutable_property_alist_ = s.immutable_property_alist_;
134 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
135 interfaces_ = s.interfaces_;
136 object_alist_ = SCM_EOL;
138 /* No properties are copied. That is the job of
139 handle_broken_dependencies. */
146 ((Object_key *)key_)->unprotect ();
154 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
156 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
158 Grob *s = unsmob_grob (element_smob);
159 Axis a = (Axis) scm_to_int (scm_axis);
161 Stencil *m = s->get_stencil ();
165 return ly_interval2scm (e);
169 robust_relative_extent (Grob *me, Grob *refp, Axis a)
171 Interval ext = me->extent (refp, a);
174 ext.add_point (me->relative_coordinate (refp, a));
181 Grob::get_layout () const
183 return pscore_ ? pscore_->layout () : 0;
186 /* Recursively track all dependencies of this Grob. The status_ field
187 is used as a mark-field. It is marked with BUSY during execution
188 of this function, and marked with FINAL when finished.
190 FUNCPTR is the function to call to update this element. */
192 Grob::calculate_dependencies (int final, int busy, SCM funcname)
194 if (status_ >= final)
199 programming_error ("element is busy, come back later");
205 extract_grob_set (this, "dependencies", deps);
206 for (int i = 0; i < deps.size (); i++)
207 deps[i]->calculate_dependencies (final, busy, funcname);
209 SCM proc = internal_get_property (funcname);
210 if (ly_is_procedure (proc))
211 scm_call_1 (proc, this->self_scm ());
217 Grob::get_stencil () const
222 SCM stil = get_property ("stencil");
223 if (unsmob_stencil (stil))
224 return unsmob_stencil (stil);
226 stil = get_uncached_stencil ();
229 Grob *me = (Grob *) this;
230 me->set_property ("stencil", stil);
233 return unsmob_stencil (stil);
237 Grob::get_uncached_stencil () const
239 SCM proc = get_property ("print-function");
242 if (ly_is_procedure (proc))
243 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
245 if (Stencil *m = unsmob_stencil (stil))
247 if (to_boolean (get_property ("transparent")))
248 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
251 SCM expr = m->expr ();
252 if (point_and_click_global)
253 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
255 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
258 /* color support... see interpret_stencil_expression () for more... */
259 SCM color = get_property ("color");
260 if (color != SCM_EOL)
262 m = unsmob_stencil (stil);
263 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
267 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
278 Grob::do_break_processing ()
283 Grob::get_system () const
289 Grob::add_dependency (Grob *e)
292 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
294 programming_error ("null dependency added");
298 Grob::handle_broken_dependencies ()
300 Spanner *sp = dynamic_cast<Spanner *> (this);
305 /* THIS, SP is the original spanner. We use a special function
306 because some Spanners have enormously long lists in their
307 properties, and a special function fixes FOO */
309 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
310 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
312 System *system = get_system ();
316 && common_refpoint (system, X_AXIS)
317 && common_refpoint (system, Y_AXIS))
318 substitute_object_links (system->self_scm (), object_alist_);
319 else if (dynamic_cast<System *> (this))
320 substitute_object_links (SCM_UNDEFINED, object_alist_);
322 /* THIS element is `invalid'; it has been removed from all
323 dependencies, so let's junk the element itself.
325 Do not do this for System, since that would remove references
326 to the originals of score-grobs, which get then GC'd (a bad
331 /* Note that we still want references to this element to be
332 rearranged, and not silently thrown away, so we keep pointers like
333 {broken_into_{drul, array}, original}
341 mutable_property_alist_ = SCM_EOL;
342 object_alist_ = SCM_EOL;
343 immutable_property_alist_ = SCM_EOL;
344 interfaces_ = SCM_EOL;
346 set_extent (SCM_EOL, Y_AXIS);
347 set_extent (SCM_EOL, X_AXIS);
349 set_extent_callback (SCM_EOL, Y_AXIS);
350 set_extent_callback (SCM_EOL, X_AXIS);
352 for (int a = X_AXIS; a <= Y_AXIS; a++)
354 dim_cache_[a].offset_callbacks_ = SCM_EOL;
355 dim_cache_[a].offsets_left_ = 0;
360 Grob::handle_prebroken_dependencies ()
362 /* Don't do this in the derived method, since we want to keep access to
363 object_alist_ centralized. */
366 Item *it = dynamic_cast<Item *> (this);
367 substitute_object_links (scm_from_int (it->break_status_dir ()),
368 original_->object_alist_);
373 Grob::find_broken_piece (System *) const
378 /* Translate in one direction. */
380 Grob::translate_axis (Real y, Axis a)
382 if (isinf (y) || isnan (y))
383 programming_error (_ (INFINITY_MSG));
385 dim_cache_[a].offset_ += y;
388 /* Find the offset relative to D. If D equals THIS, then it is 0.
389 Otherwise, it recursively defd as
391 OFFSET_ + PARENT_L_->relative_coordinate (D) */
393 Grob::relative_coordinate (Grob const *refp, Axis a) const
398 /* We catch PARENT_L_ == nil case with this, but we crash if we did
399 not ask for the absolute coordinate (ie. REFP == nil.) */
400 if (refp == dim_cache_[a].parent_)
401 return get_offset (a);
403 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
406 /* Invoke callbacks to get offset relative to parent. */
408 Grob::get_offset (Axis a) const
410 Grob *me = (Grob *) this;
411 while (dim_cache_[a].offsets_left_)
413 int l = --me->dim_cache_[a].offsets_left_;
414 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_from_int (l));
415 SCM retval = scm_call_2 (cb, self_scm (), scm_from_int (a));
417 Real r = scm_to_double (retval);
418 if (isinf (r) || isnan (r))
420 programming_error (INFINITY_MSG);
423 me->dim_cache_[a].offset_ += r;
425 return dim_cache_[a].offset_;
429 Grob::is_empty (Axis a) const
431 return !(scm_is_pair (dim_cache_[a].dimension_)
432 || ly_is_procedure (dim_cache_[a].dimension_callback_));
436 Grob::flush_extent_cache (Axis axis)
438 Dimension_cache *d = &dim_cache_[axis];
439 if (ly_is_procedure (d->dimension_callback_)
440 && scm_is_pair (d->dimension_))
442 d->dimension_ = SCM_EOL;
444 if (get_parent (axis))
445 get_parent (axis)->flush_extent_cache (axis);
450 Grob::extent (Grob *refp, Axis a) const
452 Real x = relative_coordinate (refp, a);
454 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
457 SCM dimpair = d->dimension_;
458 if (scm_is_pair (dimpair))
460 else if (ly_is_procedure (d->dimension_callback_)
461 && d->dimension_ == SCM_EOL)
462 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_from_int (a));
466 if (!scm_is_pair (d->dimension_))
469 ext = ly_scm2interval (d->dimension_);
471 SCM extra = (a == X_AXIS)
472 ? get_property ("extra-X-extent")
473 : get_property ("extra-Y-extent");
476 if (scm_is_pair (extra))
478 ext[BIGGER] += scm_to_double (scm_cdr (extra));
479 ext[SMALLER] += scm_to_double (scm_car (extra));
482 extra = (a == X_AXIS)
483 ? get_property ("minimum-X-extent")
484 : get_property ("minimum-Y-extent");
486 if (scm_is_pair (extra))
487 ext.unite (Interval (scm_to_double (scm_car (extra)),
488 scm_to_double (scm_cdr (extra))));
495 /* Find the group-element which has both #this# and #s# */
497 Grob::common_refpoint (Grob const *s, Axis a) const
499 /* I don't like the quadratic aspect of this code, but I see no
500 other way. The largest chain of parents might be 10 high or so,
501 so it shouldn't be a real issue. */
502 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
503 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
511 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
513 for (; scm_is_pair (elist); elist = scm_cdr (elist))
514 if (Grob *s = unsmob_grob (scm_car (elist)))
517 common = common->common_refpoint (s, a);
526 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
528 for (int i = arr.size (); i--;)
529 if (Grob *s = arr[i])
532 common = common->common_refpoint (s, a);
543 SCM meta = get_property ("meta");
544 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
545 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
546 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
550 Grob::add_offset_callback (SCM cb, Axis a)
552 if (!has_offset_callback (cb, a))
554 dim_cache_[a].offset_callbacks_
555 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
556 dim_cache_[a].offsets_left_++;
561 Grob::has_extent_callback (SCM cb, Axis a) const
563 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
567 Grob::has_offset_callback (SCM cb, Axis a) const
569 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
573 Grob::set_extent (SCM dc, Axis a)
575 dim_cache_[a].dimension_ = dc;
579 Grob::set_extent_callback (SCM dc, Axis a)
581 dim_cache_[a].dimension_callback_ = dc;
585 Grob::set_parent (Grob *g, Axis a)
587 dim_cache_[a].parent_ = g;
591 Grob::fixup_refpoint ()
593 for (int a = X_AXIS; a < NO_AXES; a++)
596 Grob *parent = get_parent (ax);
601 if (parent->get_system () != get_system () && get_system ())
603 Grob *newparent = parent->find_broken_piece (get_system ());
604 set_parent (newparent, ax);
607 if (Item *i = dynamic_cast<Item *> (this))
609 Item *parenti = dynamic_cast<Item *> (parent);
613 Direction my_dir = i->break_status_dir ();
614 if (my_dir != parenti->break_status_dir ())
616 Item *newparent = parenti->find_prebroken_piece (my_dir);
617 set_parent (newparent, ax);
625 Grob::warning (String s) const
627 SCM cause = self_scm ();
628 while (Grob *g = unsmob_grob (cause))
629 cause = g->get_property ("cause");
631 if (Music *m = unsmob_music (cause))
632 m->origin ()->warning (s);
638 Grob::programming_error (String s) const
640 s = _f ("programming error: %s", s);
644 Grob::discretionary_processing ()
649 Grob::internal_has_interface (SCM k)
651 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
655 Grob::get_parent (Axis a) const
657 return dim_cache_[a].parent_;
660 /** Return Array of Grobs in SCM list LST */
662 ly_scm2grobs (SCM lst)
664 Link_array<Grob> arr;
666 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
669 arr.push (unsmob_grob (e));
677 Grob::get_key () const
682 /** Return SCM list of Grob array A */
684 ly_grobs2scm (Link_array<Grob> a)
687 for (int i = a.size (); i; i--)
688 s = scm_cons (a[i - 1]->self_scm (), s);
693 ADD_INTERFACE (Grob, "grob-interface",
694 "A grob represents a piece of music notation\n"
696 "All grobs have an X and Y-position on the page. These X and Y positions\n"
697 "are stored in a relative format, so they can easily be combined by\n"
698 "stacking them, hanging one grob to the side of another, and coupling\n"
699 "them into a grouping objects.\n"
701 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
702 "is stored relative to that reference point. For example the X-reference\n"
703 "point of a staccato dot usually is the note head that it applies\n"
704 "to. When the note head is moved, the staccato dot moves along\n"
707 "A grob is often associated with a symbol, but some grobs do not print\n"
708 "any symbols. They take care of grouping objects. For example, there is a\n"
709 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
710 "is also an abstract grob: it only moves around chords, but doesn't print\n"
713 "Grobs have a properties: Scheme variables, that can be read and set. "
714 "They have two types. Immutable variables "
715 "define the default style and behavior. They are shared between many objects. "
716 "They can be changed using @code{\\override} and @code{\\revert}. "
718 "Mutable properties are variables that are specific to one grob. Typically, "
719 "lists of other objects, or results from computations are stored in"
720 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
721 "sets a mutable property. ",
722 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
723 "Y-extent-callback print-function extra-offset spacing-procedure "
724 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
725 "meta layer before-line-breaking-callback "
727 "axis-group-parent-X "
728 "axis-group-parent-Y "
729 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
730 "minimum-Y-extent transparent");