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