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"
24 #include "line-of-score.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
43 #define INFINITY_MSG "Infinity or NaN encountered"
45 Grob::Grob (SCM basicprops)
48 fixme: default should be no callback.
54 immutable_property_alist_ = basicprops;
55 mutable_property_alist_ = SCM_EOL;
62 destill this into a function, so we can re-init the immutable
63 properties with a new BASICPROPS value after creation. Convenient
64 eg. when using \override with StaffSymbol. */
66 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
67 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
69 for (int a = X_AXIS; a <= Y_AXIS; a++)
71 SCM l = get_grob_property (onames[a]);
73 if (scm_ilength (l) >=0)
75 dim_cache_[a].offset_callbacks_ = l;
76 dim_cache_[a].offsets_left_ = scm_ilength (l);
80 programming_error ("[XY]-offset-callbacks must be a list");
83 SCM cb = get_grob_property (enames[a]);
86 Should change default to be empty?
88 if (cb != SCM_BOOL_F && !gh_procedure_p (cb) && !gh_pair_p (cb))
89 cb = molecule_extent_proc;
91 dim_cache_[a].dimension_ = cb;
94 SCM meta = get_grob_property ("meta");
97 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
99 set_grob_property ("interfaces",ly_cdr (ifs));
103 Grob::Grob (Grob const&s)
104 : dim_cache_ (s.dim_cache_)
106 original_l_ = (Grob*) &s;
107 immutable_property_alist_ = s.immutable_property_alist_;
108 mutable_property_alist_ = SCM_EOL;
110 status_c_ = s.status_c_;
111 pscore_l_ = s.pscore_l_;
119 do nothing scm-ish and no unprotecting here.
125 Grob::internal_get_grob_property (SCM sym) const
127 SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
131 s = scm_sloppy_assq (sym, immutable_property_alist_);
132 return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s);
136 Remove the value associated with KEY, and return it. The result is
137 that a next call will yield SCM_EOL (and not the underlying
141 Grob::remove_grob_property (const char* key)
143 SCM val = get_grob_property (key);
145 set_grob_property (key, SCM_EOL);
152 Puts the k, v in the immutable_property_alist_, which is convenient for
153 storing variables that are needed during the breaking process. (eg.
157 Grob::set_immutable_grob_property (const char*k, SCM v)
159 SCM s = ly_symbol2scm (k);
160 set_immutable_grob_property (s, v);
164 Grob::set_immutable_grob_property (SCM s, SCM v)
166 immutable_property_alist_ = gh_cons (gh_cons (s,v), mutable_property_alist_);
167 mutable_property_alist_ = scm_assq_remove_x (mutable_property_alist_, s);
172 Grob::internal_set_grob_property (SCM s, SCM v)
175 if (internal_type_checking_global_b)
176 assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
180 mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
184 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
186 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
188 Grob *s = unsmob_grob (element_smob);
189 Axis a = (Axis) gh_scm2int (scm_axis);
191 Molecule *m = s->get_molecule ();
195 return ly_interval2scm (e);
198 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
201 Grob::preset_extent (SCM element_smob, SCM scm_axis)
203 Grob *s = unsmob_grob (element_smob);
204 Axis a = (Axis) gh_scm2int (scm_axis);
206 SCM ext = s->get_grob_property ((a == X_AXIS)
212 Real l = gh_scm2double (ly_car (ext));
213 Real r = gh_scm2double (ly_cdr (ext));
214 return ly_interval2scm (Interval (l, r));
217 return ly_interval2scm (Interval ());
223 Grob::paper_l () const
225 return pscore_l_ ? pscore_l_->paper_l_ : 0;
229 Grob::calculate_dependencies (int final, int busy, SCM funcname)
231 if (status_c_ >= final)
234 if (status_c_== busy)
236 programming_error ("Element is busy, come back later");
242 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
245 unsmob_grob (ly_car (d))
246 ->calculate_dependencies (final, busy, funcname);
250 String s = ly_symbol2string (funcname);
251 SCM proc = get_grob_property (s.ch_C ());
252 if (gh_procedure_p (proc))
253 gh_call1 (proc, this->self_scm ());
254 else if (gh_list_p (proc))
255 for (SCM i = proc; gh_pair_p (i); i = ly_cdr (i))
256 gh_call1 (ly_car (i), this->self_scm ());
262 Grob::get_molecule () const
264 SCM mol = get_grob_property ("molecule");
265 if (unsmob_molecule (mol))
266 return unsmob_molecule (mol);
268 mol = get_uncached_molecule ();
270 Grob *me = (Grob*)this;
271 me->set_grob_property ("molecule", mol);
273 return unsmob_molecule (mol);
276 Grob::get_uncached_molecule ()const
278 SCM proc = get_grob_property ("molecule-callback");
281 if (gh_procedure_p (proc))
282 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
285 Molecule *m = unsmob_molecule (mol);
287 if (unsmob_molecule (mol))
289 SCM origin = ly_symbol2scm ("no-origin");
291 if (store_locations_global_b){
292 SCM cause = get_grob_property ("cause");
293 if (Music*m = unsmob_music (cause))
295 SCM music_origin = m->get_mus_property ("origin");
296 if (unsmob_input (music_origin))
297 origin = music_origin;
303 mol = Molecule (m->extent_box (),
304 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
307 m = unsmob_molecule (mol);
311 transparent retains dimensions of element.
313 if (m && to_boolean (get_grob_property ("transparent")))
314 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
325 Grob::do_break_processing ()
335 Grob::line_l () const
341 Grob::add_dependency (Grob*e)
345 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
348 programming_error ("Null dependency added");
355 Do break substitution in S, using CRITERION. Return new value.
356 CRITERION is either a SMOB pointer to the desired line, or a number
357 representing the break direction. Do not modify SRC.
359 It is rather tightly coded, since it takes a lot of time; it is
360 one of the top functions in the profile.
364 Grob::handle_broken_grobs (SCM src, SCM criterion)
367 Grob *sc = unsmob_grob (src);
370 if (SCM_INUMP (criterion))
372 Item * i = dynamic_cast<Item*> (sc);
373 Direction d = to_dir (criterion);
374 if (i && i->break_status_dir () != d)
376 Item *br = i->find_prebroken_piece (d);
377 return (br) ? br->self_scm () : SCM_UNDEFINED;
383 = dynamic_cast<System*> (unsmob_grob (criterion));
384 if (sc->line_l () != line)
386 sc = sc->find_broken_piece (line);
390 /* now: !sc || (sc && sc->line_l () == line) */
392 return SCM_UNDEFINED;
394 /* now: sc && sc->line_l () == line */
396 return sc->self_scm();
398 This was introduced in 1.3.49 as a measure to prevent
399 programming errors. It looks expensive (?). TODO:
400 benchmark , document when (what kind of programming
401 errors) this happens.
403 if (sc->common_refpoint (line, X_AXIS)
404 && sc->common_refpoint (line, Y_AXIS))
406 return sc->self_scm ();
408 return SCM_UNDEFINED;
411 else if (ly_pair_p (src)) // SCM_CONSP (src)) // huh?
413 SCM oldcar =ly_car (src);
415 UGH! breaks on circular lists.
417 SCM newcar = handle_broken_grobs (oldcar, criterion);
418 SCM oldcdr = ly_cdr (src);
420 if (newcar == SCM_UNDEFINED
421 && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
424 This is tail-recursion, ie.
426 return handle_broken_grobs (cdr, criterion);
428 We don't want to rely on the compiler to do this. Without
429 tail-recursion, this easily crashes with a stack overflow. */
434 SCM newcdr = handle_broken_grobs (oldcdr, criterion);
435 return scm_cons (newcar, newcdr);
444 Grob::handle_broken_dependencies ()
446 Spanner * s= dynamic_cast<Spanner*> (this);
447 if (original_l_ && s)
452 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
454 Grob * sc = s->broken_into_l_arr_[i];
455 System * l = sc->line_l ();
456 sc->mutable_property_alist_ =
457 handle_broken_grobs (mutable_property_alist_,
458 l ? l->self_scm () : SCM_UNDEFINED);
463 System *line = line_l ();
465 if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
467 mutable_property_alist_
468 = handle_broken_grobs (mutable_property_alist_,
469 line ? line->self_scm () : SCM_UNDEFINED);
471 else if (dynamic_cast <System*> (this))
473 mutable_property_alist_ = handle_broken_grobs (mutable_property_alist_,
479 This element is `invalid'; it has been removed from all
480 dependencies, so let's junk the element itself.
482 do not do this for System, since that would remove
483 references to the originals of score-elts, which get then GC'd
491 Note that we still want references to this element to be
492 rearranged, and not silently thrown away, so we keep pointers
493 like {broken_into_{drul,array}, original}
498 mutable_property_alist_ = SCM_EOL;
499 immutable_property_alist_ = SCM_EOL;
501 set_extent_callback (SCM_EOL, Y_AXIS);
502 set_extent_callback (SCM_EOL, X_AXIS);
504 for (int a= X_AXIS; a <= Y_AXIS; a++)
506 dim_cache_[a].offset_callbacks_ = SCM_EOL;
507 dim_cache_[a].offsets_left_ = 0;
512 Grob::handle_prebroken_dependencies ()
517 Grob::find_broken_piece (System*) const
523 Grob::translate_axis (Real y, Axis a)
525 if (isinf (y) || isnan (y))
526 programming_error (_ (INFINITY_MSG));
529 dim_cache_[a].offset_ += y;
534 Grob::relative_coordinate (Grob const*refp, Axis a) const
540 We catch PARENT_L_ == nil case with this, but we crash if we did
541 not ask for the absolute coordinate (ie. REFP == nil.)
544 if (refp == dim_cache_[a].parent_l_)
545 return get_offset (a);
547 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
551 Grob::get_offset (Axis a) const
553 Grob *me = (Grob*) this;
554 while (dim_cache_[a].offsets_left_)
556 int l = --me->dim_cache_[a].offsets_left_;
557 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
558 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
560 Real r = gh_scm2double (retval);
561 if (isinf (r) || isnan (r))
563 programming_error (INFINITY_MSG);
566 me->dim_cache_[a].offset_ +=r;
568 return dim_cache_[a].offset_;
572 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
574 Grob::point_dimension_callback (SCM , SCM)
576 return ly_interval2scm (Interval (0,0));
580 Grob::empty_b (Axis a)const
582 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
583 gh_procedure_p (dim_cache_[a].dimension_));
594 Grob::extent (Grob * refp, Axis a) const
596 Real x = relative_coordinate (refp, a);
599 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
601 if (gh_pair_p (d->dimension_))
603 else if (gh_procedure_p (d->dimension_))
606 FIXME: add doco on types, and should typecheck maybe?
608 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
613 if (!gh_pair_p (d->dimension_))
616 ext = ly_scm2interval (d->dimension_);
618 SCM extra = get_grob_property (a == X_AXIS
625 if (gh_pair_p (extra))
627 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
628 ext[SMALLER] += gh_scm2double (ly_car (extra));
631 extra = get_grob_property (a == X_AXIS
633 : "minimum-extent-Y");
634 if (gh_pair_p (extra))
636 ext.unite (Interval (gh_scm2double (ly_car (extra)),
637 gh_scm2double (ly_cdr (extra))));
646 Grob::common_refpoint (Grob const* s, Axis a) const
649 I don't like the quadratic aspect of this code, but I see no other
650 way. The largest chain of parents might be 10 high or so, so
651 it shouldn't be a real issue. */
652 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
653 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
662 Grob::common_refpoint (SCM elist, Axis a) const
664 Grob * common = (Grob*) this;
665 for (; gh_pair_p (elist); elist = ly_cdr (elist))
667 Grob * s = unsmob_grob (ly_car (elist));
669 common = common->common_refpoint (s, a);
678 SCM meta = get_grob_property ("meta");
679 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
680 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
681 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
685 Grob::add_offset_callback (SCM cb, Axis a)
687 if (!has_offset_callback_b (cb, a))
689 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
690 dim_cache_[a].offsets_left_ ++;
695 Grob::has_extent_callback_b (SCM cb, Axis a)const
697 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
702 Grob::has_extent_callback_b (Axis a) const
704 return gh_procedure_p (dim_cache_[a].dimension_);
708 Grob::has_offset_callback_b (SCM cb, Axis a)const
710 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
714 Grob::set_extent_callback (SCM dc, Axis a)
716 dim_cache_[a].dimension_ =dc;
720 Grob::set_parent (Grob *g, Axis a)
722 dim_cache_[a].parent_l_ = g;
725 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
727 Grob::fixup_refpoint (SCM smob)
729 Grob *me = unsmob_grob (smob);
730 for (int a = X_AXIS; a < NO_AXES; a ++)
733 Grob * parent = me->get_parent (ax);
738 if (parent->line_l () != me->line_l () && me->line_l ())
740 Grob * newparent = parent->find_broken_piece (me->line_l ());
741 me->set_parent (newparent, ax);
744 if (Item * i = dynamic_cast<Item*> (me))
746 Item *parenti = dynamic_cast<Item*> (parent);
750 Direction my_dir = i->break_status_dir () ;
751 if (my_dir!= parenti->break_status_dir ())
753 Item *newparent = parenti->find_prebroken_piece (my_dir);
754 me->set_parent (newparent, ax);
763 Grob::warning (String s)
765 SCM cause = self_scm();
766 while (cause != SCM_EOL && !unsmob_music (cause))
768 Grob * g = unsmob_grob (cause);
769 cause = g->get_grob_property ("cause");
772 if (Music *m = unsmob_music (cause))
774 m->origin()->warning (s);
782 /****************************************************
784 ****************************************************/
788 IMPLEMENT_SMOBS (Grob);
789 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
792 Grob::mark_smob (SCM ses)
794 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
795 scm_gc_mark (s->immutable_property_alist_);
796 scm_gc_mark (s->mutable_property_alist_);
798 for (int a =0 ; a < 2; a++)
800 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
801 scm_gc_mark (s->dim_cache_[a].dimension_);
802 Grob *p = s->get_parent (Y_AXIS);
804 scm_gc_mark (p->self_scm ());
808 scm_gc_mark (s->original_l_->self_scm ());
810 return s->do_derived_mark ();
814 Grob::print_smob (SCM s, SCM port, scm_print_state *)
816 Grob *sc = (Grob *) ly_cdr (s);
818 scm_puts ("#<Grob ", port);
819 scm_puts ((char *)sc->name ().ch_C (), port);
822 don't try to print properties, that is too much hassle.
824 scm_puts (" >", port);
829 Grob::do_derived_mark ()
836 ly_set_grob_property (SCM elt, SCM sym, SCM val)
838 Grob * sc = unsmob_grob (elt);
839 SCM_ASSERT_TYPE(sc, elt, SCM_ARG1, __FUNCTION__, "grob");
840 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
842 if (!type_check_assignment (sym, val, ly_symbol2scm ("backend-type?")))
843 error ("typecheck failed");
845 sc->internal_set_grob_property (sym, val);
846 return SCM_UNSPECIFIED;
851 ly_get_grob_property (SCM elt, SCM sym)
853 Grob * sc = unsmob_grob (elt);
854 SCM_ASSERT_TYPE(sc, elt, SCM_ARG1, __FUNCTION__, "grob");
855 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
857 return sc->internal_get_grob_property (sym);
862 Grob::discretionary_processing ()
869 spanner_get_bound (SCM slur, SCM dir)
871 Spanner * sl = dynamic_cast<Spanner*> (unsmob_grob (slur));
872 SCM_ASSERT_TYPE(sl, slur, SCM_ARG1, __FUNCTION__, "spanner grob");
873 SCM_ASSERT_TYPE(ly_dir_p (dir), slur, SCM_ARG2, __FUNCTION__, "dir");
874 return sl->get_bound (to_dir (dir))->self_scm ();
878 ly_get_paper_var (SCM grob, SCM sym)
880 Grob * sc = unsmob_grob (grob);
881 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
882 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
884 return sc->paper_l() ->get_scmvar_scm (sym);
890 ly_get_extent (SCM grob, SCM refp, SCM axis)
892 Grob * sc = unsmob_grob (grob);
893 Grob * ref = unsmob_grob (refp);
894 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
895 SCM_ASSERT_TYPE(ref, refp, SCM_ARG2, __FUNCTION__, "grob");
897 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG3, __FUNCTION__, "axis");
899 return ly_interval2scm ( sc->extent (ref, Axis (gh_scm2int (axis))));
903 ly_get_parent (SCM grob, SCM axis)
905 Grob * sc = unsmob_grob (grob);
906 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
907 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG2, __FUNCTION__, "axis");
909 return sc->get_parent (Axis (gh_scm2int (axis)))->self_scm();
916 scm_c_define_gsubr ("ly-get-grob-property", 2, 0, 0,
917 (Scheme_function_unknown)ly_get_grob_property);
918 scm_c_define_gsubr ("ly-set-grob-property", 3, 0, 0,
919 (Scheme_function_unknown)ly_set_grob_property);
920 scm_c_define_gsubr ("ly-get-spanner-bound", 2 , 0, 0,
921 (Scheme_function_unknown) spanner_get_bound);
922 scm_c_define_gsubr ("ly-get-extent", 3, 0, 0,
923 (Scheme_function_unknown) ly_get_extent);
924 scm_c_define_gsubr ("ly-get-parent", 2, 0, 0,
925 (Scheme_function_unknown) ly_get_parent);
929 Grob::has_interface (SCM k)
931 SCM ifs = get_grob_property ("interfaces");
933 return scm_memq (k, ifs) != SCM_BOOL_F;
937 Grob::set_interface (SCM k)
939 if (has_interface (k))
943 set_grob_property ("interfaces",
944 gh_cons (k, get_grob_property ("interfaces")));
949 ADD_SCM_INIT_FUNC (scoreelt, init_functions);
950 IMPLEMENT_TYPE_P (Grob, "ly-grob?");