]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
''
[lilypond.git] / lily / grob.cc
1 /*
2   grob.cc -- implement Grob
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <string.h>
11 #include <math.h>
12
13 #include "main.hh"
14 #include "input-smob.hh"
15
16 #include "group-interface.hh"
17 #include "misc.hh"
18 #include "paper-score.hh"
19 #include "paper-def.hh"
20 #include "molecule.hh"
21 #include "grob.hh"
22 #include "debug.hh"
23 #include "spanner.hh"
24 #include "system.hh"
25 #include "item.hh"
26 #include "paper-column.hh"
27 #include "molecule.hh"
28 #include "misc.hh"
29 #include "paper-outputter.hh"
30 #include "music.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_c_ = 0;
53   original_l_ = 0;
54   immutable_property_alist_ =  basicprops;
55   mutable_property_alist_ = SCM_EOL;
56
57   /*
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.
61    */
62   smobify_self ();
63
64
65   SCM meta = get_grob_property ("meta");
66   if (gh_pair_p (meta))
67     {
68       SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
69
70       /*
71         do it directly to bypass interface checks.
72        */
73       mutable_property_alist_ = gh_cons (gh_cons (ly_symbol2scm ("interfaces"),
74                                                   gh_cdr (ifs)),
75                                          mutable_property_alist_);
76     }
77   
78   /*
79     TODO:
80
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.  */
84   
85   char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86   char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
87   
88   for (int a = X_AXIS; a <= Y_AXIS; a++)
89     {
90       SCM l = get_grob_property (onames[a]);
91
92       if (scm_ilength (l) >=0)
93         {
94           dim_cache_[a].offset_callbacks_ = l;
95           dim_cache_[a].offsets_left_ = scm_ilength (l);
96         }
97       else
98         {
99           programming_error ("[XY]-offset-callbacks must be a list");
100         }
101
102       SCM cb = get_grob_property (enames[a]);
103
104       /*
105         Should change default to be empty? 
106       */
107       if (cb != SCM_BOOL_F
108           && !gh_procedure_p (cb) && !gh_pair_p (cb)
109           && gh_procedure_p (get_grob_property ("molecule-callback"))
110           )
111         cb = molecule_extent_proc;
112     
113       dim_cache_[a].dimension_ = cb;
114     }
115
116 }
117
118 Grob::Grob (Grob const&s)
119    : dim_cache_ (s.dim_cache_)
120 {
121   original_l_ = (Grob*) &s;
122   immutable_property_alist_ = s.immutable_property_alist_;
123   mutable_property_alist_ = SCM_EOL;
124
125   /*
126     No properties are copied. That is the job of handle_broken_dependencies.
127    */
128   
129   status_c_ = s.status_c_;
130   pscore_l_ = s.pscore_l_;
131
132   smobify_self ();
133
134
135 }
136
137 Grob::~Grob ()
138 {
139   /*
140     do nothing scm-ish and no unprotecting here.
141    */
142 }
143
144
145
146 extern void check_interfaces_for_property (Grob const *me, SCM sym);
147
148 void
149 Grob::internal_set_grob_property (SCM s, SCM v)
150 {
151 #ifndef NDEBUG
152   if (internal_type_checking_global_b)
153     {
154       assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
155       check_interfaces_for_property(this, s);
156     }
157 #endif
158
159   
160   mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
161 }
162
163
164 SCM
165 Grob::internal_get_grob_property (SCM sym) const
166 {
167   SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
168   if (s != SCM_BOOL_F)
169     return ly_cdr (s);
170
171   s = scm_sloppy_assq (sym, immutable_property_alist_);
172   
173 #ifndef NDEBUG
174   if (internal_type_checking_global_b && gh_pair_p (s))
175     {
176       assert (type_check_assignment (sym, gh_cdr (s), ly_symbol2scm ("backend-type?")));
177       check_interfaces_for_property(this, sym);
178     }
179 #endif
180
181   return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s); 
182 }
183
184 /*
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
187   `basic' property.
188 */
189 SCM
190 Grob::remove_grob_property (const char* key)
191 {
192   SCM val = get_grob_property (key);
193   if (val != SCM_EOL)
194     set_grob_property (key, SCM_EOL);
195   return val;
196 }
197
198
199
200 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
201 SCM
202 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
203 {
204   Grob *s = unsmob_grob (element_smob);
205   Axis a = (Axis) gh_scm2int (scm_axis);
206
207   Molecule *m = s->get_molecule ();
208   Interval e ;
209   if (m)
210     e = m->extent (a);
211   return ly_interval2scm (e);
212 }
213
214 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
215
216 SCM
217 Grob::preset_extent (SCM element_smob, SCM scm_axis)
218 {
219   Grob *s = unsmob_grob (element_smob);
220   Axis a = (Axis) gh_scm2int (scm_axis);
221
222   SCM ext = s->get_grob_property ((a == X_AXIS)
223                                  ? "extent-X"
224                                  : "extent-Y");
225   
226   if (gh_pair_p (ext))
227     {
228       Real l = gh_scm2double (ly_car (ext));
229       Real r = gh_scm2double (ly_cdr (ext));
230       return ly_interval2scm (Interval (l, r));
231     }
232   
233   return ly_interval2scm (Interval ());
234 }
235
236
237
238 Paper_def*
239 Grob::paper_l ()  const
240 {
241  return pscore_l_ ? pscore_l_->paper_l_ : 0;
242 }
243
244 void
245 Grob::calculate_dependencies (int final, int busy, SCM funcname)
246 {
247   if (status_c_ >= final)
248     return;
249
250   if (status_c_== busy)
251     {
252       programming_error ("Element is busy, come back later");
253       return;
254     }
255   
256   status_c_= busy;
257
258   for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
259        d = ly_cdr (d))
260     {
261       unsmob_grob (ly_car (d))
262         ->calculate_dependencies (final, busy, funcname);
263     }
264
265   
266   SCM proc = internal_get_grob_property (funcname);
267   if (gh_procedure_p (proc))
268     gh_call1 (proc, this->self_scm ());
269  
270   status_c_= final;
271 }
272
273 Molecule *
274 Grob::get_molecule ()  const
275 {
276   if (immutable_property_alist_ == SCM_EOL)
277     {
278       return 0;
279       
280     }
281   
282   SCM mol = get_grob_property ("molecule");
283   if (unsmob_molecule (mol))
284     return unsmob_molecule (mol);
285
286   mol =  get_uncached_molecule ();
287   
288   Grob *me = (Grob*)this;
289   me->set_grob_property ("molecule", mol);
290   
291   return unsmob_molecule (mol);  
292 }
293 SCM
294 Grob::get_uncached_molecule ()const
295 {
296   SCM proc = get_grob_property ("molecule-callback");
297
298   SCM  mol = SCM_EOL;
299   if (gh_procedure_p (proc)) 
300     mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
301
302   
303   Molecule *m = unsmob_molecule (mol);
304   
305   if (unsmob_molecule (mol))
306     {
307       SCM origin = ly_symbol2scm ("no-origin");
308       
309       if (store_locations_global_b){
310         SCM cause = get_grob_property ("cause");
311         if (Music*m = unsmob_music (cause))
312           {
313             SCM music_origin = m->get_mus_property ("origin");
314             if (unsmob_input (music_origin))
315               origin = music_origin;
316           }
317       }
318
319       // ugr.
320       
321       mol = Molecule (m->extent_box (),
322                       scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
323                       ). smobbed_copy ();
324
325       m = unsmob_molecule (mol);
326     }
327   
328   /*
329     transparent retains dimensions of element.
330    */
331   if (m && to_boolean (get_grob_property ("transparent")))
332     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
333
334   return mol;
335 }
336
337 /*
338   
339   VIRTUAL STUBS
340
341  */
342 void
343 Grob::do_break_processing ()
344 {
345 }
346
347
348
349
350
351
352 System *
353 Grob::line_l () const
354 {
355   return 0;
356 }
357
358 void
359 Grob::add_dependency (Grob*e)
360 {
361   if (e)
362     {
363       Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
364     }
365   else
366     programming_error ("Null dependency added");
367 }
368
369
370
371
372 /**
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.
376
377       It is rather tightly coded, since it takes a lot of time; it is
378       one of the top functions in the profile.
379
380       We don't pass break_criterion as a parameter, since it is
381       `constant', but takes up stack space.
382
383 */
384
385
386 static SCM break_criterion; 
387 void
388 set_break_subsititution (SCM criterion)
389 {
390   break_criterion = criterion;
391 }
392
393 SCM
394 do_break_substitution (SCM src)
395 {
396  again:
397   Grob *sc = unsmob_grob (src);
398   if (sc)
399     {
400       if (SCM_INUMP (break_criterion))
401         {
402           Item * i = dynamic_cast<Item*> (sc);
403           Direction d = to_dir (break_criterion);
404           if (i && i->break_status_dir () != d)
405             {
406               Item *br = i->find_prebroken_piece (d);
407               return (br) ? br->self_scm () : SCM_UNDEFINED;
408             }
409         }
410       else
411         {
412           System * line
413             = dynamic_cast<System*> (unsmob_grob (break_criterion));
414           if (sc->line_l () != line)
415             {
416               sc = sc->find_broken_piece (line);
417
418             }
419
420           /* now: !sc || (sc && sc->line_l () == line) */
421           if (!sc)
422             return SCM_UNDEFINED;
423
424           /* now: sc && sc->line_l () == line */
425           if (!line)
426             return sc->self_scm();
427           /*
428             This was introduced in 1.3.49 as a measure to prevent
429             programming errors. It looks expensive (?).
430
431             TODO:
432                 
433             benchmark , document when (what kind of programming
434             errors) this happens.
435           */
436           if (sc->common_refpoint (line, X_AXIS)
437                && sc->common_refpoint (line, Y_AXIS))
438             {
439               return sc->self_scm ();
440             }
441           return SCM_UNDEFINED;
442         }
443     }
444   else if (ly_pair_p (src)) // SCM_CONSP (src))  // huh?
445     {
446       SCM oldcar =ly_car (src);
447       /*
448         UGH! breaks on circular lists.
449       */
450       SCM newcar = do_break_substitution (oldcar);
451       SCM oldcdr = ly_cdr (src);
452       
453       if (newcar == SCM_UNDEFINED
454           && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
455         {
456           /*
457             This is tail-recursion, ie. 
458             
459             return do_break_substution (cdr, break_criterion);
460
461             We don't want to rely on the compiler to do this.  Without
462             tail-recursion, this easily crashes with a stack overflow.  */
463           src =  oldcdr;
464           goto again;
465         }
466
467       SCM newcdr = do_break_substitution (oldcdr);
468       return scm_cons (newcar, newcdr);
469     }
470   else
471     return src;
472
473   return src;
474 }
475
476 void
477 Grob::handle_broken_dependencies ()
478 {
479   Spanner * s= dynamic_cast<Spanner*> (this);
480   if (original_l_ && s)
481     return;
482
483   if (s)
484     {
485       for (int i = 0;  i< s->broken_into_l_arr_ .size (); i++)
486         {
487           Grob * sc = s->broken_into_l_arr_[i];
488           System * l = sc->line_l ();
489
490           set_break_subsititution (l ? l->self_scm () : SCM_UNDEFINED);
491           sc->mutable_property_alist_ =
492             do_break_substitution (mutable_property_alist_);
493
494         }
495     }
496
497
498   System *line = line_l ();
499
500   if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
501     {
502       set_break_subsititution (line ? line->self_scm () : SCM_UNDEFINED);
503       mutable_property_alist_ = do_break_substitution (mutable_property_alist_);
504     }
505   else if (dynamic_cast <System*> (this))
506     {
507       set_break_subsititution (SCM_UNDEFINED);
508       mutable_property_alist_ = do_break_substitution (mutable_property_alist_);
509     }
510   else
511     {
512       /*
513         This element is `invalid'; it has been removed from all
514         dependencies, so let's junk the element itself.
515
516         do not do this for System, since that would remove
517         references to the originals of score-grobs, which get then GC'd
518  (a bad thing.)
519       */
520       suicide ();
521     }
522 }
523
524 /*
525  Note that we still want references to this element to be
526  rearranged, and not silently thrown away, so we keep pointers
527  like {broken_into_{drul,array}, original}
528 */
529 void
530 Grob::suicide ()
531 {
532   mutable_property_alist_ = SCM_EOL;
533   immutable_property_alist_ = SCM_EOL;
534
535   set_extent (SCM_EOL, Y_AXIS);
536   set_extent (SCM_EOL, X_AXIS);
537
538   for (int a= X_AXIS; a <= Y_AXIS; a++)
539     {
540       dim_cache_[a].offset_callbacks_ = SCM_EOL;
541       dim_cache_[a].offsets_left_ = 0;
542     }
543 }
544
545 void
546 Grob::handle_prebroken_dependencies ()
547 {
548 }
549
550 Grob*
551 Grob::find_broken_piece (System*) const
552 {
553   return 0;
554 }
555
556 /*
557   translate in one direction
558 */
559 void
560 Grob::translate_axis (Real y, Axis a)
561 {
562   if (isinf (y) || isnan (y))
563     programming_error (_ (INFINITY_MSG));
564   else
565     {
566       dim_cache_[a].offset_ += y;
567     }
568 }  
569
570
571 /*
572   Find the offset relative to D.  If   D equals THIS, then it is 0.
573   Otherwise, it recursively defd as
574   
575   OFFSET_ + PARENT_L_->relative_coordinate (D)
576 */
577 Real
578 Grob::relative_coordinate (Grob const*refp, Axis a) const
579 {
580   if (refp == this)
581     return 0.0;
582
583   /*
584     We catch PARENT_L_ == nil case with this, but we crash if we did
585     not ask for the absolute coordinate (ie. REFP == nil.)
586     
587    */
588   if (refp == dim_cache_[a].parent_l_)
589     return get_offset (a);
590   else
591     return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
592 }
593
594
595   
596 /*
597   Invoke callbacks to get offset relative to parent.
598 */
599 Real
600 Grob::get_offset (Axis a) const
601 {
602   Grob *me = (Grob*) this;
603   while (dim_cache_[a].offsets_left_)
604     {
605       int l = --me->dim_cache_[a].offsets_left_;
606       SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_,  gh_int2scm (l));
607       SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
608
609       Real r =  gh_scm2double (retval);
610       if (isinf (r) || isnan (r))
611         {
612           programming_error (INFINITY_MSG);
613           r = 0.0;
614         }
615       me->dim_cache_[a].offset_ +=r;
616     }
617   return dim_cache_[a].offset_;
618 }
619
620
621 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
622 SCM
623 Grob::point_dimension_callback (SCM , SCM)
624 {
625   return ly_interval2scm (Interval (0,0));
626 }
627
628 bool
629 Grob::empty_b (Axis a)const
630 {
631   return ! (gh_pair_p (dim_cache_[a].dimension_) ||
632             gh_procedure_p (dim_cache_[a].dimension_));
633 }
634
635 Interval
636 Grob::extent (Grob * refp, Axis a) const
637 {
638   Real x = relative_coordinate (refp, a);
639
640   
641   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
642   Interval ext ;   
643   if (gh_pair_p (d->dimension_))
644     ;
645   else if (gh_procedure_p (d->dimension_))
646     {
647       /*
648         FIXME: add doco on types, and should typecheck maybe? 
649        */
650       d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
651     }
652   else
653     return ext;
654
655   if (!gh_pair_p (d->dimension_))
656     return ext;
657   
658   ext = ly_scm2interval (d->dimension_);
659
660   SCM extra = get_grob_property (a == X_AXIS
661                                 ? "extra-extent-X"
662                                 : "extra-extent-Y");
663
664   /*
665     signs ?
666    */
667   if (gh_pair_p (extra))
668     {
669       ext[BIGGER] +=  gh_scm2double (ly_cdr (extra));
670       ext[SMALLER] +=   gh_scm2double (ly_car (extra));
671     }
672   
673   extra = get_grob_property (a == X_AXIS
674                                 ? "minimum-extent-X"
675                                 : "minimum-extent-Y");
676   if (gh_pair_p (extra))
677     {
678       ext.unite (Interval (gh_scm2double (ly_car (extra)),
679                            gh_scm2double (ly_cdr (extra))));
680     }
681
682   ext.translate (x);
683   
684   return ext;
685 }
686
687 /*
688   Find the group-element which has both #this# and #s#
689 */
690 Grob * 
691 Grob::common_refpoint (Grob const* s, Axis a) const
692 {
693   /*
694     I don't like the quadratic aspect of this code, but I see no other
695     way. The largest chain of parents might be 10 high or so, so
696     it shouldn't be a real issue. */
697   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
698     for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
699       if (d == c)
700         return (Grob*)d;
701
702   return 0;
703 }
704
705
706 Grob *
707 common_refpoint_of_list (SCM elist, Grob *common, Axis a) 
708 {
709   for (; gh_pair_p (elist); elist = ly_cdr (elist))
710     {
711       Grob * s = unsmob_grob (ly_car (elist));
712       if (!s)
713         continue;
714       if (common)
715         common = common->common_refpoint (s, a);
716       else
717         common = s;
718     }
719
720   return common;
721 }
722
723
724
725 Grob *
726 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a) 
727 {
728   for (int i = arr.size() ; i--; )
729     {
730       Grob * s = arr[i];
731       if (!s)
732         continue;
733
734       if (common)
735         common = common->common_refpoint (s, a);
736       else
737         common = s;
738     }
739
740   return common;
741 }
742
743 String
744 Grob::name () const
745 {
746   SCM meta = get_grob_property ("meta");
747   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
748   nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
749   return  gh_symbol_p (nm) ? ly_symbol2string (nm) :  classname (this);  
750 }
751
752 void
753 Grob::add_offset_callback (SCM cb, Axis a)
754 {
755   if (!has_offset_callback_b (cb, a))
756   {
757     dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
758     dim_cache_[a].offsets_left_ ++;
759   }
760 }
761
762 bool
763 Grob::has_extent_callback_b (SCM cb, Axis a)const
764 {
765   return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
766 }
767
768
769 bool
770 Grob::has_offset_callback_b (SCM cb, Axis a)const
771 {
772   return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
773 }
774
775 void
776 Grob::set_extent (SCM dc, Axis a)
777 {
778   dim_cache_[a].dimension_ =dc;
779 }
780
781 void
782 Grob::set_parent (Grob *g, Axis a)
783 {
784   dim_cache_[a].parent_l_ = g;
785 }
786
787 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
788 SCM
789 Grob::fixup_refpoint (SCM smob)
790 {
791   Grob *me = unsmob_grob (smob);
792   for (int a = X_AXIS; a < NO_AXES; a ++)
793     {
794       Axis ax = (Axis)a;
795       Grob * parent = me->get_parent (ax);
796
797       if (!parent)
798         continue;
799       
800       if (parent->line_l () != me->line_l () && me->line_l ())
801         {
802           Grob * newparent = parent->find_broken_piece (me->line_l ());
803           me->set_parent (newparent, ax);
804         }
805
806       if (Item * i  = dynamic_cast<Item*> (me))
807         {
808           Item *parenti = dynamic_cast<Item*> (parent);
809
810           if (parenti && i)
811             {
812               Direction  my_dir = i->break_status_dir () ;
813               if (my_dir!= parenti->break_status_dir ())
814                 {
815                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
816                   me->set_parent (newparent, ax);
817                 }
818             }
819         }
820     }
821   return smob;
822 }
823
824 void
825 Grob::warning (String s)const
826 {
827   SCM cause = self_scm();
828   while (cause != SCM_EOL && !unsmob_music (cause))
829     {
830       Grob * g = unsmob_grob (cause);
831       cause = g->get_grob_property ("cause");
832     }
833
834   if (Music *m = unsmob_music (cause))
835     {
836       m->origin()->warning (s);
837     }
838   else
839     ::warning (s);
840 }
841
842 void
843 Grob::programming_error (String s)const
844 {
845   s = "Programming error: "  + s;
846   warning (s);
847 }
848
849
850 /****************************************************
851   SMOB funcs
852  ****************************************************/
853
854
855
856 IMPLEMENT_SMOBS (Grob);
857 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
858
859 SCM
860 Grob::mark_smob (SCM ses)
861 {
862   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
863   scm_gc_mark (s->immutable_property_alist_);
864   scm_gc_mark (s->mutable_property_alist_);
865
866   for (int a =0 ; a < 2; a++)
867     {
868       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
869       scm_gc_mark (s->dim_cache_[a].dimension_);
870       Grob *p = s->get_parent (Y_AXIS);
871       if (p)
872         scm_gc_mark (p->self_scm ());
873     }
874   
875   if (s->original_l_)
876     scm_gc_mark (s->original_l_->self_scm ());
877
878   return s->do_derived_mark ();
879 }
880
881 int
882 Grob::print_smob (SCM s, SCM port, scm_print_state *)
883 {
884   Grob *sc = (Grob *) ly_cdr (s);
885      
886   scm_puts ("#<Grob ", port);
887   scm_puts ((char *)sc->name ().ch_C (), port);
888
889   /*
890     don't try to print properties, that is too much hassle.
891    */
892   scm_puts (" >", port);
893   return 1;
894 }
895
896 SCM
897 Grob::do_derived_mark ()
898 {
899   return SCM_EOL;
900 }
901
902 LY_DEFINE(ly_set_grob_property,"ly-set-grob-property", 3, 0, 0,
903 (SCM grob, SCM sym, SCM val),
904 "
905 Set @var{sym} in grob @var{grob} to value @var{val}")
906 {
907   Grob * sc = unsmob_grob (grob);
908   SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
909   SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");  
910
911   if (!type_check_assignment (sym, val, ly_symbol2scm ("backend-type?")))
912     error ("typecheck failed");
913       
914   sc->internal_set_grob_property (sym, val);
915   return SCM_UNSPECIFIED;
916 }
917
918 LY_DEFINE(ly_get_grob_property,
919           "ly-get-grob-property", 2, 0, 0, (SCM grob, SCM sym),
920           "  Get the value of a value in grob @var{g} of property @var{sym}. It
921 will return @code{'()} (end-of-list) if @var{g} doesn't have @var{sym} set.
922 ")
923 {
924   Grob * sc = unsmob_grob (grob);
925   SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
926   SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");  
927
928   return sc->internal_get_grob_property (sym);
929 }
930
931
932 void
933 Grob::discretionary_processing ()
934 {
935 }
936
937
938 LY_DEFINE(spanner_get_bound, "ly-get-spanner-bound", 2 , 0, 0,
939           (SCM slur, SCM dir),
940           "Get one of the bounds of @var{spanner}. @var{dir} may be @code{-1} for
941 left, and @code{1} for right.
942 ")
943 {
944   Spanner * sl = dynamic_cast<Spanner*> (unsmob_grob (slur));
945   SCM_ASSERT_TYPE(sl, slur, SCM_ARG1, __FUNCTION__, "spanner grob");
946   SCM_ASSERT_TYPE(ly_dir_p (dir), slur, SCM_ARG2, __FUNCTION__, "dir");
947   return sl->get_bound (to_dir (dir))->self_scm ();
948 }
949
950 LY_DEFINE(ly_get_paper_var,"ly-get-paper-variable", 2, 0, 0,
951   (SCM grob, SCM sym),
952   "Get a variable from the \\paper block.")
953 {
954   Grob * sc = unsmob_grob (grob);
955   SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
956   SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");  
957
958   return sc->paper_l() ->get_scmvar_scm (sym);
959 }
960
961
962
963 LY_DEFINE(ly_get_extent, "ly-get-extent", 3, 0, 0,
964           (SCM grob, SCM refp, SCM axis),
965           "Get the extent in @var{axis} direction of @var{grob} relative to the
966 grob @var{refp}")
967 {
968   Grob * sc = unsmob_grob (grob);
969   Grob * ref = unsmob_grob (refp);
970   SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
971   SCM_ASSERT_TYPE(ref, refp, SCM_ARG2, __FUNCTION__, "grob");
972   
973   SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG3, __FUNCTION__, "axis");
974
975   return ly_interval2scm ( sc->extent (ref, Axis (gh_scm2int (axis))));
976 }
977
978 LY_DEFINE (ly_get_parent,   "ly-get-parent", 2, 0, 0, (SCM grob, SCM axis),
979            "Get the parent of @var{grob}.  @var{axis} can be 0 for the X-axis, 1
980 for the Y-axis.")
981 {
982   Grob * sc = unsmob_grob (grob);
983   SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
984   SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG2, __FUNCTION__, "axis");
985
986   return sc->get_parent (Axis (gh_scm2int (axis)))->self_scm();
987 }
988
989
990 bool
991 Grob::internal_has_interface (SCM k)
992 {
993   SCM ifs = get_grob_property ("interfaces");
994
995   return scm_memq (k, ifs) != SCM_BOOL_F;
996 }
997
998 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
999
1000 ADD_INTERFACE (Grob, "grob-interface",
1001   "All grobs support this",
1002   "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
1003 Y-extent-callback molecule-callback extra-offset
1004 spacing-procedure
1005 staff-symbol interfaces dependencies extra-extent-X causes meta
1006 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");
1007