2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2009 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"
22 #include "program-option.hh"
24 #include "stream-event.hh"
28 #include "ly-smobs.icc"
33 return new Grob (*this);
36 Grob::Grob (SCM basicprops)
39 /* FIXME: default should be no callback. */
43 interfaces_ = SCM_EOL;
44 immutable_property_alist_ = basicprops;
45 mutable_property_alist_ = SCM_EOL;
46 object_alist_ = SCM_EOL;
48 /* We do smobify_self () as the first step. Since the object lives
49 on the heap, none of its SCM variables are protected from
50 GC. After smobify_self (), they are. */
53 SCM meta = get_property ("meta");
54 if (scm_is_pair (meta))
56 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
58 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
59 if (scm_is_pair (object_cbs))
61 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
62 set_object (scm_caar (s), scm_cdar (s));
66 if (get_property_data ("X-extent") == SCM_EOL)
67 set_property ("X-extent", Grob::stencil_width_proc);
68 if (get_property_data ("Y-extent") == SCM_EOL)
69 set_property ("Y-extent", Grob::stencil_height_proc);
72 Grob::Grob (Grob const &s)
73 : dim_cache_ (s.dim_cache_)
75 original_ = (Grob *) & s;
78 immutable_property_alist_ = s.immutable_property_alist_;
79 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
80 interfaces_ = s.interfaces_;
81 object_alist_ = SCM_EOL;
91 /****************************************************************
93 ****************************************************************/
96 Grob::get_stencil () const
101 SCM stil = get_property ("stencil");
102 return unsmob_stencil (stil);
106 Grob::get_print_stencil () const
108 SCM stil = get_property ("stencil");
111 if (Stencil *m = unsmob_stencil (stil))
114 if (to_boolean (get_property ("transparent")))
115 retval = Stencil (m->extent_box (), SCM_EOL);
118 SCM expr = m->expr ();
119 expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
122 retval = Stencil (m->extent_box (), expr);
125 SCM rot = get_property ("rotation");
126 if (scm_is_pair (rot))
128 Real angle = scm_to_double (scm_car (rot));
129 Real x = scm_to_double (scm_cadr (rot));
130 Real y = scm_to_double (scm_caddr (rot));
132 retval.rotate_degrees (angle, Offset (x, y));
135 /* color support... see interpret_stencil_expression () for more... */
136 SCM color = get_property ("color");
137 if (scm_is_pair (color))
139 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
143 retval = Stencil (retval.extent_box (), expr);
151 /****************************************************************
153 ****************************************************************/
155 Grob::do_break_processing ()
160 Grob::discretionary_processing ()
165 Grob::get_system () const
172 Grob::handle_broken_dependencies ()
174 Spanner *sp = dynamic_cast<Spanner *> (this);
175 if (original () && sp)
179 /* THIS, SP is the original spanner. We use a special function
180 because some Spanners have enormously long lists in their
181 properties, and a special function fixes FOO */
183 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
184 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
186 System *system = get_system ();
190 && common_refpoint (system, X_AXIS)
191 && common_refpoint (system, Y_AXIS))
192 substitute_object_links (system->self_scm (), object_alist_);
193 else if (dynamic_cast<System *> (this))
194 substitute_object_links (SCM_UNDEFINED, object_alist_);
196 /* THIS element is `invalid'; it has been removed from all
197 dependencies, so let's junk the element itself.
199 Do not do this for System, since that would remove references
200 to the originals of score-grobs, which get then GC'd (a bad
205 /* Note that we still want references to this element to be
206 rearranged, and not silently thrown away, so we keep pointers like
207 {broken_into_{drul, array}, original}
215 for (int a = X_AXIS; a < NO_AXES; a++)
216 dim_cache_[a].clear ();
218 mutable_property_alist_ = SCM_EOL;
219 object_alist_ = SCM_EOL;
220 immutable_property_alist_ = SCM_EOL;
221 interfaces_ = SCM_EOL;
225 Grob::handle_prebroken_dependencies ()
227 /* Don't do this in the derived method, since we want to keep access to
228 object_alist_ centralized. */
231 Item *it = dynamic_cast<Item *> (this);
232 substitute_object_links (scm_from_int (it->break_status_dir ()),
233 original ()->object_alist_);
238 Grob::find_broken_piece (System *) const
243 /****************************************************************
245 ****************************************************************/
248 Grob::translate_axis (Real y, Axis a)
250 if (isinf (y) || isnan (y))
252 programming_error (_ ("Infinity or NaN encountered"));
256 if (!dim_cache_[a].offset_)
257 dim_cache_[a].offset_ = new Real (y);
259 *dim_cache_[a].offset_ += y;
262 /* Find the offset relative to D. If D equals THIS, then it is 0.
263 Otherwise, it recursively defd as
265 OFFSET_ + PARENT_L_->relative_coordinate (D) */
267 Grob::relative_coordinate (Grob const *refp, Axis a) const
269 /* eaa - hmmm, should we do a programming_error() here? */
270 if ((this == NULL) || (refp == this))
273 /* We catch PARENT_L_ == nil case with this, but we crash if we did
274 not ask for the absolute coordinate (ie. REFP == nil.) */
275 Real off = get_offset (a);
276 if (refp == dim_cache_[a].parent_)
279 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
285 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
292 if (dim_cache_[Y_AXIS].offset_)
294 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
295 programming_error ("cyclic chain in pure-Y-offset callbacks");
297 off = *dim_cache_[Y_AXIS].offset_;
301 SCM proc = get_property_data ("Y-offset");
303 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
304 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
305 off = robust_scm2double (call_pure_function (proc,
306 scm_list_1 (self_scm ()),
309 del_property ("pure-Y-offset-in-progress");
310 delete dim_cache_[Y_AXIS].offset_;
311 dim_cache_[Y_AXIS].offset_ = 0;
314 /* we simulate positioning-done if we are the child of a VerticalAlignment,
315 but only if we don't have a cached offset. If we do have a cached offset,
316 it probably means that the Alignment was fixed and it has already been
319 if (Grob *p = get_parent (Y_AXIS))
322 if (Align_interface::has_interface (p) && !dim_cache_[Y_AXIS].offset_)
323 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
325 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
330 /* Invoke callbacks to get offset relative to parent. */
332 Grob::get_offset (Axis a) const
334 if (dim_cache_[a].offset_)
335 return *dim_cache_[a].offset_;
337 Grob *me = (Grob *) this;
339 SCM sym = axis_offset_symbol (a);
340 me->dim_cache_[a].offset_ = new Real (0.0);
343 UGH: can't fold next 2 statements together. Apparently GCC thinks
344 dim_cache_[a].offset_ is unaliased.
346 Real off = robust_scm2double (internal_get_property (sym), 0.0);
347 if (me->dim_cache_[a].offset_)
349 *me->dim_cache_[a].offset_ += off;
350 me->del_property (sym);
351 return *me->dim_cache_[a].offset_;
358 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
360 if (pure && a != Y_AXIS)
361 programming_error ("tried to get pure X-offset");
362 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
363 : relative_coordinate (refp, a);
366 /****************************************************************
368 ****************************************************************/
371 Grob::flush_extent_cache (Axis axis)
373 if (dim_cache_[axis].extent_)
376 Ugh, this is not accurate; will flush property, causing
377 callback to be called if.
379 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
380 delete dim_cache_[axis].extent_;
381 dim_cache_[axis].extent_ = 0;
382 if (get_parent (axis))
383 get_parent (axis)->flush_extent_cache (axis);
389 Grob::extent (Grob *refp, Axis a) const
391 Real offset = relative_coordinate (refp, a);
393 if (dim_cache_[a].extent_)
395 real_ext = *dim_cache_[a].extent_;
400 Order is significant: ?-extent may trigger suicide.
404 ? ly_symbol2scm ("X-extent")
405 : ly_symbol2scm ("Y-extent");
407 SCM ext = internal_get_property (ext_sym);
408 if (is_number_pair (ext))
409 real_ext.unite (ly_scm2interval (ext));
413 ? ly_symbol2scm ("minimum-X-extent")
414 : ly_symbol2scm ("minimum-Y-extent");
415 SCM min_ext = internal_get_property (min_ext_sym);
416 if (is_number_pair (min_ext))
417 real_ext.unite (ly_scm2interval (min_ext));
419 ((Grob*)this)->dim_cache_[a].extent_ = new Interval (real_ext);
422 real_ext.translate (offset);
428 Grob::pure_height (Grob *refp, int start, int end)
430 SCM proc = get_property_data (ly_symbol2scm ("Y-extent"));
431 SCM iv_scm = call_pure_function (proc,
432 scm_list_1 (self_scm ()),
434 Interval iv = robust_scm2interval (iv_scm, Interval (0, 0));
435 Real offset = pure_relative_y_coordinate (refp, start, end);
437 SCM min_ext = get_property ("minimum-Y-extent");
439 /* we don't add minimum-Y-extent if the extent is empty. This solves
440 a problem with Hara-kiri spanners. They would request_suicide and
441 return empty extents, but we would force them here to be large. */
442 if (!iv.is_empty () && is_number_pair (min_ext))
443 iv.unite (ly_scm2interval (min_ext));
446 iv.translate (offset);
451 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
453 if (pure && a != Y_AXIS)
454 programming_error ("tried to get pure width");
455 return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
459 Grob::spanned_rank_interval () const
461 return Interval_t<int> (-1, 0);
464 /****************************************************************
466 ****************************************************************/
468 /* Find the group-element which has both #this# and #s# */
470 Grob::common_refpoint (Grob const *s, Axis a) const
472 /* I don't like the quadratic aspect of this code, but I see no
473 other way. The largest chain of parents might be 10 high or so,
474 so it shouldn't be a real issue. */
475 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
476 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
484 Grob::set_parent (Grob *g, Axis a)
486 dim_cache_[a].parent_ = g;
490 Grob::get_parent (Axis a) const
492 return dim_cache_[a].parent_;
497 Grob::fixup_refpoint ()
499 for (int a = X_AXIS; a < NO_AXES; a++)
502 Grob *parent = get_parent (ax);
507 if (parent->get_system () != get_system () && get_system ())
509 Grob *newparent = parent->find_broken_piece (get_system ());
510 set_parent (newparent, ax);
513 if (Item *i = dynamic_cast<Item *> (this))
515 Item *parenti = dynamic_cast<Item *> (parent);
519 Direction my_dir = i->break_status_dir ();
520 if (my_dir != parenti->break_status_dir ())
522 Item *newparent = parenti->find_prebroken_piece (my_dir);
523 set_parent (newparent, ax);
531 /****************************************************************
533 ****************************************************************/
535 Grob::warning (string s) const
537 if (get_program_option ("warning-as-error"))
540 SCM cause = self_scm ();
541 while (Grob *g = unsmob_grob (cause))
542 cause = g->get_property ("cause");
544 /* ES TODO: cause can't be Music*/
545 if (Music *m = unsmob_music (cause))
546 m->origin ()->warning (s);
547 else if (Stream_event *ev = unsmob_stream_event (cause))
548 ev->origin ()->warning (s);
557 SCM meta = get_property ("meta");
558 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
559 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
560 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
564 Grob::programming_error (string s) const
566 if (get_program_option ("warning-as-error"))
569 SCM cause = self_scm ();
570 while (Grob *g = unsmob_grob (cause))
571 cause = g->get_property ("cause");
573 s = _f ("programming error: %s", s);
575 /* ES TODO: cause can't be Music*/
576 if (Music *m = unsmob_music (cause))
577 m->origin ()->message (s);
578 else if (Stream_event *ev = unsmob_stream_event (cause))
579 ev->origin ()->message (s);
586 "A grob represents a piece of music notation.\n"
588 "All grobs have an X and Y@tie{}position on the page. These"
589 " X and Y@tie{}positions are stored in a relative format, thus"
590 " they can easily be combined by stacking them, hanging one"
591 " grob to the side of another, or coupling them into grouping"
594 "Each grob has a reference point (a.k.a.@: parent): The"
595 " position of a grob is stored relative to that reference"
596 " point. For example, the X@tie{}reference point of a staccato"
597 " dot usually is the note head that it applies to. When the"
598 " note head is moved, the staccato dot moves along"
601 "A grob is often associated with a symbol, but some grobs do"
602 " not print any symbols. They take care of grouping objects."
603 " For example, there is a separate grob that stacks staves"
604 " vertically. The @ref{NoteCollision} object is also an"
605 " abstract grob: It only moves around chords, but doesn't print"
608 "Grobs have properties (Scheme variables) that can be read and"
609 " set. Two types of them exist: immutable and mutable."
610 " Immutable variables define the default style and behavior."
611 " They are shared between many objects. They can be changed"
612 " using @code{\\override} and @code{\\revert}. Mutable"
613 " properties are variables that are specific to one grob."
614 " Typically, lists of other objects, or results from"
615 " computations are stored in mutable properties. In"
616 " particular, every call to @code{ly:grob-set-property!}"
617 " (or its C++ equivalent) sets a mutable property.\n"
619 "The properties @code{after-line-breaking} and"
620 " @code{before-line-breaking} are dummies that are not"
621 " user-serviceable.",
628 "after-line-breaking "
630 "axis-group-parent-X "
631 "axis-group-parent-Y "
632 "before-line-breaking "
644 "outside-staff-horizontal-padding "
645 "outside-staff-padding "
646 "outside-staff-priority "
647 "pure-Y-offset-in-progress "
655 /****************************************************************
657 ****************************************************************/
660 grob_stencil_extent (Grob *me, Axis a)
662 Stencil *m = me->get_stencil ();
666 return ly_interval2scm (e);
670 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
672 Grob::stencil_height (SCM smob)
674 Grob *me = unsmob_grob (smob);
675 return grob_stencil_extent (me, Y_AXIS);
678 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
680 Grob::y_parent_positioning (SCM smob)
682 Grob *me = unsmob_grob (smob);
683 Grob *par = me->get_parent (Y_AXIS);
685 (void) par->get_property ("positioning-done");
687 return scm_from_double (0.0);
691 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
693 Grob::x_parent_positioning (SCM smob)
695 Grob *me = unsmob_grob (smob);
697 Grob *par = me->get_parent (X_AXIS);
699 (void) par->get_property ("positioning-done");
701 return scm_from_double (0.0);
704 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
706 Grob::stencil_width (SCM smob)
708 Grob *me = unsmob_grob (smob);
709 return grob_stencil_extent (me, X_AXIS);
714 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
716 for (; scm_is_pair (elist); elist = scm_cdr (elist))
717 if (Grob *s = unsmob_grob (scm_car (elist)))
720 common = common->common_refpoint (s, a);
729 common_refpoint_of_array (vector<Grob*> const &arr, Grob *common, Axis a)
731 for (vsize i = 0; i < arr.size (); i++)
733 common = common->common_refpoint (arr[i], a);
741 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
743 Interval ext = me->extent (refpoint, a);
745 ext.add_point (me->relative_coordinate (refpoint, a));