From 10f1616c28cace97512aa988add034ca066aa0f1 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 23 Jan 2018 17:25:53 +0100 Subject: [PATCH] base: add util/list_model.h The new 'List_model' utility helps with the implementation of component-internal data models created and updated from XML. Fixes #2653 --- repos/base/include/util/list_model.h | 220 ++++++++++++++++++ .../src/app/menu_view/box_layout_widget.h | 34 +-- repos/gems/src/app/menu_view/button_widget.h | 20 +- .../gems/src/app/menu_view/depgraph_widget.h | 42 ++-- repos/gems/src/app/menu_view/float_widget.h | 20 +- repos/gems/src/app/menu_view/frame_widget.h | 21 +- .../src/app/menu_view/list_model_from_xml.h | 138 ----------- repos/gems/src/app/menu_view/root_widget.h | 28 ++- repos/gems/src/app/menu_view/widget.h | 35 ++- 9 files changed, 322 insertions(+), 236 deletions(-) create mode 100644 repos/base/include/util/list_model.h delete mode 100644 repos/gems/src/app/menu_view/list_model_from_xml.h diff --git a/repos/base/include/util/list_model.h b/repos/base/include/util/list_model.h new file mode 100644 index 0000000000..d151c43bab --- /dev/null +++ b/repos/base/include/util/list_model.h @@ -0,0 +1,220 @@ +/* + * \brief List-based data model created and updated from XML + * \author Norman Feske + * \date 2017-08-09 + * + * The 'List_model' stores a component-internal representation of XML-node + * content. The XML information is imported according to an 'Update_policy', + * which specifies how the elements of the data model are created, destroyed, + * and updated. The elements are ordered according to the order of XML nodes. + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__UTIL__LIST_MODEL_H_ +#define _INCLUDE__UTIL__LIST_MODEL_H_ + +#include + +namespace Genode { template class List_model; } + + +template +class Genode::List_model +{ + private: + + List _elements { }; + + public: + + struct Update_policy; + + class Element : private List::Element + { + private: + + /* used by 'List_model::update_from_xml' only */ + friend class List_model; + friend class List; + + ELEM *_next() const { return List::Element::next(); } + + public: + + /** + * Return the element's neighbor if present, otherwise nullptr + */ + ELEM const *next() const { return List::Element::next(); } + }; + + struct Unknown_element_type : Exception { }; + + ~List_model() + { + if (_elements.first()) + warning("list model not empty at destruction time"); + } + + /** + * Update data model according to XML structure 'node' + * + * \throw Unknown_element_type + */ + template + inline void update_from_xml(POLICY &policy, Xml_node node); + + /** + * Call functor 'fn' for each const element + */ + template + void for_each(FN const &fn) const + { + for (Element const *e = _elements.first(); e; e = e->next()) + fn(static_cast(*e)); + } + + /** + * Call functor 'fn' for each non-const element + */ + template + void for_each(FN const &fn) + { + Element *next = nullptr; + for (Element *e = _elements.first(); e; e = next) { + next = e->_next(); + fn(static_cast(*e)); + } + } + + /** + * Remove all elements from the data model + * + * This method should be called at the destruction time of the + * 'List_model'. + * + * List-model elements are not implicitly destroyed by the destructor + * because the 'policy' needed to destruct elements is not kept as + * member of the list model (multiple policies may applied to the same + * list model). + */ + template + void destroy_all_elements(POLICY &policy) + { + Element *next = nullptr; + for (Element *e = _elements.first(); e; e = next) { + next = e->_next(); + ELEM &elem = static_cast(*e); + _elements.remove(&elem); + policy.destroy_element(elem); + } + } +}; + + +template +template +void Genode::List_model::update_from_xml(POLICY &policy, Xml_node node) +{ + typedef typename POLICY::Element Element; + + List updated_list; + + Element *last_updated = nullptr; /* used for appending to 'updated_list' */ + + node.for_each_sub_node([&] (Xml_node sub_node) { + + /* skip XML nodes that are unrelated to the data model */ + if (!policy.node_is_element(sub_node)) + return; + + /* look up corresponding element in original list */ + Element *curr = _elements.first(); + while (curr && !policy.element_matches_xml_node(*curr, sub_node)) + curr = curr->_next(); + + /* consume existing element or create new one */ + if (curr) { + _elements.remove(curr); + } else { + /* \throw Unknown_element_type */ + curr = &policy.create_element(sub_node); + } + + /* append current element to 'updated_list' */ + updated_list.insert(curr, last_updated); + last_updated = curr; + + policy.update_element(*curr, sub_node); + }); + + /* remove stale elements */ + Element *next = nullptr; + for (Element *e = _elements.first(); e; e = next) { + next = e->_next(); + policy.destroy_element(*e); + } + + /* use 'updated_list' list new data model */ + _elements = updated_list; +} + + +/** + * Policy interface to be supplied to 'List_model::update_from_xml' + * + * \param ELEM element type, must be a list element + * + * This class template is merely a blue print of a policy to document the + * interface. + */ +template +struct Genode::List_model::Update_policy +{ + typedef List_model::Unknown_element_type Unknown_element_type; + + /* + * Type that needs to be supplied by the policy implementation + */ + typedef ELEM Element; + + /** + * Destroy element + * + * When this function is called, the element is no longer contained + * in the model's list. + */ + void destroy_element(ELEM &elem); + + /** + * Create element of the type given in the 'elem_node' + * + * \throw List_model::Unknown_element_type + */ + ELEM &create_element(Xml_node elem_node); + + /** + * Import element properties from XML node + */ + void update_element(ELEM &elem, Xml_node elem_node); + + /** + * Return true if element corresponds to XML node + */ + static bool element_matches_xml_node(Element const &, Xml_node); + + /** + * Return true if XML node should be imported + * + * This method allows the policy to disregard certain XML node types from + * building the data model. + */ + static bool node_is_element(Xml_node node) { return true; } +}; + +#endif /* _INCLUDE__UTIL__LIST_MODEL_H_ */ diff --git a/repos/gems/src/app/menu_view/box_layout_widget.h b/repos/gems/src/app/menu_view/box_layout_widget.h index aaf8655524..cf22f26ed9 100644 --- a/repos/gems/src/app/menu_view/box_layout_widget.h +++ b/repos/gems/src/app/menu_view/box_layout_widget.h @@ -44,31 +44,31 @@ struct Menu_view::Box_layout_widget : Widget /* determine largest size among our children */ unsigned largest_size = 0; - for (Widget *w = _children.first(); w; w = w->next()) + _children.for_each([&] (Widget const &w) { largest_size = - max(largest_size, _direction == VERTICAL ? w->min_size().w() - : w->min_size().h()); + max(largest_size, _direction == VERTICAL ? w.min_size().w() + : w.min_size().h()); }); /* position children on one row/column */ Point position(0, 0); - for (Widget *w = _children.first(); w; w = w->next()) { + _children.for_each([&] (Widget &w) { - Area const child_min_size = w->min_size(); + Area const child_min_size = w.min_size(); if (_direction == VERTICAL) { - w->geometry(Rect(position, Area(largest_size, child_min_size.h()))); - unsigned const next_top_margin = w->next() ? w->next()->margin.top : 0; - unsigned const dy = child_min_size.h() - min(w->margin.bottom, next_top_margin); + w.geometry(Rect(position, Area(largest_size, child_min_size.h()))); + unsigned const next_top_margin = w.next() ? w.next()->margin.top : 0; + unsigned const dy = child_min_size.h() - min(w.margin.bottom, next_top_margin); position = position + Point(0, dy); } else { - w->geometry(Rect(position, Area(child_min_size.w(), largest_size))); - unsigned const next_left_margin = w->next() ? w->next()->margin.left : 0; - unsigned const dx = child_min_size.w() - min(w->margin.right, next_left_margin); + w.geometry(Rect(position, Area(child_min_size.w(), largest_size))); + unsigned const next_left_margin = w.next() ? w.next()->margin.left : 0; + unsigned const dx = child_min_size.w() - min(w.margin.right, next_left_margin); position = position + Point(dx, 0); } - _min_size = Area(w->geometry().x2() + 1, w->geometry().y2() + 1); - } + _min_size = Area(w.geometry().x2() + 1, w.geometry().y2() + 1); + }); } Area min_size() const override @@ -85,12 +85,12 @@ struct Menu_view::Box_layout_widget : Widget void _layout() override { - for (Widget *w = _children.first(); w; w = w->next()) { + _children.for_each([&] (Widget &w) { if (_direction == VERTICAL) - w->size(Area(geometry().w(), w->min_size().h())); + w.size(Area(geometry().w(), w.min_size().h())); else - w->size(Area(w->min_size().w(), geometry().h())); - } + w.size(Area(w.min_size().w(), geometry().h())); + }); } }; diff --git a/repos/gems/src/app/menu_view/button_widget.h b/repos/gems/src/app/menu_view/button_widget.h index 25ed7edfd2..8d61ffca57 100644 --- a/repos/gems/src/app/menu_view/button_widget.h +++ b/repos/gems/src/app/menu_view/button_widget.h @@ -85,17 +85,19 @@ struct Menu_view::Button_widget : Widget, Animator::Item bool const dy = selected ? 1 : 0; - if (Widget *child = _children.first()) - child->geometry(Rect(Point(margin.left + padding.left, - margin.top + padding.top + dy), - child->min_size())); + _children.for_each([&] (Widget &child) { + child.geometry(Rect(Point(margin.left + padding.left, + margin.top + padding.top + dy), + child.min_size())); + }); } Area min_size() const override { /* determine minimum child size */ - Widget const * const child = _children.first(); - Area const child_min_size = child ? child->min_size() : Area(300, 10); + Area child_min_size(300, 10); + _children.for_each([&] (Widget const &child) { + child_min_size = child.min_size(); }); /* don't get smaller than the background texture */ Area const texture_size = default_texture->size(); @@ -140,9 +142,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item void _layout() override { - for (Widget *w = _children.first(); w; w = w->next()) - w->size(Area(geometry().w() - _space().w(), - geometry().h() - _space().h())); + _children.for_each([&] (Widget &w) { + w.size(Area(geometry().w() - _space().w(), + geometry().h() - _space().h())); }); } diff --git a/repos/gems/src/app/menu_view/depgraph_widget.h b/repos/gems/src/app/menu_view/depgraph_widget.h index f65f4be22e..6be764ae4f 100644 --- a/repos/gems/src/app/menu_view/depgraph_widget.h +++ b/repos/gems/src/app/menu_view/depgraph_widget.h @@ -377,7 +377,7 @@ struct Menu_view::Depgraph_widget : Widget * Customized model-update policy that augments the list of child widgets * with their graph-node topology */ - struct Model_update_policy : List_model_update_policy + struct Model_update_policy : List_model::Update_policy { Widget::Model_update_policy &_generic_model_update_policy; Allocator &_alloc; @@ -440,10 +440,7 @@ struct Menu_view::Depgraph_widget : Widget ~Depgraph_widget() { - while (Widget *w = _children.first()) { - _children.remove(w); - _model_update_policy.destroy_element(*w); - } + _children.destroy_all_elements(_model_update_policy); } void update(Xml_node node) override @@ -459,7 +456,7 @@ struct Menu_view::Depgraph_widget : Widget if (dir_name == "west") _depth_direction = { Depth_direction::WEST }; } - update_list_model_from_xml(_model_update_policy, _children, node); + _children.update_from_xml(_model_update_policy, node); /* * Import dependencies @@ -511,9 +508,9 @@ struct Menu_view::Depgraph_widget : Widget * * The computation depends on the order of '_children'. */ - for (Widget *w = _children.first(); w; w = w->next()) { + _children.for_each([&] (Widget &w) { _nodes.for_each([&] (Registered_node &node) { - if (!node.belongs_to(*w)) + if (!node.belongs_to(w)) return; apply_to_primary_dependency(node, [&] (Node &parent) { @@ -525,16 +522,15 @@ struct Menu_view::Depgraph_widget : Widget node.breadth_size(_depth_direction); }); }); - } + }); /* * Apply layout to the children, determine _min_size */ Rect bounding_box(Point(0, 0), Area(0, 0)); - for (Widget *w = _children.first(); w; w = w->next()) { - + _children.for_each([&] (Widget &w) { _nodes.for_each([&] (Registered_node &node) { - if (!node.belongs_to(*w)) + if (!node.belongs_to(w)) return; int const depth_pos = node.depth_pos(_depth_direction), @@ -548,11 +544,11 @@ struct Menu_view::Depgraph_widget : Widget : Rect(Point(breadth_pos, depth_pos), Area(breadth_size, depth_size)); - w->geometry(Rect(node_rect.center(w->min_size()), w->min_size())); + w.geometry(Rect(node_rect.center(w.min_size()), w.min_size())); - bounding_box = Rect::compound(bounding_box, w->geometry()); + bounding_box = Rect::compound(bounding_box, w.geometry()); }); - } + }); /* * Mirror coordinates if graph grows towards north or west @@ -560,18 +556,18 @@ struct Menu_view::Depgraph_widget : Widget if (_depth_direction.value == Depth_direction::NORTH || _depth_direction.value == Depth_direction::WEST) { - for (Widget *w = _children.first(); w; w = w->next()) { + _children.for_each([&] (Widget &w) { - int x = w->geometry().x1(), y = w->geometry().y1(); + int x = w.geometry().x1(), y = w.geometry().y1(); if (_depth_direction.value == Depth_direction::NORTH) - y = (int)bounding_box.h() - y - w->geometry().h(); + y = (int)bounding_box.h() - y - w.geometry().h(); if (_depth_direction.value == Depth_direction::WEST) - x = (int)bounding_box.w() - x - w->geometry().w(); + x = (int)bounding_box.w() - x - w.geometry().w(); - w->geometry(Rect(Point(x, y), w->geometry().area())); - } + w.geometry(Rect(Point(x, y), w.geometry().area())); + }); } _min_size = bounding_box.area(); } @@ -657,8 +653,8 @@ struct Menu_view::Depgraph_widget : Widget void _layout() override { - for (Widget *w = _children.first(); w; w = w->next()) - w->size(w->geometry().area()); + _children.for_each([&] (Widget &w) { + w.size(w.geometry().area()); }); } }; diff --git a/repos/gems/src/app/menu_view/float_widget.h b/repos/gems/src/app/menu_view/float_widget.h index 15c18986d7..80cf1bec52 100644 --- a/repos/gems/src/app/menu_view/float_widget.h +++ b/repos/gems/src/app/menu_view/float_widget.h @@ -55,15 +55,19 @@ struct Menu_view::Float_widget : Widget _east = node.attribute_value("east", false), _west = node.attribute_value("west", false); - if (Widget *child = _children.first()) - _place_child(*child); + _children.for_each([&] (Widget &child) { + _place_child(child); }); } Area min_size() const override { + Area result(0, 0); + /* determine minimum child size */ - Widget const * const child = _children.first(); - return child ? child->min_size() : Area(0, 0); + _children.for_each([&] (Widget const &child) { + result = child.min_size(); }); + + return result; } void draw(Surface &pixel_surface, @@ -75,10 +79,10 @@ struct Menu_view::Float_widget : Widget void _layout() override { - if (Widget *child = _children.first()) { - _place_child(*child); - child->size(child->geometry().area()); - } + _children.for_each([&] (Widget &child) { + _place_child(child); + child.size(child.geometry().area()); + }); } }; diff --git a/repos/gems/src/app/menu_view/frame_widget.h b/repos/gems/src/app/menu_view/frame_widget.h index 2f4419fd6a..2ee51e00b5 100644 --- a/repos/gems/src/app/menu_view/frame_widget.h +++ b/repos/gems/src/app/menu_view/frame_widget.h @@ -47,17 +47,18 @@ struct Menu_view::Frame_widget : Widget /* * layout */ - if (Widget *child = _children.first()) - child->geometry(Rect(Point(margin.left + padding.left, - margin.top + padding.top), - child->min_size())); + _children.for_each([&] (Widget &child) { + child.geometry(Rect(Point(margin.left + padding.left, + margin.top + padding.top), + child.min_size())); }); } Area min_size() const override { - /* determine minimum child size */ - Widget const * const child = _children.first(); - Area const child_min_size = child ? child->min_size() : Area(0, 0); + /* determine minimum child size (there is only one child) */ + Area child_min_size(0, 0); + _children.for_each([&] (Widget const &child) { + child_min_size = child.min_size(); }); /* don't get smaller than the background texture */ Area const texture_size = texture ? texture->size() : Area(0, 0); @@ -81,9 +82,9 @@ struct Menu_view::Frame_widget : Widget void _layout() override { - if (Widget *child = _children.first()) - child->size(Area(geometry().w() - _space().w(), - geometry().h() - _space().h())); + _children.for_each([&] (Widget &child) { + child.size(Area(geometry().w() - _space().w(), + geometry().h() - _space().h())); }); } }; diff --git a/repos/gems/src/app/menu_view/list_model_from_xml.h b/repos/gems/src/app/menu_view/list_model_from_xml.h deleted file mode 100644 index e12dc75417..0000000000 --- a/repos/gems/src/app/menu_view/list_model_from_xml.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * \brief Utility for updating an internal data model from an XML structure - * \author Norman Feske - * \date 2017-08-09 - */ - -/* - * Copyright (C) 2017 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU Affero General Public License version 3. - */ - -#ifndef _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_ -#define _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_ - -#include - -namespace Genode { - - template - static inline void - update_list_model_from_xml(POLICY &policy, - List &list, - Xml_node node); - - template struct List_model_update_policy; -} - - -/** - * Policy interface to be supplied to 'update_list_model_from_xml' - * - * \param ELEM element type, must be a list element - * - * This class template is merely a blue print of a policy to document the - * interface. - */ -template -struct Genode::List_model_update_policy -{ - /* - * Types that need to be supplied by the policy implementation - */ - typedef ELEM Element; - - struct Unknown_element_type : Exception { }; - - /** - * Destroy element - * - * When this function is called, the element is no longer contained - * in the model's list. - */ - void destroy_element(ELEM &elem); - - /** - * Create element of the type given in the 'elem_node' - * - * \throw Unknown_element_type - */ - ELEM &create_element(Xml_node elem_node); - - /** - * Import element properties from XML node - */ - void update_element(ELEM &elem, Xml_node elem_node); - - /** - * Return true if element corresponds to XML node - */ - static bool element_matches_xml_node(Element const &, Xml_node); - - /** - * Return true if XML node should be imported - * - * This method allows the policy to disregard certain XML node types from - * building the data model. - */ - static bool node_is_element(Xml_node node) { return true; } -}; - - -/** - * Update 'list' data model according to XML structure 'node' - * - * \throw Unknown_element_type - */ -template -static inline void -Genode::update_list_model_from_xml(POLICY &policy, - List &list, - Xml_node node) -{ - typedef typename POLICY::Element Element; - - List updated_list; - - Element *last_updated = nullptr; /* used for appending to 'updated_list' */ - - node.for_each_sub_node([&] (Xml_node sub_node) { - - /* skip XML nodes that are unrelated to the data model */ - if (!policy.node_is_element(sub_node)) - return; - - /* look up corresponding element in original list */ - Element *curr = list.first(); - while (curr && !policy.element_matches_xml_node(*curr, sub_node)) - curr = curr->next(); - - /* consume existing element or create new one */ - if (curr) { - list.remove(curr); - } else { - /* \throw Unknown_element_type */ - curr = &policy.create_element(sub_node); - } - - /* append current element to 'updated_list' */ - updated_list.insert(curr, last_updated); - last_updated = curr; - - policy.update_element(*curr, sub_node); - }); - - /* remove stale elements */ - Element *next = nullptr; - for (Element *e = list.first(); e; e = next) { - next = e->next(); - policy.destroy_element(*e); - } - - /* use 'updated_list' list new data model */ - list = updated_list; -} - -#endif /* _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_ */ diff --git a/repos/gems/src/app/menu_view/root_widget.h b/repos/gems/src/app/menu_view/root_widget.h index 5c961ad006..458fa125f8 100644 --- a/repos/gems/src/app/menu_view/root_widget.h +++ b/repos/gems/src/app/menu_view/root_widget.h @@ -29,10 +29,12 @@ struct Menu_view::Root_widget : Widget Area animated_size() const { - if (Widget const * const child = _children.first()) - return child->animated_geometry().area(); + Area result(1, 1); - return Area(1, 1); + _children.for_each([&] (Widget const &child) { + result = child.animated_geometry().area(); }); + + return result; } void update(Xml_node node) override @@ -51,16 +53,18 @@ struct Menu_view::Root_widget : Widget _update_children(node); - if (Widget *child = _children.first()) - child->geometry(Rect(Point(0, 0), child->min_size())); + _children.for_each([&] (Widget &child) { + child.geometry(Rect(Point(0, 0), child.min_size())); }); } Area min_size() const override { - if (Widget const * const child = _children.first()) - return child->min_size(); + Area result(1, 1); - return Area(1, 1); + _children.for_each([&] (Widget const &child) { + result = child.min_size(); }); + + return result; } void draw(Surface &pixel_surface, @@ -72,10 +76,10 @@ struct Menu_view::Root_widget : Widget void _layout() override { - if (Widget *child = _children.first()) { - child->size(geometry().area()); - child->position(Point(0, 0)); - } + _children.for_each([&] (Widget &child) { + child.size(geometry().area()); + child.position(Point(0, 0)); + }); } }; diff --git a/repos/gems/src/app/menu_view/widget.h b/repos/gems/src/app/menu_view/widget.h index 0d1c1eba15..d7cf9b8ce3 100644 --- a/repos/gems/src/app/menu_view/widget.h +++ b/repos/gems/src/app/menu_view/widget.h @@ -16,10 +16,10 @@ /* Genode includes */ #include +#include /* local includes */ #include -#include #include namespace Menu_view { @@ -45,7 +45,7 @@ struct Menu_view::Margin }; -class Menu_view::Widget : public List::Element +class Menu_view::Widget : public List_model::Element { public: @@ -99,9 +99,9 @@ class Menu_view::Widget : public List::Element Widget_factory &_factory; - List _children; + List_model _children; - struct Model_update_policy : List_model_update_policy + struct Model_update_policy : List_model::Update_policy { Widget_factory &_factory; @@ -129,15 +129,15 @@ class Menu_view::Widget : public List::Element inline void _update_children(Xml_node node) { - update_list_model_from_xml(_model_update_policy, _children, node); + _children.update_from_xml(_model_update_policy, node); } void _draw_children(Surface &pixel_surface, Surface &alpha_surface, Point at) const { - for (Widget const *w = _children.first(); w; w = w->next()) - w->draw(pixel_surface, alpha_surface, at + w->_animated_geometry.p1()); + _children.for_each([&] (Widget const &w) { + w.draw(pixel_surface, alpha_surface, at + w._animated_geometry.p1()); }); } virtual void _layout() { } @@ -192,10 +192,7 @@ class Menu_view::Widget : public List::Element virtual ~Widget() { - while (Widget *w = _children.first()) { - _children.remove(w); - _model_update_policy.destroy_element(*w); - } + _children.destroy_all_elements(_model_update_policy); } bool has_name(Name const &name) const { return name == _name; } @@ -230,11 +227,12 @@ class Menu_view::Widget : public List::Element if (!_inner_geometry().contains(at)) return Unique_id(); - for (Widget const *w = _children.first(); w; w = w->next()) { - Unique_id res = w->hovered(at - w->geometry().p1()); - if (res.valid()) - return res; - } + Unique_id result { }; + _children.for_each([&] (Widget const &w) { + Unique_id const id = w.hovered(at - w.geometry().p1()); + if (id.valid()) + result = id; + }); return _unique_id; } @@ -256,9 +254,8 @@ class Menu_view::Widget : public List::Element xml.attribute("width", geometry().w()); xml.attribute("height", geometry().h()); - for (Widget const *w = _children.first(); w; w = w->next()) { - w->gen_hover_model(xml, at - w->geometry().p1()); - } + _children.for_each([&] (Widget const &w) { + w.gen_hover_model(xml, at - w.geometry().p1()); }); }); } }