2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
14 #include "input-smob.hh"
16 #include "group-interface.hh"
18 #include "paper-score.hh"
30 #include "ly-smobs.icc"
34 - remove dynamic_cast<Spanner,Item> and put this code into respective
38 #define INFINITY_MSG "Infinity or NaN encountered"
40 Grob::Grob (SCM basicprops)
42 /* FIXME: default should be no callback. */
47 immutable_property_alist_ = basicprops;
48 mutable_property_alist_ = SCM_EOL;
50 /* We do smobify_self () as the first step. Since the object lives
51 on the heap, none of its SCM variables are protected from
52 GC. After smobify_self (), they are. */
55 SCM meta = get_property ("meta");
56 if (ly_c_pair_p (meta))
58 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
60 /* Switch off interface checks for the moment. */
61 bool itc = internal_type_checking_global_b;
62 internal_type_checking_global_b = false;
63 internal_set_property (ly_symbol2scm ("interfaces"), ly_cdr (ifs));
64 internal_type_checking_global_b = itc;
69 - destill this into a function, so we can re-init the immutable
70 properties with a new BASICPROPS value after
71 creation. Convenient eg. when using \override with
74 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
75 char const*xnames[] = {"X-extent", "Y-extent"};
76 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
78 for (int a = X_AXIS; a <= Y_AXIS; a++)
80 SCM l = get_property (onames[a]);
82 if (scm_ilength (l) >=0)
84 dim_cache_[a].offset_callbacks_ = l;
85 dim_cache_[a].offsets_left_ = scm_ilength (l);
88 programming_error ("[XY]-offset-callbacks must be a list");
90 SCM cb = get_property (enames[a]);
91 SCM xt = get_property (xnames[a]);
93 /* Should change default to empty? */
94 if (is_number_pair (xt))
96 else if (cb != SCM_BOOL_F
97 && !ly_c_procedure_p (cb) && !ly_c_pair_p (cb)
98 && ly_c_procedure_p (get_property ("print-function")))
99 cb = stencil_extent_proc;
101 dim_cache_[a].dimension_ = cb;
105 Grob::Grob (Grob const &s)
106 : dim_cache_ (s.dim_cache_)
108 original_ = (Grob*) &s;
111 immutable_property_alist_ = s.immutable_property_alist_;
112 mutable_property_alist_ = SCM_EOL;
114 /* No properties are copied. That is the job of
115 handle_broken_dependencies. */
126 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
128 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
130 Grob *s = unsmob_grob (element_smob);
131 Axis a = (Axis) ly_scm2int (scm_axis);
133 Stencil *m = s->get_stencil ();
137 return ly_interval2scm (e);
141 Grob::get_paper () const
143 return pscore_ ? pscore_->paper_ : 0;
147 /* Recursively track all dependencies of this Grob. The status_ field
148 is used as a mark-field. It is marked with BUSY during execution
149 of this function, and marked with FINAL when finished.
151 FUNCPTR is the function to call to update this element. */
153 Grob::calculate_dependencies (int final, int busy, SCM funcname)
155 if (status_ >= final)
160 programming_error ("Element is busy, come back later");
166 for (SCM d = get_property ("dependencies"); ly_c_pair_p (d);
168 unsmob_grob (ly_car (d))->calculate_dependencies (final, busy, funcname);
170 SCM proc = internal_get_property (funcname);
171 if (ly_c_procedure_p (proc))
172 scm_call_1 (proc, this->self_scm ());
178 Grob::get_stencil () const
183 SCM stil = get_property ("stencil");
184 if (unsmob_stencil (stil))
185 return unsmob_stencil (stil);
187 stil = get_uncached_stencil ();
191 Grob *me = (Grob*) this;
192 me->set_property ("stencil", stil);
195 return unsmob_stencil (stil);
199 Grob::get_uncached_stencil () const
201 SCM proc = get_property ("print-function");
204 if (ly_c_procedure_p (proc))
205 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
207 if (Stencil *m = unsmob_stencil (stil))
209 if (to_boolean (get_property ("transparent")))
210 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
213 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm(),
215 stil = Stencil (m->extent_box (),expr). smobbed_copy ();
227 Grob::do_break_processing ()
232 Grob::get_system () const
238 Grob::add_dependency (Grob *e)
241 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
244 programming_error ("Null dependency added");
248 Grob::handle_broken_dependencies ()
250 Spanner *sp = dynamic_cast<Spanner*> (this);
255 /* THIS, SP is the original spanner. We use a special function
256 because some Spanners have enormously long lists in their
257 properties, and a special function fixes FOO */
258 for (SCM s = mutable_property_alist_; ly_c_pair_p (s); s = ly_cdr (s))
259 sp->substitute_one_mutable_property (ly_caar (s), ly_cdar (s));
261 System *system = get_system ();
264 && system && common_refpoint (system, X_AXIS)
265 && common_refpoint (system, Y_AXIS))
266 substitute_mutable_properties (system
267 ? system->self_scm () : SCM_UNDEFINED,
268 mutable_property_alist_);
269 else if (dynamic_cast <System*> (this))
270 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
272 /* THIS element is `invalid'; it has been removed from all
273 dependencies, so let's junk the element itself.
275 Do not do this for System, since that would remove references
276 to the originals of score-grobs, which get then GC'd (a bad
281 /* Note that we still want references to this element to be
282 rearranged, and not silently thrown away, so we keep pointers like
283 {broken_into_{drul, array}, original}
291 mutable_property_alist_ = SCM_EOL;
292 immutable_property_alist_ = SCM_EOL;
294 set_extent (SCM_EOL, Y_AXIS);
295 set_extent (SCM_EOL, X_AXIS);
297 for (int a = X_AXIS; a <= Y_AXIS; a++)
299 dim_cache_[a].offset_callbacks_ = SCM_EOL;
300 dim_cache_[a].offsets_left_ = 0;
305 Grob::handle_prebroken_dependencies ()
307 /* Don't do this in the derived method, since we want to keep access to
308 mutable_property_alist_ centralized. */
311 Item *it = dynamic_cast<Item*> (this);
312 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
313 original_->mutable_property_alist_);
318 Grob::find_broken_piece (System *) const
323 /* Translate in one direction. */
325 Grob::translate_axis (Real y, Axis a)
327 if (isinf (y) || isnan (y))
328 programming_error (_ (INFINITY_MSG));
330 dim_cache_[a].offset_ += y;
334 /* Find the offset relative to D. If D equals THIS, then it is 0.
335 Otherwise, it recursively defd as
337 OFFSET_ + PARENT_L_->relative_coordinate (D) */
339 Grob::relative_coordinate (Grob const *refp, Axis a) const
344 /* We catch PARENT_L_ == nil case with this, but we crash if we did
345 not ask for the absolute coordinate (ie. REFP == nil.) */
346 if (refp == dim_cache_[a].parent_)
347 return get_offset (a);
349 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
352 /* Invoke callbacks to get offset relative to parent. */
354 Grob::get_offset (Axis a) const
356 Grob *me = (Grob*) this;
357 while (dim_cache_[a].offsets_left_)
359 int l = --me->dim_cache_[a].offsets_left_;
360 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
361 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
363 Real r = ly_scm2double (retval);
364 if (isinf (r) || isnan (r))
366 programming_error (INFINITY_MSG);
369 me->dim_cache_[a].offset_ += r;
371 return dim_cache_[a].offset_;
375 Grob::is_empty (Axis a) const
377 return ! (ly_c_pair_p (dim_cache_[a].dimension_)
378 || ly_c_procedure_p (dim_cache_[a].dimension_));
382 Grob::extent (Grob *refp, Axis a) const
384 Real x = relative_coordinate (refp, a);
386 Dimension_cache *d = (Dimension_cache *) &dim_cache_[a];
388 if (ly_c_pair_p (d->dimension_))
390 else if (ly_c_procedure_p (d->dimension_))
391 /* FIXME: add doco on types, and should typecheck maybe? */
392 d->dimension_= scm_call_2 (d->dimension_, self_scm (), scm_int2num (a));
396 if (!ly_c_pair_p (d->dimension_))
399 ext = ly_scm2interval (d->dimension_);
401 SCM extra = get_property (a == X_AXIS
406 if (ly_c_pair_p (extra))
408 ext[BIGGER] += ly_scm2double (ly_cdr (extra));
409 ext[SMALLER] += ly_scm2double (ly_car (extra));
412 extra = get_property (a == X_AXIS
414 : "minimum-Y-extent");
415 if (ly_c_pair_p (extra))
416 ext.unite (Interval (ly_scm2double (ly_car (extra)),
417 ly_scm2double (ly_cdr (extra))));
424 /* Find the group-element which has both #this# and #s# */
426 Grob::common_refpoint (Grob const *s, Axis a) const
428 /* I don't like the quadratic aspect of this code, but I see no
429 other way. The largest chain of parents might be 10 high or so,
430 so it shouldn't be a real issue. */
431 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
432 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
440 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
442 for (; ly_c_pair_p (elist); elist = ly_cdr (elist))
443 if (Grob *s = unsmob_grob (ly_car (elist)))
446 common = common->common_refpoint (s, a);
455 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
457 for (int i = arr.size (); i--; )
458 if (Grob *s = arr[i])
461 common = common->common_refpoint (s, a);
472 SCM meta = get_property ("meta");
473 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
474 nm = (ly_c_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
475 return ly_c_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
479 Grob::add_offset_callback (SCM cb, Axis a)
481 if (!has_offset_callback (cb, a))
483 dim_cache_[a].offset_callbacks_
484 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
485 dim_cache_[a].offsets_left_ ++;
490 Grob::has_extent_callback (SCM cb, Axis a)const
492 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
496 Grob::has_offset_callback (SCM cb, Axis a)const
498 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
502 Grob::set_extent (SCM dc, Axis a)
504 dim_cache_[a].dimension_ = dc;
508 Grob::set_parent (Grob *g, Axis a)
510 dim_cache_[a].parent_ = g;
513 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
515 Grob::fixup_refpoint (SCM smob)
517 Grob *me = unsmob_grob (smob);
518 for (int a = X_AXIS; a < NO_AXES; a ++)
521 Grob *parent = me->get_parent (ax);
526 if (parent->get_system () != me->get_system () && me->get_system ())
528 Grob *newparent = parent->find_broken_piece (me->get_system ());
529 me->set_parent (newparent, ax);
532 if (Item *i = dynamic_cast<Item*> (me))
534 Item *parenti = dynamic_cast<Item*> (parent);
538 Direction my_dir = i->break_status_dir () ;
539 if (my_dir!= parenti->break_status_dir ())
541 Item *newparent = parenti->find_prebroken_piece (my_dir);
542 me->set_parent (newparent, ax);
551 Grob::warning (String s)const
553 SCM cause = self_scm ();
554 while (Grob *g = unsmob_grob (cause))
555 cause = g->get_property ("cause");
557 if (Music *m = unsmob_music (cause))
558 m->origin ()->warning (s);
564 Grob::programming_error (String s) const
566 s = "Programming error: " + s;
571 /****************************************************
573 ****************************************************/
575 IMPLEMENT_SMOBS (Grob);
576 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
579 Grob::mark_smob (SCM ses)
581 Grob *s = (Grob*) SCM_CELL_WORD_1 (ses);
582 scm_gc_mark (s->immutable_property_alist_);
584 for (int a = 0 ; a < 2; a++)
586 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
587 scm_gc_mark (s->dim_cache_[a].dimension_);
589 /* Do not mark the parents. The pointers in the mutable
590 property list form two tree like structures (one for X
591 relations, one for Y relations). Marking these can be done
592 in limited stack space. If we add the parents, we will jump
593 between X and Y in an erratic manner, leading to much more
594 recursion depth (and core dumps if we link to pthreads). */
598 scm_gc_mark (s->original_->self_scm ());
600 s->do_derived_mark ();
601 return s->mutable_property_alist_;
605 Grob::print_smob (SCM s, SCM port, scm_print_state *)
607 Grob *sc = (Grob *) ly_cdr (s);
609 scm_puts ("#<Grob ", port);
610 scm_puts ((char *) sc->name ().to_str0 (), port);
612 /* Do not print properties, that is too much hassle. */
613 scm_puts (" >", port);
618 Grob::do_derived_mark () const
624 Grob::discretionary_processing ()
629 Grob::internal_has_interface (SCM k)
631 SCM ifs = get_property ("interfaces");
633 return scm_c_memq (k, ifs) != SCM_BOOL_F;
636 /** Return Array of Grobs in SCM list LST */
638 ly_scm2grobs (SCM lst)
640 Link_array<Grob> arr;
642 for (SCM s = lst; ly_c_pair_p (s); s = ly_cdr (s))
645 arr.push (unsmob_grob (e));
652 /** Return SCM list of Grob array A */
654 ly_grobs2scm (Link_array<Grob> a)
657 for (int i = a.size (); i; i--)
658 s = scm_cons (a[i-1]->self_scm (), s);
664 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
666 ADD_INTERFACE (Grob, "grob-interface",
667 "A grob represents a piece of music notation\n"
669 "All grobs have an X and Y-position on the page. These X and Y positions\n"
670 "are stored in a relative format, so they can easily be combined by\n"
671 "stacking them, hanging one grob to the side of another, and coupling\n"
672 "them into a grouping objects.\n"
674 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
675 "is stored relative to that reference point. For example the X-reference\n"
676 "point of a staccato dot usually is the note head that it applies\n"
677 "to. When the note head is moved, the staccato dot moves along\n"
680 "A grob is often associated with a symbol, but some grobs do not print\n"
681 "any symbols. They take care of grouping objects. For example, there is a\n"
682 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
683 "is also an abstract grob: it only moves around chords, but doesn't print\n"
686 "Grobs have a properties: Scheme variables, that can be read and set. "
687 "They have two types. Immutable variables "
688 "define the default style and behavior. They are shared between many objects. "
689 "They can be changed using @code{\\override} and @code{\\revert}. "
691 "Mutable properties are variables that are specific to one grob. Typically, "
692 "lists of other objects, or results from computations are stored in"
693 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
694 "sets a mutable property. "
697 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
698 "Y-extent-callback print-function extra-offset spacing-procedure "
699 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
700 "meta layer before-line-breaking-callback "
701 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
702 // FIXME: page-penalty?
703 "minimum-Y-extent page-penalty transparent "