]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
f6c84bc079e0df7650fc0a1d45e2e76f282e1aff
[lilypond.git] / lily / grob.cc
1 /*
2   score-elem.cc -- implement Grob
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2001 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <string.h>
11 #include <math.h>
12
13 #include "input-smob.hh"
14 #include "libc-extension.hh"
15 #include "group-interface.hh"
16 #include "misc.hh"
17 #include "paper-score.hh"
18 #include "paper-def.hh"
19 #include "molecule.hh"
20 #include "grob.hh"
21 #include "debug.hh"
22 #include "spanner.hh"
23 #include "line-of-score.hh"
24 #include "item.hh"
25 #include "paper-column.hh"
26 #include "molecule.hh"
27 #include "misc.hh"
28 #include "paper-outputter.hh"
29 #include "dimension-cache.hh"
30 #include "side-position-interface.hh"
31 #include "item.hh"
32
33 #include "ly-smobs.icc"
34
35 /*
36 TODO:
37
38 remove dynamic_cast<Spanner,Item> and put this code into respective
39   subclass.
40 */
41
42
43 #define INFINITY_MSG "Infinity or NaN encountered"
44
45 Grob::Grob (SCM basicprops)
46 {
47   /*
48     fixme: default should be no callback.
49    */
50
51   pscore_l_=0;
52   status_i_ = 0;
53   original_l_ = 0;
54   immutable_property_alist_ =  basicprops;
55   mutable_property_alist_ = SCM_EOL;
56
57   smobify_self ();
58
59   char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
60   char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
61   
62   for (int a = X_AXIS; a <= Y_AXIS; a++){
63     SCM l = get_grob_property (onames[a]);
64
65     if (scm_ilength (l) >=0)
66       {
67         dim_cache_[a].offset_callbacks_ = l;
68         dim_cache_[a].offsets_left_ = scm_ilength (l);
69       }
70     else
71       {
72         programming_error ("[XY]-offset-callbacks must be a list");
73       }
74
75     SCM cb = get_grob_property (enames[a]);
76
77     /*
78       Should change default to be empty? 
79      */
80     if (cb != SCM_BOOL_F && !gh_procedure_p (cb) && !gh_pair_p (cb))
81       cb = molecule_extent_proc;
82     
83     dim_cache_[a].dimension_ = cb;
84   }
85
86   SCM meta = get_grob_property ("meta");
87   SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
88   
89   set_grob_property ("interfaces",gh_cdr (ifs));
90 }
91
92
93 Grob::Grob (Grob const&s)
94    : dim_cache_ (s.dim_cache_)
95 {
96   original_l_ = (Grob*) &s;
97   immutable_property_alist_ = s.immutable_property_alist_;
98   mutable_property_alist_ = SCM_EOL;
99   
100   status_i_ = s.status_i_;
101   pscore_l_ = s.pscore_l_;
102
103   smobify_self ();
104 }
105
106 Grob::~Grob ()
107 {
108   /*
109     do nothing scm-ish and no unprotecting here.
110    */
111 }
112
113
114 SCM
115 Grob::get_grob_property (const char *nm) const
116 {
117   SCM sym = ly_symbol2scm (nm);
118   return get_grob_property (sym);
119 }
120
121 SCM
122 Grob::get_grob_property (SCM sym) const
123 {
124   SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
125   if (s != SCM_BOOL_F)
126     return gh_cdr (s);
127
128   s = scm_sloppy_assq (sym, immutable_property_alist_);
129   return (s == SCM_BOOL_F) ? SCM_EOL : gh_cdr (s); 
130 }
131
132 /*
133   Remove the value associated with KEY, and return it. The result is
134   that a next call will yield SCM_UNDEFINED (and not the underlying
135   `basic' property.
136 */
137 SCM
138 Grob::remove_grob_property (const char* key)
139 {
140   SCM val = get_grob_property (key);
141   if (val != SCM_EOL)
142     set_grob_property (key, SCM_EOL);
143   return val;
144 }
145
146 void
147 Grob::set_grob_property (const char* k, SCM v)
148 {
149   SCM s = ly_symbol2scm (k);
150   set_grob_property (s, v);
151 }
152
153 /*
154   Puts the k, v in the immutable_property_alist_, which is convenient for
155   storing variables that are needed during the breaking process. (eg.
156   Line_of_score::rank : int)
157  */
158 void
159 Grob::set_immutable_grob_property (const char*k, SCM v)
160 {
161   SCM s = ly_symbol2scm (k);
162   set_immutable_grob_property (s, v);
163 }
164
165 void
166 Grob::set_immutable_grob_property (SCM s, SCM v)
167 {
168   immutable_property_alist_ = gh_cons (gh_cons (s,v), mutable_property_alist_);
169   mutable_property_alist_ = scm_assq_remove_x (mutable_property_alist_, s);
170 }
171 void
172 Grob::set_grob_property (SCM s, SCM v)
173 {
174   mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
175 }
176
177
178 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
179 SCM
180 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
181 {
182   Grob *s = unsmob_grob (element_smob);
183   Axis a = (Axis) gh_scm2int (scm_axis);
184
185   Molecule *m = s->get_molecule ();
186   Interval e ;
187   if (m)
188     e = m->extent (a);
189   return ly_interval2scm (e);
190 }
191
192 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
193
194 SCM
195 Grob::preset_extent (SCM element_smob, SCM scm_axis)
196 {
197   Grob *s = unsmob_grob (element_smob);
198   Axis a = (Axis) gh_scm2int (scm_axis);
199
200   SCM ext = s->get_grob_property ((a == X_AXIS)
201                                  ? "extent-X"
202                                  : "extent-Y");
203   
204   if (gh_pair_p (ext))
205     {
206       Real l = gh_scm2double (gh_car (ext));
207       Real r = gh_scm2double (gh_cdr (ext));
208       return ly_interval2scm (Interval (l, r));
209     }
210   
211   return ly_interval2scm (Interval ());
212 }
213
214
215
216 Paper_def*
217 Grob::paper_l ()  const
218 {
219  return pscore_l_ ? pscore_l_->paper_l_ : 0;
220 }
221
222 void
223 Grob::calculate_dependencies (int final, int busy, SCM funcname)
224 {
225   assert (status_i_ >=0);
226
227   if (status_i_ >= final)
228     return;
229
230   if (status_i_== busy)
231     {
232       programming_error ("Element is busy, come back later");
233       return;
234     }
235   
236   status_i_= busy;
237
238   for (SCM d=  get_grob_property ("dependencies"); gh_pair_p (d); d = gh_cdr (d))
239     {
240       unsmob_grob (gh_car (d))
241         ->calculate_dependencies (final, busy, funcname);
242     }
243
244   // ughugh.
245   String s = ly_symbol2string (funcname);
246   SCM proc = get_grob_property (s.ch_C ());
247   if (gh_procedure_p (proc))
248     gh_call1 (proc, this->self_scm ());
249   
250   status_i_= final;
251
252 }
253
254 Molecule *
255 Grob::get_molecule ()  const
256 {
257   SCM mol = get_grob_property ("molecule");
258   if (unsmob_molecule (mol))
259     return unsmob_molecule (mol);
260
261   mol =  get_uncached_molecule ();
262   
263   Grob *me = (Grob*)this;
264   me->set_grob_property ("molecule", mol);
265   
266   return unsmob_molecule (mol);  
267 }
268 SCM
269 Grob::get_uncached_molecule ()const
270 {
271   SCM proc = get_grob_property ("molecule-callback");
272
273   SCM  mol = SCM_EOL;
274   if (gh_procedure_p (proc)) 
275     mol = gh_apply (proc, gh_list (this->self_scm (), SCM_UNDEFINED));
276
277   
278   Molecule *m = unsmob_molecule (mol);
279   
280   if (unsmob_molecule (mol))
281     {
282       /*
283         TODO: add option for not copying origin info. 
284       */
285       SCM origin =get_grob_property ("origin");
286       if (!unsmob_input (origin))
287         origin =ly_symbol2scm ("no-origin");
288       
289       
290       // ugr.
291       
292       mol = Molecule (m->extent_box (),
293                       gh_list (origin, m->get_expr (), SCM_UNDEFINED)
294                       ). smobbed_copy ();
295
296       m = unsmob_molecule (mol);
297     }
298   
299   /*
300     transparent retains dimensions of element.
301    */
302   if (m && to_boolean (get_grob_property ("transparent")))
303     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
304
305   return mol;
306 }
307
308 /*
309   
310   VIRTUAL STUBS
311
312  */
313 void
314 Grob::do_break_processing ()
315 {
316 }
317
318
319
320
321
322
323 Line_of_score *
324 Grob::line_l () const
325 {
326   return 0;
327 }
328
329 void
330 Grob::add_dependency (Grob*e)
331 {
332   if (e)
333     {
334       Pointer_group_interface ::add_element (this, "dependencies",e);
335
336     }
337   else
338     programming_error ("Null dependency added");
339 }
340
341
342
343
344 /**
345       Do break substitution in S, using CRITERION. Return new value.
346       CRITERION is either a SMOB pointer to the desired line, or a number
347       representing the break direction. Do not modify SRC.
348 */
349 SCM
350 Grob::handle_broken_grobs (SCM src, SCM criterion)
351 {
352  again:
353   Grob *sc = unsmob_grob (src);
354   if (sc)
355     {
356       if (gh_number_p (criterion))
357         {
358           Item * i = dynamic_cast<Item*> (sc);
359           Direction d = to_dir (criterion);
360           if (i && i->break_status_dir () != d)
361             {
362               Item *br = i->find_prebroken_piece (d);
363               return (br) ? br->self_scm () : SCM_UNDEFINED;
364             }
365         }
366       else
367         {
368           Line_of_score * line
369             = dynamic_cast<Line_of_score*> (unsmob_grob (criterion));
370           if (sc->line_l () != line)
371             {
372               sc = sc->find_broken_piece (line);
373
374             }
375
376           /* now: !sc || (sc && sc->line_l () == line) */
377           if (!sc)
378             return SCM_UNDEFINED;
379
380           /* now: sc && sc->line_l () == line */
381           if (!line
382               || (sc->common_refpoint (line, X_AXIS)
383                   && sc->common_refpoint (line, Y_AXIS)))
384             {
385               return sc->self_scm ();
386             }
387           return SCM_UNDEFINED;
388         }
389     }
390   else if (gh_pair_p (src))
391     {
392       SCM oldcar =gh_car (src);
393       /*
394         UGH! breaks on circular lists.
395       */
396       SCM newcar = handle_broken_grobs (oldcar, criterion);
397       SCM oldcdr = gh_cdr (src);
398       
399       if (newcar == SCM_UNDEFINED
400           && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
401         {
402           /*
403             This is tail-recursion, ie. 
404             
405             return handle_broken_grobs (cdr, criterion);
406
407             We don't want to rely on the compiler to do this.  Without
408             tail-recursion, this easily crashes with a stack overflow.  */
409           src =  oldcdr;
410           goto again;
411         }
412
413       SCM newcdr = handle_broken_grobs (oldcdr, criterion);
414       return gh_cons (newcar, newcdr);
415     }
416   else
417     return src;
418
419   return src;
420 }
421
422 void
423 Grob::handle_broken_dependencies ()
424 {
425   Spanner * s= dynamic_cast<Spanner*> (this);
426   if (original_l_ && s)
427     return;
428
429   if (s)
430     {
431       for (int i = 0;  i< s->broken_into_l_arr_ .size (); i++)
432         {
433           Grob * sc = s->broken_into_l_arr_[i];
434           Line_of_score * l = sc->line_l ();
435           sc->mutable_property_alist_ =
436             handle_broken_grobs (mutable_property_alist_,
437                                  l ? l->self_scm () : SCM_UNDEFINED);
438         }
439     }
440
441
442   Line_of_score *line = line_l ();
443
444   if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
445     {
446       mutable_property_alist_
447         = handle_broken_grobs (mutable_property_alist_,
448                                line ? line->self_scm () : SCM_UNDEFINED);
449     }
450   else if (dynamic_cast <Line_of_score*> (this))
451     {
452       mutable_property_alist_ = handle_broken_grobs (mutable_property_alist_,
453                                             SCM_UNDEFINED);
454     }
455   else
456     {
457       /*
458         This element is `invalid'; it has been removed from all
459         dependencies, so let's junk the element itself.
460
461         do not do this for Line_of_score, since that would remove
462         references to the originals of score-elts, which get then GC'd
463  (a bad thing.)
464       */
465       suicide ();
466     }
467 }
468
469 /*
470  Note that we still want references to this element to be
471  rearranged, and not silently thrown away, so we keep pointers
472  like {broken_into_{drul,array}, original}
473 */
474 void
475 Grob::suicide ()
476 {
477   mutable_property_alist_ = SCM_EOL;
478   immutable_property_alist_ = SCM_EOL;
479
480   set_extent_callback (SCM_EOL, Y_AXIS);
481   set_extent_callback (SCM_EOL, X_AXIS);
482
483   for (int a= X_AXIS; a <= Y_AXIS; a++)
484     {
485       dim_cache_[a].offset_callbacks_ = SCM_EOL;
486       dim_cache_[a].offsets_left_ = 0;
487     }
488 }
489
490 void
491 Grob::handle_prebroken_dependencies ()
492 {
493 }
494
495 Grob*
496 Grob::find_broken_piece (Line_of_score*) const
497 {
498   return 0;
499 }
500
501 void
502 Grob::translate_axis (Real y, Axis a)
503 {
504   if (isinf (y) || isnan (y))
505     programming_error (_ (INFINITY_MSG));
506   else
507     {
508       dim_cache_[a].offset_ += y;
509     }
510 }  
511
512 Real
513 Grob::relative_coordinate (Grob const*refp, Axis a) const
514 {
515   if (refp == this)
516     return 0.0;
517
518   /*
519     We catch PARENT_L_ == nil case with this, but we crash if we did
520     not ask for the absolute coordinate (ie. REFP == nil.)
521     
522    */
523   if (refp == dim_cache_[a].parent_l_)
524     return get_offset (a);
525   else
526     return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
527 }
528
529 Real
530 Grob::get_offset (Axis a) const
531 {
532   Grob *me = (Grob*) this;
533   while (dim_cache_[a].offsets_left_)
534     {
535       int l = --me->dim_cache_[a].offsets_left_;
536       SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_,  gh_int2scm (l));
537       SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
538
539       Real r =  gh_scm2double (retval);
540       if (isinf (r) || isnan (r))
541         {
542           programming_error (INFINITY_MSG);
543           r = 0.0;
544         }
545       me->dim_cache_[a].offset_ +=r;
546     }
547   return dim_cache_[a].offset_;
548 }
549
550
551 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
552 SCM
553 Grob::point_dimension_callback (SCM , SCM)
554 {
555   return ly_interval2scm (Interval (0,0));
556 }
557
558 bool
559 Grob::empty_b (Axis a)const
560 {
561   return ! (gh_pair_p (dim_cache_[a].dimension_) ||
562             gh_procedure_p (dim_cache_[a].dimension_));
563 }
564
565 /*
566   TODO: add
567
568     Grob *refpoint
569
570   to arguments?
571  */
572 Interval
573 Grob::extent (Grob * refp, Axis a) const
574 {
575   Real x = relative_coordinate (refp, a);
576
577   
578   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
579   Interval ext ;   
580   if (gh_pair_p (d->dimension_))
581     ;
582   else if (gh_procedure_p (d->dimension_))
583     {
584       /*
585         FIXME: add doco on types, and should typecheck maybe? 
586        */
587       d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
588     }
589   else
590     return ext;
591
592   if (!gh_pair_p (d->dimension_))
593     return ext;
594   
595   ext = ly_scm2interval (d->dimension_);
596
597   SCM extra = get_grob_property (a == X_AXIS
598                                 ? "extra-extent-X"
599                                 : "extra-extent-Y");
600
601   /*
602     signs ?
603    */
604   if (gh_pair_p (extra))
605     {
606       ext[BIGGER] +=  gh_scm2double (gh_cdr (extra));
607       ext[SMALLER] +=   gh_scm2double (gh_car (extra));
608     }
609   
610   extra = get_grob_property (a == X_AXIS
611                                 ? "minimum-extent-X"
612                                 : "minimum-extent-Y");
613   if (gh_pair_p (extra))
614     {
615       ext.unite (Interval (gh_scm2double (gh_car (extra)),
616                            gh_scm2double (gh_cdr (extra))));
617     }
618
619   ext.translate (x);
620   
621   return ext;
622 }
623
624
625 Grob*
626 Grob::parent_l (Axis a) const
627 {
628   return  dim_cache_[a].parent_l_;
629 }
630
631 Grob * 
632 Grob::common_refpoint (Grob const* s, Axis a) const
633 {
634   /*
635     I don't like the quadratic aspect of this code, but I see no other
636     way. The largest chain of parents might be 10 high or so, so
637     it shouldn't be a real issue. */
638   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
639     for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
640       if (d == c)
641         return (Grob*)d;
642
643   return 0;
644 }
645
646
647 Grob *
648 Grob::common_refpoint (SCM elist, Axis a) const
649 {
650   Grob * common = (Grob*) this;
651   for (; gh_pair_p (elist); elist = gh_cdr (elist))
652     {
653       Grob * s = unsmob_grob (gh_car (elist));
654       if (s)
655         common = common->common_refpoint (s, a);
656     }
657
658   return common;
659 }
660
661 String
662 Grob::name () const
663 {
664   SCM meta = get_grob_property ("meta");
665   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
666   nm = (gh_pair_p (nm)) ? gh_cdr (nm) : SCM_EOL;
667   return  gh_string_p (nm) ?ly_scm2string (nm) :  classname (this);  
668 }
669
670 void
671 Grob::add_offset_callback (SCM cb, Axis a)
672 {
673   if (!has_offset_callback_b (cb, a))
674   {
675     dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
676     dim_cache_[a].offsets_left_ ++;
677   }
678 }
679
680 bool
681 Grob::has_extent_callback_b (SCM cb, Axis a)const
682 {
683   return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
684 }
685
686
687 bool
688 Grob::has_extent_callback_b (Axis a) const
689 {
690   return gh_procedure_p (dim_cache_[a].dimension_);
691 }
692
693 bool
694 Grob::has_offset_callback_b (SCM cb, Axis a)const
695 {
696   return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
697 }
698
699 void
700 Grob::set_extent_callback (SCM dc, Axis a)
701 {
702   dim_cache_[a].dimension_ =dc;
703 }
704
705 void
706 Grob::set_parent (Grob *g, Axis a)
707 {
708   dim_cache_[a].parent_l_ = g;
709 }
710
711 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
712 SCM
713 Grob::fixup_refpoint (SCM smob)
714 {
715   Grob *me = unsmob_grob (smob);
716   for (int a = X_AXIS; a < NO_AXES; a ++)
717     {
718       Axis ax = (Axis)a;
719       Grob * parent = me->parent_l (ax);
720
721       if (!parent)
722         continue;
723       
724       if (parent->line_l () != me->line_l () && me->line_l ())
725         {
726           Grob * newparent = parent->find_broken_piece (me->line_l ());
727           me->set_parent (newparent, ax);
728         }
729
730       if (Item * i  = dynamic_cast<Item*> (me))
731         {
732           Item *parenti = dynamic_cast<Item*> (parent);
733
734           if (parenti && i)
735             {
736               Direction  my_dir = i->break_status_dir () ;
737               if (my_dir!= parenti->break_status_dir ())
738                 {
739                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
740                   me->set_parent (newparent, ax);
741                 }
742             }
743         }
744     }
745   return smob;
746 }
747
748
749
750 /****************************************************
751   SMOB funcs
752  ****************************************************/
753
754
755 IMPLEMENT_UNSMOB (Grob, grob);
756 IMPLEMENT_SMOBS (Grob);
757 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
758
759 SCM
760 Grob::mark_smob (SCM ses)
761 {
762   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
763   scm_gc_mark (s->immutable_property_alist_);
764   scm_gc_mark (s->mutable_property_alist_);
765
766   for (int a =0 ; a < 2; a++)
767     {
768       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
769       scm_gc_mark (s->dim_cache_[a].dimension_);
770     }
771   
772   if (s->parent_l (Y_AXIS))
773     scm_gc_mark (s->parent_l (Y_AXIS)->self_scm ());
774   if (s->parent_l (X_AXIS))
775     scm_gc_mark (s->parent_l (X_AXIS)->self_scm ());
776
777   if (s->original_l_)
778     scm_gc_mark (s->original_l_->self_scm ());
779   return s->do_derived_mark ();
780 }
781
782 int
783 Grob::print_smob (SCM s, SCM port, scm_print_state *)
784 {
785   Grob *sc = (Grob *) gh_cdr (s);
786      
787   scm_puts ("#<Grob ", port);
788   scm_puts ((char *)sc->name ().ch_C (), port);
789
790   /*
791     don't try to print properties, that is too much hassle.
792    */
793   scm_puts (" >", port);
794   return 1;
795 }
796
797 SCM
798 Grob::do_derived_mark ()
799 {
800   return SCM_EOL;
801 }
802
803
804 SCM
805 ly_set_grob_property (SCM elt, SCM sym, SCM val)
806 {
807   Grob * sc = unsmob_grob (elt);
808
809   if (!gh_symbol_p (sym))
810     {
811       error ("Not a symbol");
812       ly_display_scm (sym);
813       return SCM_UNSPECIFIED;
814     }
815
816   if (sc)
817     {
818       sc->set_grob_property (sym, val);
819     }
820   else
821     {
822       error ("Not a score element");
823       ly_display_scm (elt);
824     }
825
826   return SCM_UNSPECIFIED;
827 }
828
829
830 SCM
831 ly_get_grob_property (SCM elt, SCM sym)
832 {
833   Grob * sc = unsmob_grob (elt);
834   
835   if (sc)
836     {
837       return sc->get_grob_property (sym);
838     }
839   else
840     {
841       error ("Not a score element");
842       ly_display_scm (elt);
843     }
844   return SCM_UNSPECIFIED;
845 }
846
847
848 void
849 Grob::discretionary_processing ()
850 {
851 }
852
853
854
855 SCM
856 spanner_get_bound (SCM slur, SCM dir)
857 {
858   return dynamic_cast<Spanner*> (unsmob_grob (slur))->get_bound (to_dir (dir))->self_scm ();
859 }
860
861
862
863 static SCM interfaces_sym;
864 static void
865 init_functions ()
866 {
867   interfaces_sym = scm_permanent_object (ly_symbol2scm ("interfaces"));
868
869   scm_make_gsubr ("ly-get-grob-property", 2, 0, 0, (Scheme_function_unknown)ly_get_grob_property);
870   scm_make_gsubr ("ly-set-grob-property", 3, 0, 0, (Scheme_function_unknown)ly_set_grob_property);
871   scm_make_gsubr ("ly-get-spanner-bound", 2 , 0, 0, (Scheme_function_unknown) spanner_get_bound);
872 }
873
874 bool
875 Grob::has_interface (SCM k)
876 {
877   SCM ifs = get_grob_property (interfaces_sym);
878
879   return scm_memq (k, ifs) != SCM_BOOL_F;
880 }
881
882 void
883 Grob::set_interface (SCM k)
884 {
885   if (has_interface (k))
886     return ;
887   else
888     {
889       set_grob_property (interfaces_sym,
890                         gh_cons (k, get_grob_property (interfaces_sym)));
891     }
892 }
893
894
895 ADD_SCM_INIT_FUNC (scoreelt, init_functions);
896 IMPLEMENT_TYPE_P (Grob, "ly-grob?");