2017-10-16 02:39:22 +02:00
/*
GEN - NODE - LOCATION - QUERY . hpp - pose UI - coordinate location queries against a GenNode structure
Copyright ( C ) Lumiera . org
2017 , Hermann Vosseler < Ichthyostega @ web . de >
This program 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 .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
/** @file gen-node-location-query.hpp
2018-11-15 23:59:23 +01:00
* * Implementation of the stage : : interact : : LocationQuery interface to work on a GenNode tree .
2017-10-16 02:39:22 +02:00
* * 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 < GenNode > ` .
* *
2017-10-23 02:16:57 +02:00
* * # 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
2017-10-28 00:06:44 +02:00
* * 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 ( ) ` .
2017-10-23 02:16:57 +02:00
* * - 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
* *
2017-10-16 02:39:22 +02:00
* * @ 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
2023-06-22 20:23:55 +02:00
* * @ see IterExplorer_test
2017-10-16 02:39:22 +02:00
* * @ see ui - coord - resolver . hpp
* * @ see navigator . hpp
*/
2018-11-15 23:52:02 +01:00
# ifndef STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H
# define STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H
2017-10-16 02:39:22 +02:00
# include "lib/error.hpp"
2017-12-24 23:26:22 +01:00
# include "lib/symbol.hpp"
2018-11-15 23:42:43 +01:00
# include "stage/interact/ui-coord-resolver.hpp"
2017-10-16 02:39:22 +02:00
# include "lib/diff/gen-node.hpp"
2017-10-23 04:05:44 +02:00
# include "lib/format-string.hpp"
2023-06-22 20:23:55 +02:00
# include "lib/iter-explorer.hpp"
2017-12-24 23:26:22 +01:00
# include "lib/iter-source.hpp"
2017-10-23 02:16:57 +02:00
# include "lib/itertools.hpp"
# include "lib/util.hpp"
2017-10-16 02:39:22 +02:00
2017-10-27 05:12:28 +02:00
# include <utility>
2017-12-26 14:56:43 +01:00
# include <string>
2017-10-16 02:39:22 +02:00
2018-11-15 23:55:13 +01:00
namespace stage {
2017-10-16 02:39:22 +02:00
namespace interact {
namespace error = lumiera : : error ;
2017-12-24 23:26:22 +01:00
using lib : : Symbol ;
2017-12-26 14:56:43 +01:00
using lib : : Literal ;
2017-10-16 02:39:22 +02:00
using lib : : diff : : Rec ;
2017-12-26 14:56:43 +01:00
using util : : isnil ;
2017-10-23 04:05:44 +02:00
using util : : _Fmt ;
2017-12-24 23:26:22 +01:00
using std : : forward ;
2017-12-26 14:56:43 +01:00
using std : : string ;
2017-10-16 02:39:22 +02:00
/**
2017-10-21 23:37:04 +02:00
* Test / Diagnostics : implementation of the LocationQuery - API
* based on a abstract topological structure given as Record < GenNode > ( " GenNode tree " ) .
* @ remark intended for verifying path resolution and navigation through unit tests
2017-10-16 02:39:22 +02:00
*/
class GenNodeLocationQuery
: public LocationQuery
{
2017-10-27 05:12:28 +02:00
Rec tree_ ;
2017-10-16 02:39:22 +02:00
public :
2017-10-27 05:12:28 +02:00
template < class REC >
GenNodeLocationQuery ( REC & & backingStructure )
: tree_ ( std : : forward < REC > ( backingStructure ) )
2017-10-23 02:16:57 +02:00
{ }
2017-10-16 02:39:22 +02:00
/* === LocationQuery interface === */
2017-10-21 23:37:04 +02:00
/** resolve Anchor against GenNode tree */
virtual Literal
2017-12-22 19:35:36 +01:00
determineAnchor ( UICoord const & path ) override
2017-10-21 23:37:04 +02:00
{
2017-10-23 02:16:57 +02:00
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 ( ) ;
2017-10-23 04:05:44 +02:00
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 ;
2017-10-23 02:16:57 +02:00
return path . getWindow ( ) ;
2017-10-21 23:37:04 +02:00
}
2017-10-23 03:17:18 +02:00
/** evaluate to what extent a UIcoord spec matches the structure given as GenNode tree */
2017-10-21 23:37:04 +02:00
virtual size_t
2017-12-22 19:35:36 +01:00
determineCoverage ( UICoord const & path ) override
2017-10-21 23:37:04 +02:00
{
2017-10-23 03:17:18 +02:00
size_t depth = 0 ;
2017-10-23 04:05:44 +02:00
drillDown ( tree_ , path , path . size ( ) , depth ) ;
2017-10-23 03:17:18 +02:00
return depth ;
2017-10-21 23:37:04 +02:00
}
2017-10-23 03:17:18 +02:00
/** get the sequence of child IDs at a designated position in the backing GenNode tree */
2017-10-21 23:37:04 +02:00
virtual ChildIter
2017-12-22 19:35:36 +01:00
getChildren ( UICoord const & path , size_t pos ) override
2017-10-21 23:37:04 +02:00
{
2017-10-23 04:05:44 +02:00
size_t depth = 0 ;
Rec const & node = drillDown ( tree_ , path , pos , depth ) ;
2017-10-28 02:06:05 +02:00
if ( depth ! = pos )
2017-10-23 04:05:44 +02:00
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 " }
2017-10-29 16:00:08 +01:00
% pos
2017-10-23 04:05:44 +02:00
% ( depth < path . size ( ) ? path [ depth ] : Symbol : : BOTTOM )
% depth
% path
) ;
2017-12-26 03:51:02 +01:00
return TreeStructureNavigator : : buildIterator (
childNavigator ( node , depth ) ) ;
2017-10-21 23:37:04 +02:00
}
2017-10-16 02:39:22 +02:00
private :
2017-10-23 02:16:57 +02:00
Literal
getFirstWindow ( )
{
return Symbol { * tree_ . keys ( ) } ; //////////////////////////////////////////////TICKET #1113 : warning use of Symbol table becomes obsolete when EntryID relies on Literal
}
Literal
getCurrentWindow ( )
{
return Symbol { lib : : pull_last ( tree_ . keys ( ) ) } ; //////////////////////////////////////////////TICKET #1113 : warning use of Symbol table becomes obsolete when EntryID relies on Literal
2018-01-03 02:46:12 +01:00
} // special convention for unit-tests
2017-10-23 03:17:18 +02:00
2017-10-28 02:06:05 +02:00
Literal
resolveElm ( UICoord const & path , size_t depth )
{
REQUIRE ( path . isPresent ( depth ) ) ;
return depth = = UIC_WINDOW ? GenNodeLocationQuery : : determineAnchor ( path )
: path [ depth ] ;
}
2017-10-28 01:12:06 +02:00
2017-10-28 02:06:05 +02:00
Rec const &
2017-10-23 03:17:18 +02:00
drillDown ( Rec const & tree , UICoord const & path , size_t maxDepth , size_t & depth )
{
if ( depth < maxDepth and path . isPresent ( depth ) )
{
2017-10-28 02:06:05 +02:00
const char * pathElm = resolveElm ( path , depth ) ;
if ( hasNode ( tree , pathElm , depth ) )
2017-10-28 01:12:06 +02:00
{
+ + depth ;
2017-12-26 14:04:21 +01:00
return drillDown ( descendInto ( tree , depth - 1 , pathElm ) , path , maxDepth , depth ) ;
2017-10-28 01:12:06 +02:00
}
2017-10-23 03:17:18 +02:00
}
return tree ;
}
2017-10-28 01:12:06 +02:00
/** does the guiding tree contain the element as requested by the UICoord path?
* @ remark this function abstracts a special asymmetry of the tree representation :
2017-10-28 02:06:05 +02:00
* at ` level = = UIC_PERSP ` ( the second level ) , the perspective info is packed into
* the type meta attribute . This was done on purpose , to verify our design is able
* to handle such implementation intricacies , which we expect to encounter
2017-10-28 01:12:06 +02:00
* when navigating the widgets of a real - world UI toolkit set
*/
static bool
hasNode ( Rec const & tree , const char * pathElm , size_t depth )
{
return depth = = UIC_PERSP ? pathElm = = tree . getType ( )
: tree . hasAttribute ( pathElm ) ;
}
2017-12-26 14:04:21 +01:00
/** within `tree` _at level_ `depth` descend into the child element designated by `pathElm` */
2017-10-28 01:12:06 +02:00
static Rec const &
2017-12-26 14:04:21 +01:00
descendInto ( Rec const & tree , size_t depth , const char * pathElm )
2017-10-28 01:12:06 +02:00
{
return depth = = UIC_PERSP ? tree // perspective info is attached as type at the parent node
: tree . get ( pathElm ) . data . get < Rec > ( ) ;
}
2017-12-22 19:35:36 +01:00
2017-12-26 14:56:43 +01:00
/* ==== 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 < Literal > 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
*/
2017-12-26 03:51:02 +01:00
template < class PAR >
2017-12-24 23:26:22 +01:00
class GenNodeNavigator
2017-12-26 03:51:02 +01:00
: public PAR
2017-12-22 19:35:36 +01:00
{
2017-12-26 03:51:02 +01:00
Rec const & pos_ ;
size_t depth_ ;
2017-12-24 23:26:22 +01:00
virtual TreeStructureNavigator *
expandChildren ( ) const override
{
2017-12-26 14:04:21 +01:00
return childNavigator ( descendInto ( pos_ , depth_ , currentChild_ ) , depth_ + 1 ) ;
2017-12-24 23:26:22 +01:00
}
2017-12-26 04:40:51 +01:00
///////////////////////////////////////////////////////////////////////////////////////////////////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 = typename 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 ;
}
2017-12-26 14:56:43 +01:00
///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1125 : work around the misaligned IterSource design
2017-12-26 03:51:02 +01:00
public :
template < class IT >
GenNodeNavigator ( Rec const & node , size_t depth , IT & & rawChildIter )
: PAR { forward < IT > ( rawChildIter ) }
, pos_ { node }
, depth_ { depth }
{ }
2017-12-22 19:35:36 +01:00
} ;
2017-12-26 14:56:43 +01:00
/** type rebinding helper to pick up the concrete child iterator type `IT` */
2017-12-24 23:26:22 +01:00
template < class IT >
2017-12-26 03:51:02 +01:00
static TreeStructureNavigator *
buildNavigator ( Rec const & node , size_t depth , IT & & rawIterator )
2017-12-24 23:26:22 +01:00
{
2017-12-26 03:51:02 +01:00
return new GenNodeNavigator <
lib : : WrappedLumieraIter < IT ,
TreeStructureNavigator > > { node , depth , forward < IT > ( rawIterator ) } ;
2017-12-24 23:26:22 +01:00
}
2017-10-16 02:39:22 +02:00
} ;
2018-11-15 23:55:13 +01:00
} } // namespace stage::interact
2018-11-15 23:52:02 +01:00
# endif /*STAGE_INTERACT_GEN_NODE_LOCATION_QUERY_H*/