]> git.tdb.fi Git - libs/gltk.git/blobdiff - source/layout.cpp
Fix exception description
[libs/gltk.git] / source / layout.cpp
index 66eb66b8b98c3a1e817a5dcbca9a11116aea3829..b4cd7683c6bc60d2b615c7c46323b773e4a668cf 100644 (file)
@@ -1,5 +1,8 @@
 #include <algorithm>
 #include <limits>
+#include <msp/core/maputils.h>
+#include <msp/strings/format.h>
+#include "arrangement.h"
 #include "container.h"
 #include "layout.h"
 #include "widget.h"
@@ -88,7 +91,10 @@ Layout::Layout():
        margin(8),
        row_spacing(5),
        col_spacing(4)
-{ }
+{
+       n_slack_vars[0] = 0;
+       n_slack_vars[1] = 0;
+}
 
 Layout::~Layout()
 {
@@ -133,16 +139,45 @@ void Layout::set_column_spacing(unsigned s)
                update();
 }
 
+void Layout::push_arrangement(Arrangement &arr)
+{
+       arrangement_stack.push_back(&arr);
+}
+
+Arrangement *Layout::get_arrangement() const
+{
+       if(arrangement_stack.empty())
+               return 0;
+       else
+               return arrangement_stack.back();
+}
+
+void Layout::pop_arrangement(Arrangement &arr)
+{
+       list<Arrangement *>::iterator begin = find(arrangement_stack.begin(), arrangement_stack.end(), &arr);
+       if(begin==arrangement_stack.end())
+               return;
+
+       while(1)
+       {
+               Arrangement *top = arrangement_stack.back();
+               arrangement_stack.pop_back();
+               if(!arrangement_stack.empty())
+                       arrangement_stack.back()->arrange(*top);
+               if(top==&arr)
+                       break;
+       }
+}
+
 void Layout::add_widget(Widget &wdg)
 {
        if(!container)
                throw logic_error("!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));
-       slots.push_back(slot);
+       slots.push_back(new Slot(*this, wdg));
        update_slot_indices();
+       if(!arrangement_stack.empty())
+               arrangement_stack.back()->arrange(wdg);
        if(container)
                update();
 }
@@ -167,30 +202,44 @@ void Layout::remove_widget(Widget &wdg)
                        delete *i;
                        slots.erase(i);
 
-                       unsigned n = 0;
-                       for(i=slots.begin(); i!=slots.end(); ++i, ++n)
-                               (*i)->index = n;
-
+                       update_slot_indices();
                        update();
                        return;
                }
 }
 
-Layout::Slot *Layout::create_slot(Widget &wdg)
-{
-       return new Slot(*this, wdg);
-}
-
 void Layout::update_slot_indices()
 {
        n_active_slots = 0;
+       unsigned n_floating = 0;
        for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
        {
-               if((*i)->widget.is_visible())
+               if((*i)->widget.is_visible() || (*i)->ghost)
+               {
                        (*i)->index = n_active_slots++;
+                       if((*i)->floating)
+                               ++n_floating;
+               }
                else
                        (*i)->index = -1;
        }
+
+       n_slack_vars[0] = n_floating*2;
+       n_slack_vars[1] = n_floating*2;
+       for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
+               if((*i)->index>=0)
+               {
+                       if(!(*i)->floating)
+                       {
+                               for(unsigned j=0; j<2; ++j)
+                                       if(((*i)->*(pointers[j].packing)).gravity==0)
+                                               n_slack_vars[j] += 2;
+                       }
+
+                       for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
+                               if(j->target.index>(*i)->index && (j->type&SLACK))
+                                       ++n_slack_vars[j->type&1];
+               }
 }
 
 Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
@@ -210,7 +259,7 @@ Layout::ConstraintType Layout::complement(ConstraintType type)
 void Layout::create_constraint(Widget &src, ConstraintType type, Widget &tgt, int sp)
 {
        if(&src==&tgt)
-               throw invalid_argument("&src==&tgt");
+               throw invalid_argument("Layout::create_constraint");
 
        Slot &src_slot = get_slot_for_widget(src);
        Slot &tgt_slot = get_slot_for_widget(tgt);
@@ -224,6 +273,7 @@ void Layout::create_constraint(Widget &src, ConstraintType type, Widget &tgt, in
        tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
        tgt_slot.constraints.back().spacing = sp;
 
+       update_slot_indices();
        update();
 }
 
@@ -244,6 +294,7 @@ void Layout::set_gravity(Widget &wdg, int h, int v)
        slot.horiz_pack.gravity = h;
        slot.vert_pack.gravity = v;
 
+       update_slot_indices();
        update();
 }
 
@@ -257,6 +308,29 @@ void Layout::set_expand(Widget &wdg, bool h, bool v)
        update();
 }
 
