]> git.donarmstrong.com Git - lilypond.git/blob - lily/score-element.cc
f1974fe63abd5b622d3cb2c7b7a114c33d0cd61f
[lilypond.git] / lily / score-element.cc
1 /*
2   score-elem.cc -- implement Score_element
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <string.h>
11 #include <math.h>
12
13 #include "input-smob.hh"
14 #include "libc-extension.hh"
15 #include "group-interface.hh"
16 #include "misc.hh"
17 #include "paper-score.hh"
18 #include "paper-def.hh"
19 #include "lookup.hh"
20 #include "molecule.hh"
21 #include "score-element.hh"
22 #include "debug.hh"
23 #include "spanner.hh"
24 #include "line-of-score.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 "dimension-cache.hh"
31 #include "side-position-interface.hh"
32 #include "item.hh"
33
34 #include "ly-smobs.icc"
35
36 /*
37 TODO:
38
39 remove dynamic_cast<Spanner,Item> and put this code into respective
40   subclass.
41 */
42
43
44 #define INFINITY_MSG "Infinity or NaN encountered"
45
46 Score_element::Score_element(SCM basicprops)
47 {
48   /*
49     fixme: default should be no callback.
50    */
51   set_extent_callback (molecule_extent, X_AXIS);
52   set_extent_callback (molecule_extent, Y_AXIS);    
53
54   pscore_l_=0;
55   lookup_l_ =0;
56   status_i_ = 0;
57   original_l_ = 0;
58   immutable_property_alist_ =  basicprops;
59   mutable_property_alist_ = SCM_EOL;
60
61   smobify_self ();
62 }
63
64
65 Score_element::Score_element (Score_element const&s)
66    : dim_cache_ (s.dim_cache_)
67 {
68   original_l_ =(Score_element*) &s;
69   immutable_property_alist_ = s.immutable_property_alist_;
70   mutable_property_alist_ = SCM_EOL;
71   
72   status_i_ = s.status_i_;
73   lookup_l_ = s.lookup_l_;
74   pscore_l_ = s.pscore_l_;
75
76   smobify_self ();
77 }
78
79 Score_element::~Score_element()
80 {
81   /*
82     do nothing scm-ish and no unprotecting here.
83    */
84 }
85
86
87 SCM
88 Score_element::get_elt_property (const char *nm) const
89 {
90   SCM sym = ly_symbol2scm (nm);
91   return get_elt_property (sym);
92 }
93
94 SCM
95 Score_element::get_elt_property (SCM sym) const
96 {
97   SCM s = scm_sloppy_assq(sym, mutable_property_alist_);
98   if (s != SCM_BOOL_F)
99     return gh_cdr (s);
100
101   s = scm_sloppy_assq (sym, immutable_property_alist_);
102   return (s == SCM_BOOL_F) ? SCM_EOL : gh_cdr (s); 
103 }
104
105 /*
106   Remove the value associated with KEY, and return it. The result is
107   that a next call will yield SCM_UNDEFINED (and not the underlying
108   `basic' property.
109 */
110 SCM
111 Score_element::remove_elt_property (const char* key)
112 {
113   SCM val = get_elt_property (key);
114   if (val != SCM_EOL)
115     set_elt_property (key, SCM_EOL);
116   return val;
117 }
118
119 void
120 Score_element::set_elt_property (const char* k, SCM v)
121 {
122   SCM s = ly_symbol2scm (k);
123   set_elt_property (s, v);
124 }
125
126 /*
127   Puts the k, v in the immutable_property_alist_, which is convenient for
128   storing variables that are needed during the breaking process. (eg.
129   Line_of_score::rank : int )
130  */
131 void
132 Score_element::set_immutable_elt_property (const char*k, SCM v)
133 {
134   SCM s = ly_symbol2scm (k);
135   set_immutable_elt_property (s, v);
136 }
137
138 void
139 Score_element::set_immutable_elt_property (SCM s, SCM v)
140 {
141   immutable_property_alist_ = gh_cons (gh_cons (s,v), mutable_property_alist_);
142   mutable_property_alist_ = scm_assq_remove_x (mutable_property_alist_, s);
143 }
144 void
145 Score_element::set_elt_property (SCM s, SCM v)
146 {
147   mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
148 }
149
150
151 Interval
152 Score_element::molecule_extent (Score_element *s, Axis a)
153 {
154   Molecule m = s->get_molecule ();
155   return m.extent(a);
156 }
157
158 Interval
159 Score_element::preset_extent (Score_element  *s , Axis a)
160 {
161   SCM ext = s->get_elt_property ((a == X_AXIS)
162                                  ? "extent-X"
163                                  : "extent-Y");
164   
165   if (gh_pair_p (ext))
166     {
167       Real l = gh_scm2double (gh_car (ext));
168       Real r = gh_scm2double (gh_cdr (ext));
169       l *= s->paper_l ()->get_var ("staffspace");
170       r *= s->paper_l ()->get_var ("staffspace");
171       return Interval (l, r);
172     }
173   
174   return Interval ();
175 }
176
177
178
179 Paper_def*
180 Score_element::paper_l ()  const
181 {
182  return pscore_l_ ? pscore_l_->paper_l_ : 0;
183 }
184
185 Lookup const *
186 Score_element::lookup_l () const
187 {
188   /*
189     URG junkthis, caching is clumsy.
190    */
191   if (!lookup_l_)
192     {
193       Score_element * urg = (Score_element*)this;
194       SCM sz = urg->remove_elt_property ("font-size");
195       int i = (gh_number_p (sz))
196         ? gh_scm2int  (sz)
197         : 0;
198
199       urg->lookup_l_ =  (Lookup*)pscore_l_->paper_l_->lookup_l (i);
200     }
201   return lookup_l_;
202 }
203
204 void
205 Score_element::add_processing()
206 {
207   assert (status_i_ >=0);
208   if (status_i_)
209     return;
210   status_i_ ++;
211
212   do_add_processing();
213 }
214
215 void
216 Score_element::calculate_dependencies (int final, int busy, SCM funcname)
217 {
218   assert (status_i_ >=0);
219
220   if (status_i_ >= final)
221     return;
222
223   if (status_i_== busy)
224     {
225       programming_error ("Element is busy, come back later");
226       return;
227     }
228   
229   status_i_= busy;
230
231   for (SCM d=  get_elt_property ("dependencies"); gh_pair_p (d); d = gh_cdr (d))
232     {
233       unsmob_element (gh_car (d))
234         ->calculate_dependencies (final, busy, funcname);
235     }
236
237   // ughugh.
238   String s = ly_symbol2string (funcname);
239   SCM proc = get_elt_property (s.ch_C());
240   if (gh_procedure_p (proc))
241     gh_call1 (proc, this->self_scm ());
242   
243   status_i_= final;
244
245 }
246
247 Molecule
248 Score_element::get_molecule ()  const
249 {
250   SCM proc = get_elt_property ("molecule-callback");
251
252   SCM mol = SCM_EOL;
253   if (gh_procedure_p (proc)) 
254     mol = gh_apply (proc, gh_list (this->self_scm (), SCM_UNDEFINED));
255
256     
257   SCM origin =get_elt_property ("origin");
258   if (!unsmob_input (origin))
259     origin =ly_symbol2scm ("no-origin");
260   
261   if (gh_pair_p (mol))
262     {
263       // ugr.
264         mol = gh_cons (gh_list (origin, gh_car (mol), SCM_UNDEFINED), gh_cdr (mol));
265     }
266
267
268   Molecule m (create_molecule (mol));
269
270   /*
271     This is almost the same as setting molecule-callback to #f, but
272     this retains the dimensions of this element, which means that you
273     can erase elements individually.  */
274   if (to_boolean (get_elt_property ("transparent")))
275     m = Molecule (m.extent_box (), SCM_EOL);
276
277   return m;
278 }
279
280
281 /*
282   
283   VIRTUAL STUBS
284
285  */
286 void
287 Score_element::do_break_processing()
288 {
289 }
290
291
292 void
293 Score_element::do_add_processing()
294 {
295 }
296
297
298 MAKE_SCHEME_CALLBACK(Score_element,brew_molecule)
299
300 /*
301   ugh.
302  */  
303 SCM
304 Score_element::brew_molecule (SCM smob) 
305 {
306   Score_element * sc = unsmob_element (smob);
307   SCM glyph = sc->get_elt_property ("glyph");
308   if (gh_string_p (glyph))
309     {
310       return sc->lookup_l ()->afm_find (String (ly_scm2string (glyph))).create_scheme ();
311     }
312   else
313     {
314       return SCM_EOL;
315     }
316 }
317
318
319 Line_of_score *
320 Score_element::line_l() const
321 {
322   return 0;
323 }
324
325 void
326 Score_element::add_dependency (Score_element*e)
327 {
328   if (e)
329     {
330       Pointer_group_interface gi (this, "dependencies");
331       gi.add_element (e);
332     }
333   else
334     programming_error ("Null dependency added");
335 }
336
337
338
339
340 /**
341       Do break substitution in S, using CRITERION. Return new value.
342       CRITERION is either a SMOB pointer to the desired line, or a number
343       representing the break direction. Do not modify SRC.
344 */
345 SCM
346 Score_element::handle_broken_smobs (SCM src, SCM criterion)
347 {
348  again:
349   Score_element *sc = unsmob_element (src);
350   if (sc)
351     {
352       if (gh_number_p (criterion))
353         {
354           Item * i = dynamic_cast<Item*> (sc);
355           Direction d = to_dir (criterion);
356           if (i && i->break_status_dir () != d)
357             {
358               Item *br = i->find_prebroken_piece (d);
359               return  (br) ? br->self_scm () : SCM_UNDEFINED;
360             }
361         }
362       else
363         {
364           Line_of_score * line
365             = dynamic_cast<Line_of_score*> (unsmob_element (criterion));
366           if (sc->line_l () != line)
367             {
368               sc = sc->find_broken_piece (line);
369
370             }
371
372           /* now: !sc || (sc && sc->line_l () == line) */
373           if (!sc)
374             return SCM_UNDEFINED;
375
376           /* now: sc && sc->line_l () == line */
377           if (!line
378               || (sc->common_refpoint (line, X_AXIS)
379                   && sc->common_refpoint (line, Y_AXIS)))
380             {
381               return sc->self_scm ();
382             }
383           return SCM_UNDEFINED;
384         }
385     }
386   else if (gh_pair_p (src))
387     {
388       SCM oldcar =gh_car (src);
389       /*
390         UGH! breaks on circular lists.
391       */
392       SCM newcar = handle_broken_smobs (oldcar, criterion);
393       SCM oldcdr = gh_cdr (src);
394       
395       if (newcar == SCM_UNDEFINED
396           && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
397         {
398           /*
399             This is tail-recursion, ie. 
400             
401             return handle_broken_smobs (cdr, criterion);
402
403             We don't want to rely on the compiler to do this.  Without
404             tail-recursion, this easily crashes with a stack overflow.  */
405           src =  oldcdr;
406           goto again;
407         }
408
409       SCM newcdr = handle_broken_smobs (oldcdr, criterion);
410       return gh_cons (newcar, newcdr);
411     }
412   else
413     return src;
414
415   return src;
416 }
417
418 void
419 Score_element::handle_broken_dependencies()
420 {
421   Spanner * s= dynamic_cast<Spanner*> (this);
422   if (original_l_ && s)
423     return;
424
425   if (s)
426     {
427       for (int i = 0;  i< s->broken_into_l_arr_ .size (); i++)
428         {
429           Score_element * sc = s->broken_into_l_arr_[i];
430           Line_of_score * l = sc->line_l ();
431           sc->mutable_property_alist_ =
432             handle_broken_smobs (mutable_property_alist_,
433                                  l ? l->self_scm () : SCM_UNDEFINED);
434         }
435     }
436
437
438   Line_of_score *line = line_l();
439
440   if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
441     {
442       mutable_property_alist_
443         = handle_broken_smobs (mutable_property_alist_,
444                                line ? line->self_scm () : SCM_UNDEFINED);
445     }
446   else if (dynamic_cast <Line_of_score*> (this))
447     {
448       mutable_property_alist_ = handle_broken_smobs (mutable_property_alist_,
449                                             SCM_UNDEFINED);
450     }
451   else
452     {
453       /*
454         This element is `invalid'; it has been removed from all
455         dependencies, so let's junk the element itself.
456
457         do not do this for Line_of_score, since that would remove
458         references to the originals of score-elts, which get then GC'd
459         (a bad thing.)
460       */
461       suicide();
462     }
463 }
464
465 /*
466  Note that we still want references to this element to be
467  rearranged, and not silently thrown away, so we keep pointers
468  like {broken_into_{drul,array}, original}
469 */
470 void
471 Score_element::suicide ()
472 {
473   mutable_property_alist_ = SCM_EOL;
474   immutable_property_alist_ = SCM_EOL;
475   set_extent_callback (0, Y_AXIS);
476   set_extent_callback (0, X_AXIS);
477
478   for (int a= X_AXIS; a <= Y_AXIS; a++)
479     {
480       dim_cache_[a].off_callbacks_.clear ();
481     }
482 }
483
484 void
485 Score_element::handle_prebroken_dependencies()
486 {
487 }
488
489 Score_element*
490 Score_element::find_broken_piece (Line_of_score*) const
491 {
492   return 0;
493 }
494
495 void
496 Score_element::translate_axis (Real y, Axis a)
497 {
498   if (isinf (y) || isnan (y))
499     programming_error (_(INFINITY_MSG));
500   else
501     {
502       dim_cache_[a].offset_ += y;
503     }
504 }  
505
506 Real
507 Score_element::relative_coordinate (Score_element const*refp, Axis a) const
508 {
509   if (refp == this)
510     return 0.0;
511
512   /*
513     We catch PARENT_L_ == nil case with this, but we crash if we did
514     not ask for the absolute coordinate (ie. REFP == nil.)
515     
516    */
517   if (refp == dim_cache_[a].parent_l_)
518     return get_offset (a);
519   else
520     return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
521 }
522
523 Real
524 Score_element::get_offset (Axis a) const
525 {
526   Score_element *me = (Score_element*) this;
527   while (dim_cache_[a].off_callbacks_.size ())
528     {
529       Offset_callback c = dim_cache_[a].off_callbacks_[0];
530       me->dim_cache_[a].off_callbacks_.del (0);
531       Real r =  (*c) (me,a );
532       if (isinf (r) || isnan (r))
533         {
534           programming_error (INFINITY_MSG);
535           r = 0.0;
536         }
537       me->dim_cache_[a].offset_ +=r;
538     }
539   return dim_cache_[a].offset_;
540 }
541
542
543 Interval
544 Score_element::point_dimension_callback (Score_element* , Axis)
545 {
546   return Interval (0,0);
547 }
548
549 bool
550 Score_element::empty_b (Axis a)const
551 {
552   return !dim_cache_[a].extent_callback_l_;
553 }
554
555 Interval
556 Score_element::extent (Axis a) const
557 {
558   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
559   if (!d->extent_callback_l_)
560     {
561       d->dim_.set_empty ();
562     }
563   else if (!d->valid_b_)
564     {
565       d->dim_= (*d->extent_callback_l_ ) ((Score_element*)this, a);
566       d->valid_b_ = true;
567     }
568
569   Interval ext = d->dim_;
570   
571   if (empty_b (a)) 
572     return ext;
573
574   SCM extra = get_elt_property (a == X_AXIS
575                                 ? "extra-extent-X"
576                                 : "extra-extent-Y");
577
578   /*
579     signs ?
580    */
581   Real s = paper_l ()->get_var ("staffspace");
582   if (gh_pair_p (extra))
583     {
584       ext[BIGGER] +=  s * gh_scm2double (gh_cdr (extra));
585       ext[SMALLER] +=  s * gh_scm2double (gh_car (extra));
586     }
587   
588   extra = get_elt_property (a == X_AXIS
589                                 ? "minimum-extent-X"
590                                 : "minimum-extent-Y");
591   if (gh_pair_p (extra))
592     {
593       ext.unite (Interval (s * gh_scm2double (gh_car (extra)),
594                            s * gh_scm2double (gh_cdr (extra))));
595     }
596   
597   return ext;
598 }
599
600
601 Score_element*
602 Score_element::parent_l (Axis a) const
603 {
604   return  dim_cache_[a].parent_l_;
605 }
606
607 Score_element * 
608 Score_element::common_refpoint (Score_element const* s, Axis a) const
609 {
610   /*
611     I don't like the quadratic aspect of this code, but I see no other
612     way. The largest chain of parents might be 10 high or so, so
613     it shouldn't be a real issue. */
614   for (Score_element const *c = this; c; c = c->dim_cache_[a].parent_l_)
615     for (Score_element const * d = s; d; d = d->dim_cache_[a].parent_l_)
616       if (d == c)
617         return (Score_element*)d;
618
619   return 0;
620 }
621
622
623 Score_element *
624 Score_element::common_refpoint (SCM elist, Axis a) const
625 {
626   Score_element * common = (Score_element*) this;
627   for (; gh_pair_p (elist); elist = gh_cdr (elist))
628     {
629       Score_element * s = unsmob_element (gh_car (elist));
630       if (s)
631         common = common->common_refpoint (s, a);
632     }
633
634   return common;
635 }
636
637 String
638 Score_element::name () const
639 {
640   SCM nm = get_elt_property ("name");
641   
642   return  nm == SCM_EOL ? classname (this) :ly_scm2string (nm) ;  
643 }
644
645 void
646 Score_element::add_offset_callback (Offset_callback cb, Axis a)
647 {
648   dim_cache_[a].off_callbacks_.push (cb);
649 }
650
651 bool
652 Score_element::has_extent_callback_b (Extent_callback cb, Axis a)const
653 {
654   return cb == dim_cache_[a].extent_callback_l_;
655 }
656
657
658 bool
659 Score_element::has_extent_callback_b (Axis a) const
660 {
661   return dim_cache_[a].extent_callback_l_;
662 }
663
664 bool
665 Score_element::has_offset_callback_b (Offset_callback cb, Axis a)const
666 {
667   for (int i= dim_cache_[a].off_callbacks_.size (); i--;)
668     {
669       if (dim_cache_[a].off_callbacks_[i] == cb)
670         return true;
671     }
672   return false;
673 }
674
675 void
676 Score_element::set_extent_callback (Dim_cache_callback dc, Axis a)
677 {
678   dim_cache_[a].extent_callback_l_ = dc ;
679 }
680
681                                     
682 void
683 Score_element::set_parent (Score_element *g, Axis a)
684 {
685   dim_cache_[a].parent_l_ = g;
686 }
687
688 MAKE_SCHEME_CALLBACK(Score_element,fixup_refpoint);
689 SCM
690 Score_element::fixup_refpoint (SCM smob)
691 {
692   Score_element *me = unsmob_element (smob);
693   for (int a = X_AXIS; a < NO_AXES; a ++)
694     {
695       Axis ax = (Axis)a;
696       Score_element * parent = me->parent_l (ax);
697
698       if (!parent)
699         continue;
700       
701       if (parent->line_l () != me->line_l () && me->line_l ())
702         {
703           Score_element * newparent = parent->find_broken_piece (me->line_l ());
704           me->set_parent (newparent, ax);
705         }
706
707       if (Item * i  = dynamic_cast<Item*> (me))
708         {
709           Item *parenti = dynamic_cast<Item*> (parent);
710
711           if (parenti && i)
712             {
713               Direction  my_dir = i->break_status_dir () ;
714               if (my_dir!= parenti->break_status_dir())
715                 {
716                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
717                   me->set_parent (newparent, ax);
718                 }
719             }
720         }
721     }
722   return smob;
723 }
724
725
726
727 /****************************************************
728   SMOB funcs
729  ****************************************************/
730
731
732 IMPLEMENT_UNSMOB(Score_element, element);
733 IMPLEMENT_SMOBS(Score_element);
734 IMPLEMENT_DEFAULT_EQUAL_P(Score_element);
735
736 SCM
737 Score_element::mark_smob (SCM ses)
738 {
739   Score_element * s = (Score_element*) SCM_CELL_WORD_1(ses);
740   scm_gc_mark (s->immutable_property_alist_);
741   scm_gc_mark (s->mutable_property_alist_);
742   
743   if (s->parent_l (Y_AXIS))
744     scm_gc_mark (s->parent_l (Y_AXIS)->self_scm ());
745   if (s->parent_l (X_AXIS))
746     scm_gc_mark (s->parent_l (X_AXIS)->self_scm ());
747
748   if (s->original_l_)
749     scm_gc_mark (s->original_l_->self_scm ());
750   return s->do_derived_mark ();
751 }
752
753 int
754 Score_element::print_smob (SCM s, SCM port, scm_print_state *)
755 {
756   Score_element *sc = (Score_element *) gh_cdr (s);
757      
758   scm_puts ("#<Score_element ", port);
759   scm_puts ((char *)sc->name ().ch_C(), port);
760
761   /*
762     don't try to print properties, that is too much hassle.
763    */
764   scm_puts (" >", port);
765   return 1;
766 }
767
768 SCM
769 Score_element::do_derived_mark ()
770 {
771   return SCM_EOL;
772 }
773
774
775 SCM
776 ly_set_elt_property (SCM elt, SCM sym, SCM val)
777 {
778   Score_element * sc = unsmob_element (elt);
779
780   if (!gh_symbol_p (sym))
781     {
782       error ("Not a symbol");
783       ly_display_scm (sym);
784       return SCM_UNSPECIFIED;
785     }
786
787   if (sc)
788     {
789       sc->set_elt_property (sym, val);
790     }
791   else
792     {
793       error ("Not a score element");
794       ly_display_scm (elt);
795     }
796
797   return SCM_UNSPECIFIED;
798 }
799
800
801 SCM
802 ly_get_elt_property (SCM elt, SCM sym)
803 {
804   Score_element * sc = unsmob_element (elt);
805   
806   if (sc)
807     {
808       return sc->get_elt_property (sym);
809     }
810   else
811     {
812       error ("Not a score element");
813       ly_display_scm (elt);
814     }
815   return SCM_UNSPECIFIED;
816 }
817
818
819 void
820 Score_element::discretionary_processing()
821 {
822 }
823
824
825
826 SCM
827 spanner_get_bound (SCM slur, SCM dir)
828 {
829   return dynamic_cast<Spanner*> (unsmob_element (slur))->get_bound (to_dir (dir))->self_scm ();
830 }
831
832
833
834 static SCM interfaces_sym;
835 #define UNDEFINED_ARG 
836 static void
837 init_functions ()
838 {
839   interfaces_sym = scm_permanent_object (ly_symbol2scm ("interfaces"));
840   
841   scm_make_gsubr ("ly-get-elt-property", 2, 0, 0, (SCM(*)(UNDEFINED_ARG))ly_get_elt_property);
842   scm_make_gsubr ("ly-set-elt-property", 3, 0, 0, (SCM(*)(UNDEFINED_ARG))ly_set_elt_property);
843   scm_make_gsubr ("ly-get-spanner-bound", 2 , 0, 0, (SCM(*)(UNDEFINED_ARG)) spanner_get_bound);
844 }
845
846 bool
847 Score_element::has_interface (SCM k)
848 {
849   //  if (mutable_property_alist_ == SCM_EOL)
850   //  return false;
851   
852   SCM ifs = get_elt_property (interfaces_sym);
853
854   return scm_memq (k, ifs) != SCM_BOOL_F;
855 }
856
857 void
858 Score_element::set_interface (SCM k)
859 {
860   if (has_interface (k))
861     return ;
862   else
863     {
864       set_elt_property (interfaces_sym,
865                         gh_cons  (k, get_elt_property (interfaces_sym)));
866     }
867 }
868
869
870 ADD_SCM_INIT_FUNC(scoreelt, init_functions);
871