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.
142 do nothing scm-ish and no unprotecting here.
147 MAKE_SCHEME_CALLBACK (Grob,stencil_extent,2);
149 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
151 Grob *s = unsmob_grob (element_smob);
152 Axis a = (Axis) ly_scm2int (scm_axis);
154 Stencil *m = s->get_stencil ();
158 return ly_interval2scm (e);
162 Grob::get_paper () const
164 return pscore_ ? pscore_->paper_ : 0;
168 Grob::calculate_dependencies (int final, int busy, SCM funcname)
170 if (status_ >= final)
175 programming_error ("Element is busy, come back later");
181 for (SCM d = get_property ("dependencies"); ly_c_pair_p (d);
184 unsmob_grob (ly_car (d))
185 ->calculate_dependencies (final, busy, funcname);
189 SCM proc = internal_get_property (funcname);
190 if (ly_c_procedure_p (proc))
191 scm_call_1 (proc, this->self_scm ());
197 Grob::get_stencil () const
204 SCM mol = get_property ("stencil");
205 if (unsmob_stencil (mol))
206 return unsmob_stencil (mol);
208 mol = get_uncached_stencil ();
212 Grob *me = (Grob*)this;
213 me->set_property ("stencil", mol);
216 return unsmob_stencil (mol);
220 Grob::get_uncached_stencil ()const
222 SCM proc = get_property ("print-function");
225 if (ly_c_procedure_p (proc))
226 mol = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
228 if (Stencil *m = unsmob_stencil (mol))
230 if (to_boolean (get_property ("transparent")))
231 mol = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
234 SCM origin = ly_symbol2scm ("no-origin");
236 if (store_locations_global_b)
238 SCM cause = get_property ("cause");
239 if (Music*m = unsmob_music (cause))
241 SCM music_origin = m->get_property ("origin");
242 if (unsmob_input (music_origin))
243 origin = music_origin;
248 mol = Stencil (m->extent_box (),
249 scm_list_n (origin, m->expr (), SCM_UNDEFINED)
263 Grob::do_break_processing ()
268 Grob::get_system () const
274 Grob::add_dependency (Grob*e)
278 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
281 programming_error ("Null dependency added");
286 Grob::handle_broken_dependencies ()
288 Spanner * sp = dynamic_cast<Spanner*> (this);
295 This is the original spanner. We use a special function
296 because some Spanners have enormously long lists in their
299 for (SCM s = mutable_property_alist_; ly_c_pair_p (s);
302 sp->substitute_one_mutable_property (ly_caar (s),
307 System *system = get_system ();
310 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
312 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
313 mutable_property_alist_);
315 else if (dynamic_cast <System*> (this))
317 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
322 This element is `invalid'; it has been removed from all
323 dependencies, so let's junk the element itself.
325 do not do this for System, since that would remove references
326 to the originals of score-grobs, which get then GC'd (a bad
335 Note that we still want references to this element to be
336 rearranged, and not silently thrown away, so we keep pointers
337 like {broken_into_{drul,array}, original}
346 mutable_property_alist_ = SCM_EOL;
347 immutable_property_alist_ = SCM_EOL;
349 set_extent (SCM_EOL, Y_AXIS);
350 set_extent (SCM_EOL, X_AXIS);
352 for (int a= X_AXIS; a <= Y_AXIS; a++)
354 dim_cache_[a].offset_callbacks_ = SCM_EOL;
355 dim_cache_[a].offsets_left_ = 0;
360 Grob::handle_prebroken_dependencies ()
363 Don't do this in the derived method, since we want to keep access to
364 mutable_property_alist_ centralized.
368 Item * it = dynamic_cast<Item*> (this);
369 substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
370 original_->mutable_property_alist_);
375 Grob::find_broken_piece (System*) const
381 translate in one direction
384 Grob::translate_axis (Real y, Axis a)
386 if (isinf (y) || isnan (y))
387 programming_error (_ (INFINITY_MSG));
390 dim_cache_[a].offset_ += y;
396 Find the offset relative to D. If D equals THIS, then it is 0.
397 Otherwise, it recursively defd as
399 OFFSET_ + PARENT_L_->relative_coordinate (D)
402 Grob::relative_coordinate (Grob const*refp, Axis a) const
408 We catch PARENT_L_ == nil case with this, but we crash if we did
409 not ask for the absolute coordinate (ie. REFP == nil.)
412 if (refp == dim_cache_[a].parent_)
413 return get_offset (a);
415 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
421 Invoke callbacks to get offset relative to parent.
424 Grob::get_offset (Axis a) const
426 Grob *me = (Grob*) this;
427 while (dim_cache_[a].offsets_left_)
429 int l = --me->dim_cache_[a].offsets_left_;
430 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
431 SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
433 Real r = ly_scm2double (retval);
434 if (isinf (r) || isnan (r))
436 programming_error (INFINITY_MSG);
439 me->dim_cache_[a].offset_ +=r;
441 return dim_cache_[a].offset_;
446 Grob::is_empty (Axis a)const
448 return ! (ly_c_pair_p (dim_cache_[a].dimension_) ||
449 ly_c_procedure_p (dim_cache_[a].dimension_));
453 Grob::extent (Grob * refp, Axis a) const
455 Real x = relative_coordinate (refp, a);
458 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
460 if (ly_c_pair_p (d->dimension_))
462 else if (ly_c_procedure_p (d->dimension_))
465 FIXME: add doco on types, and should typecheck maybe?
467 d->dimension_= scm_call_2 (d->dimension_, self_scm (), scm_int2num (a));
472 if (!ly_c_pair_p (d->dimension_))
475 ext = ly_scm2interval (d->dimension_);
477 SCM extra = get_property (a == X_AXIS
484 if (ly_c_pair_p (extra))
486 ext[BIGGER] += ly_scm2double (ly_cdr (extra));
487 ext[SMALLER] += ly_scm2double (ly_car (extra));
490 extra = get_property (a == X_AXIS
492 : "minimum-Y-extent");
493 if (ly_c_pair_p (extra))
495 ext.unite (Interval (ly_scm2double (ly_car (extra)),
496 ly_scm2double (ly_cdr (extra))));
505 Find the group-element which has both #this# and #s#
508 Grob::common_refpoint (Grob const* s, Axis a) const
511 I don't like the quadratic aspect of this code, but I see no other
512 way. The largest chain of parents might be 10 high or so, so
513 it shouldn't be a real issue. */
514 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
515 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
524 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
526 for (; ly_c_pair_p (elist); elist = ly_cdr (elist))
528 Grob * s = unsmob_grob (ly_car (elist));
532 common = common->common_refpoint (s, a);
543 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
545 for (int i = arr.size () ; i--; )
552 common = common->common_refpoint (s, a);
563 SCM meta = get_property ("meta");
564 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
565 nm = (ly_c_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
566 return ly_c_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
570 Grob::add_offset_callback (SCM cb, Axis a)
572 if (!has_offset_callback (cb, a))
574 dim_cache_[a].offset_callbacks_ = scm_cons (cb, dim_cache_[a].offset_callbacks_);
575 dim_cache_[a].offsets_left_ ++;
580 Grob::has_extent_callback (SCM cb, Axis a)const
582 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
587 Grob::has_offset_callback (SCM cb, Axis a)const
589 return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
593 Grob::set_extent (SCM dc, Axis a)
595 dim_cache_[a].dimension_ =dc;
599 Grob::set_parent (Grob *g, Axis a)
601 dim_cache_[a].parent_ = g;
604 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
606 Grob::fixup_refpoint (SCM smob)
608 Grob *me = unsmob_grob (smob);
609 for (int a = X_AXIS; a < NO_AXES; a ++)
612 Grob * parent = me->get_parent (ax);
617 if (parent->get_system () != me->get_system () && me->get_system ())
619 Grob * newparent = parent->find_broken_piece (me->get_system ());
620 me->set_parent (newparent, ax);
623 if (Item * i = dynamic_cast<Item*> (me))
625 Item *parenti = dynamic_cast<Item*> (parent);
629 Direction my_dir = i->break_status_dir () ;
630 if (my_dir!= parenti->break_status_dir ())
632 Item *newparent = parenti->find_prebroken_piece (my_dir);
633 me->set_parent (newparent, ax);
642 Grob::warning (String s)const
644 SCM cause = self_scm ();
645 while (Grob * g = unsmob_grob (cause))
647 cause = g->get_property ("cause");
650 if (Music *m = unsmob_music (cause))
652 m->origin ()->warning (s);
659 Grob::programming_error (String s)const
661 s = "Programming error: " + s;
666 /****************************************************
668 ****************************************************/
672 IMPLEMENT_SMOBS (Grob);
673 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
676 Grob::mark_smob (SCM ses)
678 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
679 scm_gc_mark (s->immutable_property_alist_);
681 for (int a =0 ; a < 2; a++)
683 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
684 scm_gc_mark (s->dim_cache_[a].dimension_);
687 don't mark the parents. The pointers in the mutable property
688 list form two tree like structures (one for X relations, one
689 for Y relations). Marking these can be done in limited stack
690 space. If we add the parents, we will jump between X and Y in
691 an erratic manner, leading to much more recursion depth (and
692 core dumps if we link to pthreads.)
697 scm_gc_mark (s->original_->self_scm ());
699 s->do_derived_mark ();
700 return s->mutable_property_alist_;
704 Grob::print_smob (SCM s, SCM port, scm_print_state *)
706 Grob *sc = (Grob *) ly_cdr (s);
708 scm_puts ("#<Grob ", port);
709 scm_puts ((char *)sc->name ().to_str0 (), port);
712 don't try to print properties, that is too much hassle.
714 scm_puts (" >", port);
719 Grob::do_derived_mark () const
727 Grob::discretionary_processing ()
732 Grob::internal_has_interface (SCM k)
734 SCM ifs = get_property ("interfaces");
736 return scm_c_memq (k, ifs) != SCM_BOOL_F;
740 /** Return Array of Grobs in SCM list L */
744 Link_array<Grob> arr;
746 for (SCM s = l; ly_c_pair_p (s); s = ly_cdr (s))
749 arr.push (unsmob_grob (e));
756 /** Return SCM list of Grob array A */
758 ly_grobs2scm (Link_array<Grob> a)
761 for (int i = a.size (); i; i--)
762 s = scm_cons (a[i-1]->self_scm (), s);
768 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
770 ADD_INTERFACE (Grob, "grob-interface",
771 "A grob represents a piece of music notation\n"
773 "All grobs have an X and Y-position on the page. These X and Y positions\n"
774 "are stored in a relative format, so they can easily be combined by\n"
775 "stacking them, hanging one grob to the side of another, and coupling\n"
776 "them into a grouping objects.\n"
778 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
779 "is stored relative to that reference point. For example the X-reference\n"
780 "point of a staccato dot usually is the note head that it applies\n"
781 "to. When the note head is moved, the staccato dot moves along\n"
784 "A grob is often associated with a symbol, but some grobs do not print\n"
785 "any symbols. They take care of grouping objects. For example, there is a\n"
786 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
787 "is also an abstract grob: it only moves around chords, but doesn't print\n"
790 "Grobs have a properties: Scheme variables, that can be read and set. "
791 "They have two types. Immutable variables "
792 "define the default style and behavior. They are shared between many objects. "
793 "They can be changed using @code{\\override} and @code{\\revert}. "
795 "Mutable properties are variables that are specific to one grob. Typically, "
796 "lists of other objects, or results from computations are stored in"
797 "mutable properties: every call to set-grob-property (or its C++ equivalent) "
798 "sets a mutable property. "
801 "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
802 "Y-extent-callback print-function extra-offset spacing-procedure "
803 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
804 "meta layer before-line-breaking-callback "
805 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
806 // FIXME: page-penalty?
807 "minimum-Y-extent page-penalty transparent "