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
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;
60 SCM meta = get_grob_property ("meta");
63 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
66 do it directly to bypass interface checks.
68 mutable_property_alist_ = gh_cons (gh_cons (ly_symbol2scm ("interfaces"),
70 mutable_property_alist_);
76 destill this into a function, so we can re-init the immutable
77 properties with a new BASICPROPS value after creation. Convenient
78 eg. when using \override with StaffSymbol. */
80 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
81 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
83 for (int a = X_AXIS; a <= Y_AXIS; a++)
85 SCM l = get_grob_property (onames[a]);
87 if (scm_ilength (l) >=0)
89 dim_cache_[a].offset_callbacks_ = l;
90 dim_cache_[a].offsets_left_ = scm_ilength (l);
94 programming_error ("[XY]-offset-callbacks must be a list");
97 SCM cb = get_grob_property (enames[a]);
100 Should change default to be empty?
103 && !gh_procedure_p (cb) && !gh_pair_p (cb)
104 && gh_procedure_p (get_grob_property ("molecule-callback"))
106 cb = molecule_extent_proc;
108 dim_cache_[a].dimension_ = cb;
113 Grob::Grob (Grob const&s)
114 : dim_cache_ (s.dim_cache_)
116 original_l_ = (Grob*) &s;
117 immutable_property_alist_ = s.immutable_property_alist_;
118 mutable_property_alist_ = SCM_EOL;
120 status_c_ = s.status_c_;
121 pscore_l_ = s.pscore_l_;
129 do nothing scm-ish and no unprotecting here.
135 extern void check_interfaces_for_property (Grob const *me, SCM sym);
138 Grob::internal_set_grob_property (SCM s, SCM v)
141 if (internal_type_checking_global_b)
143 assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
144 check_interfaces_for_property(this, s);
149 mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
154 Grob::internal_get_grob_property (SCM sym) const
156 SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
160 s = scm_sloppy_assq (sym, immutable_property_alist_);
163 if (internal_type_checking_global_b && gh_pair_p (s))
165 assert (type_check_assignment (sym, gh_cdr (s), ly_symbol2scm ("backend-type?")));
166 check_interfaces_for_property(this, sym);
170 return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s);
174 Remove the value associated with KEY, and return it. The result is
175 that a next call will yield SCM_EOL (and not the underlying
179 Grob::remove_grob_property (const char* key)
181 SCM val = get_grob_property (key);
183 set_grob_property (key, SCM_EOL);
189 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
191 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
193 Grob *s = unsmob_grob (element_smob);
194 Axis a = (Axis) gh_scm2int (scm_axis);
196 Molecule *m = s->get_molecule ();
200 return ly_interval2scm (e);
203 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
206 Grob::preset_extent (SCM element_smob, SCM scm_axis)
208 Grob *s = unsmob_grob (element_smob);
209 Axis a = (Axis) gh_scm2int (scm_axis);
211 SCM ext = s->get_grob_property ((a == X_AXIS)
217 Real l = gh_scm2double (ly_car (ext));
218 Real r = gh_scm2double (ly_cdr (ext));
219 return ly_interval2scm (Interval (l, r));
222 return ly_interval2scm (Interval ());
228 Grob::paper_l () const
230 return pscore_l_ ? pscore_l_->paper_l_ : 0;
234 Grob::calculate_dependencies (int final, int busy, SCM funcname)
236 if (status_c_ >= final)
239 if (status_c_== busy)
241 programming_error ("Element is busy, come back later");
247 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
250 unsmob_grob (ly_car (d))
251 ->calculate_dependencies (final, busy, funcname);
255 SCM proc = internal_get_grob_property (funcname);
256 if (gh_procedure_p (proc))
257 gh_call1 (proc, this->self_scm ());
263 Grob::get_molecule () const
265 if (immutable_property_alist_ == SCM_EOL)
271 SCM mol = get_grob_property ("molecule");
272 if (unsmob_molecule (mol))
273 return unsmob_molecule (mol);
275 mol = get_uncached_molecule ();
277 Grob *me = (Grob*)this;
278 me->set_grob_property ("molecule", mol);
280 return unsmob_molecule (mol);
283 Grob::get_uncached_molecule ()const
285 SCM proc = get_grob_property ("molecule-callback");
288 if (gh_procedure_p (proc))
289 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
292 Molecule *m = unsmob_molecule (mol);
294 if (unsmob_molecule (mol))
296 SCM origin = ly_symbol2scm ("no-origin");
298 if (store_locations_global_b){
299 SCM cause = get_grob_property ("cause");
300 if (Music*m = unsmob_music (cause))
302 SCM music_origin = m->get_mus_property ("origin");
303 if (unsmob_input (music_origin))
304 origin = music_origin;
310 mol = Molecule (m->extent_box (),
311 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
314 m = unsmob_molecule (mol);
318 transparent retains dimensions of element.
320 if (m && to_boolean (get_grob_property ("transparent")))
321 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
332 Grob::do_break_processing ()
342 Grob::line_l () const
348 Grob::add_dependency (Grob*e)
352 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
355 programming_error ("Null dependency added");
362 Do break substitution in S, using CRITERION. Return new value.
363 CRITERION is either a SMOB pointer to the desired line, or a number
364 representing the break direction. Do not modify SRC.
366 It is rather tightly coded, since it takes a lot of time; it is
367 one of the top functions in the profile.
371 Grob::handle_broken_grobs (SCM src, SCM criterion)
374 Grob *sc = unsmob_grob (src);
377 if (SCM_INUMP (criterion))
379 Item * i = dynamic_cast<Item*> (sc);
380 Direction d = to_dir (criterion);
381 if (i && i->break_status_dir () != d)
383 Item *br = i->find_prebroken_piece (d);
384 return (br) ? br->self_scm () : SCM_UNDEFINED;
390 = dynamic_cast<System*> (unsmob_grob (criterion));
391 if (sc->line_l () != line)
393 sc = sc->find_broken_piece (line);
397 /* now: !sc || (sc && sc->line_l () == line) */
399 return SCM_UNDEFINED;
401 /* now: sc && sc->line_l () == line */
403 return sc->self_scm();
405 This was introduced in 1.3.49 as a measure to prevent
406 programming errors. It looks expensive (?).
410 benchmark , document when (what kind of programming
411 errors) this happens.
413 if (sc->common_refpoint (line, X_AXIS)
414 && sc->common_refpoint (line, Y_AXIS))
416 return sc->self_scm ();
418 return SCM_UNDEFINED;
421 else if (ly_pair_p (src)) // SCM_CONSP (src)) // huh?
423 SCM oldcar =ly_car (src);
425 UGH! breaks on circular lists.
427 SCM newcar = handle_broken_grobs (oldcar, criterion);
428 SCM oldcdr = ly_cdr (src);
430 if (newcar == SCM_UNDEFINED
431 && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
434 This is tail-recursion, ie.
436 return handle_broken_grobs (cdr, criterion);
438 We don't want to rely on the compiler to do this. Without
439 tail-recursion, this easily crashes with a stack overflow. */
444 SCM newcdr = handle_broken_grobs (oldcdr, criterion);
445 return scm_cons (newcar, newcdr);
454 Grob::handle_broken_dependencies ()
456 Spanner * s= dynamic_cast<Spanner*> (this);
457 if (original_l_ && s)
462 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
464 Grob * sc = s->broken_into_l_arr_[i];
465 System * l = sc->line_l ();
466 sc->mutable_property_alist_ =
467 handle_broken_grobs (mutable_property_alist_,
468 l ? l->self_scm () : SCM_UNDEFINED);
473 System *line = line_l ();
475 if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
477 mutable_property_alist_
478 = handle_broken_grobs (mutable_property_alist_,
479 line ? line->self_scm () : SCM_UNDEFINED);
481 else if (dynamic_cast <System*> (this))
483 mutable_property_alist_ = handle_broken_grobs (mutable_property_alist_,
489 This element is `invalid'; it has been removed from all
490 dependencies, so let's junk the element itself.
492 do not do this for System, since that would remove
493 references to the originals of score-grobs, which get then GC'd
501 Note that we still want references to this element to be
502 rearranged, and not silently thrown away, so we keep pointers
503 like {broken_into_{drul,array}, original}
508 mutable_property_alist_ = SCM_EOL;
509 immutable_property_alist_ = SCM_EOL;
511 set_extent (SCM_EOL, Y_AXIS);
512 set_extent (SCM_EOL, X_AXIS);
514 for (int a= X_AXIS; a <= Y_AXIS; a++)
516 dim_cache_[a].offset_callbacks_ = SCM_EOL;
517 dim_cache_[a].offsets_left_ = 0;
522 Grob::handle_prebroken_dependencies ()
527 Grob::find_broken_piece (System*) const
533 Grob::translate_axis (Real y, Axis a)
535 if (isinf (y) || isnan (y))
536 programming_error (_ (INFINITY_MSG));
539 dim_cache_[a].offset_ += y;
544 Grob::relative_coordinate (Grob const*refp, Axis a) const
550 We catch PARENT_L_ == nil case with this, but we crash if we did
551 not ask for the absolute coordinate (ie. REFP == nil.)
554 if (refp == dim_cache_[a].parent_l_)
555 return get_offset (a);
557 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
561 Grob::get_offset (Axis a) const
563 Grob *me = (Grob*) this;
564 while (dim_cache_[a].offsets_left_)
566 int l = --me->dim_cache_[a].offsets_left_;
567 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
568 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
570 Real r = gh_scm2double (retval);
571 if (isinf (r) || isnan (r))
573 programming_error (INFINITY_MSG);
576 me->dim_cache_[a].offset_ +=r;
578 return dim_cache_[a].offset_;
582 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
584 Grob::point_dimension_callback (SCM , SCM)
586 return ly_interval2scm (Interval (0,0));
590 Grob::empty_b (Axis a)const
592 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
593 gh_procedure_p (dim_cache_[a].dimension_));
597 Grob::extent (Grob * refp, Axis a) const
599 Real x = relative_coordinate (refp, a);
602 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
604 if (gh_pair_p (d->dimension_))
606 else if (gh_procedure_p (d->dimension_))
609 FIXME: add doco on types, and should typecheck maybe?
611 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
616 if (!gh_pair_p (d->dimension_))
619 ext = ly_scm2interval (d->dimension_);
621 SCM extra = get_grob_property (a == X_AXIS
628 if (gh_pair_p (extra))
630 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
631 ext[SMALLER] += gh_scm2double (ly_car (extra));
634 extra = get_grob_property (a == X_AXIS
636 : "minimum-extent-Y");
637 if (gh_pair_p (extra))
639 ext.unite (Interval (gh_scm2double (ly_car (extra)),
640 gh_scm2double (ly_cdr (extra))));
649 Grob::common_refpoint (Grob const* s, Axis a) const
652 I don't like the quadratic aspect of this code, but I see no other
653 way. The largest chain of parents might be 10 high or so, so
654 it shouldn't be a real issue. */
655 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
656 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
665 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
667 for (; gh_pair_p (elist); elist = ly_cdr (elist))
669 Grob * s = unsmob_grob (ly_car (elist));
673 common = common->common_refpoint (s, a);
684 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
686 for (int i = arr.size() ; i--; )
693 common = common->common_refpoint (s, a);
704 SCM meta = get_grob_property ("meta");
705 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
706 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
707 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
711 Grob::add_offset_callback (SCM cb, Axis a)
713 if (!has_offset_callback_b (cb, a))
715 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
716 dim_cache_[a].offsets_left_ ++;
721 Grob::has_extent_callback_b (SCM cb, Axis a)const
723 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
728 Grob::has_offset_callback_b (SCM cb, Axis a)const
730 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
734 Grob::set_extent (SCM dc, Axis a)
736 dim_cache_[a].dimension_ =dc;
740 Grob::set_parent (Grob *g, Axis a)
742 dim_cache_[a].parent_l_ = g;
745 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
747 Grob::fixup_refpoint (SCM smob)
749 Grob *me = unsmob_grob (smob);
750 for (int a = X_AXIS; a < NO_AXES; a ++)
753 Grob * parent = me->get_parent (ax);
758 if (parent->line_l () != me->line_l () && me->line_l ())
760 Grob * newparent = parent->find_broken_piece (me->line_l ());
761 me->set_parent (newparent, ax);
764 if (Item * i = dynamic_cast<Item*> (me))
766 Item *parenti = dynamic_cast<Item*> (parent);
770 Direction my_dir = i->break_status_dir () ;
771 if (my_dir!= parenti->break_status_dir ())
773 Item *newparent = parenti->find_prebroken_piece (my_dir);
774 me->set_parent (newparent, ax);
783 Grob::warning (String s)
785 SCM cause = self_scm();
786 while (cause != SCM_EOL && !unsmob_music (cause))
788 Grob * g = unsmob_grob (cause);
789 cause = g->get_grob_property ("cause");
792 if (Music *m = unsmob_music (cause))
794 m->origin()->warning (s);
802 /****************************************************
804 ****************************************************/
808 IMPLEMENT_SMOBS (Grob);
809 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
812 Grob::mark_smob (SCM ses)
814 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
815 scm_gc_mark (s->immutable_property_alist_);
816 scm_gc_mark (s->mutable_property_alist_);
818 for (int a =0 ; a < 2; a++)
820 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
821 scm_gc_mark (s->dim_cache_[a].dimension_);
822 Grob *p = s->get_parent (Y_AXIS);
824 scm_gc_mark (p->self_scm ());
828 scm_gc_mark (s->original_l_->self_scm ());
830 return s->do_derived_mark ();
834 Grob::print_smob (SCM s, SCM port, scm_print_state *)
836 Grob *sc = (Grob *) ly_cdr (s);
838 scm_puts ("#<Grob ", port);
839 scm_puts ((char *)sc->name ().ch_C (), port);
842 don't try to print properties, that is too much hassle.
844 scm_puts (" >", port);
849 Grob::do_derived_mark ()
854 LY_DEFINE(ly_set_grob_property,"ly-set-grob-property", 3, 0, 0,
855 (SCM grob, SCM sym, SCM val),
857 Set @var{sym} in grob @var{grob} to value @var{val}")
859 Grob * sc = unsmob_grob (grob);
860 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
861 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
863 if (!type_check_assignment (sym, val, ly_symbol2scm ("backend-type?")))
864 error ("typecheck failed");
866 sc->internal_set_grob_property (sym, val);
867 return SCM_UNSPECIFIED;
870 LY_DEFINE(ly_get_grob_property,
871 "ly-get-grob-property", 2, 0, 0, (SCM grob, SCM sym),
872 " Get the value of a value in grob @var{g} of property @var{sym}. It
873 will return @code{'()} (end-of-list) if @var{g} doesn't have @var{sym} set.
876 Grob * sc = unsmob_grob (grob);
877 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
878 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
880 return sc->internal_get_grob_property (sym);
885 Grob::discretionary_processing ()
890 LY_DEFINE(spanner_get_bound, "ly-get-spanner-bound", 2 , 0, 0,
892 "Get one of the bounds of @var{spanner}. @var{dir} may be @code{-1} for
893 left, and @code{1} for right.
896 Spanner * sl = dynamic_cast<Spanner*> (unsmob_grob (slur));
897 SCM_ASSERT_TYPE(sl, slur, SCM_ARG1, __FUNCTION__, "spanner grob");
898 SCM_ASSERT_TYPE(ly_dir_p (dir), slur, SCM_ARG2, __FUNCTION__, "dir");
899 return sl->get_bound (to_dir (dir))->self_scm ();
902 LY_DEFINE(ly_get_paper_var,"ly-get-paper-variable", 2, 0, 0,
904 "Get a variable from the \\paper block.")
906 Grob * sc = unsmob_grob (grob);
907 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
908 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
910 return sc->paper_l() ->get_scmvar_scm (sym);
915 LY_DEFINE(ly_get_extent, "ly-get-extent", 3, 0, 0,
916 (SCM grob, SCM refp, SCM axis),
917 "Get the extent in @var{axis} direction of @var{grob} relative to the
920 Grob * sc = unsmob_grob (grob);
921 Grob * ref = unsmob_grob (refp);
922 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
923 SCM_ASSERT_TYPE(ref, refp, SCM_ARG2, __FUNCTION__, "grob");
925 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG3, __FUNCTION__, "axis");
927 return ly_interval2scm ( sc->extent (ref, Axis (gh_scm2int (axis))));
930 LY_DEFINE (ly_get_parent, "ly-get-parent", 2, 0, 0, (SCM grob, SCM axis),
931 "Get the parent of @var{grob}. @var{axis} can be 0 for the X-axis, 1
934 Grob * sc = unsmob_grob (grob);
935 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
936 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG2, __FUNCTION__, "axis");
938 return sc->get_parent (Axis (gh_scm2int (axis)))->self_scm();
943 Grob::internal_has_interface (SCM k)
945 SCM ifs = get_grob_property ("interfaces");
947 return scm_memq (k, ifs) != SCM_BOOL_F;
950 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
952 ADD_INTERFACE (Grob, "grob-interface",
953 "All grobs support this",
954 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
955 Y-extent-callback molecule-callback extra-offset
957 staff-symbol interfaces dependencies extra-extent-X causes meta
958 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");