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