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