]> git.tdb.fi Git - libs/gltk.git/commitdiff
Add a Layout class to automatically position widgets within a Panel
authorMikko Rasa <tdb@tdb.fi>
Mon, 28 Feb 2011 15:29:05 +0000 (15:29 +0000)
committerMikko Rasa <tdb@tdb.fi>
Mon, 28 Feb 2011 15:29:05 +0000 (15:29 +0000)
source/layout.cpp [new file with mode: 0644]
source/layout.h [new file with mode: 0644]
source/panel.cpp
source/panel.h

diff --git a/source/layout.cpp b/source/layout.cpp
new file mode 100644 (file)
index 0000000..3501948
--- /dev/null
@@ -0,0 +1,497 @@
+#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
diff --git a/source/layout.h b/source/layout.h
new file mode 100644 (file)
index 0000000..540c5ee
--- /dev/null
@@ -0,0 +1,122 @@
+#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 &target;
+
+               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
index 0ecdf1077ca0a7ae05abaa957dad2fb327493c5c..a1a453a4447022dc6c6ae9723ea904d433f64951 100644 (file)
@@ -13,6 +13,7 @@ Distributed under the LGPL
 #include "hslider.h"
 #include "indicator.h"
 #include "label.h"
+#include "layout.h"
 #include "list.h"
 #include "panel.h"
 #include "part.h"
@@ -26,11 +27,25 @@ namespace Msp {
 namespace GLtk {
 
 Panel::Panel():
+       layout(0),
        pointer_focus(0),
        pointer_grabbed(false),
        input_focus(0)
 { }
 
+Panel::~Panel()
+{
+       delete layout;
+       layout = 0;
+}
+
+void Panel::set_layout(Layout *l)
+{
+       l->set_container(*this);
+       delete layout;
+       layout = l;
+}
+
 Panel::Child *Panel::create_child(Widget *wdg)
 {
        return new Child(*this, wdg);
@@ -134,6 +149,19 @@ void Panel::key_release(unsigned key, unsigned mod)
 void Panel::focus_out()
 {
        set_input_focus(0);
+       Widget::focus_out();
+}
+
+void Panel::on_child_added(Widget &wdg)
+{
+       if(layout)
+               layout->add_widget(wdg);
+}
+
+void Panel::on_child_removed(Widget &wdg)
+{
+       if(layout)
+               layout->remove_widget(wdg);
 }
 
 void Panel::set_pointer_focus(Widget *wdg)
@@ -230,6 +258,13 @@ void Panel::Child::visibility_changed(bool v)
        }
 }
 
+void Panel::Child::autosize_changed()
+{
+       Panel &panel = static_cast<Panel &>(container);
+       if(panel.layout)
+               panel.layout->update();
+}
+
 void Panel::Child::request_focus()
 {
        Panel &panel = static_cast<Panel &>(container);
index 54e75ef5309e9e23d62645fadaf5064cafa7fddb..a5483c452c57f7b0d639511878fb8a8ef2f4b8d6 100644 (file)
@@ -13,6 +13,8 @@ Distributed under the LGPL
 namespace Msp {
 namespace GLtk {
 
+class Layout;
+
 /**
 Panels are containers for other widgets.  Panel styles should have a special
 part "children" to render the child widgets.  All properties of this part are
@@ -45,12 +47,14 @@ protected:
                virtual ~Child();
 
                void visibility_changed(bool);
+               void autosize_changed();
                void request_focus();
                void grab_pointer();
                void ungrab_pointer();
                void raise();
        };
 
+       Layout *layout;
        Widget *pointer_focus;
        bool pointer_grabbed;
        Widget *input_focus;
@@ -59,9 +63,12 @@ protected:
        Panel &operator=(const Panel &);
 public:
        Panel();
+       virtual ~Panel();
 
        virtual const char *get_class() const { return "panel"; }
 
+       void set_layout(Layout *);
+
 protected:
        virtual Child *create_child(Widget *);
 
@@ -81,8 +88,10 @@ public:
        virtual void key_press(unsigned, unsigned, wchar_t);
        virtual void key_release(unsigned, unsigned);
        virtual void focus_out();
-
 protected:
+       virtual void on_child_added(Widget &);
+       virtual void on_child_removed(Widget &);
+
        void set_pointer_focus(Widget *);
        void set_input_focus(Widget *);
 };