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));
447 else if (ly_c_procedure_p (d->dimension_callback_)
448 && d->dimension_ == SCM_EOL)
449 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
453 if (!scm_is_pair (d->dimension_))
456 ext = ly_scm2interval (d->dimension_);
458 SCM extra = get_property (a == X_AXIS
463 if (scm_is_pair (extra))
465 ext[BIGGER] += scm_to_double (scm_cdr (extra));
466 ext[SMALLER] += scm_to_double (scm_car (extra));
469 extra = get_property (a == X_AXIS
471 : "minimum-Y-extent");
472 if (scm_is_pair (extra))
473 ext.unite (Interval (scm_to_double (scm_car (extra)),
474 scm_to_double (scm_cdr (extra))));
481 /* Find the group-element which has both #this# and #s# */
483 Grob::common_refpoint (Grob const *s, Axis a) const
485 /* I don't like the quadratic aspect of this code, but I see no
486 other way. The largest chain of parents might be 10 high or so,
487 so it shouldn't be a real issue. */
488 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
489 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
497 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
499 for (; scm_is_pair (elist); elist = scm_cdr (elist))
500 if (Grob *s = unsmob_grob (scm_car (elist)))
503 common = common->common_refpoint (s, a);
512 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
514 for (int i = arr.size (); i--;)
515 if (Grob *s = arr[i])
518 common = common->common_refpoint (s, a);
529 SCM meta = get_property ("meta");
530 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
531 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
532 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
536 Grob::add_offset_callback (SCM cb, Axis a)
538 if (!has_offset_callback (cb, a))
540 dim_cache_[a].offset_callbacks_
541 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
542 dim_cache_[a].offsets_left_++;
547 Grob::has_extent_callback (SCM cb, Axis a) const
549 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
553 Grob::has_offset_callback (SCM cb, Axis a) const
555 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
559 Grob::set_extent (SCM dc, Axis a)
561 dim_cache_[a].dimension_ = dc;
565 Grob::set_extent_callback (SCM dc, Axis a)
567 dim_cache_[a].dimension_callback_ = dc;
571 Grob::set_parent (Grob *g, Axis a)
573 dim_cache_[a].parent_ = g;
576 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
578 Grob::fixup_refpoint (SCM smob)
580 Grob *me = unsmob_grob (smob);
581 for (int a = X_AXIS; a < NO_AXES; a++)
584 Grob *parent = me->get_parent (ax);
589 if (parent->get_system () != me->get_system () && me->get_system ())
591 Grob *newparent = parent->find_broken_piece (me->get_system ());
592 me->set_parent (newparent, ax);
595 if (Item *i = dynamic_cast<Item *> (me))
597 Item *parenti = dynamic_cast<Item *> (parent);
601 Direction my_dir = i->break_status_dir ();
602 if (my_dir!= parenti->break_status_dir ())
604 Item *newparent = parenti->find_prebroken_piece (my_dir);
605 me->set_parent (newparent, ax);
614 Grob::warning (String s) const
616 SCM cause = self_scm ();
617 while (Grob *g = unsmob_grob (cause))
618 cause = g->get_property ("cause");
620 if (Music *m = unsmob_music (cause))
621 m->origin ()->warning (s);
627 Grob::programming_error (String s) const
629 s = "Programming error: " + s;
633 /****************************************************
635 ****************************************************/
637 IMPLEMENT_SMOBS (Grob);
638 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
641 Grob::mark_smob (SCM ses)
643 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
644 scm_gc_mark (s->immutable_property_alist_);
645 scm_gc_mark (s->key_->self_scm ());
646 for (int a = 0; a < 2; a++)
648 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
649 scm_gc_mark (s->dim_cache_[a].dimension_);
650 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
652 /* Do not mark the parents. The pointers in the mutable
653 property list form two tree like structures (one for X
654 relations, one for Y relations). Marking these can be done
655 in limited stack space. If we add the parents, we will jump
656 between X and Y in an erratic manner, leading to much more
657 recursion depth (and core dumps if we link to pthreads). */
661 scm_gc_mark (s->original_->self_scm ());
663 s->do_derived_mark ();
664 return s->mutable_property_alist_;
668 Grob::print_smob (SCM s, SCM port, scm_print_state *)
670 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
672 scm_puts ("#<Grob ", port);
673 scm_puts ((char *) sc->name ().to_str0 (), port);
675 /* Do not print properties, that is too much hassle. */
676 scm_puts (" >", port);
681 Grob::do_derived_mark () const
687 Grob::discretionary_processing ()
692 Grob::internal_has_interface (SCM k)
694 SCM ifs = get_property ("interfaces");
696 return scm_c_memq (k, ifs) != SCM_BOOL_F;
700 Grob::get_parent (Axis a) const
702 return dim_cache_[a].parent_;
705 /** Return Array of Grobs in SCM list LST */
707 ly_scm2grobs (SCM lst)
709 Link_array<Grob> arr;
711 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
714 arr.push (unsmob_grob (e));
722 Grob::get_key () const
727 /** Return SCM list of Grob array A */
729 ly_grobs2scm (Link_array<Grob> a)
732 for (int i = a.size (); i; i--)
733 s = scm_cons (a[i - 1]->self_scm (), s);
738 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
740 ADD_INTERFACE (Grob, "grob-interface",
741 "A grob represents a piece of music notation\n"
743 "All grobs have an X and Y-position on the page. These X and Y positions\n"
744 "are stored in a relative format, so they can easily be combined by\n"
745 "stacking them, hanging one grob to the side of another, and coupling\n"
746 "them into a grouping objects.\n"
748 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
749 "is stored relative to that reference point. For example the X-reference\n"
750 "point of a staccato dot usually is the note head that it applies\n"
751 "to. When the note head is moved, the staccato dot moves along\n"
754 "A grob is often associated with a symbol, but some grobs do not print\n"
755 "any symbols. They take care of grouping objects. For example, there is a\n"
756 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
757 "is also an abstract grob: it only moves around chords, but doesn't print\n"
760 "Grobs have a properties: Scheme variables, that can be read and set. "
761 "They have two types. Immutable variables "
762 "define the default style and behavior. They are shared between many objects. "
763 "They can be changed using @code{\\override} and @code{\\revert}. "
765 "Mutable properties are variables that are specific to one grob. Typically, "
766 "lists of other objects, or results from computations are stored in"
767 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
768 "sets a mutable property. ",
769 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
770 "Y-extent-callback print-function extra-offset spacing-procedure "
771 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
772 "meta layer before-line-breaking-callback "
774 "axis-group-parent-X "
775 "axis-group-parent-Y "
776 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
777 "minimum-Y-extent transparent tweak-count tweak-rank");