2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
14 #include "input-smob.hh"
16 #include "group-interface.hh"
18 #include "paper-score.hh"
30 #include "ly-smobs.icc"
35 remove dynamic_cast<Spanner,Item> and put this code into respective
40 #define INFINITY_MSG "Infinity or NaN encountered"
42 Grob::Grob (SCM basicprops)
45 fixme: default should be no callback.
51 immutable_property_alist_ = basicprops;
52 mutable_property_alist_ = SCM_EOL;
55 We do smobify_self () as the first step. Since the object lives on
56 the heap, none of its SCM variables are protected from GC. After
57 smobify_self (), they are.
63 SCM meta = get_property ("meta");
64 if (ly_c_pair_p (meta))
66 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
69 Switch off interface checks for the moment.
71 bool itc = internal_type_checking_global_b;
72 internal_type_checking_global_b = false;
73 internal_set_property (ly_symbol2scm ("interfaces"), ly_cdr (ifs));
74 internal_type_checking_global_b = itc;
80 destill this into a function, so we can re-init the immutable
81 properties with a new BASICPROPS value after creation. Convenient
82 eg. when using \override with StaffSymbol. */
84 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
85 char const*xnames[] = {"X-extent", "Y-extent"};
86 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
88 for (int a = X_AXIS; a <= Y_AXIS; a++)
90 SCM l = get_property (onames[a]);
92 if (scm_ilength (l) >=0)
94 dim_cache_[a].offset_callbacks_ = l;
95 dim_cache_[a].offsets_left_ = scm_ilength (l);
99 programming_error ("[XY]-offset-callbacks must be a list");
102 SCM cb = get_property (enames[a]);
103 SCM xt = get_property (xnames[a]);
106 Should change default to empty?
108 if (is_number_pair (xt))
110 else if (cb != SCM_BOOL_F
111 && !ly_c_procedure_p (cb) && !ly_c_pair_p (cb)
112 && ly_c_procedure_p (get_property ("print-function")))
113 cb = stencil_extent_proc;
115 dim_cache_[a].dimension_ = cb;
120 Grob::Grob (Grob const&s)
121 : dim_cache_ (s.dim_cache_)
123 original_ = (Grob*) &s;
126 immutable_property_alist_ = s.immutable_property_alist_;
127 mutable_property_alist_ = SCM_EOL;
130 No properties are copied. That is the job of handle_broken_dependencies.
144 MAKE_SCHEME_CALLBACK (Grob,stencil_extent,2);
146 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
148 Grob *s = unsmob_grob (element_smob);
149 Axis a = (Axis) ly_scm2int (scm_axis);
151 Stencil *m = s->get_stencil ();
155 return ly_interval2scm (e);
159 Grob::get_paper () const
161 return pscore_ ? pscore_->paper_ : 0;
166 Recursively track all dependencies of this Grob. The
167 status_ field is used as a mark-field. It is marked with
168 BUSY during execution of this function, and marked with FINAL
171 FUNCPTR is the function to call to update this element.
174 Grob::calculate_dependencies (int final, int busy, SCM funcname)
176 if (status_ >= final)
181 programming_error ("Element is busy, come back later");
187 for (SCM d = get_property ("dependencies"); ly_c_pair_p (d);
190 unsmob_grob (ly_car (d))
191 ->calculate_dependencies (final, busy, funcname);
195 SCM proc = internal_get_property (funcname);
196 if (ly_c_procedure_p (proc))
197 scm_call_1 (proc, this->self_scm ());
203 Grob::get_stencil () const
210 SCM mol = get_property ("stencil");
211 if (unsmob_stencil (mol))
212 return unsmob_stencil (mol);
214 mol = get_uncached_stencil ();
218 Grob *me = (Grob*)this;
219 me->set_property ("stencil", mol);
222 return unsmob_stencil (mol);
226 Grob::get_uncached_stencil ()const
228 SCM proc = get_property ("print-function");
231 if (ly_c_procedure_p (proc))
232 mol = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
234 if (Stencil *m = unsmob_stencil (mol))
236 if (to_boolean (get_property ("transparent")))
237 mol = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
240 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm(), m->expr ());
241 mol = Stencil (m->extent_box (),expr). smobbed_copy ();
254 Grob::do_break_processing ()
259 Grob::get_system () const
265 Grob::add_dependency (Grob*e)
269 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
272 programming_error ("Null dependency added");
276 Grob::handle_broken_dependencies ()
278 Spanner * sp = dynamic_cast<Spanner*> (this);
285 This is the original spanner. We use a special function
286 because some Spanners have enormously long lists in their
289 for (SCM s = mutable_property_alist_; ly_c_pair_p (s);
292 sp->substitute_one_mutable_property (ly_caar (s),
297 System *system = get_system ();
300 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
302 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
303 mutable_property_alist_);
305 else if (dynamic_cast <System*> (this))
307 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
312 This element is `invalid'; it has been removed from all
313 dependencies, so let's junk the element itself.
315 do not do this for System, since that would remove references
316 to the originals of score-grobs, which get then GC'd (a bad
325 Note that we still want references to this element to be
326 rearranged, and not silently thrown away, so we keep pointers
327 like {broken_into_{drul,array}, original}
336 mutable_property_alist_ = SCM_EOL;
337 immutable_property_alist_ = SCM_EOL;
339 set_extent (SCM_EOL, Y_AXIS);
340 set_extent (SCM_EOL, X_AXIS);
342 for (int a= X_AXIS; a <= Y_AXIS; a++)
344 dim_cache_[a].offset_callbacks_ = SCM_EOL;
345 dim_cache_[a].offsets_left_ = 0;
350 Grob::handle_prebroken_dependencies ()
353 Don't do this in the derived method, since we want to keep access to
354 mutable_property_alist_ centralized.
358 Item * it = dynamic_cast<Item*> (this);
359 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
360 original_->mutable_property_alist_);
365 Grob::find_broken_piece (System*) const
371 translate in one direction
374 Grob::translate_axis (Real y, Axis a)
376 if (isinf (y) || isnan (y))
377 programming_error (_ (INFINITY_MSG));
380 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)
392 Grob::relative_coordinate (Grob const*refp, Axis a) const
398 We catch PARENT_L_ == nil case with this, but we crash if we did
399 not ask for the absolute coordinate (ie. REFP == nil.)
402 if (refp == dim_cache_[a].parent_)
403 return get_offset (a);
405 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
411 Invoke callbacks to get offset relative to parent.
414 Grob::get_offset (Axis a) const
416 Grob *me = (Grob*) this;
417 while (dim_cache_[a].offsets_left_)
419 int l = --me->dim_cache_[a].offsets_left_;
420 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
421 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
423 Real r = ly_scm2double (retval);
424 if (isinf (r) || isnan (r))
426 programming_error (INFINITY_MSG);
429 me->dim_cache_[a].offset_ +=r;
431 return dim_cache_[a].offset_;
436 Grob::is_empty (Axis a)const
438 return ! (ly_c_pair_p (dim_cache_[a].dimension_) ||
439 ly_c_procedure_p (dim_cache_[a].dimension_));
443 Grob::extent (Grob * refp, Axis a) const
445 Real x = relative_coordinate (refp, a);
448 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
450 if (ly_c_pair_p (d->dimension_))
452 else if (ly_c_procedure_p (d->dimension_))
455 FIXME: add doco on types, and should typecheck maybe?
457 d->dimension_= scm_call_2 (d->dimension_, self_scm (), scm_int2num (a));
462 if (!ly_c_pair_p (d->dimension_))
465 ext = ly_scm2interval (d->dimension_);
467 SCM extra = get_property (a == X_AXIS
474 if (ly_c_pair_p (extra))
476 ext[BIGGER] += ly_scm2double (ly_cdr (extra));
477 ext[SMALLER] += ly_scm2double (ly_car (extra));
480 extra = get_property (a == X_AXIS
482 : "minimum-Y-extent");
483 if (ly_c_pair_p (extra))
485 ext.unite (Interval (ly_scm2double (ly_car (extra)),
486 ly_scm2double (ly_cdr (extra))));
495 Find the group-element which has both #this# and #s#
498 Grob::common_refpoint (Grob const* s, Axis a) const
501 I don't like the quadratic aspect of this code, but I see no other
502 way. The largest chain of parents might be 10 high or so, so
503 it shouldn't be a real issue. */
504 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
505 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
514 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
516 for (; ly_c_pair_p (elist); elist = ly_cdr (elist))
518 Grob * s = unsmob_grob (ly_car (elist));
522 common = common->common_refpoint (s, a);
533 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
535 for (int i = arr.size () ; i--; )
542 common = common->common_refpoint (s, a);
553 SCM meta = get_property ("meta");
554 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
555 nm = (ly_c_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
556 return ly_c_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
560 Grob::add_offset_callback (SCM cb, Axis a)
562 if (!has_offset_callback (cb, a))
564 dim_cache_[a].offset_callbacks_ = scm_cons (cb, dim_cache_[a].offset_callbacks_);
565 dim_cache_[a].offsets_left_ ++;
570 Grob::has_extent_callback (SCM cb, Axis a)const
572 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
577 Grob::has_offset_callback (SCM cb, Axis a)const
579 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
583 Grob::set_extent (SCM dc, Axis a)
585 dim_cache_[a].dimension_ = dc;
589 Grob::set_parent (Grob *g, Axis a)
591 dim_cache_[a].parent_ = g;
594 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
596 Grob::fixup_refpoint (SCM smob)
598 Grob *me = unsmob_grob (smob);
599 for (int a = X_AXIS; a < NO_AXES; a ++)
602 Grob * parent = me->get_parent (ax);
607 if (parent->get_system () != me->get_system () && me->get_system ())
609 Grob * newparent = parent->find_broken_piece (me->get_system ());
610 me->set_parent (newparent, ax);
613 if (Item * i = dynamic_cast<Item*> (me))
615 Item *parenti = dynamic_cast<Item*> (parent);
619 Direction my_dir = i->break_status_dir () ;
620 if (my_dir!= parenti->break_status_dir ())
622 Item *newparent = parenti->find_prebroken_piece (my_dir);
623 me->set_parent (newparent, ax);
632 Grob::warning (String s)const
634 SCM cause = self_scm ();
635 while (Grob * g = unsmob_grob (cause))
637 cause = g->get_property ("cause");
640 if (Music *m = unsmob_music (cause))
642 m->origin ()->warning (s);
649 Grob::programming_error (String s)const
651 s = "Programming error: " + s;
656 /****************************************************
658 ****************************************************/
662 IMPLEMENT_SMOBS (Grob);
663 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
666 Grob::mark_smob (SCM ses)
668 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
669 scm_gc_mark (s->immutable_property_alist_);
671 for (int a =0 ; a < 2; a++)
673 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
674 scm_gc_mark (s->dim_cache_[a].dimension_);
677 don't mark the parents. The pointers in the mutable property
678 list form two tree like structures (one for X relations, one
679 for Y relations). Marking these can be done in limited stack
680 space. If we add the parents, we will jump between X and Y in
681 an erratic manner, leading to much more recursion depth (and
682 core dumps if we link to pthreads.)
687 scm_gc_mark (s->original_->self_scm ());
689 s->do_derived_mark ();
690 return s->mutable_property_alist_;
694 Grob::print_smob (SCM s, SCM port, scm_print_state *)
696 Grob *sc = (Grob *) ly_cdr (s);
698 scm_puts ("#<Grob ", port);
699 scm_puts ((char *)sc->name ().to_str0 (), port);
702 don't try to print properties, that is too much hassle.
704 scm_puts (" >", port);
709 Grob::do_derived_mark () const
717 Grob::discretionary_processing ()
722 Grob::internal_has_interface (SCM k)
724 SCM ifs = get_property ("interfaces");
726 return scm_c_memq (k, ifs) != SCM_BOOL_F;
730 /** Return Array of Grobs in SCM list L */
734 Link_array<Grob> arr;
736 for (SCM s = l; ly_c_pair_p (s); s = ly_cdr (s))
739 arr.push (unsmob_grob (e));
746 /** Return SCM list of Grob array A */
748 ly_grobs2scm (Link_array<Grob> a)
751 for (int i = a.size (); i; i--)
752 s = scm_cons (a[i-1]->self_scm (), s);
758 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
760 ADD_INTERFACE (Grob, "grob-interface",
761 "A grob represents a piece of music notation\n"
763 "All grobs have an X and Y-position on the page. These X and Y positions\n"
764 "are stored in a relative format, so they can easily be combined by\n"
765 "stacking them, hanging one grob to the side of another, and coupling\n"
766 "them into a grouping objects.\n"
768 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
769 "is stored relative to that reference point. For example the X-reference\n"
770 "point of a staccato dot usually is the note head that it applies\n"
771 "to. When the note head is moved, the staccato dot moves along\n"
774 "A grob is often associated with a symbol, but some grobs do not print\n"
775 "any symbols. They take care of grouping objects. For example, there is a\n"
776 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
777 "is also an abstract grob: it only moves around chords, but doesn't print\n"
780 "Grobs have a properties: Scheme variables, that can be read and set. "
781 "They have two types. Immutable variables "
782 "define the default style and behavior. They are shared between many objects. "
783 "They can be changed using @code{\\override} and @code{\\revert}. "
785 "Mutable properties are variables that are specific to one grob. Typically, "
786 "lists of other objects, or results from computations are stored in"
787 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
788 "sets a mutable property. "
791 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
792 "Y-extent-callback print-function extra-offset spacing-procedure "
793 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
794 "meta layer before-line-breaking-callback "
795 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
796 // FIXME: page-penalty?
797 "minimum-Y-extent page-penalty transparent "