]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
(warning): better robustness fix.
[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 #include "warn.hh"
16 #include "group-interface.hh"
17 #include "misc.hh"
18 #include "paper-score.hh"
19 #include "molecule.hh"
20 #include "grob.hh"
21 #include "warn.hh"
22 #include "spanner.hh"
23 #include "system.hh"
24 #include "item.hh"
25 #include "molecule.hh"
26 #include "misc.hh"
27 #include "music.hh"
28 #include "item.hh"
29
30 #include "ly-smobs.icc"
31
32 /*
33 TODO:
34
35 remove dynamic_cast<Spanner,Item> and put this code into respective
36   subclass.
37 */
38
39 //#define HASHING_FOR_MUTABLE_PROPS
40 #define HASH_SIZE 3
41 #define INFINITY_MSG "Infinity or NaN encountered"
42
43 Grob::Grob (SCM basicprops)
44 {
45   /*
46     fixme: default should be no callback.
47    */
48   self_scm_ = SCM_EOL;
49   pscore_=0;
50   status_ = 0;
51   original_ = 0;
52   immutable_property_alist_ =  basicprops;
53   mutable_property_alist_ = SCM_EOL;
54
55   /*
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.
59    */
60   smobify_self ();
61
62
63 #ifdef HASHING_FOR_MUTABLE_PROPS
64   mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
65 #endif
66   
67   SCM meta = get_grob_property ("meta");
68   if (gh_pair_p (meta))
69     {
70       SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
71
72       /*
73         Switch off interface checks for the moment.
74        */
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;
79     }
80   
81   /*
82     TODO:
83
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.  */
87   
88   char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
89   char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
90   
91   for (int a = X_AXIS; a <= Y_AXIS; a++)
92     {
93       SCM l = get_grob_property (onames[a]);
94
95       if (scm_ilength (l) >=0)
96         {
97           dim_cache_[a].offset_callbacks_ = l;
98           dim_cache_[a].offsets_left_ = scm_ilength (l);
99         }
100       else
101         {
102           programming_error ("[XY]-offset-callbacks must be a list");
103         }
104
105       SCM cb = get_grob_property (enames[a]);
106
107       /*
108         Should change default to be empty? 
109       */
110       if (cb != SCM_BOOL_F
111           && !gh_procedure_p (cb) && !gh_pair_p (cb)
112           && gh_procedure_p (get_grob_property ("molecule-callback"))
113           )
114         cb = molecule_extent_proc;
115     
116       dim_cache_[a].dimension_ = cb;
117     }
118
119 }
120
121 Grob::Grob (Grob const&s)
122    : dim_cache_ (s.dim_cache_)
123 {
124   original_ = (Grob*) &s;
125   self_scm_ = SCM_EOL;
126
127   immutable_property_alist_ = s.immutable_property_alist_;
128
129   mutable_property_alist_ = SCM_EOL;
130   
131   /*
132     No properties are copied. That is the job of handle_broken_dependencies.
133    */
134   
135   status_ = s.status_;
136   pscore_ = s.pscore_;
137
138   smobify_self ();
139
140 #ifdef HASHING_FOR_MUTABLE_PROPS
141   mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
142 #endif
143 }
144
145 Grob::~Grob ()
146 {
147   /*
148     do nothing scm-ish and no unprotecting here.
149    */
150 }
151
152
153
154
155
156 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
157 SCM
158 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
159 {
160   Grob *s = unsmob_grob (element_smob);
161   Axis a = (Axis) gh_scm2int (scm_axis);
162
163   Molecule *m = s->get_molecule ();
164   Interval e ;
165   if (m)
166     e = m->extent (a);
167   return ly_interval2scm (e);
168 }
169
170 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
171 SCM
172 Grob::preset_extent (SCM element_smob, SCM scm_axis)
173 {
174   Grob *s = unsmob_grob (element_smob);
175   Axis a = (Axis) gh_scm2int (scm_axis);
176
177   SCM ext = s->get_grob_property ((a == X_AXIS)
178                                  ? "X-extent"
179                                  : "Y-extent");
180   
181   if (gh_pair_p (ext))
182     {
183       Real l = gh_scm2double (ly_car (ext));
184       Real r = gh_scm2double (ly_cdr (ext));
185       return ly_interval2scm (Interval (l, r));
186     }
187   
188   return ly_interval2scm (Interval ());
189 }
190
191
192
193 Paper_def*
194 Grob::get_paper ()  const
195 {
196  return pscore_ ? pscore_->paper_ : 0;
197 }
198
199 void
200 Grob::calculate_dependencies (int final, int busy, SCM funcname)
201 {
202   if (status_ >= final)
203     return;
204
205   if (status_== busy)
206     {
207       programming_error ("Element is busy, come back later");
208       return;
209     }
210   
211   status_= busy;
212
213   for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
214        d = ly_cdr (d))
215     {
216       unsmob_grob (ly_car (d))
217         ->calculate_dependencies (final, busy, funcname);
218     }
219
220   
221   SCM proc = internal_get_grob_property (funcname);
222   if (gh_procedure_p (proc))
223     gh_call1 (proc, this->self_scm ());
224  
225   status_= final;
226 }
227
228 Molecule *
229 Grob::get_molecule ()  const
230 {
231   if (!live())
232     {
233       return 0;
234       
235     }
236   
237   SCM mol = get_grob_property ("molecule");
238   if (unsmob_molecule (mol))
239     return unsmob_molecule (mol);
240
241   mol =  get_uncached_molecule ();
242   
243   if (live ())
244     {
245       Grob *me = (Grob*)this;
246       me->set_grob_property ("molecule", mol);
247     }
248   
249   return unsmob_molecule (mol);  
250 }
251
252 SCM
253 Grob::get_uncached_molecule ()const
254 {
255   SCM proc = get_grob_property ("molecule-callback");
256
257   SCM  mol = SCM_EOL;
258   if (gh_procedure_p (proc)) 
259     mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
260   
261   Molecule *m = unsmob_molecule (mol);
262   
263   if (unsmob_molecule (mol))
264     {
265       SCM origin = ly_symbol2scm ("no-origin");
266       
267       if (store_locations_global_b)
268         {
269           SCM cause = get_grob_property ("cause");
270           if (Music*m = unsmob_music (cause))
271             {
272               SCM music_origin = m->get_mus_property ("origin");
273               if (unsmob_input (music_origin))
274                 origin = music_origin;
275             }
276       }
277
278       // ugr.
279       
280       mol = Molecule (m->extent_box (),
281                       scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
282                       ). smobbed_copy ();
283
284       m = unsmob_molecule (mol);
285     }
286   
287   /*
288     transparent retains dimensions of element.
289    */
290   if (m && to_boolean (get_grob_property ("transparent")))
291     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
292
293   return mol;
294 }
295
296 /*
297   
298   VIRTUAL STUBS
299
300  */
301 void
302 Grob::do_break_processing ()
303 {
304 }
305
306 System *
307 Grob::get_system () const
308 {
309   return 0;
310 }
311
312 void
313 Grob::add_dependency (Grob*e)
314 {
315   if (e)
316     {
317       Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
318     }
319   else
320     programming_error ("Null dependency added");
321 }
322
323
324 void
325 Grob::handle_broken_dependencies ()
326 {
327   Spanner * sp = dynamic_cast<Spanner*> (this);
328   if (original_ && sp)
329     return;
330
331   if (sp)
332     {
333       for (SCM s = mutable_property_alist_; gh_pair_p(s);
334            s = gh_cdr(s))
335         {
336           sp->substitute_one_mutable_property (gh_caar (s),
337                                               gh_cdar (s));
338           
339         }
340     }
341
342   System *system = get_system ();
343
344   if (live ()
345       && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
346     {
347       substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
348                                      mutable_property_alist_);
349     }
350   else if (dynamic_cast <System*> (this))
351     {
352       substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
353     }
354   else
355     {
356       /*
357         This element is `invalid'; it has been removed from all
358         dependencies, so let's junk the element itself.
359
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
362         thing.)
363  
364       */
365       suicide ();
366     }
367 }
368
369 /*
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}
373 */
374 void
375 Grob::suicide ()
376 {
377   if (!live ())
378     return; 
379
380 #if 0 // see below. 
381    String nm = name();
382 #endif
383   
384   mutable_property_alist_ = SCM_EOL;
385   immutable_property_alist_ = SCM_EOL;
386
387   set_extent (SCM_EOL, Y_AXIS);
388   set_extent (SCM_EOL, X_AXIS);
389
390   for (int a= X_AXIS; a <= Y_AXIS; a++)
391     {
392       dim_cache_[a].offset_callbacks_ = SCM_EOL;
393       dim_cache_[a].offsets_left_ = 0;
394     }
395
396 #if 0
397   /*
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.
402    */
403   mutable_property_alist_ = scm_acons (ly_symbol2scm ("name"),
404                                        scm_makfrom0str (nm.to_str0()),
405                                        mutable_property_alist_
406                                        );
407 #endif
408 }
409
410 void
411 Grob::handle_prebroken_dependencies ()
412 {
413   /*
414     Don't do this in the derived method, since we want to keep access to
415     mutable_property_alist_ centralized.
416    */
417   if (original_)
418     {
419       Item * it = dynamic_cast<Item*> (this);
420       substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
421                                original_->mutable_property_alist_);
422     }
423 }
424
425 Grob*
426 Grob::find_broken_piece (System*) const
427 {
428   return 0;
429 }
430
431 /*
432   translate in one direction
433 */
434 void
435 Grob::translate_axis (Real y, Axis a)
436 {
437   if (isinf (y) || isnan (y))
438     programming_error (_ (INFINITY_MSG));
439   else
440     {
441       dim_cache_[a].offset_ += y;
442     }
443 }  
444
445
446 /*
447   Find the offset relative to D.  If   D equals THIS, then it is 0.
448   Otherwise, it recursively defd as
449   
450   OFFSET_ + PARENT_L_->relative_coordinate (D)
451 */
452 Real
453 Grob::relative_coordinate (Grob const*refp, Axis a) const
454 {
455   if (refp == this)
456     return 0.0;
457
458   /*
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.)
461     
462    */
463   if (refp == dim_cache_[a].parent_)
464     return get_offset (a);
465   else
466     return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
467 }
468
469
470   
471 /*
472   Invoke callbacks to get offset relative to parent.
473 */
474 Real
475 Grob::get_offset (Axis a) const
476 {
477   Grob *me = (Grob*) this;
478   while (dim_cache_[a].offsets_left_)
479     {
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));
483
484       Real r =  gh_scm2double (retval);
485       if (isinf (r) || isnan (r))
486         {
487           programming_error (INFINITY_MSG);
488           r = 0.0;
489         }
490       me->dim_cache_[a].offset_ +=r;
491     }
492   return dim_cache_[a].offset_;
493 }
494
495
496 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
497 SCM
498 Grob::point_dimension_callback (SCM , SCM)
499 {
500   return ly_interval2scm (Interval (0,0));
501 }
502
503 bool
504 Grob::empty_b (Axis a)const
505 {
506   return ! (gh_pair_p (dim_cache_[a].dimension_) ||
507             gh_procedure_p (dim_cache_[a].dimension_));
508 }
509
510 Interval
511 Grob::extent (Grob * refp, Axis a) const
512 {
513   Real x = relative_coordinate (refp, a);
514
515   
516   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
517   Interval ext ;   
518   if (gh_pair_p (d->dimension_))
519     ;
520   else if (gh_procedure_p (d->dimension_))
521     {
522       /*
523         FIXME: add doco on types, and should typecheck maybe? 
524        */
525       d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
526     }
527   else
528     return ext;
529
530   if (!gh_pair_p (d->dimension_))
531     return ext;
532   
533   ext = ly_scm2interval (d->dimension_);
534
535   SCM extra = get_grob_property (a == X_AXIS
536                                 ? "extra-X-extent"
537                                 : "extra-Y-extent");
538
539   /*
540     signs ?
541    */
542   if (gh_pair_p (extra))
543     {
544       ext[BIGGER] +=  gh_scm2double (ly_cdr (extra));
545       ext[SMALLER] +=   gh_scm2double (ly_car (extra));
546     }
547   
548   extra = get_grob_property (a == X_AXIS
549                                 ? "minimum-X-extent"
550                                 : "minimum-Y-extent");
551   if (gh_pair_p (extra))
552     {
553       ext.unite (Interval (gh_scm2double (ly_car (extra)),
554                            gh_scm2double (ly_cdr (extra))));
555     }
556
557   ext.translate (x);
558   
559   return ext;
560 }
561
562 /*
563   Find the group-element which has both #this# and #s#
564 */
565 Grob * 
566 Grob::common_refpoint (Grob const* s, Axis a) const
567 {
568   /*
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_)
574       if (d == c)
575         return (Grob*)d;
576
577   return 0;
578 }
579
580
581 Grob *
582 common_refpoint_of_list (SCM elist, Grob *common, Axis a) 
583 {
584   for (; gh_pair_p (elist); elist = ly_cdr (elist))
585     {
586       Grob * s = unsmob_grob (ly_car (elist));
587       if (!s)
588         continue;
589       if (common)
590         common = common->common_refpoint (s, a);
591       else
592         common = s;
593     }
594
595   return common;
596 }
597
598
599
600 Grob *
601 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a) 
602 {
603   for (int i = arr.size() ; i--; )
604     {
605       Grob * s = arr[i];
606       if (!s)
607         continue;
608
609       if (common)
610         common = common->common_refpoint (s, a);
611       else
612         common = s;
613     }
614
615   return common;
616 }
617
618 String
619 Grob::name () const
620 {
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);  
625 }
626
627 void
628 Grob::add_offset_callback (SCM cb, Axis a)
629 {
630   if (!has_offset_callback_b (cb, a))
631   {
632     dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
633     dim_cache_[a].offsets_left_ ++;
634   }
635 }
636
637 bool
638 Grob::has_extent_callback_b (SCM cb, Axis a)const
639 {
640   return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
641 }
642
643
644 bool
645 Grob::has_offset_callback_b (SCM cb, Axis a)const
646 {
647   return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
648 }
649
650 void
651 Grob::set_extent (SCM dc, Axis a)
652 {
653   dim_cache_[a].dimension_ =dc;
654 }
655
656 void
657 Grob::set_parent (Grob *g, Axis a)
658 {
659   dim_cache_[a].parent_ = g;
660 }
661
662 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
663 SCM
664 Grob::fixup_refpoint (SCM smob)
665 {
666   Grob *me = unsmob_grob (smob);
667   for (int a = X_AXIS; a < NO_AXES; a ++)
668     {
669       Axis ax = (Axis)a;
670       Grob * parent = me->get_parent (ax);
671
672       if (!parent)
673         continue;
674       
675       if (parent->get_system () != me->get_system () && me->get_system ())
676         {
677           Grob * newparent = parent->find_broken_piece (me->get_system ());
678           me->set_parent (newparent, ax);
679         }
680
681       if (Item * i  = dynamic_cast<Item*> (me))
682         {
683           Item *parenti = dynamic_cast<Item*> (parent);
684
685           if (parenti && i)
686             {
687               Direction  my_dir = i->break_status_dir () ;
688               if (my_dir!= parenti->break_status_dir ())
689                 {
690                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
691                   me->set_parent (newparent, ax);
692                 }
693             }
694         }
695     }
696   return smob;
697 }
698
699 void
700 Grob::warning (String s)const
701 {
702   SCM cause = self_scm();
703   while (Grob * g = unsmob_grob (cause))
704     {
705       cause = g->get_grob_property ("cause");
706     }
707
708   if (Music *m = unsmob_music (cause))
709     {
710       m->origin()->warning (s);
711     }
712   else
713     ::warning (s);
714 }
715
716 void
717 Grob::programming_error (String s)const
718 {
719   s = "Programming error: "  + s;
720   warning (s);
721 }
722
723
724 /****************************************************
725   SMOB funcs
726  ****************************************************/
727
728
729
730 IMPLEMENT_SMOBS (Grob);
731 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
732
733 SCM
734 Grob::mark_smob (SCM ses)
735 {
736   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
737   scm_gc_mark (s->immutable_property_alist_);
738
739   for (int a =0 ; a < 2; a++)
740     {
741       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
742       scm_gc_mark (s->dim_cache_[a].dimension_);
743       
744       /*
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.)
751        */
752     }
753   
754   if (s->original_)
755     scm_gc_mark (s->original_->self_scm ());
756
757   s->do_derived_mark ();  
758   return s->mutable_property_alist_;
759 }
760
761 int
762 Grob::print_smob (SCM s, SCM port, scm_print_state *)
763 {
764   Grob *sc = (Grob *) ly_cdr (s);
765      
766   scm_puts ("#<Grob ", port);
767   scm_puts ((char *)sc->name ().to_str0 (), port);
768
769   /*
770     don't try to print properties, that is too much hassle.
771    */
772   scm_puts (" >", port);
773   return 1;
774 }
775
776 SCM
777 Grob::do_derived_mark () const
778 {
779   return SCM_EOL;
780 }
781
782
783
784 void
785 Grob::discretionary_processing ()
786 {
787 }
788
789 bool
790 Grob::internal_has_interface (SCM k)
791 {
792   SCM ifs = get_grob_property ("interfaces");
793
794   return scm_memq (k, ifs) != SCM_BOOL_F;
795 }
796
797
798 /** Return Array of Grobs in SCM list L */
799 Link_array<Grob>
800 ly_scm2grobs (SCM l)
801 {
802   Link_array<Grob> arr;
803
804   for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
805     {
806       SCM e = gh_car (s);
807       arr.push (unsmob_grob (e));
808     }
809
810   arr.reverse ();
811   return arr;
812 }
813
814 /** Return SCM list of Grob array A */
815 SCM
816 ly_grobs2scm (Link_array<Grob> a)
817 {
818   SCM s = SCM_EOL;
819   for (int i = a.size (); i; i--)
820     s = gh_cons (a[i-1]->self_scm (), s);
821
822   return s;
823 }
824
825
826 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
827
828 ADD_INTERFACE (Grob, "grob-interface",
829   "In music notation, lots of symbols are related in some way.  You can
830 think of music notation as a graph where nodes are formed by the
831 symbols, and the arcs by their relations. A grob is a node in that
832 graph.  The directed edges in the graph are formed by references to
833 other grobs (i.e. pointers).  This big graph of grobs specifies the
834 notation problem. The solution of this problem is a description of the
835 printout in closed form, i.e. a list of values.  These values are
836 Molecules.
837
838 All grobs have an X and Y-position on the page.  These X and Y positions
839 are stored in a relative format, so they can easily be combined by
840 stacking them, hanging one grob to the side of another, and coupling
841 them into a grouping-grob.
842
843 Each grob has a reference point (a.k.a.  parent): the position of a grob
844 is stored relative to that reference point. For example the X-reference
845 point of a staccato dot usually is the note head that it applies
846 to. When the note head is moved, the staccato dot moves along
847 automatically.
848
849 A grob is often associated with a symbol, but some grobs do not print
850 any symbols. They take care of grouping objects. For example, there is a
851 separate grob that stacks staves vertically. The @ref{NoteCollision}
852 is also an abstract grob: it only moves around chords, but doesn't print
853 anything.
854 ",
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 causes meta layer before-line-breaking-callback
859 after-line-breaking-callback extra-Y-extent minimum-X-extent
860 minimum-Y-extent transparent");
861
862