--- /dev/null
+#include <limits>
+#include "container.h"
+#include "layout.h"
+#include "widget.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GLtk {
+
+class Layout::LinearProgram
+{
+public:
+ class Row
+ {
+ private:
+ LinearProgram &linprog;
+ unsigned index;
+
+ public:
+ Row(LinearProgram &, unsigned);
+
+ float &operator[](unsigned);
+ float &back();
+ };
+
+private:
+ struct Column
+ {
+ unsigned basic;
+ std::vector<float> values;
+
+ Column();
+ };
+
+ unsigned n_columns;
+ unsigned n_rows;
+ std::vector<Column> columns;
+ bool solved;
+ bool infeasible;
+
+public:
+ LinearProgram(unsigned);
+
+ Row add_row();
+ Row operator[](unsigned);
+ Row get_object_row();
+ bool solve();
+ float get_variable(unsigned);
+private:
+ unsigned find_minimal_ratio(unsigned);
+ void make_basic_column(unsigned, unsigned);
+ bool pivot();
+};
+
+
+struct Layout::Pointers
+{
+ int Geometry::*pos;
+ unsigned Geometry::*dim;
+ Packing Slot::*packing;
+ unsigned Sides::*low_margin;
+ unsigned Sides::*high_margin;
+ unsigned Layout::*spacing;
+};
+
+Layout::Pointers Layout::pointers[2] =
+{ {
+ &Geometry::x, &Geometry::w,
+ &Slot::horiz_pack,
+ &Sides::left, &Sides::right,
+ &Layout::col_spacing
+ }, {
+ &Geometry::y, &Geometry::h,
+ &Slot::vert_pack,
+ &Sides::bottom, &Sides::top,
+ &Layout::row_spacing
+} };
+
+
+Layout::Layout():
+ container(0),
+ margin(8),
+ row_spacing(5),
+ col_spacing(4)
+{ }
+
+Layout::~Layout()
+{
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ delete *i;
+}
+
+void Layout::set_container(Container &c)
+{
+ if(container)
+ throw InvalidState("This layout is already assigned to a Container");
+
+ container = &c;
+}
+
+void Layout::set_margin(const Sides &m)
+{
+ margin = m;
+ if(container)
+ update();
+}
+
+void Layout::add_widget(Widget &wdg)
+{
+ if(!container)
+ throw InvalidState("Can't add Widgets without a Container");
+
+ Slot *slot = create_slot(wdg);
+ for(list<Constraint>::iterator i=slot->constraints.begin(); i!=slot->constraints.end(); ++i)
+ i->target.constraints.push_back(Constraint(complement(i->type), *slot));
+ slot->index = slots.size();
+ slots.push_back(slot);
+ if(container)
+ update();
+}
+
+void Layout::remove_widget(Widget &wdg)
+{
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ if(&(*i)->widget==&wdg)
+ {
+ for(list<Slot *>::iterator j=slots.begin(); j!=slots.end(); ++j)
+ if(j!=i)
+ {
+ for(list<Constraint>::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); )
+ {
+ if(&k->target==*i)
+ (*j)->constraints.erase(k++);
+ else
+ ++k;
+ }
+ }
+
+ delete *i;
+ slots.erase(i);
+
+ unsigned n = 0;
+ for(i=slots.begin(); i!=slots.end(); ++i, ++n)
+ (*i)->index = n;
+
+ update();
+ return;
+ }
+}
+
+Layout::Slot *Layout::create_slot(Widget &wdg)
+{
+ return new Slot(*this, wdg);
+}
+
+Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
+{
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ if(&(*i)->widget==&wdg)
+ return **i;
+
+ throw InvalidParameterValue("Widget is not in the Layout");
+}
+
+Layout::ConstraintType Layout::complement(ConstraintType type)
+{
+ if(type==RIGHT_OF)
+ return LEFT_OF;
+ else if(type==LEFT_OF)
+ return RIGHT_OF;
+ else if(type==ABOVE)
+ return BELOW;
+ else if(type==BELOW)
+ return ABOVE;
+ else
+ return type;
+}
+
+void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt)
+{
+ Slot &src_slot = get_slot_for_widget(src);
+ Slot &tgt_slot = get_slot_for_widget(tgt);
+
+ for(list<Constraint>::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i)
+ if(i->type==type && &i->target==&tgt_slot)
+ return;
+
+ src_slot.constraints.push_back(Constraint(type, tgt_slot));
+ tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
+
+ update();
+}
+
+void Layout::set_gravity(Widget &wdg, int h, int v)
+{
+ Slot &slot = get_slot_for_widget(wdg);
+
+ slot.horiz_pack.gravity = h;
+ slot.vert_pack.gravity = v;
+
+ update();
+}
+
+void Layout::set_expand(Widget &wdg, bool h, bool v)
+{
+ Slot &slot = get_slot_for_widget(wdg);
+
+ slot.horiz_pack.expand = h;
+ slot.vert_pack.expand = v;
+
+ update();
+}
+
+void Layout::update()
+{
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ {
+ (*i)->widget.autosize();
+ (*i)->geom = (*i)->widget.get_geometry();
+ }
+
+ solve_constraints(HORIZONTAL);
+ solve_constraints(VERTICAL);
+
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ (*i)->widget.set_geometry((*i)->geom);
+
+}
+
+void Layout::solve_constraints(int dir)
+{
+ Pointers &ptrs = pointers[dir&VERTICAL];
+
+ LinearProgram linprog(slots.size()*5+1);
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ {
+ linprog.get_object_row()[(*i)->index*5] = 0.1*((*i)->*(ptrs.packing)).gravity;
+ linprog.get_object_row()[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? 5 : -1);
+
+ {
+ LinearProgram::Row row = linprog.add_row();
+ row[(*i)->index*5] = 1;
+ row[(*i)->index*5+2] = -1;
+ row.back() = margin.*(ptrs.low_margin);
+ }
+
+ {
+ LinearProgram::Row row = linprog.add_row();
+ row[(*i)->index*5] = 1;
+ row[(*i)->index*5+1] = 1;
+ row[(*i)->index*5+3] = 1;
+ row.back() = container->get_geometry().*(ptrs.dim)-margin.*(ptrs.high_margin);
+ }
+
+ {
+ LinearProgram::Row row = linprog.add_row();
+ row[(*i)->index*5+1] = 1;
+ row[(*i)->index*5+4] = -1;
+ row.back() = (*i)->geom.*(ptrs.dim);
+ }
+
+ for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
+ {
+ if((j->type&1)==dir && j->type!=BELOW && j->type!=LEFT_OF)
+ {
+ LinearProgram::Row row = linprog.add_row();
+ if(j->type&SELF_POS)
+ row[(*i)->index*5] = 1;
+ if(j->type&SELF_DIM)
+ row[(*i)->index*5+1] = 1;
+ if(j->type&TARGET_POS)
+ row[j->target.index*5] = -1;
+ if(j->type&TARGET_DIM)
+ row[j->target.index*5+1] = -1;
+ if(j->type&SPACING)
+ row.back() = this->*(ptrs.spacing);
+ }
+ }
+ }
+
+ if(!linprog.solve())
+ return;
+
+ for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+ {
+ (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5);
+ (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1);
+ }
+}
+
+
+Layout::Constraint::Constraint(ConstraintType t, Slot &s):
+ type(t),
+ target(s)
+{ }
+
+
+Layout::Packing::Packing():
+ gravity(-1),
+ expand(false)
+{ }
+
+
+Layout::Slot::Slot(Layout &l, Widget &w):
+ layout(l),
+ index(0),
+ widget(w)
+{
+ vert_pack.gravity = 1;
+ widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
+}
+
+void Layout::Slot::autosize_changed()
+{
+ layout.update();
+}
+
+
+Layout::LinearProgram::LinearProgram(unsigned s):
+ n_columns(s),
+ n_rows(1),
+ columns(n_columns),
+ solved(false),
+ infeasible(false)
+{ }
+
+Layout::LinearProgram::Row Layout::LinearProgram::add_row()
+{
+ return Row(*this, n_rows++);
+}
+
+Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r)
+{
+ if(r>=n_rows)
+ throw InvalidParameterValue("Row index out of range");
+
+ return Row(*this, r);
+}
+
+Layout::LinearProgram::Row Layout::LinearProgram::get_object_row()
+{
+ return Row(*this, 0);
+}
+
+float Layout::LinearProgram::get_variable(unsigned i)
+{
+ if(!solved || infeasible)
+ throw InvalidState("Not solved");
+ if(i+1>=n_columns)
+ throw InvalidParameterValue("Variable index out of range");
+
+ unsigned r = columns[i].basic;
+ return columns.back().values[r];
+}
+
+bool Layout::LinearProgram::solve()
+{
+ if(solved || infeasible)
+ return !infeasible;
+
+ // Force all columns fully into existence and relocate objective row to bottom
+ for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
+ {
+ float objective = i->values.front();
+ i->values.front() = 0.0f;
+ for(vector<float>::iterator j=i->values.begin(); j!=i->values.end(); ++j)
+ i->values.front() += *j;
+ i->values.resize(n_rows+1, 0.0f);
+ i->values.back() = objective;
+ }
+
+ // Create artificial variables for phase 1
+ columns.resize(n_columns+n_rows-1);
+ columns.back() = columns[n_columns-1];
+ columns[n_columns-1].values.clear();
+ for(unsigned i=1; i<n_rows; ++i)
+ {
+ Column &column = columns[n_columns+i-2];
+ column.basic = i;
+ }
+
+ // Solve the phase 1 problem
+ while(pivot()) ;
+
+ if(columns.back().values.front())
+ {
+ infeasible = true;
+ return false;
+ }
+
+ // Get rid of artificial columns and restore objective row
+ columns.erase(columns.begin()+(n_columns-1), columns.end()-1);
+ for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
+ if(!i->basic)
+ {
+ i->values.front() = i->values.back();
+ i->values.pop_back();
+ }
+
+ while(pivot()) ;
+
+ solved = true;
+
+ return true;
+}
+
+unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c)
+{
+ float best = numeric_limits<float>::infinity();
+ unsigned row = 0;
+ /* Intentionally use n_rows since we need to ignore the relocated original
+ objective row in phase 1 */
+ for(unsigned i=1; i<n_rows; ++i)
+ if(columns[c].values[i]>0)
+ {
+ float ratio = columns.back().values[i]/columns[c].values[i];
+ if(ratio<best)
+ {
+ best = ratio;
+ row = i;
+ }
+ }
+
+ return row;
+}
+
+void Layout::LinearProgram::make_basic_column(unsigned c, unsigned r)
+{
+ for(unsigned i=0; i<columns.size(); ++i)
+ if(i!=c && (columns[i].basic==r || (!columns[i].basic && columns[i].values[r])))
+ {
+ if(columns[i].basic)
+ {
+ columns[i].values.resize(columns.back().values.size(), 0.0f);
+ columns[i].values[columns[i].basic] = 1.0f;
+ columns[i].basic = 0;
+ }
+
+ float scale = columns[i].values[r]/columns[c].values[r];
+
+ for(unsigned j=0; j<columns[i].values.size(); ++j)
+ {
+ if(j==r)
+ columns[i].values[j] = scale;
+ else
+ columns[i].values[j] -= scale*columns[c].values[j];
+ }
+ }
+
+ columns[c].basic = r;
+ columns[c].values.clear();
+}
+
+bool Layout::LinearProgram::pivot()
+{
+ for(unsigned i=0; i+1<columns.size(); ++i)
+ if(!columns[i].basic && columns[i].values.front()>0)
+ if(unsigned row = find_minimal_ratio(i))
+ {
+ make_basic_column(i, row);
+ return true;
+ }
+
+ return false;
+}
+
+
+Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i):
+ linprog(lp),
+ index(i)
+{ }
+
+float &Layout::LinearProgram::Row::operator[](unsigned c)
+{
+ if(c>=linprog.n_columns)
+ throw InvalidParameterValue("Column index out of range");
+
+ Column &column = linprog.columns[c];
+ if(column.values.size()<=index)
+ column.values.resize(index+1);
+
+ return column.values[index];
+}
+
+float &Layout::LinearProgram::Row::back()
+{
+ return (*this)[linprog.n_columns-1];
+}
+
+
+Layout::LinearProgram::Column::Column():
+ basic(0)
+{ }
+
+} // namespace GLtk
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GLTK_LAYOUT_H_
+#define MSP_GLTK_LAYOUT_H_
+
+#include <list>
+#include <set>
+#include <sigc++/trackable.h>
+#include "geometry.h"
+
+namespace Msp {
+namespace GLtk {
+
+class Container;
+class Widget;
+
+class Layout
+{
+private:
+ enum
+ {
+ HORIZONTAL = 0,
+ VERTICAL = 1,
+ SELF_POS = 2,
+ SELF_DIM = 4,
+ TARGET_POS = 8,
+ TARGET_DIM = 16,
+ SPACING = 32
+ };
+
+public:
+ enum ConstraintType
+ {
+ ABOVE = VERTICAL|SELF_POS|TARGET_POS|TARGET_DIM|SPACING,
+ BELOW = VERTICAL|SELF_POS|SELF_DIM|TARGET_POS|SPACING,
+ RIGHT_OF = HORIZONTAL|SELF_POS|TARGET_POS|TARGET_DIM|SPACING,
+ LEFT_OF = HORIZONTAL|SELF_POS|SELF_DIM|TARGET_POS|SPACING,
+ ALIGN_TOP = VERTICAL|SELF_POS|SELF_DIM|TARGET_POS|TARGET_DIM,
+ ALIGN_BOTTOM = VERTICAL|SELF_POS|TARGET_POS,
+ ALIGN_RIGHT = HORIZONTAL|SELF_POS|SELF_DIM|TARGET_POS|TARGET_DIM,
+ ALIGN_LEFT = HORIZONTAL|SELF_POS|TARGET_POS,
+ COPY_WIDTH = HORIZONTAL|SELF_DIM|TARGET_DIM,
+ COPY_HEIGHT = VERTICAL|SELF_DIM|TARGET_DIM
+ };
+
+protected:
+ struct Slot;
+
+ struct Constraint
+ {
+ ConstraintType type;
+ Slot ⌖
+
+ Constraint(ConstraintType, Slot &);
+ };
+
+ struct Packing
+ {
+ int gravity;
+ bool expand;
+
+ Packing();
+ };
+
+ struct Slot: public sigc::trackable
+ {
+ Layout &layout;
+ unsigned index;
+ Widget &widget;
+ Geometry geom;
+ std::list<Constraint> constraints;
+ Packing horiz_pack;
+ Packing vert_pack;
+
+ Slot(Layout &, Widget &);
+ virtual ~Slot() { }
+
+ void autosize_changed();
+ };
+
+ class LinearProgram;
+ struct Pointers;
+
+ Container *container;
+ std::list<Slot *> slots;
+ Sides margin;
+ unsigned row_spacing;
+ unsigned col_spacing;
+
+ static Pointers pointers[2];
+
+public:
+ Layout();
+ virtual ~Layout();
+
+ void set_container(Container &);
+ void set_margin(const Sides &);
+ void set_spacing(unsigned);
+ void set_row_spacing(unsigned);
+ void set_column_spacing(unsigned);
+
+ void add_widget(Widget &);
+ void remove_widget(Widget &);
+protected:
+ virtual Slot *create_slot(Widget &);
+ Slot &get_slot_for_widget(Widget &);
+ static ConstraintType complement(ConstraintType);
+public:
+ void add_constraint(Widget &, ConstraintType, Widget &);
+ void set_gravity(Widget &, int, int);
+ void set_expand(Widget &, bool, bool);
+
+ void update();
+
+protected:
+ void find_constraint_group(Slot &, ConstraintType, std::set<Slot *> &);
+ void sort_slots(std::list<Slot *> &, ConstraintType);
+ void solve_constraints(int);
+};
+
+} // namespace GLtk
+} // namespace Msp
+
+#endif