2018-02-01 23:08:43 +01:00
/*
UI - LOCATION - SOLVER . hpp - decide upon a possible location for some UI component
Copyright ( C ) Lumiera . org
2018 , 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 ui-location-resolver.hpp
* * A solver to match incomplete coordinate specifications against the actual UI topology .
* * Within the Lumiera UI , a _component view_ is typically _created or retrieved_ to live at some position
* * within the tree - like topology of the interface . Such happens as a consequence of interaction or other
* * events , and the logic as to where and how to place a new UI element shall not be intermingled with the
* * actual event handling code . Rather , the ViewLocator , as a service related to the InteractionDirector ,
* * can be invoked to draw on some default configuration plus the actual UI topology present at this time .
* *
2018-02-17 03:45:07 +01:00
* * # # Pattern matching against the actual UI - tree
* *
* * This Location solver is based on the pattern matching algorithm [ embedded ] ( \ ref ui - coord - resolver . cpp )
* * within the UICoordResolver component . This mechanism allows to treat ( typically ) incomplete coordinate
* * specifications as rules for locating an element . Several such coordinate _clauses_ can be combined into
* * a _disjunctive_ LocationRule , which is evaluated by matching the clauses one by one , in given order ,
* * against the currently existing UI tree ( topology ) . Each clause is evaluated individually from scratch
* * ( there is no common variable binding ) ; the first clause to produce a successful match is used as
* * solution - - with any placeholders replaced by the actually matching UI elements .
* *
* * # # Default View location configuration DSL
* *
* * Whenever a new UI element of a given kind is to be created , we query a standard location configuration
* * to determine it ' s actual location within the interface . This standard configuration is known as
* * [ " ViewSpec DSL " ] ( \ ref id - scheme . hpp ) and hard - wired into the UI code . Based on the aforementioned
* * pattern matching rules , it allows to express placement rules dependent on the already existing UI .
* * There are two kinds of location clauses
* * - the standard rules describe an element required to exist . Typically this is the _parent element_
* * of the UI widget in question . But it is also possible to write clauses directly mentioning this
* * element , in which case such an element must already exist in the UI and will be retrieved as result .
* * - the more relaxed _create clauses_ describe a new location / path within the UI - tree , meaning that
* * any ( parent ) elements not yet present are to be created . A _create clause_ is defined within the
* * DSL by ending a UI coordinate specification with the term ` . create ( ) ` . It may still be incomplete
* * ( i . e . contain wildcards ) , which means , that the first explicitly given element after ( below ) the
* * wildcards must exist in the tree , to allow for an unambiguous pattern match . Otherwise , for
* * creating a new path completely from scratch , all elements have to be given explicitly .
* * As a minimum requirement , each LocationRule should be concluded with such a " catch-all " explicit
* * create clause , which describes the standard location of the element in question .
2018-02-01 23:08:43 +01:00
* *
* * @ see UILocationResolver_test
* * @ see ViewSpecDSL_test
* * @ see UICoordResolver
* * @ see view - locator . hpp
*/
# ifndef GUI_INTERACT_UI_LOCATION_SOLVER_H
# define GUI_INTERACT_UI_LOCATION_SOLVER_H
# include "lib/error.hpp"
2018-02-08 00:37:02 +01:00
# include "lib/symbol.hpp"
2018-02-14 03:02:44 +01:00
# include "lib/format-util.hpp"
2018-02-01 23:08:43 +01:00
# include "gui/interact/ui-coord.hpp"
2018-02-08 00:37:02 +01:00
# include "gui/interact/ui-coord-resolver.hpp"
2018-02-01 23:08:43 +01:00
2018-02-07 03:11:12 +01:00
# include <boost/noncopyable.hpp>
2018-02-01 23:08:43 +01:00
# include <utility>
2018-02-07 03:11:12 +01:00
# include <vector>
2018-02-14 03:02:44 +01:00
# include <string>
2018-02-01 23:08:43 +01:00
namespace gui {
namespace interact {
2018-02-08 00:37:02 +01:00
using lib : : Symbol ;
2018-02-14 03:02:44 +01:00
using std : : string ;
using std : : move ;
2018-02-08 00:37:02 +01:00
class LocationQuery ;
using LocationQueryAccess = std : : function < LocationQuery & ( ) > ;
/** @internal access UI service to query and discover locations within UI topology */
2018-02-17 03:45:07 +01:00
extern LocationQueryAccess loactionQuery ; ///////////////////////////////////////////////////////////////TODO this global variable seems to be dispensable
2018-02-08 00:37:02 +01:00
2018-02-01 23:08:43 +01:00
2018-02-17 03:45:07 +01:00
/**
* A single location specification to be matched and fulfilled .
* It is created from a - - typically incomplete - - UICoord spec ,
* which in turn can be built through a DSL notation .
* @ todo maybe add a flag to require the current query goal to exist in tree //////////////////////////////TICKET #1130
*/
2018-02-09 04:10:53 +01:00
struct LocationClause
2018-02-07 03:11:12 +01:00
: boost : : noncopyable
{
2018-02-09 04:10:53 +01:00
UICoord pattern ;
bool createParents ;
2018-02-07 03:11:12 +01:00
2018-02-09 04:10:53 +01:00
LocationClause ( UICoord & & locationPattern , bool allowCreate = false )
: pattern { move ( locationPattern ) }
, createParents { allowCreate }
2018-02-07 03:11:12 +01:00
{ }
LocationClause ( LocationClause & & rr )
2018-02-09 04:10:53 +01:00
: pattern { move ( rr . pattern ) }
, createParents { rr . createParents }
2018-02-07 03:11:12 +01:00
{ }
2018-02-14 03:02:44 +01:00
operator string ( ) const ;
2018-02-07 03:11:12 +01:00
} ;
2018-02-17 03:45:07 +01:00
/**
* A rule to determine some location by matching against the UI - tree .
* It is comprised of one or several disjunctive [ clauses ] ( \ ref LocationClause ) ,
* each of which is a coordinate pattern to be matched . The clauses are tried
* in order and the first successfully matched clause wins .
*/
2018-02-07 03:11:12 +01:00
class LocationRule
: boost : : noncopyable
{
using Clauses = std : : vector < LocationClause > ;
Clauses clauses_ ;
public :
2018-02-11 04:00:59 +01:00
LocationRule ( LocationClause & & firstRule )
2018-02-07 03:11:12 +01:00
: clauses_ { }
2018-02-07 04:23:44 +01:00
{
this - > append ( move ( firstRule ) ) ;
}
LocationRule ( LocationRule & & rr )
: clauses_ { move ( rr . clauses_ ) }
{ }
LocationRule & &
2018-02-11 04:00:59 +01:00
append ( LocationClause & & furtherRule )
2018-02-07 04:23:44 +01:00
{
clauses_ . emplace_back ( move ( furtherRule ) ) ;
return move ( * this ) ;
2018-02-07 03:11:12 +01:00
}
2018-02-08 01:50:11 +01:00
using iterator = lib : : RangeIter < Clauses : : const_iterator > ;
iterator begin ( ) const { return iterator { clauses_ . begin ( ) , clauses_ . end ( ) } ; }
iterator end ( ) const { return iterator ( ) ; }
2018-02-14 03:02:44 +01:00
operator string ( ) const ;
2018-02-07 03:11:12 +01:00
} ;
2018-02-01 23:08:43 +01:00
2018-02-14 03:02:44 +01:00
2018-02-17 03:45:07 +01:00
2018-02-11 04:00:59 +01:00
/* ==== Support of UI-Coordinate notation within the ViewSpec-DSL ==== */
/** interprets the current (inline) contents of an UICoord builder expression
* as a standard LocationClause , which has the meaning of " when an element
* exists at the location XYZ in the real UI "
* @ warning like all the UICoord : : Builder functions , the contents are moved .
* Thus , after using this conversion path _once_ , the Builder _is defunct_
*/
inline
UICoord : : Builder : : operator LocationClause ( )
{
return LocationClause { move ( * this ) , false } ;
}
/** interprets the current (inline) builder contents as _create clause_,
* which has the meaning " create a new element XYZ when possible " */
inline LocationClause
UICoord : : Builder : : create ( )
{
return LocationClause { move ( * this ) , true } ;
}
2018-02-07 04:23:44 +01:00
/** DSL operator to assemble a sequence of clauses.
* Introduced solely for the purpose of writing location specifications within the
* [ ViewSpec - DSL ] ( \ ref id - scheme . hpp ) , this operator acts on several UI - Coordinate specs
* to create a sequence of clauses , to be checked against the currently existing UI topology ,
* in the given order , ranging from more specific to more general patterns .
*/
inline LocationRule
operator or ( UICoord : : Builder & & firstRule , UICoord secondRule )
{
return LocationRule { move ( firstRule ) }
. append ( move ( secondRule ) ) ;
}
inline LocationRule & &
operator or ( LocationRule & & ruleSet , UICoord furtherRule )
{
ruleSet . append ( move ( furtherRule ) ) ;
return move ( ruleSet ) ;
}
2018-02-14 03:02:44 +01:00
/* === diagnostics === */
inline
LocationClause : : operator string ( ) const
{
return string { pattern }
+ ( createParents ? " create! " : " " ) ;
}
inline
LocationRule : : operator string ( ) const
{
return " =~ \t .. "
+ util : : join ( clauses_ , " \n \t OR " ) ;
}
2018-02-07 04:23:44 +01:00
2018-02-01 23:08:43 +01:00
/**
2018-02-17 03:45:07 +01:00
* Service to determine the location of an UI component view .
* @ see LocationRule
* @ see UILocationResolver_test : : simple_usage_example ( )
2018-02-01 23:08:43 +01:00
*/
class UILocationSolver
: boost : : noncopyable
{
2018-02-08 01:50:11 +01:00
LocationQueryAccess getLocationQuery ;
2018-02-01 23:08:43 +01:00
public :
2018-02-08 00:37:02 +01:00
explicit
2018-02-08 01:50:11 +01:00
UILocationSolver ( LocationQueryAccess accessor )
: getLocationQuery { accessor }
2018-02-08 00:37:02 +01:00
{ }
explicit
2018-02-08 01:50:11 +01:00
UILocationSolver ( LocationQuery & locationQueryService )
: getLocationQuery { [ & ] ( ) - > LocationQuery & { return locationQueryService ; } }
2018-02-01 23:08:43 +01:00
{ }
/**
2018-02-08 01:50:11 +01:00
* Solve for a location according to the given location rule .
* @ param depth desired kind of UI element ( and thus the depth in the UI topology tree )
* @ param elementType designator of the specific element to be created at that level
2018-02-09 04:10:53 +01:00
* @ return an explicit location , resolved against the current UI topology . May be empty
2018-02-08 01:50:11 +01:00
* @ remarks the returned path is either empty ( no solution exists ) , or it is " partially covered "
* by the existing UI ; here , the " covered " part are the already existing UI elements ,
* while the remaining , uncovered extension describes additional elements to be created .
* When the resolution process found an already existing UI element , the returned path
* is completely covered . The degree of coverage of a path can be found out with the
2018-02-09 23:49:36 +01:00
* help of a UICoordResolver , which also needs a LocationQuery ( service ) to discover
* the currently existing UI topology .
2018-02-01 23:08:43 +01:00
*/
2018-02-08 00:37:02 +01:00
UICoord
2018-02-11 04:16:58 +01:00
solve ( LocationRule const & rule , size_t depth , Symbol elementTypeID )
2018-02-08 00:37:02 +01:00
{
2018-02-08 01:50:11 +01:00
for ( auto & clause : rule )
{
2018-02-09 23:49:36 +01:00
// Clauses which do not at least describe an element at parent level
// will never lead to a solution and can thus be skipped
if ( depth + 1 < clause . pattern . size ( )
or depth > clause . pattern . size ( ) )
continue ;
// try to solve the current Clause by matching against real UI topology
2018-02-09 04:10:53 +01:00
UICoordResolver resolver { clause . pattern , getLocationQuery ( ) } ;
2018-02-17 05:11:34 +01:00
preprocess ( resolver , clause ) ;
2018-02-09 23:49:36 +01:00
resolver . coverPartially ( ) ; // now either holds a solution or is empty
if ( not isnil ( resolver ) // Solution found!
and ( clause . createParents // The "create" case requires only some part to exist,
or resolver . isCoveredTotally ( ) ) ) // while in the default case we demand complete coverage
2018-02-09 04:10:53 +01:00
{
2018-02-09 23:49:36 +01:00
if ( depth = = clause . pattern . size ( ) )
// append ID of the new element to be created
// unless it's already there (and thus exists)
resolver . append ( elementTypeID ) ;
return move ( resolver ) ;
// use the first suitable solution and exit
2018-02-09 04:10:53 +01:00
}
else
2018-02-09 23:49:36 +01:00
if ( clause . createParents and clause . pattern . isExplicit ( ) )
// allow creation of a totally new path from scratch
// as long as it is complete and explicitly given
2018-02-13 02:46:43 +01:00
{
if ( depth = = clause . pattern . size ( ) )
return clause . pattern . append ( elementTypeID ) ;
else
return clause . pattern ;
}
2018-02-08 01:50:11 +01:00
}
2018-02-09 23:49:36 +01:00
//all clauses tried without success...
return UICoord ( ) ;
2018-02-08 00:37:02 +01:00
}
2018-02-01 23:08:43 +01:00
private :
2018-02-17 05:11:34 +01:00
/** perform adjustments on the current pattern to support some very specific situations
* - when we want to create a new panel ( or re - use an existing of the same name ) , on top
* of an existing ( but irrelevant ) perspective , we mark this perspective as " just there " .
* This allows to get a new path as solution , which is just covered up to and including
* that perspective ; without special treatment , such a match would otherwise be rejected .
*/
void
preprocess ( UICoordResolver & builder , LocationClause const & clause )
{
if ( clause . createParents
and clause . pattern . isComplete ( ) )
{
builder . existentiallyQuantify ( UIC_PERSP ) ;
}
}
2018-02-01 23:08:43 +01:00
} ;
} } // namespace gui::interact
# endif /*GUI_INTERACT_UI_LOCATION_SOLVER_H*/