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 "paper-def.hh"
20 #include "molecule.hh"
26 #include "paper-column.hh"
27 #include "molecule.hh"
29 #include "paper-outputter.hh"
33 #include "ly-smobs.icc"
38 remove dynamic_cast<Spanner,Item> and put this code into respective
42 //#define HASHING_FOR_MUTABLE_PROPS
44 #define INFINITY_MSG "Infinity or NaN encountered"
46 Grob::Grob (SCM basicprops)
49 fixme: default should be no callback.
55 immutable_property_alist_ = basicprops;
56 mutable_property_alist_ = SCM_EOL;
59 We do smobify_self() as the first step. Since the object lives on
60 the heap, none of its SCM variables are protected from GC. After
61 smobify_self(), they are.
66 #ifdef HASHING_FOR_MUTABLE_PROPS
67 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
70 SCM meta = get_grob_property ("meta");
73 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
76 Switch off interface checks for the moment.
78 bool itc = internal_type_checking_global_b;
79 internal_type_checking_global_b = false;
80 internal_set_grob_property (ly_symbol2scm ("interfaces"), gh_cdr(ifs));
81 internal_type_checking_global_b = itc;
87 destill this into a function, so we can re-init the immutable
88 properties with a new BASICPROPS value after creation. Convenient
89 eg. when using \override with StaffSymbol. */
91 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
92 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
94 for (int a = X_AXIS; a <= Y_AXIS; a++)
96 SCM l = get_grob_property (onames[a]);
98 if (scm_ilength (l) >=0)
100 dim_cache_[a].offset_callbacks_ = l;
101 dim_cache_[a].offsets_left_ = scm_ilength (l);
105 programming_error ("[XY]-offset-callbacks must be a list");
108 SCM cb = get_grob_property (enames[a]);
111 Should change default to be empty?
114 && !gh_procedure_p (cb) && !gh_pair_p (cb)
115 && gh_procedure_p (get_grob_property ("molecule-callback"))
117 cb = molecule_extent_proc;
119 dim_cache_[a].dimension_ = cb;
124 Grob::Grob (Grob const&s)
125 : dim_cache_ (s.dim_cache_)
127 original_l_ = (Grob*) &s;
128 immutable_property_alist_ = s.immutable_property_alist_;
130 mutable_property_alist_ = SCM_EOL;
133 No properties are copied. That is the job of handle_broken_dependencies.
136 status_c_ = s.status_c_;
137 pscore_l_ = s.pscore_l_;
141 #ifdef HASHING_FOR_MUTABLE_PROPS
142 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
149 do nothing scm-ish and no unprotecting here.
157 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
159 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
161 Grob *s = unsmob_grob (element_smob);
162 Axis a = (Axis) gh_scm2int (scm_axis);
164 Molecule *m = s->get_molecule ();
168 return ly_interval2scm (e);
171 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
173 Grob::preset_extent (SCM element_smob, SCM scm_axis)
175 Grob *s = unsmob_grob (element_smob);
176 Axis a = (Axis) gh_scm2int (scm_axis);
178 SCM ext = s->get_grob_property ((a == X_AXIS)
184 Real l = gh_scm2double (ly_car (ext));
185 Real r = gh_scm2double (ly_cdr (ext));
186 return ly_interval2scm (Interval (l, r));
189 return ly_interval2scm (Interval ());
195 Grob::paper_l () const
197 return pscore_l_ ? pscore_l_->paper_l_ : 0;
201 Grob::calculate_dependencies (int final, int busy, SCM funcname)
203 if (status_c_ >= final)
206 if (status_c_== busy)
208 programming_error ("Element is busy, come back later");
214 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
217 unsmob_grob (ly_car (d))
218 ->calculate_dependencies (final, busy, funcname);
222 SCM proc = internal_get_grob_property (funcname);
223 if (gh_procedure_p (proc))
224 gh_call1 (proc, this->self_scm ());
230 Grob::get_molecule () const
238 SCM mol = get_grob_property ("molecule");
239 if (unsmob_molecule (mol))
240 return unsmob_molecule (mol);
242 mol = get_uncached_molecule ();
244 Grob *me = (Grob*)this;
245 me->set_grob_property ("molecule", mol);
247 return unsmob_molecule (mol);
250 Grob::get_uncached_molecule ()const
252 SCM proc = get_grob_property ("molecule-callback");
255 if (gh_procedure_p (proc))
256 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
259 Molecule *m = unsmob_molecule (mol);
261 if (unsmob_molecule (mol))
263 SCM origin = ly_symbol2scm ("no-origin");
265 if (store_locations_global_b){
266 SCM cause = get_grob_property ("cause");
267 if (Music*m = unsmob_music (cause))
269 SCM music_origin = m->get_mus_property ("origin");
270 if (unsmob_input (music_origin))
271 origin = music_origin;
277 mol = Molecule (m->extent_box (),
278 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
281 m = unsmob_molecule (mol);
285 transparent retains dimensions of element.
287 if (m && to_boolean (get_grob_property ("transparent")))
288 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
299 Grob::do_break_processing ()
309 Grob::line_l () const
315 Grob::add_dependency (Grob*e)
319 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
322 programming_error ("Null dependency added");
328 Grob::handle_broken_dependencies ()
330 Spanner * s= dynamic_cast<Spanner*> (this);
331 if (original_l_ && s)
336 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
338 Grob * sc = s->broken_into_l_arr_[i];
339 System * l = sc->line_l ();
341 sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
342 mutable_property_alist_);
347 System *line = line_l ();
350 && line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
352 substitute_mutable_properties (line ? line->self_scm () : SCM_UNDEFINED,
353 mutable_property_alist_);
355 else if (dynamic_cast <System*> (this))
357 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
362 This element is `invalid'; it has been removed from all
363 dependencies, so let's junk the element itself.
365 do not do this for System, since that would remove references
366 to the originals of score-grobs, which get then GC'd (a bad
375 Note that we still want references to this element to be
376 rearranged, and not silently thrown away, so we keep pointers
377 like {broken_into_{drul,array}, original}
382 mutable_property_alist_ = SCM_EOL;
383 immutable_property_alist_ = SCM_EOL;
385 set_extent (SCM_EOL, Y_AXIS);
386 set_extent (SCM_EOL, X_AXIS);
388 for (int a= X_AXIS; a <= Y_AXIS; a++)
390 dim_cache_[a].offset_callbacks_ = SCM_EOL;
391 dim_cache_[a].offsets_left_ = 0;
396 Grob::handle_prebroken_dependencies ()
399 Don't do this in the derived method, since we want to keep access to
400 mutable_property_alist_ centralized.
404 Item * it = dynamic_cast<Item*> (this);
405 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
406 original_l_->mutable_property_alist_);
411 Grob::find_broken_piece (System*) const
417 translate in one direction
420 Grob::translate_axis (Real y, Axis a)
422 if (isinf (y) || isnan (y))
423 programming_error (_ (INFINITY_MSG));
426 dim_cache_[a].offset_ += y;
432 Find the offset relative to D. If D equals THIS, then it is 0.
433 Otherwise, it recursively defd as
435 OFFSET_ + PARENT_L_->relative_coordinate (D)
438 Grob::relative_coordinate (Grob const*refp, Axis a) const
444 We catch PARENT_L_ == nil case with this, but we crash if we did
445 not ask for the absolute coordinate (ie. REFP == nil.)
448 if (refp == dim_cache_[a].parent_l_)
449 return get_offset (a);
451 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
457 Invoke callbacks to get offset relative to parent.
460 Grob::get_offset (Axis a) const
462 Grob *me = (Grob*) this;
463 while (dim_cache_[a].offsets_left_)
465 int l = --me->dim_cache_[a].offsets_left_;
466 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
467 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
469 Real r = gh_scm2double (retval);
470 if (isinf (r) || isnan (r))
472 programming_error (INFINITY_MSG);
475 me->dim_cache_[a].offset_ +=r;
477 return dim_cache_[a].offset_;
481 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
483 Grob::point_dimension_callback (SCM , SCM)
485 return ly_interval2scm (Interval (0,0));
489 Grob::empty_b (Axis a)const
491 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
492 gh_procedure_p (dim_cache_[a].dimension_));
496 Grob::extent (Grob * refp, Axis a) const
498 Real x = relative_coordinate (refp, a);
501 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
503 if (gh_pair_p (d->dimension_))
505 else if (gh_procedure_p (d->dimension_))
508 FIXME: add doco on types, and should typecheck maybe?
510 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
515 if (!gh_pair_p (d->dimension_))
518 ext = ly_scm2interval (d->dimension_);
520 SCM extra = get_grob_property (a == X_AXIS
527 if (gh_pair_p (extra))
529 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
530 ext[SMALLER] += gh_scm2double (ly_car (extra));
533 extra = get_grob_property (a == X_AXIS
535 : "minimum-extent-Y");
536 if (gh_pair_p (extra))
538 ext.unite (Interval (gh_scm2double (ly_car (extra)),
539 gh_scm2double (ly_cdr (extra))));
548 Find the group-element which has both #this# and #s#
551 Grob::common_refpoint (Grob const* s, Axis a) const
554 I don't like the quadratic aspect of this code, but I see no other
555 way. The largest chain of parents might be 10 high or so, so
556 it shouldn't be a real issue. */
557 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
558 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
567 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
569 for (; gh_pair_p (elist); elist = ly_cdr (elist))
571 Grob * s = unsmob_grob (ly_car (elist));
575 common = common->common_refpoint (s, a);
586 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
588 for (int i = arr.size() ; i--; )
595 common = common->common_refpoint (s, a);
606 SCM meta = get_grob_property ("meta");
607 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
608 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
609 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
613 Grob::add_offset_callback (SCM cb, Axis a)
615 if (!has_offset_callback_b (cb, a))
617 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
618 dim_cache_[a].offsets_left_ ++;
623 Grob::has_extent_callback_b (SCM cb, Axis a)const
625 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
630 Grob::has_offset_callback_b (SCM cb, Axis a)const
632 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
636 Grob::set_extent (SCM dc, Axis a)
638 dim_cache_[a].dimension_ =dc;
642 Grob::set_parent (Grob *g, Axis a)
644 dim_cache_[a].parent_l_ = g;
647 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
649 Grob::fixup_refpoint (SCM smob)
651 Grob *me = unsmob_grob (smob);
652 for (int a = X_AXIS; a < NO_AXES; a ++)
655 Grob * parent = me->get_parent (ax);
660 if (parent->line_l () != me->line_l () && me->line_l ())
662 Grob * newparent = parent->find_broken_piece (me->line_l ());
663 me->set_parent (newparent, ax);
666 if (Item * i = dynamic_cast<Item*> (me))
668 Item *parenti = dynamic_cast<Item*> (parent);
672 Direction my_dir = i->break_status_dir () ;
673 if (my_dir!= parenti->break_status_dir ())
675 Item *newparent = parenti->find_prebroken_piece (my_dir);
676 me->set_parent (newparent, ax);
685 Grob::warning (String s)const
687 SCM cause = self_scm();
688 while (cause != SCM_EOL && !unsmob_music (cause))
690 Grob * g = unsmob_grob (cause);
691 cause = g->get_grob_property ("cause");
694 if (Music *m = unsmob_music (cause))
696 m->origin()->warning (s);
703 Grob::programming_error (String s)const
705 s = "Programming error: " + s;
710 /****************************************************
712 ****************************************************/
716 IMPLEMENT_SMOBS (Grob);
717 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
720 Grob::mark_smob (SCM ses)
722 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
723 scm_gc_mark (s->immutable_property_alist_);
725 for (int a =0 ; a < 2; a++)
727 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
728 scm_gc_mark (s->dim_cache_[a].dimension_);
731 don't mark the parents. The pointers in the mutable property
732 list form two tree like structures (one for X relations, one
733 for Y relations). Marking these can be done in limited stack
734 space. If we add the parents, we will jump between X and Y in
735 an erratic manner, leading to much more recursion depth (and
736 core dumps if we link to pthreads.)
741 scm_gc_mark (s->original_l_->self_scm ());
743 s->do_derived_mark ();
744 return s->mutable_property_alist_;
748 Grob::print_smob (SCM s, SCM port, scm_print_state *)
750 Grob *sc = (Grob *) ly_cdr (s);
752 scm_puts ("#<Grob ", port);
753 scm_puts ((char *)sc->name ().ch_C (), port);
756 don't try to print properties, that is too much hassle.
758 scm_puts (" >", port);
763 Grob::do_derived_mark ()
771 Grob::discretionary_processing ()
776 Grob::internal_has_interface (SCM k)
778 SCM ifs = get_grob_property ("interfaces");
780 return scm_memq (k, ifs) != SCM_BOOL_F;
783 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
785 ADD_INTERFACE (Grob, "grob-interface",
786 "In music notation, lots of symbols are related in some way. You can
787 think of music notation as a graph where nodes are formed by the
788 symbols, and the arcs by their relations. A grob is a node in that
789 graph. The directed edges in the graph are formed by references to
790 other grobs (i.e. pointers). This big graph of grobs specifies the
791 notation problem. The solution of this problem is a description of the
792 printout in closed form, i.e. a list of values. These values are
795 All grobs have an X and Y-position on the page. These X and Y positions
796 are stored in a relative format, so they can easily be combined by
797 stacking them, hanging one grob to the side of another, and coupling
798 them into a grouping-grob.
800 Each grob has a reference point (a.k.a. parent): the position of a grob
801 is stored relative to that reference point. For example the X-reference
802 point of a staccato dot usually is the note head that it applies
803 to. When the note head is moved, the staccato dot moves along
806 A grob is often associated with a symbol, but some grobs do not print
807 any symbols. They take care of grouping objects. For example, there is a
808 separate grob that stacks staves vertically. The @ref{NoteCollision}
809 is also an abstract grob: it only moves around chords, but doesn't print
812 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
813 Y-extent-callback molecule-callback extra-offset
815 staff-symbol interfaces dependencies extra-extent-X causes meta
816 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");