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>
16 #include "input-smob.hh"
18 #include "pointer-group-interface.hh"
20 #include "paper-score.hh"
29 #include "paper-score.hh"
30 #include "ly-smobs.icc"
31 #include "output-def.hh"
34 Grob::clone (int count) const
36 return new Grob (*this, count);
41 - remove dynamic_cast<Spanner, Item> and put this code into respective
45 #define INFINITY_MSG "Infinity or NaN encountered"
47 Grob::Grob (SCM basicprops,
48 Object_key const *key)
51 /* FIXME: default should be no callback. */
56 interfaces_ = SCM_EOL;
57 immutable_property_alist_ = basicprops;
58 mutable_property_alist_ = SCM_EOL;
59 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.
70 ((Object_key *)key_)->unprotect ();
72 SCM meta = get_property ("meta");
73 if (scm_is_pair (meta))
74 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
78 - destill this into a function, so we can re-init the immutable
79 properties with a new BASICPROPS value after
80 creation. Convenient eg. when using \override with
83 SCM off_callbacks[] = {
84 get_property ("X-offset-callbacks"),
85 get_property ("Y-offset-callbacks")
88 get_property ("X-extent"),
89 get_property ("Y-extent")
91 SCM extent_callbacks[] = {
92 get_property ("X-extent-callback"),
93 get_property ("Y-extent-callback")
96 for (int a = X_AXIS; a <= Y_AXIS; a++)
98 SCM l = off_callbacks[a];
100 if (scm_ilength (l) >= 0)
102 dim_cache_[a].offset_callbacks_ = l;
103 dim_cache_[a].offsets_left_ = scm_ilength (l);
106 programming_error ("[XY]-offset-callbacks must be a list");
108 SCM cb = extent_callbacks[a];
109 if (cb == SCM_BOOL_F)
110 dim_cache_[a].dimension_ = SCM_BOOL_F;
113 if (is_number_pair (xt))
114 dim_cache_[a].dimension_ = xt;
115 else if (ly_is_procedure (cb))
116 dim_cache_[a].dimension_callback_ = cb;
117 else if (cb == SCM_EOL
118 && ly_is_procedure (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_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
127 original_ = (Grob *) & s;
130 immutable_property_alist_ = s.immutable_property_alist_;
131 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
132 interfaces_ = s.interfaces_;
133 object_alist_ = SCM_EOL;
135 /* No properties are copied. That is the job of
136 handle_broken_dependencies. */
142 ((Object_key *)key_)->unprotect ();
149 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
151 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
153 Grob *s = unsmob_grob (element_smob);
154 Axis a = (Axis) scm_to_int (scm_axis);
156 Stencil *m = s->get_stencil ();
160 return ly_interval2scm (e);
164 robust_relative_extent (Grob *me, Grob *refp, Axis a)
166 Interval ext = me->extent (refp, a);
168 ext.add_point (me->relative_coordinate (refp, a));
174 Grob::get_layout () const
176 return pscore_ ? pscore_->layout () : 0;
179 /* Recursively track all dependencies of this Grob. The status_ field
180 is used as a mark-field. It is marked with BUSY during execution
181 of this function, and marked with FINAL when finished.
183 FUNCPTR is the function to call to update this element. */
185 Grob::calculate_dependencies (int final, int busy, SCM funcname)
187 if (status_ >= final)
192 programming_error ("element is busy, come back later");
198 extract_grob_set (this, "dependencies", deps);
199 for (int i = 0; i < deps.size (); i++)
200 deps[i]->calculate_dependencies (final, busy, funcname);
202 SCM proc = internal_get_property (funcname);
203 if (ly_is_procedure (proc))
204 scm_call_1 (proc, this->self_scm ());
210 Grob::get_stencil () const
215 SCM stil = get_property ("stencil");
216 if (unsmob_stencil (stil))
217 return unsmob_stencil (stil);
219 stil = get_uncached_stencil ();
222 Grob *me = (Grob *) this;
223 me->set_property ("stencil", stil);
226 return unsmob_stencil (stil);
230 Grob::get_uncached_stencil () const
232 SCM proc = get_property ("print-function");
235 if (ly_is_procedure (proc))
236 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
238 if (Stencil *m = unsmob_stencil (stil))
240 if (to_boolean (get_property ("transparent")))
241 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"), e);
287 programming_error ("null dependency added");
291 Grob::handle_broken_dependencies ()
293 Spanner *sp = dynamic_cast<Spanner *> (this);
298 /* THIS, SP is the original spanner. We use a special function
299 because some Spanners have enormously long lists in their
300 properties, and a special function fixes FOO */
302 for (SCM s = object_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 ();
309 && common_refpoint (system, X_AXIS)
310 && common_refpoint (system, Y_AXIS))
311 substitute_object_links (system->self_scm (), object_alist_);
312 else if (dynamic_cast<System *> (this))
313 substitute_object_links (SCM_UNDEFINED, object_alist_);
315 /* THIS element is `invalid'; it has been removed from all
316 dependencies, so let's junk the element itself.
318 Do not do this for System, since that would remove references
319 to the originals of score-grobs, which get then GC'd (a bad
324 /* Note that we still want references to this element to be
325 rearranged, and not silently thrown away, so we keep pointers like
326 {broken_into_{drul, array}, original}
334 mutable_property_alist_ = SCM_EOL;
335 object_alist_ = SCM_EOL;
336 immutable_property_alist_ = SCM_EOL;
337 interfaces_ = SCM_EOL;
339 set_extent (SCM_EOL, Y_AXIS);
340 set_extent (SCM_EOL, X_AXIS);
342 set_extent_callback (SCM_EOL, Y_AXIS);
343 set_extent_callback (SCM_EOL, X_AXIS);
345 for (int a = X_AXIS; a <= Y_AXIS; a++)
347 dim_cache_[a].offset_callbacks_ = SCM_EOL;
348 dim_cache_[a].offsets_left_ = 0;
353 Grob::handle_prebroken_dependencies ()
355 /* Don't do this in the derived method, since we want to keep access to
356 object_alist_ centralized. */
359 Item *it = dynamic_cast<Item *> (this);
360 substitute_object_links (scm_from_int (it->break_status_dir ()),
361 original_->object_alist_);
366 Grob::find_broken_piece (System *) const
371 /* Translate in one direction. */
373 Grob::translate_axis (Real y, Axis a)
375 if (isinf (y) || isnan (y))
376 programming_error (_ (INFINITY_MSG));
378 dim_cache_[a].offset_ += y;
381 /* Find the offset relative to D. If D equals THIS, then it is 0.
382 Otherwise, it recursively defd as
384 OFFSET_ + PARENT_L_->relative_coordinate (D) */
386 Grob::relative_coordinate (Grob const *refp, Axis a) const
391 /* We catch PARENT_L_ == nil case with this, but we crash if we did
392 not ask for the absolute coordinate (ie. REFP == nil.) */
393 if (refp == dim_cache_[a].parent_)
394 return get_offset (a);
396 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
399 /* Invoke callbacks to get offset relative to parent. */
401 Grob::get_offset (Axis a) const
403 Grob *me = (Grob *) this;
404 while (dim_cache_[a].offsets_left_)
406 int l = --me->dim_cache_[a].offsets_left_;
407 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_from_int (l));
408 SCM retval = scm_call_2 (cb, self_scm (), scm_from_int (a));
410 Real r = scm_to_double (retval);
411 if (isinf (r) || isnan (r))
413 programming_error (INFINITY_MSG);
416 me->dim_cache_[a].offset_ += r;
418 return dim_cache_[a].offset_;
422 Grob::is_empty (Axis a) const
424 return !(scm_is_pair (dim_cache_[a].dimension_)
425 || ly_is_procedure (dim_cache_[a].dimension_callback_));
429 Grob::flush_extent_cache (Axis axis)
431 Dimension_cache *d = &dim_cache_[axis];
432 if (ly_is_procedure (d->dimension_callback_)
433 && scm_is_pair (d->dimension_))
435 d->dimension_ = SCM_EOL;
437 if (get_parent (axis))
438 get_parent (axis)->flush_extent_cache (axis);
443 Grob::extent (Grob *refp, Axis a) const
445 Real x = relative_coordinate (refp, a);
447 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
450 SCM dimpair = d->dimension_;
451 if (scm_is_pair (dimpair))
453 else if (ly_is_procedure (d->dimension_callback_)
454 && d->dimension_ == SCM_EOL)
455 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_from_int (a));
459 if (!scm_is_pair (d->dimension_))
462 ext = ly_scm2interval (d->dimension_);
464 SCM extra = (a == X_AXIS)
465 ? get_property ("extra-X-extent")
466 : get_property ("extra-Y-extent");
469 if (scm_is_pair (extra))
471 ext[BIGGER] += scm_to_double (scm_cdr (extra));
472 ext[SMALLER] += scm_to_double (scm_car (extra));
475 extra = (a == X_AXIS)
476 ? get_property ("minimum-X-extent")
477 : get_property ("minimum-Y-extent");
479 if (scm_is_pair (extra))
480 ext.unite (Interval (scm_to_double (scm_car (extra)),
481 scm_to_double (scm_cdr (extra))));
488 /* Find the group-element which has both #this# and #s# */
490 Grob::common_refpoint (Grob const *s, Axis a) const
492 /* I don't like the quadratic aspect of this code, but I see no
493 other way. The largest chain of parents might be 10 high or so,
494 so it shouldn't be a real issue. */
495 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
496 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
504 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
506 for (; scm_is_pair (elist); elist = scm_cdr (elist))
507 if (Grob *s = unsmob_grob (scm_car (elist)))
510 common = common->common_refpoint (s, a);
519 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
521 for (int i = arr.size (); i--;)
522 if (Grob *s = arr[i])
525 common = common->common_refpoint (s, a);
536 SCM meta = get_property ("meta");
537 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
538 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
539 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
543 Grob::add_offset_callback (SCM cb, Axis a)
545 if (!has_offset_callback (cb, a))
547 dim_cache_[a].offset_callbacks_
548 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
549 dim_cache_[a].offsets_left_++;
554 Grob::has_extent_callback (SCM cb, Axis a) const
556 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
560 Grob::has_offset_callback (SCM cb, Axis a) const
562 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
566 Grob::set_extent (SCM dc, Axis a)
568 dim_cache_[a].dimension_ = dc;
572 Grob::set_extent_callback (SCM dc, Axis a)
574 dim_cache_[a].dimension_callback_ = dc;
578 Grob::set_parent (Grob *g, Axis a)
580 dim_cache_[a].parent_ = g;
584 Grob::fixup_refpoint ()
586 for (int a = X_AXIS; a < NO_AXES; a++)
589 Grob *parent = get_parent (ax);
594 if (parent->get_system () != get_system () && get_system ())
596 Grob *newparent = parent->find_broken_piece (get_system ());
597 set_parent (newparent, ax);
600 if (Item *i = dynamic_cast<Item *> (this))
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 set_parent (newparent, ax);
618 Grob::warning (String s) const
620 SCM cause = self_scm ();
621 while (Grob *g = unsmob_grob (cause))
622 cause = g->get_property ("cause");
624 if (Music *m = unsmob_music (cause))
625 m->origin ()->warning (s);
631 Grob::programming_error (String s) const
633 SCM cause = self_scm ();
634 while (Grob *g = unsmob_grob (cause))
635 cause = g->get_property ("cause");
637 s = _f ("programming error: %s", s);
639 if (Music *m = unsmob_music (cause))
640 m->origin ()->message (s);
645 Grob::discretionary_processing ()
650 Grob::internal_has_interface (SCM k)
652 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
656 Grob::get_parent (Axis a) const
658 return dim_cache_[a].parent_;
661 /** Return Array of Grobs in SCM list LST */
663 ly_scm2grobs (SCM lst)
665 Link_array<Grob> arr;
667 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
670 arr.push (unsmob_grob (e));
678 Grob::get_key () const
683 /** Return SCM list of Grob array A */
685 ly_grobs2scm (Link_array<Grob> a)
688 for (int i = a.size (); i; i--)
689 s = scm_cons (a[i - 1]->self_scm (), s);
694 ADD_INTERFACE (Grob, "grob-interface",
695 "A grob represents a piece of music notation\n"
697 "All grobs have an X and Y-position on the page. These X and Y positions\n"
698 "are stored in a relative format, so they can easily be combined by\n"
699 "stacking them, hanging one grob to the side of another, and coupling\n"
700 "them into a grouping objects.\n"
702 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
703 "is stored relative to that reference point. For example the X-reference\n"
704 "point of a staccato dot usually is the note head that it applies\n"
705 "to. When the note head is moved, the staccato dot moves along\n"
708 "A grob is often associated with a symbol, but some grobs do not print\n"
709 "any symbols. They take care of grouping objects. For example, there is a\n"
710 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
711 "is also an abstract grob: it only moves around chords, but doesn't print\n"
714 "Grobs have a properties: Scheme variables, that can be read and set. "
715 "They have two types. Immutable variables "
716 "define the default style and behavior. They are shared between many objects. "
717 "They can be changed using @code{\\override} and @code{\\revert}. "
719 "Mutable properties are variables that are specific to one grob. Typically, "
720 "lists of other objects, or results from computations are stored in"
721 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
722 "sets a mutable property. ",
723 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
724 "Y-extent-callback print-function extra-offset spacing-procedure "
725 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
726 "meta layer before-line-breaking-callback "
728 "axis-group-parent-X "
729 "axis-group-parent-Y "
730 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
731 "minimum-Y-extent transparent");