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 (ly_symbol2scm ("X-extent")) == SCM_EOL)
65 set_property ("X-extent", Grob::stencil_width_proc);
66 if (get_property_data (ly_symbol2scm ("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)
290 SCM pure_off = ly_lily_module_constant ("pure-Y-offset");
293 if (dim_cache_[Y_AXIS].offset_)
294 off = *dim_cache_[Y_AXIS].offset_;
295 else if (ly_is_procedure (pure_off))
297 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
298 off = scm_to_double (scm_apply_3 (pure_off, self_scm (),
299 scm_from_int (start), scm_from_int (end),
301 delete dim_cache_[Y_AXIS].offset_;
302 dim_cache_[Y_AXIS].offset_ = 0;
305 /* we simulate positioning-done if we are the child of a VerticalAlignment,
306 but only if we don't have a cached offset. If we do have a cached offset,
307 it probably means that the Alignment was fixed and it has already been
310 Grob *p = get_parent (Y_AXIS);
312 if (Align_interface::has_interface (p) && !dim_cache_[Y_AXIS].offset_)
313 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
316 + dim_cache_[Y_AXIS].parent_->pure_relative_y_coordinate (refp, start, end);
319 /* Invoke callbacks to get offset relative to parent. */
321 Grob::get_offset (Axis a) const
323 if (dim_cache_[a].offset_)
324 return *dim_cache_[a].offset_;
326 Grob *me = (Grob *) this;
328 SCM sym = axis_offset_symbol (a);
329 me->dim_cache_[a].offset_ = new Real (0.0);
332 UGH: can't fold next 2 statements together. Apparently GCC thinks
333 dim_cache_[a].offset_ is unaliased.
335 Real off = robust_scm2double (internal_get_property (sym), 0.0);
336 if (me->dim_cache_[a].offset_)
338 *me->dim_cache_[a].offset_ += off;
339 me->del_property (sym);
340 return *me->dim_cache_[a].offset_;
347 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
349 if (pure && a != Y_AXIS)
350 programming_error ("tried to get pure X-offset");
351 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
352 : relative_coordinate (refp, a);
355 /****************************************************************
357 ****************************************************************/
360 Grob::flush_extent_cache (Axis axis)
362 if (dim_cache_[axis].extent_)
365 Ugh, this is not accurate; will flush property, causing
366 callback to be called if.
368 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
369 delete dim_cache_[axis].extent_;
370 dim_cache_[axis].extent_ = 0;
371 if (get_parent (axis))
372 get_parent (axis)->flush_extent_cache (axis);
378 Grob::extent (Grob *refp, Axis a) const
380 Real offset = relative_coordinate (refp, a);
382 if (dim_cache_[a].extent_)
384 real_ext = *dim_cache_[a].extent_;
389 Order is significant: ?-extent may trigger suicide.
393 ? ly_symbol2scm ("X-extent")
394 : ly_symbol2scm ("Y-extent");
396 SCM ext = internal_get_property (ext_sym);
397 if (is_number_pair (ext))
398 real_ext.unite (ly_scm2interval (ext));
402 ? ly_symbol2scm ("minimum-X-extent")
403 : ly_symbol2scm ("minimum-Y-extent");
404 SCM min_ext = internal_get_property (min_ext_sym);
405 if (is_number_pair (min_ext))
406 real_ext.unite (ly_scm2interval (min_ext));
407 ((Grob*)this)->dim_cache_[a].extent_ = new Interval (real_ext);
410 real_ext.translate (offset);
416 Grob::pure_height (Grob *refp, int start, int end)
418 SCM pure_height = ly_lily_module_constant ("pure-Y-extent");
421 if (ly_is_procedure (pure_height))
422 iv = ly_scm2interval (scm_apply_3 (pure_height, self_scm (),
423 scm_from_int (start), scm_from_int (end),
425 Real offset = pure_relative_y_coordinate (refp, start, end);
427 SCM min_ext = get_property ("minimum-Y-extent");
428 if (is_number_pair (min_ext))
429 iv.unite (ly_scm2interval (min_ext));
431 iv.translate (offset);
436 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
438 if (pure && a != Y_AXIS)
439 programming_error ("tried to get pure width");
440 return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
444 Grob::spanned_rank_iv ()
446 return Interval_t<int> (-1, 0);
449 /****************************************************************
451 ****************************************************************/
453 /* Find the group-element which has both #this# and #s# */
455 Grob::common_refpoint (Grob const *s, Axis a) const
457 /* I don't like the quadratic aspect of this code, but I see no
458 other way. The largest chain of parents might be 10 high or so,
459 so it shouldn't be a real issue. */
460 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
461 for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
469 Grob::set_parent (Grob *g, Axis a)
471 dim_cache_[a].parent_ = g;
475 Grob::get_parent (Axis a) const
477 return dim_cache_[a].parent_;
482 Grob::fixup_refpoint ()
484 for (int a = X_AXIS; a < NO_AXES; a++)
487 Grob *parent = get_parent (ax);
492 if (parent->get_system () != get_system () && get_system ())
494 Grob *newparent = parent->find_broken_piece (get_system ());
495 set_parent (newparent, ax);
498 if (Item *i = dynamic_cast<Item *> (this))
500 Item *parenti = dynamic_cast<Item *> (parent);
504 Direction my_dir = i->break_status_dir ();
505 if (my_dir != parenti->break_status_dir ())
507 Item *newparent = parenti->find_prebroken_piece (my_dir);
508 set_parent (newparent, ax);
516 /****************************************************************
518 ****************************************************************/
520 Grob::warning (string s) const
522 SCM cause = self_scm ();
523 while (Grob *g = unsmob_grob (cause))
524 cause = g->get_property ("cause");
526 /* ES TODO: cause can't be Music*/
527 if (Music *m = unsmob_music (cause))
528 m->origin ()->warning (s);
529 else if (Stream_event *ev = unsmob_stream_event (cause))
530 ev->origin ()->warning (s);
539 SCM meta = get_property ("meta");
540 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
541 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
542 return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
546 Grob::programming_error (string s) const
548 SCM cause = self_scm ();
549 while (Grob *g = unsmob_grob (cause))
550 cause = g->get_property ("cause");
552 s = _f ("programming error: %s", s);
554 /* ES TODO: cause can't be Music*/
555 if (Music *m = unsmob_music (cause))
556 m->origin ()->message (s);
557 else if (Stream_event *ev = unsmob_stream_event (cause))
558 ev->origin ()->warning (s);
564 ADD_INTERFACE (Grob, "grob-interface",
565 "A grob represents a piece of music notation\n"
567 "All grobs have an X and Y-position on the page. These X and Y positions\n"
568 "are stored in a relative format, so they can easily be combined by\n"
569 "stacking them, hanging one grob to the side of another, and coupling\n"
570 "them into a grouping objects.\n"
572 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
573 "is stored relative to that reference point. For example the X-reference\n"
574 "point of a staccato dot usually is the note head that it applies\n"
575 "to. When the note head is moved, the staccato dot moves along\n"
578 "A grob is often associated with a symbol, but some grobs do not print\n"
579 "any symbols. They take care of grouping objects. For example, there is a\n"
580 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
581 "is also an abstract grob: it only moves around chords, but doesn't print\n"
584 "Grobs have a properties: Scheme variables, that can be read and set. "
585 "They have two types. Immutable variables "
586 "define the default style and behavior. They are shared between many objects. "
587 "They can be changed using @code{\\override} and @code{\\revert}. "
589 "Mutable properties are variables that are specific to one grob. Typically, "
590 "lists of other objects, or results from computations are stored in"
591 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
592 "sets a mutable property. "
594 "The properties @code{after-line-breaking} and @code{before-line-breaking} "
595 "are dummies that are not user-serviceable. "
604 "after-line-breaking "
605 "axis-group-parent-X "
606 "axis-group-parent-Y "
607 "before-line-breaking "
629 /****************************************************************
631 ****************************************************************/
635 grob_stencil_extent (Grob *me, Axis a)
637 Stencil *m = me->get_stencil ();
641 return ly_interval2scm (e);
645 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
647 Grob::stencil_height (SCM smob)
649 Grob *me = unsmob_grob (smob);
650 return grob_stencil_extent (me, Y_AXIS);
653 MAKE_SCHEME_CALLBACK(Grob, y_parent_positioning, 1);
655 Grob::y_parent_positioning (SCM smob)
657 Grob *me = unsmob_grob (smob);
658 Grob *par = me->get_parent (Y_AXIS);
660 (void) par->get_property ("positioning-done");
662 return scm_from_double (0.0);
666 MAKE_SCHEME_CALLBACK(Grob, x_parent_positioning, 1);
668 Grob::x_parent_positioning (SCM smob)
670 Grob *me = unsmob_grob (smob);
672 Grob *par = me->get_parent (X_AXIS);
674 (void) par->get_property ("positioning-done");
676 return scm_from_double (0.0);
679 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
681 Grob::stencil_width (SCM smob)
683 Grob *me = unsmob_grob (smob);
684 return grob_stencil_extent (me, X_AXIS);
690 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
692 for (; scm_is_pair (elist); elist = scm_cdr (elist))
693 if (Grob *s = unsmob_grob (scm_car (elist)))
696 common = common->common_refpoint (s, a);
705 common_refpoint_of_array (vector<Grob*> const &arr, Grob *common, Axis a)
707 for (vsize i = arr.size (); i--;)
708 if (Grob *s = arr[i])
711 common = common->common_refpoint (s, a);
720 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
722 Interval ext = me->extent (refpoint, a);
724 ext.add_point (me->relative_coordinate (refpoint, a));