]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
9aeb95f65195822a2afbd10ac640a4cc46e0dd68
[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 (cause != SCM_EOL && !unsmob_music (cause))
704     {
705       Grob * g = unsmob_grob (cause);
706       cause = g->get_grob_property ("cause");
707     }
708
709   if (Music *m = unsmob_music (cause))
710     {
711       m->origin()->warning (s);
712     }
713   else
714     ::warning (s);
715 }
716
717 void
718 Grob::programming_error (String s)const
719 {
720   s = "Programming error: "  + s;
721   warning (s);
722 }
723
724
725 /****************************************************
726   SMOB funcs
727  ****************************************************/
728
729
730
731 IMPLEMENT_SMOBS (Grob);
732 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
733
734 SCM
735 Grob::mark_smob (SCM ses)
736 {
737   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
738   scm_gc_mark (s->immutable_property_alist_);
739
740   for (int a =0 ; a < 2; a++)
741     {
742       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
743       scm_gc_mark (s->dim_cache_[a].dimension_);
744       
745       /*
746         don't mark the parents. The pointers in the mutable property
747         list form two tree like structures (one for X relations, one
748         for Y relations). Marking these can be done in limited stack
749         space.  If we add the parents, we will jump between X and Y in
750         an erratic manner, leading to much more recursion depth (and
751         core dumps if we link to pthreads.)
752        */
753     }
754   
755   if (s->original_)
756     scm_gc_mark (s->original_->self_scm ());
757
758   s->do_derived_mark ();  
759   return s->mutable_property_alist_;
760 }
761
762 int
763 Grob::print_smob (SCM s, SCM port, scm_print_state *)
764 {
765   Grob *sc = (Grob *) ly_cdr (s);
766      
767   scm_puts ("#<Grob ", port);
768   scm_puts ((char *)sc->name ().to_str0 (), port);
769
770   /*
771     don't try to print properties, that is too much hassle.
772    */
773   scm_puts (" >", port);
774   return 1;
775 }
776
777 SCM
778 Grob::do_derived_mark () const
779 {
780   return SCM_EOL;
781 }
782
783
784
785 void
786 Grob::discretionary_processing ()
787 {
788 }
789
790 bool
791 Grob::internal_has_interface (SCM k)
792 {
793   SCM ifs = get_grob_property ("interfaces");
794
795   return scm_memq (k, ifs) != SCM_BOOL_F;
796 }
797
798
799 /** Return Array of Grobs in SCM list L */
800 Link_array<Grob>
801 ly_scm2grobs (SCM l)
802 {
803   Link_array<Grob> arr;
804
805   for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
806     {
807       SCM e = gh_car (s);
808       arr.push (unsmob_grob (e));
809     }
810
811   arr.reverse ();
812   return arr;
813 }
814
815 /** Return SCM list of Grob array A */
816 SCM
817 ly_grobs2scm (Link_array<Grob> a)
818 {
819   SCM s = SCM_EOL;
820   for (int i = a.size (); i; i--)
821     s = gh_cons (a[i-1]->self_scm (), s);
822
823   return s;
824 }
825
826
827 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
828
829 ADD_INTERFACE (Grob, "grob-interface",
830   "In music notation, lots of symbols are related in some way.  You can
831 think of music notation as a graph where nodes are formed by the
832 symbols, and the arcs by their relations. A grob is a node in that
833 graph.  The directed edges in the graph are formed by references to
834 other grobs (i.e. pointers).  This big graph of grobs specifies the
835 notation problem. The solution of this problem is a description of the
836 printout in closed form, i.e. a list of values.  These values are
837 Molecules.
838
839 All grobs have an X and Y-position on the page.  These X and Y positions
840 are stored in a relative format, so they can easily be combined by
841 stacking them, hanging one grob to the side of another, and coupling
842 them into a grouping-grob.
843
844 Each grob has a reference point (a.k.a.  parent): the position of a grob
845 is stored relative to that reference point. For example the X-reference
846 point of a staccato dot usually is the note head that it applies
847 to. When the note head is moved, the staccato dot moves along
848 automatically.
849
850 A grob is often associated with a symbol, but some grobs do not print
851 any symbols. They take care of grouping objects. For example, there is a
852 separate grob that stacks staves vertically. The @ref{NoteCollision}
853 is also an abstract grob: it only moves around chords, but doesn't print
854 anything.
855 ",
856   "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
857 Y-extent-callback molecule-callback extra-offset spacing-procedure
858 staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent
859 causes meta layer before-line-breaking-callback
860 after-line-breaking-callback extra-Y-extent minimum-X-extent
861 minimum-Y-extent transparent");
862
863