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_ = (Grob*) &s;
127 immutable_property_alist_ = s.immutable_property_alist_;
129 mutable_property_alist_ = SCM_EOL;
132 No properties are copied. That is the job of handle_broken_dependencies.
140 #ifdef HASHING_FOR_MUTABLE_PROPS
141 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
148 do nothing scm-ish and no unprotecting here.
156 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
158 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
160 Grob *s = unsmob_grob (element_smob);
161 Axis a = (Axis) gh_scm2int (scm_axis);
163 Molecule *m = s->get_molecule ();
167 return ly_interval2scm (e);
170 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
172 Grob::preset_extent (SCM element_smob, SCM scm_axis)
174 Grob *s = unsmob_grob (element_smob);
175 Axis a = (Axis) gh_scm2int (scm_axis);
177 SCM ext = s->get_grob_property ((a == X_AXIS)
183 Real l = gh_scm2double (ly_car (ext));
184 Real r = gh_scm2double (ly_cdr (ext));
185 return ly_interval2scm (Interval (l, r));
188 return ly_interval2scm (Interval ());
194 Grob::get_paper () const
196 return pscore_ ? pscore_->paper_ : 0;
200 Grob::calculate_dependencies (int final, int busy, SCM funcname)
202 if (status_ >= final)
207 programming_error ("Element is busy, come back later");
213 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
216 unsmob_grob (ly_car (d))
217 ->calculate_dependencies (final, busy, funcname);
221 SCM proc = internal_get_grob_property (funcname);
222 if (gh_procedure_p (proc))
223 gh_call1 (proc, this->self_scm ());
229 Grob::get_molecule () const
237 SCM mol = get_grob_property ("molecule");
238 if (unsmob_molecule (mol))
239 return unsmob_molecule (mol);
241 mol = get_uncached_molecule ();
245 Grob *me = (Grob*)this;
246 me->set_grob_property ("molecule", mol);
249 return unsmob_molecule (mol);
253 Grob::get_uncached_molecule ()const
255 SCM proc = get_grob_property ("molecule-callback");
258 if (gh_procedure_p (proc))
259 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
262 Molecule *m = unsmob_molecule (mol);
264 if (unsmob_molecule (mol))
266 SCM origin = ly_symbol2scm ("no-origin");
268 if (store_locations_global_b)
270 SCM cause = get_grob_property ("cause");
271 if (Music*m = unsmob_music (cause))
273 SCM music_origin = m->get_mus_property ("origin");
274 if (unsmob_input (music_origin))
275 origin = music_origin;
281 mol = Molecule (m->extent_box (),
282 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
285 m = unsmob_molecule (mol);
289 transparent retains dimensions of element.
291 if (m && to_boolean (get_grob_property ("transparent")))
292 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
303 Grob::do_break_processing ()
308 Grob::get_system () const
314 Grob::add_dependency (Grob*e)
318 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
321 programming_error ("Null dependency added");
326 Grob::handle_broken_dependencies ()
328 Spanner * sp = dynamic_cast<Spanner*> (this);
334 for (SCM s = mutable_property_alist_; gh_pair_p(s);
337 sp->substitute_one_mutable_property (gh_caar (s),
343 System *system = get_system ();
346 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
348 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
349 mutable_property_alist_);
351 else if (dynamic_cast <System*> (this))
353 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
358 This element is `invalid'; it has been removed from all
359 dependencies, so let's junk the element itself.
361 do not do this for System, since that would remove references
362 to the originals of score-grobs, which get then GC'd (a bad
371 Note that we still want references to this element to be
372 rearranged, and not silently thrown away, so we keep pointers
373 like {broken_into_{drul,array}, original}
385 mutable_property_alist_ = SCM_EOL;
386 immutable_property_alist_ = SCM_EOL;
388 set_extent (SCM_EOL, Y_AXIS);
389 set_extent (SCM_EOL, X_AXIS);
391 for (int a= X_AXIS; a <= Y_AXIS; a++)
393 dim_cache_[a].offset_callbacks_ = SCM_EOL;
394 dim_cache_[a].offsets_left_ = 0;
399 This can make debugging a little easier: we can still know what
400 the object used to be. However, since all its links have been
401 broken, it's usually more convenient to set a conditional
402 breakpoint in GDB before the property lists are wiped.
404 mutable_property_alist_ = scm_acons (ly_symbol2scm ("name"),
405 scm_makfrom0str (nm.to_str0()),
406 mutable_property_alist_
412 Grob::handle_prebroken_dependencies ()
415 Don't do this in the derived method, since we want to keep access to
416 mutable_property_alist_ centralized.
420 Item * it = dynamic_cast<Item*> (this);
421 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
422 original_->mutable_property_alist_);
427 Grob::find_broken_piece (System*) const
433 translate in one direction
436 Grob::translate_axis (Real y, Axis a)
438 if (isinf (y) || isnan (y))
439 programming_error (_ (INFINITY_MSG));
442 dim_cache_[a].offset_ += y;
448 Find the offset relative to D. If D equals THIS, then it is 0.
449 Otherwise, it recursively defd as
451 OFFSET_ + PARENT_L_->relative_coordinate (D)
454 Grob::relative_coordinate (Grob const*refp, Axis a) const
460 We catch PARENT_L_ == nil case with this, but we crash if we did
461 not ask for the absolute coordinate (ie. REFP == nil.)
464 if (refp == dim_cache_[a].parent_)
465 return get_offset (a);
467 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
473 Invoke callbacks to get offset relative to parent.
476 Grob::get_offset (Axis a) const
478 Grob *me = (Grob*) this;
479 while (dim_cache_[a].offsets_left_)
481 int l = --me->dim_cache_[a].offsets_left_;
482 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
483 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
485 Real r = gh_scm2double (retval);
486 if (isinf (r) || isnan (r))
488 programming_error (INFINITY_MSG);
491 me->dim_cache_[a].offset_ +=r;
493 return dim_cache_[a].offset_;
497 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
499 Grob::point_dimension_callback (SCM , SCM)
501 return ly_interval2scm (Interval (0,0));
505 Grob::empty_b (Axis a)const
507 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
508 gh_procedure_p (dim_cache_[a].dimension_));
512 Grob::extent (Grob * refp, Axis a) const
514 Real x = relative_coordinate (refp, a);
517 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
519 if (gh_pair_p (d->dimension_))
521 else if (gh_procedure_p (d->dimension_))
524 FIXME: add doco on types, and should typecheck maybe?
526 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
531 if (!gh_pair_p (d->dimension_))
534 ext = ly_scm2interval (d->dimension_);
536 SCM extra = get_grob_property (a == X_AXIS
543 if (gh_pair_p (extra))
545 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
546 ext[SMALLER] += gh_scm2double (ly_car (extra));
549 extra = get_grob_property (a == X_AXIS
551 : "minimum-Y-extent");
552 if (gh_pair_p (extra))
554 ext.unite (Interval (gh_scm2double (ly_car (extra)),
555 gh_scm2double (ly_cdr (extra))));
564 Find the group-element which has both #this# and #s#
567 Grob::common_refpoint (Grob const* s, Axis a) const
570 I don't like the quadratic aspect of this code, but I see no other
571 way. The largest chain of parents might be 10 high or so, so
572 it shouldn't be a real issue. */
573 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
574 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
583 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
585 for (; gh_pair_p (elist); elist = ly_cdr (elist))
587 Grob * s = unsmob_grob (ly_car (elist));
591 common = common->common_refpoint (s, a);
602 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
604 for (int i = arr.size() ; i--; )
611 common = common->common_refpoint (s, a);
622 SCM meta = get_grob_property ("meta");
623 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
624 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
625 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
629 Grob::add_offset_callback (SCM cb, Axis a)
631 if (!has_offset_callback_b (cb, a))
633 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
634 dim_cache_[a].offsets_left_ ++;
639 Grob::has_extent_callback_b (SCM cb, Axis a)const
641 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
646 Grob::has_offset_callback_b (SCM cb, Axis a)const
648 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
652 Grob::set_extent (SCM dc, Axis a)
654 dim_cache_[a].dimension_ =dc;
658 Grob::set_parent (Grob *g, Axis a)
660 dim_cache_[a].parent_ = g;
663 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
665 Grob::fixup_refpoint (SCM smob)
667 Grob *me = unsmob_grob (smob);
668 for (int a = X_AXIS; a < NO_AXES; a ++)
671 Grob * parent = me->get_parent (ax);
676 if (parent->get_system () != me->get_system () && me->get_system ())
678 Grob * newparent = parent->find_broken_piece (me->get_system ());
679 me->set_parent (newparent, ax);
682 if (Item * i = dynamic_cast<Item*> (me))
684 Item *parenti = dynamic_cast<Item*> (parent);
688 Direction my_dir = i->break_status_dir () ;
689 if (my_dir!= parenti->break_status_dir ())
691 Item *newparent = parenti->find_prebroken_piece (my_dir);
692 me->set_parent (newparent, ax);
701 Grob::warning (String s)const
703 SCM cause = self_scm();
704 while (cause != SCM_EOL && !unsmob_music (cause))
706 Grob * g = unsmob_grob (cause);
707 cause = g->get_grob_property ("cause");
710 if (Music *m = unsmob_music (cause))
712 m->origin()->warning (s);
719 Grob::programming_error (String s)const
721 s = "Programming error: " + s;
726 /****************************************************
728 ****************************************************/
732 IMPLEMENT_SMOBS (Grob);
733 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
736 Grob::mark_smob (SCM ses)
738 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
739 scm_gc_mark (s->immutable_property_alist_);
741 for (int a =0 ; a < 2; a++)
743 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
744 scm_gc_mark (s->dim_cache_[a].dimension_);
747 don't mark the parents. The pointers in the mutable property
748 list form two tree like structures (one for X relations, one
749 for Y relations). Marking these can be done in limited stack
750 space. If we add the parents, we will jump between X and Y in
751 an erratic manner, leading to much more recursion depth (and
752 core dumps if we link to pthreads.)
757 scm_gc_mark (s->original_->self_scm ());
759 s->do_derived_mark ();
760 return s->mutable_property_alist_;
764 Grob::print_smob (SCM s, SCM port, scm_print_state *)
766 Grob *sc = (Grob *) ly_cdr (s);
768 scm_puts ("#<Grob ", port);
769 scm_puts ((char *)sc->name ().to_str0 (), port);
772 don't try to print properties, that is too much hassle.
774 scm_puts (" >", port);
779 Grob::do_derived_mark () const
787 Grob::discretionary_processing ()
792 Grob::internal_has_interface (SCM k)
794 SCM ifs = get_grob_property ("interfaces");
796 return scm_memq (k, ifs) != SCM_BOOL_F;
800 /** Return Array of Grobs in SCM list L */
804 Link_array<Grob> arr;
806 for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
809 arr.push (unsmob_grob (e));
816 /** Return SCM list of Grob array A */
818 ly_grobs2scm (Link_array<Grob> a)
821 for (int i = a.size (); i; i--)
822 s = gh_cons (a[i-1]->self_scm (), s);
828 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
830 ADD_INTERFACE (Grob, "grob-interface",
831 "In music notation, lots of symbols are related in some way. You can
832 think of music notation as a graph where nodes are formed by the
833 symbols, and the arcs by their relations. A grob is a node in that
834 graph. The directed edges in the graph are formed by references to
835 other grobs (i.e. pointers). This big graph of grobs specifies the
836 notation problem. The solution of this problem is a description of the
837 printout in closed form, i.e. a list of values. These values are
840 All grobs have an X and Y-position on the page. These X and Y positions
841 are stored in a relative format, so they can easily be combined by
842 stacking them, hanging one grob to the side of another, and coupling
843 them into a grouping-grob.
845 Each grob has a reference point (a.k.a. parent): the position of a grob
846 is stored relative to that reference point. For example the X-reference
847 point of a staccato dot usually is the note head that it applies
848 to. When the note head is moved, the staccato dot moves along
851 A grob is often associated with a symbol, but some grobs do not print
852 any symbols. They take care of grouping objects. For example, there is a
853 separate grob that stacks staves vertically. The @ref{NoteCollision}
854 is also an abstract grob: it only moves around chords, but doesn't print
857 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
858 Y-extent-callback molecule-callback extra-offset spacing-procedure
859 staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent
860 causes meta layer before-line-breaking-callback
861 after-line-breaking-callback extra-Y-extent minimum-X-extent
862 minimum-Y-extent transparent");