2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
13 #include "align-interface.hh"
15 #include "international.hh"
20 #include "output-def.hh"
21 #include "pointer-group-interface.hh"
23 #include "stream-event.hh"
27 #include "ly-smobs.icc"
30 Grob::clone (int count) const
32 return new Grob (*this, count);
35 Grob::Grob (SCM basicprops,
36 Object_key const *key)
40 /* FIXME: default should be no callback. */
44 interfaces_ = SCM_EOL;
45 immutable_property_alist_ = basicprops;
46 mutable_property_alist_ = SCM_EOL;
47 object_alist_ = SCM_EOL;
49 /* We do smobify_self () as the first step. Since the object lives
50 on the heap, none of its SCM variables are protected from
51 GC. After smobify_self (), they are. */
55 We always get a new key object for a new grob.
58 ((Object_key *)key_)->unprotect ();
60 SCM meta = get_property ("meta");
61 if (scm_is_pair (meta))
63 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
65 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
66 if (scm_is_pair (object_cbs))
68 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
69 set_object (scm_caar (s), scm_cdar (s));
73 if (get_property_data ("X-extent") == SCM_EOL)
74 set_property ("X-extent", Grob::stencil_width_proc);
75 if (get_property_data ("Y-extent") == SCM_EOL)
76 set_property ("Y-extent", Grob::stencil_height_proc);
79 Grob::Grob (Grob const &s, int copy_index)
80 : dim_cache_ (s.dim_cache_)
82 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
83 original_ = (Grob *) & s;
86 immutable_property_alist_ = s.immutable_property_alist_;
87 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
88 interfaces_ = s.interfaces_;
89 object_alist_ = SCM_EOL;
95 ((Object_key *)key_)->unprotect ();
101 /****************************************************************
103 ****************************************************************/
106 Grob::get_stencil () const
111 SCM stil = get_property ("stencil");
112 return unsmob_stencil (stil);
116 Grob::get_print_stencil () const
118 SCM stil = get_property ("stencil");
121 if (Stencil *m = unsmob_stencil (stil))
124 if (to_boolean (get_property ("transparent")))
125 retval = Stencil (m->extent_box (), SCM_EOL);
128 SCM expr = m->expr ();
129 expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
132 retval = Stencil (m->extent_box (), expr);
135 SCM rot = get_property ("rotation");
136 if (scm_is_pair (rot))
138 Real angle = scm_to_double (scm_car (rot));
139 Real x = scm_to_double (scm_cadr (rot));
140 Real y = scm_to_double (scm_caddr (rot));
142 retval.rotate (angle, Offset (x, y));
145 /* color support... see interpret_stencil_expression () for more... */
146 SCM color = get_property ("color");
147 if (scm_is_pair (color))
149 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
153 retval = Stencil (retval.extent_box (), expr);
161 /****************************************************************
163 ****************************************************************/
165 Grob::do_break_processing ()
170 Grob::discretionary_processing ()
175 Grob::get_system () const
182 Grob::handle_broken_dependencies ()
184 Spanner *sp = dynamic_cast<Spanner *> (this);
185 if (original () && sp)
189 /* THIS, SP is the original spanner. We use a special function
190 because some Spanners have enormously long lists in their
191 properties, and a special function fixes FOO */
193 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
194 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
196 System *system = get_system ();
200 && common_refpoint (system, X_AXIS)
201 && common_refpoint (system, Y_AXIS))
202 substitute_object_links (system->self_scm (), object_alist_);
203 else if (dynamic_cast<System *> (this))
204 substitute_object_links (SCM_UNDEFINED, object_alist_);
206 /* THIS element is `invalid'; it has been removed from all
207 dependencies, so let's junk the element itself.
209 Do not do this for System, since that would remove references
210 to the originals of score-grobs, which get then GC'd (a bad
215 /* Note that we still want references to this element to be
216 rearranged, and not silently thrown away, so we keep pointers like
217 {broken_into_{drul, array}, original}
225 for (int a = X_AXIS; a < NO_AXES; a++)
226 dim_cache_[a].clear ();
228 mutable_property_alist_ = SCM_EOL;
229 object_alist_ = SCM_EOL;
230 immutable_property_alist_ = SCM_EOL;
231 interfaces_ = SCM_EOL;
235 Grob::handle_prebroken_dependencies ()
237 /* Don't do this in the derived method, since we want to keep access to
238 object_alist_ centralized. */
241 Item *it = dynamic_cast<Item *> (this);
242 substitute_object_links (scm_from_int (it->break_status_dir ()),
243 original ()->object_alist_);
248 Grob::find_broken_piece (System *) const
253 /****************************************************************
255 ****************************************************************/
258 Grob::translate_axis (Real y, Axis a)
260 if (isinf (y) || isnan (y))
262 programming_error (_ ("Infinity or NaN encountered"));
266 if (!dim_cache_[a].offset_)
267 dim_cache_[a].offset_ = new Real (y);
269 *dim_cache_[a].offset_ += y;
272 /* Find the offset relative to D. If D equals THIS, then it is 0.
273 Otherwise, it recursively defd as
275 OFFSET_ + PARENT_L_->relative_coordinate (D) */
277 Grob::relative_coordinate (Grob const *refp, Axis a) const
282 /* We catch PARENT_L_ == nil case with this, but we crash if we did
283 not ask for the absolute coordinate (ie. REFP == nil.) */
284 Real off = get_offset (a);
285 if (refp == dim_cache_[a].parent_)
288 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
294 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
301 if (dim_cache_[Y_AXIS].offset_)
302 off = *dim_cache_[Y_AXIS].offset_;
305 SCM proc = get_property_data ("Y-offset");
307 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
308 off = robust_scm2double (call_pure_function (proc,
309 scm_list_1 (self_scm ()),
312 delete dim_cache_[Y_AXIS].offset_;
313 dim_cache_[Y_AXIS].offset_ = 0;
316 /* we simulate positioning-done if we are the child of a VerticalAlignment,
317 but only if we don't have a cached offset. If we do have a cached offset,
318 it probably means that the Alignment was fixed and it has already been
321 if (Grob *p = get_parent (Y_AXIS))
324 if (Align_interface::has_interface (p) && !dim_cache_[Y_AXIS].offset_)
325 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
327 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
332 /* Invoke callbacks to get offset relative to parent. */
334 Grob::get_offset (Axis a) const
336 if (dim_cache_[a].offset_)
337 return *dim_cache_[a].offset_;
339 Grob *me = (Grob *) this;
341 SCM sym = axis_offset_symbol (a);
342 me->dim_cache_[a].offset_ = new Real (0.0);
345 UGH: can't fold next 2 statements together. Apparently GCC thinks
346 dim_cache_[a].offset_ is unaliased.
348 Real off = robust_scm2double (internal_get_property (sym), 0.0);
349 if (me->dim_cache_[a].offset_)
351 *me->dim_cache_[a].offset_ += off;
352 me->del_property (sym);
353 return *me->dim_cache_[a].offset_;
360 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
362 if (pure && a != Y_AXIS)
363 programming_error ("tried to get pure X-offset");
364 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
365 : relative_coordinate (refp, a);
368 /****************************************************************
370 ****************************************************************/
373 Grob::flush_extent_cache (Axis axis)
375 if (dim_cache_[axis].extent_)
378 Ugh, this is not accurate; will flush property, causing
379 callback to be called if.
381 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
382 delete dim_cache_[axis].extent_;
383 dim_cache_[axis].extent_ = 0;
384 if (get_parent (axis))
385 get_parent (axis)->flush_extent_cache (axis);
391 Grob::extent (Grob *refp, Axis a) const
393 Real offset = relative_coordinate (refp, a);
395 if (dim_cache_[a].extent_)
397 real_ext = *dim_cache_[a].extent_;
402 Order is significant: ?-extent may trigger suicide.
406 ? ly_symbol2scm ("X-extent")
407 : ly_symbol2scm ("Y-extent");
409 SCM ext = internal_get_property (ext_sym);
410 if (is_number_pair (ext))
411 real_ext.unite (ly_scm2interval (ext));
415 ? ly_symbol2scm ("minimum-X-extent")
416 : ly_symbol2scm ("minimum-Y-extent");
417 SCM min_ext = internal_get_property (min_ext_sym);
418 if (is_number_pair (min_ext))
419 real_ext.unite (ly_scm2interval (min_ext));
421 ((Grob*)this)->dim_cache_[a].extent_ = new Interval (real_ext);
424 real_ext.translate (offset);
430 Grob::pure_height (Grob *refp, int start, int end)
432 SCM proc = get_property_data ( ly_symbol2scm ("Y-extent"));
433 Interval iv = robust_scm2interval (call_pure_function (proc,
434 scm_list_1 (self_scm ()),
437 Real offset = pure_relative_y_coordinate (refp, start, end);
439 SCM min_ext = get_property ("minimum-Y-extent");
441 /* we don't add minimum-Y-extent if the extent is empty. This solves
442 a problem with Hara-kiri spanners. They would request_suicide and
443 return empty extents, but we would force them here to be large. */
444 if (!iv.is_empty () && is_number_pair (min_ext))
445 iv.unite (ly_scm2interval (min_ext));
447 iv.translate (offset);
452 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
454 if (pure && a != Y_AXIS)
455 programming_error ("tried to get pure width");
456 return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
460 Grob::spanned_rank_iv ()
462 return Interval_t<int> (-1, 0);
465 /****************************************************************
467 ****************************************************************/
469 /* Find the group-element which has both #this# and #s# */
471 Grob::common_refpoint (Grob const *s, Axis a) const
473 /* I don't like the quadratic aspect of this code, but I see no
474 other way. The largest chain of parents might be 10 high or so,
475 so it shouldn't be a real issue. */
476 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
477 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
485 Grob::set_parent (Grob *g, Axis a)
487 dim_cache_[a].parent_ = g;
491 Grob::get_parent (Axis a) const
493 return dim_cache_[a].parent_;
498 Grob::fixup_refpoint ()
500 for (int a = X_AXIS; a < NO_AXES; a++)
503 Grob *parent = get_parent (ax);
508 if (parent->get_system () != get_system () && get_system ())
510 Grob *newparent = parent->find_broken_piece (get_system ());
511 set_parent (newparent, ax);
514 if (Item *i = dynamic_cast<Item *> (this))
516 Item *parenti = dynamic_cast<Item *> (parent);
520 Direction my_dir = i->break_status_dir ();
521 if (my_dir != parenti->break_status_dir ())
523 Item *newparent = parenti->find_prebroken_piece (my_dir);
524 set_parent (newparent, ax);
532 /****************************************************************
534 ****************************************************************/
536 Grob::warning (string s) const
538 SCM cause = self_scm ();
539 while (Grob *g = unsmob_grob (cause))
540 cause = g->get_property ("cause");
542 /* ES TODO: cause can't be Music*/
543 if (Music *m = unsmob_music (cause))
544 m->origin ()->warning (s);
545 else if (Stream_event *ev = unsmob_stream_event (cause))
546 ev->origin ()->warning (s);
555 SCM meta = get_property ("meta");
556 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
557 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
558 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
562 Grob::programming_error (string s) const
564 SCM cause = self_scm ();
565 while (Grob *g = unsmob_grob (cause))
566 cause = g->get_property ("cause");
568 s = _f ("programming error: %s", s);
570 /* ES TODO: cause can't be Music*/
571 if (Music *m = unsmob_music (cause))
572 m->origin ()->message (s);
573 else if (Stream_event *ev = unsmob_stream_event (cause))
574 ev->origin ()->warning (s);
581 "A grob represents a piece of music notation\n"
583 "All grobs have an X and Y-position on the page. These X and Y positions\n"
584 "are stored in a relative format, so they can easily be combined by\n"
585 "stacking them, hanging one grob to the side of another, and coupling\n"
586 "them into a grouping objects.\n"
588 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
589 "is stored relative to that reference point. For example the X-reference\n"
590 "point of a staccato dot usually is the note head that it applies\n"
591 "to. When the note head is moved, the staccato dot moves along\n"
594 "A grob is often associated with a symbol, but some grobs do not print\n"
595 "any symbols. They take care of grouping objects. For example, there is a\n"
596 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
597 "is also an abstract grob: it only moves around chords, but doesn't print\n"
600 "Grobs have a properties: Scheme variables, that can be read and set. "
601 "They have two types. Immutable variables "
602 "define the default style and behavior. They are shared between many objects. "
603 "They can be changed using @code{\\override} and @code{\\revert}. "
605 "Mutable properties are variables that are specific to one grob. Typically, "
606 "lists of other objects, or results from computations are stored in"
607 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
608 "sets a mutable property. "
610 "The properties @code{after-line-breaking} and @code{before-line-breaking} "
611 "are dummies that are not user-serviceable. "
620 "after-line-breaking "
622 "axis-group-parent-X "
623 "axis-group-parent-Y "
624 "before-line-breaking "
635 "outside-staff-horizontal-padding "
636 "outside-staff-padding "
637 "outside-staff-priority "
645 /****************************************************************
647 ****************************************************************/
650 grob_stencil_extent (Grob *me, Axis a)
652 Stencil *m = me->get_stencil ();
656 return ly_interval2scm (e);
660 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
662 Grob::stencil_height (SCM smob)
664 Grob *me = unsmob_grob (smob);
665 return grob_stencil_extent (me, Y_AXIS);
668 MAKE_SCHEME_CALLBACK(Grob, y_parent_positioning, 1);
670 Grob::y_parent_positioning (SCM smob)
672 Grob *me = unsmob_grob (smob);
673 Grob *par = me->get_parent (Y_AXIS);
675 (void) par->get_property ("positioning-done");
677 return scm_from_double (0.0);
681 MAKE_SCHEME_CALLBACK(Grob, x_parent_positioning, 1);
683 Grob::x_parent_positioning (SCM smob)
685 Grob *me = unsmob_grob (smob);
687 Grob *par = me->get_parent (X_AXIS);
689 (void) par->get_property ("positioning-done");
691 return scm_from_double (0.0);
694 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
696 Grob::stencil_width (SCM smob)
698 Grob *me = unsmob_grob (smob);
699 return grob_stencil_extent (me, X_AXIS);
705 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
707 for (; scm_is_pair (elist); elist = scm_cdr (elist))
708 if (Grob *s = unsmob_grob (scm_car (elist)))
711 common = common->common_refpoint (s, a);
720 common_refpoint_of_array (vector<Grob*> const &arr, Grob *common, Axis a)
722 for (vsize i = arr.size (); i--;)
723 if (Grob *s = arr[i])
726 common = common->common_refpoint (s, a);
735 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
737 Interval ext = me->extent (refpoint, a);
739 ext.add_point (me->relative_coordinate (refpoint, a));