+void Layout::set_ghost(Widget &wdg, bool g)
+{
+       Slot &slot = get_slot_for_widget(wdg);
+
+       slot.ghost = g;
+
+       if(!wdg.is_visible())
+       {
+               update_slot_indices();
+               update();
+       }
+}
+
+void Layout::set_floating(Widget &wdg, bool f)
+{
+       Slot &slot = get_slot_for_widget(wdg);
+
+       slot.floating = f;
+
+       update_slot_indices();
+       update();
+}
+
 void Layout::update()
 {
        solve_constraints(HORIZONTAL, UPDATE);
@@ -266,12 +340,13 @@ void Layout::update()
                (*i)->widget.set_geometry((*i)->geom);
 }
 
-void Layout::autosize()
+void Layout::autosize(Geometry &geom)
 {
        solve_constraints(HORIZONTAL, AUTOSIZE);
        solve_constraints(VERTICAL, AUTOSIZE);
 
-       container->set_size(autosize_geom.w, autosize_geom.h);
+       geom.w = max(geom.w, autosize_geom.w);
+       geom.h = max(geom.h, autosize_geom.h);
 }
 
 void Layout::solve_constraints(int dir, SolveMode mode)
@@ -286,8 +361,9 @@ void Layout::solve_constraints(int dir, SolveMode mode)
        five columns for each widget, and one constant column.  The first and second
        columns of a widget are its position and dimension, respectively.  The
        remaining three are slack columns; see below for their purposes. */
-       LinearProgram linprog(n_active_slots*5+1);
+       LinearProgram linprog(n_active_slots*5+n_slack_vars[dir]+1);
        float weight = slots.size();
+       unsigned k = n_active_slots*5;
        for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
        {
                if((*i)->index<0)
@@ -301,7 +377,8 @@ void Layout::solve_constraints(int dir, SolveMode mode)
                }
                else
                {
-                       objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
+                       if(!(*i)->floating)
+                               objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
                        objective[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
                }
 
@@ -323,14 +400,27 @@ void Layout::solve_constraints(int dir, SolveMode mode)
                        row.back() = geom.*(ptrs.dim)-margin.*(ptrs.high_margin);
                }
 
-               if(((*i)->*(ptrs.packing)).gravity==0)
+               if((*i)->floating || ((*i)->*(ptrs.packing)).gravity==0)
                {
-                       /* This forces the widget's distance from the left and right edge of
-                       the container to be equal.  It's a bit of a hack, but more time and
-                       thought is needed for a better solution. */
+                       /* Try to keep the widget as close to a target position as possible.
+                       Since linear programs can't express absolute values directly, use two
+                       opposing slack variables that are optimized for a low value. */
+                       float a = ((*i)->*(ptrs.packing)).gravity*0.5+0.5;
                        LinearProgram::Row row = linprog.add_row();
-                       row[(*i)->index*5+2] = 1;
-                       row[(*i)->index*5+3] = -1;
+                       row[(*i)->index*5] = 1;
+                       row[(*i)->index*5+1] = a;
+                       row[k] = 1;
+                       row[k+1] = -1;
+                       if((*i)->floating)
+                       {
+                               const Geometry &cgeom = (*i)->widget.get_geometry();
+                               row.back() = cgeom.*(ptrs.pos)+cgeom.*(ptrs.dim)*a;
+                       }
+                       else
+                               row.back() = geom.*(ptrs.dim)/2;
+                       objective[k] = -1;
+                       objective[k+1] = -1;
+                       k += 2;
                }
 
                {
@@ -359,6 +449,8 @@ void Layout::solve_constraints(int dir, SolveMode mode)
                                        row[j->target.index*5+1] = -polarity;
                                if(j->type&SPACING)
                                        row.back() = (j->spacing>=0 ? j->spacing : this->*(ptrs.spacing));
+                               if(j->type&SLACK)
+                                       row[k++] = -1;
                        }
        }
 
@@ -403,27 +495,25 @@ Layout::Packing::Packing():
 Layout::Slot::Slot(Layout &l, Widget &w):
        layout(l),
        index(0),
