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