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::get_system () const
311 LY_DEFINE (get_system,
315 Return the System Grob of @var{grob}.
318 Grob *me = unsmob_grob (grob);
319 SCM_ASSERT_TYPE (me, grob, SCM_ARG1, __FUNCTION__, "grob");
321 if (Grob *g = me->get_system ())
322 return g->self_scm ();
327 LY_DEFINE (get_original,
331 Return the original Grob of @var{grob}
334 Grob *me = unsmob_grob (grob);
335 SCM_ASSERT_TYPE (me, grob, SCM_ARG1, __FUNCTION__, "grob");
336 return me->original_l_ ? me->original_l_->self_scm () : me->self_scm ();
340 Grob::add_dependency (Grob*e)
344 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
347 programming_error ("Null dependency added");
353 Grob::handle_broken_dependencies ()
355 Spanner * s= dynamic_cast<Spanner*> (this);
356 if (original_l_ && s)
361 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
363 Grob * sc = s->broken_into_l_arr_[i];
364 System * l = sc->get_system ();
366 sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
367 mutable_property_alist_);
372 System *system = get_system ();
375 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
377 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
378 mutable_property_alist_);
380 else if (dynamic_cast <System*> (this))
382 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
387 This element is `invalid'; it has been removed from all
388 dependencies, so let's junk the element itself.
390 do not do this for System, since that would remove references
391 to the originals of score-grobs, which get then GC'd (a bad
400 Note that we still want references to this element to be
401 rearranged, and not silently thrown away, so we keep pointers
402 like {broken_into_{drul,array}, original}
407 mutable_property_alist_ = SCM_EOL;
408 immutable_property_alist_ = SCM_EOL;
410 set_extent (SCM_EOL, Y_AXIS);
411 set_extent (SCM_EOL, X_AXIS);
413 for (int a= X_AXIS; a <= Y_AXIS; a++)
415 dim_cache_[a].offset_callbacks_ = SCM_EOL;
416 dim_cache_[a].offsets_left_ = 0;
421 Grob::handle_prebroken_dependencies ()
424 Don't do this in the derived method, since we want to keep access to
425 mutable_property_alist_ centralized.
429 Item * it = dynamic_cast<Item*> (this);
430 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
431 original_l_->mutable_property_alist_);
436 Grob::find_broken_piece (System*) const
442 translate in one direction
445 Grob::translate_axis (Real y, Axis a)
447 if (isinf (y) || isnan (y))
448 programming_error (_ (INFINITY_MSG));
451 dim_cache_[a].offset_ += y;
457 Find the offset relative to D. If D equals THIS, then it is 0.
458 Otherwise, it recursively defd as
460 OFFSET_ + PARENT_L_->relative_coordinate (D)
463 Grob::relative_coordinate (Grob const*refp, Axis a) const
469 We catch PARENT_L_ == nil case with this, but we crash if we did
470 not ask for the absolute coordinate (ie. REFP == nil.)
473 if (refp == dim_cache_[a].parent_l_)
474 return get_offset (a);
476 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
482 Invoke callbacks to get offset relative to parent.
485 Grob::get_offset (Axis a) const
487 Grob *me = (Grob*) this;
488 while (dim_cache_[a].offsets_left_)
490 int l = --me->dim_cache_[a].offsets_left_;
491 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
492 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
494 Real r = gh_scm2double (retval);
495 if (isinf (r) || isnan (r))
497 programming_error (INFINITY_MSG);
500 me->dim_cache_[a].offset_ +=r;
502 return dim_cache_[a].offset_;
506 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
508 Grob::point_dimension_callback (SCM , SCM)
510 return ly_interval2scm (Interval (0,0));
514 Grob::empty_b (Axis a)const
516 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
517 gh_procedure_p (dim_cache_[a].dimension_));
521 Grob::extent (Grob * refp, Axis a) const
523 Real x = relative_coordinate (refp, a);
526 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
528 if (gh_pair_p (d->dimension_))
530 else if (gh_procedure_p (d->dimension_))
533 FIXME: add doco on types, and should typecheck maybe?
535 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
540 if (!gh_pair_p (d->dimension_))
543 ext = ly_scm2interval (d->dimension_);
545 SCM extra = get_grob_property (a == X_AXIS
552 if (gh_pair_p (extra))
554 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
555 ext[SMALLER] += gh_scm2double (ly_car (extra));
558 extra = get_grob_property (a == X_AXIS
560 : "minimum-extent-Y");
561 if (gh_pair_p (extra))
563 ext.unite (Interval (gh_scm2double (ly_car (extra)),
564 gh_scm2double (ly_cdr (extra))));
573 Find the group-element which has both #this# and #s#
576 Grob::common_refpoint (Grob const* s, Axis a) const
579 I don't like the quadratic aspect of this code, but I see no other
580 way. The largest chain of parents might be 10 high or so, so
581 it shouldn't be a real issue. */
582 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
583 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
592 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
594 for (; gh_pair_p (elist); elist = ly_cdr (elist))
596 Grob * s = unsmob_grob (ly_car (elist));
600 common = common->common_refpoint (s, a);
611 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
613 for (int i = arr.size() ; i--; )
620 common = common->common_refpoint (s, a);
631 SCM meta = get_grob_property ("meta");
632 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
633 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
634 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
638 Grob::add_offset_callback (SCM cb, Axis a)
640 if (!has_offset_callback_b (cb, a))
642 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
643 dim_cache_[a].offsets_left_ ++;
648 Grob::has_extent_callback_b (SCM cb, Axis a)const
650 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
655 Grob::has_offset_callback_b (SCM cb, Axis a)const
657 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
661 Grob::set_extent (SCM dc, Axis a)
663 dim_cache_[a].dimension_ =dc;
667 Grob::set_parent (Grob *g, Axis a)
669 dim_cache_[a].parent_l_ = g;
672 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
674 Grob::fixup_refpoint (SCM smob)
676 Grob *me = unsmob_grob (smob);
677 for (int a = X_AXIS; a < NO_AXES; a ++)
680 Grob * parent = me->get_parent (ax);
685 if (parent->get_system () != me->get_system () && me->get_system ())
687 Grob * newparent = parent->find_broken_piece (me->get_system ());
688 me->set_parent (newparent, ax);
691 if (Item * i = dynamic_cast<Item*> (me))
693 Item *parenti = dynamic_cast<Item*> (parent);
697 Direction my_dir = i->break_status_dir () ;
698 if (my_dir!= parenti->break_status_dir ())
700 Item *newparent = parenti->find_prebroken_piece (my_dir);
701 me->set_parent (newparent, ax);
710 Grob::warning (String s)const
712 SCM cause = self_scm();
713 while (cause != SCM_EOL && !unsmob_music (cause))
715 Grob * g = unsmob_grob (cause);
716 cause = g->get_grob_property ("cause");
719 if (Music *m = unsmob_music (cause))
721 m->origin()->warning (s);
728 Grob::programming_error (String s)const
730 s = "Programming error: " + s;
735 /****************************************************
737 ****************************************************/
741 IMPLEMENT_SMOBS (Grob);
742 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
745 Grob::mark_smob (SCM ses)
747 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
748 scm_gc_mark (s->immutable_property_alist_);
750 for (int a =0 ; a < 2; a++)
752 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
753 scm_gc_mark (s->dim_cache_[a].dimension_);
756 don't mark the parents. The pointers in the mutable property
757 list form two tree like structures (one for X relations, one
758 for Y relations). Marking these can be done in limited stack
759 space. If we add the parents, we will jump between X and Y in
760 an erratic manner, leading to much more recursion depth (and
761 core dumps if we link to pthreads.)
766 scm_gc_mark (s->original_l_->self_scm ());
768 s->do_derived_mark ();
769 return s->mutable_property_alist_;
773 Grob::print_smob (SCM s, SCM port, scm_print_state *)
775 Grob *sc = (Grob *) ly_cdr (s);
777 scm_puts ("#<Grob ", port);
778 scm_puts ((char *)sc->name ().ch_C (), port);
781 don't try to print properties, that is too much hassle.
783 scm_puts (" >", port);
788 Grob::do_derived_mark ()
796 Grob::discretionary_processing ()
801 Grob::internal_has_interface (SCM k)
803 SCM ifs = get_grob_property ("interfaces");
805 return scm_memq (k, ifs) != SCM_BOOL_F;
809 /** Return Array of Grobs in SCM list L */
811 ly_scm2grob_array (SCM l)
813 Link_array<Grob> arr;
815 for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
818 arr.push (unsmob_grob (e));
825 /** Return SCM list of Grob array A */
827 ly_grob_array2scm (Link_array<Grob> a)
830 for (int i = a.size (); i; i--)
831 s = gh_cons (a[i-1]->self_scm (), s);
837 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
839 ADD_INTERFACE (Grob, "grob-interface",
840 "In music notation, lots of symbols are related in some way. You can
841 think of music notation as a graph where nodes are formed by the
842 symbols, and the arcs by their relations. A grob is a node in that
843 graph. The directed edges in the graph are formed by references to
844 other grobs (i.e. pointers). This big graph of grobs specifies the
845 notation problem. The solution of this problem is a description of the
846 printout in closed form, i.e. a list of values. These values are
849 All grobs have an X and Y-position on the page. These X and Y positions
850 are stored in a relative format, so they can easily be combined by
851 stacking them, hanging one grob to the side of another, and coupling
852 them into a grouping-grob.
854 Each grob has a reference point (a.k.a. parent): the position of a grob
855 is stored relative to that reference point. For example the X-reference
856 point of a staccato dot usually is the note head that it applies
857 to. When the note head is moved, the staccato dot moves along
860 A grob is often associated with a symbol, but some grobs do not print
861 any symbols. They take care of grouping objects. For example, there is a
862 separate grob that stacks staves vertically. The @ref{NoteCollision}
863 is also an abstract grob: it only moves around chords, but doesn't print
866 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
867 Y-extent-callback molecule-callback extra-offset
869 staff-symbol interfaces dependencies extra-extent-X causes meta
870 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");