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>
14 #include "input-smob.hh"
16 #include "pointer-group-interface.hh"
18 #include "paper-score.hh"
27 #include "paper-score.hh"
28 #include "ly-smobs.icc"
29 #include "output-def.hh"
31 MAKE_SCHEME_CALLBACK(Grob, same_axis_parent_positioning, 2);
33 Grob::same_axis_parent_positioning (SCM element_smob, SCM axis)
35 Grob *me = unsmob_grob (element_smob);
36 Axis ax = other_axis ((Axis)scm_to_int (axis));
38 Grob *par = me->get_parent (ax);
40 par->get_property ("positioning-done");
42 return scm_from_double (0.0);
45 MAKE_SCHEME_CALLBACK(Grob,other_axis_parent_positioning, 2);
47 Grob::other_axis_parent_positioning (SCM element_smob, SCM axis)
49 Grob *me = unsmob_grob (element_smob);
50 Axis ax = other_axis ((Axis) scm_to_int (axis));
52 Grob *par = me->get_parent (ax);
54 par->get_property ("positioning-done");
56 return scm_from_double (0.0);
62 Grob::clone (int count) const
64 return new Grob (*this, count);
69 - remove dynamic_cast<Spanner, Item> and put this code into respective
73 #define INFINITY_MSG "Infinity or NaN encountered"
75 Grob::Grob (SCM basicprops,
76 Object_key const *key)
79 /* FIXME: default should be no callback. */
84 interfaces_ = SCM_EOL;
85 immutable_property_alist_ = basicprops;
86 mutable_property_alist_ = SCM_EOL;
87 object_alist_ = SCM_EOL;
88 property_callbacks_ = SCM_EOL;
90 /* We do smobify_self () as the first step. Since the object lives
91 on the heap, none of its SCM variables are protected from
92 GC. After smobify_self (), they are. */
96 We always get a new key object for a new grob.
99 ((Object_key *)key_)->unprotect ();
101 SCM meta = get_property ("meta");
102 if (scm_is_pair (meta))
103 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
107 - destill this into a function, so we can re-init the immutable
108 properties with a new BASICPROPS value after
109 creation. Convenient eg. when using \override with
112 property_callbacks_ = get_property ("callbacks");
114 SCM off_callbacks[] = {
115 get_property ("X-offset-callbacks"),
116 get_property ("Y-offset-callbacks")
119 get_property ("X-extent"),
120 get_property ("Y-extent")
122 SCM extent_callbacks[] = {
123 get_property ("X-extent-callback"),
124 get_property ("Y-extent-callback")
127 for (int a = X_AXIS; a <= Y_AXIS; a++)
129 SCM l = off_callbacks[a];
131 if (scm_ilength (l) >= 0)
133 dim_cache_[a].offset_callbacks_ = l;
134 dim_cache_[a].offsets_left_ = scm_ilength (l);
137 programming_error ("[XY]-offset-callbacks must be a list");
139 SCM cb = extent_callbacks[a];
140 if (cb == SCM_BOOL_F)
141 dim_cache_[a].dimension_ = SCM_BOOL_F;
144 if (is_number_pair (xt))
145 dim_cache_[a].dimension_ = xt;
146 else if (ly_is_procedure (cb))
147 dim_cache_[a].dimension_callback_ = cb;
148 else if (cb == SCM_EOL
149 && ly_is_procedure (get_property ("print-function")))
150 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
155 Grob::Grob (Grob const &s, int copy_index)
156 : dim_cache_ (s.dim_cache_)
158 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
159 original_ = (Grob *) & s;
162 immutable_property_alist_ = s.immutable_property_alist_;
163 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
164 interfaces_ = s.interfaces_;
165 property_callbacks_ = s.property_callbacks_;
166 object_alist_ = SCM_EOL;
168 /* No properties are copied. That is the job of
169 handle_broken_dependencies. */
175 ((Object_key *)key_)->unprotect ();
182 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
184 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
186 Grob *s = unsmob_grob (element_smob);
187 Axis a = (Axis) scm_to_int (scm_axis);
189 Stencil *m = s->get_stencil ();
193 return ly_interval2scm (e);
197 robust_relative_extent (Grob *me, Grob *refp, Axis a)
199 Interval ext = me->extent (refp, a);
201 ext.add_point (me->relative_coordinate (refp, a));
207 Grob::get_layout () const
209 return pscore_ ? pscore_->layout () : 0;
212 /* Recursively track all dependencies of this Grob. The status_ field
213 is used as a mark-field. It is marked with BUSY during execution
214 of this function, and marked with FINAL when finished.
216 FUNCPTR is the function to call to update this element. */
218 Grob::calculate_dependencies (int final, int busy, SCM funcname)
220 if (status_ >= final)
225 programming_error ("element is busy, come back later");
231 extract_grob_set (this, "dependencies", deps);
232 for (int i = 0; i < deps.size (); i++)
233 deps[i]->calculate_dependencies (final, busy, funcname);
235 SCM proc = internal_get_property (funcname);
236 if (ly_is_procedure (proc))
237 scm_call_1 (proc, this->self_scm ());
243 Grob::get_stencil () const
248 SCM stil = get_property ("stencil");
249 if (unsmob_stencil (stil))
250 return unsmob_stencil (stil);
252 stil = get_uncached_stencil ();
255 Grob *me = (Grob *) this;
256 me->set_property ("stencil", stil);
259 return unsmob_stencil (stil);
263 Grob::get_uncached_stencil () const
265 SCM proc = get_property ("print-function");
268 if (ly_is_procedure (proc))
269 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
271 if (Stencil *m = unsmob_stencil (stil))
273 if (to_boolean (get_property ("transparent")))
274 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
277 SCM expr = m->expr ();
278 if (point_and_click_global)
279 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
281 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
284 /* color support... see interpret_stencil_expression () for more... */
285 SCM color = get_property ("color");
286 if (color != SCM_EOL)
288 m = unsmob_stencil (stil);
289 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
293 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
304 Grob::do_break_processing ()
309 Grob::get_system () const
316 Grob::handle_broken_dependencies ()
318 Spanner *sp = dynamic_cast<Spanner *> (this);
323 /* THIS, SP is the original spanner. We use a special function
324 because some Spanners have enormously long lists in their
325 properties, and a special function fixes FOO */
327 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
328 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
330 System *system = get_system ();
334 && common_refpoint (system, X_AXIS)
335 && common_refpoint (system, Y_AXIS))
336 substitute_object_links (system->self_scm (), object_alist_);
337 else if (dynamic_cast<System *> (this))
338 substitute_object_links (SCM_UNDEFINED, object_alist_);
340 /* THIS element is `invalid'; it has been removed from all
341 dependencies, so let's junk the element itself.
343 Do not do this for System, since that would remove references
344 to the originals of score-grobs, which get then GC'd (a bad
349 /* Note that we still want references to this element to be
350 rearranged, and not silently thrown away, so we keep pointers like
351 {broken_into_{drul, array}, original}
359 mutable_property_alist_ = SCM_EOL;
360 object_alist_ = SCM_EOL;
361 property_callbacks_ = SCM_EOL;
362 immutable_property_alist_ = SCM_EOL;
363 interfaces_ = SCM_EOL;
365 set_extent (SCM_EOL, Y_AXIS);
366 set_extent (SCM_EOL, X_AXIS);
368 set_extent_callback (SCM_EOL, Y_AXIS);
369 set_extent_callback (SCM_EOL, X_AXIS);
371 for (int a = X_AXIS; a <= Y_AXIS; a++)
373 dim_cache_[a].offset_callbacks_ = SCM_EOL;
374 dim_cache_[a].offsets_left_ = 0;
379 Grob::handle_prebroken_dependencies ()
381 /* Don't do this in the derived method, since we want to keep access to
382 object_alist_ centralized. */
385 Item *it = dynamic_cast<Item *> (this);
386 substitute_object_links (scm_from_int (it->break_status_dir ()),
387 original_->object_alist_);
392 Grob::find_broken_piece (System *) const
397 /* Translate in one direction. */
399 Grob::translate_axis (Real y, Axis a)
401 if (isinf (y) || isnan (y))
402 programming_error (_ (INFINITY_MSG));
404 dim_cache_[a].offset_ += y;
407 /* Find the offset relative to D. If D equals THIS, then it is 0.
408 Otherwise, it recursively defd as
410 OFFSET_ + PARENT_L_->relative_coordinate (D) */
412 Grob::relative_coordinate (Grob const *refp, Axis a) const
417 /* We catch PARENT_L_ == nil case with this, but we crash if we did
418 not ask for the absolute coordinate (ie. REFP == nil.) */
419 if (refp == dim_cache_[a].parent_)
420 return get_offset (a);
422 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
425 /* Invoke callbacks to get offset relative to parent. */
427 Grob::get_offset (Axis a) const
429 Grob *me = (Grob *) this;
430 while (dim_cache_[a].offsets_left_)
432 int l = --me->dim_cache_[a].offsets_left_;
433 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_from_int (l));
434 SCM retval = scm_call_2 (cb, self_scm (), scm_from_int (a));
436 Real r = scm_to_double (retval);
437 if (isinf (r) || isnan (r))
439 programming_error (INFINITY_MSG);
442 me->dim_cache_[a].offset_ += r;
444 return dim_cache_[a].offset_;
448 Grob::is_empty (Axis a) const
450 return !(scm_is_pair (dim_cache_[a].dimension_)
451 || ly_is_procedure (dim_cache_[a].dimension_callback_));
455 Grob::flush_extent_cache (Axis axis)
457 Dimension_cache *d = &dim_cache_[axis];
458 if (ly_is_procedure (d->dimension_callback_)
459 && scm_is_pair (d->dimension_))
461 d->dimension_ = SCM_EOL;
463 if (get_parent (axis))
464 get_parent (axis)->flush_extent_cache (axis);
469 Grob::extent (Grob *refp, Axis a) const
471 Real x = relative_coordinate (refp, a);
473 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
476 SCM dimpair = d->dimension_;
477 if (scm_is_pair (dimpair))
479 else if (ly_is_procedure (d->dimension_callback_)
480 && d->dimension_ == SCM_EOL)
481 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_from_int (a));
485 if (!scm_is_pair (d->dimension_))
488 ext = ly_scm2interval (d->dimension_);
490 SCM extra = (a == X_AXIS)
491 ? get_property ("extra-X-extent")
492 : get_property ("extra-Y-extent");
495 if (scm_is_pair (extra))
497 ext[BIGGER] += scm_to_double (scm_cdr (extra));
498 ext[SMALLER] += scm_to_double (scm_car (extra));
501 extra = (a == X_AXIS)
502 ? get_property ("minimum-X-extent")
503 : get_property ("minimum-Y-extent");
505 if (scm_is_pair (extra))
506 ext.unite (Interval (scm_to_double (scm_car (extra)),
507 scm_to_double (scm_cdr (extra))));
514 /* Find the group-element which has both #this# and #s# */
516 Grob::common_refpoint (Grob const *s, Axis a) const
518 /* I don't like the quadratic aspect of this code, but I see no
519 other way. The largest chain of parents might be 10 high or so,
520 so it shouldn't be a real issue. */
521 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
522 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
530 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
532 for (; scm_is_pair (elist); elist = scm_cdr (elist))
533 if (Grob *s = unsmob_grob (scm_car (elist)))
536 common = common->common_refpoint (s, a);
545 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
547 for (int i = arr.size (); i--;)
548 if (Grob *s = arr[i])
551 common = common->common_refpoint (s, a);
562 SCM meta = get_property ("meta");
563 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
564 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
565 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
569 Grob::add_offset_callback (SCM cb, Axis a)
571 if (!has_offset_callback (cb, a))
573 dim_cache_[a].offset_callbacks_
574 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
575 dim_cache_[a].offsets_left_++;
580 Grob::has_extent_callback (SCM cb, Axis a) const
582 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
586 Grob::has_offset_callback (SCM cb, Axis a) const
588 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
592 Grob::set_extent (SCM dc, Axis a)
594 dim_cache_[a].dimension_ = dc;
598 Grob::set_extent_callback (SCM dc, Axis a)
600 dim_cache_[a].dimension_callback_ = dc;
604 Grob::set_parent (Grob *g, Axis a)
606 dim_cache_[a].parent_ = g;
610 Grob::fixup_refpoint ()
612 for (int a = X_AXIS; a < NO_AXES; a++)
615 Grob *parent = get_parent (ax);
620 if (parent->get_system () != get_system () && get_system ())
622 Grob *newparent = parent->find_broken_piece (get_system ());
623 set_parent (newparent, ax);
626 if (Item *i = dynamic_cast<Item *> (this))
628 Item *parenti = dynamic_cast<Item *> (parent);
632 Direction my_dir = i->break_status_dir ();
633 if (my_dir != parenti->break_status_dir ())
635 Item *newparent = parenti->find_prebroken_piece (my_dir);
636 set_parent (newparent, ax);
644 Grob::warning (String s) const
646 SCM cause = self_scm ();
647 while (Grob *g = unsmob_grob (cause))
648 cause = g->get_property ("cause");
650 if (Music *m = unsmob_music (cause))
651 m->origin ()->warning (s);
657 Grob::programming_error (String s) const
659 SCM cause = self_scm ();
660 while (Grob *g = unsmob_grob (cause))
661 cause = g->get_property ("cause");
663 s = _f ("programming error: %s", s);
665 if (Music *m = unsmob_music (cause))
666 m->origin ()->message (s);
671 Grob::discretionary_processing ()
676 Grob::internal_has_interface (SCM k)
678 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
682 Grob::get_parent (Axis a) const
684 return dim_cache_[a].parent_;
687 /** Return Array of Grobs in SCM list LST */
689 ly_scm2grobs (SCM lst)
691 Link_array<Grob> arr;
693 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
696 arr.push (unsmob_grob (e));
704 Grob::get_key () const
709 /** Return SCM list of Grob array A */
711 ly_grobs2scm (Link_array<Grob> a)
714 for (int i = a.size (); i; i--)
715 s = scm_cons (a[i - 1]->self_scm (), s);
720 ADD_INTERFACE (Grob, "grob-interface",
721 "A grob represents a piece of music notation\n"
723 "All grobs have an X and Y-position on the page. These X and Y positions\n"
724 "are stored in a relative format, so they can easily be combined by\n"
725 "stacking them, hanging one grob to the side of another, and coupling\n"
726 "them into a grouping objects.\n"
728 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
729 "is stored relative to that reference point. For example the X-reference\n"
730 "point of a staccato dot usually is the note head that it applies\n"
731 "to. When the note head is moved, the staccato dot moves along\n"
734 "A grob is often associated with a symbol, but some grobs do not print\n"
735 "any symbols. They take care of grouping objects. For example, there is a\n"
736 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
737 "is also an abstract grob: it only moves around chords, but doesn't print\n"
740 "Grobs have a properties: Scheme variables, that can be read and set. "
741 "They have two types. Immutable variables "
742 "define the default style and behavior. They are shared between many objects. "
743 "They can be changed using @code{\\override} and @code{\\revert}. "
745 "Mutable properties are variables that are specific to one grob. Typically, "
746 "lists of other objects, or results from computations are stored in"
747 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
748 "sets a mutable property. ",
753 "X-offset-callbacks "
756 "Y-offset-callbacks "
757 "after-line-breaking-callback "
758 "axis-group-parent-X "
759 "axis-group-parent-Y "
760 "before-line-breaking-callback "