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