]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
* flower
[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   else if (ly_c_procedure_p (d->dimension_callback_)
448            && d->dimension_ == SCM_EOL)
449     d->dimension_ = scm_call_2 (d->dimension_callback_, self_scm (), scm_int2num (a));
450   else
451     return ext;
452
453   if (!scm_is_pair (d->dimension_))
454     return ext;
455
456   ext = ly_scm2interval (d->dimension_);
457
458   SCM extra = get_property (a == X_AXIS
459                             ? "extra-X-extent"
460                             : "extra-Y-extent");
461
462   /* Signs ?  */
463   if (scm_is_pair (extra))
464     {
465       ext[BIGGER] += scm_to_double (scm_cdr (extra));
466       ext[SMALLER] += scm_to_double (scm_car (extra));
467     }
468
469   extra = get_property (a == X_AXIS
470                         ? "minimum-X-extent"
471                         : "minimum-Y-extent");
472   if (scm_is_pair (extra))
473     ext.unite (Interval (scm_to_double (scm_car (extra)),
474                          scm_to_double (scm_cdr (extra))));
475
476   ext.translate (x);
477
478   return ext;
479 }
480
481 /* Find the group-element which has both #this# and #s#  */
482 Grob *
483 Grob::common_refpoint (Grob const *s, Axis a) const
484 {
485   /* I don't like the quadratic aspect of this code, but I see no
486      other way.  The largest chain of parents might be 10 high or so,
487      so it shouldn't be a real issue.  */
488   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
489     for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
490       if (d == c)
491         return (Grob *) d;
492
493   return 0;
494 }
495
496 Grob *
497 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
498 {
499   for (; scm_is_pair (elist); elist = scm_cdr (elist))
500     if (Grob *s = unsmob_grob (scm_car (elist)))
501       {
502         if (common)
503           common = common->common_refpoint (s, a);
504         else
505           common = s;
506       }
507
508   return common;
509 }
510
511 Grob *
512 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
513 {
514   for (int i = arr.size (); i--;)
515     if (Grob *s = arr[i])
516       {
517         if (common)
518           common = common->common_refpoint (s, a);
519         else
520           common = s;
521       }
522
523   return common;
524 }
525
526 String
527 Grob::name () const
528 {
529   SCM meta = get_property ("meta");
530   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
531   nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
532   return scm_is_symbol (nm) ? ly_symbol2string (nm) : classname (this);
533 }
534
535 void
536 Grob::add_offset_callback (SCM cb, Axis a)
537 {
538   if (!has_offset_callback (cb, a))
539     {
540       dim_cache_[a].offset_callbacks_
541         = scm_cons (cb, dim_cache_[a].offset_callbacks_);
542       dim_cache_[a].offsets_left_++;
543     }
544 }
545
546 bool
547 Grob::has_extent_callback (SCM cb, Axis a) const
548 {
549   return scm_equal_p (cb, dim_cache_[a].dimension_callback_) == SCM_BOOL_T;
550 }
551
552 bool
553 Grob::has_offset_callback (SCM cb, Axis a) const
554 {
555   return scm_c_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
556 }
557
558 void
559 Grob::set_extent (SCM dc, Axis a)
560 {
561   dim_cache_[a].dimension_ = dc;
562 }
563
564 void
565 Grob::set_extent_callback (SCM dc, Axis a)
566 {
567   dim_cache_[a].dimension_callback_ = dc;
568 }
569
570 void
571 Grob::set_parent (Grob *g, Axis a)
572 {
573   dim_cache_[a].parent_ = g;
574 }
575
576 MAKE_SCHEME_CALLBACK (Grob, fixup_refpoint, 1);
577 SCM
578 Grob::fixup_refpoint (SCM smob)
579 {
580   Grob *me = unsmob_grob (smob);
581   for (int a = X_AXIS; a < NO_AXES; a++)
582     {
583       Axis ax = (Axis)a;
584       Grob *parent = me->get_parent (ax);
585
586       if (!parent)
587         continue;
588
589       if (parent->get_system () != me->get_system () && me->get_system ())
590         {
591           Grob *newparent = parent->find_broken_piece (me->get_system ());
592           me->set_parent (newparent, ax);
593         }
594
595       if (Item *i = dynamic_cast<Item *> (me))
596         {
597           Item *parenti = dynamic_cast<Item *> (parent);
598
599           if (parenti && i)
600             {
601               Direction my_dir = i->break_status_dir ();
602               if (my_dir!= parenti->break_status_dir ())
603                 {
604                   Item *newparent = parenti->find_prebroken_piece (my_dir);
605                   me->set_parent (newparent, ax);
606                 }
607             }
608         }
609     }
610   return smob;
611 }
612
613 void
614 Grob::warning (String s) const
615 {
616   SCM cause = self_scm ();
617   while (Grob *g = unsmob_grob (cause))
618     cause = g->get_property ("cause");
619
620   if (Music *m = unsmob_music (cause))
621     m->origin ()->warning (s);
622   else
623     ::warning (s);
624 }
625
626 void
627 Grob::programming_error (String s) const
628 {
629   s = "Programming error: " + s;
630   warning (s);
631 }
632
633 /****************************************************
634   SMOB funcs
635 ****************************************************/
636
637 IMPLEMENT_SMOBS (Grob);
638 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
639
640 SCM
641 Grob::mark_smob (SCM ses)
642 {
643   Grob *s = (Grob *) SCM_CELL_WORD_1 (ses);
644   scm_gc_mark (s->immutable_property_alist_);
645   scm_gc_mark (s->key_->self_scm ());
646   for (int a = 0; a < 2; a++)
647     {
648       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
649       scm_gc_mark (s->dim_cache_[a].dimension_);
650       scm_gc_mark (s->dim_cache_[a].dimension_callback_);
651
652       /* Do not mark the parents.  The pointers in the mutable
653          property list form two tree like structures (one for X
654          relations, one for Y relations).  Marking these can be done
655          in limited stack space.  If we add the parents, we will jump
656          between X and Y in an erratic manner, leading to much more
657          recursion depth (and core dumps if we link to pthreads).  */
658     }
659
660   if (s->original_)
661     scm_gc_mark (s->original_->self_scm ());
662
663   s->do_derived_mark ();
664   return s->mutable_property_alist_;
665 }
666
667 int
668 Grob::print_smob (SCM s, SCM port, scm_print_state *)
669 {
670   Grob *sc = (Grob *) SCM_CELL_WORD_1 (s);
671
672   scm_puts ("#<Grob ", port);
673   scm_puts ((char *) sc->name ().to_str0 (), port);
674
675   /* Do not print properties, that is too much hassle.  */
676   scm_puts (" >", port);
677   return 1;
678 }
679
680 SCM
681 Grob::do_derived_mark () const
682 {
683   return SCM_EOL;
684 }
685
686 void
687 Grob::discretionary_processing ()
688 {
689 }
690
691 bool
692 Grob::internal_has_interface (SCM k)
693 {
694   SCM ifs = get_property ("interfaces");
695
696   return scm_c_memq (k, ifs) != SCM_BOOL_F;
697 }
698
699 Grob *
700 Grob::get_parent (Axis a) const
701 {
702   return dim_cache_[a].parent_;
703 }
704
705 /** Return Array of Grobs in SCM list LST */
706 Link_array<Grob>
707 ly_scm2grobs (SCM lst)
708 {
709   Link_array<Grob> arr;
710
711   for (SCM s = lst; scm_is_pair (s); s = scm_cdr (s))
712     {
713       SCM e = scm_car (s);
714       arr.push (unsmob_grob (e));
715     }
716
717   arr.reverse ();
718   return arr;
719 }
720
721 Object_key const *
722 Grob::get_key () const
723 {
724   return key_;
725 }
726
727 /** Return SCM list of Grob array A */
728 SCM
729 ly_grobs2scm (Link_array<Grob> a)
730 {
731   SCM s = SCM_EOL;
732   for (int i = a.size (); i; i--)
733     s = scm_cons (a[i - 1]->self_scm (), s);
734
735   return s;
736 }
737
738 IMPLEMENT_TYPE_P (Grob, "ly:grob?");
739
740 ADD_INTERFACE (Grob, "grob-interface",
741                "A grob represents a piece of music notation\n"
742                "\n"
743                "All grobs have an X and Y-position on the page.  These X and Y positions\n"
744                "are stored in a relative format, so they can easily be combined by\n"
745                "stacking them, hanging one grob to the side of another, and coupling\n"
746                "them into a grouping objects.\n"
747                "\n"
748                "Each grob has a reference point (a.k.a.  parent): the position of a grob\n"
749                "is stored relative to that reference point. For example the X-reference\n"
750                "point of a staccato dot usually is the note head that it applies\n"
751                "to. When the note head is moved, the staccato dot moves along\n"
752                "automatically.\n"
753                "\n"
754                "A grob is often associated with a symbol, but some grobs do not print\n"
755                "any symbols. They take care of grouping objects. For example, there is a\n"
756                "separate grob that stacks staves vertically. The @ref{NoteCollision}\n"
757                "is also an abstract grob: it only moves around chords, but doesn't print\n"
758                "anything.\n"
759                "\n"
760                "Grobs have a properties: Scheme variables, that can be read and set. "
761                "They have two types. Immutable variables "
762                "define the default style and behavior.  They are shared between  many objects. "
763                "They can be changed using @code{\\override} and @code{\\revert}. "
764                "\n\n"
765                "Mutable properties are variables that are specific to one grob. Typically, "
766                "lists of other objects, or results from computations are stored in"
767                "mutable properties: every call to set-grob-property (or its C++ equivalent) "
768                "sets a mutable property. ",
769                "X-offset-callbacks Y-offset-callbacks X-extent-callback stencil cause "
770                "Y-extent-callback print-function extra-offset spacing-procedure "
771                "context staff-symbol interfaces dependencies X-extent Y-extent extra-X-extent "
772                "meta layer before-line-breaking-callback "
773                "color "
774                "axis-group-parent-X "
775                "axis-group-parent-Y "
776                "after-line-breaking-callback extra-Y-extent minimum-X-extent "
777                "minimum-Y-extent transparent tweak-count tweak-rank");
778