]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
Grand fixcc.py run on all .hh .cc files.
[lilypond.git] / lily / grob.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2011 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 Interval
422 Grob::extent (Grob *refp, Axis a) const
423 {
424   Real offset = relative_coordinate (refp, a);
425   Interval real_ext;
426   if (dim_cache_[a].extent_)
427     {
428       real_ext = *dim_cache_[a].extent_;
429     }
430   else
431     {
432       /*
433         Order is significant: ?-extent may trigger suicide.
434        */
435       SCM ext_sym
436         = (a == X_AXIS)
437           ? ly_symbol2scm ("X-extent")
438           : ly_symbol2scm ("Y-extent");
439
440       SCM ext = internal_get_property (ext_sym);
441       if (is_number_pair (ext))
442         real_ext.unite (ly_scm2interval (ext));
443
444       SCM min_ext_sym
445         = (a == X_AXIS)
446           ? ly_symbol2scm ("minimum-X-extent")
447           : ly_symbol2scm ("minimum-Y-extent");
448       SCM min_ext = internal_get_property (min_ext_sym);
449       if (is_number_pair (min_ext))
450         real_ext.unite (ly_scm2interval (min_ext));
451
452       ((Grob *)this)->dim_cache_[a].extent_ = new Interval (real_ext);
453     }
454
455   real_ext.translate (offset);
456
457   return real_ext;
458 }
459
460 Interval
461 Grob::pure_height (Grob *refp, int start, int end)
462 {
463   SCM iv_scm = get_pure_property ("Y-extent", start, end);
464   Interval iv = robust_scm2interval (iv_scm, Interval (0, 0));
465   Real offset = pure_relative_y_coordinate (refp, start, end);
466
467   SCM min_ext = get_property ("minimum-Y-extent");
468
469   /* we don't add minimum-Y-extent if the extent is empty. This solves
470      a problem with Hara-kiri spanners. They would request_suicide and
471      return empty extents, but we would force them here to be large. */
472   if (!iv.is_empty () && is_number_pair (min_ext))
473     iv.unite (ly_scm2interval (min_ext));
474
475   if (!iv.is_empty ())
476     iv.translate (offset);
477   return iv;
478 }
479
480 Interval
481 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
482 {
483   if (pure && a != Y_AXIS)
484     programming_error ("tried to get pure width");
485   return (pure && a == Y_AXIS) ? pure_height (refp, start, end) : extent (refp, a);
486 }
487
488 Interval_t<int>
489 Grob::spanned_rank_interval () const
490 {
491   return Interval_t<int> (-1, 0);
492 }
493
494 bool
495 Grob::pure_is_visible (int /* start */, int /* end */) const
496 {
497   return true;
498 }
499
500 /* Sort grobs according to their starting column. */
501 bool
502 Grob::less (Grob *g1, Grob *g2)
503 {
504   return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
505 }
506
507 /****************************************************************
508   REFPOINTS
509 ****************************************************************/
510
511 /* Find the group-element which has both #this# and #s#  */
512 Grob *
513 Grob::common_refpoint (Grob const *s, Axis a) const
514 {
515   /* I don't like the quadratic aspect of this code, but I see no
516      other way.  The largest chain of parents might be 10 high or so,
517      so it shouldn't be a real issue.  */
518   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
519     for (Grob const *d = s; d; d = d->dim_cache_[a].parent_)
520       if (d == c)
521         return (Grob *) d;
522
523   return 0;
524 }
525
526 void
527 Grob::set_parent (Grob *g, Axis a)
528 {
529   dim_cache_[a].parent_ = g;
530 }
531
532 Grob *
533 Grob::get_parent (Axis a) const
534 {
535   return dim_cache_[a].parent_;
536 }
537
538 void
539 Grob::fixup_refpoint ()
540 {
541   for (int a = X_AXIS; a < NO_AXES; a++)
542     {
543       Axis ax = (Axis)a;
544       Grob *parent = get_parent (ax);
545
546       if (!parent)
547         continue;
548
549       if (parent->get_system () != get_system () && get_system ())
550         {
551           Grob *newparent = parent->find_broken_piece (get_system ());
552           set_parent (newparent, ax);
553         }
554
555       if (Item *i = dynamic_cast<Item *> (this))
556         {
557           Item *parenti = dynamic_cast<Item *> (parent);
558
559           if (parenti && i)
560             {
561               Direction my_dir = i->break_status_dir ();
562               if (my_dir != parenti->break_status_dir ())
563                 {
564                   Item *newparent = parenti->find_prebroken_piece (my_dir);
565                   set_parent (newparent, ax);
566                 }
567             }
568         }
569     }
570 }
571
572 /****************************************************************
573   MESSAGES
574 ****************************************************************/
575 void
576 Grob::warning (string s) const
577 {
578   if (get_program_option ("warning-as-error"))
579     error (s);
580
581   SCM cause = self_scm ();
582   while (Grob *g = unsmob_grob (cause))
583     cause = g->get_property ("cause");
584
585   /* ES TODO: cause can't be Music*/
586   if (Music *m = unsmob_music (cause))
587     m->origin ()->warning (s);
588   else if (Stream_event *ev = unsmob_stream_event (cause))
589     ev->origin ()->warning (s);
590   else
591     ::warning (s);
592 }
593
594 string
595 Grob::name () const
596 {
597   SCM meta = get_property ("meta");
598   SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
599   nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
600   return scm_is_symbol (nm) ? ly_symbol2string (nm) : this->class_name ();
601 }
602
603 void
604 Grob::programming_error (string s) const
605 {
606   if (get_program_option ("warning-as-error"))
607     error (s);
608
609   SCM cause = self_scm ();
610   while (Grob *g = unsmob_grob (cause))
611     cause = g->get_property ("cause");
612
613   s = _f ("programming error: %s", s);
614
615   /* ES TODO: cause can't be Music*/
616   if (Music *m = unsmob_music (cause))
617     m->origin ()->message (s);
618   else if (Stream_event *ev = unsmob_stream_event (cause))
619     ev->origin ()->message (s);
620   else
621     ::message (s);
622 }
623
624 ADD_INTERFACE (Grob,
625                "A grob represents a piece of music notation.\n"
626                "\n"
627                "All grobs have an X and Y@tie{}position on the page.  These"
628                " X and Y@tie{}positions are stored in a relative format, thus"
629                " they can easily be combined by stacking them, hanging one"
630                " grob to the side of another, or coupling them into grouping"
631                " objects.\n"
632                "\n"
633                "Each grob has a reference point (a.k.a.@: parent): The"
634                " position of a grob is stored relative to that reference"
635                " point.  For example, the X@tie{}reference point of a staccato"
636                " dot usually is the note head that it applies to.  When the"
637                " note head is moved, the staccato dot moves along"
638                " automatically.\n"
639                "\n"
640                "A grob is often associated with a symbol, but some grobs do"
641                " not print any symbols.  They take care of grouping objects."
642                " For example, there is a separate grob that stacks staves"
643                " vertically.  The @ref{NoteCollision} object is also an"
644                " abstract grob: It only moves around chords, but doesn't print"
645                " anything.\n"
646                "\n"
647                "Grobs have properties (Scheme variables) that can be read and"
648                " set.  Two types of them exist: immutable and mutable."
649                "  Immutable variables define the default style and behavior."
650                "  They are shared between many objects.  They can be changed"
651                " using @code{\\override} and @code{\\revert}.  Mutable"
652                " properties are variables that are specific to one grob."
653                "  Typically, lists of other objects, or results from"
654                " computations are stored in mutable properties.  In"
655                " particular, every call to @code{ly:grob-set-property!}"
656                " (or its C++ equivalent) sets a mutable property.\n"
657                "\n"
658                "The properties @code{after-line-breaking} and"
659                " @code{before-line-breaking} are dummies that are not"
660                " user-serviceable.",
661
662                /* properties */
663                "X-extent "
664                "X-offset "
665                "Y-extent "
666                "Y-offset "
667                "after-line-breaking "
668                "avoid-slur "
669                "axis-group-parent-X "
670                "axis-group-parent-Y "
671                "before-line-breaking "
672                "cause "
673                "color "
674                "cross-staff "
675                "extra-X-extent "
676                "extra-Y-extent "
677                "extra-offset "
678                "interfaces "
679                "layer "
680                "meta "
681                "minimum-X-extent "
682                "minimum-Y-extent "
683                "outside-staff-horizontal-padding "
684                "outside-staff-padding "
685                "outside-staff-priority "
686                "pure-Y-offset-in-progress "
687                "rotation "
688                "springs-and-rods "
689                "staff-symbol "
690                "stencil "
691                "transparent "
692                "whiteout "
693               );
694
695 /****************************************************************
696   CALLBACKS
697 ****************************************************************/
698
699 static SCM
700 grob_stencil_extent (Grob *me, Axis a)
701 {
702   Stencil *m = me->get_stencil ();
703   Interval e;
704   if (m)
705     e = m->extent (a);
706   return ly_interval2scm (e);
707 }
708
709 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
710 SCM
711 Grob::stencil_height (SCM smob)
712 {
713   Grob *me = unsmob_grob (smob);
714   return grob_stencil_extent (me, Y_AXIS);
715 }
716
717 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
718 SCM
719 Grob::y_parent_positioning (SCM smob)
720 {
721   Grob *me = unsmob_grob (smob);
722   Grob *par = me->get_parent (Y_AXIS);
723   if (par)
724     (void) par->get_property ("positioning-done");
725
726   return scm_from_double (0.0);
727 }
728
729 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
730 SCM
731 Grob::x_parent_positioning (SCM smob)
732 {
733   Grob *me = unsmob_grob (smob);
734
735   Grob *par = me->get_parent (X_AXIS);
736   if (par)
737     (void) par->get_property ("positioning-done");
738
739   return scm_from_double (0.0);
740 }
741
742 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
743 SCM
744 Grob::stencil_width (SCM smob)
745 {
746   Grob *me = unsmob_grob (smob);
747   return grob_stencil_extent (me, X_AXIS);
748 }
749
750 Grob *
751 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
752 {
753   for (; scm_is_pair (elist); elist = scm_cdr (elist))
754     if (Grob *s = unsmob_grob (scm_car (elist)))
755       {
756         if (common)
757           common = common->common_refpoint (s, a);
758         else
759           common = s;
760       }
761
762   return common;
763 }
764
765 Grob *
766 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
767 {
768   for (vsize i = 0; i < arr.size (); i++)
769     if (common)
770       common = common->common_refpoint (arr[i], a);
771     else
772       common = arr[i];
773
774   return common;
775 }
776
777 Interval
778 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
779 {
780   Interval ext = me->extent (refpoint, a);
781   if (ext.is_empty ())
782     ext.add_point (me->relative_coordinate (refpoint, a));
783
784   return ext;
785 }
786
787 // Checks whether there is a vertical alignment in the chain of
788 // parents between this and commony.
789 bool
790 Grob::check_cross_staff (Grob *commony)
791 {
792   if (Align_interface::has_interface (commony))
793     return true;
794
795   for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
796     if (Align_interface::has_interface (g))
797       return true;
798
799   return false;
800 }
801