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"
32 Grob::clone (int count) const
34 return new Grob (*this, count);
39 - remove dynamic_cast<Spanner, Item> and put this code into respective
43 #define INFINITY_MSG "Infinity or NaN encountered"
45 Grob::Grob (SCM basicprops,
46 Object_key const *key)
49 /* FIXME: default should be no callback. */
54 interfaces_ = SCM_EOL;
55 immutable_property_alist_ = basicprops;
56 mutable_property_alist_ = SCM_EOL;
57 object_alist_ = SCM_EOL;
59 /* We do smobify_self () as the first step. Since the object lives
60 on the heap, none of its SCM variables are protected from
61 GC. After smobify_self (), they are. */
65 We always get a new key object for a new grob.
68 ((Object_key *)key_)->unprotect ();
70 SCM meta = get_property ("meta");
71 if (scm_is_pair (meta))
72 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
76 - destill this into a function, so we can re-init the immutable
77 properties with a new BASICPROPS value after
78 creation. Convenient eg. when using \override with
81 SCM off_callbacks[] = {
82 get_property ("X-offset-callbacks"),
83 get_property ("Y-offset-callbacks")
86 get_property ("X-extent"),
87 get_property ("Y-extent")
89 SCM extent_callbacks[] = {
90 get_property ("X-extent-callback"),
91 get_property ("Y-extent-callback")
94 for (int a = X_AXIS; a <= Y_AXIS; a++)
96 SCM l = off_callbacks[a];
98 if (scm_ilength (l) >= 0)
100 dim_cache_[a].offset_callbacks_ = l;
101 dim_cache_[a].offsets_left_ = scm_ilength (l);
104 programming_error ("[XY]-offset-callbacks must be a list");
106 SCM cb = extent_callbacks[a];
107 if (cb == SCM_BOOL_F)
108 dim_cache_[a].dimension_ = SCM_BOOL_F;
111 if (is_number_pair (xt))
112 dim_cache_[a].dimension_ = xt;
113 else if (ly_is_procedure (cb))
114 dim_cache_[a].dimension_callback_ = cb;
115 else if (cb == SCM_EOL
116 && ly_is_procedure (get_property ("print-function")))
117 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
121 Grob::Grob (Grob const &s, int copy_index)
122 : dim_cache_ (s.dim_cache_)
124 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
125 original_ = (Grob *) & s;
128 immutable_property_alist_ = s.immutable_property_alist_;
129 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
130 interfaces_ = s.interfaces_;
131 object_alist_ = SCM_EOL;
133 /* No properties are copied. That is the job of
134 handle_broken_dependencies. */
140 ((Object_key *)key_)->unprotect ();
147 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
149 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
151 Grob *s = unsmob_grob (element_smob);
152 Axis a = (Axis) scm_to_int (scm_axis);
154 Stencil *m = s->get_stencil ();
158 return ly_interval2scm (e);
162 robust_relative_extent (Grob *me, Grob *refp, Axis a)
164 Interval ext = me->extent (refp, a);
166 ext.add_point (me->relative_coordinate (refp, a));
172 Grob::get_layout () const
174 return pscore_ ? pscore_->layout () : 0;
177 /* Recursively track all dependencies of this Grob. The status_ field
178 is used as a mark-field. It is marked with BUSY during execution
179 of this function, and marked with FINAL when finished.
181 FUNCPTR is the function to call to update this element. */
183 Grob::calculate_dependencies (int final, int busy, SCM funcname)
185 if (status_ >= final)
190 programming_error ("element is busy, come back later");
196 extract_grob_set (this, "dependencies", deps);
197 for (int i = 0; i < deps.size (); i++)
198 deps[i]->calculate_dependencies (final, busy, funcname);
200 SCM proc = internal_get_property (funcname);
201 if (ly_is_procedure (proc))
202 scm_call_1 (proc, this->self_scm ());
208 Grob::get_stencil () const
213 SCM stil = get_property ("stencil");
214 if (unsmob_stencil (stil))
215 return unsmob_stencil (stil);
217 stil = get_uncached_stencil ();
220 Grob *me = (Grob *) this;
221 me->set_property ("stencil", stil);
224 return unsmob_stencil (stil);
228 Grob::get_uncached_stencil () const
230 SCM proc = get_property ("print-function");
233 if (ly_is_procedure (proc))
234 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
236 if (Stencil *m = unsmob_stencil (stil))
238 if (to_boolean (get_property ("transparent")))
239 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
242 SCM expr = m->expr ();
243 if (point_and_click_global)
244 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
246 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
249 /* color support... see interpret_stencil_expression () for more... */
250 SCM color = get_property ("color");
251 if (color != SCM_EOL)
253 m = unsmob_stencil (stil);
254 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
258 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
269 Grob::do_break_processing ()
274 Grob::get_system () const
280 Grob::add_dependency (Grob *e)
283 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
285 programming_error ("null dependency added");
289 Grob::handle_broken_dependencies ()
291 Spanner *sp = dynamic_cast<Spanner *> (this);
296 /* THIS, SP is the original spanner. We use a special function
297 because some Spanners have enormously long lists in their
298 properties, and a special function fixes FOO */
300 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
301 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
303 System *system = get_system ();
307 && common_refpoint (system, X_AXIS)
308 && common_refpoint (system, Y_AXIS))
309 substitute_object_links (system->self_scm (), object_alist_);
310 else if (dynamic_cast<System *> (this))
311 substitute_object_links (SCM_UNDEFINED, object_alist_);
313 /* THIS element is `invalid'; it has been removed from all
314 dependencies, so let's junk the element itself.
316 Do not do this for System, since that would remove references
317 to the originals of score-grobs, which get then GC'd (a bad
322 /* Note that we still want references to this element to be
323 rearranged, and not silently thrown away, so we keep pointers like
324 {broken_into_{drul, array}, original}
332 mutable_property_alist_ = SCM_EOL;
333 object_alist_ = SCM_EOL;
334 immutable_property_alist_ = SCM_EOL;
335 interfaces_ = SCM_EOL;
337 set_extent (SCM_EOL, Y_AXIS);
338 set_extent (SCM_EOL, X_AXIS);
340 set_extent_callback (SCM_EOL, Y_AXIS);
341 set_extent_callback (SCM_EOL, X_AXIS);
343 for (int a = X_AXIS; a <= Y_AXIS; a++)
345 dim_cache_[a].offset_callbacks_ = SCM_EOL;
346 dim_cache_[a].offsets_left_ = 0;
351 Grob::handle_prebroken_dependencies ()
353 /* Don't do this in the derived method, since we want to keep access to
354 object_alist_ centralized. */
357 Item *it = dynamic_cast<Item *> (this);
358 substitute_object_links (scm_from_int (it->break_status_dir ()),
359 original_->object_alist_);
364 Grob::find_broken_piece (System *) const
369 /* Translate in one direction. */
371 Grob::translate_axis (Real y, Axis a)
373 if (isinf (y) || isnan (y))
374 programming_error (_ (INFINITY_MSG));
376 dim_cache_[a].offset_ += y;
379 /* Find the offset relative to D. If D equals THIS, then it is 0.
380 Otherwise, it recursively defd as
382 OFFSET_ + PARENT_L_->relative_coordinate (D) */
384 Grob::relative_coordinate (Grob const *refp, Axis a) const
389 /* We catch PARENT_L_ == nil case with this, but we crash if we did
390 not ask for the absolute coordinate (ie. REFP == nil.) */
391 if (refp == dim_cache_[a].parent_)
392 return get_offset (a);
394 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
397 /* Invoke callbacks to get offset relative to parent. */
399 Grob::get_offset (Axis a) const
401 Grob *me = (Grob *) this;
402 while (dim_cache_[a].offsets_left_)
404 int l = --me->dim_cache_[a].offsets_left_;
405 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_from_int (l));
406 SCM retval = scm_call_2 (cb, self_scm (), scm_from_int (a));
408 Real r = scm_to_double (retval);
409 if (isinf (r) || isnan (r))
411 programming_error (INFINITY_MSG);
414 me->dim_cache_[a].offset_ += r;
416 return dim_cache_[a].offset_;
420 Grob::is_empty (Axis a) const
422 return !(scm_is_pair (dim_cache_[a].dimension_)
423 || ly_is_procedure (dim_cache_[a].dimension_callback_));
427 Grob::flush_extent_cache (Axis axis)
429 Dimension_cache *d = &dim_cache_[axis];
430 if (ly_is_procedure (d->dimension_callback_)
431 && scm_is_pair (d->dimension_))
433 d->dimension_ = SCM_EOL;
435 if (get_parent (axis))
436 get_parent (axis)->flush_extent_cache (axis);
441 Grob::extent (Grob *refp, Axis a) const
443 Real x = relative_coordinate (refp, a);
445 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
448 SCM dimpair = d->dimension_;
449 if (scm_is_pair (dimpair))
451 else if (ly_is_procedure (d->dimension_callback_)
452 && d->dimension_ == SCM_EOL)
453 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_from_int (a));
457 if (!scm_is_pair (d->dimension_))
460 ext = ly_scm2interval (d->dimension_);
462 SCM extra = (a == X_AXIS)
463 ? get_property ("extra-X-extent")
464 : get_property ("extra-Y-extent");
467 if (scm_is_pair (extra))
469 ext[BIGGER] += scm_to_double (scm_cdr (extra));
470 ext[SMALLER] += scm_to_double (scm_car (extra));
473 extra = (a == X_AXIS)
474 ? get_property ("minimum-X-extent")
475 : get_property ("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) : this->class_name ();
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;
582 Grob::fixup_refpoint ()
584 for (int a = X_AXIS; a < NO_AXES; a++)
587 Grob *parent = get_parent (ax);
592 if (parent->get_system () != get_system () && get_system ())
594 Grob *newparent = parent->find_broken_piece (get_system ());
595 set_parent (newparent, ax);
598 if (Item *i = dynamic_cast<Item *> (this))
600 Item *parenti = dynamic_cast<Item *> (parent);
604 Direction my_dir = i->break_status_dir ();
605 if (my_dir != parenti->break_status_dir ())
607 Item *newparent = parenti->find_prebroken_piece (my_dir);
608 set_parent (newparent, ax);
616 Grob::warning (String s) const
618 SCM cause = self_scm ();
619 while (Grob *g = unsmob_grob (cause))
620 cause = g->get_property ("cause");
622 if (Music *m = unsmob_music (cause))
623 m->origin ()->warning (s);
629 Grob::programming_error (String s) const
631 SCM cause = self_scm ();
632 while (Grob *g = unsmob_grob (cause))
633 cause = g->get_property ("cause");
635 s = _f ("programming error: %s", s);
637 if (Music *m = unsmob_music (cause))
638 m->origin ()->message (s);
643 Grob::discretionary_processing ()
648 Grob::internal_has_interface (SCM k)
650 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
654 Grob::get_parent (Axis a) const
656 return dim_cache_[a].parent_;
659 /** Return Array of Grobs in SCM list LST */
661 ly_scm2grobs (SCM lst)
663 Link_array<Grob> arr;
665 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
668 arr.push (unsmob_grob (e));
676 Grob::get_key () const
681 /** Return SCM list of Grob array A */
683 ly_grobs2scm (Link_array<Grob> a)
686 for (int i = a.size (); i; i--)
687 s = scm_cons (a[i - 1]->self_scm (), s);
692 ADD_INTERFACE (Grob, "grob-interface",
693 "A grob represents a piece of music notation\n"
695 "All grobs have an X and Y-position on the page. These X and Y positions\n"
696 "are stored in a relative format, so they can easily be combined by\n"
697 "stacking them, hanging one grob to the side of another, and coupling\n"
698 "them into a grouping objects.\n"
700 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
701 "is stored relative to that reference point. For example the X-reference\n"
702 "point of a staccato dot usually is the note head that it applies\n"
703 "to. When the note head is moved, the staccato dot moves along\n"
706 "A grob is often associated with a symbol, but some grobs do not print\n"
707 "any symbols. They take care of grouping objects. For example, there is a\n"
708 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
709 "is also an abstract grob: it only moves around chords, but doesn't print\n"
712 "Grobs have a properties: Scheme variables, that can be read and set. "
713 "They have two types. Immutable variables "
714 "define the default style and behavior. They are shared between many objects. "
715 "They can be changed using @code{\\override} and @code{\\revert}. "
717 "Mutable properties are variables that are specific to one grob. Typically, "
718 "lists of other objects, or results from computations are stored in"
719 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
720 "sets a mutable property. ",
721 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
722 "Y-extent-callback print-function extra-offset spacing-procedure "
723 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
724 "meta layer before-line-breaking-callback "
726 "axis-group-parent-X "
727 "axis-group-parent-Y "
728 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
729 "minimum-Y-extent transparent");