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;
58 We do smobify_self() as the first step. Since the object lives on
59 the heap, none of its SCM variables are protected from GC. After
60 smobify_self(), they are.
65 SCM meta = get_grob_property ("meta");
68 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
71 do it directly to bypass interface checks.
73 mutable_property_alist_ = gh_cons (gh_cons (ly_symbol2scm ("interfaces"),
75 mutable_property_alist_);
81 destill this into a function, so we can re-init the immutable
82 properties with a new BASICPROPS value after creation. Convenient
83 eg. when using \override with StaffSymbol. */
85 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
88 for (int a = X_AXIS; a <= Y_AXIS; a++)
90 SCM l = get_grob_property (onames[a]);
92 if (scm_ilength (l) >=0)
94 dim_cache_[a].offset_callbacks_ = l;
95 dim_cache_[a].offsets_left_ = scm_ilength (l);
99 programming_error ("[XY]-offset-callbacks must be a list");
102 SCM cb = get_grob_property (enames[a]);
105 Should change default to be empty?
108 && !gh_procedure_p (cb) && !gh_pair_p (cb)
109 && gh_procedure_p (get_grob_property ("molecule-callback"))
111 cb = molecule_extent_proc;
113 dim_cache_[a].dimension_ = cb;
118 Grob::Grob (Grob const&s)
119 : dim_cache_ (s.dim_cache_)
121 original_l_ = (Grob*) &s;
122 immutable_property_alist_ = s.immutable_property_alist_;
123 mutable_property_alist_ = SCM_EOL;
126 No properties are copied. That is the job of handle_broken_dependencies.
129 status_c_ = s.status_c_;
130 pscore_l_ = s.pscore_l_;
140 do nothing scm-ish and no unprotecting here.
146 extern void check_interfaces_for_property (Grob const *me, SCM sym);
149 Grob::internal_set_grob_property (SCM s, SCM v)
152 if (internal_type_checking_global_b)
154 assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
155 check_interfaces_for_property(this, s);
160 mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
165 Grob::internal_get_grob_property (SCM sym) const
167 SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
171 s = scm_sloppy_assq (sym, immutable_property_alist_);
174 if (internal_type_checking_global_b && gh_pair_p (s))
176 assert (type_check_assignment (sym, gh_cdr (s), ly_symbol2scm ("backend-type?")));
177 check_interfaces_for_property(this, sym);
181 return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s);
185 Remove the value associated with KEY, and return it. The result is
186 that a next call will yield SCM_EOL (and not the underlying
190 Grob::remove_grob_property (const char* key)
192 SCM val = get_grob_property (key);
194 set_grob_property (key, SCM_EOL);
200 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
202 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
204 Grob *s = unsmob_grob (element_smob);
205 Axis a = (Axis) gh_scm2int (scm_axis);
207 Molecule *m = s->get_molecule ();
211 return ly_interval2scm (e);
214 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
217 Grob::preset_extent (SCM element_smob, SCM scm_axis)
219 Grob *s = unsmob_grob (element_smob);
220 Axis a = (Axis) gh_scm2int (scm_axis);
222 SCM ext = s->get_grob_property ((a == X_AXIS)
228 Real l = gh_scm2double (ly_car (ext));
229 Real r = gh_scm2double (ly_cdr (ext));
230 return ly_interval2scm (Interval (l, r));
233 return ly_interval2scm (Interval ());
239 Grob::paper_l () const
241 return pscore_l_ ? pscore_l_->paper_l_ : 0;
245 Grob::calculate_dependencies (int final, int busy, SCM funcname)
247 if (status_c_ >= final)
250 if (status_c_== busy)
252 programming_error ("Element is busy, come back later");
258 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
261 unsmob_grob (ly_car (d))
262 ->calculate_dependencies (final, busy, funcname);
266 SCM proc = internal_get_grob_property (funcname);
267 if (gh_procedure_p (proc))
268 gh_call1 (proc, this->self_scm ());
274 Grob::get_molecule () const
276 if (immutable_property_alist_ == SCM_EOL)
282 SCM mol = get_grob_property ("molecule");
283 if (unsmob_molecule (mol))
284 return unsmob_molecule (mol);
286 mol = get_uncached_molecule ();
288 Grob *me = (Grob*)this;
289 me->set_grob_property ("molecule", mol);
291 return unsmob_molecule (mol);
294 Grob::get_uncached_molecule ()const
296 SCM proc = get_grob_property ("molecule-callback");
299 if (gh_procedure_p (proc))
300 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
303 Molecule *m = unsmob_molecule (mol);
305 if (unsmob_molecule (mol))
307 SCM origin = ly_symbol2scm ("no-origin");
309 if (store_locations_global_b){
310 SCM cause = get_grob_property ("cause");
311 if (Music*m = unsmob_music (cause))
313 SCM music_origin = m->get_mus_property ("origin");
314 if (unsmob_input (music_origin))
315 origin = music_origin;
321 mol = Molecule (m->extent_box (),
322 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
325 m = unsmob_molecule (mol);
329 transparent retains dimensions of element.
331 if (m && to_boolean (get_grob_property ("transparent")))
332 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
343 Grob::do_break_processing ()
353 Grob::line_l () const
359 Grob::add_dependency (Grob*e)
363 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
366 programming_error ("Null dependency added");
373 Do break substitution in S, using CRITERION. Return new value.
374 CRITERION is either a SMOB pointer to the desired line, or a number
375 representing the break direction. Do not modify SRC.
377 It is rather tightly coded, since it takes a lot of time; it is
378 one of the top functions in the profile.
382 Grob::handle_broken_grobs (SCM src, SCM criterion)
385 Grob *sc = unsmob_grob (src);
388 if (SCM_INUMP (criterion))
390 Item * i = dynamic_cast<Item*> (sc);
391 Direction d = to_dir (criterion);
392 if (i && i->break_status_dir () != d)
394 Item *br = i->find_prebroken_piece (d);
395 return (br) ? br->self_scm () : SCM_UNDEFINED;
401 = dynamic_cast<System*> (unsmob_grob (criterion));
402 if (sc->line_l () != line)
404 sc = sc->find_broken_piece (line);
408 /* now: !sc || (sc && sc->line_l () == line) */
410 return SCM_UNDEFINED;
412 /* now: sc && sc->line_l () == line */
414 return sc->self_scm();
416 This was introduced in 1.3.49 as a measure to prevent
417 programming errors. It looks expensive (?).
421 benchmark , document when (what kind of programming
422 errors) this happens.
424 if (sc->common_refpoint (line, X_AXIS)
425 && sc->common_refpoint (line, Y_AXIS))
427 return sc->self_scm ();
429 return SCM_UNDEFINED;
432 else if (ly_pair_p (src)) // SCM_CONSP (src)) // huh?
434 SCM oldcar =ly_car (src);
436 UGH! breaks on circular lists.
438 SCM newcar = handle_broken_grobs (oldcar, criterion);
439 SCM oldcdr = ly_cdr (src);
441 if (newcar == SCM_UNDEFINED
442 && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
445 This is tail-recursion, ie.
447 return handle_broken_grobs (cdr, criterion);
449 We don't want to rely on the compiler to do this. Without
450 tail-recursion, this easily crashes with a stack overflow. */
455 SCM newcdr = handle_broken_grobs (oldcdr, criterion);
456 return scm_cons (newcar, newcdr);
465 Grob::handle_broken_dependencies ()
467 Spanner * s= dynamic_cast<Spanner*> (this);
468 if (original_l_ && s)
473 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
475 Grob * sc = s->broken_into_l_arr_[i];
476 System * l = sc->line_l ();
477 sc->mutable_property_alist_ =
478 handle_broken_grobs (mutable_property_alist_,
479 l ? l->self_scm () : SCM_UNDEFINED);
484 System *line = line_l ();
486 if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
488 mutable_property_alist_
489 = handle_broken_grobs (mutable_property_alist_,
490 line ? line->self_scm () : SCM_UNDEFINED);
492 else if (dynamic_cast <System*> (this))
494 mutable_property_alist_ = handle_broken_grobs (mutable_property_alist_,
500 This element is `invalid'; it has been removed from all
501 dependencies, so let's junk the element itself.
503 do not do this for System, since that would remove
504 references to the originals of score-grobs, which get then GC'd
512 Note that we still want references to this element to be
513 rearranged, and not silently thrown away, so we keep pointers
514 like {broken_into_{drul,array}, original}
519 mutable_property_alist_ = SCM_EOL;
520 immutable_property_alist_ = SCM_EOL;
522 set_extent (SCM_EOL, Y_AXIS);
523 set_extent (SCM_EOL, X_AXIS);
525 for (int a= X_AXIS; a <= Y_AXIS; a++)
527 dim_cache_[a].offset_callbacks_ = SCM_EOL;
528 dim_cache_[a].offsets_left_ = 0;
533 Grob::handle_prebroken_dependencies ()
538 Grob::find_broken_piece (System*) const
544 Grob::translate_axis (Real y, Axis a)
546 if (isinf (y) || isnan (y))
547 programming_error (_ (INFINITY_MSG));
550 dim_cache_[a].offset_ += y;
555 Grob::relative_coordinate (Grob const*refp, Axis a) const
561 We catch PARENT_L_ == nil case with this, but we crash if we did
562 not ask for the absolute coordinate (ie. REFP == nil.)
565 if (refp == dim_cache_[a].parent_l_)
566 return get_offset (a);
568 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
572 Grob::get_offset (Axis a) const
574 Grob *me = (Grob*) this;
575 while (dim_cache_[a].offsets_left_)
577 int l = --me->dim_cache_[a].offsets_left_;
578 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
579 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
581 Real r = gh_scm2double (retval);
582 if (isinf (r) || isnan (r))
584 programming_error (INFINITY_MSG);
587 me->dim_cache_[a].offset_ +=r;
589 return dim_cache_[a].offset_;
593 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
595 Grob::point_dimension_callback (SCM , SCM)
597 return ly_interval2scm (Interval (0,0));
601 Grob::empty_b (Axis a)const
603 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
604 gh_procedure_p (dim_cache_[a].dimension_));
608 Grob::extent (Grob * refp, Axis a) const
610 Real x = relative_coordinate (refp, a);
613 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
615 if (gh_pair_p (d->dimension_))
617 else if (gh_procedure_p (d->dimension_))
620 FIXME: add doco on types, and should typecheck maybe?
622 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
627 if (!gh_pair_p (d->dimension_))
630 ext = ly_scm2interval (d->dimension_);
632 SCM extra = get_grob_property (a == X_AXIS
639 if (gh_pair_p (extra))
641 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
642 ext[SMALLER] += gh_scm2double (ly_car (extra));
645 extra = get_grob_property (a == X_AXIS
647 : "minimum-extent-Y");
648 if (gh_pair_p (extra))
650 ext.unite (Interval (gh_scm2double (ly_car (extra)),
651 gh_scm2double (ly_cdr (extra))));
660 Grob::common_refpoint (Grob const* s, Axis a) const
663 I don't like the quadratic aspect of this code, but I see no other
664 way. The largest chain of parents might be 10 high or so, so
665 it shouldn't be a real issue. */
666 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
667 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
676 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
678 for (; gh_pair_p (elist); elist = ly_cdr (elist))
680 Grob * s = unsmob_grob (ly_car (elist));
684 common = common->common_refpoint (s, a);
695 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
697 for (int i = arr.size() ; i--; )
704 common = common->common_refpoint (s, a);
715 SCM meta = get_grob_property ("meta");
716 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
717 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
718 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
722 Grob::add_offset_callback (SCM cb, Axis a)
724 if (!has_offset_callback_b (cb, a))
726 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
727 dim_cache_[a].offsets_left_ ++;
732 Grob::has_extent_callback_b (SCM cb, Axis a)const
734 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
739 Grob::has_offset_callback_b (SCM cb, Axis a)const
741 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
745 Grob::set_extent (SCM dc, Axis a)
747 dim_cache_[a].dimension_ =dc;
751 Grob::set_parent (Grob *g, Axis a)
753 dim_cache_[a].parent_l_ = g;
756 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
758 Grob::fixup_refpoint (SCM smob)
760 Grob *me = unsmob_grob (smob);
761 for (int a = X_AXIS; a < NO_AXES; a ++)
764 Grob * parent = me->get_parent (ax);
769 if (parent->line_l () != me->line_l () && me->line_l ())
771 Grob * newparent = parent->find_broken_piece (me->line_l ());
772 me->set_parent (newparent, ax);
775 if (Item * i = dynamic_cast<Item*> (me))
777 Item *parenti = dynamic_cast<Item*> (parent);
781 Direction my_dir = i->break_status_dir () ;
782 if (my_dir!= parenti->break_status_dir ())
784 Item *newparent = parenti->find_prebroken_piece (my_dir);
785 me->set_parent (newparent, ax);
794 Grob::warning (String s)const
796 SCM cause = self_scm();
797 while (cause != SCM_EOL && !unsmob_music (cause))
799 Grob * g = unsmob_grob (cause);
800 cause = g->get_grob_property ("cause");
803 if (Music *m = unsmob_music (cause))
805 m->origin()->warning (s);
812 Grob::programming_error (String s)const
814 s = "Programming error: " + s;
819 /****************************************************
821 ****************************************************/
825 IMPLEMENT_SMOBS (Grob);
826 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
829 Grob::mark_smob (SCM ses)
831 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
832 scm_gc_mark (s->immutable_property_alist_);
833 scm_gc_mark (s->mutable_property_alist_);
835 for (int a =0 ; a < 2; a++)
837 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
838 scm_gc_mark (s->dim_cache_[a].dimension_);
839 Grob *p = s->get_parent (Y_AXIS);
841 scm_gc_mark (p->self_scm ());
845 scm_gc_mark (s->original_l_->self_scm ());
847 return s->do_derived_mark ();
851 Grob::print_smob (SCM s, SCM port, scm_print_state *)
853 Grob *sc = (Grob *) ly_cdr (s);
855 scm_puts ("#<Grob ", port);
856 scm_puts ((char *)sc->name ().ch_C (), port);
859 don't try to print properties, that is too much hassle.
861 scm_puts (" >", port);
866 Grob::do_derived_mark ()
871 LY_DEFINE(ly_set_grob_property,"ly-set-grob-property", 3, 0, 0,
872 (SCM grob, SCM sym, SCM val),
874 Set @var{sym} in grob @var{grob} to value @var{val}")
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 if (!type_check_assignment (sym, val, ly_symbol2scm ("backend-type?")))
881 error ("typecheck failed");
883 sc->internal_set_grob_property (sym, val);
884 return SCM_UNSPECIFIED;
887 LY_DEFINE(ly_get_grob_property,
888 "ly-get-grob-property", 2, 0, 0, (SCM grob, SCM sym),
889 " Get the value of a value in grob @var{g} of property @var{sym}. It
890 will return @code{'()} (end-of-list) if @var{g} doesn't have @var{sym} set.
893 Grob * sc = unsmob_grob (grob);
894 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
895 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
897 return sc->internal_get_grob_property (sym);
902 Grob::discretionary_processing ()
907 LY_DEFINE(spanner_get_bound, "ly-get-spanner-bound", 2 , 0, 0,
909 "Get one of the bounds of @var{spanner}. @var{dir} may be @code{-1} for
910 left, and @code{1} for right.
913 Spanner * sl = dynamic_cast<Spanner*> (unsmob_grob (slur));
914 SCM_ASSERT_TYPE(sl, slur, SCM_ARG1, __FUNCTION__, "spanner grob");
915 SCM_ASSERT_TYPE(ly_dir_p (dir), slur, SCM_ARG2, __FUNCTION__, "dir");
916 return sl->get_bound (to_dir (dir))->self_scm ();
919 LY_DEFINE(ly_get_paper_var,"ly-get-paper-variable", 2, 0, 0,
921 "Get a variable from the \\paper block.")
923 Grob * sc = unsmob_grob (grob);
924 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
925 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
927 return sc->paper_l() ->get_scmvar_scm (sym);
932 LY_DEFINE(ly_get_extent, "ly-get-extent", 3, 0, 0,
933 (SCM grob, SCM refp, SCM axis),
934 "Get the extent in @var{axis} direction of @var{grob} relative to the
937 Grob * sc = unsmob_grob (grob);
938 Grob * ref = unsmob_grob (refp);
939 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
940 SCM_ASSERT_TYPE(ref, refp, SCM_ARG2, __FUNCTION__, "grob");
942 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG3, __FUNCTION__, "axis");
944 return ly_interval2scm ( sc->extent (ref, Axis (gh_scm2int (axis))));
947 LY_DEFINE (ly_get_parent, "ly-get-parent", 2, 0, 0, (SCM grob, SCM axis),
948 "Get the parent of @var{grob}. @var{axis} can be 0 for the X-axis, 1
951 Grob * sc = unsmob_grob (grob);
952 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
953 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG2, __FUNCTION__, "axis");
955 return sc->get_parent (Axis (gh_scm2int (axis)))->self_scm();
960 Grob::internal_has_interface (SCM k)
962 SCM ifs = get_grob_property ("interfaces");
964 return scm_memq (k, ifs) != SCM_BOOL_F;
967 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
969 ADD_INTERFACE (Grob, "grob-interface",
970 "All grobs support this",
971 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
972 Y-extent-callback molecule-callback extra-offset
974 staff-symbol interfaces dependencies extra-extent-X causes meta
975 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");