2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2004 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_;
128 mutable_property_alist_ = SCM_EOL;
131 No properties are copied. That is the job of handle_broken_dependencies.
139 #ifdef HASHING_FOR_MUTABLE_PROPS
140 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
147 do nothing scm-ish and no unprotecting here.
152 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
154 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
156 Grob *s = unsmob_grob (element_smob);
157 Axis a = (Axis) gh_scm2int (scm_axis);
159 Molecule *m = s->get_molecule ();
163 return ly_interval2scm (e);
166 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
168 Grob::preset_extent (SCM element_smob, SCM scm_axis)
170 Grob *s = unsmob_grob (element_smob);
171 Axis a = (Axis) gh_scm2int (scm_axis);
173 SCM ext = s->get_grob_property ((a == X_AXIS)
177 if (is_number_pair (ext))
180 return ly_interval2scm (Interval());
186 Grob::get_paper () const
188 return pscore_ ? pscore_->paper_ : 0;
192 Grob::calculate_dependencies (int final, int busy, SCM funcname)
194 if (status_ >= final)
199 programming_error ("Element is busy, come back later");
205 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
208 unsmob_grob (ly_car (d))
209 ->calculate_dependencies (final, busy, funcname);
213 SCM proc = internal_get_grob_property (funcname);
214 if (gh_procedure_p (proc))
215 gh_call1 (proc, this->self_scm ());
221 Grob::get_molecule () const
228 SCM mol = get_grob_property ("molecule");
229 if (unsmob_molecule (mol))
230 return unsmob_molecule (mol);
232 mol = get_uncached_molecule ();
236 Grob *me = (Grob*)this;
237 me->set_grob_property ("molecule", mol);
240 return unsmob_molecule (mol);
244 Grob::get_uncached_molecule ()const
246 SCM proc = get_grob_property ("molecule-callback");
249 if (gh_procedure_p (proc))
250 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
252 Molecule *m = unsmob_molecule (mol);
254 if (unsmob_molecule (mol))
256 SCM origin = ly_symbol2scm ("no-origin");
258 if (store_locations_global_b)
260 SCM cause = get_grob_property ("cause");
261 if (Music*m = unsmob_music (cause))
263 SCM music_origin = m->get_mus_property ("origin");
264 if (unsmob_input (music_origin))
265 origin = music_origin;
271 mol = Molecule (m->extent_box (),
272 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
275 m = unsmob_molecule (mol);
279 transparent retains dimensions of element.
281 if (m && to_boolean (get_grob_property ("transparent")))
282 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
293 Grob::do_break_processing ()
298 Grob::get_system () const
304 Grob::add_dependency (Grob*e)
308 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
311 programming_error ("Null dependency added");
316 Grob::handle_broken_dependencies ()
318 Spanner * sp = dynamic_cast<Spanner*> (this);
325 This is the original spanner. We use a special function
326 because some Spanners have enormously long lists in their
329 for (SCM s = mutable_property_alist_; gh_pair_p(s);
332 sp->substitute_one_mutable_property (gh_caar (s),
337 System *system = get_system ();
340 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
342 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
343 mutable_property_alist_);
345 else if (dynamic_cast <System*> (this))
347 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
352 This element is `invalid'; it has been removed from all
353 dependencies, so let's junk the element itself.
355 do not do this for System, since that would remove references
356 to the originals of score-grobs, which get then GC'd (a bad
365 Note that we still want references to this element to be
366 rearranged, and not silently thrown away, so we keep pointers
367 like {broken_into_{drul,array}, original}
379 mutable_property_alist_ = SCM_EOL;
380 immutable_property_alist_ = SCM_EOL;
382 set_extent (SCM_EOL, Y_AXIS);
383 set_extent (SCM_EOL, X_AXIS);
385 for (int a= X_AXIS; a <= Y_AXIS; a++)
387 dim_cache_[a].offset_callbacks_ = SCM_EOL;
388 dim_cache_[a].offsets_left_ = 0;
393 This can make debugging a little easier: we can still know what
394 the object used to be. However, since all its links have been
395 broken, it's usually more convenient to set a conditional
396 breakpoint in GDB before the property lists are wiped.
398 mutable_property_alist_ = scm_acons (ly_symbol2scm ("name"),
399 scm_makfrom0str (nm.to_str0()),
400 mutable_property_alist_
406 Grob::handle_prebroken_dependencies ()
409 Don't do this in the derived method, since we want to keep access to
410 mutable_property_alist_ centralized.
414 Item * it = dynamic_cast<Item*> (this);
415 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
416 original_->mutable_property_alist_);
421 Grob::find_broken_piece (System*) const
427 translate in one direction
430 Grob::translate_axis (Real y, Axis a)
432 if (isinf (y) || isnan (y))
433 programming_error (_ (INFINITY_MSG));
436 dim_cache_[a].offset_ += y;
442 Find the offset relative to D. If D equals THIS, then it is 0.
443 Otherwise, it recursively defd as
445 OFFSET_ + PARENT_L_->relative_coordinate (D)
448 Grob::relative_coordinate (Grob const*refp, Axis a) const
454 We catch PARENT_L_ == nil case with this, but we crash if we did
455 not ask for the absolute coordinate (ie. REFP == nil.)
458 if (refp == dim_cache_[a].parent_)
459 return get_offset (a);
461 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
467 Invoke callbacks to get offset relative to parent.
470 Grob::get_offset (Axis a) const
472 Grob *me = (Grob*) this;
473 while (dim_cache_[a].offsets_left_)
475 int l = --me->dim_cache_[a].offsets_left_;
476 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
477 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
479 Real r = gh_scm2double (retval);
480 if (isinf (r) || isnan (r))
482 programming_error (INFINITY_MSG);
485 me->dim_cache_[a].offset_ +=r;
487 return dim_cache_[a].offset_;
491 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
493 Grob::point_dimension_callback (SCM , SCM)
495 return ly_interval2scm (Interval (0,0));
499 Grob::is_empty (Axis a)const
501 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
502 gh_procedure_p (dim_cache_[a].dimension_));
506 Grob::extent (Grob * refp, Axis a) const
508 Real x = relative_coordinate (refp, a);
511 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
513 if (gh_pair_p (d->dimension_))
515 else if (gh_procedure_p (d->dimension_))
518 FIXME: add doco on types, and should typecheck maybe?
520 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
525 if (!gh_pair_p (d->dimension_))
528 ext = ly_scm2interval (d->dimension_);
530 SCM extra = get_grob_property (a == X_AXIS
537 if (gh_pair_p (extra))
539 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
540 ext[SMALLER] += gh_scm2double (ly_car (extra));
543 extra = get_grob_property (a == X_AXIS
545 : "minimum-Y-extent");
546 if (gh_pair_p (extra))
548 ext.unite (Interval (gh_scm2double (ly_car (extra)),
549 gh_scm2double (ly_cdr (extra))));
558 Find the group-element which has both #this# and #s#
561 Grob::common_refpoint (Grob const* s, Axis a) const
564 I don't like the quadratic aspect of this code, but I see no other
565 way. The largest chain of parents might be 10 high or so, so
566 it shouldn't be a real issue. */
567 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
568 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
577 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
579 for (; gh_pair_p (elist); elist = ly_cdr (elist))
581 Grob * s = unsmob_grob (ly_car (elist));
585 common = common->common_refpoint (s, a);
596 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
598 for (int i = arr.size() ; i--; )
605 common = common->common_refpoint (s, a);
616 SCM meta = get_grob_property ("meta");
617 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
618 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
619 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
623 Grob::add_offset_callback (SCM cb, Axis a)
625 if (!has_offset_callback_b (cb, a))
627 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
628 dim_cache_[a].offsets_left_ ++;
633 Grob::has_extent_callback_b (SCM cb, Axis a)const
635 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
640 Grob::has_offset_callback_b (SCM cb, Axis a)const
642 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
646 Grob::set_extent (SCM dc, Axis a)
648 dim_cache_[a].dimension_ =dc;
652 Grob::set_parent (Grob *g, Axis a)
654 dim_cache_[a].parent_ = g;
657 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
659 Grob::fixup_refpoint (SCM smob)
661 Grob *me = unsmob_grob (smob);
662 for (int a = X_AXIS; a < NO_AXES; a ++)
665 Grob * parent = me->get_parent (ax);
670 if (parent->get_system () != me->get_system () && me->get_system ())
672 Grob * newparent = parent->find_broken_piece (me->get_system ());
673 me->set_parent (newparent, ax);
676 if (Item * i = dynamic_cast<Item*> (me))
678 Item *parenti = dynamic_cast<Item*> (parent);
682 Direction my_dir = i->break_status_dir () ;
683 if (my_dir!= parenti->break_status_dir ())
685 Item *newparent = parenti->find_prebroken_piece (my_dir);
686 me->set_parent (newparent, ax);
695 Grob::warning (String s)const
697 SCM cause = self_scm();
698 while (Grob * g = unsmob_grob (cause))
700 cause = g->get_grob_property ("cause");
703 if (Music *m = unsmob_music (cause))
705 m->origin()->warning (s);
712 Grob::programming_error (String s)const
714 s = "Programming error: " + s;
719 /****************************************************
721 ****************************************************/
725 IMPLEMENT_SMOBS (Grob);
726 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
729 Grob::mark_smob (SCM ses)
731 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
732 scm_gc_mark (s->immutable_property_alist_);
734 for (int a =0 ; a < 2; a++)
736 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
737 scm_gc_mark (s->dim_cache_[a].dimension_);
740 don't mark the parents. The pointers in the mutable property
741 list form two tree like structures (one for X relations, one
742 for Y relations). Marking these can be done in limited stack
743 space. If we add the parents, we will jump between X and Y in
744 an erratic manner, leading to much more recursion depth (and
745 core dumps if we link to pthreads.)
750 scm_gc_mark (s->original_->self_scm ());
752 s->do_derived_mark ();
753 return s->mutable_property_alist_;
757 Grob::print_smob (SCM s, SCM port, scm_print_state *)
759 Grob *sc = (Grob *) ly_cdr (s);
761 scm_puts ("#<Grob ", port);
762 scm_puts ((char *)sc->name ().to_str0 (), port);
765 don't try to print properties, that is too much hassle.
767 scm_puts (" >", port);
772 Grob::do_derived_mark () const
780 Grob::discretionary_processing ()
785 Grob::internal_has_interface (SCM k)
787 SCM ifs = get_grob_property ("interfaces");
789 return scm_memq (k, ifs) != SCM_BOOL_F;
793 /** Return Array of Grobs in SCM list L */
797 Link_array<Grob> arr;
799 for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
802 arr.push (unsmob_grob (e));
809 /** Return SCM list of Grob array A */
811 ly_grobs2scm (Link_array<Grob> a)
814 for (int i = a.size (); i; i--)
815 s = gh_cons (a[i-1]->self_scm (), s);
821 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
823 ADD_INTERFACE (Grob, "grob-interface",
824 "In music notation, lots of symbols are related in some way. You can\n"
825 "think of music notation as a graph where nodes are formed by the\n"
826 "symbols, and the arcs by their relations. A grob is a node in that\n"
827 "graph. The directed edges in the graph are formed by references to\n"
828 "other grobs (i.e. pointers). This big graph of grobs specifies the\n"
829 "notation problem. The solution of this problem is a description of the\n"
830 "printout in closed form, i.e. a list of values. These values are\n"
833 "All grobs have an X and Y-position on the page. These X and Y positions\n"
834 "are stored in a relative format, so they can easily be combined by\n"
835 "stacking them, hanging one grob to the side of another, and coupling\n"
836 "them into a grouping-grob.\n"
838 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
839 "is stored relative to that reference point. For example the X-reference\n"
840 "point of a staccato dot usually is the note head that it applies\n"
841 "to. When the note head is moved, the staccato dot moves along\n"
844 "A grob is often associated with a symbol, but some grobs do not print\n"
845 "any symbols. They take care of grouping objects. For example, there is a\n"
846 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
847 "is also an abstract grob: it only moves around chords, but doesn't print\n"
850 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause "
851 "Y-extent-callback molecule-callback extra-offset spacing-procedure "
852 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
853 "meta layer before-line-breaking-callback "
854 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
855 "minimum-Y-extent transparent");