11 class Layout::LinearProgram
17 LinearProgram &linprog;
21 Row(LinearProgram &, unsigned);
23 float &operator[](unsigned);
31 std::vector<float> values;
38 std::vector<Column> columns;
43 LinearProgram(unsigned);
46 Row operator[](unsigned);
47 Row get_objective_row();
49 float get_variable(unsigned);
51 unsigned find_minimal_ratio(unsigned);
52 void make_basic_column(unsigned, unsigned);
57 struct Layout::Pointers
60 unsigned Geometry::*dim;
61 Packing Slot::*packing;
62 unsigned Sides::*low_margin;
63 unsigned Sides::*high_margin;
64 unsigned Layout::*spacing;
67 Layout::Pointers Layout::pointers[2] =
69 &Geometry::x, &Geometry::w,
71 &Sides::left, &Sides::right,
74 &Geometry::y, &Geometry::h,
76 &Sides::bottom, &Sides::top,
90 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
94 void Layout::set_container(Container &c)
97 throw logic_error("container!=0");
102 void Layout::set_margin(const Sides &m)
109 void Layout::add_widget(Widget &wdg)
112 throw logic_error("!container");
114 Slot *slot = create_slot(wdg);
115 for(list<Constraint>::iterator i=slot->constraints.begin(); i!=slot->constraints.end(); ++i)
116 i->target.constraints.push_back(Constraint(complement(i->type), *slot));
117 slot->index = slots.size();
118 slots.push_back(slot);
123 void Layout::remove_widget(Widget &wdg)
125 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
126 if(&(*i)->widget==&wdg)
128 for(list<Slot *>::iterator j=slots.begin(); j!=slots.end(); ++j)
131 for(list<Constraint>::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); )
134 (*j)->constraints.erase(k++);
144 for(i=slots.begin(); i!=slots.end(); ++i, ++n)
152 Layout::Slot *Layout::create_slot(Widget &wdg)
154 return new Slot(*this, wdg);
157 Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
159 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
160 if(&(*i)->widget==&wdg)
163 throw hierarchy_error("widget not in layout");
166 Layout::ConstraintType Layout::complement(ConstraintType type)
170 else if(type==LEFT_OF)
180 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt)
183 throw invalid_argument("&src==&tgt");
185 Slot &src_slot = get_slot_for_widget(src);
186 Slot &tgt_slot = get_slot_for_widget(tgt);
188 for(list<Constraint>::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i)
189 if(i->type==type && &i->target==&tgt_slot)
192 src_slot.constraints.push_back(Constraint(type, tgt_slot));
193 tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
198 void Layout::set_gravity(Widget &wdg, int h, int v)
200 Slot &slot = get_slot_for_widget(wdg);
202 slot.horiz_pack.gravity = h;
203 slot.vert_pack.gravity = v;
208 void Layout::set_expand(Widget &wdg, bool h, bool v)
210 Slot &slot = get_slot_for_widget(wdg);
212 slot.horiz_pack.expand = h;
213 slot.vert_pack.expand = v;
218 void Layout::update()
220 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
222 (*i)->widget.autosize();
223 (*i)->geom = (*i)->widget.get_geometry();
226 solve_constraints(HORIZONTAL);
227 solve_constraints(VERTICAL);
229 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
230 (*i)->widget.set_geometry((*i)->geom);
233 void Layout::solve_constraints(int dir)
235 Pointers &ptrs = pointers[dir&VERTICAL];
237 /* Set up a linear program to solve the constraints. The program matrix has
238 five columns for each widget, and one constant column. The first and second
239 columns of a widget are its position and dimension, respectively. The
240 remaining three are slack columns; see below for their purposes. */
241 LinearProgram linprog(slots.size()*5+1);
242 float weight = slots.size();
243 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
245 linprog.get_objective_row()[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
246 linprog.get_objective_row()[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
249 // Prevent the widget from going past the container's low edge.
250 LinearProgram::Row row = linprog.add_row();
251 row[(*i)->index*5] = 1;
252 row[(*i)->index*5+2] = -1;
253 row.back() = margin.*(ptrs.low_margin);
257 // Prevent the widget from going past the container's high edge.
258 LinearProgram::Row row = linprog.add_row();
259 row[(*i)->index*5] = 1;
260 row[(*i)->index*5+1] = 1;
261 row[(*i)->index*5+3] = 1;
262 row.back() = container->get_geometry().*(ptrs.dim)-margin.*(ptrs.high_margin);
266 /* Only allow the widget's dimension to increase. The geometry has
267 previously been set to the smallest allowable size. */
268 LinearProgram::Row row = linprog.add_row();
269 row[(*i)->index*5+1] = 1;
270 row[(*i)->index*5+4] = -1;
271 row.back() = (*i)->geom.*(ptrs.dim);
274 /* Add rows for user-defined constraints. Below/above and left/right of
275 constraints are always added in pairs, so it's only necessary to create a
277 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
278 if((j->type&1)==dir && j->type!=BELOW && j->type!=LEFT_OF)
280 LinearProgram::Row row = linprog.add_row();
282 row[(*i)->index*5] = 1;
284 row[(*i)->index*5+1] = 1;
285 if(j->type&TARGET_POS)
286 row[j->target.index*5] = -1;
287 if(j->type&TARGET_DIM)
288 row[j->target.index*5+1] = -1;
290 row.back() = this->*(ptrs.spacing);
297 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
299 (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5);
300 (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1);
305 Layout::Constraint::Constraint(ConstraintType t, Slot &s):
311 Layout::Packing::Packing():
317 Layout::Slot::Slot(Layout &l, Widget &w):
322 vert_pack.gravity = 1;
323 widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
326 void Layout::Slot::autosize_changed()
332 Layout::LinearProgram::LinearProgram(unsigned s):
340 Layout::LinearProgram::Row Layout::LinearProgram::add_row()
342 return Row(*this, n_rows++);
345 Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r)
348 throw out_of_range("LinearProgram::operator[]");
350 return Row(*this, r);
353 Layout::LinearProgram::Row Layout::LinearProgram::get_objective_row()
355 return Row(*this, 0);
358 float Layout::LinearProgram::get_variable(unsigned i)
360 if(!solved || infeasible)
361 throw logic_error("not solved");
363 throw out_of_range("LinearProgram::get_variable");
365 unsigned r = columns[i].basic;
366 return columns.back().values[r];
369 bool Layout::LinearProgram::solve()
371 if(solved || infeasible)
374 /* Solve the program using the simplex method. The column representing the
375 objective variable is kept implicit, as it would never change during the
376 execution of the algorithm. */
378 /* Force all columns fully into existence and relocate objective row to
379 bottom in preparation of phase 1. A new objective row is calculated by
380 pricing out the constraint rows. */
381 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
383 float objective = i->values.front();
384 i->values.front() = 0.0f;
385 for(vector<float>::iterator j=i->values.begin(); j!=i->values.end(); ++j)
386 i->values.front() += *j;
387 i->values.resize(n_rows+1, 0.0f);
388 i->values.back() = objective;
391 /* Create artificial variables for phase 1. This ensures that each row has
392 a basic variable associated with it. The original objective row already
393 contains the implicit objective variable, which is basic. */
394 columns.resize(n_columns+n_rows-1);
395 columns.back() = columns[n_columns-1];
396 columns[n_columns-1].values.clear();
397 for(unsigned i=1; i<n_rows; ++i)
399 Column &column = columns[n_columns+i-2];
403 // Solve the phase 1 problem.
406 /* All artificial variables should now be non-basic and thus zero, so the
407 objective function's value should also be zero. If it isn't, the original
408 program can't be solved. */
409 if(columns.back().values.front())
415 /* Get rid of the artificial variables and restore the original objective
416 row to form the phase 2 problem. */
417 columns.erase(columns.begin()+(n_columns-1), columns.end()-1);
418 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
421 i->values.front() = i->values.back();
422 i->values.pop_back();
425 // Solve the phase 2 problem. We already know it to be feasible.
433 unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c)
435 /* Pick the row with the minimum ratio between the constant column and the
436 pivot column. This ensures that when the pivot column is made basic, values
437 in the constant column stay positive.
439 The use of n_rows instead of the true size of the column is intentional,
440 since the relocated objective row must be ignored in phase 1. */
441 float best = numeric_limits<float>::infinity();
443 for(unsigned i=1; i<n_rows; ++i)
444 if(columns[c].values[i]>0)
446 float ratio = columns.back().values[i]/columns[c].values[i];
457 void Layout::LinearProgram::make_basic_column(unsigned c, unsigned r)
459 /* Perform row transfer operations to make the pivot column basic,
460 containing a 1 on the pivot row. */
461 for(unsigned i=0; i<columns.size(); ++i)
462 if(i!=c && (columns[i].basic==r || (!columns[i].basic && columns[i].values[r])))
466 columns[i].values.resize(columns.back().values.size(), 0.0f);
467 columns[i].values[columns[i].basic] = 1.0f;
468 columns[i].basic = 0;
471 float scale = columns[i].values[r]/columns[c].values[r];
473 columns[i].values[r] = scale;
474 for(unsigned j=0; j<columns[i].values.size(); ++j)
476 columns[i].values[j] -= scale*columns[c].values[j];
479 columns[c].basic = r;
480 columns[c].values.clear();
483 bool Layout::LinearProgram::pivot()
485 /* Pick a nonbasic column and make it basic. Requiring a positive objective
486 coefficient ensures that the objective function's value will decrease in the
488 for(unsigned i=0; i+1<columns.size(); ++i)
489 if(!columns[i].basic && columns[i].values.front()>0)
490 if(unsigned row = find_minimal_ratio(i))
492 make_basic_column(i, row);
500 Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i):
505 float &Layout::LinearProgram::Row::operator[](unsigned c)
507 if(c>=linprog.n_columns)
508 throw out_of_range("Row::operator[]");
510 Column &column = linprog.columns[c];
511 if(column.values.size()<=index)
512 column.values.resize(index+1);
514 return column.values[index];
517 float &Layout::LinearProgram::Row::back()
519 return (*this)[linprog.n_columns-1];
523 Layout::LinearProgram::Column::Column():