2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
14 #include "input-smob.hh"
16 #include "group-interface.hh"
18 #include "paper-score.hh"
19 #include "molecule.hh"
25 #include "molecule.hh"
30 #include "ly-smobs.icc"
35 remove dynamic_cast<Spanner,Item> and put this code into respective
39 //#define HASHING_FOR_MUTABLE_PROPS
41 #define INFINITY_MSG "Infinity or NaN encountered"
43 Grob::Grob (SCM basicprops)
46 fixme: default should be no callback.
52 immutable_property_alist_ = basicprops;
53 mutable_property_alist_ = SCM_EOL;
56 We do smobify_self() as the first step. Since the object lives on
57 the heap, none of its SCM variables are protected from GC. After
58 smobify_self(), they are.
63 #ifdef HASHING_FOR_MUTABLE_PROPS
64 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
67 SCM meta = get_grob_property ("meta");
70 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
73 Switch off interface checks for the moment.
75 bool itc = internal_type_checking_global_b;
76 internal_type_checking_global_b = false;
77 internal_set_grob_property (ly_symbol2scm ("interfaces"), gh_cdr(ifs));
78 internal_type_checking_global_b = itc;
84 destill this into a function, so we can re-init the immutable
85 properties with a new BASICPROPS value after creation. Convenient
86 eg. when using \override with StaffSymbol. */
88 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
89 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
91 for (int a = X_AXIS; a <= Y_AXIS; a++)
93 SCM l = get_grob_property (onames[a]);
95 if (scm_ilength (l) >=0)
97 dim_cache_[a].offset_callbacks_ = l;
98 dim_cache_[a].offsets_left_ = scm_ilength (l);
102 programming_error ("[XY]-offset-callbacks must be a list");
105 SCM cb = get_grob_property (enames[a]);
108 Should change default to be empty?
111 && !gh_procedure_p (cb) && !gh_pair_p (cb)
112 && gh_procedure_p (get_grob_property ("molecule-callback"))
114 cb = molecule_extent_proc;
116 dim_cache_[a].dimension_ = cb;
121 Grob::Grob (Grob const&s)
122 : dim_cache_ (s.dim_cache_)
124 original_l_ = (Grob*) &s;
125 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.
133 status_c_ = s.status_c_;
134 pscore_l_ = s.pscore_l_;
138 #ifdef HASHING_FOR_MUTABLE_PROPS
139 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
146 do nothing scm-ish and no unprotecting here.
154 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
156 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
158 Grob *s = unsmob_grob (element_smob);
159 Axis a = (Axis) gh_scm2int (scm_axis);
161 Molecule *m = s->get_molecule ();
165 return ly_interval2scm (e);
168 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
170 Grob::preset_extent (SCM element_smob, SCM scm_axis)
172 Grob *s = unsmob_grob (element_smob);
173 Axis a = (Axis) gh_scm2int (scm_axis);
175 SCM ext = s->get_grob_property ((a == X_AXIS)
181 Real l = gh_scm2double (ly_car (ext));
182 Real r = gh_scm2double (ly_cdr (ext));
183 return ly_interval2scm (Interval (l, r));
186 return ly_interval2scm (Interval ());
192 Grob::paper_l () const
194 return pscore_l_ ? pscore_l_->paper_l_ : 0;
198 Grob::calculate_dependencies (int final, int busy, SCM funcname)
200 if (status_c_ >= final)
203 if (status_c_== busy)
205 programming_error ("Element is busy, come back later");
211 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
214 unsmob_grob (ly_car (d))
215 ->calculate_dependencies (final, busy, funcname);
219 SCM proc = internal_get_grob_property (funcname);
220 if (gh_procedure_p (proc))
221 gh_call1 (proc, this->self_scm ());
227 Grob::get_molecule () const
235 SCM mol = get_grob_property ("molecule");
236 if (unsmob_molecule (mol))
237 return unsmob_molecule (mol);
239 mol = get_uncached_molecule ();
241 Grob *me = (Grob*)this;
242 me->set_grob_property ("molecule", mol);
244 return unsmob_molecule (mol);
247 Grob::get_uncached_molecule ()const
249 SCM proc = get_grob_property ("molecule-callback");
252 if (gh_procedure_p (proc))
253 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
256 Molecule *m = unsmob_molecule (mol);
258 if (unsmob_molecule (mol))
260 SCM origin = ly_symbol2scm ("no-origin");
262 if (store_locations_global_b){
263 SCM cause = get_grob_property ("cause");
264 if (Music*m = unsmob_music (cause))
266 SCM music_origin = m->get_mus_property ("origin");
267 if (unsmob_input (music_origin))
268 origin = music_origin;
274 mol = Molecule (m->extent_box (),
275 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
278 m = unsmob_molecule (mol);
282 transparent retains dimensions of element.
284 if (m && to_boolean (get_grob_property ("transparent")))
285 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
296 Grob::do_break_processing ()
306 Grob::line_l () const
312 Grob::add_dependency (Grob*e)
316 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
319 programming_error ("Null dependency added");
325 Grob::handle_broken_dependencies ()
327 Spanner * s= dynamic_cast<Spanner*> (this);
328 if (original_l_ && s)
333 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
335 Grob * sc = s->broken_into_l_arr_[i];
336 System * l = sc->line_l ();
338 sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
339 mutable_property_alist_);
344 System *line = line_l ();
347 && line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
349 substitute_mutable_properties (line ? line->self_scm () : SCM_UNDEFINED,
350 mutable_property_alist_);
352 else if (dynamic_cast <System*> (this))
354 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
359 This element is `invalid'; it has been removed from all
360 dependencies, so let's junk the element itself.
362 do not do this for System, since that would remove references
363 to the originals of score-grobs, which get then GC'd (a bad
372 Note that we still want references to this element to be
373 rearranged, and not silently thrown away, so we keep pointers
374 like {broken_into_{drul,array}, original}
379 mutable_property_alist_ = SCM_EOL;
380 immutable_property_alist_ = SCM_EOL;
382 set_extent (SCM_EOL, Y_AXIS);
383 set_extent (SCM_EOL, X_AXIS);
385 for (int a= X_AXIS; a <= Y_AXIS; a++)
387 dim_cache_[a].offset_callbacks_ = SCM_EOL;
388 dim_cache_[a].offsets_left_ = 0;
393 Grob::handle_prebroken_dependencies ()
396 Don't do this in the derived method, since we want to keep access to
397 mutable_property_alist_ centralized.
401 Item * it = dynamic_cast<Item*> (this);
402 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
403 original_l_->mutable_property_alist_);
408 Grob::find_broken_piece (System*) const
414 translate in one direction
417 Grob::translate_axis (Real y, Axis a)
419 if (isinf (y) || isnan (y))
420 programming_error (_ (INFINITY_MSG));
423 dim_cache_[a].offset_ += y;
429 Find the offset relative to D. If D equals THIS, then it is 0.
430 Otherwise, it recursively defd as
432 OFFSET_ + PARENT_L_->relative_coordinate (D)
435 Grob::relative_coordinate (Grob const*refp, Axis a) const
441 We catch PARENT_L_ == nil case with this, but we crash if we did
442 not ask for the absolute coordinate (ie. REFP == nil.)
445 if (refp == dim_cache_[a].parent_l_)
446 return get_offset (a);
448 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
454 Invoke callbacks to get offset relative to parent.
457 Grob::get_offset (Axis a) const
459 Grob *me = (Grob*) this;
460 while (dim_cache_[a].offsets_left_)
462 int l = --me->dim_cache_[a].offsets_left_;
463 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
464 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
466 Real r = gh_scm2double (retval);
467 if (isinf (r) || isnan (r))
469 programming_error (INFINITY_MSG);
472 me->dim_cache_[a].offset_ +=r;
474 return dim_cache_[a].offset_;
478 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
480 Grob::point_dimension_callback (SCM , SCM)
482 return ly_interval2scm (Interval (0,0));
486 Grob::empty_b (Axis a)const
488 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
489 gh_procedure_p (dim_cache_[a].dimension_));
493 Grob::extent (Grob * refp, Axis a) const
495 Real x = relative_coordinate (refp, a);
498 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
500 if (gh_pair_p (d->dimension_))
502 else if (gh_procedure_p (d->dimension_))
505 FIXME: add doco on types, and should typecheck maybe?
507 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
512 if (!gh_pair_p (d->dimension_))
515 ext = ly_scm2interval (d->dimension_);
517 SCM extra = get_grob_property (a == X_AXIS
524 if (gh_pair_p (extra))
526 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
527 ext[SMALLER] += gh_scm2double (ly_car (extra));
530 extra = get_grob_property (a == X_AXIS
532 : "minimum-extent-Y");
533 if (gh_pair_p (extra))
535 ext.unite (Interval (gh_scm2double (ly_car (extra)),
536 gh_scm2double (ly_cdr (extra))));
545 Find the group-element which has both #this# and #s#
548 Grob::common_refpoint (Grob const* s, Axis a) const
551 I don't like the quadratic aspect of this code, but I see no other
552 way. The largest chain of parents might be 10 high or so, so
553 it shouldn't be a real issue. */
554 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
555 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
564 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
566 for (; gh_pair_p (elist); elist = ly_cdr (elist))
568 Grob * s = unsmob_grob (ly_car (elist));
572 common = common->common_refpoint (s, a);
583 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
585 for (int i = arr.size() ; i--; )
592 common = common->common_refpoint (s, a);
603 SCM meta = get_grob_property ("meta");
604 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
605 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
606 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
610 Grob::add_offset_callback (SCM cb, Axis a)
612 if (!has_offset_callback_b (cb, a))
614 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
615 dim_cache_[a].offsets_left_ ++;
620 Grob::has_extent_callback_b (SCM cb, Axis a)const
622 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
627 Grob::has_offset_callback_b (SCM cb, Axis a)const
629 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
633 Grob::set_extent (SCM dc, Axis a)
635 dim_cache_[a].dimension_ =dc;
639 Grob::set_parent (Grob *g, Axis a)
641 dim_cache_[a].parent_l_ = g;
644 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
646 Grob::fixup_refpoint (SCM smob)
648 Grob *me = unsmob_grob (smob);
649 for (int a = X_AXIS; a < NO_AXES; a ++)
652 Grob * parent = me->get_parent (ax);
657 if (parent->line_l () != me->line_l () && me->line_l ())
659 Grob * newparent = parent->find_broken_piece (me->line_l ());
660 me->set_parent (newparent, ax);
663 if (Item * i = dynamic_cast<Item*> (me))
665 Item *parenti = dynamic_cast<Item*> (parent);
669 Direction my_dir = i->break_status_dir () ;
670 if (my_dir!= parenti->break_status_dir ())
672 Item *newparent = parenti->find_prebroken_piece (my_dir);
673 me->set_parent (newparent, ax);
682 Grob::warning (String s)const
684 SCM cause = self_scm();
685 while (cause != SCM_EOL && !unsmob_music (cause))
687 Grob * g = unsmob_grob (cause);
688 cause = g->get_grob_property ("cause");
691 if (Music *m = unsmob_music (cause))
693 m->origin()->warning (s);
700 Grob::programming_error (String s)const
702 s = "Programming error: " + s;
707 /****************************************************
709 ****************************************************/
713 IMPLEMENT_SMOBS (Grob);
714 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
717 Grob::mark_smob (SCM ses)
719 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
720 scm_gc_mark (s->immutable_property_alist_);
722 for (int a =0 ; a < 2; a++)
724 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
725 scm_gc_mark (s->dim_cache_[a].dimension_);
728 don't mark the parents. The pointers in the mutable property
729 list form two tree like structures (one for X relations, one
730 for Y relations). Marking these can be done in limited stack
731 space. If we add the parents, we will jump between X and Y in
732 an erratic manner, leading to much more recursion depth (and
733 core dumps if we link to pthreads.)
738 scm_gc_mark (s->original_l_->self_scm ());
740 s->do_derived_mark ();
741 return s->mutable_property_alist_;
745 Grob::print_smob (SCM s, SCM port, scm_print_state *)
747 Grob *sc = (Grob *) ly_cdr (s);
749 scm_puts ("#<Grob ", port);
750 scm_puts ((char *)sc->name ().ch_C (), port);
753 don't try to print properties, that is too much hassle.
755 scm_puts (" >", port);
760 Grob::do_derived_mark ()
768 Grob::discretionary_processing ()
773 Grob::internal_has_interface (SCM k)
775 SCM ifs = get_grob_property ("interfaces");
777 return scm_memq (k, ifs) != SCM_BOOL_F;
780 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
782 ADD_INTERFACE (Grob, "grob-interface",
783 "In music notation, lots of symbols are related in some way. You can
784 think of music notation as a graph where nodes are formed by the
785 symbols, and the arcs by their relations. A grob is a node in that
786 graph. The directed edges in the graph are formed by references to
787 other grobs (i.e. pointers). This big graph of grobs specifies the
788 notation problem. The solution of this problem is a description of the
789 printout in closed form, i.e. a list of values. These values are
792 All grobs have an X and Y-position on the page. These X and Y positions
793 are stored in a relative format, so they can easily be combined by
794 stacking them, hanging one grob to the side of another, and coupling
795 them into a grouping-grob.
797 Each grob has a reference point (a.k.a. parent): the position of a grob
798 is stored relative to that reference point. For example the X-reference
799 point of a staccato dot usually is the note head that it applies
800 to. When the note head is moved, the staccato dot moves along
803 A grob is often associated with a symbol, but some grobs do not print
804 any symbols. They take care of grouping objects. For example, there is a
805 separate grob that stacks staves vertically. The @ref{NoteCollision}
806 is also an abstract grob: it only moves around chords, but doesn't print
809 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
810 Y-extent-callback molecule-callback extra-offset
812 staff-symbol interfaces dependencies extra-extent-X causes meta
813 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");