]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
* lily/include/music-iterator.hh (class Music_iterator): remove
[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--2003 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_ = 0;
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 (is_number_pair (ext))
182     return ext;
183   else
184     return ly_interval2scm (Interval());
185 }
186
187
188
189 Paper_def*
190 Grob::get_paper ()  const
191 {
192  return pscore_ ? pscore_->paper_ : 0;
193 }
194
195 void
196 Grob::calculate_dependencies (int final, int busy, SCM funcname)
197 {
198   if (status_ >= final)
199     return;
200
201   if (status_== busy)
202     {
203       programming_error ("Element is busy, come back later");
204       return;
205     }
206   
207   status_= busy;
208
209   for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
210        d = ly_cdr (d))
211     {
212       unsmob_grob (ly_car (d))
213         ->calculate_dependencies (final, busy, funcname);
214     }
215
216   
217   SCM proc = internal_get_grob_property (funcname);
218   if (gh_procedure_p (proc))
219     gh_call1 (proc, this->self_scm ());
220  
221   status_= final;
222 }
223
224 Molecule *
225 Grob::get_molecule ()  const
226 {
227   if (!live())
228     {
229       return 0;
230     }
231   
232   SCM mol = get_grob_property ("molecule");
233   if (unsmob_molecule (mol))
234     return unsmob_molecule (mol);
235
236   mol = get_uncached_molecule ();
237   
238   if (live ())
239     {
240       Grob *me = (Grob*)this;
241       me->set_grob_property ("molecule", mol);
242     }
243   
244   return unsmob_molecule (mol);  
245 }
246
247 SCM
248 Grob::get_uncached_molecule ()const
249 {
250   SCM proc = get_grob_property ("molecule-callback");
251
252   SCM  mol = SCM_EOL;
253   if (gh_procedure_p (proc)) 
254     mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
255   
256   Molecule *m = unsmob_molecule (mol);
257   
258   if (unsmob_molecule (mol))
259     {
260       SCM origin = ly_symbol2scm ("no-origin");
261       
262       if (store_locations_global_b)
263         {
264           SCM cause = get_grob_property ("cause");
265           if (Music*m = unsmob_music (cause))
266             {
267               SCM music_origin = m->get_mus_property ("origin");
268               if (unsmob_input (music_origin))
269                 origin = music_origin;
270             }
271       }
272
273       // ugr.
274       
275       mol = Molecule (m->extent_box (),
276                       scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
277                       ). smobbed_copy ();
278
279       m = unsmob_molecule (mol);
280     }
281   
282   /*
283     transparent retains dimensions of element.
284    */
285   if (m && to_boolean (get_grob_property ("transparent")))
286     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
287
288   return mol;
289 }
290
291 /*
292   
293   VIRTUAL STUBS
294
295  */
296 void
297 Grob::do_break_processing ()
298 {
299 }
300
301 System *
302 Grob::get_system () const
303 {
304   return 0;
305 }
306
307 void
308 Grob::add_dependency (Grob*e)
309 {
310   if (e)
311     {
312       Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
313     }
314   else
315     programming_error ("Null dependency added");
316 }
317
318
319 void
320 Grob::handle_broken_dependencies ()
321 {
322   Spanner * sp = dynamic_cast<Spanner*> (this);
323   if (original_ && sp)
324     return;
325
326   if (sp)
327     {
328       for (SCM s = mutable_property_alist_; gh_pair_p(s);
329            s = gh_cdr(s))
330         {
331           sp->substitute_one_mutable_property (gh_caar (s),
332                                               gh_cdar (s));
333           
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