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