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 ();
244 SCM expr = m->expr ();
245 if (point_and_click_global)
246 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
248 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
251 /* color support... see interpret_stencil_expression () for more... */
252 SCM color = get_property ("color");
253 if (color != SCM_EOL)
255 m = unsmob_stencil (stil);
256 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
260 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
271 Grob::do_break_processing ()
276 Grob::get_system () const
282 Grob::add_dependency (Grob *e)
285 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
288 programming_error ("Null dependency added");
292 Grob::handle_broken_dependencies ()
294 Spanner *sp = dynamic_cast<Spanner *> (this);
299 /* THIS, SP is the original spanner. We use a special function
300 because some Spanners have enormously long lists in their
301 properties, and a special function fixes FOO */
302 for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
303 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
305 System *system = get_system ();
308 && system && common_refpoint (system, X_AXIS)
309 && common_refpoint (system, Y_AXIS))
310 substitute_mutable_properties (system
311 ? system->self_scm () : SCM_UNDEFINED,
312 mutable_property_alist_);
313 else if (dynamic_cast<System *> (this))
314 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
316 /* THIS element is `invalid'; it has been removed from all
317 dependencies, so let's junk the element itself.
319 Do not do this for System, since that would remove references
320 to the originals of score-grobs, which get then GC'd (a bad
325 /* Note that we still want references to this element to be
326 rearranged, and not silently thrown away, so we keep pointers like
327 {broken_into_{drul, array}, original}
335 mutable_property_alist_ = SCM_EOL;
336 immutable_property_alist_ = SCM_EOL;
338 set_extent (SCM_EOL, Y_AXIS);
339 set_extent (SCM_EOL, X_AXIS);
341 set_extent_callback (SCM_EOL, Y_AXIS);
342 set_extent_callback (SCM_EOL, X_AXIS);
344 for (int a = X_AXIS; a <= Y_AXIS; a++)
346 dim_cache_[a].offset_callbacks_ = SCM_EOL;
347 dim_cache_[a].offsets_left_ = 0;
352 Grob::handle_prebroken_dependencies ()
354 /* Don't do this in the derived method, since we want to keep access to
355 mutable_property_alist_ centralized. */
358 Item *it = dynamic_cast<Item *> (this);
359 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
360 original_->mutable_property_alist_);
365 Grob::find_broken_piece (System *) const
370 /* Translate in one direction. */
372 Grob::translate_axis (Real y, Axis a)
374 if (isinf (y) || isnan (y))
375 programming_error (_ (INFINITY_MSG));
377 dim_cache_[a].offset_ += y;
380 /* Find the offset relative to D. If D equals THIS, then it is 0.
381 Otherwise, it recursively defd as
383 OFFSET_ + PARENT_L_->relative_coordinate (D) */
385 Grob::relative_coordinate (Grob const *refp, Axis a) const
390 /* We catch PARENT_L_ == nil case with this, but we crash if we did
391 not ask for the absolute coordinate (ie. REFP == nil.) */
392 if (refp == dim_cache_[a].parent_)
393 return get_offset (a);
395 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
398 /* Invoke callbacks to get offset relative to parent. */
400 Grob::get_offset (Axis a) const
402 Grob *me = (Grob *) this;
403 while (dim_cache_[a].offsets_left_)
405 int l = --me->dim_cache_[a].offsets_left_;
406 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
407 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
409 Real r = scm_to_double (retval);
410 if (isinf (r) || isnan (r))
412 programming_error (INFINITY_MSG);
415 me->dim_cache_[a].offset_ += r;
417 return dim_cache_[a].offset_;
421 Grob::is_empty (Axis a) const
423 return !(scm_is_pair (dim_cache_[a].dimension_)
424 || ly_c_procedure_p (dim_cache_[a].dimension_callback_));
428 Grob::flush_extent_cache (Axis axis)
430 Dimension_cache *d = &dim_cache_[axis];
431 if (ly_c_procedure_p (d->dimension_callback_)
432 && scm_is_pair (d->dimension_))
434 d->dimension_ = SCM_EOL;
436 if (get_parent (axis))
437 get_parent (axis)->flush_extent_cache (axis);
442 Grob::extent (Grob *refp, Axis a) const
444 Real x = relative_coordinate (refp, a);
446 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
449 SCM dimpair = d->dimension_;
450 if (scm_is_pair (dimpair))
452 else if (ly_c_procedure_p (d->dimension_callback_)
453 && d->dimension_ == SCM_EOL)
454 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
458 if (!scm_is_pair (d->dimension_))
461 ext = ly_scm2interval (d->dimension_);
463 SCM extra = get_property (a == X_AXIS
468 if (scm_is_pair (extra))
470 ext[BIGGER] += scm_to_double (scm_cdr (extra));
471 ext[SMALLER] += scm_to_double (scm_car (extra));
474 extra = get_property (a == X_AXIS
476 : "minimum-Y-extent");
477 if (scm_is_pair (extra))
478 ext.unite (Interval (scm_to_double (scm_car (extra)),
479 scm_to_double (scm_cdr (extra))));
486 /* Find the group-element which has both #this# and #s# */
488 Grob::common_refpoint (Grob const *s, Axis a) const
490 /* I don't like the quadratic aspect of this code, but I see no
491 other way. The largest chain of parents might be 10 high or so,
492 so it shouldn't be a real issue. */
493 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
494 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
502 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
504 for (; scm_is_pair (elist); elist = scm_cdr (elist))
505 if (Grob *s = unsmob_grob (scm_car (elist)))
508 common = common->common_refpoint (s, a);
517 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
519 for (int i = arr.size (); i--;)
520 if (Grob *s = arr[i])
523 common = common->common_refpoint (s, a);
534 SCM meta = get_property ("meta");
535 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
536 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
537 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
541 Grob::add_offset_callback (SCM cb, Axis a)
543 if (!has_offset_callback (cb, a))
545 dim_cache_[a].offset_callbacks_
546 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
547 dim_cache_[a].offsets_left_++;
552 Grob::has_extent_callback (SCM cb, Axis a) const
554 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
558 Grob::has_offset_callback (SCM cb, Axis a) const
560 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
564 Grob::set_extent (SCM dc, Axis a)
566 dim_cache_[a].dimension_ = dc;
570 Grob::set_extent_callback (SCM dc, Axis a)
572 dim_cache_[a].dimension_callback_ = dc;
576 Grob::set_parent (Grob *g, Axis a)
578 dim_cache_[a].parent_ = g;
581 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
583 Grob::fixup_refpoint (SCM smob)
585 Grob *me = unsmob_grob (smob);
586 for (int a = X_AXIS; a < NO_AXES; a++)
589 Grob *parent = me->get_parent (ax);
594 if (parent->get_system () != me->get_system () && me->get_system ())
596 Grob *newparent = parent->find_broken_piece (me->get_system ());
597 me->set_parent (newparent, ax);
600 if (Item *i = dynamic_cast<Item *> (me))
602 Item *parenti = dynamic_cast<Item *> (parent);
606 Direction my_dir = i->break_status_dir ();
607 if (my_dir != parenti->break_status_dir ())
609 Item *newparent = parenti->find_prebroken_piece (my_dir);
610 me->set_parent (newparent, ax);
619 Grob::warning (String s) const
621 SCM cause = self_scm ();
622 while (Grob *g = unsmob_grob (cause))
623 cause = g->get_property ("cause");
625 if (Music *m = unsmob_music (cause))
626 m->origin ()->warning (s);
632 Grob::programming_error (String s) const
634 s = "Programming error: " + s;
638 /****************************************************
640 ****************************************************/
642 IMPLEMENT_SMOBS (Grob);
643 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
646 Grob::mark_smob (SCM ses)
648 Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
649 scm_gc_mark (s->immutable_property_alist_);
650 scm_gc_mark (s->key_->self_scm ());
651 for (int a = 0; a < 2; a++)
653 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
654 scm_gc_mark (s->dim_cache_[a].dimension_);
655 scm_gc_mark (s->dim_cache_[a].dimension_callback_);
657 /* Do not mark the parents. The pointers in the mutable
658 property list form two tree like structures (one for X
659 relations, one for Y relations). Marking these can be done
660 in limited stack space. If we add the parents, we will jump
661 between X and Y in an erratic manner, leading to much more
662 recursion depth (and core dumps if we link to pthreads). */
666 scm_gc_mark (s->original_->self_scm ());
669 scm_gc_mark (s->pscore_->layout_->self_scm ());
671 s->do_derived_mark ();
672 return s->mutable_property_alist_;
676 Grob::print_smob (SCM s, SCM port, scm_print_state *)
678 Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
680 scm_puts ("#<Grob ", port);
681 scm_puts ((char *) sc->name ().to_str0 (), port);
683 /* Do not print properties, that is too much hassle. */
684 scm_puts (" >", port);
689 Grob::do_derived_mark () const
695 Grob::discretionary_processing ()
700 Grob::internal_has_interface (SCM k)
702 SCM ifs = get_property ("interfaces");
704 return scm_c_memq (k, ifs) != SCM_BOOL_F;
708 Grob::get_parent (Axis a) const
710 return dim_cache_[a].parent_;
713 /** Return Array of Grobs in SCM list LST */
715 ly_scm2grobs (SCM lst)
717 Link_array<Grob> arr;
719 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
722 arr.push (unsmob_grob (e));
730 Grob::get_key () const
735 /** Return SCM list of Grob array A */
737 ly_grobs2scm (Link_array<Grob> a)
740 for (int i = a.size (); i; i--)
741 s = scm_cons (a[i - 1]->self_scm (), s);
746 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
748 ADD_INTERFACE (Grob, "grob-interface",
749 "A grob represents a piece of music notation\n"
751 "All grobs have an X and Y-position on the page. These X and Y positions\n"
752 "are stored in a relative format, so they can easily be combined by\n"
753 "stacking them, hanging one grob to the side of another, and coupling\n"
754 "them into a grouping objects.\n"
756 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
757 "is stored relative to that reference point. For example the X-reference\n"
758 "point of a staccato dot usually is the note head that it applies\n"
759 "to. When the note head is moved, the staccato dot moves along\n"
762 "A grob is often associated with a symbol, but some grobs do not print\n"
763 "any symbols. They take care of grouping objects. For example, there is a\n"
764 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
765 "is also an abstract grob: it only moves around chords, but doesn't print\n"
768 "Grobs have a properties: Scheme variables, that can be read and set. "
769 "They have two types. Immutable variables "
770 "define the default style and behavior. They are shared between many objects. "
771 "They can be changed using @code{\\override} and @code{\\revert}. "
773 "Mutable properties are variables that are specific to one grob. Typically, "
774 "lists of other objects, or results from computations are stored in"
775 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
776 "sets a mutable property. ",
777 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
778 "Y-extent-callback print-function extra-offset spacing-procedure "
779 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
780 "meta layer before-line-breaking-callback "
782 "axis-group-parent-X "
783 "axis-group-parent-Y "
784 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
785 "minimum-Y-extent transparent tweak-count tweak-rank");