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
311 MAKE_SCHEME_CALLBACK (Grob, line_scm, 1);
313 Grob::line_scm (SCM smob)
315 Grob *me = unsmob_grob (smob);
316 if (Grob *g = me->line_l ())
317 return g->self_scm ();
322 MAKE_SCHEME_CALLBACK (Grob, original_scm, 1);
324 Grob::original_scm (SCM smob)
326 Grob *me = unsmob_grob (smob);
328 return me->original_l_->self_scm ();
334 Grob::add_dependency (Grob*e)
338 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
341 programming_error ("Null dependency added");
347 Grob::handle_broken_dependencies ()
349 Spanner * s= dynamic_cast<Spanner*> (this);
350 if (original_l_ && s)
355 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
357 Grob * sc = s->broken_into_l_arr_[i];
358 System * l = sc->line_l ();
360 sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
361 mutable_property_alist_);
366 System *line = line_l ();
369 && line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
371 substitute_mutable_properties (line ? line->self_scm () : SCM_UNDEFINED,
372 mutable_property_alist_);
374 else if (dynamic_cast <System*> (this))
376 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
381 This element is `invalid'; it has been removed from all
382 dependencies, so let's junk the element itself.
384 do not do this for System, since that would remove references
385 to the originals of score-grobs, which get then GC'd (a bad
394 Note that we still want references to this element to be
395 rearranged, and not silently thrown away, so we keep pointers
396 like {broken_into_{drul,array}, original}
401 mutable_property_alist_ = SCM_EOL;
402 immutable_property_alist_ = SCM_EOL;
404 set_extent (SCM_EOL, Y_AXIS);
405 set_extent (SCM_EOL, X_AXIS);
407 for (int a= X_AXIS; a <= Y_AXIS; a++)
409 dim_cache_[a].offset_callbacks_ = SCM_EOL;
410 dim_cache_[a].offsets_left_ = 0;
415 Grob::handle_prebroken_dependencies ()
418 Don't do this in the derived method, since we want to keep access to
419 mutable_property_alist_ centralized.
423 Item * it = dynamic_cast<Item*> (this);
424 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
425 original_l_->mutable_property_alist_);
430 Grob::find_broken_piece (System*) const
436 translate in one direction
439 Grob::translate_axis (Real y, Axis a)
441 if (isinf (y) || isnan (y))
442 programming_error (_ (INFINITY_MSG));
445 dim_cache_[a].offset_ += y;
451 Find the offset relative to D. If D equals THIS, then it is 0.
452 Otherwise, it recursively defd as
454 OFFSET_ + PARENT_L_->relative_coordinate (D)
457 Grob::relative_coordinate (Grob const*refp, Axis a) const
463 We catch PARENT_L_ == nil case with this, but we crash if we did
464 not ask for the absolute coordinate (ie. REFP == nil.)
467 if (refp == dim_cache_[a].parent_l_)
468 return get_offset (a);
470 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
476 Invoke callbacks to get offset relative to parent.
479 Grob::get_offset (Axis a) const
481 Grob *me = (Grob*) this;
482 while (dim_cache_[a].offsets_left_)
484 int l = --me->dim_cache_[a].offsets_left_;
485 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
486 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
488 Real r = gh_scm2double (retval);
489 if (isinf (r) || isnan (r))
491 programming_error (INFINITY_MSG);
494 me->dim_cache_[a].offset_ +=r;
496 return dim_cache_[a].offset_;
500 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
502 Grob::point_dimension_callback (SCM , SCM)
504 return ly_interval2scm (Interval (0,0));
508 Grob::empty_b (Axis a)const
510 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
511 gh_procedure_p (dim_cache_[a].dimension_));
515 Grob::extent (Grob * refp, Axis a) const
517 Real x = relative_coordinate (refp, a);
520 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
522 if (gh_pair_p (d->dimension_))
524 else if (gh_procedure_p (d->dimension_))
527 FIXME: add doco on types, and should typecheck maybe?
529 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
534 if (!gh_pair_p (d->dimension_))
537 ext = ly_scm2interval (d->dimension_);
539 SCM extra = get_grob_property (a == X_AXIS
546 if (gh_pair_p (extra))
548 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
549 ext[SMALLER] += gh_scm2double (ly_car (extra));
552 extra = get_grob_property (a == X_AXIS
554 : "minimum-extent-Y");
555 if (gh_pair_p (extra))
557 ext.unite (Interval (gh_scm2double (ly_car (extra)),
558 gh_scm2double (ly_cdr (extra))));
567 Find the group-element which has both #this# and #s#
570 Grob::common_refpoint (Grob const* s, Axis a) const
573 I don't like the quadratic aspect of this code, but I see no other
574 way. The largest chain of parents might be 10 high or so, so
575 it shouldn't be a real issue. */
576 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
577 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
586 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
588 for (; gh_pair_p (elist); elist = ly_cdr (elist))
590 Grob * s = unsmob_grob (ly_car (elist));
594 common = common->common_refpoint (s, a);
605 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
607 for (int i = arr.size() ; i--; )
614 common = common->common_refpoint (s, a);
625 SCM meta = get_grob_property ("meta");
626 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
627 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
628 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
632 Grob::add_offset_callback (SCM cb, Axis a)
634 if (!has_offset_callback_b (cb, a))
636 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
637 dim_cache_[a].offsets_left_ ++;
642 Grob::has_extent_callback_b (SCM cb, Axis a)const
644 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
649 Grob::has_offset_callback_b (SCM cb, Axis a)const
651 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
655 Grob::set_extent (SCM dc, Axis a)
657 dim_cache_[a].dimension_ =dc;
661 Grob::set_parent (Grob *g, Axis a)
663 dim_cache_[a].parent_l_ = g;
666 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
668 Grob::fixup_refpoint (SCM smob)
670 Grob *me = unsmob_grob (smob);
671 for (int a = X_AXIS; a < NO_AXES; a ++)
674 Grob * parent = me->get_parent (ax);
679 if (parent->line_l () != me->line_l () && me->line_l ())
681 Grob * newparent = parent->find_broken_piece (me->line_l ());
682 me->set_parent (newparent, ax);
685 if (Item * i = dynamic_cast<Item*> (me))
687 Item *parenti = dynamic_cast<Item*> (parent);
691 Direction my_dir = i->break_status_dir () ;
692 if (my_dir!= parenti->break_status_dir ())
694 Item *newparent = parenti->find_prebroken_piece (my_dir);
695 me->set_parent (newparent, ax);
704 Grob::warning (String s)const
706 SCM cause = self_scm();
707 while (cause != SCM_EOL && !unsmob_music (cause))
709 Grob * g = unsmob_grob (cause);
710 cause = g->get_grob_property ("cause");
713 if (Music *m = unsmob_music (cause))
715 m->origin()->warning (s);
722 Grob::programming_error (String s)const
724 s = "Programming error: " + s;
729 /****************************************************
731 ****************************************************/
735 IMPLEMENT_SMOBS (Grob);
736 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
739 Grob::mark_smob (SCM ses)
741 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
742 scm_gc_mark (s->immutable_property_alist_);
744 for (int a =0 ; a < 2; a++)
746 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
747 scm_gc_mark (s->dim_cache_[a].dimension_);
750 don't mark the parents. The pointers in the mutable property
751 list form two tree like structures (one for X relations, one
752 for Y relations). Marking these can be done in limited stack
753 space. If we add the parents, we will jump between X and Y in
754 an erratic manner, leading to much more recursion depth (and
755 core dumps if we link to pthreads.)
760 scm_gc_mark (s->original_l_->self_scm ());
762 s->do_derived_mark ();
763 return s->mutable_property_alist_;
767 Grob::print_smob (SCM s, SCM port, scm_print_state *)
769 Grob *sc = (Grob *) ly_cdr (s);
771 scm_puts ("#<Grob ", port);
772 scm_puts ((char *)sc->name ().ch_C (), port);
775 don't try to print properties, that is too much hassle.
777 scm_puts (" >", port);
782 Grob::do_derived_mark ()
790 Grob::discretionary_processing ()
795 Grob::internal_has_interface (SCM k)
797 SCM ifs = get_grob_property ("interfaces");
799 return scm_memq (k, ifs) != SCM_BOOL_F;
802 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
804 ADD_INTERFACE (Grob, "grob-interface",
805 "In music notation, lots of symbols are related in some way. You can
806 think of music notation as a graph where nodes are formed by the
807 symbols, and the arcs by their relations. A grob is a node in that
808 graph. The directed edges in the graph are formed by references to
809 other grobs (i.e. pointers). This big graph of grobs specifies the
810 notation problem. The solution of this problem is a description of the
811 printout in closed form, i.e. a list of values. These values are
814 All grobs have an X and Y-position on the page. These X and Y positions
815 are stored in a relative format, so they can easily be combined by
816 stacking them, hanging one grob to the side of another, and coupling
817 them into a grouping-grob.
819 Each grob has a reference point (a.k.a. parent): the position of a grob
820 is stored relative to that reference point. For example the X-reference
821 point of a staccato dot usually is the note head that it applies
822 to. When the note head is moved, the staccato dot moves along
825 A grob is often associated with a symbol, but some grobs do not print
826 any symbols. They take care of grouping objects. For example, there is a
827 separate grob that stacks staves vertically. The @ref{NoteCollision}
828 is also an abstract grob: it only moves around chords, but doesn't print
831 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
832 Y-extent-callback molecule-callback extra-offset
834 staff-symbol interfaces dependencies extra-extent-X causes meta
835 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");