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"
24 #include "molecule.hh"
29 #include "ly-smobs.icc"
34 remove dynamic_cast<Spanner,Item> and put this code into respective
38 //#define HASHING_FOR_MUTABLE_PROPS
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.
62 #ifdef HASHING_FOR_MUTABLE_PROPS
63 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
66 SCM meta = get_grob_property ("meta");
69 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
72 Switch off interface checks for the moment.
74 bool itc = internal_type_checking_global_b;
75 internal_type_checking_global_b = false;
76 internal_set_grob_property (ly_symbol2scm ("interfaces"), gh_cdr(ifs));
77 internal_type_checking_global_b = itc;
83 destill this into a function, so we can re-init the immutable
84 properties with a new BASICPROPS value after creation. Convenient
85 eg. when using \override with StaffSymbol. */
87 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
88 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
90 for (int a = X_AXIS; a <= Y_AXIS; a++)
92 SCM l = get_grob_property (onames[a]);
94 if (scm_ilength (l) >=0)
96 dim_cache_[a].offset_callbacks_ = l;
97 dim_cache_[a].offsets_left_ = scm_ilength (l);
101 programming_error ("[XY]-offset-callbacks must be a list");
104 SCM cb = get_grob_property (enames[a]);
107 Should change default to be empty?
110 && !gh_procedure_p (cb) && !gh_pair_p (cb)
111 && gh_procedure_p (get_grob_property ("molecule-callback"))
113 cb = molecule_extent_proc;
115 dim_cache_[a].dimension_ = cb;
120 Grob::Grob (Grob const&s)
121 : dim_cache_ (s.dim_cache_)
123 original_l_ = (Grob*) &s;
124 immutable_property_alist_ = s.immutable_property_alist_;
126 mutable_property_alist_ = SCM_EOL;
129 No properties are copied. That is the job of handle_broken_dependencies.
132 status_c_ = s.status_c_;
133 pscore_l_ = s.pscore_l_;
137 #ifdef HASHING_FOR_MUTABLE_PROPS
138 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
145 do nothing scm-ish and no unprotecting here.
153 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
155 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
157 Grob *s = unsmob_grob (element_smob);
158 Axis a = (Axis) gh_scm2int (scm_axis);
160 Molecule *m = s->get_molecule ();
164 return ly_interval2scm (e);
167 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
169 Grob::preset_extent (SCM element_smob, SCM scm_axis)
171 Grob *s = unsmob_grob (element_smob);
172 Axis a = (Axis) gh_scm2int (scm_axis);
174 SCM ext = s->get_grob_property ((a == X_AXIS)
180 Real l = gh_scm2double (ly_car (ext));
181 Real r = gh_scm2double (ly_cdr (ext));
182 return ly_interval2scm (Interval (l, r));
185 return ly_interval2scm (Interval ());
191 Grob::paper_l () const
193 return pscore_l_ ? pscore_l_->paper_l_ : 0;
197 Grob::calculate_dependencies (int final, int busy, SCM funcname)
199 if (status_c_ >= final)
202 if (status_c_== busy)
204 programming_error ("Element is busy, come back later");
210 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
213 unsmob_grob (ly_car (d))
214 ->calculate_dependencies (final, busy, funcname);
218 SCM proc = internal_get_grob_property (funcname);
219 if (gh_procedure_p (proc))
220 gh_call1 (proc, this->self_scm ());
226 Grob::get_molecule () const
234 SCM mol = get_grob_property ("molecule");
235 if (unsmob_molecule (mol))
236 return unsmob_molecule (mol);
238 mol = get_uncached_molecule ();
240 Grob *me = (Grob*)this;
241 me->set_grob_property ("molecule", mol);
243 return unsmob_molecule (mol);
246 Grob::get_uncached_molecule ()const
248 SCM proc = get_grob_property ("molecule-callback");
251 if (gh_procedure_p (proc))
252 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
255 Molecule *m = unsmob_molecule (mol);
257 if (unsmob_molecule (mol))
259 SCM origin = ly_symbol2scm ("no-origin");
261 if (store_locations_global_b){
262 SCM cause = get_grob_property ("cause");
263 if (Music*m = unsmob_music (cause))
265 SCM music_origin = m->get_mus_property ("origin");
266 if (unsmob_input (music_origin))
267 origin = music_origin;
273 mol = Molecule (m->extent_box (),
274 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
277 m = unsmob_molecule (mol);
281 transparent retains dimensions of element.
283 if (m && to_boolean (get_grob_property ("transparent")))
284 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
295 Grob::do_break_processing ()
305 Grob::line_l () const
311 Grob::add_dependency (Grob*e)
315 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
318 programming_error ("Null dependency added");
324 Grob::handle_broken_dependencies ()
326 Spanner * s= dynamic_cast<Spanner*> (this);
327 if (original_l_ && s)
332 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
334 Grob * sc = s->broken_into_l_arr_[i];
335 System * l = sc->line_l ();
337 sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
338 mutable_property_alist_);
343 System *line = line_l ();
346 && line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
348 substitute_mutable_properties (line ? line->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}
378 mutable_property_alist_ = SCM_EOL;
379 immutable_property_alist_ = SCM_EOL;
381 set_extent (SCM_EOL, Y_AXIS);
382 set_extent (SCM_EOL, X_AXIS);
384 for (int a= X_AXIS; a <= Y_AXIS; a++)
386 dim_cache_[a].offset_callbacks_ = SCM_EOL;
387 dim_cache_[a].offsets_left_ = 0;
392 Grob::handle_prebroken_dependencies ()
395 Don't do this in the derived method, since we want to keep access to
396 mutable_property_alist_ centralized.
400 Item * it = dynamic_cast<Item*> (this);
401 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
402 original_l_->mutable_property_alist_);
407 Grob::find_broken_piece (System*) const
413 translate in one direction
416 Grob::translate_axis (Real y, Axis a)
418 if (isinf (y) || isnan (y))
419 programming_error (_ (INFINITY_MSG));
422 dim_cache_[a].offset_ += y;
428 Find the offset relative to D. If D equals THIS, then it is 0.
429 Otherwise, it recursively defd as
431 OFFSET_ + PARENT_L_->relative_coordinate (D)
434 Grob::relative_coordinate (Grob const*refp, Axis a) const
440 We catch PARENT_L_ == nil case with this, but we crash if we did
441 not ask for the absolute coordinate (ie. REFP == nil.)
444 if (refp == dim_cache_[a].parent_l_)
445 return get_offset (a);
447 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
453 Invoke callbacks to get offset relative to parent.
456 Grob::get_offset (Axis a) const
458 Grob *me = (Grob*) this;
459 while (dim_cache_[a].offsets_left_)
461 int l = --me->dim_cache_[a].offsets_left_;
462 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
463 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
465 Real r = gh_scm2double (retval);
466 if (isinf (r) || isnan (r))
468 programming_error (INFINITY_MSG);
471 me->dim_cache_[a].offset_ +=r;
473 return dim_cache_[a].offset_;
477 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
479 Grob::point_dimension_callback (SCM , SCM)
481 return ly_interval2scm (Interval (0,0));
485 Grob::empty_b (Axis a)const
487 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
488 gh_procedure_p (dim_cache_[a].dimension_));
492 Grob::extent (Grob * refp, Axis a) const
494 Real x = relative_coordinate (refp, a);
497 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
499 if (gh_pair_p (d->dimension_))
501 else if (gh_procedure_p (d->dimension_))
504 FIXME: add doco on types, and should typecheck maybe?
506 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
511 if (!gh_pair_p (d->dimension_))
514 ext = ly_scm2interval (d->dimension_);
516 SCM extra = get_grob_property (a == X_AXIS
523 if (gh_pair_p (extra))
525 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
526 ext[SMALLER] += gh_scm2double (ly_car (extra));
529 extra = get_grob_property (a == X_AXIS
531 : "minimum-extent-Y");
532 if (gh_pair_p (extra))
534 ext.unite (Interval (gh_scm2double (ly_car (extra)),
535 gh_scm2double (ly_cdr (extra))));
544 Find the group-element which has both #this# and #s#
547 Grob::common_refpoint (Grob const* s, Axis a) const
550 I don't like the quadratic aspect of this code, but I see no other
551 way. The largest chain of parents might be 10 high or so, so
552 it shouldn't be a real issue. */
553 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
554 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
563 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
565 for (; gh_pair_p (elist); elist = ly_cdr (elist))
567 Grob * s = unsmob_grob (ly_car (elist));
571 common = common->common_refpoint (s, a);
582 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
584 for (int i = arr.size() ; i--; )
591 common = common->common_refpoint (s, a);
602 SCM meta = get_grob_property ("meta");
603 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
604 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
605 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
609 Grob::add_offset_callback (SCM cb, Axis a)
611 if (!has_offset_callback_b (cb, a))
613 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
614 dim_cache_[a].offsets_left_ ++;
619 Grob::has_extent_callback_b (SCM cb, Axis a)const
621 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
626 Grob::has_offset_callback_b (SCM cb, Axis a)const
628 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
632 Grob::set_extent (SCM dc, Axis a)
634 dim_cache_[a].dimension_ =dc;
638 Grob::set_parent (Grob *g, Axis a)
640 dim_cache_[a].parent_l_ = g;
643 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
645 Grob::fixup_refpoint (SCM smob)
647 Grob *me = unsmob_grob (smob);
648 for (int a = X_AXIS; a < NO_AXES; a ++)
651 Grob * parent = me->get_parent (ax);
656 if (parent->line_l () != me->line_l () && me->line_l ())
658 Grob * newparent = parent->find_broken_piece (me->line_l ());
659 me->set_parent (newparent, ax);
662 if (Item * i = dynamic_cast<Item*> (me))
664 Item *parenti = dynamic_cast<Item*> (parent);
668 Direction my_dir = i->break_status_dir () ;
669 if (my_dir!= parenti->break_status_dir ())
671 Item *newparent = parenti->find_prebroken_piece (my_dir);
672 me->set_parent (newparent, ax);
681 Grob::warning (String s)const
683 SCM cause = self_scm();
684 while (cause != SCM_EOL && !unsmob_music (cause))
686 Grob * g = unsmob_grob (cause);
687 cause = g->get_grob_property ("cause");
690 if (Music *m = unsmob_music (cause))
692 m->origin()->warning (s);
699 Grob::programming_error (String s)const
701 s = "Programming error: " + s;
706 /****************************************************
708 ****************************************************/
712 IMPLEMENT_SMOBS (Grob);
713 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
716 Grob::mark_smob (SCM ses)
718 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
719 scm_gc_mark (s->immutable_property_alist_);
721 for (int a =0 ; a < 2; a++)
723 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
724 scm_gc_mark (s->dim_cache_[a].dimension_);
727 don't mark the parents. The pointers in the mutable property
728 list form two tree like structures (one for X relations, one
729 for Y relations). Marking these can be done in limited stack
730 space. If we add the parents, we will jump between X and Y in
731 an erratic manner, leading to much more recursion depth (and
732 core dumps if we link to pthreads.)
737 scm_gc_mark (s->original_l_->self_scm ());
739 s->do_derived_mark ();
740 return s->mutable_property_alist_;
744 Grob::print_smob (SCM s, SCM port, scm_print_state *)
746 Grob *sc = (Grob *) ly_cdr (s);
748 scm_puts ("#<Grob ", port);
749 scm_puts ((char *)sc->name ().ch_C (), port);
752 don't try to print properties, that is too much hassle.
754 scm_puts (" >", port);
759 Grob::do_derived_mark ()
767 Grob::discretionary_processing ()
772 Grob::internal_has_interface (SCM k)
774 SCM ifs = get_grob_property ("interfaces");
776 return scm_memq (k, ifs) != SCM_BOOL_F;
779 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
781 ADD_INTERFACE (Grob, "grob-interface",
782 "In music notation, lots of symbols are related in some way. You can
783 think of music notation as a graph where nodes are formed by the
784 symbols, and the arcs by their relations. A grob is a node in that
785 graph. The directed edges in the graph are formed by references to
786 other grobs (i.e. pointers). This big graph of grobs specifies the
787 notation problem. The solution of this problem is a description of the
788 printout in closed form, i.e. a list of values. These values are
791 All grobs have an X and Y-position on the page. These X and Y positions
792 are stored in a relative format, so they can easily be combined by
793 stacking them, hanging one grob to the side of another, and coupling
794 them into a grouping-grob.
796 Each grob has a reference point (a.k.a. parent): the position of a grob
797 is stored relative to that reference point. For example the X-reference
798 point of a staccato dot usually is the note head that it applies
799 to. When the note head is moved, the staccato dot moves along
802 A grob is often associated with a symbol, but some grobs do not print
803 any symbols. They take care of grouping objects. For example, there is a
804 separate grob that stacks staves vertically. The @ref{NoteCollision}
805 is also an abstract grob: it only moves around chords, but doesn't print
808 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
809 Y-extent-callback molecule-callback extra-offset
811 staff-symbol interfaces dependencies extra-extent-X causes meta
812 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");