3 #include "arrangement.h"
13 class Layout::LinearProgram
19 LinearProgram &linprog;
23 Row(LinearProgram &, unsigned);
25 float &operator[](unsigned);
33 std::vector<float> values;
40 std::vector<Column> columns;
45 LinearProgram(unsigned);
48 Row operator[](unsigned);
49 Row get_objective_row();
50 float get_variable(unsigned);
53 void prepare_columns();
54 void add_artificial_variables();
55 void remove_artificial_variables();
56 unsigned find_minimal_ratio(unsigned);
57 void make_basic_column(unsigned, unsigned);
62 struct Layout::Pointers
65 unsigned Geometry::*dim;
66 Packing Slot::*packing;
67 unsigned Sides::*low_margin;
68 unsigned Sides::*high_margin;
69 unsigned Layout::*spacing;
72 Layout::Pointers Layout::pointers[2] =
74 &Geometry::x, &Geometry::w,
76 &Sides::left, &Sides::right,
79 &Geometry::y, &Geometry::h,
81 &Sides::bottom, &Sides::top,
93 n_slack_constraints[0] = 0;
94 n_slack_constraints[1] = 0;
99 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
103 void Layout::set_container(Container &c)
106 throw logic_error("container!=0");
111 void Layout::set_margin(const Sides &m)
118 void Layout::set_spacing(unsigned s)
126 void Layout::set_row_spacing(unsigned s)
133 void Layout::set_column_spacing(unsigned s)
140 void Layout::push_arrangement(Arrangement &arr)
142 arrangement_stack.push_back(&arr);
145 Arrangement *Layout::get_arrangement() const
147 if(arrangement_stack.empty())
150 return arrangement_stack.back();
153 void Layout::pop_arrangement(Arrangement &arr)
155 list<Arrangement *>::iterator begin = find(arrangement_stack.begin(), arrangement_stack.end(), &arr);
156 if(begin==arrangement_stack.end())
161 Arrangement *top = arrangement_stack.back();
162 arrangement_stack.pop_back();
163 if(!arrangement_stack.empty())
164 arrangement_stack.back()->arrange(*top);
170 void Layout::add_widget(Widget &wdg)
173 throw logic_error("!container");
175 slots.push_back(new Slot(*this, wdg));
176 update_slot_indices();
177 if(!arrangement_stack.empty())
178 arrangement_stack.back()->arrange(wdg);
183 void Layout::remove_widget(Widget &wdg)
185 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
186 if(&(*i)->widget==&wdg)
188 for(list<Slot *>::iterator j=slots.begin(); j!=slots.end(); ++j)
191 for(list<Constraint>::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); )
194 (*j)->constraints.erase(k++);
203 update_slot_indices();
209 void Layout::update_slot_indices()
212 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
214 if((*i)->widget.is_visible())
215 (*i)->index = n_active_slots++;
220 n_slack_constraints[0] = 0;
221 n_slack_constraints[1] = 0;
222 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
225 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
226 if(j->target.index>(*i)->index && (j->type&SLACK))
227 ++n_slack_constraints[j->type&1];
231 Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
233 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
234 if(&(*i)->widget==&wdg)
237 throw hierarchy_error("widget not in layout");
240 Layout::ConstraintType Layout::complement(ConstraintType type)
242 return static_cast<ConstraintType>((type&~(SELF_MASK|TARGET_MASK)) | ((type&SELF_MASK)<<2) | ((type&TARGET_MASK)>>2));
245 void Layout::create_constraint(Widget &src, ConstraintType type, Widget &tgt, int sp)
248 throw invalid_argument("&src==&tgt");
250 Slot &src_slot = get_slot_for_widget(src);
251 Slot &tgt_slot = get_slot_for_widget(tgt);
253 for(list<Constraint>::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i)
254 if(i->type==type && &i->target==&tgt_slot)
257 src_slot.constraints.push_back(Constraint(type, tgt_slot));
258 src_slot.constraints.back().spacing = sp;
259 tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
260 tgt_slot.constraints.back().spacing = sp;
262 update_slot_indices();
266 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt)
268 create_constraint(src, type, tgt, -1);
271 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt, unsigned spacing)
273 create_constraint(src, type, tgt, spacing);
276 void Layout::set_gravity(Widget &wdg, int h, int v)
278 Slot &slot = get_slot_for_widget(wdg);
280 slot.horiz_pack.gravity = h;
281 slot.vert_pack.gravity = v;
286 void Layout::set_expand(Widget &wdg, bool h, bool v)
288 Slot &slot = get_slot_for_widget(wdg);
290 slot.horiz_pack.expand = h;
291 slot.vert_pack.expand = v;
296 void Layout::update()
298 solve_constraints(HORIZONTAL, UPDATE);
299 solve_constraints(VERTICAL, UPDATE);
301 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
302 (*i)->widget.set_geometry((*i)->geom);
305 void Layout::autosize()
307 solve_constraints(HORIZONTAL, AUTOSIZE);
308 solve_constraints(VERTICAL, AUTOSIZE);
310 container->set_size(autosize_geom.w, autosize_geom.h);
313 void Layout::solve_constraints(int dir, SolveMode mode)
315 Pointers &ptrs = pointers[dir&VERTICAL];
317 const Geometry &geom = container->get_geometry();
318 if(mode==UPDATE && geom.*(ptrs.dim)<margin.*(ptrs.low_margin)+margin.*(ptrs.high_margin))
321 /* Set up a linear program to solve the constraints. The program matrix has
322 five columns for each widget, and one constant column. The first and second
323 columns of a widget are its position and dimension, respectively. The
324 remaining three are slack columns; see below for their purposes. */
325 LinearProgram linprog(n_active_slots*5+n_slack_constraints[dir]+1);
326 float weight = slots.size();
327 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
332 LinearProgram::Row objective = linprog.get_objective_row();
335 objective[(*i)->index*5] = -1;
336 objective[(*i)->index*5+1] = -1;
340 objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
341 objective[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
345 // Prevent the widget from going past the container's low edge.
346 LinearProgram::Row row = linprog.add_row();
347 row[(*i)->index*5] = 1;
348 row[(*i)->index*5+2] = -1;
349 row.back() = margin.*(ptrs.low_margin);
354 // Prevent the widget from going past the container's high edge.
355 LinearProgram::Row row = linprog.add_row();
356 row[(*i)->index*5] = 1;
357 row[(*i)->index*5+1] = 1;
358 row[(*i)->index*5+3] = 1;
359 row.back() = geom.*(ptrs.dim)-margin.*(ptrs.high_margin);
362 if(((*i)->*(ptrs.packing)).gravity==0)
364 /* This forces the widget's distance from the left and right edge of
365 the container to be equal. It's a bit of a hack, but more time and
366 thought is needed for a better solution. */
367 LinearProgram::Row row = linprog.add_row();
368 row[(*i)->index*5+2] = 1;
369 row[(*i)->index*5+3] = -1;
373 /* Don't allow the widget's dimension to get below that determined
375 LinearProgram::Row row = linprog.add_row();
376 row[(*i)->index*5+1] = 1;
377 row[(*i)->index*5+4] = -1;
378 row.back() = (*i)->autosize_geom.*(ptrs.dim);
381 /* Add rows for user-defined constraints. Constraints are always added
382 in pairs, so it's only necessary to create a row for one half. */
383 unsigned k = n_active_slots*5;
384 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
385 if(j->target.index>(*i)->index && (j->type&1)==dir)
387 LinearProgram::Row row = linprog.add_row();
388 float polarity = ((j->type&SELF_DIM) ? -1 : 1);
390 row[(*i)->index*5] = polarity;
392 row[(*i)->index*5+1] = polarity;
393 if(j->type&TARGET_POS)
394 row[j->target.index*5] = -polarity;
395 if(j->type&TARGET_DIM)
396 row[j->target.index*5+1] = -polarity;
398 row.back() = (j->spacing>=0 ? j->spacing : this->*(ptrs.spacing));
409 autosize_geom.*(ptrs.dim) = 0;
410 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
413 int high_edge = linprog.get_variable((*i)->index*5)+linprog.get_variable((*i)->index*5+1);
414 autosize_geom.*(ptrs.dim) = max(autosize_geom.*(ptrs.dim), high_edge+margin.*(ptrs.high_margin));
419 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
422 (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5);
423 (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1);
429 Layout::Constraint::Constraint(ConstraintType t, Slot &s):
436 Layout::Packing::Packing():
442 Layout::Slot::Slot(Layout &l, Widget &w):
447 vert_pack.gravity = 1;
448 widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
449 widget.signal_visibility_changed.connect(sigc::mem_fun(this, &Slot::visibility_changed));
451 autosize_geom = widget.get_geometry();
454 void Layout::Slot::autosize_changed()
457 autosize_geom = widget.get_geometry();
459 if(!widget.is_visible())
462 // If the widget fits in the area it had, just leave it there.
463 if(autosize_geom.w<=geom.w && autosize_geom.h<=geom.h)
464 widget.set_geometry(geom);
467 layout.container->signal_autosize_changed.emit();
472 void Layout::Slot::visibility_changed(bool v)
474 layout.update_slot_indices();
477 layout.container->signal_autosize_changed.emit();
483 Layout::LinearProgram::LinearProgram(unsigned s):
491 Layout::LinearProgram::Row Layout::LinearProgram::add_row()
493 return Row(*this, n_rows++);
496 Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r)
499 throw out_of_range("LinearProgram::operator[]");
501 return Row(*this, r);
504 Layout::LinearProgram::Row Layout::LinearProgram::get_objective_row()
506 return Row(*this, 0);
509 float Layout::LinearProgram::get_variable(unsigned i)
511 if(!solved || infeasible)
512 throw logic_error("not solved");
514 throw out_of_range("LinearProgram::get_variable");
516 if(unsigned r = columns[i].basic)
517 return columns.back().values[r];
522 bool Layout::LinearProgram::solve()
524 if(solved || infeasible)
527 /* Solve the program using the simplex method. The column representing the
528 objective variable is kept implicit, as it would never change during the
529 execution of the algorithm. */
533 add_artificial_variables();
535 // Solve the phase 1 problem.
538 /* All artificial variables should now be non-basic and thus zero, so the
539 objective function's value should also be zero. If it isn't, the original
540 program can't be solved. */
541 if(columns.back().values.front())
547 remove_artificial_variables();
549 // Solve the phase 2 problem. We already know it to be feasible.
557 void Layout::LinearProgram::prepare_columns()
559 /* See if any variables are already basic. A basic variable must only have
560 a nonzero coefficient on one row, and its product with the constant column
561 must not be negative. Only one variable can be basic for any given row. */
562 vector<float> obj_coeff(n_rows, 0.0f);
563 vector<float> row_coeff(n_rows, 1.0f);
564 const vector<float> &constants = columns.back().values;
565 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
567 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) && obj_coeff[i->values.size()-1]==0.0f)
570 for(unsigned j=1; (basic && j+1<i->values.size()); ++j)
571 basic = (i->values[j]==0.0f);
574 i->basic = i->values.size()-1;
575 row_coeff[i->basic] = 1.0f/i->values.back();
576 obj_coeff[i->basic] = -i->values.front();
582 // Price out the newly-created basic variables.
583 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
584 if(!i->values.empty())
586 for(unsigned j=0; j<i->values.size(); ++j)
588 i->values[j] *= row_coeff[j];
589 i->values.front() += obj_coeff[j]*i->values[j];
594 void Layout::LinearProgram::add_artificial_variables()
596 vector<unsigned> artificial_rows(n_rows-1);
597 for(unsigned i=0; i<artificial_rows.size(); ++i)
598 artificial_rows[i] = i+1;
600 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
602 artificial_rows[i->basic-1] = 0;
603 artificial_rows.erase(std::remove(artificial_rows.begin(), artificial_rows.end(), 0), artificial_rows.end());
605 /* Force all non-basic columns fully into existence and relocate objective
606 row to bottom in preparation of phase 1. A new objective row is calculated
607 by pricing out the constraint rows. */
608 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
611 float objective = 0.0f;
612 if(!i->values.empty())
614 objective = i->values.front();
615 i->values.front() = 0.0f;
616 for(vector<unsigned>::iterator j=artificial_rows.begin(); j!=artificial_rows.end(); ++j)
617 if(*j<i->values.size())
618 i->values.front() += i->values[*j];
620 i->values.resize(n_rows+1, 0.0f);
621 i->values.back() = objective;
624 if(artificial_rows.empty())
627 /* Create artificial variables for phase 1. This ensures that each row has
628 a basic variable associated with it. The original objective row already
629 contains the implicit objective variable, which is basic. */
630 columns.resize(n_columns+artificial_rows.size());
631 columns.back() = columns[n_columns-1];
632 columns[n_columns-1].values.clear();
633 for(unsigned i=0; i<artificial_rows.size(); ++i)
634 columns[n_columns+i-1].basic = artificial_rows[i];
637 void Layout::LinearProgram::remove_artificial_variables()
639 /* See if any artificial variables are still basic. This could be because
640 the program is degenerate. To avoid trouble later on, use pivots to make
641 some of the original variables basic instead.
643 I don't fully understand why this is needed, but it appears to work. */
644 for(unsigned i=n_columns-1; i+1<columns.size(); ++i)
645 if(columns[i].basic && columns.back().values[columns[i].basic]==0.0f)
647 for(unsigned j=0; j+1<n_columns; ++j)
648 if(!columns[j].basic && columns[j].values[columns[i].basic]!=0.0f)
650 make_basic_column(j, columns[i].basic);
655 /* Get rid of the artificial variables and restore the original objective
656 row to form the phase 2 problem. */
657 columns.erase(columns.begin()+(n_columns-1), columns.end()-1);
658 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
661 i->values.front() = i->values.back();
662 i->values.pop_back();
666 unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c)
668 /* Pick the row with the minimum ratio between the constant column and the
669 pivot column. This ensures that when the pivot column is made basic, values
670 in the constant column stay positive.
672 The use of n_rows instead of the true size of the column is intentional,
673 since the relocated objective row must be ignored in phase 1. */
674 float best = numeric_limits<float>::infinity();
676 for(unsigned i=1; i<n_rows; ++i)
677 if(columns[c].values[i]>0)
679 float ratio = columns.back().values[i]/columns[c].values[i];
690 void Layout::LinearProgram::make_basic_column(unsigned c, unsigned r)
692 /* Perform row transfer operations to make the pivot column basic,
693 containing a 1 on the pivot row. */
694 for(unsigned i=0; i<columns.size(); ++i)
695 if(i!=c && (columns[i].basic==r || (!columns[i].basic && columns[i].values[r])))
699 columns[i].values.resize(columns.back().values.size(), 0.0f);
700 columns[i].values[columns[i].basic] = 1.0f;
701 columns[i].basic = 0;
704 float scale = columns[i].values[r]/columns[c].values[r];
706 columns[i].values[r] = scale;
707 for(unsigned j=0; j<columns[i].values.size(); ++j)
709 columns[i].values[j] -= scale*columns[c].values[j];
712 columns[c].basic = r;
713 columns[c].values.clear();
716 bool Layout::LinearProgram::pivot()
718 /* Pick a nonbasic column and make it basic. Requiring a positive objective
719 coefficient ensures that the objective function's value will decrease in the
721 for(unsigned i=0; i+1<columns.size(); ++i)
722 if(!columns[i].basic && columns[i].values.front()>0)
723 if(unsigned row = find_minimal_ratio(i))
725 make_basic_column(i, row);
733 Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i):
738 float &Layout::LinearProgram::Row::operator[](unsigned c)
740 if(c>=linprog.n_columns)
741 throw out_of_range("Row::operator[]");
743 Column &column = linprog.columns[c];
744 if(column.values.size()<=index)
745 column.values.resize(index+1);
747 return column.values[index];
750 float &Layout::LinearProgram::Row::back()
752 return (*this)[linprog.n_columns-1];
756 Layout::LinearProgram::Column::Column():