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 ();
73 SCM meta = get_property ("meta");
74 if (scm_is_pair (meta))
76 interfaces_ = scm_cdr (scm_assoc (ly_symbol2scm ("interfaces"), meta));
81 - destill this into a function, so we can re-init the immutable
82 properties with a new BASICPROPS value after
83 creation. Convenient eg. when using \override with
86 char const *onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
87 char const *xnames[] = {"X-extent", "Y-extent"};
88 char const *enames[] = {"X-extent-callback", "Y-extent-callback"};
90 for (int a = X_AXIS; a <= Y_AXIS; a++)
92 SCM l = get_property (onames[a]);
94 if (scm_ilength (l) >= 0)
96 dim_cache_[a].offset_callbacks_ = l;
97 dim_cache_[a].offsets_left_ = scm_ilength (l);
100 programming_error ("[XY]-offset-callbacks must be a list");
102 SCM cb = get_property (enames[a]);
103 if (cb == SCM_BOOL_F)
105 dim_cache_[a].dimension_ = SCM_BOOL_F;
108 SCM xt = get_property (xnames[a]);
109 if (is_number_pair (xt))
111 dim_cache_[a].dimension_ = xt;
113 else if (ly_is_procedure (cb))
115 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. */
143 ((Object_key*)key_)->unprotect ();
151 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
153 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
155 Grob *s = unsmob_grob (element_smob);
156 Axis a = (Axis) scm_to_int (scm_axis);
158 Stencil *m = s->get_stencil ();
162 return ly_interval2scm (e);
166 robust_relative_extent (Grob *me, Grob *refp, Axis a)
168 Interval ext = me->extent (refp, a);
171 ext.add_point (me->relative_coordinate (refp, a));
178 Grob::get_layout () const
180 return pscore_ ? pscore_->layout () : 0;
183 /* Recursively track all dependencies of this Grob. The status_ field
184 is used as a mark-field. It is marked with BUSY during execution
185 of this function, and marked with FINAL when finished.
187 FUNCPTR is the function to call to update this element. */
189 Grob::calculate_dependencies (int final, int busy, SCM funcname)
191 if (status_ >= final)
196 programming_error ("element is busy, come back later");
202 extract_grob_set (this, "dependencies", deps);
203 for (int i = 0; i < deps.size (); i++)
204 deps[i]->calculate_dependencies (final, busy, funcname);
206 SCM proc = internal_get_property (funcname);
207 if (ly_is_procedure (proc))
208 scm_call_1 (proc, this->self_scm ());
214 Grob::get_stencil () const
219 SCM stil = get_property ("stencil");
220 if (unsmob_stencil (stil))
221 return unsmob_stencil (stil);
223 stil = get_uncached_stencil ();
226 Grob *me = (Grob *) this;
227 me->set_property ("stencil", stil);
230 return unsmob_stencil (stil);
234 Grob::get_uncached_stencil () const
236 SCM proc = get_property ("print-function");
239 if (ly_is_procedure (proc))
240 stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
242 if (Stencil *m = unsmob_stencil (stil))
244 if (to_boolean (get_property ("transparent")))
245 stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
248 SCM expr = m->expr ();
249 if (point_and_click_global)
250 expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (), expr);
252 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
255 /* color support... see interpret_stencil_expression () for more... */
256 SCM color = get_property ("color");
257 if (color != SCM_EOL)
259 m = unsmob_stencil (stil);
260 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
264 stil = Stencil (m->extent_box (), expr).smobbed_copy ();
275 Grob::do_break_processing ()
280 Grob::get_system () const
286 Grob::add_dependency (Grob *e)
289 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"), e);
291 programming_error ("null dependency added");
295 Grob::handle_broken_dependencies ()
297 Spanner *sp = dynamic_cast<Spanner *> (this);
302 /* THIS, SP is the original spanner. We use a special function
303 because some Spanners have enormously long lists in their
304 properties, and a special function fixes FOO */
306 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
307 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
310 System *system = get_system ();
314 && common_refpoint (system, X_AXIS)
315 && common_refpoint (system, Y_AXIS))
316 substitute_object_links (system->self_scm (), object_alist_);
317 else if (dynamic_cast<System *> (this))
318 substitute_object_links (SCM_UNDEFINED, object_alist_);
320 /* THIS element is `invalid'; it has been removed from all
321 dependencies, so let's junk the element itself.
323 Do not do this for System, since that would remove references
324 to the originals of score-grobs, which get then GC'd (a bad
329 /* Note that we still want references to this element to be
330 rearranged, and not silently thrown away, so we keep pointers like
331 {broken_into_{drul, array}, original}
339 mutable_property_alist_ = SCM_EOL;
340 object_alist_ = SCM_EOL;
341 immutable_property_alist_ = SCM_EOL;
342 interfaces_ = SCM_EOL;
344 set_extent (SCM_EOL, Y_AXIS);
345 set_extent (SCM_EOL, X_AXIS);
347 set_extent_callback (SCM_EOL, Y_AXIS);
348 set_extent_callback (SCM_EOL, X_AXIS);
350 for (int a = X_AXIS; a <= Y_AXIS; a++)
352 dim_cache_[a].offset_callbacks_ = SCM_EOL;
353 dim_cache_[a].offsets_left_ = 0;
358 Grob::handle_prebroken_dependencies ()
360 /* Don't do this in the derived method, since we want to keep access to
361 object_alist_ centralized. */
364 Item *it = dynamic_cast<Item *> (this);
365 substitute_object_links (scm_int2num (it->break_status_dir ()),
366 original_->object_alist_);
371 Grob::find_broken_piece (System *) const
376 /* Translate in one direction. */
378 Grob::translate_axis (Real y, Axis a)
380 if (isinf (y) || isnan (y))
381 programming_error (_ (INFINITY_MSG));
383 dim_cache_[a].offset_ += y;
386 /* Find the offset relative to D. If D equals THIS, then it is 0.
387 Otherwise, it recursively defd as
389 OFFSET_ + PARENT_L_->relative_coordinate (D) */
391 Grob::relative_coordinate (Grob const *refp, Axis a) const
396 /* We catch PARENT_L_ == nil case with this, but we crash if we did
397 not ask for the absolute coordinate (ie. REFP == nil.) */
398 if (refp == dim_cache_[a].parent_)
399 return get_offset (a);
401 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
404 /* Invoke callbacks to get offset relative to parent. */
406 Grob::get_offset (Axis a) const
408 Grob *me = (Grob *) this;
409 while (dim_cache_[a].offsets_left_)
411 int l = --me->dim_cache_[a].offsets_left_;
412 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
413 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
415 Real r = scm_to_double (retval);
416 if (isinf (r) || isnan (r))
418 programming_error (INFINITY_MSG);
421 me->dim_cache_[a].offset_ += r;
423 return dim_cache_[a].offset_;
427 Grob::is_empty (Axis a) const
429 return !(scm_is_pair (dim_cache_[a].dimension_)
430 || ly_is_procedure (dim_cache_[a].dimension_callback_));
434 Grob::flush_extent_cache (Axis axis)
436 Dimension_cache *d = &dim_cache_[axis];
437 if (ly_is_procedure (d->dimension_callback_)
438 && scm_is_pair (d->dimension_))
440 d->dimension_ = SCM_EOL;
442 if (get_parent (axis))
443 get_parent (axis)->flush_extent_cache (axis);
448 Grob::extent (Grob *refp, Axis a) const
450 Real x = relative_coordinate (refp, a);
452 Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
455 SCM dimpair = d->dimension_;
456 if (scm_is_pair (dimpair))
458 else if (ly_is_procedure (d->dimension_callback_)
459 && d->dimension_ == SCM_EOL)
460 d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
464 if (!scm_is_pair (d->dimension_))
467 ext = ly_scm2interval (d->dimension_);
469 SCM extra = get_property (a == X_AXIS
474 if (scm_is_pair (extra))
476 ext[BIGGER] += scm_to_double (scm_cdr (extra));
477 ext[SMALLER] += scm_to_double (scm_car (extra));
480 extra = get_property (a == X_AXIS
482 : "minimum-Y-extent");
483 if (scm_is_pair (extra))
484 ext.unite (Interval (scm_to_double (scm_car (extra)),
485 scm_to_double (scm_cdr (extra))));
492 /* Find the group-element which has both #this# and #s# */
494 Grob::common_refpoint (Grob const *s, Axis a) const
496 /* I don't like the quadratic aspect of this code, but I see no
497 other way. The largest chain of parents might be 10 high or so,
498 so it shouldn't be a real issue. */
499 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
500 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
508 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
510 for (; scm_is_pair (elist); elist = scm_cdr (elist))
511 if (Grob *s = unsmob_grob (scm_car (elist)))
514 common = common->common_refpoint (s, a);
523 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
525 for (int i = arr.size (); i--;)
526 if (Grob *s = arr[i])
529 common = common->common_refpoint (s, a);
540 SCM meta = get_property ("meta");
541 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
542 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
543 return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
547 Grob::add_offset_callback (SCM cb, Axis a)
549 if (!has_offset_callback (cb, a))
551 dim_cache_[a].offset_callbacks_
552 = scm_cons (cb, dim_cache_[a].offset_callbacks_);
553 dim_cache_[a].offsets_left_++;
558 Grob::has_extent_callback (SCM cb, Axis a) const
560 return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
564 Grob::has_offset_callback (SCM cb, Axis a) const
566 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
570 Grob::set_extent (SCM dc, Axis a)
572 dim_cache_[a].dimension_ = dc;
576 Grob::set_extent_callback (SCM dc, Axis a)
578 dim_cache_[a].dimension_callback_ = dc;
582 Grob::set_parent (Grob *g, Axis a)
584 dim_cache_[a].parent_ = g;
588 Grob::fixup_refpoint ()
590 for (int a = X_AXIS; a < NO_AXES; a++)
593 Grob *parent = get_parent (ax);
598 if (parent->get_system () != get_system () && get_system ())
600 Grob *newparent = parent->find_broken_piece (get_system ());
601 set_parent (newparent, ax);
604 if (Item *i = dynamic_cast<Item *> (this))
606 Item *parenti = dynamic_cast<Item *> (parent);
610 Direction my_dir = i->break_status_dir ();
611 if (my_dir != parenti->break_status_dir ())
613 Item *newparent = parenti->find_prebroken_piece (my_dir);
614 set_parent (newparent, ax);
622 Grob::warning (String s) const
624 SCM cause = self_scm ();
625 while (Grob *g = unsmob_grob (cause))
626 cause = g->get_property ("cause");
628 if (Music *m = unsmob_music (cause))
629 m->origin ()->warning (s);
635 Grob::programming_error (String s) const
637 s = _f ("programming error: %s", s);
641 Grob::discretionary_processing ()
647 Grob::internal_has_interface (SCM k)
649 return scm_c_memq (k, interfaces_) != SCM_BOOL_F;
653 Grob::get_parent (Axis a) const
655 return dim_cache_[a].parent_;
658 /** Return Array of Grobs in SCM list LST */
660 ly_scm2grobs (SCM lst)
662 Link_array<Grob> arr;
664 for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
667 arr.push (unsmob_grob (e));
675 Grob::get_key () const
680 /** Return SCM list of Grob array A */
682 ly_grobs2scm (Link_array<Grob> a)
685 for (int i = a.size (); i; i--)
686 s = scm_cons (a[i - 1]->self_scm (), s);
691 ADD_INTERFACE (Grob, "grob-interface",
692 "A grob represents a piece of music notation\n"
694 "All grobs have an X and Y-position on the page. These X and Y positions\n"
695 "are stored in a relative format, so they can easily be combined by\n"
696 "stacking them, hanging one grob to the side of another, and coupling\n"
697 "them into a grouping objects.\n"
699 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
700 "is stored relative to that reference point. For example the X-reference\n"
701 "point of a staccato dot usually is the note head that it applies\n"
702 "to. When the note head is moved, the staccato dot moves along\n"
705 "A grob is often associated with a symbol, but some grobs do not print\n"
706 "any symbols. They take care of grouping objects. For example, there is a\n"
707 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
708 "is also an abstract grob: it only moves around chords, but doesn't print\n"
711 "Grobs have a properties: Scheme variables, that can be read and set. "
712 "They have two types. Immutable variables "
713 "define the default style and behavior. They are shared between many objects. "
714 "They can be changed using @code{\\override} and @code{\\revert}. "
716 "Mutable properties are variables that are specific to one grob. Typically, "
717 "lists of other objects, or results from computations are stored in"
718 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
719 "sets a mutable property. ",
720 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
721 "Y-extent-callback print-function extra-offset spacing-procedure "
722 "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
723 "meta layer before-line-breaking-callback "
725 "axis-group-parent-X "
726 "axis-group-parent-Y "
727 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
728 "minimum-Y-extent transparent");