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))
62 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
64 if (get_property_data ("X-extent") == SCM_EOL)
65 set_property ("X-extent", Grob::stencil_width_proc);
66 if (get_property_data ("Y-extent") == SCM_EOL)
67 set_property ("Y-extent", Grob::stencil_height_proc);
70 Grob::Grob (Grob const &s, int copy_index)
71 : dim_cache_ (s.dim_cache_)
73 key_ = (use_object_keys) ? new Copied_key (s.key_, copy_index) : 0;
74 original_ = (Grob *) & s;
77 immutable_property_alist_ = s.immutable_property_alist_;
78 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
79 interfaces_ = s.interfaces_;
80 object_alist_ = SCM_EOL;
86 ((Object_key *)key_)->unprotect ();
92 /****************************************************************
94 ****************************************************************/
97 Grob::get_stencil () const
102 SCM stil = get_property ("stencil");
103 return unsmob_stencil (stil);
107 Grob::get_print_stencil () const
109 SCM stil = get_property ("stencil");
112 if (Stencil *m = unsmob_stencil (stil))
115 if (to_boolean (get_property ("transparent")))
116 retval = Stencil (m->extent_box (), SCM_EOL);
119 SCM expr = m->expr ();
120 expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
123 retval = Stencil (m->extent_box (), expr);
126 SCM rot = get_property ("rotation");
127 if (scm_is_pair (rot))
129 Real angle = scm_to_double (scm_car (rot));
130 Real x = scm_to_double (scm_cadr (rot));
131 Real y = scm_to_double (scm_caddr (rot));
133 retval.rotate (angle, Offset (x, y));
136 /* color support... see interpret_stencil_expression () for more... */
137 SCM color = get_property ("color");
138 if (scm_is_pair (color))
140 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
144 retval = Stencil (retval.extent_box (), expr);
152 /****************************************************************
154 ****************************************************************/
156 Grob::do_break_processing ()
161 Grob::discretionary_processing ()
166 Grob::get_system () const
173 Grob::handle_broken_dependencies ()
175 Spanner *sp = dynamic_cast<Spanner *> (this);
176 if (original () && sp)
180 /* THIS, SP is the original spanner. We use a special function
181 because some Spanners have enormously long lists in their
182 properties, and a special function fixes FOO */
184 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
185 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
187 System *system = get_system ();
191 && common_refpoint (system, X_AXIS)
192 && common_refpoint (system, Y_AXIS))
193 substitute_object_links (system->self_scm (), object_alist_);
194 else if (dynamic_cast<System *> (this))
195 substitute_object_links (SCM_UNDEFINED, object_alist_);
197 /* THIS element is `invalid'; it has been removed from all
198 dependencies, so let's junk the element itself.
200 Do not do this for System, since that would remove references
201 to the originals of score-grobs, which get then GC'd (a bad
206 /* Note that we still want references to this element to be
207 rearranged, and not silently thrown away, so we keep pointers like
208 {broken_into_{drul, array}, original}
216 for (int a = X_AXIS; a < NO_AXES; a++)
217 dim_cache_[a].clear ();
219 mutable_property_alist_ = SCM_EOL;
220 object_alist_ = SCM_EOL;
221 immutable_property_alist_ = SCM_EOL;
222 interfaces_ = SCM_EOL;
226 Grob::handle_prebroken_dependencies ()
228 /* Don't do this in the derived method, since we want to keep access to
229 object_alist_ centralized. */
232 Item *it = dynamic_cast<Item *> (this);
233 substitute_object_links (scm_from_int (it->break_status_dir ()),
234 original ()->object_alist_);
239 Grob::find_broken_piece (System *) const
244 /****************************************************************
246 ****************************************************************/
249 Grob::translate_axis (Real y, Axis a)
251 if (isinf (y) || isnan (y))
253 programming_error (_ ("Infinity or NaN encountered"));
257 if (!dim_cache_[a].offset_)
258 dim_cache_[a].offset_ = new Real (y);
260 *dim_cache_[a].offset_ += y;
263 /* Find the offset relative to D. If D equals THIS, then it is 0.
264 Otherwise, it recursively defd as
266 OFFSET_ + PARENT_L_->relative_coordinate (D) */
268 Grob::relative_coordinate (Grob const *refp, Axis a) const
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_)
293 off = *dim_cache_[Y_AXIS].offset_;
296 SCM proc = get_property_data ("Y-offset");
298 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
299 off = robust_scm2double (call_pure_function (proc,
300 scm_list_1 (self_scm ()),
303 delete dim_cache_[Y_AXIS].offset_;
304 dim_cache_[Y_AXIS].offset_ = 0;
307 /* we simulate positioning-done if we are the child of a VerticalAlignment,
308 but only if we don't have a cached offset. If we do have a cached offset,
309 it probably means that the Alignment was fixed and it has already been
312 if (Grob *p = get_parent (Y_AXIS))
315 if (Align_interface::has_interface (p) && !dim_cache_[Y_AXIS].offset_)
316 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
318 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
323 /* Invoke callbacks to get offset relative to parent. */
325 Grob::get_offset (Axis a) const
327 if (dim_cache_[a].offset_)
328 return *dim_cache_[a].offset_;
330 Grob *me = (Grob *) this;
332 SCM sym = axis_offset_symbol (a);
333 me->dim_cache_[a].offset_ = new Real (0.0);
336 UGH: can't fold next 2 statements together. Apparently GCC thinks
337 dim_cache_[a].offset_ is unaliased.
339 Real off = robust_scm2double (internal_get_property (sym), 0.0);
340 if (me->dim_cache_[a].offset_)
342 *me->dim_cache_[a].offset_ += off;
343 me->del_property (sym);
344 return *me->dim_cache_[a].offset_;
351 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
353 if (pure && a != Y_AXIS)
354 programming_error ("tried to get pure X-offset");
355 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
356 : relative_coordinate (refp, a);
359 /****************************************************************
361 ****************************************************************/
364 Grob::flush_extent_cache (Axis axis)
366 if (dim_cache_[axis].extent_)
369 Ugh, this is not accurate; will flush property, causing
370 callback to be called if.
372 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
373 delete dim_cache_[axis].extent_;
374 dim_cache_[axis].extent_ = 0;
375 if (get_parent (axis))
376 get_parent (axis)->flush_extent_cache (axis);
382 Grob::extent (Grob *refp, Axis a) const
384 Real offset = relative_coordinate (refp, a);
386 if (dim_cache_[a].extent_)
388 real_ext = *dim_cache_[a].extent_;
393 Order is significant: ?-extent may trigger suicide.
397 ? ly_symbol2scm ("X-extent")
398 : ly_symbol2scm ("Y-extent");
400 SCM ext = internal_get_property (ext_sym);
401 if (is_number_pair (ext))
402 real_ext.unite (ly_scm2interval (ext));
406 ? ly_symbol2scm ("minimum-X-extent")
407 : ly_symbol2scm ("minimum-Y-extent");
408 SCM min_ext = internal_get_property (min_ext_sym);
409 if (is_number_pair (min_ext))
410 real_ext.unite (ly_scm2interval (min_ext));
412 ((Grob*)this)->dim_cache_[a].extent_ = new Interval (real_ext);
415 real_ext.translate (offset);
421 Grob::pure_height (Grob *refp, int start, int end)
423 SCM proc = get_property_data (ly_symbol2scm ("Y-extent"));
424 SCM pure_proc = get_property_data (ly_symbol2scm ("pure-Y-extent"));
427 if (ly_is_procedure (pure_proc))
428 iv_scm = scm_apply_3 (pure_proc,
430 scm_from_int (start),
431 scm_from_int (end), SCM_EOL);
433 iv_scm = call_pure_function (proc,
434 scm_list_1 (self_scm ()),
437 Interval iv = robust_scm2interval (iv_scm, Interval (0, 0));
438 Real offset = pure_relative_y_coordinate (refp, start, end);
440 SCM min_ext = get_property ("minimum-Y-extent");
442 /* we don't add minimum-Y-extent if the extent is empty. This solves
443 a problem with Hara-kiri spanners. They would request_suicide and
444 return empty extents, but we would force them here to be large. */
445 if (!iv.is_empty () && is_number_pair (min_ext))
446 iv.unite (ly_scm2interval (min_ext));
448 iv.translate (offset);
453 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
455 if (pure && a != Y_AXIS)
456 programming_error ("tried to get pure width");
457 return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
461 Grob::spanned_rank_iv ()
463 return Interval_t<int> (-1, 0);
466 /****************************************************************
468 ****************************************************************/
470 /* Find the group-element which has both #this# and #s# */
472 Grob::common_refpoint (Grob const *s, Axis a) const
474 /* I don't like the quadratic aspect of this code, but I see no
475 other way. The largest chain of parents might be 10 high or so,
476 so it shouldn't be a real issue. */
477 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
478 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
486 Grob::set_parent (Grob *g, Axis a)
488 dim_cache_[a].parent_ = g;
492 Grob::get_parent (Axis a) const
494 return dim_cache_[a].parent_;
499 Grob::fixup_refpoint ()
501 for (int a = X_AXIS; a < NO_AXES; a++)
504 Grob *parent = get_parent (ax);
509 if (parent->get_system () != get_system () && get_system ())
511 Grob *newparent = parent->find_broken_piece (get_system ());
512 set_parent (newparent, ax);
515 if (Item *i = dynamic_cast<Item *> (this))
517 Item *parenti = dynamic_cast<Item *> (parent);
521 Direction my_dir = i->break_status_dir ();
522 if (my_dir != parenti->break_status_dir ())
524 Item *newparent = parenti->find_prebroken_piece (my_dir);
525 set_parent (newparent, ax);
533 /****************************************************************
535 ****************************************************************/
537 Grob::warning (string s) const
539 SCM cause = self_scm ();
540 while (Grob *g = unsmob_grob (cause))
541 cause = g->get_property ("cause");
543 /* ES TODO: cause can't be Music*/
544 if (Music *m = unsmob_music (cause))
545 m->origin ()->warning (s);
546 else if (Stream_event *ev = unsmob_stream_event (cause))
547 ev->origin ()->warning (s);
556 SCM meta = get_property ("meta");
557 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
558 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
559 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
563 Grob::programming_error (string s) const
565 SCM cause = self_scm ();
566 while (Grob *g = unsmob_grob (cause))
567 cause = g->get_property ("cause");
569 s = _f ("programming error: %s", s);
571 /* ES TODO: cause can't be Music*/
572 if (Music *m = unsmob_music (cause))
573 m->origin ()->message (s);
574 else if (Stream_event *ev = unsmob_stream_event (cause))
575 ev->origin ()->warning (s);
582 "A grob represents a piece of music notation\n"
584 "All grobs have an X and Y-position on the page. These X and Y positions\n"
585 "are stored in a relative format, so they can easily be combined by\n"
586 "stacking them, hanging one grob to the side of another, and coupling\n"
587 "them into a grouping objects.\n"
589 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
590 "is stored relative to that reference point. For example the X-reference\n"
591 "point of a staccato dot usually is the note head that it applies\n"
592 "to. When the note head is moved, the staccato dot moves along\n"
595 "A grob is often associated with a symbol, but some grobs do not print\n"
596 "any symbols. They take care of grouping objects. For example, there is a\n"
597 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
598 "is also an abstract grob: it only moves around chords, but doesn't print\n"
601 "Grobs have a properties: Scheme variables, that can be read and set. "
602 "They have two types. Immutable variables "
603 "define the default style and behavior. They are shared between many objects. "
604 "They can be changed using @code{\\override} and @code{\\revert}. "
606 "Mutable properties are variables that are specific to one grob. Typically, "
607 "lists of other objects, or results from computations are stored in"
608 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
609 "sets a mutable property. "
611 "The properties @code{after-line-breaking} and @code{before-line-breaking} "
612 "are dummies that are not user-serviceable. "
621 "after-line-breaking "
623 "axis-group-parent-X "
624 "axis-group-parent-Y "
625 "before-line-breaking "
636 "outside-staff-horizontal-padding "
637 "outside-staff-padding "
638 "outside-staff-priority "
651 /****************************************************************
653 ****************************************************************/
657 grob_stencil_extent (Grob *me, Axis a)
659 Stencil *m = me->get_stencil ();
663 return ly_interval2scm (e);
667 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
669 Grob::stencil_height (SCM smob)
671 Grob *me = unsmob_grob (smob);
672 return grob_stencil_extent (me, Y_AXIS);
675 MAKE_SCHEME_CALLBACK(Grob, y_parent_positioning, 1);
677 Grob::y_parent_positioning (SCM smob)
679 Grob *me = unsmob_grob (smob);
680 Grob *par = me->get_parent (Y_AXIS);
682 (void) par->get_property ("positioning-done");
684 return scm_from_double (0.0);
688 MAKE_SCHEME_CALLBACK(Grob, x_parent_positioning, 1);
690 Grob::x_parent_positioning (SCM smob)
692 Grob *me = unsmob_grob (smob);
694 Grob *par = me->get_parent (X_AXIS);
696 (void) par->get_property ("positioning-done");
698 return scm_from_double (0.0);
701 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
703 Grob::stencil_width (SCM smob)
705 Grob *me = unsmob_grob (smob);
706 return grob_stencil_extent (me, X_AXIS);
712 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
714 for (; scm_is_pair (elist); elist = scm_cdr (elist))
715 if (Grob *s = unsmob_grob (scm_car (elist)))
718 common = common->common_refpoint (s, a);
727 common_refpoint_of_array (vector<Grob*> const &arr, Grob *common, Axis a)
729 for (vsize i = arr.size (); i--;)
730 if (Grob *s = arr[i])
733 common = common->common_refpoint (s, a);
742 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
744 Interval ext = me->extent (refpoint, a);
746 ext.add_point (me->relative_coordinate (refpoint, a));