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 = m->expr ();
244 if (point_and_click_global)
245 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
247 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
250 /* color support... see interpret_stencil_expression () for more... */
251 SCM color = get_property ("color");
252 if (color != SCM_EOL)
254 m = unsmob_stencil (stil);
255 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
259 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
270 Grob::do_break_processing ()
275 Grob::get_system () const
281 Grob::add_dependency (Grob *e)
284 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
286 programming_error ("null dependency added");
290 Grob::handle_broken_dependencies ()
292 Spanner *sp = dynamic_cast<Spanner *> (this);
297 /* THIS, SP is the original spanner. We use a special function
298 because some Spanners have enormously long lists in their
299 properties, and a special function fixes FOO */
300 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
301 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
303 System *system = get_system ();
306 && system && common_refpoint (system, X_AXIS)
307 && common_refpoint (system, Y_AXIS))
308 substitute_mutable_properties (system
309 ? system->self_scm () : SCM_UNDEFINED,
310 mutable_property_alist_);
311 else if (dynamic_cast<System *> (this))
312 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
314 /* THIS element is `invalid'; it has been removed from all
315 dependencies, so let's junk the element itself.
317 Do not do this for System, since that would remove references
318 to the originals of score-grobs, which get then GC'd (a bad
323 /* Note that we still want references to this element to be
324 rearranged, and not silently thrown away, so we keep pointers like
325 {broken_into_{drul, array}, original}
333 mutable_property_alist_ = SCM_EOL;
334 immutable_property_alist_ = SCM_EOL;
336 set_extent (SCM_EOL, Y_AXIS);
337 set_extent (SCM_EOL, X_AXIS);
339 set_extent_callback (SCM_EOL, Y_AXIS);
340 set_extent_callback (SCM_EOL, X_AXIS);
342 for (int a = X_AXIS; a <= Y_AXIS; a++)
344 dim_cache_[a].offset_callbacks_ = SCM_EOL;
345 dim_cache_[a].offsets_left_ = 0;
350 Grob::handle_prebroken_dependencies ()
352 /* Don't do this in the derived method, since we want to keep access to
353 mutable_property_alist_ centralized. */
356 Item *it = dynamic_cast<Item *> (this);
357 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
358 original_->mutable_property_alist_);
363 Grob::find_broken_piece (System *) const
368 /* Translate in one direction. */
370 Grob::translate_axis (Real y, Axis a)
372 if (isinf (y) || isnan (y))
373 programming_error (_ (INFINITY_MSG));
375 dim_cache_[a].offset_ += y;
378 /* Find the offset relative to D. If D equals THIS, then it is 0.
379 Otherwise, it recursively defd as
381 OFFSET_ + PARENT_L_->relative_coordinate (D) */
383 Grob::relative_coordinate (Grob const *refp, Axis a) const
388 /* We catch PARENT_L_ == nil case with this, but we crash if we did
389 not ask for the absolute coordinate (ie. REFP == nil.) */
390 if (refp == dim_cache_[a].parent_)
391 return get_offset (a);
393 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
396 /* Invoke callbacks to get offset relative to parent. */
398 Grob::get_offset (Axis a) const
400 Grob *me = (Grob *) this;
401 while (dim_cache_[a].offsets_left_)
403 int l = --me->dim_cache_[a].offsets_left_;
404 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
405 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
407 Real r = scm_to_double (retval);
408 if (isinf (r) || isnan (r))
410 programming_error (INFINITY_MSG);
413 me->dim_cache_[a].offset_ += r;
415 return dim_cache_[a].offset_;
419 Grob::is_empty (Axis a) const
421 return !(scm_is_pair (dim_cache_[a].dimension_)
422 || ly_c_procedure_p (dim_cache_[a].dimension_callback_));
426 Grob::flush_extent_cache (Axis axis)
428 Dimension_cache *d = &dim_cache_[axis];
429 if (ly_c_procedure_p (d->dimension_callback_)
430 && scm_is_pair (d->dimension_))
432 d->dimension_ = SCM_EOL;
434 if (get_parent (axis))
435 get_parent (axis)->flush_extent_cache (axis);
440 Grob::extent (Grob *refp, Axis a) const
442 Real x = relative_coordinate (refp, a);
444 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
447 SCM dimpair = d->dimension_;
448 if (scm_is_pair (dimpair))
450 else if (ly_c_procedure_p (d->dimension_callback_)
451 && d->dimension_ == SCM_EOL)
452 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
456 if (!scm_is_pair (d->dimension_))
459 ext = ly_scm2interval (d->dimension_);
461 SCM extra = get_property (a == X_AXIS
466 if (scm_is_pair (extra))
468 ext[BIGGER] += scm_to_double (scm_cdr (extra));
469 ext[SMALLER] += scm_to_double (scm_car (extra));
472 extra = get_property (a == X_AXIS
474 : "minimum-Y-extent");
475 if (scm_is_pair (extra))
476 ext.unite (Interval (scm_to_double (scm_car (extra)),
477 scm_to_double (scm_cdr (extra))));
484 /* Find the group-element which has both #this# and #s# */
486 Grob::common_refpoint (Grob const *s, Axis a) const
488 /* I don't like the quadratic aspect of this code, but I see no
489 other way. The largest chain of parents might be 10 high or so,
490 so it shouldn't be a real issue. */
491 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
492 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
500 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
502 for (; scm_is_pair (elist); elist = scm_cdr (elist))
503 if (Grob *s = unsmob_grob (scm_car (elist)))
506 common = common->common_refpoint (s, a);
515 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
517 for (int i = arr.size (); i--;)
518 if (Grob *s = arr[i])
521 common = common->common_refpoint (s, a);
532 SCM meta = get_property ("meta");
533 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
534 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
535 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
539 Grob::add_offset_callback (SCM cb, Axis a)
541 if (!has_offset_callback (cb, a))
543 dim_cache_[a].offset_callbacks_
544 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
545 dim_cache_[a].offsets_left_++;
550 Grob::has_extent_callback (SCM cb, Axis a) const
552 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
556 Grob::has_offset_callback (SCM cb, Axis a) const
558 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
562 Grob::set_extent (SCM dc, Axis a)
564 dim_cache_[a].dimension_ = dc;
568 Grob::set_extent_callback (SCM dc, Axis a)
570 dim_cache_[a].dimension_callback_ = dc;
574 Grob::set_parent (Grob *g, Axis a)
576 dim_cache_[a].parent_ = g;
579 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
581 Grob::fixup_refpoint (SCM smob)
583 Grob *me = unsmob_grob (smob);
584 for (int a = X_AXIS; a < NO_AXES; a++)
587 Grob *parent = me->get_parent (ax);
592 if (parent->get_system () != me->get_system () && me->get_system ())
594 Grob *newparent = parent->find_broken_piece (me->get_system ());
595 me->set_parent (newparent, ax);
598 if (Item *i = dynamic_cast<Item *> (me))
600 Item *parenti = dynamic_cast<Item *> (parent);
604 Direction my_dir = i->break_status_dir ();
605 if (my_dir != parenti->break_status_dir ())
607 Item *newparent = parenti->find_prebroken_piece (my_dir);
608 me->set_parent (newparent, ax);
617 Grob::warning (String s) const
619 SCM cause = self_scm ();
620 while (Grob *g = unsmob_grob (cause))
621 cause = g->get_property ("cause");
623 if (Music *m = unsmob_music (cause))
624 m->origin ()->warning (s);
630 Grob::programming_error (String s) const
632 s = _f ("programming error: %s", s);
636 /****************************************************
638 ****************************************************/
640 IMPLEMENT_SMOBS (Grob);
641 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
644 Grob::mark_smob (SCM ses)
646 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
647 scm_gc_mark (s->immutable_property_alist_);
648 scm_gc_mark (s->key_->self_scm ());
649 for (int a = 0; a < 2; a++)
651 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
652 scm_gc_mark (s->dim_cache_[a].dimension_);
653 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
655 /* Do not mark the parents. The pointers in the mutable
656 property list form two tree like structures (one for X
657 relations, one for Y relations). Marking these can be done
658 in limited stack space. If we add the parents, we will jump
659 between X and Y in an erratic manner, leading to much more
660 recursion depth (and core dumps if we link to pthreads). */
664 scm_gc_mark (s->original_->self_scm ());
666 if (s->pscore_ && s->pscore_->layout ())
667 scm_gc_mark (s->pscore_->layout ()->self_scm ());
669 s->do_derived_mark ();
670 return s->mutable_property_alist_;
674 Grob::print_smob (SCM s, SCM port, scm_print_state *)
676 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
678 scm_puts ("#<Grob ", port);
679 scm_puts ((char *) sc->name ().to_str0 (), port);
681 /* Do not print properties, that is too much hassle. */
682 scm_puts (" >", port);
687 Grob::do_derived_mark () const
693 Grob::discretionary_processing ()
698 Grob::internal_has_interface (SCM k)
700 SCM ifs = get_property ("interfaces");
702 return scm_c_memq (k, ifs) != SCM_BOOL_F;
706 Grob::get_parent (Axis a) const
708 return dim_cache_[a].parent_;
711 /** Return Array of Grobs in SCM list LST */
713 ly_scm2grobs (SCM lst)
715 Link_array<Grob> arr;
717 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
720 arr.push (unsmob_grob (e));
728 Grob::get_key () const
733 /** Return SCM list of Grob array A */
735 ly_grobs2scm (Link_array<Grob> a)
738 for (int i = a.size (); i; i--)
739 s = scm_cons (a[i - 1]->self_scm (), s);
744 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
746 ADD_INTERFACE (Grob, "grob-interface",
747 "A grob represents a piece of music notation\n"
749 "All grobs have an X and Y-position on the page. These X and Y positions\n"
750 "are stored in a relative format, so they can easily be combined by\n"
751 "stacking them, hanging one grob to the side of another, and coupling\n"
752 "them into a grouping objects.\n"
754 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
755 "is stored relative to that reference point. For example the X-reference\n"
756 "point of a staccato dot usually is the note head that it applies\n"
757 "to. When the note head is moved, the staccato dot moves along\n"
760 "A grob is often associated with a symbol, but some grobs do not print\n"
761 "any symbols. They take care of grouping objects. For example, there is a\n"
762 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
763 "is also an abstract grob: it only moves around chords, but doesn't print\n"
766 "Grobs have a properties: Scheme variables, that can be read and set. "
767 "They have two types. Immutable variables "
768 "define the default style and behavior. They are shared between many objects. "
769 "They can be changed using @code{\\override} and @code{\\revert}. "
771 "Mutable properties are variables that are specific to one grob. Typically, "
772 "lists of other objects, or results from computations are stored in"
773 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
774 "sets a mutable property. ",
775 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
776 "Y-extent-callback print-function extra-offset spacing-procedure "
777 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
778 "meta layer before-line-breaking-callback "
780 "axis-group-parent-X "
781 "axis-group-parent-Y "
782 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
783 "minimum-Y-extent transparent tweak-count tweak-rank");