3 #include <msp/core/maputils.h>
4 #include <msp/strings/format.h>
5 #include "arrangement.h"
15 class Layout::LinearProgram
21 LinearProgram &linprog;
25 Row(LinearProgram &, unsigned);
27 float &operator[](unsigned);
35 std::vector<float> values;
42 std::vector<Column> columns;
47 LinearProgram(unsigned);
50 Row operator[](unsigned);
51 Row get_objective_row();
52 float get_variable(unsigned);
55 void prepare_columns();
56 void add_artificial_variables();
57 void remove_artificial_variables();
58 unsigned find_minimal_ratio(unsigned);
59 void make_basic_column(unsigned, unsigned);
64 struct Layout::Pointers
67 unsigned Geometry::*dim;
68 Packing Slot::*packing;
69 unsigned Sides::*low_margin;
70 unsigned Sides::*high_margin;
71 unsigned Layout::*spacing;
74 Layout::Pointers Layout::pointers[2] =
76 &Geometry::x, &Geometry::w,
78 &Sides::left, &Sides::right,
81 &Geometry::y, &Geometry::h,
83 &Sides::bottom, &Sides::top,
95 n_slack_constraints[0] = 0;
96 n_slack_constraints[1] = 0;
101 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
105 void Layout::set_container(Container &c)
108 throw logic_error("container!=0");
113 void Layout::set_margin(const Sides &m)
120 void Layout::set_spacing(unsigned s)
128 void Layout::set_row_spacing(unsigned s)
135 void Layout::set_column_spacing(unsigned s)
142 void Layout::push_arrangement(Arrangement &arr)
144 arrangement_stack.push_back(&arr);
147 Arrangement *Layout::get_arrangement() const
149 if(arrangement_stack.empty())
152 return arrangement_stack.back();
155 void Layout::pop_arrangement(Arrangement &arr)
157 list<Arrangement *>::iterator begin = find(arrangement_stack.begin(), arrangement_stack.end(), &arr);
158 if(begin==arrangement_stack.end())
163 Arrangement *top = arrangement_stack.back();
164 arrangement_stack.pop_back();
165 if(!arrangement_stack.empty())
166 arrangement_stack.back()->arrange(*top);
172 void Layout::add_widget(Widget &wdg)
175 throw logic_error("!container");
177 slots.push_back(new Slot(*this, wdg));
178 update_slot_indices();
179 if(!arrangement_stack.empty())
180 arrangement_stack.back()->arrange(wdg);
185 void Layout::remove_widget(Widget &wdg)
187 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
188 if(&(*i)->widget==&wdg)
190 for(list<Slot *>::iterator j=slots.begin(); j!=slots.end(); ++j)
193 for(list<Constraint>::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); )
196 (*j)->constraints.erase(k++);
205 update_slot_indices();
211 void Layout::update_slot_indices()
214 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
216 if((*i)->widget.is_visible())
217 (*i)->index = n_active_slots++;
222 n_slack_constraints[0] = 0;
223 n_slack_constraints[1] = 0;
224 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
227 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
228 if(j->target.index>(*i)->index && (j->type&SLACK))
229 ++n_slack_constraints[j->type&1];
233 Layout::Slot &Layout::get_slot_for_widget(Widget &wdg)
235 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
236 if(&(*i)->widget==&wdg)
239 throw hierarchy_error("widget not in layout");
242 Layout::ConstraintType Layout::complement(ConstraintType type)
244 return static_cast<ConstraintType>((type&~(SELF_MASK|TARGET_MASK)) | ((type&SELF_MASK)<<2) | ((type&TARGET_MASK)>>2));
247 void Layout::create_constraint(Widget &src, ConstraintType type, Widget &tgt, int sp)
250 throw invalid_argument("&src==&tgt");
252 Slot &src_slot = get_slot_for_widget(src);
253 Slot &tgt_slot = get_slot_for_widget(tgt);
255 for(list<Constraint>::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i)
256 if(i->type==type && &i->target==&tgt_slot)
259 src_slot.constraints.push_back(Constraint(type, tgt_slot));
260 src_slot.constraints.back().spacing = sp;
261 tgt_slot.constraints.push_back(Constraint(complement(type), src_slot));
262 tgt_slot.constraints.back().spacing = sp;
264 update_slot_indices();
268 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt)
270 create_constraint(src, type, tgt, -1);
273 void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt, unsigned spacing)
275 create_constraint(src, type, tgt, spacing);
278 void Layout::set_gravity(Widget &wdg, int h, int v)
280 Slot &slot = get_slot_for_widget(wdg);
282 slot.horiz_pack.gravity = h;
283 slot.vert_pack.gravity = v;
288 void Layout::set_expand(Widget &wdg, bool h, bool v)
290 Slot &slot = get_slot_for_widget(wdg);
292 slot.horiz_pack.expand = h;
293 slot.vert_pack.expand = v;
298 void Layout::update()
300 solve_constraints(HORIZONTAL, UPDATE);
301 solve_constraints(VERTICAL, UPDATE);
303 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
304 (*i)->widget.set_geometry((*i)->geom);
307 void Layout::autosize()
309 solve_constraints(HORIZONTAL, AUTOSIZE);
310 solve_constraints(VERTICAL, AUTOSIZE);
312 container->set_size(autosize_geom.w, autosize_geom.h);
315 void Layout::solve_constraints(int dir, SolveMode mode)
317 Pointers &ptrs = pointers[dir&VERTICAL];
319 const Geometry &geom = container->get_geometry();
320 if(mode==UPDATE && geom.*(ptrs.dim)<margin.*(ptrs.low_margin)+margin.*(ptrs.high_margin))
323 /* Set up a linear program to solve the constraints. The program matrix has
324 five columns for each widget, and one constant column. The first and second
325 columns of a widget are its position and dimension, respectively. The
326 remaining three are slack columns; see below for their purposes. */
327 LinearProgram linprog(n_active_slots*5+n_slack_constraints[dir]+1);
328 float weight = slots.size();
329 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
334 LinearProgram::Row objective = linprog.get_objective_row();
337 objective[(*i)->index*5] = -1;
338 objective[(*i)->index*5+1] = -1;
342 objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
343 objective[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
347 // Prevent the widget from going past the container's low edge.
348 LinearProgram::Row row = linprog.add_row();
349 row[(*i)->index*5] = 1;
350 row[(*i)->index*5+2] = -1;
351 row.back() = margin.*(ptrs.low_margin);
356 // Prevent the widget from going past the container's high edge.
357 LinearProgram::Row row = linprog.add_row();
358 row[(*i)->index*5] = 1;
359 row[(*i)->index*5+1] = 1;
360 row[(*i)->index*5+3] = 1;
361 row.back() = geom.*(ptrs.dim)-margin.*(ptrs.high_margin);
364 if(((*i)->*(ptrs.packing)).gravity==0)
366 /* This forces the widget's distance from the left and right edge of
367 the container to be equal. It's a bit of a hack, but more time and
368 thought is needed for a better solution. */
369 LinearProgram::Row row = linprog.add_row();
370 row[(*i)->index*5+2] = 1;
371 row[(*i)->index*5+3] = -1;
375 /* Don't allow the widget's dimension to get below that determined
377 LinearProgram::Row row = linprog.add_row();
378 row[(*i)->index*5+1] = 1;
379 row[(*i)->index*5+4] = -1;
380 row.back() = (*i)->autosize_geom.*(ptrs.dim);
383 /* Add rows for user-defined constraints. Constraints are always added
384 in pairs, so it's only necessary to create a row for one half. */
385 unsigned k = n_active_slots*5;
386 for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
387 if(j->target.index>(*i)->index && (j->type&1)==dir)
389 LinearProgram::Row row = linprog.add_row();
390 float polarity = ((j->type&SELF_DIM) ? -1 : 1);
392 row[(*i)->index*5] = polarity;
394 row[(*i)->index*5+1] = polarity;
395 if(j->type&TARGET_POS)
396 row[j->target.index*5] = -polarity;
397 if(j->type&TARGET_DIM)
398 row[j->target.index*5+1] = -polarity;
400 row.back() = (j->spacing>=0 ? j->spacing : this->*(ptrs.spacing));
411 autosize_geom.*(ptrs.dim) = 0;
412 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
415 int high_edge = linprog.get_variable((*i)->index*5)+linprog.get_variable((*i)->index*5+1);
416 autosize_geom.*(ptrs.dim) = max(autosize_geom.*(ptrs.dim), high_edge+margin.*(ptrs.high_margin));
421 for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
424 (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5);
425 (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1);
431 Layout::Constraint::Constraint(ConstraintType t, Slot &s):
438 Layout::Packing::Packing():
444 Layout::Slot::Slot(Layout &l, Widget &w):
449 vert_pack.gravity = 1;
450 widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
451 widget.signal_visibility_changed.connect(sigc::mem_fun(this, &Slot::visibility_changed));
453 autosize_geom = widget.get_geometry();
456 void Layout::Slot::autosize_changed()
459 autosize_geom = widget.get_geometry();
461 if(!widget.is_visible())
464 // If the widget fits in the area it had, just leave it there.
465 if(autosize_geom.w<=geom.w && autosize_geom.h<=geom.h)
466 widget.set_geometry(geom);
469 layout.container->signal_autosize_changed.emit();
474 void Layout::Slot::visibility_changed(bool v)
476 layout.update_slot_indices();
479 layout.container->signal_autosize_changed.emit();
485 Layout::Loader::Loader(Layout &l, const WidgetMap &wm):
486 DataFile::ObjectLoader<Layout>(l),
489 add("column_spacing", &Loader::column_spacing);
490 add("margin", &Loader::margin);
491 add("row_spacing", &Loader::row_spacing);
492 add("spacing", &Loader::spacing);
493 add("widget", &Loader::widget);
496 void Layout::Loader::column_spacing(unsigned s)
498 obj.set_column_spacing(s);
501 void Layout::Loader::margin()
505 obj.set_margin(sides);
508 void Layout::Loader::spacing(unsigned s)
513 void Layout::Loader::row_spacing(unsigned s)
515 obj.set_row_spacing(s);
518 void Layout::Loader::widget(const string &n)
520 Widget &wdg = *get_item(wdg_map, n);
521 WidgetLoader ldr(obj, wdg, wdg_map);
526 Layout::WidgetLoader::WidgetLoader(Layout &l, Widget &w, const Layout::Loader::WidgetMap &wm):
531 add("constraint", &WidgetLoader::constraint);
532 add("expand", &WidgetLoader::expand);
533 add("gravity", &WidgetLoader::gravity);
536 void Layout::WidgetLoader::constraint(ConstraintType type, const string &n)
538 Widget &target = *get_item(wdg_map, n);
539 layout.add_constraint(widget, type, target);
542 void Layout::WidgetLoader::expand(bool h, bool v)
544 layout.set_expand(widget, h, v);
547 void Layout::WidgetLoader::gravity(int h, int v)
549 layout.set_gravity(widget, h, v);
553 void operator>>(const LexicalConverter &conv, Layout::ConstraintType &ctype)
555 const string &str = conv.get();
557 ctype = Layout::ABOVE;
558 else if(str=="BELOW")
559 ctype = Layout::BELOW;
560 else if(str=="RIGHT_OF")
561 ctype = Layout::RIGHT_OF;
562 else if(str=="LEFT_OF")
563 ctype = Layout::LEFT_OF;
564 else if(str=="FAR_ABOVE")
565 ctype = Layout::FAR_ABOVE;
566 else if(str=="FAR_BELOW")
567 ctype = Layout::FAR_BELOW;
568 else if(str=="FAR_RIGHT_OF")
569 ctype = Layout::FAR_RIGHT_OF;
570 else if(str=="FAR_LEFT_OF")
571 ctype = Layout::FAR_LEFT_OF;
572 else if(str=="ALIGN_TOP")
573 ctype = Layout::ALIGN_TOP;
574 else if(str=="ALIGN_BOTTOM")
575 ctype = Layout::ALIGN_BOTTOM;
576 else if(str=="ALIGN_RIGHT")
577 ctype = Layout::ALIGN_RIGHT;
578 else if(str=="ALIGN_LEFT")
579 ctype = Layout::ALIGN_LEFT;
580 else if(str=="COPY_WIDTH")
581 ctype = Layout::COPY_WIDTH;
582 else if(str=="COPY_HEIGHT")
583 ctype = Layout::COPY_HEIGHT;
585 throw lexical_error(format("conversion of '%s' to ConstraintType", str));
589 Layout::LinearProgram::LinearProgram(unsigned s):
597 Layout::LinearProgram::Row Layout::LinearProgram::add_row()
599 return Row(*this, n_rows++);
602 Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r)
605 throw out_of_range("LinearProgram::operator[]");
607 return Row(*this, r);
610 Layout::LinearProgram::Row Layout::LinearProgram::get_objective_row()
612 return Row(*this, 0);
615 float Layout::LinearProgram::get_variable(unsigned i)
617 if(!solved || infeasible)
618 throw logic_error("not solved");
620 throw out_of_range("LinearProgram::get_variable");
622 if(unsigned r = columns[i].basic)
623 return columns.back().values[r];
628 bool Layout::LinearProgram::solve()
630 if(solved || infeasible)
633 /* Solve the program using the simplex method. The column representing the
634 objective variable is kept implicit, as it would never change during the
635 execution of the algorithm. */
639 add_artificial_variables();
641 // Solve the phase 1 problem.
644 /* All artificial variables should now be non-basic and thus zero, so the
645 objective function's value should also be zero. If it isn't, the original
646 program can't be solved. */
647 if(columns.back().values.front())
653 remove_artificial_variables();
655 // Solve the phase 2 problem. We already know it to be feasible.
663 void Layout::LinearProgram::prepare_columns()
665 /* See if any variables are already basic. A basic variable must only have
666 a nonzero coefficient on one row, and its product with the constant column
667 must not be negative. Only one variable can be basic for any given row. */
668 vector<float> obj_coeff(n_rows, 0.0f);
669 vector<float> row_coeff(n_rows, 1.0f);
670 const vector<float> &constants = columns.back().values;
671 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
673 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)
676 for(unsigned j=1; (basic && j+1<i->values.size()); ++j)
677 basic = (i->values[j]==0.0f);
680 i->basic = i->values.size()-1;
681 row_coeff[i->basic] = 1.0f/i->values.back();
682 obj_coeff[i->basic] = -i->values.front();
688 // Price out the newly-created basic variables.
689 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
690 if(!i->values.empty())
692 for(unsigned j=0; j<i->values.size(); ++j)
694 i->values[j] *= row_coeff[j];
695 i->values.front() += obj_coeff[j]*i->values[j];
700 void Layout::LinearProgram::add_artificial_variables()
702 vector<unsigned> artificial_rows(n_rows-1);
703 for(unsigned i=0; i<artificial_rows.size(); ++i)
704 artificial_rows[i] = i+1;
706 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
708 artificial_rows[i->basic-1] = 0;
709 artificial_rows.erase(std::remove(artificial_rows.begin(), artificial_rows.end(), 0), artificial_rows.end());
711 /* Force all non-basic columns fully into existence and relocate objective
712 row to bottom in preparation of phase 1. A new objective row is calculated
713 by pricing out the constraint rows. */
714 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
717 float objective = 0.0f;
718 if(!i->values.empty())
720 objective = i->values.front();
721 i->values.front() = 0.0f;
722 for(vector<unsigned>::iterator j=artificial_rows.begin(); j!=artificial_rows.end(); ++j)
723 if(*j<i->values.size())
724 i->values.front() += i->values[*j];
726 i->values.resize(n_rows+1, 0.0f);
727 i->values.back() = objective;
730 if(artificial_rows.empty())
733 /* Create artificial variables for phase 1. This ensures that each row has
734 a basic variable associated with it. The original objective row already
735 contains the implicit objective variable, which is basic. */
736 columns.resize(n_columns+artificial_rows.size());
737 columns.back() = columns[n_columns-1];
738 columns[n_columns-1].values.clear();
739 for(unsigned i=0; i<artificial_rows.size(); ++i)
740 columns[n_columns+i-1].basic = artificial_rows[i];
743 void Layout::LinearProgram::remove_artificial_variables()
745 /* See if any artificial variables are still basic. This could be because
746 the program is degenerate. To avoid trouble later on, use pivots to make
747 some of the original variables basic instead.
749 I don't fully understand why this is needed, but it appears to work. */
750 for(unsigned i=n_columns-1; i+1<columns.size(); ++i)
751 if(columns[i].basic && columns.back().values[columns[i].basic]==0.0f)
753 for(unsigned j=0; j+1<n_columns; ++j)
754 if(!columns[j].basic && columns[j].values[columns[i].basic]!=0.0f)
756 make_basic_column(j, columns[i].basic);
761 /* Get rid of the artificial variables and restore the original objective
762 row to form the phase 2 problem. */
763 columns.erase(columns.begin()+(n_columns-1), columns.end()-1);
764 for(vector<Column>::iterator i=columns.begin(); i!=columns.end(); ++i)
767 i->values.front() = i->values.back();
768 i->values.pop_back();
772 unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c)
774 /* Pick the row with the minimum ratio between the constant column and the
775 pivot column. This ensures that when the pivot column is made basic, values
776 in the constant column stay positive.
778 The use of n_rows instead of the true size of the column is intentional,
779 since the relocated objective row must be ignored in phase 1. */
780 float best = numeric_limits<float>::infinity();
782 for(unsigned i=1; i<n_rows; ++i)
783 if(columns[c].values[i]>0)
785 float ratio = columns.back().values[i]/columns[c].values[i];
796 void Layout::LinearProgram::make_basic_column(unsigned c, unsigned r)
798 /* Perform row transfer operations to make the pivot column basic,
799 containing a 1 on the pivot row. */
800 for(unsigned i=0; i<columns.size(); ++i)
801 if(i!=c && (columns[i].basic==r || (!columns[i].basic && columns[i].values[r])))
805 columns[i].values.resize(columns.back().values.size(), 0.0f);
806 columns[i].values[columns[i].basic] = 1.0f;
807 columns[i].basic = 0;
810 float scale = columns[i].values[r]/columns[c].values[r];
812 columns[i].values[r] = scale;
813 for(unsigned j=0; j<columns[i].values.size(); ++j)
815 columns[i].values[j] -= scale*columns[c].values[j];
818 columns[c].basic = r;
819 columns[c].values.clear();
822 bool Layout::LinearProgram::pivot()
824 /* Pick a nonbasic column and make it basic. Requiring a positive objective
825 coefficient ensures that the objective function's value will decrease in the
827 for(unsigned i=0; i+1<columns.size(); ++i)
828 if(!columns[i].basic && columns[i].values.front()>0)
829 if(unsigned row = find_minimal_ratio(i))
831 make_basic_column(i, row);
839 Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i):
844 float &Layout::LinearProgram::Row::operator[](unsigned c)
846 if(c>=linprog.n_columns)
847 throw out_of_range("Row::operator[]");
849 Column &column = linprog.columns[c];
850 if(column.values.size()<=index)
851 column.values.resize(index+1);
853 return column.values[index];
856 float &Layout::LinearProgram::Row::back()
858 return (*this)[linprog.n_columns-1];
862 Layout::LinearProgram::Column::Column():