]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
6ae709739f0bfd8612089fde9e690b744e15e29c
[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   "In music notation, lots of symbols are related in some way.  You can
822 think of music notation as a graph where nodes are formed by the
823 symbols, and the arcs by their relations. A grob is a node in that
824 graph.  The directed edges in the graph are formed by references to
825 other grobs (i.e. pointers).  This big graph of grobs specifies the
826 notation problem. The solution of this problem is a description of the
827 printout in closed form, i.e. a list of values.  These values are
828 Molecules.
829
830 All grobs have an X and Y-position on the page.  These X and Y positions
831 are stored in a relative format, so they can easily be combined by
832 stacking them, hanging one grob to the side of another, and coupling
833 them into a grouping-grob.
834
835 Each grob has a reference point (a.k.a.  parent): the position of a grob
836 is stored relative to that reference point. For example the X-reference
837 point of a staccato dot usually is the note head that it applies
838 to. When the note head is moved, the staccato dot moves along
839 automatically.
840
841 A grob is often associated with a symbol, but some grobs do not print
842 any symbols. They take care of grouping objects. For example, there is a
843 separate grob that stacks staves vertically. The @ref{NoteCollision}
844 is also an abstract grob: it only moves around chords, but doesn't print
845 anything.
846 ",
847   "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
848 Y-extent-callback molecule-callback extra-offset
849 spacing-procedure
850 staff-symbol interfaces dependencies extra-extent-X causes meta
851 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");
852
853