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"
29 #include "ly-smobs.icc"
32 Grob::clone (int count) const
34 return new Grob (*this, count);
39 - remove dynamic_cast<Spanner, Item> and put this code into respective
43 #define INFINITY_MSG "Infinity or NaN encountered"
45 Grob::Grob (SCM basicprops,
46 Object_key const *key)
49 /* FIXME: default should be no callback. */
54 immutable_property_alist_ = basicprops;
55 mutable_property_alist_ = SCM_EOL;
57 /* We do smobify_self () as the first step. Since the object lives
58 on the heap, none of its SCM variables are protected from
59 GC. After smobify_self (), they are. */
63 We always get a new key object for a new grob.
65 scm_gc_unprotect_object (key_->self_scm ());
66 SCM meta = get_property ("meta");
67 if (scm_is_pair (meta))
69 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
71 /* Switch off interface checks for the moment. */
72 bool itc = do_internal_type_checking_global;
73 do_internal_type_checking_global = false;
74 internal_set_property (ly_symbol2scm ("interfaces"), scm_cdr (ifs));
75 do_internal_type_checking_global = itc;
80 - destill this into a function, so we can re-init the immutable
81 properties with a new BASICPROPS value after
82 creation. Convenient eg. when using \override with
85 char const *onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86 char const *xnames[] = {"X-extent", "Y-extent"};
87 char const *enames[] = {"X-extent-callback", "Y-extent-callback"};
89 for (int a = X_AXIS; a <= Y_AXIS; a++)
91 SCM l = get_property (onames[a]);
93 if (scm_ilength (l) >= 0)
95 dim_cache_[a].offset_callbacks_ = l;
96 dim_cache_[a].offsets_left_ = scm_ilength (l);
99 programming_error ("[XY]-offset-callbacks must be a list");
101 SCM cb = get_property (enames[a]);
102 if (cb == SCM_BOOL_F)
104 dim_cache_[a].dimension_ = SCM_BOOL_F;
107 SCM xt = get_property (xnames[a]);
108 if (is_number_pair (xt))
110 dim_cache_[a].dimension_ = xt;
112 else if (ly_c_procedure_p (cb))
114 dim_cache_[a].dimension_callback_ = cb;
116 else if (cb == SCM_EOL
117 && ly_c_procedure_p (get_property ("print-function")))
118 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
122 Grob::Grob (Grob const &s, int copy_index)
123 : dim_cache_ (s.dim_cache_)
125 key_ = new Copied_key (s.key_, copy_index);
126 original_ = (Grob *) & s;
129 immutable_property_alist_ = s.immutable_property_alist_;
130 mutable_property_alist_ = SCM_EOL;
132 /* No properties are copied. That is the job of
133 handle_broken_dependencies. */
138 scm_gc_unprotect_object (key_->self_scm ());
145 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
147 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
149 Grob *s = unsmob_grob (element_smob);
150 Axis a = (Axis) scm_to_int (scm_axis);
152 Stencil *m = s->get_stencil ();
156 return ly_interval2scm (e);
160 robust_relative_extent (Grob *me, Grob *refp, Axis a)
162 Interval ext = me->extent (refp, a);
165 ext.add_point (me->relative_coordinate (refp, a));
172 Grob::get_layout () const
174 return pscore_ ? pscore_->layout_ : 0;
177 /* Recursively track all dependencies of this Grob. The status_ field
178 is used as a mark-field. It is marked with BUSY during execution
179 of this function, and marked with FINAL when finished.
181 FUNCPTR is the function to call to update this element. */
183 Grob::calculate_dependencies (int final, int busy, SCM funcname)
185 if (status_ >= final)
190 programming_error ("Element is busy, come back later");
196 for (SCM d = get_property ("dependencies"); scm_is_pair (d);
198 unsmob_grob (scm_car (d))->calculate_dependencies (final, busy, funcname);
200 SCM proc = internal_get_property (funcname);
201 if (ly_c_procedure_p (proc))
202 scm_call_1 (proc, this->self_scm ());
208 Grob::get_stencil () const
213 SCM stil = get_property ("stencil");
214 if (unsmob_stencil (stil))
215 return unsmob_stencil (stil);
217 stil = get_uncached_stencil ();
220 Grob *me = (Grob *) this;
221 me->set_property ("stencil", stil);
224 return unsmob_stencil (stil);
228 Grob::get_uncached_stencil () const
230 SCM proc = get_property ("print-function");
233 if (ly_c_procedure_p (proc))
234 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
236 if (Stencil *m = unsmob_stencil (stil))
238 if (to_boolean (get_property ("transparent")))
239 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
242 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (),
244 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
247 /* color support... see interpret_stencil_expression() for more... */
248 SCM color = get_property ("color");
249 if (color != SCM_EOL)
251 m = unsmob_stencil (stil);
252 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
256 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
267 Grob::do_break_processing ()
272 Grob::get_system () const
278 Grob::add_dependency (Grob *e)
281 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
284 programming_error ("Null dependency added");
288 Grob::handle_broken_dependencies ()
290 Spanner *sp = dynamic_cast<Spanner *> (this);
295 /* THIS, SP is the original spanner. We use a special function
296 because some Spanners have enormously long lists in their
297 properties, and a special function fixes FOO */
298 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
299 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
301 System *system = get_system ();
304 && system && common_refpoint (system, X_AXIS)
305 && common_refpoint (system, Y_AXIS))
306 substitute_mutable_properties (system
307 ? system->self_scm () : SCM_UNDEFINED,
308 mutable_property_alist_);
309 else if (dynamic_cast<System *> (this))
310 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
312 /* THIS element is `invalid'; it has been removed from all
313 dependencies, so let's junk the element itself.
315 Do not do this for System, since that would remove references
316 to the originals of score-grobs, which get then GC'd (a bad
321 /* Note that we still want references to this element to be
322 rearranged, and not silently thrown away, so we keep pointers like
323 {broken_into_{drul, array}, original}
331 mutable_property_alist_ = SCM_EOL;
332 immutable_property_alist_ = SCM_EOL;
334 set_extent (SCM_EOL, Y_AXIS);
335 set_extent (SCM_EOL, X_AXIS);
337 set_extent_callback (SCM_EOL, Y_AXIS);
338 set_extent_callback (SCM_EOL, X_AXIS);
340 for (int a = X_AXIS; a <= Y_AXIS; a++)
342 dim_cache_[a].offset_callbacks_ = SCM_EOL;
343 dim_cache_[a].offsets_left_ = 0;
348 Grob::handle_prebroken_dependencies ()
350 /* Don't do this in the derived method, since we want to keep access to
351 mutable_property_alist_ centralized. */
354 Item *it = dynamic_cast<Item *> (this);
355 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
356 original_->mutable_property_alist_);
361 Grob::find_broken_piece (System *) const
366 /* Translate in one direction. */
368 Grob::translate_axis (Real y, Axis a)
370 if (isinf (y) || isnan (y))
371 programming_error (_ (INFINITY_MSG));
373 dim_cache_[a].offset_ += y;
376 /* Find the offset relative to D. If D equals THIS, then it is 0.
377 Otherwise, it recursively defd as
379 OFFSET_ + PARENT_L_->relative_coordinate (D) */
381 Grob::relative_coordinate (Grob const *refp, Axis a) const
386 /* We catch PARENT_L_ == nil case with this, but we crash if we did
387 not ask for the absolute coordinate (ie. REFP == nil.) */
388 if (refp == dim_cache_[a].parent_)
389 return get_offset (a);
391 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
394 /* Invoke callbacks to get offset relative to parent. */
396 Grob::get_offset (Axis a) const
398 Grob *me = (Grob *) this;
399 while (dim_cache_[a].offsets_left_)
401 int l = --me->dim_cache_[a].offsets_left_;
402 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
403 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
405 Real r = scm_to_double (retval);
406 if (isinf (r) || isnan (r))
408 programming_error (INFINITY_MSG);
411 me->dim_cache_[a].offset_ += r;
413 return dim_cache_[a].offset_;
417 Grob::is_empty (Axis a) const
419 return !(scm_is_pair (dim_cache_[a].dimension_)
420 || ly_c_procedure_p (dim_cache_[a].dimension_callback_));
424 Grob::flush_extent_cache (Axis axis)
426 Dimension_cache *d = &dim_cache_[axis];
427 if (ly_c_procedure_p (d->dimension_callback_)
428 && scm_is_pair (d->dimension_))
430 d->dimension_ = SCM_EOL;
432 if (get_parent (axis))
433 get_parent (axis)->flush_extent_cache (axis);
438 Grob::extent (Grob *refp, Axis a) const
440 Real x = relative_coordinate (refp, a);
442 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
445 SCM dimpair = d->dimension_;
446 if (scm_is_pair (dimpair))
448 else if (ly_c_procedure_p (d->dimension_callback_)
449 && d->dimension_ == SCM_EOL)
450 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
454 if (!scm_is_pair (d->dimension_))
457 ext = ly_scm2interval (d->dimension_);
459 SCM extra = get_property (a == X_AXIS
464 if (scm_is_pair (extra))
466 ext[BIGGER] += scm_to_double (scm_cdr (extra));
467 ext[SMALLER] += scm_to_double (scm_car (extra));
470 extra = get_property (a == X_AXIS
472 : "minimum-Y-extent");
473 if (scm_is_pair (extra))
474 ext.unite (Interval (scm_to_double (scm_car (extra)),
475 scm_to_double (scm_cdr (extra))));
482 /* Find the group-element which has both #this# and #s# */
484 Grob::common_refpoint (Grob const *s, Axis a) const
486 /* I don't like the quadratic aspect of this code, but I see no
487 other way. The largest chain of parents might be 10 high or so,
488 so it shouldn't be a real issue. */
489 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
490 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
498 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
500 for (; scm_is_pair (elist); elist = scm_cdr (elist))
501 if (Grob *s = unsmob_grob (scm_car (elist)))
504 common = common->common_refpoint (s, a);
513 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
515 for (int i = arr.size (); i--;)
516 if (Grob *s = arr[i])
519 common = common->common_refpoint (s, a);
530 SCM meta = get_property ("meta");
531 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
532 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
533 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
537 Grob::add_offset_callback (SCM cb, Axis a)
539 if (!has_offset_callback (cb, a))
541 dim_cache_[a].offset_callbacks_
542 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
543 dim_cache_[a].offsets_left_++;
548 Grob::has_extent_callback (SCM cb, Axis a) const
550 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
554 Grob::has_offset_callback (SCM cb, Axis a) const
556 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
560 Grob::set_extent (SCM dc, Axis a)
562 dim_cache_[a].dimension_ = dc;
566 Grob::set_extent_callback (SCM dc, Axis a)
568 dim_cache_[a].dimension_callback_ = dc;
572 Grob::set_parent (Grob *g, Axis a)
574 dim_cache_[a].parent_ = g;
577 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
579 Grob::fixup_refpoint (SCM smob)
581 Grob *me = unsmob_grob (smob);
582 for (int a = X_AXIS; a < NO_AXES; a++)
585 Grob *parent = me->get_parent (ax);
590 if (parent->get_system () != me->get_system () && me->get_system ())
592 Grob *newparent = parent->find_broken_piece (me->get_system ());
593 me->set_parent (newparent, ax);
596 if (Item *i = dynamic_cast<Item *> (me))
598 Item *parenti = dynamic_cast<Item *> (parent);
602 Direction my_dir = i->break_status_dir ();
603 if (my_dir != parenti->break_status_dir ())
605 Item *newparent = parenti->find_prebroken_piece (my_dir);
606 me->set_parent (newparent, ax);
615 Grob::warning (String s) const
617 SCM cause = self_scm ();
618 while (Grob *g = unsmob_grob (cause))
619 cause = g->get_property ("cause");
621 if (Music *m = unsmob_music (cause))
622 m->origin ()->warning (s);
628 Grob::programming_error (String s) const
630 s = "Programming error: " + s;
634 /****************************************************
636 ****************************************************/
638 IMPLEMENT_SMOBS (Grob);
639 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
642 Grob::mark_smob (SCM ses)
644 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
645 scm_gc_mark (s->immutable_property_alist_);
646 scm_gc_mark (s->key_->self_scm ());
647 for (int a = 0; a < 2; a++)
649 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
650 scm_gc_mark (s->dim_cache_[a].dimension_);
651 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
653 /* Do not mark the parents. The pointers in the mutable
654 property list form two tree like structures (one for X
655 relations, one for Y relations). Marking these can be done
656 in limited stack space. If we add the parents, we will jump
657 between X and Y in an erratic manner, leading to much more
658 recursion depth (and core dumps if we link to pthreads). */
662 scm_gc_mark (s->original_->self_scm ());
664 s->do_derived_mark ();
665 return s->mutable_property_alist_;
669 Grob::print_smob (SCM s, SCM port, scm_print_state *)
671 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
673 scm_puts ("#<Grob ", port);
674 scm_puts ((char *) sc->name ().to_str0 (), port);
676 /* Do not print properties, that is too much hassle. */
677 scm_puts (" >", port);
682 Grob::do_derived_mark () const
688 Grob::discretionary_processing ()
693 Grob::internal_has_interface (SCM k)
695 SCM ifs = get_property ("interfaces");
697 return scm_c_memq (k, ifs) != SCM_BOOL_F;
701 Grob::get_parent (Axis a) const
703 return dim_cache_[a].parent_;
706 /** Return Array of Grobs in SCM list LST */
708 ly_scm2grobs (SCM lst)
710 Link_array<Grob> arr;
712 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
715 arr.push (unsmob_grob (e));
723 Grob::get_key () const
728 /** Return SCM list of Grob array A */
730 ly_grobs2scm (Link_array<Grob> a)
733 for (int i = a.size (); i; i--)
734 s = scm_cons (a[i - 1]->self_scm (), s);
739 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
741 ADD_INTERFACE (Grob, "grob-interface",
742 "A grob represents a piece of music notation\n"
744 "All grobs have an X and Y-position on the page. These X and Y positions\n"
745 "are stored in a relative format, so they can easily be combined by\n"
746 "stacking them, hanging one grob to the side of another, and coupling\n"
747 "them into a grouping objects.\n"
749 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
750 "is stored relative to that reference point. For example the X-reference\n"
751 "point of a staccato dot usually is the note head that it applies\n"
752 "to. When the note head is moved, the staccato dot moves along\n"
755 "A grob is often associated with a symbol, but some grobs do not print\n"
756 "any symbols. They take care of grouping objects. For example, there is a\n"
757 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
758 "is also an abstract grob: it only moves around chords, but doesn't print\n"
761 "Grobs have a properties: Scheme variables, that can be read and set. "
762 "They have two types. Immutable variables "
763 "define the default style and behavior. They are shared between many objects. "
764 "They can be changed using @code{\\override} and @code{\\revert}. "
766 "Mutable properties are variables that are specific to one grob. Typically, "
767 "lists of other objects, or results from computations are stored in"
768 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
769 "sets a mutable property. ",
770 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
771 "Y-extent-callback print-function extra-offset spacing-procedure "
772 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
773 "meta layer before-line-breaking-callback "
775 "axis-group-parent-X "
776 "axis-group-parent-Y "
777 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
778 "minimum-Y-extent transparent tweak-count tweak-rank");