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