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