]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
release commit
[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
16 #include "group-interface.hh"
17 #include "misc.hh"
18 #include "paper-score.hh"
19 #include "paper-def.hh"
20 #include "molecule.hh"
21 #include "grob.hh"
22 #include "debug.hh"
23 #include "spanner.hh"
24 #include "system.hh"
25 #include "item.hh"
26 #include "paper-column.hh"
27 #include "molecule.hh"
28 #include "misc.hh"
29 #include "paper-outputter.hh"
30 #include "music.hh"
31 #include "item.hh"
32
33 #include "ly-smobs.icc"
34
35 /*
36 TODO:
37
38 remove dynamic_cast<Spanner,Item> and put this code into respective
39   subclass.
40 */
41
42
43 #define INFINITY_MSG "Infinity or NaN encountered"
44
45 Grob::Grob (SCM basicprops)
46 {
47   /*
48     fixme: default should be no callback.
49    */
50
51   pscore_l_=0;
52   status_c_ = 0;
53   original_l_ = 0;
54   immutable_property_alist_ =  basicprops;
55   mutable_property_alist_ = SCM_EOL;
56
57   /*
58     We do smobify_self() as the first step. Since the object lives on
59     the heap, none of its SCM variables are protected from GC. After
60     smobify_self(), they are.
61    */
62   smobify_self ();
63
64
65   SCM meta = get_grob_property ("meta");
66   if (gh_pair_p (meta))
67     {
68       SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
69
70       /*
71         do it directly to bypass interface checks.
72        */
73       mutable_property_alist_ = gh_cons (gh_cons (ly_symbol2scm ("interfaces"),
74                                                   gh_cdr (ifs)),
75                                          mutable_property_alist_);
76     }
77   
78   /*
79     TODO:
80
81     destill this into a function, so we can re-init the immutable
82     properties with a new BASICPROPS value after creation. Convenient
83     eg. when using \override with StaffSymbol.  */
84   
85   char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86   char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
87   
88   for (int a = X_AXIS; a <= Y_AXIS; a++)
89     {
90       SCM l = get_grob_property (onames[a]);
91
92       if (scm_ilength (l) >=0)
93         {
94           dim_cache_[a].offset_callbacks_ = l;
95           dim_cache_[a].offsets_left_ = scm_ilength (l);
96         }
97       else
98         {
99           programming_error ("[XY]-offset-callbacks must be a list");
100         }
101
102       SCM cb = get_grob_property (enames[a]);
103
104       /*
105         Should change default to be empty? 
106       */
107       if (cb != SCM_BOOL_F
108           && !gh_procedure_p (cb) && !gh_pair_p (cb)
109           && gh_procedure_p (get_grob_property ("molecule-callback"))
110           )
111         cb = molecule_extent_proc;
112     
113       dim_cache_[a].dimension_ = cb;
114     }
115
116 }
117
118 Grob::Grob (Grob const&s)
119    : dim_cache_ (s.dim_cache_)
120 {
121   original_l_ = (Grob*) &s;
122   immutable_property_alist_ = s.immutable_property_alist_;
123   mutable_property_alist_ = SCM_EOL;
124
125   /*
126     No properties are copied. That is the job of handle_broken_dependencies.
127    */
128   
129   status_c_ = s.status_c_;
130   pscore_l_ = s.pscore_l_;
131
132   smobify_self ();
133
134
135 }
136
137 Grob::~Grob ()
138 {
139   /*
140     do nothing scm-ish and no unprotecting here.
141    */
142 }
143
144
145
146 extern void check_interfaces_for_property (Grob const *me, SCM sym);
147
148 void
149 Grob::internal_set_grob_property (SCM s, SCM v)
150 {
151 #ifndef NDEBUG
152   if (internal_type_checking_global_b)
153     {
154       assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
155       check_interfaces_for_property(this, s);
156     }
157 #endif
158
159   
160   mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
161 }
162
163
164 SCM
165 Grob::internal_get_grob_property (SCM sym) const
166 {
167   SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
168   if (s != SCM_BOOL_F)
169     return ly_cdr (s);
170
171   s = scm_sloppy_assq (sym, immutable_property_alist_);
172   
173 #ifndef NDEBUG
174   if (internal_type_checking_global_b && gh_pair_p (s))
175     {
176       assert (type_check_assignment (sym, gh_cdr (s), ly_symbol2scm ("backend-type?")));
177       check_interfaces_for_property(this, sym);
178     }
179 #endif
180
181   return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s); 
182 }
183
184 /*
185   Remove the value associated with KEY, and return it. The result is
186   that a next call will yield SCM_EOL (and not the underlying
187   `basic' property.
188 */
189 SCM
190 Grob::remove_grob_property (const char* key)
191 {
192   SCM val = get_grob_property (key);
193   if (val != SCM_EOL)
194     set_grob_property (key, SCM_EOL);
195   return val;
196 }
197
198
199
200 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
201 SCM
202 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
203 {
204   Grob *s = unsmob_grob (element_smob);
205   Axis a = (Axis) gh_scm2int (scm_axis);
206
207   Molecule *m = s->get_molecule ();
208   Interval e ;
209   if (m)
210     e = m->extent (a);
211   return ly_interval2scm (e);
212 }
213
214 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
215 SCM
216 Grob::preset_extent (SCM element_smob, SCM scm_axis)
217 {
218   Grob *s = unsmob_grob (element_smob);
219   Axis a = (Axis) gh_scm2int (scm_axis);
220
221   SCM ext = s->get_grob_property ((a == X_AXIS)
222                                  ? "extent-X"
223                                  : "extent-Y");
224   
225   if (gh_pair_p (ext))
226     {
227       Real l = gh_scm2double (ly_car (ext));
228       Real r = gh_scm2double (ly_cdr (ext));
229       return ly_interval2scm (Interval (l, r));
230     }
231   
232   return ly_interval2scm (Interval ());
233 }
234
235
236
237 Paper_def*
238 Grob::paper_l ()  const
239 {
240  return pscore_l_ ? pscore_l_->paper_l_ : 0;
241 }
242
243 void
244 Grob::calculate_dependencies (int final, int busy, SCM funcname)
245 {
246   if (status_c_ >= final)
247     return;
248
249   if (status_c_== busy)
250     {
251       programming_error ("Element is busy, come back later");
252       return;
253     }
254   
255   status_c_= busy;
256
257   for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
258        d = ly_cdr (d))
259     {
260       unsmob_grob (ly_car (d))
261         ->calculate_dependencies (final, busy, funcname);
262     }
263
264   
265   SCM proc = internal_get_grob_property (funcname);
266   if (gh_procedure_p (proc))
267     gh_call1 (proc, this->self_scm ());
268  
269   status_c_= final;
270 }
271
272 Molecule *
273 Grob::get_molecule ()  const
274 {
275   if (immutable_property_alist_ == SCM_EOL)
276     {
277       return 0;
278       
279     }
280   
281   SCM mol = get_grob_property ("molecule");
282   if (unsmob_molecule (mol))
283     return unsmob_molecule (mol);
284
285   mol =  get_uncached_molecule ();
286   
287   Grob *me = (Grob*)this;
288   me->set_grob_property ("molecule", mol);
289   
290   return unsmob_molecule (mol);  
291 }
292 SCM
293 Grob::get_uncached_molecule ()const
294 {
295   SCM proc = get_grob_property ("molecule-callback");
296
297   SCM  mol = SCM_EOL;
298   if (gh_procedure_p (proc)) 
299     mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
300
301   
302   Molecule *m = unsmob_molecule (mol);
303   
304   if (unsmob_molecule (mol))
305     {
306       SCM origin = ly_symbol2scm ("no-origin");
307       
308       if (store_locations_global_b){
309         SCM cause = get_grob_property ("cause");
310         if (Music*m = unsmob_music (cause))
311           {
312             SCM music_origin = m->get_mus_property ("origin");
313             if (unsmob_input (music_origin))
314               origin = music_origin;
315           }
316       }
317
318       // ugr.
319       
320       mol = Molecule (m->extent_box (),
321                       scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
322                       ). smobbed_copy ();
323
324       m = unsmob_molecule (mol);
325     }
326   
327   /*
328     transparent retains dimensions of element.
329    */
330   if (m && to_boolean (get_grob_property ("transparent")))
331     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
332
333   return mol;
334 }
335
336 /*
337   
338   VIRTUAL STUBS
339
340  */
341 void
342 Grob::do_break_processing ()
343 {
344 }
345
346
347
348
349
350
351 System *
352 Grob::line_l () const
353 {
354   return 0;
355 }
356
357 void
358 Grob::add_dependency (Grob*e)
359 {
360   if (e)
361     {
362       Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
363     }
364   else
365     programming_error ("Null dependency added");
366 }
367
368
369
370 void
371 Grob::handle_broken_dependencies ()
372 {
373   Spanner * s= dynamic_cast<Spanner*> (this);
374   if (original_l_ && s)
375     return;
376
377   if (s)
378     {
379       for (int i = 0;  i< s->broken_into_l_arr_ .size (); i++)
380         {
381           Grob * sc = s->broken_into_l_arr_[i];
382           System * l = sc->line_l ();
383
384           set_break_subsititution (l ? l->self_scm () : SCM_UNDEFINED);
385           sc->mutable_property_alist_ =
386             substitute_mutable_properties (mutable_property_alist_);
387
388         }
389     }
390
391
392   System *line = line_l ();
393
394   if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
395     {
396       set_break_subsititution (line ? line->self_scm () : SCM_UNDEFINED);
397       mutable_property_alist_ = substitute_mutable_properties (mutable_property_alist_);
398     }
399   else if (dynamic_cast <System*> (this))
400     {
401       set_break_subsititution (SCM_UNDEFINED);
402       mutable_property_alist_ = substitute_mutable_properties (mutable_property_alist_);
403     }
404   else
405     {
406       /*
407         This element is `invalid'; it has been removed from all
408         dependencies, so let's junk the element itself.
409
410         do not do this for System, since that would remove references
411         to the originals of score-grobs, which get then GC'd (a bad
412         thing.)
413  
414       */
415       suicide ();
416     }
417 }
418
419 /*
420  Note that we still want references to this element to be
421  rearranged, and not silently thrown away, so we keep pointers
422  like {broken_into_{drul,array}, original}
423 */
424 void
425 Grob::suicide ()
426 {
427   mutable_property_alist_ = SCM_EOL;
428   immutable_property_alist_ = SCM_EOL;
429
430   set_extent (SCM_EOL, Y_AXIS);
431   set_extent (SCM_EOL, X_AXIS);
432
433   for (int a= X_AXIS; a <= Y_AXIS; a++)
434     {
435       dim_cache_[a].offset_callbacks_ = SCM_EOL;
436       dim_cache_[a].offsets_left_ = 0;
437     }
438 }
439
440 void
441 Grob::handle_prebroken_dependencies ()
442 {
443 }
444
445 Grob*
446 Grob::find_broken_piece (System*) const
447 {
448   return 0;
449 }
450
451 /*
452   translate in one direction
453 */
454 void
455 Grob::translate_axis (Real y, Axis a)
456 {
457   if (isinf (y) || isnan (y))
458     programming_error (_ (INFINITY_MSG));
459   else
460     {
461       dim_cache_[a].offset_ += y;
462     }
463 }  
464
465
466 /*
467   Find the offset relative to D.  If   D equals THIS, then it is 0.
468   Otherwise, it recursively defd as
469   
470   OFFSET_ + PARENT_L_->relative_coordinate (D)
471 */
472 Real
473 Grob::relative_coordinate (Grob const*refp, Axis a) const
474 {
475   if (refp == this)
476     return 0.0;
477
478   /*
479     We catch PARENT_L_ == nil case with this, but we crash if we did
480     not ask for the absolute coordinate (ie. REFP == nil.)
481     
482    */
483   if (refp == dim_cache_[a].parent_l_)
484     return get_offset (a);
485   else
486     return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
487 }
488
489
490   
491 /*
492   Invoke callbacks to get offset relative to parent.
493 */
494 Real
495 Grob::get_offset (Axis a) const
496 {
497   Grob *me = (Grob*) this;
498   while (dim_cache_[a].offsets_left_)
499     {
500       int l = --me->dim_cache_[a].offsets_left_;
501       SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_,  gh_int2scm (l));
502       SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
503
504       Real r =  gh_scm2double (retval);
505       if (isinf (r) || isnan (r))
506         {
507           programming_error (INFINITY_MSG);
508           r = 0.0;
509         }
510       me->dim_cache_[a].offset_ +=r;
511     }
512   return dim_cache_[a].offset_;
513 }
514
515
516 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
517 SCM
518 Grob::point_dimension_callback (SCM , SCM)
519 {
520   return ly_interval2scm (Interval (0,0));
521 }
522
523 bool
524 Grob::empty_b (Axis a)const
525 {
526   return ! (gh_pair_p (dim_cache_[a].dimension_) ||
527             gh_procedure_p (dim_cache_[a].dimension_));
528 }
529
530 Interval
531 Grob::extent (Grob * refp, Axis a) const
532 {
533   Real x = relative_coordinate (refp, a);
534
535   
536   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
537   Interval ext ;   
538   if (gh_pair_p (d->dimension_))
539     ;
540   else if (gh_procedure_p (d->dimension_))
541     {
542       /*
543         FIXME: add doco on types, and should typecheck maybe? 
544        */
545       d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
546     }
547   else
548     return ext;
549
550   if (!gh_pair_p (d->dimension_))
551     return ext;
552   
553   ext = ly_scm2interval (d->dimension_);
554
555   SCM extra = get_grob_property (a == X_AXIS
556                                 ? "extra-extent-X"
557                                 : "extra-extent-Y");
558
559   /*
560     signs ?
561    */
562   if (gh_pair_p (extra))
563     {
564       ext[BIGGER] +=  gh_scm2double (ly_cdr (extra));
565       ext[SMALLER] +=   gh_scm2double (ly_car (extra));
566     }
567   
568   extra = get_grob_property (a == X_AXIS
569                                 ? "minimum-extent-X"
570                                 : "minimum-extent-Y");
571   if (gh_pair_p (extra))
572     {
573       ext.unite (Interval (gh_scm2double (ly_car (extra)),
574                            gh_scm2double (ly_cdr (extra))));
575     }
576
577   ext.translate (x);
578   
579   return ext;
580 }
581
582 /*
583   Find the group-element which has both #this# and #s#
584 */
585 Grob * 
586 Grob::common_refpoint (Grob const* s, Axis a) const
587 {
588   /*
589     I don't like the quadratic aspect of this code, but I see no other
590     way. The largest chain of parents might be 10 high or so, so
591     it shouldn't be a real issue. */
592   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
593     for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
594       if (d == c)
595         return (Grob*)d;
596
597   return 0;
598 }
599
600
601 Grob *
602 common_refpoint_of_list (SCM elist, Grob *common, Axis a) 
603 {
604   for (; gh_pair_p (elist); elist = ly_cdr (elist))
605     {
606       Grob * s = unsmob_grob (ly_car (elist));
607       if (!s)
608         continue;
609       if (common)
610         common = common->common_refpoint (s, a);
611       else
612         common = s;
613     }
614
615   return common;
616 }
617
618
619
620 Grob *
621 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a) 
622 {
623   for (int i = arr.size() ; i--; )
624     {
625       Grob * s = arr[i];
626       if (!s)
627         continue;
628
629       if (common)
630         common = common->common_refpoint (s, a);
631       else
632         common = s;
633     }
634
635   return common;
636 }
637
638 String
639 Grob::name () const
640 {
641   SCM meta = get_grob_property ("meta");
642   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
643   nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
644   return  gh_symbol_p (nm) ? ly_symbol2string (nm) :  classname (this);  
645 }
646
647 void
648 Grob::add_offset_callback (SCM cb, Axis a)
649 {
650   if (!has_offset_callback_b (cb, a))
651   {
652     dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
653     dim_cache_[a].offsets_left_ ++;
654   }
655 }
656
657 bool
658 Grob::has_extent_callback_b (SCM cb, Axis a)const
659 {
660   return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
661 }
662
663
664 bool
665 Grob::has_offset_callback_b (SCM cb, Axis a)const
666 {
667   return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
668 }
669
670 void
671 Grob::set_extent (SCM dc, Axis a)
672 {
673   dim_cache_[a].dimension_ =dc;
674 }
675
676 void
677 Grob::set_parent (Grob *g, Axis a)
678 {
679   dim_cache_[a].parent_l_ = g;
680 }
681
682 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
683 SCM
684 Grob::fixup_refpoint (SCM smob)
685 {
686   Grob *me = unsmob_grob (smob);
687   for (int a = X_AXIS; a < NO_AXES; a ++)
688     {
689       Axis ax = (Axis)a;
690       Grob * parent = me->get_parent (ax);
691
692       if (!parent)
693         continue;
694       
695       if (parent->line_l () != me->line_l () && me->line_l ())
696         {
697           Grob * newparent = parent->find_broken_piece (me->line_l ());
698           me->set_parent (newparent, ax);
699         }
700
701       if (Item * i  = dynamic_cast<Item*> (me))
702         {
703           Item *parenti = dynamic_cast<Item*> (parent);
704
705           if (parenti && i)
706             {
707               Direction  my_dir = i->break_status_dir () ;
708               if (my_dir!= parenti->break_status_dir ())
709                 {
710                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
711                   me->set_parent (newparent, ax);
712                 }
713             }
714         }
715     }
716   return smob;
717 }
718
719 void
720 Grob::warning (String s)const
721 {
722   SCM cause = self_scm();
723   while (cause != SCM_EOL && !unsmob_music (cause))
724     {
725       Grob * g = unsmob_grob (cause);
726       cause = g->get_grob_property ("cause");
727     }
728
729   if (Music *m = unsmob_music (cause))
730     {
731       m->origin()->warning (s);
732     }
733   else
734     ::warning (s);
735 }
736
737 void
738 Grob::programming_error (String s)const
739 {
740   s = "Programming error: "  + s;
741   warning (s);
742 }
743
744
745 /****************************************************
746   SMOB funcs
747  ****************************************************/
748
749
750
751 IMPLEMENT_SMOBS (Grob);
752 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
753
754 SCM
755 Grob::mark_smob (SCM ses)
756 {
757   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
758   scm_gc_mark (s->immutable_property_alist_);
759   scm_gc_mark (s->mutable_property_alist_);
760
761   for (int a =0 ; a < 2; a++)
762     {
763       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
764       scm_gc_mark (s->dim_cache_[a].dimension_);
765       
766       /*
767         don't mark the parents. The pointers in the mutable property
768         list form two tree like structures (one for X relations, one
769         for Y relations). Marking these can be done in limited stack
770         space.  If we add the parents, we will jump between X and Y in
771         an erratic manner, leading to much more recursion depth (and
772         core dumps if we link to pthreads.)
773        */
774     }
775   
776   if (s->original_l_)
777     scm_gc_mark (s->original_l_->self_scm ());
778
779   return s->do_derived_mark ();
780 }
781
782 int
783 Grob::print_smob (SCM s, SCM port, scm_print_state *)
784 {
785   Grob *sc = (Grob *) ly_cdr (s);
786      
787   scm_puts ("#<Grob ", port);
788   scm_puts ((char *)sc->name ().ch_C (), port);
789
790   /*
791     don't try to print properties, that is too much hassle.
792    */
793   scm_puts (" >", port);
794   return 1;
795 }
796
797 SCM
798 Grob::do_derived_mark ()
799 {
800   return SCM_EOL;
801 }
802
803
804
805 void
806 Grob::discretionary_processing ()
807 {
808 }
809
810 bool
811 Grob::internal_has_interface (SCM k)
812 {
813   SCM ifs = get_grob_property ("interfaces");
814
815   return scm_memq (k, ifs) != SCM_BOOL_F;
816 }
817
818 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
819
820 ADD_INTERFACE (Grob, "grob-interface",
821   "All grobs support this",
822   "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
823 Y-extent-callback molecule-callback extra-offset
824 spacing-procedure
825 staff-symbol interfaces dependencies extra-extent-X causes meta
826 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");
827