-       widget(w)
+       widget(w),
+       ghost(false),
+       floating(false)
 {
        vert_pack.gravity = 1;
        widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
        widget.signal_visibility_changed.connect(sigc::mem_fun(this, &Slot::visibility_changed));
-       widget.autosize();
-       autosize_geom = widget.get_geometry();
+       widget.autosize(autosize_geom);
 }
 
 void Layout::Slot::autosize_changed()
 {
-       widget.autosize();
-       autosize_geom = widget.get_geometry();
+       widget.autosize(autosize_geom);
 
-       if(!widget.is_visible())
+       if(!widget.is_visible() && !ghost)
                return;
 
-       // If the widget fits in the area it had, just leave it there.
-       if(autosize_geom.w<=geom.w && autosize_geom.h<=geom.h)
-               widget.set_geometry(geom);
-       else
+       // Only trigger an update if the widget won't fit in its current area.
+       if(autosize_geom.w>geom.w || autosize_geom.h>geom.h)
        {
                layout.container->signal_autosize_changed.emit();
                layout.update();
@@ -433,7 +523,7 @@ void Layout::Slot::autosize_changed()
 void Layout::Slot::visibility_changed(bool v)
 {
        layout.update_slot_indices();
-       if(v)
+       if(v || ghost)
        {
                layout.container->signal_autosize_changed.emit();
                layout.update();
@@ -441,6 +531,116 @@ void Layout::Slot::visibility_changed(bool v)
 }
 
 
+Layout::Loader::Loader(Layout &l, const WidgetMap &wm):
+       DataFile::ObjectLoader<Layout>(l),
+       wdg_map(wm)
+{
+       add("column_spacing", &Loader::column_spacing);
+       add("margin",         &Loader::margin);
+       add("row_spacing",    &Loader::row_spacing);
+       add("spacing",        &Loader::spacing);
+       add("widget",         &Loader::widget);
+}
+
+void Layout::Loader::column_spacing(unsigned s)
+{
+       obj.set_column_spacing(s);
+}
+
+void Layout::Loader::margin()
+{
+       Sides sides;
+       load_sub(sides);
+       obj.set_margin(sides);
+}
+
+void Layout::Loader::spacing(unsigned s)
+{
+       obj.set_spacing(s);
+}
+
+void Layout::Loader::row_spacing(unsigned s)
+{
+       obj.set_row_spacing(s);
+}
+
+void Layout::Loader::widget(const string &n)
+{
+       Widget &wdg = *get_item(wdg_map, n);
+       WidgetLoader ldr(obj, wdg, wdg_map);
+       load_sub_with(ldr);
+}
+
+
+Layout::WidgetLoader::WidgetLoader(Layout &l, Widget &w, const Layout::Loader::WidgetMap &wm):
+       layout(l),
+       widget(w),
+       wdg_map(wm)
+{
+       add("constraint", &WidgetLoader::constraint);
+       add("expand",     &WidgetLoader::expand);
+       add("ghost",      &WidgetLoader::ghost);
+       add("gravity",    &WidgetLoader::gravity);
+}
+
+void Layout::WidgetLoader::constraint(ConstraintType type, const string &n)
+{
+       Widget &target = *get_item(wdg_map, n);
+       layout.add_constraint(widget, type, target);
+}
+
+void Layout::WidgetLoader::expand(bool h, bool v)
+{
+       layout.set_expand(widget, h, v);
+}
+
+void Layout::WidgetLoader::ghost(bool g)
+{
+       layout.set_ghost(widget, g);
+}
+
+void Layout::WidgetLoader::gravity(int h, int v)
+{
+       layout.set_gravity(widget, h, v);
+}
+
+
+void operator>>(const LexicalConverter &conv, Layout::ConstraintType &ctype)
+{
+       const string &str = conv.get();
+       if(str=="ABOVE")
+               ctype = Layout::ABOVE;
+       else if(str=="BELOW")
+               ctype = Layout::BELOW;
+       else if(str=="RIGHT_OF")
+               ctype = Layout::RIGHT_OF;
+       else if(str=="LEFT_OF")
+               ctype = Layout::LEFT_OF;
+       else if(str=="FAR_ABOVE")
+               ctype = Layout::FAR_ABOVE;
+       else if(str=="FAR_BELOW")
+               ctype = Layout::FAR_BELOW;
+       else if(str=="FAR_RIGHT_OF")
+               ctype = Layout::FAR_RIGHT_OF;
+       else if(str=="FAR_LEFT_OF")
+               ctype = Layout::FAR_LEFT_OF;
+       else if(str=="ALIGN_TOP")
+               ctype = Layout::ALIGN_TOP;
+       else if(str=="ALIGN_BOTTOM")
+               ctype = Layout::ALIGN_BOTTOM;
+       else if(str=="ALIGN_RIGHT")
+               ctype = Layout::ALIGN_RIGHT;
+       else if(str=="ALIGN_LEFT")
+               ctype = Layout::ALIGN_LEFT;
+       else if(str=="COPY_WIDTH")
+               ctype = Layout::COPY_WIDTH;
+       else if(str=="COPY_HEIGHT")
+               ctype = Layout::COPY_HEIGHT;
+       else
+               throw lexical_error(format("conversion of '%s' to ConstraintType", str));
+}
+
+
 Layout::LinearProgram::LinearProgram(unsigned s):
        n_columns(s),
        n_rows(1),