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.
66 scm_gc_unprotect_object (key_->self_scm ());
67 SCM meta = get_property ("meta");
68 if (scm_is_pair (meta))
70 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
72 /* Switch off interface checks for the moment. */
73 bool itc = do_internal_type_checking_global;
74 do_internal_type_checking_global = false;
75 internal_set_property (ly_symbol2scm ("interfaces"), scm_cdr (ifs));
76 do_internal_type_checking_global = itc;
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 char const *onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
87 char const *xnames[] = {"X-extent", "Y-extent"};
88 char const *enames[] = {"X-extent-callback", "Y-extent-callback"};
90 for (int a = X_AXIS; a <= Y_AXIS; a++)
92 SCM l = get_property (onames[a]);
94 if (scm_ilength (l) >= 0)
96 dim_cache_[a].offset_callbacks_ = l;
97 dim_cache_[a].offsets_left_ = scm_ilength (l);
100 programming_error ("[XY]-offset-callbacks must be a list");
102 SCM cb = get_property (enames[a]);
103 if (cb == SCM_BOOL_F)
105 dim_cache_[a].dimension_ = SCM_BOOL_F;
108 SCM xt = get_property (xnames[a]);
109 if (is_number_pair (xt))
111 dim_cache_[a].dimension_ = xt;
113 else if (ly_c_procedure_p (cb))
115 dim_cache_[a].dimension_callback_ = cb;
117 else if (cb == SCM_EOL
118 && ly_c_procedure_p (get_property ("print-function")))
119 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
123 Grob::Grob (Grob const &s, int copy_index)
124 : dim_cache_ (s.dim_cache_)
126 key_ = new Copied_key (s.key_, copy_index);
127 original_ = (Grob *) & s;
130 immutable_property_alist_ = s.immutable_property_alist_;
131 mutable_property_alist_ = SCM_EOL;
133 /* No properties are copied. That is the job of
134 handle_broken_dependencies. */
139 scm_gc_unprotect_object (key_->self_scm ());
146 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
148 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
150 Grob *s = unsmob_grob (element_smob);
151 Axis a = (Axis) scm_to_int (scm_axis);
153 Stencil *m = s->get_stencil ();
157 return ly_interval2scm (e);
161 robust_relative_extent (Grob *me, Grob *refp, Axis a)
163 Interval ext = me->extent (refp, a);
166 ext.add_point (me->relative_coordinate (refp, a));
173 Grob::get_layout () const
175 return pscore_ ? pscore_->layout_ : 0;
178 /* Recursively track all dependencies of this Grob. The status_ field
179 is used as a mark-field. It is marked with BUSY during execution
180 of this function, and marked with FINAL when finished.
182 FUNCPTR is the function to call to update this element. */
184 Grob::calculate_dependencies (int final, int busy, SCM funcname)
186 if (status_ >= final)
191 programming_error ("Element is busy, come back later");
197 for (SCM d = get_property ("dependencies"); scm_is_pair (d);
199 unsmob_grob (scm_car (d))->calculate_dependencies (final, busy, funcname);
201 SCM proc = internal_get_property (funcname);
202 if (ly_c_procedure_p (proc))
203 scm_call_1 (proc, this->self_scm ());
209 Grob::get_stencil () const
214 SCM stil = get_property ("stencil");
215 if (unsmob_stencil (stil))
216 return unsmob_stencil (stil);
218 stil = get_uncached_stencil ();
221 Grob *me = (Grob *) this;
222 me->set_property ("stencil", stil);
225 return unsmob_stencil (stil);
229 Grob::get_uncached_stencil () const
231 SCM proc = get_property ("print-function");
234 if (ly_c_procedure_p (proc))
235 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
237 if (Stencil *m = unsmob_stencil (stil))
239 if (to_boolean (get_property ("transparent")))
240 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
243 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (),
245 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
248 /* color support... see interpret_stencil_expression () for more... */
249 SCM color = get_property ("color");
250 if (color != SCM_EOL)
252 m = unsmob_stencil (stil);
253 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
257 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
268 Grob::do_break_processing ()
273 Grob::get_system () const
279 Grob::add_dependency (Grob *e)
282 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
285 programming_error ("Null dependency added");
289 Grob::handle_broken_dependencies ()
291 Spanner *sp = dynamic_cast<Spanner *> (this);
296 /* THIS, SP is the original spanner. We use a special function
297 because some Spanners have enormously long lists in their
298 properties, and a special function fixes FOO */
299 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
300 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
302 System *system = get_system ();
305 && system && common_refpoint (system, X_AXIS)
306 && common_refpoint (system, Y_AXIS))
307 substitute_mutable_properties (system
308 ? system->self_scm () : SCM_UNDEFINED,
309 mutable_property_alist_);
310 else if (dynamic_cast<System *> (this))
311 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
313 /* THIS element is `invalid'; it has been removed from all
314 dependencies, so let's junk the element itself.
316 Do not do this for System, since that would remove references
317 to the originals of score-grobs, which get then GC'd (a bad
322 /* Note that we still want references to this element to be
323 rearranged, and not silently thrown away, so we keep pointers like
324 {broken_into_{drul, array}, original}
332 mutable_property_alist_ = SCM_EOL;
333 immutable_property_alist_ = SCM_EOL;
335 set_extent (SCM_EOL, Y_AXIS);
336 set_extent (SCM_EOL, X_AXIS);
338 set_extent_callback (SCM_EOL, Y_AXIS);
339 set_extent_callback (SCM_EOL, X_AXIS);
341 for (int a = X_AXIS; a <= Y_AXIS; a++)
343 dim_cache_[a].offset_callbacks_ = SCM_EOL;
344 dim_cache_[a].offsets_left_ = 0;
349 Grob::handle_prebroken_dependencies ()
351 /* Don't do this in the derived method, since we want to keep access to
352 mutable_property_alist_ centralized. */
355 Item *it = dynamic_cast<Item *> (this);
356 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
357 original_->mutable_property_alist_);
362 Grob::find_broken_piece (System *) const
367 /* Translate in one direction. */
369 Grob::translate_axis (Real y, Axis a)
371 if (isinf (y) || isnan (y))
372 programming_error (_ (INFINITY_MSG));
374 dim_cache_[a].offset_ += y;
377 /* Find the offset relative to D. If D equals THIS, then it is 0.
378 Otherwise, it recursively defd as
380 OFFSET_ + PARENT_L_->relative_coordinate (D) */
382 Grob::relative_coordinate (Grob const *refp, Axis a) const
387 /* We catch PARENT_L_ == nil case with this, but we crash if we did
388 not ask for the absolute coordinate (ie. REFP == nil.) */
389 if (refp == dim_cache_[a].parent_)
390 return get_offset (a);
392 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
395 /* Invoke callbacks to get offset relative to parent. */
397 Grob::get_offset (Axis a) const
399 Grob *me = (Grob *) this;
400 while (dim_cache_[a].offsets_left_)
402 int l = --me->dim_cache_[a].offsets_left_;
403 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
404 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
406 Real r = scm_to_double (retval);
407 if (isinf (r) || isnan (r))
409 programming_error (INFINITY_MSG);
412 me->dim_cache_[a].offset_ += r;
414 return dim_cache_[a].offset_;
418 Grob::is_empty (Axis a) const
420 return !(scm_is_pair (dim_cache_[a].dimension_)
421 || ly_c_procedure_p (dim_cache_[a].dimension_callback_));
425 Grob::flush_extent_cache (Axis axis)
427 Dimension_cache *d = &dim_cache_[axis];
428 if (ly_c_procedure_p (d->dimension_callback_)
429 && scm_is_pair (d->dimension_))
431 d->dimension_ = SCM_EOL;
433 if (get_parent (axis))
434 get_parent (axis)->flush_extent_cache (axis);
439 Grob::extent (Grob *refp, Axis a) const
441 Real x = relative_coordinate (refp, a);
443 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
446 SCM dimpair = d->dimension_;
447 if (scm_is_pair (dimpair))
449 else if (ly_c_procedure_p (d->dimension_callback_)
450 && d->dimension_ == SCM_EOL)
451 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
455 if (!scm_is_pair (d->dimension_))
458 ext = ly_scm2interval (d->dimension_);
460 SCM extra = get_property (a == X_AXIS
465 if (scm_is_pair (extra))
467 ext[BIGGER] += scm_to_double (scm_cdr (extra));
468 ext[SMALLER] += scm_to_double (scm_car (extra));
471 extra = get_property (a == X_AXIS
473 : "minimum-Y-extent");
474 if (scm_is_pair (extra))
475 ext.unite (Interval (scm_to_double (scm_car (extra)),
476 scm_to_double (scm_cdr (extra))));
483 /* Find the group-element which has both #this# and #s# */
485 Grob::common_refpoint (Grob const *s, Axis a) const
487 /* I don't like the quadratic aspect of this code, but I see no
488 other way. The largest chain of parents might be 10 high or so,
489 so it shouldn't be a real issue. */
490 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
491 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
499 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
501 for (; scm_is_pair (elist); elist = scm_cdr (elist))
502 if (Grob *s = unsmob_grob (scm_car (elist)))
505 common = common->common_refpoint (s, a);
514 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
516 for (int i = arr.size (); i--;)
517 if (Grob *s = arr[i])
520 common = common->common_refpoint (s, a);
531 SCM meta = get_property ("meta");
532 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
533 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
534 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
538 Grob::add_offset_callback (SCM cb, Axis a)
540 if (!has_offset_callback (cb, a))
542 dim_cache_[a].offset_callbacks_
543 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
544 dim_cache_[a].offsets_left_++;
549 Grob::has_extent_callback (SCM cb, Axis a) const
551 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
555 Grob::has_offset_callback (SCM cb, Axis a) const
557 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
561 Grob::set_extent (SCM dc, Axis a)
563 dim_cache_[a].dimension_ = dc;
567 Grob::set_extent_callback (SCM dc, Axis a)
569 dim_cache_[a].dimension_callback_ = dc;
573 Grob::set_parent (Grob *g, Axis a)
575 dim_cache_[a].parent_ = g;
578 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
580 Grob::fixup_refpoint (SCM smob)
582 Grob *me = unsmob_grob (smob);
583 for (int a = X_AXIS; a < NO_AXES; a++)
586 Grob *parent = me->get_parent (ax);
591 if (parent->get_system () != me->get_system () && me->get_system ())
593 Grob *newparent = parent->find_broken_piece (me->get_system ());
594 me->set_parent (newparent, ax);
597 if (Item *i = dynamic_cast<Item *> (me))
599 Item *parenti = dynamic_cast<Item *> (parent);
603 Direction my_dir = i->break_status_dir ();
604 if (my_dir != parenti->break_status_dir ())
606 Item *newparent = parenti->find_prebroken_piece (my_dir);
607 me->set_parent (newparent, ax);
616 Grob::warning (String s) const
618 SCM cause = self_scm ();
619 while (Grob *g = unsmob_grob (cause))
620 cause = g->get_property ("cause");
622 if (Music *m = unsmob_music (cause))
623 m->origin ()->warning (s);
629 Grob::programming_error (String s) const
631 s = "Programming error: " + s;
635 /****************************************************
637 ****************************************************/
639 IMPLEMENT_SMOBS (Grob);
640 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
643 Grob::mark_smob (SCM ses)
645 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
646 scm_gc_mark (s->immutable_property_alist_);
647 scm_gc_mark (s->key_->self_scm ());
648 for (int a = 0; a < 2; a++)
650 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
651 scm_gc_mark (s->dim_cache_[a].dimension_);
652 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
654 /* Do not mark the parents. The pointers in the mutable
655 property list form two tree like structures (one for X
656 relations, one for Y relations). Marking these can be done
657 in limited stack space. If we add the parents, we will jump
658 between X and Y in an erratic manner, leading to much more
659 recursion depth (and core dumps if we link to pthreads). */
663 scm_gc_mark (s->original_->self_scm ());
666 scm_gc_mark (s->pscore_->layout_->self_scm ());
668 s->do_derived_mark ();
669 return s->mutable_property_alist_;
673 Grob::print_smob (SCM s, SCM port, scm_print_state *)
675 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
677 scm_puts ("#<Grob ", port);
678 scm_puts ((char *) sc->name ().to_str0 (), port);
680 /* Do not print properties, that is too much hassle. */
681 scm_puts (" >", port);
686 Grob::do_derived_mark () const
692 Grob::discretionary_processing ()
697 Grob::internal_has_interface (SCM k)
699 SCM ifs = get_property ("interfaces");
701 return scm_c_memq (k, ifs) != SCM_BOOL_F;
705 Grob::get_parent (Axis a) const
707 return dim_cache_[a].parent_;
710 /** Return Array of Grobs in SCM list LST */
712 ly_scm2grobs (SCM lst)
714 Link_array<Grob> arr;
716 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
719 arr.push (unsmob_grob (e));
727 Grob::get_key () const
732 /** Return SCM list of Grob array A */
734 ly_grobs2scm (Link_array<Grob> a)
737 for (int i = a.size (); i; i--)
738 s = scm_cons (a[i - 1]->self_scm (), s);
743 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
745 ADD_INTERFACE (Grob, "grob-interface",
746 "A grob represents a piece of music notation\n"
748 "All grobs have an X and Y-position on the page. These X and Y positions\n"
749 "are stored in a relative format, so they can easily be combined by\n"
750 "stacking them, hanging one grob to the side of another, and coupling\n"
751 "them into a grouping objects.\n"
753 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
754 "is stored relative to that reference point. For example the X-reference\n"
755 "point of a staccato dot usually is the note head that it applies\n"
756 "to. When the note head is moved, the staccato dot moves along\n"
759 "A grob is often associated with a symbol, but some grobs do not print\n"
760 "any symbols. They take care of grouping objects. For example, there is a\n"
761 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
762 "is also an abstract grob: it only moves around chords, but doesn't print\n"
765 "Grobs have a properties: Scheme variables, that can be read and set. "
766 "They have two types. Immutable variables "
767 "define the default style and behavior. They are shared between many objects. "
768 "They can be changed using @code{\\override} and @code{\\revert}. "
770 "Mutable properties are variables that are specific to one grob. Typically, "
771 "lists of other objects, or results from computations are stored in"
772 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
773 "sets a mutable property. ",
774 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
775 "Y-extent-callback print-function extra-offset spacing-procedure "
776 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
777 "meta layer before-line-breaking-callback "
779 "axis-group-parent-X "
780 "axis-group-parent-Y "
781 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
782 "minimum-Y-extent transparent tweak-count tweak-rank");