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
237 SCM mol
= get_grob_property ("molecule");
238 if (unsmob_molecule (mol
))
239 return unsmob_molecule (mol
);
241 mol
= get_uncached_molecule ();
245 Grob
*me
= (Grob
*)this;
246 me
->set_grob_property ("molecule", mol
);
249 return unsmob_molecule (mol
);
253 Grob::get_uncached_molecule ()const
255 SCM proc
= get_grob_property ("molecule-callback");
258 if (gh_procedure_p (proc
))
259 mol
= gh_apply (proc
, scm_list_n (this->self_scm (), SCM_UNDEFINED
));
261 Molecule
*m
= unsmob_molecule (mol
);
263 if (unsmob_molecule (mol
))
265 SCM origin
= ly_symbol2scm ("no-origin");
267 if (store_locations_global_b
)
269 SCM cause
= get_grob_property ("cause");
270 if (Music
*m
= unsmob_music (cause
))
272 SCM music_origin
= m
->get_mus_property ("origin");
273 if (unsmob_input (music_origin
))
274 origin
= music_origin
;
280 mol
= Molecule (m
->extent_box (),
281 scm_list_n (origin
, m
->get_expr (), SCM_UNDEFINED
)
284 m
= unsmob_molecule (mol
);
288 transparent retains dimensions of element.
290 if (m
&& to_boolean (get_grob_property ("transparent")))
291 mol
= Molecule (m
->extent_box (), SCM_EOL
).smobbed_copy ();
302 Grob::do_break_processing ()
307 Grob::get_system () const
313 Grob::add_dependency (Grob
*e
)
317 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e
);
320 programming_error ("Null dependency added");
325 Grob::handle_broken_dependencies ()
327 Spanner
* sp
= dynamic_cast<Spanner
*> (this);
333 for (SCM s
= mutable_property_alist_
; gh_pair_p(s
);
336 sp
->substitute_one_mutable_property (gh_caar (s
),
342 System
*system
= get_system ();
345 && system
&& common_refpoint (system
, X_AXIS
) && common_refpoint (system
, Y_AXIS
))
347 substitute_mutable_properties (system
? system
->self_scm () : SCM_UNDEFINED
,
348 mutable_property_alist_
);
350 else if (dynamic_cast <System
*> (this))
352 substitute_mutable_properties (SCM_UNDEFINED
, mutable_property_alist_
);
357 This element is `invalid'; it has been removed from all
358 dependencies, so let's junk the element itself.
360 do not do this for System, since that would remove references
361 to the originals of score-grobs, which get then GC'd (a bad
370 Note that we still want references to this element to be
371 rearranged, and not silently thrown away, so we keep pointers
372 like {broken_into_{drul,array}, original}
384 mutable_property_alist_
= SCM_EOL
;
385 immutable_property_alist_
= SCM_EOL
;
387 set_extent (SCM_EOL
, Y_AXIS
);
388 set_extent (SCM_EOL
, X_AXIS
);
390 for (int a
= X_AXIS
; a
<= Y_AXIS
; a
++)
392 dim_cache_
[a
].offset_callbacks_
= SCM_EOL
;
393 dim_cache_
[a
].offsets_left_
= 0;
398 This can make debugging a little easier: we can still know what
399 the object used to be. However, since all its links have been
400 broken, it's usually more convenient to set a conditional
401 breakpoint in GDB before the property lists are wiped.
403 mutable_property_alist_
= scm_acons (ly_symbol2scm ("name"),
404 scm_makfrom0str (nm
.to_str0()),
405 mutable_property_alist_
411 Grob::handle_prebroken_dependencies ()
414 Don't do this in the derived method, since we want to keep access to
415 mutable_property_alist_ centralized.
419 Item
* it
= dynamic_cast<Item
*> (this);
420 substitute_mutable_properties (gh_int2scm (it
->break_status_dir ()),
421 original_
->mutable_property_alist_
);
426 Grob::find_broken_piece (System
*) const
432 translate in one direction
435 Grob::translate_axis (Real y
, Axis a
)
437 if (isinf (y
) || isnan (y
))
438 programming_error (_ (INFINITY_MSG
));
441 dim_cache_
[a
].offset_
+= y
;
447 Find the offset relative to D. If D equals THIS, then it is 0.
448 Otherwise, it recursively defd as
450 OFFSET_ + PARENT_L_->relative_coordinate (D)
453 Grob::relative_coordinate (Grob
const*refp
, Axis a
) const
459 We catch PARENT_L_ == nil case with this, but we crash if we did
460 not ask for the absolute coordinate (ie. REFP == nil.)
463 if (refp
== dim_cache_
[a
].parent_
)
464 return get_offset (a
);
466 return get_offset (a
) + dim_cache_
[a
].parent_
->relative_coordinate (refp
, a
);
472 Invoke callbacks to get offset relative to parent.
475 Grob::get_offset (Axis a
) const
477 Grob
*me
= (Grob
*) this;
478 while (dim_cache_
[a
].offsets_left_
)
480 int l
= --me
->dim_cache_
[a
].offsets_left_
;
481 SCM cb
= scm_list_ref (dim_cache_
[a
].offset_callbacks_
, gh_int2scm (l
));
482 SCM retval
= gh_call2 (cb
, self_scm (), gh_int2scm (a
));
484 Real r
= gh_scm2double (retval
);
485 if (isinf (r
) || isnan (r
))
487 programming_error (INFINITY_MSG
);
490 me
->dim_cache_
[a
].offset_
+=r
;
492 return dim_cache_
[a
].offset_
;
496 MAKE_SCHEME_CALLBACK (Grob
,point_dimension_callback
,2);
498 Grob::point_dimension_callback (SCM
, SCM
)
500 return ly_interval2scm (Interval (0,0));
504 Grob::empty_b (Axis a
)const
506 return ! (gh_pair_p (dim_cache_
[a
].dimension_
) ||
507 gh_procedure_p (dim_cache_
[a
].dimension_
));
511 Grob::extent (Grob
* refp
, Axis a
) const
513 Real x
= relative_coordinate (refp
, a
);
516 Dimension_cache
* d
= (Dimension_cache
*)&dim_cache_
[a
];
518 if (gh_pair_p (d
->dimension_
))
520 else if (gh_procedure_p (d
->dimension_
))
523 FIXME: add doco on types, and should typecheck maybe?
525 d
->dimension_
= gh_call2 (d
->dimension_
, self_scm (), gh_int2scm (a
));
530 if (!gh_pair_p (d
->dimension_
))
533 ext
= ly_scm2interval (d
->dimension_
);
535 SCM extra
= get_grob_property (a
== X_AXIS
542 if (gh_pair_p (extra
))
544 ext
[BIGGER
] += gh_scm2double (ly_cdr (extra
));
545 ext
[SMALLER
] += gh_scm2double (ly_car (extra
));
548 extra
= get_grob_property (a
== X_AXIS
550 : "minimum-Y-extent");
551 if (gh_pair_p (extra
))
553 ext
.unite (Interval (gh_scm2double (ly_car (extra
)),
554 gh_scm2double (ly_cdr (extra
))));
563 Find the group-element which has both #this# and #s#
566 Grob::common_refpoint (Grob
const* s
, Axis a
) const
569 I don't like the quadratic aspect of this code, but I see no other
570 way. The largest chain of parents might be 10 high or so, so
571 it shouldn't be a real issue. */
572 for (Grob
const *c
= this; c
; c
= c
->dim_cache_
[a
].parent_
)
573 for (Grob
const * d
= s
; d
; d
= d
->dim_cache_
[a
].parent_
)
582 common_refpoint_of_list (SCM elist
, Grob
*common
, Axis a
)
584 for (; gh_pair_p (elist
); elist
= ly_cdr (elist
))
586 Grob
* s
= unsmob_grob (ly_car (elist
));
590 common
= common
->common_refpoint (s
, a
);
601 common_refpoint_of_array (Link_array
<Grob
> const &arr
, Grob
*common
, Axis a
)
603 for (int i
= arr
.size() ; i
--; )
610 common
= common
->common_refpoint (s
, a
);
621 SCM meta
= get_grob_property ("meta");
622 SCM nm
= scm_assoc (ly_symbol2scm ("name"), meta
);
623 nm
= (gh_pair_p (nm
)) ? ly_cdr (nm
) : SCM_EOL
;
624 return gh_symbol_p (nm
) ? ly_symbol2string (nm
) : classname (this);
628 Grob::add_offset_callback (SCM cb
, Axis a
)
630 if (!has_offset_callback_b (cb
, a
))
632 dim_cache_
[a
].offset_callbacks_
= gh_cons (cb
, dim_cache_
[a
].offset_callbacks_
);
633 dim_cache_
[a
].offsets_left_
++;
638 Grob::has_extent_callback_b (SCM cb
, Axis a
)const
640 return scm_equal_p (cb
, dim_cache_
[a
].dimension_
) == SCM_BOOL_T
;
645 Grob::has_offset_callback_b (SCM cb
, Axis a
)const
647 return scm_memq (cb
, dim_cache_
[a
].offset_callbacks_
) != SCM_BOOL_F
;
651 Grob::set_extent (SCM dc
, Axis a
)
653 dim_cache_
[a
].dimension_
=dc
;
657 Grob::set_parent (Grob
*g
, Axis a
)
659 dim_cache_
[a
].parent_
= g
;
662 MAKE_SCHEME_CALLBACK (Grob
,fixup_refpoint
,1);
664 Grob::fixup_refpoint (SCM smob
)
666 Grob
*me
= unsmob_grob (smob
);
667 for (int a
= X_AXIS
; a
< NO_AXES
; a
++)
670 Grob
* parent
= me
->get_parent (ax
);
675 if (parent
->get_system () != me
->get_system () && me
->get_system ())
677 Grob
* newparent
= parent
->find_broken_piece (me
->get_system ());
678 me
->set_parent (newparent
, ax
);
681 if (Item
* i
= dynamic_cast<Item
*> (me
))
683 Item
*parenti
= dynamic_cast<Item
*> (parent
);
687 Direction my_dir
= i
->break_status_dir () ;
688 if (my_dir
!= parenti
->break_status_dir ())
690 Item
*newparent
= parenti
->find_prebroken_piece (my_dir
);
691 me
->set_parent (newparent
, ax
);
700 Grob::warning (String s
)const
702 SCM cause
= self_scm();
703 while (Grob
* g
= unsmob_grob (cause
))
705 cause
= g
->get_grob_property ("cause");
708 if (Music
*m
= unsmob_music (cause
))
710 m
->origin()->warning (s
);
717 Grob::programming_error (String s
)const
719 s
= "Programming error: " + s
;
724 /****************************************************
726 ****************************************************/
730 IMPLEMENT_SMOBS (Grob
);
731 IMPLEMENT_DEFAULT_EQUAL_P (Grob
);
734 Grob::mark_smob (SCM ses
)
736 Grob
* s
= (Grob
*) SCM_CELL_WORD_1 (ses
);
737 scm_gc_mark (s
->immutable_property_alist_
);
739 for (int a
=0 ; a
< 2; a
++)
741 scm_gc_mark (s
->dim_cache_
[a
].offset_callbacks_
);
742 scm_gc_mark (s
->dim_cache_
[a
].dimension_
);
745 don't mark the parents. The pointers in the mutable property
746 list form two tree like structures (one for X relations, one
747 for Y relations). Marking these can be done in limited stack
748 space. If we add the parents, we will jump between X and Y in
749 an erratic manner, leading to much more recursion depth (and
750 core dumps if we link to pthreads.)
755 scm_gc_mark (s
->original_
->self_scm ());
757 s
->do_derived_mark ();
758 return s
->mutable_property_alist_
;
762 Grob::print_smob (SCM s
, SCM port
, scm_print_state
*)
764 Grob
*sc
= (Grob
*) ly_cdr (s
);
766 scm_puts ("#<Grob ", port
);
767 scm_puts ((char *)sc
->name ().to_str0 (), port
);
770 don't try to print properties, that is too much hassle.
772 scm_puts (" >", port
);
777 Grob::do_derived_mark () const
785 Grob::discretionary_processing ()
790 Grob::internal_has_interface (SCM k
)
792 SCM ifs
= get_grob_property ("interfaces");
794 return scm_memq (k
, ifs
) != SCM_BOOL_F
;
798 /** Return Array of Grobs in SCM list L */
802 Link_array
<Grob
> arr
;
804 for (SCM s
= l
; gh_pair_p (s
); s
= gh_cdr (s
))
807 arr
.push (unsmob_grob (e
));
814 /** Return SCM list of Grob array A */
816 ly_grobs2scm (Link_array
<Grob
> a
)
819 for (int i
= a
.size (); i
; i
--)
820 s
= gh_cons (a
[i
-1]->self_scm (), s
);
826 IMPLEMENT_TYPE_P (Grob
, "ly:grob?");
828 ADD_INTERFACE (Grob
, "grob-interface",
829 "In music notation, lots of symbols are related in some way. You can\n"
830 "think of music notation as a graph where nodes are formed by the\n"
831 "symbols, and the arcs by their relations. A grob is a node in that\n"
832 "graph. The directed edges in the graph are formed by references to\n"
833 "other grobs (i.e. pointers). This big graph of grobs specifies the\n"
834 "notation problem. The solution of this problem is a description of the\n"
835 "printout in closed form, i.e. a list of values. These values are\n"
838 "All grobs have an X and Y-position on the page. These X and Y positions\n"
839 "are stored in a relative format, so they can easily be combined by\n"
840 "stacking them, hanging one grob to the side of another, and coupling\n"
841 "them into a grouping-grob.\n"
843 "Each grob has a reference point (a.k.a. parent): the position of a grob\n"
844 "is stored relative to that reference point. For example the X-reference\n"
845 "point of a staccato dot usually is the note head that it applies\n"
846 "to. When the note head is moved, the staccato dot moves along\n"
849 "A grob is often associated with a symbol, but some grobs do not print\n"
850 "any symbols. They take care of grouping objects. For example, there is a\n"
851 "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
852 "is also an abstract grob: it only moves around chords, but doesn't print\n"
855 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause "
856 "Y-extent-callback molecule-callback extra-offset spacing-procedure "
857 "staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
858 "meta layer before-line-breaking-callback "
859 "after-line-breaking-callback extra-Y-extent minimum-X-extent "
860 "minimum-Y-extent transparent");