]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-determine-loose-columns.cc
7aa42caeceefec12c7778f71d3336ddfc3fce993
[lilypond.git] / lily / spacing-determine-loose-columns.cc
1 /*
2   spacing-determine-loose-columns.cc -- implement Spacing_spanner
3   methods that decide which columns to turn loose.
4
5   source file of the GNU LilyPond music typesetter
6
7   (c) 2005--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
9
10 #include "staff-spacing.hh"
11
12 #include "spacing-options.hh"
13 #include "system.hh"
14 #include "paper-column.hh"
15 #include "column-x-positions.hh"
16 #include "pointer-group-interface.hh"
17 #include "spacing-interface.hh"
18 #include "spacing-spanner.hh"
19 #include "note-spacing.hh"
20 #include "moment.hh"
21 #include "grob-array.hh"
22 #include "break-align-interface.hh"
23 #include "warn.hh"
24
25 /*
26   Return whether COL is fixed to its neighbors by some kind of spacing
27   constraint.
28
29
30   If in doubt, then we're not loose; the spacing engine should space
31   for it, risking suboptimal spacing.
32
33   (Otherwise, we might risk core dumps, and other weird stuff.)
34 */
35 static bool
36 is_loose_column (Grob *l, Grob *col, Grob *r, Spacing_options const *options)
37 {
38   if (!to_boolean (col->get_property ("allow-loose-spacing")))
39     return false;
40   
41
42   if ((options->float_nonmusical_columns_
43        || options->float_grace_columns_)
44       && Paper_column::when_mom (col).grace_part_)
45     {
46       return true;
47     }
48
49   
50   if (Paper_column::is_musical (col)
51       || Paper_column::is_breakable (col))
52     return false;
53
54   /*
55     If this column doesn't have a proper neighbor, we should really
56     make it loose, but spacing it correctly is more than we can
57     currently can handle.
58
59     (this happens in the following situation:
60
61     |
62     |    clef G
63     *
64
65     |               |      ||
66     |               |      ||
67     O               O       ||
68
69
70     the column containing the clef is really loose, and should be
71     attached right to the first column, but that is a lot of work for
72     such a borderline case.)
73
74   */
75
76   Item *r_neighbor = unsmob_item (col->get_object ("right-neighbor"));
77   Item *l_neighbor = unsmob_item (col->get_object ("left-neighbor"));
78
79   if (!l_neighbor || !r_neighbor)
80     return false;
81
82   if (l == l_neighbor && r == r_neighbor)
83     return false;
84
85   /*
86     Only declare loose if the bounds make a little sense.  This means
87     some cases (two isolated, consecutive clef changes) won't be
88     nicely folded, but hey, then don't do that.
89   */
90   if (! ((Paper_column::is_musical (l_neighbor) || Paper_column::is_breakable (l_neighbor))
91          && (Paper_column::is_musical (r_neighbor) || Paper_column::is_breakable (r_neighbor))))
92     return false;
93
94   /*
95     in any case, we don't want to move bar lines.
96   */
97   extract_grob_set (col, "elements", elts);
98   for (vsize i = elts.size (); i--;)
99     {
100       Grob *g = elts[i];
101       if (g && Break_alignment_interface::has_interface (g))
102         {
103           extract_grob_set (g, "elements", gelts);
104           for (vsize j = gelts.size (); j--;)
105             {
106               Grob *h = gelts[j];
107
108               if (h && h->get_property ("break-align-symbol") == ly_symbol2scm ("staff-bar"))
109                 return false;
110             }
111         }
112     }
113
114   return true;
115 }
116
117 void
118 Spacing_spanner::set_distances_for_loose_col (Grob *me, Grob *c,
119                                               Drul_array<Item *> next_door,
120                                               Spacing_options const *options)
121 {
122   Direction d = LEFT;
123   Drul_array<Real> dists (0, 0);
124
125   do
126     {
127       Item *lc = dynamic_cast<Item *> ((d == LEFT) ? next_door[LEFT] : c);
128       Item *rc = dynamic_cast<Item *> (d == LEFT ? c : next_door[RIGHT]);
129
130       extract_grob_set (lc, "spacing-wishes", wishes);
131       for (vsize k = wishes.size (); k--;)
132         {
133           Grob *sp = wishes[k];
134           if (Spacing_interface::left_column (sp) != lc
135               || Spacing_interface::right_column (sp) != rc)
136             continue;
137
138           if (Note_spacing::has_interface (sp))
139             {
140               /*
141                 The note spacing should be taken from the musical
142                 columns.
143               */
144               Real base = note_spacing (me, lc, rc, options);
145               Spring spring = Note_spacing::get_spacing (sp, rc, base, options->increment_);
146
147               dists[d] = max (dists[d], spring.min_distance ());
148             }
149           else if (Staff_spacing::has_interface (sp))
150             {
151               Spring spring = Staff_spacing::get_spacing (sp, rc);
152
153               dists[d] = max (dists[d], spring.min_distance ());
154             }
155           else
156             programming_error ("Subversive spacing wish");
157         }
158     }
159   while (flip (&d) != LEFT);
160
161   Rod r;
162   r.distance_ = dists[LEFT] + dists[RIGHT];
163   r.item_drul_ = next_door;
164  
165   r.add_to_cols (); 
166 }
167
168
169 /*
170   Remove columns that are not tightly fitting from COLS. In the
171   removed columns, set 'between-cols to the columns where it is in
172   between.
173 */
174 void
175 Spacing_spanner::prune_loose_columns (Grob *me,
176                                       vector<Grob*> *cols,
177                                       Spacing_options *options)
178 {
179   vector<Grob*> newcols;
180
181   for (vsize i = 0; i < cols->size (); i++)
182     {
183       Grob *c = cols->at (i);
184
185       bool loose = (i > 0 && i + 1 < cols->size ())
186         && is_loose_column (cols->at (i - 1), c, cols->at (i + 1), options);
187
188       if (loose)
189         {
190           Grob *right_neighbor = unsmob_grob (c->get_object ("right-neighbor"));
191           Grob *left_neighbor = unsmob_grob (c->get_object ("left-neighbor"));
192
193           /*
194             Either object can be non existent, if the score ends
195             prematurely.
196           */
197           if (!right_neighbor || !left_neighbor)
198             {
199               c->programming_error ("Cannot determine neighbors for floating column. ");
200               c->set_object ("between-cols", scm_cons (cols->at (i-1)->self_scm (),
201                                                        cols->at (i+1)->self_scm ()));
202             }
203           else
204             {
205               c->set_object ("between-cols", scm_cons (left_neighbor->self_scm (),
206                                                        right_neighbor->self_scm ()));
207
208
209               /*
210                 Set distance constraints for loose columns
211               */
212               Drul_array<Item *> next_door (dynamic_cast<Item*> (cols->at (i - 1)),
213                                             dynamic_cast<Item*> (cols->at (i + 1)));
214
215               set_distances_for_loose_col (me, c, next_door, options);
216             }
217         }
218
219       if (!loose)
220         newcols.push_back (c);
221     }
222
223   *cols = newcols;
224 }
225
226 /*
227   Set neighboring columns determined by the spacing-wishes grob property.
228 */
229 void
230 Spacing_spanner::set_explicit_neighbor_columns (vector<Grob*> const &cols)
231 {
232   for (vsize i = 0; i < cols.size (); i++)
233     {
234       extract_grob_set (cols[i], "spacing-wishes", wishes);
235       for (vsize j = wishes.size (); j--;)
236         {
237           Item *wish = dynamic_cast<Item*> (wishes[j]);
238           Item *left_col = wish->get_column ();
239           int left_rank = Paper_column::get_rank (left_col);
240           int min_right_rank = INT_MAX;
241
242           extract_grob_set (wish, "right-items", right_items);
243           for (vsize k = right_items.size (); k--;)
244             {
245               Item *right_col = dynamic_cast<Item*> (right_items[k])->get_column ();
246               int right_rank = Paper_column::get_rank (right_col);
247
248               if (right_rank < min_right_rank)
249                 {
250                   left_col->set_object ("right-neighbor", right_col->self_scm ());
251                   min_right_rank = right_rank;
252                 }
253
254               Grob *old_left_neighbor = unsmob_grob (right_col->get_object ("left-neighbor"));
255               if (!old_left_neighbor || left_rank > Paper_column::get_rank (old_left_neighbor))
256                 right_col->set_object ("left-neighbor", left_col->self_scm ());
257             }
258         }
259     }
260 }
261
262 /*
263   Set neighboring columns that have no left/right-neighbor set
264   yet. Only do breakable non-musical columns, and musical columns.
265   Why only these? --jneem
266 */
267 void
268 Spacing_spanner::set_implicit_neighbor_columns (vector<Grob*> const &cols)
269 {
270   for (vsize i = 0; i < cols.size (); i++)
271     {
272       Item *it = dynamic_cast<Item *> (cols[i]);
273       if (!Paper_column::is_breakable (it) && !Paper_column::is_musical (it))
274         continue;
275
276       if (i && !unsmob_grob (cols[i]->get_object ("left-neighbor")))
277         cols[i]->set_object ("left-neighbor", cols[i-1]->self_scm ());
278       if (i + 1 < cols.size () && !unsmob_grob (cols[i]->get_object ("right-neighbor")))
279         cols[i]->set_object ("right-neighbor", cols[i+1]->self_scm ());
280     }
281 }