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