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