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