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>
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 = internal_type_checking_global_b;
73 internal_type_checking_global_b = false;
74 internal_set_property (ly_symbol2scm ("interfaces"), scm_cdr (ifs));
75 internal_type_checking_global_b = 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 SCM xt = get_property (xnames[a]);
104 /* Should change default to empty? */
105 if (is_number_pair (xt))
107 else if (cb != SCM_BOOL_F
108 && !ly_c_procedure_p (cb) && !scm_is_pair (cb)
109 && ly_c_procedure_p (get_property ("print-function")))
110 cb = stencil_extent_proc;
112 dim_cache_[a].dimension_ = cb;
116 Grob::Grob (Grob const &s, int copy_index)
117 : dim_cache_ (s.dim_cache_)
119 key_ = new Copied_key (s.key_, copy_index);
120 original_ = (Grob*) &s;
123 immutable_property_alist_ = s.immutable_property_alist_;
124 mutable_property_alist_ = SCM_EOL;
126 /* No properties are copied. That is the job of
127 handle_broken_dependencies. */
132 scm_gc_unprotect_object (key_->self_scm ());
139 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
141 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
143 Grob *s = unsmob_grob (element_smob);
144 Axis a = (Axis) scm_to_int (scm_axis);
146 Stencil *m = s->get_stencil ();
150 return ly_interval2scm (e);
155 robust_relative_extent (Grob*me, Grob*refp, Axis a)
157 Interval ext = me->extent (refp, a);
160 ext.add_point (me->relative_coordinate (refp, a));
167 Grob::get_layout () const
169 return pscore_ ? pscore_->layout_ : 0;
173 /* Recursively track all dependencies of this Grob. The status_ field
174 is used as a mark-field. It is marked with BUSY during execution
175 of this function, and marked with FINAL when finished.
177 FUNCPTR is the function to call to update this element. */
179 Grob::calculate_dependencies (int final, int busy, SCM funcname)
181 if (status_ >= final)
186 programming_error ("Element is busy, come back later");
192 for (SCM d = get_property ("dependencies"); scm_is_pair (d);
194 unsmob_grob (scm_car (d))->calculate_dependencies (final, busy, funcname);
196 SCM proc = internal_get_property (funcname);
197 if (ly_c_procedure_p (proc))
198 scm_call_1 (proc, this->self_scm ());
204 Grob::get_stencil () const
209 SCM stil = get_property ("stencil");
210 if (unsmob_stencil (stil))
211 return unsmob_stencil (stil);
213 stil = get_uncached_stencil ();
217 Grob *me = (Grob*) this;
218 me->set_property ("stencil", stil);
221 return unsmob_stencil (stil);
225 Grob::get_uncached_stencil () const
227 SCM proc = get_property ("print-function");
230 if (ly_c_procedure_p (proc))
231 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
233 if (Stencil *m = unsmob_stencil (stil))
235 if (to_boolean (get_property ("transparent")))
236 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
239 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm(),
241 stil = Stencil (m->extent_box (),expr). smobbed_copy ();
252 Grob::do_break_processing ()
257 Grob::get_system () const
263 Grob::add_dependency (Grob *e)
266 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
269 programming_error ("Null dependency added");
273 Grob::handle_broken_dependencies ()
275 Spanner *sp = dynamic_cast<Spanner*> (this);
280 /* THIS, SP is the original spanner. We use a special function
281 because some Spanners have enormously long lists in their
282 properties, and a special function fixes FOO */
283 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
284 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
286 System *system = get_system ();
289 && system && common_refpoint (system, X_AXIS)
290 && common_refpoint (system, Y_AXIS))
291 substitute_mutable_properties (system
292 ? system->self_scm () : SCM_UNDEFINED,
293 mutable_property_alist_);
294 else if (dynamic_cast <System*> (this))
295 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
297 /* THIS element is `invalid'; it has been removed from all
298 dependencies, so let's junk the element itself.
300 Do not do this for System, since that would remove references
301 to the originals of score-grobs, which get then GC'd (a bad
306 /* Note that we still want references to this element to be
307 rearranged, and not silently thrown away, so we keep pointers like
308 {broken_into_{drul, array}, original}
316 mutable_property_alist_ = SCM_EOL;
317 immutable_property_alist_ = SCM_EOL;
319 set_extent (SCM_EOL, Y_AXIS);
320 set_extent (SCM_EOL, X_AXIS);
322 for (int a = X_AXIS; a <= Y_AXIS; a++)
324 dim_cache_[a].offset_callbacks_ = SCM_EOL;
325 dim_cache_[a].offsets_left_ = 0;
330 Grob::handle_prebroken_dependencies ()
332 /* Don't do this in the derived method, since we want to keep access to
333 mutable_property_alist_ centralized. */
336 Item *it = dynamic_cast<Item*> (this);
337 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
338 original_->mutable_property_alist_);
343 Grob::find_broken_piece (System *) const
348 /* Translate in one direction. */
350 Grob::translate_axis (Real y, Axis a)
352 if (isinf (y) || isnan (y))
353 programming_error (_ (INFINITY_MSG));
355 dim_cache_[a].offset_ += y;
359 /* Find the offset relative to D. If D equals THIS, then it is 0.
360 Otherwise, it recursively defd as
362 OFFSET_ + PARENT_L_->relative_coordinate (D) */
364 Grob::relative_coordinate (Grob const *refp, Axis a) const
369 /* We catch PARENT_L_ == nil case with this, but we crash if we did
370 not ask for the absolute coordinate (ie. REFP == nil.) */
371 if (refp == dim_cache_[a].parent_)
372 return get_offset (a);
374 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
377 /* Invoke callbacks to get offset relative to parent. */
379 Grob::get_offset (Axis a) const
381 Grob *me = (Grob*) this;
382 while (dim_cache_[a].offsets_left_)
384 int l = --me->dim_cache_[a].offsets_left_;
385 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
386 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
388 Real r = scm_to_double (retval);
389 if (isinf (r) || isnan (r))
391 programming_error (INFINITY_MSG);
394 me->dim_cache_[a].offset_ += r;
396 return dim_cache_[a].offset_;
400 Grob::is_empty (Axis a) const
402 return ! (scm_is_pair (dim_cache_[a].dimension_)
403 || ly_c_procedure_p (dim_cache_[a].dimension_));
407 Grob::extent (Grob *refp, Axis a) const
409 Real x = relative_coordinate (refp, a);
411 Dimension_cache *d = (Dimension_cache *) &dim_cache_[a];
413 if (scm_is_pair (d->dimension_))
415 else if (ly_c_procedure_p (d->dimension_))
416 /* FIXME: add doco on types, and should typecheck maybe? */
417 d->dimension_ = scm_call_2 (d->dimension_, self_scm (), scm_int2num (a));
421 if (!scm_is_pair (d->dimension_))
424 ext = ly_scm2interval (d->dimension_);
426 SCM extra = get_property (a == X_AXIS
431 if (scm_is_pair (extra))
433 ext[BIGGER] += scm_to_double (scm_cdr (extra));
434 ext[SMALLER] += scm_to_double (scm_car (extra));
437 extra = get_property (a == X_AXIS
439 : "minimum-Y-extent");
440 if (scm_is_pair (extra))
441 ext.unite (Interval (scm_to_double (scm_car (extra)),
442 scm_to_double (scm_cdr (extra))));
449 /* Find the group-element which has both #this# and #s# */
451 Grob::common_refpoint (Grob const *s, Axis a) const
453 /* I don't like the quadratic aspect of this code, but I see no
454 other way. The largest chain of parents might be 10 high or so,
455 so it shouldn't be a real issue. */
456 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
457 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
465 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
467 for (; scm_is_pair (elist); elist = scm_cdr (elist))
468 if (Grob *s = unsmob_grob (scm_car (elist)))
471 common = common->common_refpoint (s, a);
480 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
482 for (int i = arr.size (); i--; )
483 if (Grob *s = arr[i])
486 common = common->common_refpoint (s, a);
497 SCM meta = get_property ("meta");
498 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
499 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
500 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
504 Grob::add_offset_callback (SCM cb, Axis a)
506 if (!has_offset_callback (cb, a))
508 dim_cache_[a].offset_callbacks_
509 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
510 dim_cache_[a].offsets_left_ ++;
515 Grob::has_extent_callback (SCM cb, Axis a)const
517 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
521 Grob::has_offset_callback (SCM cb, Axis a)const
523 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
527 Grob::set_extent (SCM dc, Axis a)
529 dim_cache_[a].dimension_ = dc;
533 Grob::set_parent (Grob *g, Axis a)
535 dim_cache_[a].parent_ = g;
538 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
540 Grob::fixup_refpoint (SCM smob)
542 Grob *me = unsmob_grob (smob);
543 for (int a = X_AXIS; a < NO_AXES; a ++)
546 Grob *parent = me->get_parent (ax);
551 if (parent->get_system () != me->get_system () && me->get_system ())
553 Grob *newparent = parent->find_broken_piece (me->get_system ());
554 me->set_parent (newparent, ax);
557 if (Item *i = dynamic_cast<Item*> (me))
559 Item *parenti = dynamic_cast<Item*> (parent);
563 Direction my_dir = i->break_status_dir () ;
564 if (my_dir!= parenti->break_status_dir ())
566 Item *newparent = parenti->find_prebroken_piece (my_dir);
567 me->set_parent (newparent, ax);
576 Grob::warning (String s)const
578 SCM cause = self_scm ();
579 while (Grob *g = unsmob_grob (cause))
580 cause = g->get_property ("cause");
582 if (Music *m = unsmob_music (cause))
583 m->origin ()->warning (s);
589 Grob::programming_error (String s) const
591 s = "Programming error: " + s;
596 /****************************************************
598 ****************************************************/
600 IMPLEMENT_SMOBS (Grob);
601 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
604 Grob::mark_smob (SCM ses)
606 Grob *s = (Grob*) SCM_CELL_WORD_1 (ses);
607 scm_gc_mark (s->immutable_property_alist_);
608 scm_gc_mark (s->key_->self_scm ());
609 for (int a = 0 ; a < 2; a++)
611 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
612 scm_gc_mark (s->dim_cache_[a].dimension_);
614 /* Do not mark the parents. The pointers in the mutable
615 property list form two tree like structures (one for X
616 relations, one for Y relations). Marking these can be done
617 in limited stack space. If we add the parents, we will jump
618 between X and Y in an erratic manner, leading to much more
619 recursion depth (and core dumps if we link to pthreads). */
623 scm_gc_mark (s->original_->self_scm ());
625 s->do_derived_mark ();
626 return s->mutable_property_alist_;
630 Grob::print_smob (SCM s, SCM port, scm_print_state *)
632 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
634 scm_puts ("#<Grob ", port);
635 scm_puts ((char *) sc->name ().to_str0 (), port);
637 /* Do not print properties, that is too much hassle. */
638 scm_puts (" >", port);
643 Grob::do_derived_mark () const
649 Grob::discretionary_processing ()
656 Grob::internal_has_interface (SCM k)
658 SCM ifs = get_property ("interfaces");
660 return scm_c_memq (k, ifs) != SCM_BOOL_F;
664 Grob::get_parent (Axis a) const
666 return dim_cache_[a].parent_;
670 /** Return Array of Grobs in SCM list LST */
672 ly_scm2grobs (SCM lst)
674 Link_array<Grob> arr;
676 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
679 arr.push (unsmob_grob (e));
687 Grob::get_key () const
692 /** Return SCM list of Grob array A */
694 ly_grobs2scm (Link_array<Grob> a)
697 for (int i = a.size (); i; i--)
698 s = scm_cons (a[i-1]->self_scm (), s);
704 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
706 ADD_INTERFACE (Grob, "grob-interface",
707 "A grob represents a piece of music notation\n"
709 "All grobs have an X and Y-position on the page. These X and Y positions\n"
710 "are stored in a relative format, so they can easily be combined by\n"
711 "stacking them, hanging one grob to the side of another, and coupling\n"
712 "them into a grouping objects.\n"
714 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
715 "is stored relative to that reference point. For example the X-reference\n"
716 "point of a staccato dot usually is the note head that it applies\n"
717 "to. When the note head is moved, the staccato dot moves along\n"
720 "A grob is often associated with a symbol, but some grobs do not print\n"
721 "any symbols. They take care of grouping objects. For example, there is a\n"
722 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
723 "is also an abstract grob: it only moves around chords, but doesn't print\n"
726 "Grobs have a properties: Scheme variables, that can be read and set. "
727 "They have two types. Immutable variables "
728 "define the default style and behavior. They are shared between many objects. "
729 "They can be changed using @code{\\override} and @code{\\revert}. "
731 "Mutable properties are variables that are specific to one grob. Typically, "
732 "lists of other objects, or results from computations are stored in"
733 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
734 "sets a mutable property. "
737 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
738 "Y-extent-callback print-function extra-offset spacing-procedure "
739 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
740 "meta layer before-line-breaking-callback "
741 "axis-group-parent-X "
742 "axis-group-parent-Y "
743 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
744 "minimum-Y-extent transparent tweak-count tweak-rank"