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