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 "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 immutable_property_alist_ = basicprops;
56 mutable_property_alist_ = SCM_EOL;
58 /* We do smobify_self () as the first step. Since the object lives
59 on the heap, none of its SCM variables are protected from
60 GC. After smobify_self (), they are. */
64 We always get a new key object for a new grob.
67 scm_gc_unprotect_object (key_->self_scm ());
68 SCM meta = get_property ("meta");
69 if (scm_is_pair (meta))
71 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
73 /* Switch off interface checks for the moment. */
74 bool itc = do_internal_type_checking_global;
75 do_internal_type_checking_global = false;
76 internal_set_property (ly_symbol2scm ("interfaces"), scm_cdr (ifs));
77 do_internal_type_checking_global = itc;
82 - destill this into a function, so we can re-init the immutable
83 properties with a new BASICPROPS value after
84 creation. Convenient eg. when using \override with
87 char const *onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
88 char const *xnames[] = {"X-extent", "Y-extent"};
89 char const *enames[] = {"X-extent-callback", "Y-extent-callback"};
91 for (int a = X_AXIS; a <= Y_AXIS; a++)
93 SCM l = get_property (onames[a]);
95 if (scm_ilength (l) >= 0)
97 dim_cache_[a].offset_callbacks_ = l;
98 dim_cache_[a].offsets_left_ = scm_ilength (l);
101 programming_error ("[XY]-offset-callbacks must be a list");
103 SCM cb = get_property (enames[a]);
104 if (cb == SCM_BOOL_F)
106 dim_cache_[a].dimension_ = SCM_BOOL_F;
109 SCM xt = get_property (xnames[a]);
110 if (is_number_pair (xt))
112 dim_cache_[a].dimension_ = xt;
114 else if (ly_is_procedure (cb))
116 dim_cache_[a].dimension_callback_ = cb;
118 else if (cb == SCM_EOL
119 && ly_is_procedure (get_property ("print-function")))
120 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
124 Grob::Grob (Grob const &s, int copy_index)
125 : dim_cache_ (s.dim_cache_)
127 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
128 original_ = (Grob *) & s;
131 immutable_property_alist_ = s.immutable_property_alist_;
132 mutable_property_alist_ = SCM_EOL;
134 /* No properties are copied. That is the job of
135 handle_broken_dependencies. */
140 scm_gc_unprotect_object (key_->self_scm ());
147 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
149 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
151 Grob *s = unsmob_grob (element_smob);
152 Axis a = (Axis) scm_to_int (scm_axis);
154 Stencil *m = s->get_stencil ();
158 return ly_interval2scm (e);
162 robust_relative_extent (Grob *me, Grob *refp, Axis a)
164 Interval ext = me->extent (refp, a);
167 ext.add_point (me->relative_coordinate (refp, a));
174 Grob::get_layout () const
176 return pscore_ ? pscore_->layout () : 0;
179 /* Recursively track all dependencies of this Grob. The status_ field
180 is used as a mark-field. It is marked with BUSY during execution
181 of this function, and marked with FINAL when finished.
183 FUNCPTR is the function to call to update this element. */
185 Grob::calculate_dependencies (int final, int busy, SCM funcname)
187 if (status_ >= final)
192 programming_error ("element is busy, come back later");
198 for (SCM d = get_property ("dependencies"); scm_is_pair (d);
200 unsmob_grob (scm_car (d))->calculate_dependencies (final, busy, funcname);
202 SCM proc = internal_get_property (funcname);
203 if (ly_is_procedure (proc))
204 scm_call_1 (proc, this->self_scm ());
210 Grob::get_stencil () const
215 SCM stil = get_property ("stencil");
216 if (unsmob_stencil (stil))
217 return unsmob_stencil (stil);
219 stil = get_uncached_stencil ();
222 Grob *me = (Grob *) this;
223 me->set_property ("stencil", stil);
226 return unsmob_stencil (stil);
230 Grob::get_uncached_stencil () const
232 SCM proc = get_property ("print-function");
235 if (ly_is_procedure (proc))
236 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
238 if (Stencil *m = unsmob_stencil (stil))
240 if (to_boolean (get_property ("transparent")))
241 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
244 SCM expr = m->expr ();
245 if (point_and_click_global)
246 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
248 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
251 /* color support... see interpret_stencil_expression () for more... */
252 SCM color = get_property ("color");
253 if (color != SCM_EOL)
255 m = unsmob_stencil (stil);
256 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
260 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
271 Grob::do_break_processing ()
276 Grob::get_system () const
282 Grob::add_dependency (Grob *e)
285 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
287 programming_error ("null dependency added");
291 Grob::handle_broken_dependencies ()
293 Spanner *sp = dynamic_cast<Spanner *> (this);
298 /* THIS, SP is the original spanner. We use a special function
299 because some Spanners have enormously long lists in their
300 properties, and a special function fixes FOO */
301 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
302 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
304 System *system = get_system ();
307 && system && common_refpoint (system, X_AXIS)
308 && common_refpoint (system, Y_AXIS))
309 substitute_mutable_properties (system
310 ? system->self_scm () : SCM_UNDEFINED,
311 mutable_property_alist_);
312 else if (dynamic_cast<System *> (this))
313 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
315 /* THIS element is `invalid'; it has been removed from all
316 dependencies, so let's junk the element itself.
318 Do not do this for System, since that would remove references
319 to the originals of score-grobs, which get then GC'd (a bad
324 /* Note that we still want references to this element to be
325 rearranged, and not silently thrown away, so we keep pointers like
326 {broken_into_{drul, array}, original}
334 mutable_property_alist_ = SCM_EOL;
335 immutable_property_alist_ = SCM_EOL;
337 set_extent (SCM_EOL, Y_AXIS);
338 set_extent (SCM_EOL, X_AXIS);
340 set_extent_callback (SCM_EOL, Y_AXIS);
341 set_extent_callback (SCM_EOL, X_AXIS);
343 for (int a = X_AXIS; a <= Y_AXIS; a++)
345 dim_cache_[a].offset_callbacks_ = SCM_EOL;
346 dim_cache_[a].offsets_left_ = 0;
351 Grob::handle_prebroken_dependencies ()
353 /* Don't do this in the derived method, since we want to keep access to
354 mutable_property_alist_ centralized. */
357 Item *it = dynamic_cast<Item *> (this);
358 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
359 original_->mutable_property_alist_);
364 Grob::find_broken_piece (System *) const
369 /* Translate in one direction. */
371 Grob::translate_axis (Real y, Axis a)
373 if (isinf (y) || isnan (y))
374 programming_error (_ (INFINITY_MSG));
376 dim_cache_[a].offset_ += y;
379 /* Find the offset relative to D. If D equals THIS, then it is 0.
380 Otherwise, it recursively defd as
382 OFFSET_ + PARENT_L_->relative_coordinate (D) */
384 Grob::relative_coordinate (Grob const *refp, Axis a) const
389 /* We catch PARENT_L_ == nil case with this, but we crash if we did
390 not ask for the absolute coordinate (ie. REFP == nil.) */
391 if (refp == dim_cache_[a].parent_)
392 return get_offset (a);
394 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
397 /* Invoke callbacks to get offset relative to parent. */
399 Grob::get_offset (Axis a) const
401 Grob *me = (Grob *) this;
402 while (dim_cache_[a].offsets_left_)
404 int l = --me->dim_cache_[a].offsets_left_;
405 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
406 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
408 Real r = scm_to_double (retval);
409 if (isinf (r) || isnan (r))
411 programming_error (INFINITY_MSG);
414 me->dim_cache_[a].offset_ += r;
416 return dim_cache_[a].offset_;
420 Grob::is_empty (Axis a) const
422 return !(scm_is_pair (dim_cache_[a].dimension_)
423 || ly_is_procedure (dim_cache_[a].dimension_callback_));
427 Grob::flush_extent_cache (Axis axis)
429 Dimension_cache *d = &dim_cache_[axis];
430 if (ly_is_procedure (d->dimension_callback_)
431 && scm_is_pair (d->dimension_))
433 d->dimension_ = SCM_EOL;
435 if (get_parent (axis))
436 get_parent (axis)->flush_extent_cache (axis);
441 Grob::extent (Grob *refp, Axis a) const
443 Real x = relative_coordinate (refp, a);
445 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
448 SCM dimpair = d->dimension_;
449 if (scm_is_pair (dimpair))
451 else if (ly_is_procedure (d->dimension_callback_)
452 && d->dimension_ == SCM_EOL)
453 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
457 if (!scm_is_pair (d->dimension_))
460 ext = ly_scm2interval (d->dimension_);
462 SCM extra = get_property (a == X_AXIS
467 if (scm_is_pair (extra))
469 ext[BIGGER] += scm_to_double (scm_cdr (extra));
470 ext[SMALLER] += scm_to_double (scm_car (extra));
473 extra = get_property (a == X_AXIS
475 : "minimum-Y-extent");
476 if (scm_is_pair (extra))
477 ext.unite (Interval (scm_to_double (scm_car (extra)),
478 scm_to_double (scm_cdr (extra))));
485 /* Find the group-element which has both #this# and #s# */
487 Grob::common_refpoint (Grob const *s, Axis a) const
489 /* I don't like the quadratic aspect of this code, but I see no
490 other way. The largest chain of parents might be 10 high or so,
491 so it shouldn't be a real issue. */
492 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
493 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
501 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
503 for (; scm_is_pair (elist); elist = scm_cdr (elist))
504 if (Grob *s = unsmob_grob (scm_car (elist)))
507 common = common->common_refpoint (s, a);
516 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
518 for (int i = arr.size (); i--;)
519 if (Grob *s = arr[i])
522 common = common->common_refpoint (s, a);
533 SCM meta = get_property ("meta");
534 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
535 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
536 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
540 Grob::add_offset_callback (SCM cb, Axis a)
542 if (!has_offset_callback (cb, a))
544 dim_cache_[a].offset_callbacks_
545 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
546 dim_cache_[a].offsets_left_++;
551 Grob::has_extent_callback (SCM cb, Axis a) const
553 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
557 Grob::has_offset_callback (SCM cb, Axis a) const
559 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
563 Grob::set_extent (SCM dc, Axis a)
565 dim_cache_[a].dimension_ = dc;
569 Grob::set_extent_callback (SCM dc, Axis a)
571 dim_cache_[a].dimension_callback_ = dc;
575 Grob::set_parent (Grob *g, Axis a)
577 dim_cache_[a].parent_ = g;
580 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
582 Grob::fixup_refpoint (SCM smob)
584 Grob *me = unsmob_grob (smob);
585 for (int a = X_AXIS; a < NO_AXES; a++)
588 Grob *parent = me->get_parent (ax);
593 if (parent->get_system () != me->get_system () && me->get_system ())
595 Grob *newparent = parent->find_broken_piece (me->get_system ());
596 me->set_parent (newparent, ax);
599 if (Item *i = dynamic_cast<Item *> (me))
601 Item *parenti = dynamic_cast<Item *> (parent);
605 Direction my_dir = i->break_status_dir ();
606 if (my_dir != parenti->break_status_dir ())
608 Item *newparent = parenti->find_prebroken_piece (my_dir);
609 me->set_parent (newparent, ax);
618 Grob::warning (String s) const
620 SCM cause = self_scm ();
621 while (Grob *g = unsmob_grob (cause))
622 cause = g->get_property ("cause");
624 if (Music *m = unsmob_music (cause))
625 m->origin ()->warning (s);
631 Grob::programming_error (String s) const
633 s = _f ("programming error: %s", s);
637 /****************************************************
639 ****************************************************/
641 IMPLEMENT_SMOBS (Grob);
642 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
645 Grob::mark_smob (SCM ses)
647 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
648 scm_gc_mark (s->immutable_property_alist_);
651 scm_gc_mark (s->key_->self_scm ());
652 for (int a = 0; a < 2; a++)
654 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
655 scm_gc_mark (s->dim_cache_[a].dimension_);
656 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
658 /* Do not mark the parents. The pointers in the mutable
659 property list form two tree like structures (one for X
660 relations, one for Y relations). Marking these can be done
661 in limited stack space. If we add the parents, we will jump
662 between X and Y in an erratic manner, leading to much more
663 recursion depth (and core dumps if we link to pthreads). */
667 scm_gc_mark (s->original_->self_scm ());
670 scm_gc_mark (s->pscore_->self_scm ());
672 s->do_derived_mark ();
673 return s->mutable_property_alist_;
677 Grob::print_smob (SCM s, SCM port, scm_print_state *)
679 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
681 scm_puts ("#<Grob ", port);
682 scm_puts ((char *) sc->name ().to_str0 (), port);
684 /* Do not print properties, that is too much hassle. */
685 scm_puts (" >", port);
690 Grob::do_derived_mark () const
696 Grob::discretionary_processing ()
701 Grob::internal_has_interface (SCM k)
703 SCM ifs = get_property ("interfaces");
705 return scm_c_memq (k, ifs) != SCM_BOOL_F;
709 Grob::get_parent (Axis a) const
711 return dim_cache_[a].parent_;
714 /** Return Array of Grobs in SCM list LST */
716 ly_scm2grobs (SCM lst)
718 Link_array<Grob> arr;
720 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
723 arr.push (unsmob_grob (e));
731 Grob::get_key () const
736 /** Return SCM list of Grob array A */
738 ly_grobs2scm (Link_array<Grob> a)
741 for (int i = a.size (); i; i--)
742 s = scm_cons (a[i - 1]->self_scm (), s);
747 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
749 ADD_INTERFACE (Grob, "grob-interface",
750 "A grob represents a piece of music notation\n"
752 "All grobs have an X and Y-position on the page. These X and Y positions\n"
753 "are stored in a relative format, so they can easily be combined by\n"
754 "stacking them, hanging one grob to the side of another, and coupling\n"
755 "them into a grouping objects.\n"
757 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
758 "is stored relative to that reference point. For example the X-reference\n"
759 "point of a staccato dot usually is the note head that it applies\n"
760 "to. When the note head is moved, the staccato dot moves along\n"
763 "A grob is often associated with a symbol, but some grobs do not print\n"
764 "any symbols. They take care of grouping objects. For example, there is a\n"
765 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
766 "is also an abstract grob: it only moves around chords, but doesn't print\n"
769 "Grobs have a properties: Scheme variables, that can be read and set. "
770 "They have two types. Immutable variables "
771 "define the default style and behavior. They are shared between many objects. "
772 "They can be changed using @code{\\override} and @code{\\revert}. "
774 "Mutable properties are variables that are specific to one grob. Typically, "
775 "lists of other objects, or results from computations are stored in"
776 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
777 "sets a mutable property. ",
778 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
779 "Y-extent-callback print-function extra-offset spacing-procedure "
780 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
781 "meta layer before-line-breaking-callback "
783 "axis-group-parent-X "
784 "axis-group-parent-Y "
785 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
786 "minimum-Y-extent transparent tweak-count tweak-rank");