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 "pointer-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 interfaces_ = SCM_EOL;
56 immutable_property_alist_ = basicprops;
57 mutable_property_alist_ = SCM_EOL;
58 object_alist_ = SCM_EOL;
61 /* We do smobify_self () as the first step. Since the object lives
62 on the heap, none of its SCM variables are protected from
63 GC. After smobify_self (), they are. */
67 We always get a new key object for a new grob.
71 ((Object_key*)key_)->unprotect ();
74 SCM meta = get_property ("meta");
75 if (scm_is_pair (meta))
77 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
82 - destill this into a function, so we can re-init the immutable
83 properties with a new BASICPROPS value after
84 creation. Convenient eg. when using \override with
87 SCM off_callbacks[] = {
88 get_property ("X-offset-callbacks"),
89 get_property ("Y-offset-callbacks")
92 get_property ("X-extent"),
93 get_property ("Y-extent")
95 SCM extent_callbacks[] = {
96 get_property ("X-extent-callback"),
97 get_property ("Y-extent-callback")
100 for (int a = X_AXIS; a <= Y_AXIS; a++)
102 SCM l = off_callbacks[a];
104 if (scm_ilength (l) >= 0)
106 dim_cache_[a].offset_callbacks_ = l;
107 dim_cache_[a].offsets_left_ = scm_ilength (l);
110 programming_error ("[XY]-offset-callbacks must be a list");
112 SCM cb = extent_callbacks[a];
113 if (cb == SCM_BOOL_F)
115 dim_cache_[a].dimension_ = SCM_BOOL_F;
119 if (is_number_pair (xt))
121 dim_cache_[a].dimension_ = xt;
123 else if (ly_is_procedure (cb))
125 dim_cache_[a].dimension_callback_ = cb;
127 else if (cb == SCM_EOL
128 && ly_is_procedure (get_property ("print-function")))
129 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
133 Grob::Grob (Grob const &s, int copy_index)
134 : dim_cache_ (s.dim_cache_)
136 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
137 original_ = (Grob *) & s;
140 immutable_property_alist_ = s.immutable_property_alist_;
141 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
142 interfaces_ = s.interfaces_;
143 object_alist_ = SCM_EOL;
145 /* No properties are copied. That is the job of
146 handle_broken_dependencies. */
153 ((Object_key*)key_)->unprotect ();
161 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
163 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
165 Grob *s = unsmob_grob (element_smob);
166 Axis a = (Axis) scm_to_int (scm_axis);
168 Stencil *m = s->get_stencil ();
172 return ly_interval2scm (e);
176 robust_relative_extent (Grob *me, Grob *refp, Axis a)
178 Interval ext = me->extent (refp, a);
181 ext.add_point (me->relative_coordinate (refp, a));
188 Grob::get_layout () const
190 return pscore_ ? pscore_->layout () : 0;
193 /* Recursively track all dependencies of this Grob. The status_ field
194 is used as a mark-field. It is marked with BUSY during execution
195 of this function, and marked with FINAL when finished.
197 FUNCPTR is the function to call to update this element. */
199 Grob::calculate_dependencies (int final, int busy, SCM funcname)
201 if (status_ >= final)
206 programming_error ("element is busy, come back later");
212 extract_grob_set (this, "dependencies", deps);
213 for (int i = 0; i < deps.size (); i++)
214 deps[i]->calculate_dependencies (final, busy, funcname);
216 SCM proc = internal_get_property (funcname);
217 if (ly_is_procedure (proc))
218 scm_call_1 (proc, this->self_scm ());
224 Grob::get_stencil () const
229 SCM stil = get_property ("stencil");
230 if (unsmob_stencil (stil))
231 return unsmob_stencil (stil);
233 stil = get_uncached_stencil ();
236 Grob *me = (Grob *) this;
237 me->set_property ("stencil", stil);
240 return unsmob_stencil (stil);
244 Grob::get_uncached_stencil () const
246 SCM proc = get_property ("print-function");
249 if (ly_is_procedure (proc))
250 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
252 if (Stencil *m = unsmob_stencil (stil))
254 if (to_boolean (get_property ("transparent")))
255 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
258 SCM expr = m->expr ();
259 if (point_and_click_global)
260 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
262 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
265 /* color support... see interpret_stencil_expression () for more... */
266 SCM color = get_property ("color");
267 if (color != SCM_EOL)
269 m = unsmob_stencil (stil);
270 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
274 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
285 Grob::do_break_processing ()
290 Grob::get_system () const
296 Grob::add_dependency (Grob *e)
299 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
301 programming_error ("null dependency added");
305 Grob::handle_broken_dependencies ()
307 Spanner *sp = dynamic_cast<Spanner *> (this);
312 /* THIS, SP is the original spanner. We use a special function
313 because some Spanners have enormously long lists in their
314 properties, and a special function fixes FOO */
316 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
317 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
320 System *system = get_system ();
324 && common_refpoint (system, X_AXIS)
325 && common_refpoint (system, Y_AXIS))
326 substitute_object_links (system->self_scm (), object_alist_);
327 else if (dynamic_cast<System *> (this))
328 substitute_object_links (SCM_UNDEFINED, object_alist_);
330 /* THIS element is `invalid'; it has been removed from all
331 dependencies, so let's junk the element itself.
333 Do not do this for System, since that would remove references
334 to the originals of score-grobs, which get then GC'd (a bad
339 /* Note that we still want references to this element to be
340 rearranged, and not silently thrown away, so we keep pointers like
341 {broken_into_{drul, array}, original}
349 mutable_property_alist_ = SCM_EOL;
350 object_alist_ = SCM_EOL;
351 immutable_property_alist_ = SCM_EOL;
352 interfaces_ = SCM_EOL;
354 set_extent (SCM_EOL, Y_AXIS);
355 set_extent (SCM_EOL, X_AXIS);
357 set_extent_callback (SCM_EOL, Y_AXIS);
358 set_extent_callback (SCM_EOL, X_AXIS);
360 for (int a = X_AXIS; a <= Y_AXIS; a++)
362 dim_cache_[a].offset_callbacks_ = SCM_EOL;
363 dim_cache_[a].offsets_left_ = 0;
368 Grob::handle_prebroken_dependencies ()
370 /* Don't do this in the derived method, since we want to keep access to
371 object_alist_ centralized. */
374 Item *it = dynamic_cast<Item *> (this);
375 substitute_object_links (scm_int2num (it->break_status_dir ()),
376 original_->object_alist_);
381 Grob::find_broken_piece (System *) const
386 /* Translate in one direction. */
388 Grob::translate_axis (Real y, Axis a)
390 if (isinf (y) || isnan (y))
391 programming_error (_ (INFINITY_MSG));
393 dim_cache_[a].offset_ += y;
396 /* Find the offset relative to D. If D equals THIS, then it is 0.
397 Otherwise, it recursively defd as
399 OFFSET_ + PARENT_L_->relative_coordinate (D) */
401 Grob::relative_coordinate (Grob const *refp, Axis a) const
406 /* We catch PARENT_L_ == nil case with this, but we crash if we did
407 not ask for the absolute coordinate (ie. REFP == nil.) */
408 if (refp == dim_cache_[a].parent_)
409 return get_offset (a);
411 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
414 /* Invoke callbacks to get offset relative to parent. */
416 Grob::get_offset (Axis a) const
418 Grob *me = (Grob *) this;
419 while (dim_cache_[a].offsets_left_)
421 int l = --me->dim_cache_[a].offsets_left_;
422 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
423 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
425 Real r = scm_to_double (retval);
426 if (isinf (r) || isnan (r))
428 programming_error (INFINITY_MSG);
431 me->dim_cache_[a].offset_ += r;
433 return dim_cache_[a].offset_;
437 Grob::is_empty (Axis a) const
439 return !(scm_is_pair (dim_cache_[a].dimension_)
440 || ly_is_procedure (dim_cache_[a].dimension_callback_));
444 Grob::flush_extent_cache (Axis axis)
446 Dimension_cache *d = &dim_cache_[axis];
447 if (ly_is_procedure (d->dimension_callback_)
448 && scm_is_pair (d->dimension_))
450 d->dimension_ = SCM_EOL;
452 if (get_parent (axis))
453 get_parent (axis)->flush_extent_cache (axis);
458 Grob::extent (Grob *refp, Axis a) const
460 Real x = relative_coordinate (refp, a);
462 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
465 SCM dimpair = d->dimension_;
466 if (scm_is_pair (dimpair))
468 else if (ly_is_procedure (d->dimension_callback_)
469 && d->dimension_ == SCM_EOL)
470 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
474 if (!scm_is_pair (d->dimension_))
477 ext = ly_scm2interval (d->dimension_);
479 SCM extra = (a == X_AXIS)
480 ? get_property ("extra-X-extent")
481 : get_property ("extra-Y-extent");
484 if (scm_is_pair (extra))
486 ext[BIGGER] += scm_to_double (scm_cdr (extra));
487 ext[SMALLER] += scm_to_double (scm_car (extra));
490 extra = (a == X_AXIS)
491 ? get_property ("minimum-X-extent")
492 : get_property ("minimum-Y-extent");
494 if (scm_is_pair (extra))
495 ext.unite (Interval (scm_to_double (scm_car (extra)),
496 scm_to_double (scm_cdr (extra))));
503 /* Find the group-element which has both #this# and #s# */
505 Grob::common_refpoint (Grob const *s, Axis a) const
507 /* I don't like the quadratic aspect of this code, but I see no
508 other way. The largest chain of parents might be 10 high or so,
509 so it shouldn't be a real issue. */
510 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
511 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
519 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
521 for (; scm_is_pair (elist); elist = scm_cdr (elist))
522 if (Grob *s = unsmob_grob (scm_car (elist)))
525 common = common->common_refpoint (s, a);
534 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
536 for (int i = arr.size (); i--;)
537 if (Grob *s = arr[i])
540 common = common->common_refpoint (s, a);
551 SCM meta = get_property ("meta");
552 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
553 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
554 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
558 Grob::add_offset_callback (SCM cb, Axis a)
560 if (!has_offset_callback (cb, a))
562 dim_cache_[a].offset_callbacks_
563 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
564 dim_cache_[a].offsets_left_++;
569 Grob::has_extent_callback (SCM cb, Axis a) const
571 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
575 Grob::has_offset_callback (SCM cb, Axis a) const
577 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
581 Grob::set_extent (SCM dc, Axis a)
583 dim_cache_[a].dimension_ = dc;
587 Grob::set_extent_callback (SCM dc, Axis a)
589 dim_cache_[a].dimension_callback_ = dc;
593 Grob::set_parent (Grob *g, Axis a)
595 dim_cache_[a].parent_ = g;
599 Grob::fixup_refpoint ()
601 for (int a = X_AXIS; a < NO_AXES; a++)
604 Grob *parent = get_parent (ax);
609 if (parent->get_system () != get_system () && get_system ())
611 Grob *newparent = parent->find_broken_piece (get_system ());
612 set_parent (newparent, ax);
615 if (Item *i = dynamic_cast<Item *> (this))
617 Item *parenti = dynamic_cast<Item *> (parent);
621 Direction my_dir = i->break_status_dir ();
622 if (my_dir != parenti->break_status_dir ())
624 Item *newparent = parenti->find_prebroken_piece (my_dir);
625 set_parent (newparent, ax);
633 Grob::warning (String s) const
635 SCM cause = self_scm ();
636 while (Grob *g = unsmob_grob (cause))
637 cause = g->get_property ("cause");
639 if (Music *m = unsmob_music (cause))
640 m->origin ()->warning (s);
646 Grob::programming_error (String s) const
648 s = _f ("programming error: %s", s);
652 Grob::discretionary_processing ()
658 Grob::internal_has_interface (SCM k)
660 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
664 Grob::get_parent (Axis a) const
666 return dim_cache_[a].parent_;
669 /** Return Array of Grobs in SCM list LST */
671 ly_scm2grobs (SCM lst)
673 Link_array<Grob> arr;
675 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
678 arr.push (unsmob_grob (e));
686 Grob::get_key () const
691 /** Return SCM list of Grob array A */
693 ly_grobs2scm (Link_array<Grob> a)
696 for (int i = a.size (); i; i--)
697 s = scm_cons (a[i - 1]->self_scm (), s);
702 ADD_INTERFACE (Grob, "grob-interface",
703 "A grob represents a piece of music notation\n"
705 "All grobs have an X and Y-position on the page. These X and Y positions\n"
706 "are stored in a relative format, so they can easily be combined by\n"
707 "stacking them, hanging one grob to the side of another, and coupling\n"
708 "them into a grouping objects.\n"
710 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
711 "is stored relative to that reference point. For example the X-reference\n"
712 "point of a staccato dot usually is the note head that it applies\n"
713 "to. When the note head is moved, the staccato dot moves along\n"
716 "A grob is often associated with a symbol, but some grobs do not print\n"
717 "any symbols. They take care of grouping objects. For example, there is a\n"
718 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
719 "is also an abstract grob: it only moves around chords, but doesn't print\n"
722 "Grobs have a properties: Scheme variables, that can be read and set. "
723 "They have two types. Immutable variables "
724 "define the default style and behavior. They are shared between many objects. "
725 "They can be changed using @code{\\override} and @code{\\revert}. "
727 "Mutable properties are variables that are specific to one grob. Typically, "
728 "lists of other objects, or results from computations are stored in"
729 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
730 "sets a mutable property. ",
731 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
732 "Y-extent-callback print-function extra-offset spacing-procedure "
733 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
734 "meta layer before-line-breaking-callback "
736 "axis-group-parent-X "
737 "axis-group-parent-Y "
738 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
739 "minimum-Y-extent transparent");