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