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