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