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 = get_property (a == X_AXIS
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 = get_property (a == X_AXIS
492 : "minimum-Y-extent");
493 if (scm_is_pair (extra))
494 ext.unite (Interval (scm_to_double (scm_car (extra)),
495 scm_to_double (scm_cdr (extra))));
502 /* Find the group-element which has both #this# and #s# */
504 Grob::common_refpoint (Grob const *s, Axis a) const
506 /* I don't like the quadratic aspect of this code, but I see no
507 other way. The largest chain of parents might be 10 high or so,
508 so it shouldn't be a real issue. */
509 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
510 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
518 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
520 for (; scm_is_pair (elist); elist = scm_cdr (elist))
521 if (Grob *s = unsmob_grob (scm_car (elist)))
524 common = common->common_refpoint (s, a);
533 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
535 for (int i = arr.size (); i--;)
536 if (Grob *s = arr[i])
539 common = common->common_refpoint (s, a);
550 SCM meta = get_property ("meta");
551 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
552 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
553 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
557 Grob::add_offset_callback (SCM cb, Axis a)
559 if (!has_offset_callback (cb, a))
561 dim_cache_[a].offset_callbacks_
562 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
563 dim_cache_[a].offsets_left_++;
568 Grob::has_extent_callback (SCM cb, Axis a) const
570 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
574 Grob::has_offset_callback (SCM cb, Axis a) const
576 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
580 Grob::set_extent (SCM dc, Axis a)
582 dim_cache_[a].dimension_ = dc;
586 Grob::set_extent_callback (SCM dc, Axis a)
588 dim_cache_[a].dimension_callback_ = dc;
592 Grob::set_parent (Grob *g, Axis a)
594 dim_cache_[a].parent_ = g;
598 Grob::fixup_refpoint ()
600 for (int a = X_AXIS; a < NO_AXES; a++)
603 Grob *parent = get_parent (ax);
608 if (parent->get_system () != get_system () && get_system ())
610 Grob *newparent = parent->find_broken_piece (get_system ());
611 set_parent (newparent, ax);
614 if (Item *i = dynamic_cast<Item *> (this))
616 Item *parenti = dynamic_cast<Item *> (parent);
620 Direction my_dir = i->break_status_dir ();
621 if (my_dir != parenti->break_status_dir ())
623 Item *newparent = parenti->find_prebroken_piece (my_dir);
624 set_parent (newparent, ax);
632 Grob::warning (String s) const
634 SCM cause = self_scm ();
635 while (Grob *g = unsmob_grob (cause))
636 cause = g->get_property ("cause");
638 if (Music *m = unsmob_music (cause))
639 m->origin ()->warning (s);
645 Grob::programming_error (String s) const
647 s = _f ("programming error: %s", s);
651 Grob::discretionary_processing ()
657 Grob::internal_has_interface (SCM k)
659 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
663 Grob::get_parent (Axis a) const
665 return dim_cache_[a].parent_;
668 /** Return Array of Grobs in SCM list LST */
670 ly_scm2grobs (SCM lst)
672 Link_array<Grob> arr;
674 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
677 arr.push (unsmob_grob (e));
685 Grob::get_key () const
690 /** Return SCM list of Grob array A */
692 ly_grobs2scm (Link_array<Grob> a)
695 for (int i = a.size (); i; i--)
696 s = scm_cons (a[i - 1]->self_scm (), s);
701 ADD_INTERFACE (Grob, "grob-interface",
702 "A grob represents a piece of music notation\n"
704 "All grobs have an X and Y-position on the page. These X and Y positions\n"
705 "are stored in a relative format, so they can easily be combined by\n"
706 "stacking them, hanging one grob to the side of another, and coupling\n"
707 "them into a grouping objects.\n"
709 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
710 "is stored relative to that reference point. For example the X-reference\n"
711 "point of a staccato dot usually is the note head that it applies\n"
712 "to. When the note head is moved, the staccato dot moves along\n"
715 "A grob is often associated with a symbol, but some grobs do not print\n"
716 "any symbols. They take care of grouping objects. For example, there is a\n"
717 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
718 "is also an abstract grob: it only moves around chords, but doesn't print\n"
721 "Grobs have a properties: Scheme variables, that can be read and set. "
722 "They have two types. Immutable variables "
723 "define the default style and behavior. They are shared between many objects. "
724 "They can be changed using @code{\\override} and @code{\\revert}. "
726 "Mutable properties are variables that are specific to one grob. Typically, "
727 "lists of other objects, or results from computations are stored in"
728 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
729 "sets a mutable property. ",
730 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
731 "Y-extent-callback print-function extra-offset spacing-procedure "
732 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
733 "meta layer before-line-breaking-callback "
735 "axis-group-parent-X "
736 "axis-group-parent-Y "
737 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
738 "minimum-Y-extent transparent");