12 class Layout::LinearProgram
18 LinearProgram &linprog;
22 Row(LinearProgram &, unsigned);
24 float &operator[](unsigned);
32 std::vector<float> values;
39 std::vector<Column> columns;
44 LinearProgram(unsigned);
47 Row operator[](unsigned);
48 Row get_objective_row();
49 float get_variable(unsigned);
52 void prepare_columns();
53 void add_artificial_variables();
54 void remove_artificial_variables();
55 unsigned find_minimal_ratio(unsigned);
56 void make_basic_column(unsigned, unsigned);
61 struct Layout::Pointers
64 unsigned Geometry::*dim;
65 Packing Slot::*packing;
66 unsigned Sides::*low_margin;
67 unsigned Sides::*high_margin;
68 unsigned Layout::*spacing;
71 Layout::Pointers Layout::pointers[2] =
73 &Geometry::x, &Geometry::w,
75 &Sides::left, &Sides::right,
78 &Geometry::y, &Geometry::h,
80 &Sides::bottom, &Sides::top,
94 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
98 void Layout::set_container(Container &c)
101 throw logic_error("container!=0");
106 void Layout::set_margin(const Sides &m)
113 void Layout::add_widget(Widget &wdg)
116 throw logic_error("!container");
118 Slot *slot = create_slot(wdg);
119 for(list<Constraint>::iterator i=slot->constraints.begin(); i!=slot->constraints.end(); ++i)
120 i->target.constraints.push_back(Constraint(complement(i->type), *slot));
121 slot->index = slots.size();
122 slots.push_back(slot);
127 void Layout::remove_widget(Widget &wdg)
129 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
130 if(&(*i)->widget==&wdg)
132 for(list<Slot *>::iterator j=slots.begin(); j!=slots.end(); ++j)
135 for(list<Constraint>::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); )
138 (*j)->constraints.erase(k++);
148 for(i=slots.begin(); i!=slots.end(); ++i, ++n)
156 Layout::Slot *Layout::create_slot(Widget &wdg)
158 return new Slot(*this, wdg);
161 Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
163 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
164 if(&(*i)->widget==&wdg)
167 throw hierarchy_error("widget not in layout");
170 Layout::ConstraintType Layout::complement(ConstraintType type)
174 else if(type==LEFT_OF)
184 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt)
187 throw invalid_argument("&src==&tgt");
189 Slot &src_slot = get_slot_for_widget(src);
190 Slot &tgt_slot = get_slot_for_widget(tgt);
192 for(list<Constraint>::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i)
193 if(i->type==type && &i->target==&tgt_slot)
196 src_slot.constraints.push_back(Constraint(type, tgt_slot));
197 tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
202 void Layout::set_gravity(Widget &wdg, int h, int v)
204 Slot &slot = get_slot_for_widget(wdg);
206 slot.horiz_pack.gravity = h;
207 slot.vert_pack.gravity = v;
212 void Layout::set_expand(Widget &wdg, bool h, bool v)
214 Slot &slot = get_slot_for_widget(wdg);
216 slot.horiz_pack.expand = h;
217 slot.vert_pack.expand = v;
222 void Layout::update()
224 solve_constraints(HORIZONTAL);
225 solve_constraints(VERTICAL);
227 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
228 (*i)->widget.set_geometry((*i)->geom);
231 void Layout::solve_constraints(int dir)
233 Pointers &ptrs = pointers[dir&VERTICAL];
235 /* Set up a linear program to solve the constraints. The program matrix has
236 five columns for each widget, and one constant column. The first and second
237 columns of a widget are its position and dimension, respectively. The
238 remaining three are slack columns; see below for their purposes. */
239 LinearProgram linprog(slots.size()*5+1);
240 float weight = slots.size();
241 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
243 linprog.get_objective_row()[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
244 linprog.get_objective_row()[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
247 // Prevent the widget from going past the container's low edge.
248 LinearProgram::Row row = linprog.add_row();
249 row[(*i)->index*5] = 1;
250 row[(*i)->index*5+2] = -1;
251 row.back() = margin.*(ptrs.low_margin);
255 // Prevent the widget from going past the container's high edge.
256 LinearProgram::Row row = linprog.add_row();
257 row[(*i)->index*5] = 1;
258 row[(*i)->index*5+1] = 1;
259 row[(*i)->index*5+3] = 1;
260 row.back() = container->get_geometry().*(ptrs.dim)-margin.*(ptrs.high_margin);
264 /* Only allow the widget's dimension to increase. The geometry has
265 previously been set to the smallest allowable size. */
266 LinearProgram::Row row = linprog.add_row();
267 row[(*i)->index*5+1] = 1;
268 row[(*i)->index*5+4] = -1;
269 row.back() = (*i)->autosize_geom.*(ptrs.dim);
272 /* Add rows for user-defined constraints. Below/above and left/right of
273 constraints are always added in pairs, so it's only necessary to create a
275 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
276 if((j->type&1)==dir && j->type!=BELOW && j->type!=LEFT_OF)
278 LinearProgram::Row row = linprog.add_row();
280 row[(*i)->index*5] = 1;
282 row[(*i)->index*5+1] = 1;
283 if(j->type&TARGET_POS)
284 row[j->target.index*5] = -1;
285 if(j->type&TARGET_DIM)
286 row[j->target.index*5+1] = -1;
288 row.back() = this->*(ptrs.spacing);
295 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
297 (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5);
298 (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1);
303 Layout::Constraint::Constraint(ConstraintType t, Slot &s):
309 Layout::Packing::Packing():
315 Layout::Slot::Slot(Layout &l, Widget &w):
320 vert_pack.gravity = 1;
321 widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
323 autosize_geom = widget.get_geometry();
326 void Layout::Slot::autosize_changed()
329 autosize_geom = widget.get_geometry();
331 // If the widget fits in the area it had, just leave it there.
332 if(autosize_geom.w<=geom.w && autosize_geom.h<=geom.h)
333 widget.set_geometry(geom);
339 Layout::LinearProgram::LinearProgram(unsigned s):
347 Layout::LinearProgram::Row Layout::LinearProgram::add_row()
349 return Row(*this, n_rows++);
352 Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r)
355 throw out_of_range("LinearProgram::operator[]");
357 return Row(*this, r);
360 Layout::LinearProgram::Row Layout::LinearProgram::get_objective_row()
362 return Row(*this, 0);
365 float Layout::LinearProgram::get_variable(unsigned i)
367 if(!solved || infeasible)
368 throw logic_error("not solved");
370 throw out_of_range("LinearProgram::get_variable");
372 if(unsigned r = columns[i].basic)
373 return columns.back().values[r];
378 bool Layout::LinearProgram::solve()
380 if(solved || infeasible)
383 /* Solve the program using the simplex method. The column representing the
384 objective variable is kept implicit, as it would never change during the
385 execution of the algorithm. */
389 add_artificial_variables();
391 // Solve the phase 1 problem.
394 /* All artificial variables should now be non-basic and thus zero, so the
395 objective function's value should also be zero. If it isn't, the original
396 program can't be solved. */
397 if(columns.back().values.front())
403 remove_artificial_variables();
405 // Solve the phase 2 problem. We already know it to be feasible.
413 void Layout::LinearProgram::prepare_columns()
415 /* See if any variables are already basic. A basic variable must only have
416 a nonzero coefficient on one row, and its product with the constant column
417 must not be negative. Only one variable can be basic for any given row. */
418 vector<float> basic_coeff(n_rows, 0.0f);
419 const vector<float> &constants = columns.back().values;
420 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
422 if(i->values.size()>=2 && i->values.back()!=0.0f && (constants.size()<i->values.size() || i->values.back()*constants[i->values.size()-1]>=0.0f) && basic_coeff[i->values.size()-1]==0.0f)
425 for(unsigned j=1; (basic && j+1<i->values.size()); ++j)
426 basic = (i->values[j]==0.0f);
429 i->basic = i->values.size()-1;
430 basic_coeff[i->basic] = -i->values.front()/i->values.back();
436 // Price out the newly-created basic variables.
437 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
438 if(!i->values.empty())
440 for(unsigned j=0; j<i->values.size(); ++j)
441 i->values.front() += basic_coeff[j]*i->values[j];
445 void Layout::LinearProgram::add_artificial_variables()
447 vector<unsigned> artificial_rows(n_rows-1);
448 for(unsigned i=0; i<artificial_rows.size(); ++i)
449 artificial_rows[i] = i+1;
451 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
453 artificial_rows[i->basic-1] = 0;
454 artificial_rows.erase(std::remove(artificial_rows.begin(), artificial_rows.end(), 0), artificial_rows.end());
456 /* Force all non-basic columns fully into existence and relocate objective
457 row to bottom in preparation of phase 1. A new objective row is calculated
458 by pricing out the constraint rows. */
459 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
462 float objective = 0.0f;
463 if(!i->values.empty())
465 objective = i->values.front();
466 i->values.front() = 0.0f;
467 for(vector<unsigned>::iterator j=artificial_rows.begin(); j!=artificial_rows.end(); ++j)
468 if(*j<i->values.size())
469 i->values.front() += i->values[*j];
471 i->values.resize(n_rows+1, 0.0f);
472 i->values.back() = objective;
475 if(artificial_rows.empty())
478 /* Create artificial variables for phase 1. This ensures that each row has
479 a basic variable associated with it. The original objective row already
480 contains the implicit objective variable, which is basic. */
481 columns.resize(n_columns+artificial_rows.size());
482 columns.back() = columns[n_columns-1];
483 columns[n_columns-1].values.clear();
484 for(unsigned i=0; i<artificial_rows.size(); ++i)
485 columns[n_columns+i-1].basic = artificial_rows[i];
488 void Layout::LinearProgram::remove_artificial_variables()
490 /* See if any artificial variables are still basic. This could be because
491 the program is degenerate. To avoid trouble later on, use pivots to make
492 some of the original variables basic instead.
494 I don't fully understand why this is needed, but it appears to work. */
495 for(unsigned i=n_columns-1; i+1<columns.size(); ++i)
496 if(columns[i].basic && columns.back().values[columns[i].basic]==0.0f)
498 for(unsigned j=0; j+1<n_columns; ++j)
499 if(!columns[j].basic && columns[j].values[columns[i].basic]!=0.0f)
501 make_basic_column(j, columns[i].basic);
506 /* Get rid of the artificial variables and restore the original objective
507 row to form the phase 2 problem. */
508 columns.erase(columns.begin()+(n_columns-1), columns.end()-1);
509 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
512 i->values.front() = i->values.back();
513 i->values.pop_back();
517 unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c)
519 /* Pick the row with the minimum ratio between the constant column and the
520 pivot column. This ensures that when the pivot column is made basic, values
521 in the constant column stay positive.
523 The use of n_rows instead of the true size of the column is intentional,
524 since the relocated objective row must be ignored in phase 1. */
525 float best = numeric_limits<float>::infinity();
527 for(unsigned i=1; i<n_rows; ++i)
528 if(columns[c].values[i]>0)
530 float ratio = columns.back().values[i]/columns[c].values[i];
541 void Layout::LinearProgram::make_basic_column(unsigned c, unsigned r)
543 /* Perform row transfer operations to make the pivot column basic,
544 containing a 1 on the pivot row. */
545 for(unsigned i=0; i<columns.size(); ++i)
546 if(i!=c && (columns[i].basic==r || (!columns[i].basic && columns[i].values[r])))
550 columns[i].values.resize(columns.back().values.size(), 0.0f);
551 columns[i].values[columns[i].basic] = 1.0f;
552 columns[i].basic = 0;
555 float scale = columns[i].values[r]/columns[c].values[r];
557 columns[i].values[r] = scale;
558 for(unsigned j=0; j<columns[i].values.size(); ++j)
560 columns[i].values[j] -= scale*columns[c].values[j];
563 columns[c].basic = r;
564 columns[c].values.clear();
567 bool Layout::LinearProgram::pivot()
569 /* Pick a nonbasic column and make it basic. Requiring a positive objective
570 coefficient ensures that the objective function's value will decrease in the
572 for(unsigned i=0; i+1<columns.size(); ++i)
573 if(!columns[i].basic && columns[i].values.front()>0)
574 if(unsigned row = find_minimal_ratio(i))
576 make_basic_column(i, row);
584 Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i):
589 float &Layout::LinearProgram::Row::operator[](unsigned c)
591 if(c>=linprog.n_columns)
592 throw out_of_range("Row::operator[]");
594 Column &column = linprog.columns[c];
595 if(column.values.size()<=index)
596 column.values.resize(index+1);
598 return column.values[index];
601 float &Layout::LinearProgram::Row::back()
603 return (*this)[linprog.n_columns-1];
607 Layout::LinearProgram::Column::Column():