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