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