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;
60 /* We do smobify_self () as the first step. Since the object lives
61 on the heap, none of its SCM variables are protected from
62 GC. After smobify_self (), they are. */
66 We always get a new key object for a new grob.
69 ((Object_key *)key_)->unprotect ();
71 SCM meta = get_property ("meta");
72 if (scm_is_pair (meta))
73 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
77 - destill this into a function, so we can re-init the immutable
78 properties with a new BASICPROPS value after
79 creation. Convenient eg. when using \override with
82 SCM off_callbacks[] = {
83 get_property ("X-offset-callbacks"),
84 get_property ("Y-offset-callbacks")
87 get_property ("X-extent"),
88 get_property ("Y-extent")
90 SCM extent_callbacks[] = {
91 get_property ("X-extent-callback"),
92 get_property ("Y-extent-callback")
95 for (int a = X_AXIS; a <= Y_AXIS; a++)
97 SCM l = off_callbacks[a];
99 if (scm_ilength (l) >= 0)
101 dim_cache_[a].offset_callbacks_ = l;
102 dim_cache_[a].offsets_left_ = scm_ilength (l);
105 programming_error ("[XY]-offset-callbacks must be a list");
107 SCM cb = extent_callbacks[a];
108 if (cb == SCM_BOOL_F)
109 dim_cache_[a].dimension_ = SCM_BOOL_F;
112 if (is_number_pair (xt))
113 dim_cache_[a].dimension_ = xt;
114 else if (ly_is_procedure (cb))
115 dim_cache_[a].dimension_callback_ = cb;
116 else if (cb == SCM_EOL
117 && ly_is_procedure (get_property ("print-function")))
118 dim_cache_[a].dimension_callback_ = stencil_extent_proc;
122 Grob::Grob (Grob const &s, int copy_index)
123 : dim_cache_ (s.dim_cache_)
125 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
126 original_ = (Grob *) & s;
129 immutable_property_alist_ = s.immutable_property_alist_;
130 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
131 interfaces_ = s.interfaces_;
132 object_alist_ = SCM_EOL;
134 /* No properties are copied. That is the job of
135 handle_broken_dependencies. */
141 ((Object_key *)key_)->unprotect ();
148 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
150 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
152 Grob *s = unsmob_grob (element_smob);
153 Axis a = (Axis) scm_to_int (scm_axis);
155 Stencil *m = s->get_stencil ();
159 return ly_interval2scm (e);
163 robust_relative_extent (Grob *me, Grob *refp, Axis a)
165 Interval ext = me->extent (refp, a);
167 ext.add_point (me->relative_coordinate (refp, a));
173 Grob::get_layout () const
175 return pscore_ ? pscore_->layout () : 0;
178 /* Recursively track all dependencies of this Grob. The status_ field
179 is used as a mark-field. It is marked with BUSY during execution
180 of this function, and marked with FINAL when finished.
182 FUNCPTR is the function to call to update this element. */
184 Grob::calculate_dependencies (int final, int busy, SCM funcname)
186 if (status_ >= final)
191 programming_error ("element is busy, come back later");
197 extract_grob_set (this, "dependencies", deps);
198 for (int i = 0; i < deps.size (); i++)
199 deps[i]->calculate_dependencies (final, busy, funcname);
201 SCM proc = internal_get_property (funcname);
202 if (ly_is_procedure (proc))
203 scm_call_1 (proc, this->self_scm ());
209 Grob::get_stencil () const
214 SCM stil = get_property ("stencil");
215 if (unsmob_stencil (stil))
216 return unsmob_stencil (stil);
218 stil = get_uncached_stencil ();
221 Grob *me = (Grob *) this;
222 me->set_property ("stencil", stil);
225 return unsmob_stencil (stil);
229 Grob::get_uncached_stencil () const
231 SCM proc = get_property ("print-function");
234 if (ly_is_procedure (proc))
235 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
237 if (Stencil *m = unsmob_stencil (stil))
239 if (to_boolean (get_property ("transparent")))
240 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
243 SCM expr = m->expr ();
244 if (point_and_click_global)
245 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
247 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
250 /* color support... see interpret_stencil_expression () for more... */
251 SCM color = get_property ("color");
252 if (color != SCM_EOL)
254 m = unsmob_stencil (stil);
255 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
259 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
270 Grob::do_break_processing ()
275 Grob::get_system () const
281 Grob::add_dependency (Grob *e)
284 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
286 programming_error ("null dependency added");
290 Grob::handle_broken_dependencies ()
292 Spanner *sp = dynamic_cast<Spanner *> (this);
297 /* THIS, SP is the original spanner. We use a special function
298 because some Spanners have enormously long lists in their
299 properties, and a special function fixes FOO */
301 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
302 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
304 System *system = get_system ();
308 && common_refpoint (system, X_AXIS)
309 && common_refpoint (system, Y_AXIS))
310 substitute_object_links (system->self_scm (), object_alist_);
311 else if (dynamic_cast<System *> (this))
312 substitute_object_links (SCM_UNDEFINED, object_alist_);
314 /* THIS element is `invalid'; it has been removed from all
315 dependencies, so let's junk the element itself.
317 Do not do this for System, since that would remove references
318 to the originals of score-grobs, which get then GC'd (a bad
323 /* Note that we still want references to this element to be
324 rearranged, and not silently thrown away, so we keep pointers like
325 {broken_into_{drul, array}, original}
333 mutable_property_alist_ = SCM_EOL;
334 object_alist_ = SCM_EOL;
335 immutable_property_alist_ = SCM_EOL;
336 interfaces_ = SCM_EOL;
338 set_extent (SCM_EOL, Y_AXIS);
339 set_extent (SCM_EOL, X_AXIS);
341 set_extent_callback (SCM_EOL, Y_AXIS);
342 set_extent_callback (SCM_EOL, X_AXIS);
344 for (int a = X_AXIS; a <= Y_AXIS; a++)
346 dim_cache_[a].offset_callbacks_ = SCM_EOL;
347 dim_cache_[a].offsets_left_ = 0;
352 Grob::handle_prebroken_dependencies ()
354 /* Don't do this in the derived method, since we want to keep access to
355 object_alist_ centralized. */
358 Item *it = dynamic_cast<Item *> (this);
359 substitute_object_links (scm_from_int (it->break_status_dir ()),
360 original_->object_alist_);
365 Grob::find_broken_piece (System *) const
370 /* Translate in one direction. */
372 Grob::translate_axis (Real y, Axis a)
374 if (isinf (y) || isnan (y))
375 programming_error (_ (INFINITY_MSG));
377 dim_cache_[a].offset_ += y;
380 /* Find the offset relative to D. If D equals THIS, then it is 0.
381 Otherwise, it recursively defd as
383 OFFSET_ + PARENT_L_->relative_coordinate (D) */
385 Grob::relative_coordinate (Grob const *refp, Axis a) const
390 /* We catch PARENT_L_ == nil case with this, but we crash if we did
391 not ask for the absolute coordinate (ie. REFP == nil.) */
392 if (refp == dim_cache_[a].parent_)
393 return get_offset (a);
395 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
398 /* Invoke callbacks to get offset relative to parent. */
400 Grob::get_offset (Axis a) const
402 Grob *me = (Grob *) this;
403 while (dim_cache_[a].offsets_left_)
405 int l = --me->dim_cache_[a].offsets_left_;
406 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_from_int (l));
407 SCM retval = scm_call_2 (cb, self_scm (), scm_from_int (a));
409 Real r = scm_to_double (retval);
410 if (isinf (r) || isnan (r))
412 programming_error (INFINITY_MSG);
415 me->dim_cache_[a].offset_ += r;
417 return dim_cache_[a].offset_;
421 Grob::is_empty (Axis a) const
423 return !(scm_is_pair (dim_cache_[a].dimension_)
424 || ly_is_procedure (dim_cache_[a].dimension_callback_));
428 Grob::flush_extent_cache (Axis axis)
430 Dimension_cache *d = &dim_cache_[axis];
431 if (ly_is_procedure (d->dimension_callback_)
432 && scm_is_pair (d->dimension_))
434 d->dimension_ = SCM_EOL;
436 if (get_parent (axis))
437 get_parent (axis)->flush_extent_cache (axis);
442 Grob::extent (Grob *refp, Axis a) const
444 Real x = relative_coordinate (refp, a);
446 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
449 SCM dimpair = d->dimension_;
450 if (scm_is_pair (dimpair))
452 else if (ly_is_procedure (d->dimension_callback_)
453 && d->dimension_ == SCM_EOL)
454 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_from_int (a));
458 if (!scm_is_pair (d->dimension_))
461 ext = ly_scm2interval (d->dimension_);
463 SCM extra = (a == X_AXIS)
464 ? get_property ("extra-X-extent")
465 : get_property ("extra-Y-extent");
468 if (scm_is_pair (extra))
470 ext[BIGGER] += scm_to_double (scm_cdr (extra));
471 ext[SMALLER] += scm_to_double (scm_car (extra));
474 extra = (a == X_AXIS)
475 ? get_property ("minimum-X-extent")
476 : get_property ("minimum-Y-extent");
478 if (scm_is_pair (extra))
479 ext.unite (Interval (scm_to_double (scm_car (extra)),
480 scm_to_double (scm_cdr (extra))));
487 /* Find the group-element which has both #this# and #s# */
489 Grob::common_refpoint (Grob const *s, Axis a) const
491 /* I don't like the quadratic aspect of this code, but I see no
492 other way. The largest chain of parents might be 10 high or so,
493 so it shouldn't be a real issue. */
494 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
495 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
503 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
505 for (; scm_is_pair (elist); elist = scm_cdr (elist))
506 if (Grob *s = unsmob_grob (scm_car (elist)))
509 common = common->common_refpoint (s, a);
518 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
520 for (int i = arr.size (); i--;)
521 if (Grob *s = arr[i])
524 common = common->common_refpoint (s, a);
535 SCM meta = get_property ("meta");
536 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
537 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
538 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
542 Grob::add_offset_callback (SCM cb, Axis a)
544 if (!has_offset_callback (cb, a))
546 dim_cache_[a].offset_callbacks_
547 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
548 dim_cache_[a].offsets_left_++;
553 Grob::has_extent_callback (SCM cb, Axis a) const
555 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
559 Grob::has_offset_callback (SCM cb, Axis a) const
561 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
565 Grob::set_extent (SCM dc, Axis a)
567 dim_cache_[a].dimension_ = dc;
571 Grob::set_extent_callback (SCM dc, Axis a)
573 dim_cache_[a].dimension_callback_ = dc;
577 Grob::set_parent (Grob *g, Axis a)
579 dim_cache_[a].parent_ = g;
583 Grob::fixup_refpoint ()
585 for (int a = X_AXIS; a < NO_AXES; a++)
588 Grob *parent = get_parent (ax);
593 if (parent->get_system () != get_system () && get_system ())
595 Grob *newparent = parent->find_broken_piece (get_system ());
596 set_parent (newparent, ax);
599 if (Item *i = dynamic_cast<Item *> (this))
601 Item *parenti = dynamic_cast<Item *> (parent);
605 Direction my_dir = i->break_status_dir ();
606 if (my_dir != parenti->break_status_dir ())
608 Item *newparent = parenti->find_prebroken_piece (my_dir);
609 set_parent (newparent, ax);
617 Grob::warning (String s) const
619 SCM cause = self_scm ();
620 while (Grob *g = unsmob_grob (cause))
621 cause = g->get_property ("cause");
623 if (Music *m = unsmob_music (cause))
624 m->origin ()->warning (s);
630 Grob::programming_error (String s) const
632 s = _f ("programming error: %s", s);
636 Grob::discretionary_processing ()
641 Grob::internal_has_interface (SCM k)
643 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
647 Grob::get_parent (Axis a) const
649 return dim_cache_[a].parent_;
652 /** Return Array of Grobs in SCM list LST */
654 ly_scm2grobs (SCM lst)
656 Link_array<Grob> arr;
658 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
661 arr.push (unsmob_grob (e));
669 Grob::get_key () const
674 /** Return SCM list of Grob array A */
676 ly_grobs2scm (Link_array<Grob> a)
679 for (int i = a.size (); i; i--)
680 s = scm_cons (a[i - 1]->self_scm (), s);
685 ADD_INTERFACE (Grob, "grob-interface",
686 "A grob represents a piece of music notation\n"
688 "All grobs have an X and Y-position on the page. These X and Y positions\n"
689 "are stored in a relative format, so they can easily be combined by\n"
690 "stacking them, hanging one grob to the side of another, and coupling\n"
691 "them into a grouping objects.\n"
693 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
694 "is stored relative to that reference point. For example the X-reference\n"
695 "point of a staccato dot usually is the note head that it applies\n"
696 "to. When the note head is moved, the staccato dot moves along\n"
699 "A grob is often associated with a symbol, but some grobs do not print\n"
700 "any symbols. They take care of grouping objects. For example, there is a\n"
701 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
702 "is also an abstract grob: it only moves around chords, but doesn't print\n"
705 "Grobs have a properties: Scheme variables, that can be read and set. "
706 "They have two types. Immutable variables "
707 "define the default style and behavior. They are shared between many objects. "
708 "They can be changed using @code{\\override} and @code{\\revert}. "
710 "Mutable properties are variables that are specific to one grob. Typically, "
711 "lists of other objects, or results from computations are stored in"
712 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
713 "sets a mutable property. ",
714 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
715 "Y-extent-callback print-function extra-offset spacing-procedure "
716 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
717 "meta layer before-line-breaking-callback "
719 "axis-group-parent-X "
720 "axis-group-parent-Y "
721 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
722 "minimum-Y-extent transparent");