]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
Update.
[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--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include "grob.hh"
10
11 #include <cstring>
12 #include <math.h>
13
14 #include "main.hh"
15 #include "input-smob.hh"
16 #include "warn.hh"
17 #include "group-interface.hh"
18 #include "misc.hh"
19 #include "paper-score.hh"
20 #include "stencil.hh"
21 #include "warn.hh"
22 #include "system.hh"
23 #include "item.hh"
24 #include "stencil.hh"
25 #include "misc.hh"
26 #include "music.hh"
27 #include "item.hh"
28
29 #include "ly-smobs.icc"
30
31 Grob *
32 Grob::clone (int count) const
33 {
34   return new Grob (*this, count);
35 }
36
37 /* TODO:
38
39 - remove dynamic_cast<Spanner, Item> and put this code into respective
40 subclass.  */
41
42 #define HASH_SIZE 3
43 #define INFINITY_MSG "Infinity or NaN encountered"
44
45 Grob::Grob (SCM basicprops,
46             Object_key const *key)
47 {
48   key_ = key;
49   /* FIXME: default should be no callback.  */
50   self_scm_ = SCM_EOL;
51   pscore_ = 0;
52   status_ = 0;
53   original_ = 0;
54   immutable_property_alist_ = basicprops;
55   mutable_property_alist_ = SCM_EOL;
56
57   /* We do smobify_self () as the first step.  Since the object lives
58      on the heap, none of its SCM variables are protected from
59      GC. After smobify_self (), they are.  */
60   smobify_self ();
61
62   /*
63     We always get a new key object for a new grob.
64   */
65   scm_gc_unprotect_object (key_->self_scm ());
66   SCM meta = get_property ("meta");
67   if (scm_is_pair (meta))
68     {
69       SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
70
71       /* Switch off interface checks for the moment.  */
72       bool itc = do_internal_type_checking_global;
73       do_internal_type_checking_global = false;
74       internal_set_property (ly_symbol2scm ("interfaces"), scm_cdr (ifs));
75       do_internal_type_checking_global = itc;
76     }
77
78   /* TODO:
79
80   - destill this into a function, so we can re-init the immutable
81   properties with a new BASICPROPS value after
82   creation. Convenient eg. when using \override with
83   StaffSymbol.  */
84
85   char const *onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86   char const *xnames[] = {"X-extent", "Y-extent"};
87   char const *enames[] = {"X-extent-callback", "Y-extent-callback"};
88
89   for (int a = X_AXIS; a <= Y_AXIS; a++)
90     {
91       SCM l = get_property (onames[a]);
92
93       if (scm_ilength (l) >= 0)
94         {
95           dim_cache_[a].offset_callbacks_ = l;
96           dim_cache_[a].offsets_left_ = scm_ilength (l);
97         }
98       else
99         programming_error ("[XY]-offset-callbacks must be a list");
100
101       SCM cb = get_property (enames[a]);
102       if (cb == SCM_BOOL_F)
103         {
104           dim_cache_[a].dimension_ = SCM_BOOL_F;
105         }
106
107       SCM xt = get_property (xnames[a]);
108       if (is_number_pair (xt))
109         {
110           dim_cache_[a].dimension_ = xt;
111         }
112       else if (ly_c_procedure_p (cb))
113         {
114           dim_cache_[a].dimension_callback_ = cb;
115         }
116       else if (cb == SCM_EOL
117                && ly_c_procedure_p (get_property ("print-function")))
118         dim_cache_[a].dimension_callback_ = stencil_extent_proc;
119     }
120 }
121
122 Grob::Grob (Grob const &s, int copy_index)
123   : dim_cache_ (s.dim_cache_)
124 {
125   key_ = new Copied_key (s.key_, copy_index);
126   original_ = (Grob *) & s;
127   self_scm_ = SCM_EOL;
128
129   immutable_property_alist_ = s.immutable_property_alist_;
130   mutable_property_alist_ = SCM_EOL;
131
132   /* No properties are copied.  That is the job of
133      handle_broken_dependencies.  */
134   status_ = s.status_;
135   pscore_ = 0;
136
137   smobify_self ();
138   scm_gc_unprotect_object (key_->self_scm ());
139 }
140
141 Grob::~Grob ()
142 {
143 }
144
145 MAKE_SCHEME_CALLBACK (Grob, stencil_extent, 2);
146 SCM
147 Grob::stencil_extent (SCM element_smob, SCM scm_axis)
148 {
149   Grob *s = unsmob_grob (element_smob);
150   Axis a = (Axis) scm_to_int (scm_axis);
151
152   Stencil *m = s->get_stencil ();
153   Interval e;
154   if (m)
155     e = m->extent (a);
156   return ly_interval2scm (e);
157 }
158
159 Interval
160 robust_relative_extent (Grob *me, Grob *refp, Axis a)
161 {
162   Interval ext = me->extent (refp, a);
163   if (ext.is_empty ())
164     {
165       ext.add_point (me->relative_coordinate (refp, a));
166     }
167
168   return ext;
169 }
170
171 Output_def *
172 Grob::get_layout () const
173 {
174   return pscore_ ? pscore_->layout_ : 0;
175 }
176
177 /* Recursively track all dependencies of this Grob.  The status_ field
178    is used as a mark-field.  It is marked with BUSY during execution
179    of this function, and marked with FINAL when finished.
180
181    FUNCPTR is the function to call to update this element.  */
182 void
183 Grob::calculate_dependencies (int final, int busy, SCM funcname)
184 {
185   if (status_ >= final)
186     return;
187
188   if (status_ == busy)
189     {
190       programming_error ("Element is busy, come back later");
191       return;
192     }
193
194   status_ = busy;
195
196   for (SCM d = get_property ("dependencies"); scm_is_pair (d);
197        d = scm_cdr (d))
198     unsmob_grob (scm_car (d))->calculate_dependencies (final, busy, funcname);
199
200   SCM proc = internal_get_property (funcname);
201   if (ly_c_procedure_p (proc))
202     scm_call_1 (proc, this->self_scm ());
203
204   status_ = final;
205 }
206
207 Stencil *
208 Grob::get_stencil () const
209 {
210   if (!is_live ())
211     return 0;
212
213   SCM stil = get_property ("stencil");
214   if (unsmob_stencil (stil))
215     return unsmob_stencil (stil);
216
217   stil = get_uncached_stencil ();
218   if (is_live ())
219     {
220       Grob *me = (Grob *) this;
221       me->set_property ("stencil", stil);
222     }
223
224   return unsmob_stencil (stil);
225 }
226
227 SCM
228 Grob::get_uncached_stencil () const
229 {
230   SCM proc = get_property ("print-function");
231
232   SCM stil = SCM_EOL;
233   if (ly_c_procedure_p (proc))
234     stil = scm_apply_0 (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
235
236   if (Stencil *m = unsmob_stencil (stil))
237     {
238       if (to_boolean (get_property ("transparent")))
239         stil = Stencil (m->extent_box (), SCM_EOL).smobbed_copy ();
240       else
241         {
242           SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"), self_scm (),
243                                  m->expr ());
244           stil = Stencil (m->extent_box (), expr).smobbed_copy ();
245         }
246
247       /* color support... see interpret_stencil_expression() for more... */
248       SCM color = get_property ("color");
249       if (color != SCM_EOL)
250         {
251           m = unsmob_stencil (stil);
252           SCM expr = scm_list_3 (ly_symbol2scm ("color"),
253                                  color,
254                                  m->expr ());
255
256           stil = Stencil (m->extent_box (), expr).smobbed_copy ();
257         }
258     }
259
260   return stil;
261 }
262
263 /*
264   VIRTUAL STUBS
265 */
266 void
267 Grob::do_break_processing ()
268 {
269 }
270
271 System *
272 Grob::get_system () const
273 {
274   return 0;
275 }
276
277 void
278 Grob::add_dependency (Grob *e)
279 {
280   if (e)
281     Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),
282                                        e);
283   else
284     programming_error ("Null dependency added");
285 }
286
287 void
288 Grob::handle_broken_dependencies ()
289 {
290   Spanner *sp = dynamic_cast<Spanner *> (this);
291   if (original_ && sp)
292     return;
293
294   if (sp)
295     /* THIS, SP is the original spanner.  We use a special function
296        because some Spanners have enormously long lists in their
297        properties, and a special function fixes FOO  */
298     for (SCM s = mutable_property_alist_; scm_is_pair (s); s = scm_cdr (s))
299       sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
300
301   System *system = get_system ();
302
303   if (is_live ()
304       && system && common_refpoint (system, X_AXIS)
305       && common_refpoint (system, Y_AXIS))
306     substitute_mutable_properties (system
307                                    ? system->self_scm () : SCM_UNDEFINED,
308                                    mutable_property_alist_);
309   else if (dynamic_cast<System *> (this))
310     substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
311   else
312     /* THIS element is `invalid'; it has been removed from all
313        dependencies, so let's junk the element itself.
314
315        Do not do this for System, since that would remove references
316        to the originals of score-grobs, which get then GC'd (a bad
317        thing).  */
318     suicide ();
319 }
320
321 /* Note that we still want references to this element to be
322    rearranged, and not silently thrown away, so we keep pointers like
323    {broken_into_{drul, array}, original}
324 */
325 void
326 Grob::suicide ()
327 {
328   if (!is_live ())
329     return;
330
331   mutable_property_alist_ = SCM_EOL;
332   immutable_property_alist_ = SCM_EOL;
333
334   set_extent (SCM_EOL, Y_AXIS);
335   set_extent (SCM_EOL, X_AXIS);
336
337   set_extent_callback (SCM_EOL, Y_AXIS);
338   set_extent_callback (SCM_EOL, X_AXIS);
339
340   for (int a = X_AXIS; a <= Y_AXIS; a++)
341     {
342       dim_cache_[a].offset_callbacks_ = SCM_EOL;
343       dim_cache_[a].offsets_left_ = 0;
344     }
345 }
346
347 void
348 Grob::handle_prebroken_dependencies ()
349 {
350   /* Don't do this in the derived method, since we want to keep access to
351      mutable_property_alist_ centralized.  */
352   if (original_)
353     {
354       Item *it = dynamic_cast<Item *> (this);
355       substitute_mutable_properties (scm_int2num (it->break_status_dir ()),
356                                      original_->mutable_property_alist_);
357     }
358 }
359
360 Grob *
361 Grob::find_broken_piece (System *) const
362 {
363   return 0;
364 }
365
366 /* Translate in one direction.  */
367 void
368 Grob::translate_axis (Real y, Axis a)
369 {
370   if (isinf (y) || isnan (y))
371     programming_error (_ (INFINITY_MSG));
372   else
373     dim_cache_[a].offset_ += y;
374 }
375
376 /* Find the offset relative to D.  If D equals THIS, then it is 0.
377    Otherwise, it recursively defd as
378
379    OFFSET_ + PARENT_L_->relative_coordinate (D) */
380 Real
381 Grob::relative_coordinate (Grob const *refp, Axis a) const
382 {
383   if (refp == this)
384     return 0.0;
385
386   /* We catch PARENT_L_ == nil case with this, but we crash if we did
387      not ask for the absolute coordinate (ie. REFP == nil.)  */
388   if (refp == dim_cache_[a].parent_)
389     return get_offset (a);
390
391   return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
392 }
393
394 /* Invoke callbacks to get offset relative to parent.  */
395 Real
396 Grob::get_offset (Axis a) const
397 {
398   Grob *me = (Grob *) this;
399   while (dim_cache_[a].offsets_left_)
400     {
401       int l = --me->dim_cache_[a].offsets_left_;
402       SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, scm_int2num (l));
403       SCM retval = scm_call_2 (cb, self_scm (), scm_int2num (a));
404
405       Real r = scm_to_double (retval);
406       if (isinf (r) || isnan (r))
407         {
408           programming_error (INFINITY_MSG);
409           r = 0.0;
410         }
411       me->dim_cache_[a].offset_ += r;
412     }
413   return dim_cache_[a].offset_;
414 }
415
416 bool
417 Grob::is_empty (Axis a) const
418 {
419   return !(scm_is_pair (dim_cache_[a].dimension_)
420            || ly_c_procedure_p (dim_cache_[a].dimension_callback_));
421 }
422
423 void
424 Grob::flush_extent_cache (Axis axis)
425 {
426   Dimension_cache *d = &dim_cache_[axis];
427   if (ly_c_procedure_p (d->dimension_callback_)
428       && scm_is_pair (d->dimension_))
429     {
430       d->dimension_ = SCM_EOL;
431
432       if (get_parent (axis))
433         get_parent (axis)->flush_extent_cache (axis);
434     }
435 }
436
437 Interval
438 Grob::extent (Grob *refp, Axis a) const
439 {
440   Real x = relative_coordinate (refp, a);
441
442   Dimension_cache *d = (Dimension_cache *) & dim_cache_[a];
443   Interval ext;
444
445   SCM dimpair = d->dimension_;
446   if (scm_is_pair (dimpair))
447     ;
448   else if (ly_c_procedure_p (d->dimension_callback_)
449            && d->dimension_ == SCM_EOL)
450     d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
451   else
452     return ext;
453
454   if (!scm_is_pair (d->dimension_))
455     return ext;
456
457   ext = ly_scm2interval (d->dimension_);
458
459   SCM extra = get_property (a == X_AXIS
460                             ? "extra-X-extent"
461                             : "extra-Y-extent");
462
463   /* Signs ?  */
464   if (scm_is_pair (extra))
465     {
466       ext[BIGGER] += scm_to_double (scm_cdr (extra));
467       ext[SMALLER] += scm_to_double (scm_car (extra));
468     }
469
470   extra = get_property (a == X_AXIS
471                         ? "minimum-X-extent"
472                         : "minimum-Y-extent");
473   if (scm_is_pair (extra))
474     ext.unite (Interval (scm_to_double (scm_car (extra)),
475                          scm_to_double (scm_cdr (extra))));
476
477   ext.translate (x);
478
479   return ext;
480 }
481
482 /* Find the group-element which has both #this# and #s#  */
483 Grob *
484 Grob::common_refpoint (Grob const *s, Axis a) const
485 {
486   /* I don't like the quadratic aspect of this code, but I see no
487      other way.  The largest chain of parents might be 10 high or so,
488      so it shouldn't be a real issue.  */
489   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
490     for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
491       if (d == c)
492         return (Grob *) d;
493
494   return 0;
495 }
496
497 Grob *
498 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
499 {
500   for (; scm_is_pair (elist); elist = scm_cdr (elist))
501     if (Grob *s = unsmob_grob (scm_car (elist)))
502       {
503         if (common)
504           common = common->common_refpoint (s, a);
505         else
506           common = s;
507       }
508
509   return common;
510 }
511
512 Grob *
513 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
514 {
515   for (int i = arr.size (); i--;)
516     if (Grob *s = arr[i])
517       {
518         if (common)
519           common = common->common_refpoint (s, a);
520         else
521           common = s;
522       }
523
524   return common;
525 }
526
527 String
528 Grob::name () const
529 {
530   SCM meta = get_property ("meta");
531   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
532   nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
533   return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
534 }
535
536 void
537 Grob::add_offset_callback (SCM cb, Axis a)
538 {
539   if (!has_offset_callback (cb, a))
540     {
541       dim_cache_[a].offset_callbacks_
542         = scm_cons (cb, dim_cache_[a].offset_callbacks_);
543       dim_cache_[a].offsets_left_++;
544     }
545 }
546
547 bool
548 Grob::has_extent_callback (SCM cb, Axis a) const
549 {
550   return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
551 }
552
553 bool
554 Grob::has_offset_callback (SCM cb, Axis a) const
555 {
556   return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
557 }
558
559 void
560 Grob::set_extent (SCM dc, Axis a)
561 {
562   dim_cache_[a].dimension_ = dc;
563 }
564
565 void
566 Grob::set_extent_callback (SCM dc, Axis a)
567 {
568   dim_cache_[a].dimension_callback_ = dc;
569 }
570
571 void
572 Grob::set_parent (Grob *g, Axis a)
573 {
574   dim_cache_[a].parent_ = g;
575 }
576
577 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
578 SCM
579 Grob::fixup_refpoint (SCM smob)
580 {
581   Grob *me = unsmob_grob (smob);
582   for (int a = X_AXIS; a < NO_AXES; a++)
583     {
584       Axis ax = (Axis)a;
585       Grob *parent = me->get_parent (ax);
586
587       if (!parent)
588         continue;
589
590       if (parent->get_system () != me->get_system () && me->get_system ())
591         {
592           Grob *newparent = parent->find_broken_piece (me->get_system ());
593           me->set_parent (newparent, ax);
594         }
595
596       if (Item *i = dynamic_cast<Item *> (me))
597         {
598           Item *parenti = dynamic_cast<Item *> (parent);
599
600           if (parenti && i)
601             {
602               Direction my_dir = i->break_status_dir ();
603               if (my_dir != parenti->break_status_dir ())
604                 {
605                   Item *newparent = parenti->find_prebroken_piece (my_dir);
606                   me->set_parent (newparent, ax);
607                 }
608             }
609         }
610     }
611   return smob;
612 }
613
614 void
615 Grob::warning (String s) const
616 {
617   SCM cause = self_scm ();
618   while (Grob *g = unsmob_grob (cause))
619     cause = g->get_property ("cause");
620
621   if (Music *m = unsmob_music (cause))
622     m->origin ()->warning (s);
623   else
624     ::warning (s);
625 }
626
627 void
628 Grob::programming_error (String s) const
629 {
630   s = "Programming error: " + s;
631   warning (s);
632 }
633
634 /****************************************************
635   SMOB funcs
636 ****************************************************/
637
638 IMPLEMENT_SMOBS (Grob);
639 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
640
641 SCM
642 Grob::mark_smob (SCM ses)
643 {
644   Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
645   scm_gc_mark (s->immutable_property_alist_);
646   scm_gc_mark (s->key_->self_scm ());
647   for (int a = 0; a < 2; a++)
648     {
649       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
650       scm_gc_mark (s->dim_cache_[a].dimension_);
651       scm_gc_mark (s->dim_cache_[a].dimension_callback_);
652
653       /* Do not mark the parents.  The pointers in the mutable
654          property list form two tree like structures (one for X
655          relations, one for Y relations).  Marking these can be done
656          in limited stack space.  If we add the parents, we will jump
657          between X and Y in an erratic manner, leading to much more
658          recursion depth (and core dumps if we link to pthreads).  */
659     }
660
661   if (s->original_)
662     scm_gc_mark (s->original_->self_scm ());
663
664   s->do_derived_mark ();
665   return s->mutable_property_alist_;
666 }
667
668 int
669 Grob::print_smob (SCM s, SCM port, scm_print_state *)
670 {
671   Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
672
673   scm_puts ("#<Grob ", port);
674   scm_puts ((char *) sc->name ().to_str0 (), port);
675
676   /* Do not print properties, that is too much hassle.  */
677   scm_puts (" >", port);
678   return 1;
679 }
680
681 SCM
682 Grob::do_derived_mark () const
683 {
684   return SCM_EOL;
685 }
686
687 void
688 Grob::discretionary_processing ()
689 {
690 }
691
692 bool
693 Grob::internal_has_interface (SCM k)
694 {
695   SCM ifs = get_property ("interfaces");
696
697   return scm_c_memq (k, ifs) != SCM_BOOL_F;
698 }
699
700 Grob *
701 Grob::get_parent (Axis a) const
702 {
703   return dim_cache_[a].parent_;
704 }
705
706 /** Return Array of Grobs in SCM list LST */
707 Link_array<Grob>
708 ly_scm2grobs (SCM lst)
709 {
710   Link_array<Grob> arr;
711
712   for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
713     {
714       SCM e = scm_car (s);
715       arr.push (unsmob_grob (e));
716     }
717
718   arr.reverse ();
719   return arr;
720 }
721
722 Object_key const *
723 Grob::get_key () const
724 {
725   return key_;
726 }
727
728 /** Return SCM list of Grob array A */
729 SCM
730 ly_grobs2scm (Link_array<Grob> a)
731 {
732   SCM s = SCM_EOL;
733   for (int i = a.size (); i; i--)
734     s = scm_cons (a[i - 1]->self_scm (), s);
735
736   return s;
737 }
738
739 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
740
741 ADD_INTERFACE (Grob, "grob-interface",
742                "A grob represents a piece of music notation\n"
743                "\n"
744                "All grobs have an X and Y-position on the page.  These X and Y positions\n"
745                "are stored in a relative format, so they can easily be combined by\n"
746                "stacking them, hanging one grob to the side of another, and coupling\n"
747                "them into a grouping objects.\n"
748                "\n"
749                "Each grob has a reference point (a.k.a.  parent): the position of a grob\n"
750                "is stored relative to that reference point. For example the X-reference\n"
751                "point of a staccato dot usually is the note head that it applies\n"
752                "to. When the note head is moved, the staccato dot moves along\n"
753                "automatically.\n"
754                "\n"
755                "A grob is often associated with a symbol, but some grobs do not print\n"
756                "any symbols. They take care of grouping objects. For example, there is a\n"
757                "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
758                "is also an abstract grob: it only moves around chords, but doesn't print\n"
759                "anything.\n"
760                "\n"
761                "Grobs have a properties: Scheme variables, that can be read and set. "
762                "They have two types. Immutable variables "
763                "define the default style and behavior.  They are shared between  many objects. "
764                "They can be changed using @code{\\override} and @code{\\revert}. "
765                "\n\n"
766                "Mutable properties are variables that are specific to one grob. Typically, "
767                "lists of other objects, or results from computations are stored in"
768                "mutable properties: every call to set-grob-property (or its C++ equivalent) "
769                "sets a mutable property. ",
770                "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
771                "Y-extent-callback print-function extra-offset spacing-procedure "
772                "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
773                "meta layer before-line-breaking-callback "
774                "color "
775                "axis-group-parent-X "
776                "axis-group-parent-Y "
777                "after-line-breaking-callback extra-Y-extent minimum-X-extent "
778                "minimum-Y-extent transparent tweak-count tweak-rank");
779