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