/* GEN-NODE-LOCATION-QUERY.hpp - pose UI-coordinate location queries against a GenNode structure Copyright (C) 2017, Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the   Free Software Foundation; either version 2 of the License, or (at your   option) any later version. See the file COPYING for further details. */ /** @file gen-node-location-query.hpp ** Implementation of the stage::interact::LocationQuery interface to work on a GenNode tree. ** The interface allows to pose queries against a concrete structure to verify and reshape some ** [UI Coordinate specification](\ref UICoord); basically it offers methods to navigate within a ** tree-like structure. While in the actual implementation, such a query interface would be backed ** by navigating real UI structures, the implementation given here instead uses a generic tree structure ** given as `Record`. ** ** # Representing UI structure as GenNode tree ** ** While basically the interface LocationQuery abstracts and reduces the structure of an UI into ** just some hierarchically arranged and nested IDs, we should note some specific twists how a GenNode ** tree is used here to represent the structure elements as defined through [UI coordinates](\ref UICoord): ** - we use the special _type_ attribute to represent the _perspective_ within each window; ** deliberately, we'll use this twisted structure here to highlight the fact that the ** backing structure need not be homogeneous; rather, it may require explicit branching ** - we use the _attributes_ within the GenNode "object" representation, since these are _named_ ** nested elements, and the whole notion of an UI coordinate path is based on named child components ** - relying upon the [object builder notation](\ref Record::Mutator), it is possible to define a whole ** structure as nested inline tree; named nested elements can be added with the `set(key, val)` ** builder function, and for each nested scope, we start a new nested builder with `MakeRec()`. ** - since GenNodeLocationQuery is conceived for writing test and verification code, there is a ** special convention to set the `currentWindow` to be the last one in list -- in a real UI ** this would not of course not be a configurable property of the LocationQuery, and rather ** just reflect the transient window state and return the currently activated window ** ** @todo WIP 10/2017 started in the effort of shaping the LoactionQuery interface, and used ** to support writing unit tests, to verify the UiCoordResolver. It remains to be seen ** if this implementation can be used beyond this limited purpose ** ** @see UICoordResolver_test ** @see IterExplorer_test ** @see ui-coord-resolver.hpp ** @see navigator.hpp */ #ifndef STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H #define STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H #include "lib/error.hpp" #include "lib/symbol.hpp" #include "stage/interact/ui-coord-resolver.hpp" #include "lib/diff/gen-node.hpp" #include "lib/format-string.hpp" #include "lib/iter-explorer.hpp" #include "lib/iter-source.hpp" #include "lib/itertools.hpp" #include "lib/util.hpp" #include #include namespace stage { namespace interact { namespace error = lumiera::error; using lib::Symbol; using lib::Literal; using lib::diff::Rec; using util::isnil; using util::_Fmt; using std::forward; using std::string; /** * Test/Diagnostics: implementation of the LocationQuery-API * based on a abstract topological structure given as Record ("GenNode tree"). * @remark intended for verifying path resolution and navigation through unit tests */ class GenNodeLocationQuery : public LocationQuery { Rec tree_; public: template GenNodeLocationQuery(REC&& backingStructure) : tree_(std::forward(backingStructure)) { } /* === LocationQuery interface === */ /** resolve Anchor against GenNode tree */ virtual Literal determineAnchor (UICoord const& path) override { if (isnil(tree_) or not path.isPresent(UIC_WINDOW)) return Symbol::BOTTOM; if (UIC_FIRST_WINDOW == path.getWindow()) return getFirstWindow(); if (UIC_CURRENT_WINDOW == path.getWindow()) return getCurrentWindow(); if (not tree_.hasAttribute(string{path.getWindow()})) /////////////////////////////////////TICKET #1113 : unnecessary repackaging of a Literal into string when GenNode ID is based on Literal return Symbol::BOTTOM; return path.getWindow(); } /** evaluate to what extent a UIcoord spec matches the structure given as GenNode tree */ virtual size_t determineCoverage (UICoord const& path) override { size_t depth = 0; drillDown (tree_, path, path.size(), depth); return depth; } /** get the sequence of child IDs at a designated position in the backing GenNode tree */ virtual ChildIter getChildren (UICoord const& path, size_t pos) override { size_t depth = 0; Rec const& node = drillDown (tree_, path, pos, depth); if (depth != pos) throw error::State(_Fmt{"unable to drill down to depth %d: " "element %s at pos %d in path %s is in " "contradiction to actual UI structure"} % pos % (depth(); } /* ==== iterate over siblings with the ability to expand one node's children ==== */ /** @return a heap allocated object attached at "current tree position" while * exposing the names of all child nodes [through iteration](\ref lib::IterSource) */ static TreeStructureNavigator* childNavigator (Rec const& node, size_t depth) { //////////////////////////////////////////////////////////////////////////TICKET #1113 : capturing the string into the global Symbol table becomes obsolete, once GenNode exposes Literal as ID auto internedString = [](string const& id) -> Literal { return Symbol{id}; }; return depth==UIC_PERSP? buildNavigator (node, depth, singleValIterator (internedString (node.getType()))) : buildNavigator (node, depth, transformIterator (node.keys(), internedString)); } /** * Helper to navigate a tree topology represented as GenNode tree. * Basically this is a lib::IterSource to encapsulate a sequence of sibling nodes. * A "current element" representation is layered on top to allow to expand one level deeper * on demand. This "child expansion" is triggered by invoking the `expandChildren()` function * on the iterator front-end provided as LocationQuery::ChildIter */ template class GenNodeNavigator : public PAR { Rec const& pos_; size_t depth_; virtual TreeStructureNavigator* expandChildren() const override { return childNavigator (descendInto (pos_,depth_, currentChild_), depth_+1); } ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1125 : work around the misaligned IterSource design // The design of IterSource attempts to be too clever, and we have to pay for it now... // If IterSource would just work like a StateCore and expose the "current element" via API call, // then we'd be able to retrieve the name of the current child node. Unfortunately it doesn't // and thus we rig a "wire tap" here and capture the node names whenever an iteration happens. // Literal currentChild_ = Symbol::BOTTOM; using Pos = PAR::Pos; virtual Pos firstResult () override { Pos pos = PAR::firstResult(); if (pos) currentChild_ = *pos; return pos; } virtual void nextResult(Pos& pos) override { PAR::nextResult (pos); if (pos) currentChild_ = *pos; } ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1125 : work around the misaligned IterSource design public: template GenNodeNavigator(Rec const& node, size_t depth, IT&& rawChildIter) : PAR{forward (rawChildIter)} , pos_{node} , depth_{depth} { } }; /** type rebinding helper to pick up the concrete child iterator type `IT` */ template static TreeStructureNavigator* buildNavigator (Rec const& node, size_t depth, IT && rawIterator) { return new GenNodeNavigator< lib::WrappedLumieraIter> {node, depth, forward (rawIterator)}; } }; }}// namespace stage::interact #endif /*STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H*/