2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2003 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_;
129 mutable_property_alist_ = SCM_EOL;
132 No properties are copied. That is the job of handle_broken_dependencies.
140 #ifdef HASHING_FOR_MUTABLE_PROPS
141 mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
148 do nothing scm-ish and no unprotecting here.
156 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
158 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
160 Grob *s = unsmob_grob (element_smob);
161 Axis a = (Axis) gh_scm2int (scm_axis);
163 Molecule *m = s->get_molecule ();
167 return ly_interval2scm (e);
170 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
172 Grob::preset_extent (SCM element_smob, SCM scm_axis)
174 Grob *s = unsmob_grob (element_smob);
175 Axis a = (Axis) gh_scm2int (scm_axis);
177 SCM ext = s->get_grob_property ((a == X_AXIS)
183 Real l = gh_scm2double (ly_car (ext));
184 Real r = gh_scm2double (ly_cdr (ext));
185 return ly_interval2scm (Interval (l, r));
188 return ly_interval2scm (Interval ());
194 Grob::get_paper () const
196 return pscore_ ? pscore_->paper_ : 0;
200 Grob::calculate_dependencies (int final, int busy, SCM funcname)
202 if (status_ >= final)
207 programming_error ("Element is busy, come back later");
213 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
216 unsmob_grob (ly_car (d))
217 ->calculate_dependencies (final, busy, funcname);
221 SCM proc = internal_get_grob_property (funcname);
222 if (gh_procedure_p (proc))
223 gh_call1 (proc, this->self_scm ());
229 Grob::get_molecule () const
236 SCM mol = get_grob_property ("molecule");
237 if (unsmob_molecule (mol))
238 return unsmob_molecule (mol);
240 mol = get_uncached_molecule ();
244 Grob *me = (Grob*)this;
245 me->set_grob_property ("molecule", mol);
248 return unsmob_molecule (mol);
252 Grob::get_uncached_molecule ()const
254 SCM proc = get_grob_property ("molecule-callback");
257 if (gh_procedure_p (proc))
258 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
260 Molecule *m = unsmob_molecule (mol);
262 if (unsmob_molecule (mol))
264 SCM origin = ly_symbol2scm ("no-origin");
266 if (store_locations_global_b)
268 SCM cause = get_grob_property ("cause");
269 if (Music*m = unsmob_music (cause))
271 SCM music_origin = m->get_mus_property ("origin");
272 if (unsmob_input (music_origin))
273 origin = music_origin;
279 mol = Molecule (m->extent_box (),
280 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
283 m = unsmob_molecule (mol);
287 transparent retains dimensions of element.
289 if (m && to_boolean (get_grob_property ("transparent")))
290 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
301 Grob::do_break_processing ()
306 Grob::get_system () const
312 Grob::add_dependency (Grob*e)
316 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
319 programming_error ("Null dependency added");
324 Grob::handle_broken_dependencies ()
326 Spanner * sp = dynamic_cast<Spanner*> (this);
332 for (SCM s = mutable_property_alist_; gh_pair_p(s);
335 sp->substitute_one_mutable_property (gh_caar (s),
341 System *system = get_system ();
344 && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
346 substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
347 mutable_property_alist_);
349 else if (dynamic_cast <System*> (this))
351 substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
356 This element is `invalid'; it has been removed from all
357 dependencies, so let's junk the element itself.
359 do not do this for System, since that would remove references
360 to the originals of score-grobs, which get then GC'd (a bad
369 Note that we still want references to this element to be
370 rearranged, and not silently thrown away, so we keep pointers
371 like {broken_into_{drul,array}, original}
383 mutable_property_alist_ = SCM_EOL;
384 immutable_property_alist_ = SCM_EOL;
386 set_extent (SCM_EOL, Y_AXIS);
387 set_extent (SCM_EOL, X_AXIS);
389 for (int a= X_AXIS; a <= Y_AXIS; a++)
391 dim_cache_[a].offset_callbacks_ = SCM_EOL;
392 dim_cache_[a].offsets_left_ = 0;
397 This can make debugging a little easier: we can still know what
398 the object used to be. However, since all its links have been
399 broken, it's usually more convenient to set a conditional
400 breakpoint in GDB before the property lists are wiped.
402 mutable_property_alist_ = scm_acons (ly_symbol2scm ("name"),
403 scm_makfrom0str (nm.to_str0()),
404 mutable_property_alist_
410 Grob::handle_prebroken_dependencies ()
413 Don't do this in the derived method, since we want to keep access to
414 mutable_property_alist_ centralized.
418 Item * it = dynamic_cast<Item*> (this);
419 substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
420 original_->mutable_property_alist_);
425 Grob::find_broken_piece (System*) const
431 translate in one direction
434 Grob::translate_axis (Real y, Axis a)
436 if (isinf (y) || isnan (y))
437 programming_error (_ (INFINITY_MSG));
440 dim_cache_[a].offset_ += y;
446 Find the offset relative to D. If D equals THIS, then it is 0.
447 Otherwise, it recursively defd as
449 OFFSET_ + PARENT_L_->relative_coordinate (D)
452 Grob::relative_coordinate (Grob const*refp, Axis a) const
458 We catch PARENT_L_ == nil case with this, but we crash if we did
459 not ask for the absolute coordinate (ie. REFP == nil.)
462 if (refp == dim_cache_[a].parent_)
463 return get_offset (a);
465 return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
471 Invoke callbacks to get offset relative to parent.
474 Grob::get_offset (Axis a) const
476 Grob *me = (Grob*) this;
477 while (dim_cache_[a].offsets_left_)
479 int l = --me->dim_cache_[a].offsets_left_;
480 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
481 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
483 Real r = gh_scm2double (retval);
484 if (isinf (r) || isnan (r))
486 programming_error (INFINITY_MSG);
489 me->dim_cache_[a].offset_ +=r;
491 return dim_cache_[a].offset_;
495 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
497 Grob::point_dimension_callback (SCM , SCM)
499 return ly_interval2scm (Interval (0,0));
503 Grob::empty_b (Axis a)const
505 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
506 gh_procedure_p (dim_cache_[a].dimension_));
510 Grob::extent (Grob * refp, Axis a) const
512 Real x = relative_coordinate (refp, a);
515 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
517 if (gh_pair_p (d->dimension_))
519 else if (gh_procedure_p (d->dimension_))
522 FIXME: add doco on types, and should typecheck maybe?
524 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
529 if (!gh_pair_p (d->dimension_))
532 ext = ly_scm2interval (d->dimension_);
534 SCM extra = get_grob_property (a == X_AXIS
541 if (gh_pair_p (extra))
543 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
544 ext[SMALLER] += gh_scm2double (ly_car (extra));
547 extra = get_grob_property (a == X_AXIS
549 : "minimum-Y-extent");
550 if (gh_pair_p (extra))
552 ext.unite (Interval (gh_scm2double (ly_car (extra)),
553 gh_scm2double (ly_cdr (extra))));
562 Find the group-element which has both #this# and #s#
565 Grob::common_refpoint (Grob const* s, Axis a) const
568 I don't like the quadratic aspect of this code, but I see no other
569 way. The largest chain of parents might be 10 high or so, so
570 it shouldn't be a real issue. */
571 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
572 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
581 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
583 for (; gh_pair_p (elist); elist = ly_cdr (elist))
585 Grob * s = unsmob_grob (ly_car (elist));
589 common = common->common_refpoint (s, a);
600 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
602 for (int i = arr.size() ; i--; )
609 common = common->common_refpoint (s, a);
620 SCM meta = get_grob_property ("meta");
621 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
622 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
623 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
627 Grob::add_offset_callback (SCM cb, Axis a)
629 if (!has_offset_callback_b (cb, a))
631 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
632 dim_cache_[a].offsets_left_ ++;
637 Grob::has_extent_callback_b (SCM cb, Axis a)const
639 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
644 Grob::has_offset_callback_b (SCM cb, Axis a)const
646 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
650 Grob::set_extent (SCM dc, Axis a)
652 dim_cache_[a].dimension_ =dc;
656 Grob::set_parent (Grob *g, Axis a)
658 dim_cache_[a].parent_ = g;
661 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
663 Grob::fixup_refpoint (SCM smob)
665 Grob *me = unsmob_grob (smob);
666 for (int a = X_AXIS; a < NO_AXES; a ++)
669 Grob * parent = me->get_parent (ax);
674 if (parent->get_system () != me->get_system () && me->get_system ())
676 Grob * newparent = parent->find_broken_piece (me->get_system ());
677 me->set_parent (newparent, ax);
680 if (Item * i = dynamic_cast<Item*> (me))
682 Item *parenti = dynamic_cast<Item*> (parent);
686 Direction my_dir = i->break_status_dir () ;
687 if (my_dir!= parenti->break_status_dir ())
689 Item *newparent = parenti->find_prebroken_piece (my_dir);
690 me->set_parent (newparent, ax);
699 Grob::warning (String s)const
701 SCM cause = self_scm();
702 while (Grob * g = unsmob_grob (cause))
704 cause = g->get_grob_property ("cause");
707 if (Music *m = unsmob_music (cause))
709 m->origin()->warning (s);
716 Grob::programming_error (String s)const
718 s = "Programming error: " + s;
723 /****************************************************
725 ****************************************************/
729 IMPLEMENT_SMOBS (Grob);
730 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
733 Grob::mark_smob (SCM ses)
735 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
736 scm_gc_mark (s->immutable_property_alist_);
738 for (int a =0 ; a < 2; a++)
740 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
741 scm_gc_mark (s->dim_cache_[a].dimension_);
744 don't mark the parents. The pointers in the mutable property
745 list form two tree like structures (one for X relations, one
746 for Y relations). Marking these can be done in limited stack
747 space. If we add the parents, we will jump between X and Y in
748 an erratic manner, leading to much more recursion depth (and
749 core dumps if we link to pthreads.)
754 scm_gc_mark (s->original_->self_scm ());
756 s->do_derived_mark ();
757 return s->mutable_property_alist_;
761 Grob::print_smob (SCM s, SCM port, scm_print_state *)
763 Grob *sc = (Grob *) ly_cdr (s);
765 scm_puts ("#<Grob ", port);
766 scm_puts ((char *)sc->name ().to_str0 (), port);
769 don't try to print properties, that is too much hassle.
771 scm_puts (" >", port);
776 Grob::do_derived_mark () const
784 Grob::discretionary_processing ()
789 Grob::internal_has_interface (SCM k)
791 SCM ifs = get_grob_property ("interfaces");
793 return scm_memq (k, ifs) != SCM_BOOL_F;
797 /** Return Array of Grobs in SCM list L */
801 Link_array<Grob> arr;
803 for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
806 arr.push (unsmob_grob (e));
813 /** Return SCM list of Grob array A */
815 ly_grobs2scm (Link_array<Grob> a)
818 for (int i = a.size (); i; i--)
819 s = gh_cons (a[i-1]->self_scm (), s);
825 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
827 ADD_INTERFACE (Grob, "grob-interface",
828 "In music notation, lots of symbols are related in some way. You can\n"
829 "think of music notation as a graph where nodes are formed by the\n"
830 "symbols, and the arcs by their relations. A grob is a node in that\n"
831 "graph. The directed edges in the graph are formed by references to\n"
832 "other grobs (i.e. pointers). This big graph of grobs specifies the\n"
833 "notation problem. The solution of this problem is a description of the\n"
834 "printout in closed form, i.e. a list of values. These values are\n"
837 "All grobs have an X and Y-position on the page. These X and Y positions\n"
838 "are stored in a relative format, so they can easily be combined by\n"
839 "stacking them, hanging one grob to the side of another, and coupling\n"
840 "them into a grouping-grob.\n"
842 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
843 "is stored relative to that reference point. For example the X-reference\n"
844 "point of a staccato dot usually is the note head that it applies\n"
845 "to. When the note head is moved, the staccato dot moves along\n"
848 "A grob is often associated with a symbol, but some grobs do not print\n"
849 "any symbols. They take care of grouping objects. For example, there is a\n"
850 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
851 "is also an abstract grob: it only moves around chords, but doesn't print\n"
854 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause "
855 "Y-extent-callback molecule-callback extra-offset spacing-procedure "
856 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
857 "meta layer before-line-breaking-callback "
858 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
859 "minimum-Y-extent transparent");