LUMIERA.clone/src/gui/interact/ui-coord-resolver.hpp
Ichthyostega 782b4f949f TreeExplorer: extended analysis regarding tree expanding and backtracking computation (#1117)
This can be seen as a side track, but the hope is
by relying on some kind of monadic evaluation pattern, we'll be
able to to reconcile the IterExplorer draft from 2012 with the requirement
to keep the implementation of "tree position" entirely opaque.

The latter is mandatory in the use case here, since we must not intermingle
the algorithm to resolve UI-coordinates in any way with the code actually
navigating and accessing GTK widgets. Thus, we're forced to build some kind
of abstraction barrier, and this turns out to be surprisingly difficult.
2017-11-17 21:43:50 +01:00

302 lines
11 KiB
C++

/*
UI-COORD-RESOLVER.hpp - resolve UI coordinate spec against actual window topology
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 02139, USA.
*/
/** @file ui-coord-resolver.hpp
** Evaluation of UI coordinates against a concrete window topology.
** [UI-Coordinates](\ref UICoord) allow to describe and locate an interface component within the
** Lumiera GUI through a topological access path. As such these coordinate specifications are abstract,
** and need to be related, attached or resolved against the actual configuration of widgets in the UI.
** Through this relation it becomes possible to pose meaningful queries over these coordinates, or to
** build, rebuild and remould a coordinate specification.
**
** We need to avoid tainting with the intrinsics of the actual UI toolkit though -- which indicates
** the UICoordResolver should be designed as an abstract intermediary, built on top of a command and
** query interface, provided by the \ref Navigator and backed by the actual UI configuration.
**
** @todo WIP 10/2017 first draft ////////////////////////////////////////////////////////////////////////////TICKET #1106 generic UI coordinate system
**
** @see UICoordResolver_test
** @see UICoord_test
** @see navigator.hpp
** @see view-locator.hpp
*/
#ifndef GUI_INTERACT_UI_COORD_RESOLVER_H
#define GUI_INTERACT_UI_COORD_RESOLVER_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "gui/interact/ui-coord.hpp"
#include "lib/iter-source.hpp"
#include "lib/iter-stack.hpp"
#include "lib/util.hpp"
//#include <boost/noncopyable.hpp>
//#include <string>
//#include <vector>
#include <utility>
#include <memory>
namespace gui {
namespace interact {
namespace error = lumiera::error;
// using std::unique_ptr;
// using std::string;
using lib::Literal;
// using lib::Symbol;
using util::unConst;
// using util::isnil;
// using util::min;
/**
* Interface to discover a backing structure for the purpose of path navigation and resolution.
* UICoord are meant to designate a position within the logical structure of an UI -- yet in fact
* they may be resolved against any tree-like topological structure, which can be queried through
* this interface.
* @see Navigator the implementation used in the Lumiera UI, as backed by actual GUI components
*/
class LocationQuery
{
public:
virtual ~LocationQuery() { } ///< this is an interface
using ChildIter = lib::IterSource<Literal>::iterator;
/** make the real anchor point explicit.
* @param path an explicit UICoord spec to be anchored in the actual UI
* @return _explicit_ literal window name, where the path can be anchored
* Symbol::BOTTOM in case the given path can not be anchored currently.
* @remark here "to anchor" means to match and thus "attach" the starting point
* of the UIcoord path, i.e. the window spec, with an actual top-level
* window existing in the current UI configuration and state. This operation
* either confirms existence of a window given by explicit ID, or it supplies
* the current meaning of the meta specs `currentWindow` and `firstWindow`,
* again in the form of an explicit window name
*/
virtual Literal determineAnchor (UICoord const& path) =0;
/** evaluate to what extent a UIcoord spec matches the actual UI
* @return the depth to which the given spec is _"covered"_ by the actual UI.
* Can be zero, in which case the given coordinates can not be resolved
* and addressed within the currently existing windows, panes and views.
* @note this operation does not perform any _resolution_ or interpolation of wildcards,
* it just matches explicit UI component names.
* @see UICoordResolver for a facility to perform such a resolution and to navigate paths.
*/
virtual size_t determineCoverage (UICoord const& path) =0;
/** get the sequence of child components at a designated position in the actual UI
* @param path an explicit UIcoord spec, expected to be anchored and at least partially
* covered within the current configuration and state of the UI
* @param pos depth where the given path shall be evaluated, starting with 0 at window level
* @return an iterator to enumerate all child components actually existing in the current
* UI below the location designated by path and pos.
* @remark the path is only evaluated up to (not including) the given depth. Especially
* when `pos == 0`, then the path is not evaluated and matched at all, rather
* just the current list of top-level windows is returned.
* @throw error::State when navigating the given path touches a non-existing element
*/
virtual ChildIter getChildren (UICoord const& path, size_t pos) =0;
};
/**
* Query and mutate UICoord specifications in relation to actual UI topology.
*
* @todo initial draft as of 10/2017
*/
class UICoordResolver
: public UICoord::Builder
{
struct Resolution
{
const char* anchor = nullptr;
size_t depth = 0;
std::unique_ptr<UICoord> covfefe;
bool isResolved = false;
};
LocationQuery& query_;
Resolution res_;
struct ResolutionState;
public:
UICoordResolver (UICoord const& uic, LocationQuery& queryAPI)
: Builder{uic}
, query_{queryAPI}
, res_{}
{
attempt_trivialResolution();
}
/* === query functions === */
/** is this path explicitly anchored at an existing window?
* @remarks this also implies the path is complete and explicit (no wildcards).
*/
bool
isAnchored() const
{
return res_.isResolved
and res_.anchor and res_.anchor != Symbol::BOTTOM;
}
/** determine if a mutation is possible to anchor the path explicitly
* @remarks basically this either means the path isAnchored(), or we're able
* to calculate a path resolution, interpolating any wildcards.
* And while the path resolution as such might fail, it was at least
* successful to determine an anchor point. The existence of such an
* anchor point implies the path is not totally in contradiction to the
* existing UI*/
bool
canAnchor() const
{
return isAnchored()
or (res_.isResolved and res_.covfefe)
or unConst(this)->pathResolution()
or isAnchored(); // resolution failed, but computed at least an anchor
}
/** is this path at least _partially_ covered?
* A covered path describes an access path through widgets actually existing in the UI.
* @remarks this also implies the path is anchored, complete and explicit.
* @note this predicate tests for _partial_ coverage, which means, there might
* be some extraneous suffix in this path descending beyond existing UI
*/
bool
isCovered() const
{
return res_.isResolved
and res_.depth > 0;
}
/** there is no extraneous uncovered suffix in this path spec */
bool
isTotallyCovered() const
{
return res_.isResolved
and res_.depth == this->uic_.size();
}
/** determine if a mutation is possible to get the path (partially) covered.
* @remarks in order to be successful, a path resolution must interpolate any gaps in the
* path spec _and_ reach a point behind / below the gap (wildcards), where an existing
* explicitly stated component in the path can be confirmed (covered) by the existing UI.
* The idea behind this definition is that we do not want just some interpolated wildcards,
* rather we really want to _confirm_ the essence of the path specification. Yet we accept
* an extraneous suffix _in the explicitly given part_ of the path spec to extend beyond or
* below what exists currently within the UI.
*/
bool
canCover() const
{
return isCovered()
or (res_.isResolved and res_.covfefe)
or unConst(this)->pathResolution();
}
/* === mutation functions === */
/** mutate the path to get it totally covered
*/
UICoordResolver
cover()
{
if (isCovered())
truncateTo (res_.depth);
else if (canCover())
{
ASSERT (res_.isResolved);
REQUIRE (res_.covfefe);
res_.depth = res_.covfefe->size();
std::swap (this->uic_, *res_.covfefe);
res_.covfefe.reset();
ENSURE (isTotallyCovered());
}
else
{
ASSERT (res_.isResolved);
REQUIRE (res_.depth == 0);
REQUIRE (not res_.covfefe);
truncateTo (0);
}
return std::move (*this);
}
/** mutate the path to extend it while keeping it partially covered
*/
UICoordResolver
extend (Literal pathExtension)
{
if (not isTotallyCovered())
cover();
ENSURE (isTotallyCovered());
append (pathExtension);
res_.depth = query_.determineCoverage (this->uic_); // coverage may grow
ENSURE (isCovered());
return std::move (*this);
}
private:
/** establish a trivial anchorage and coverage, if possible.
* @note when the UICoord contains wildcards or is incomplete,
* a full resolution with backtracking is necessary to
* determine anchorage and coverage
*/
void
attempt_trivialResolution()
{
if (not uic_.isExplicit()) return;
res_.anchor = query_.determineAnchor (this->uic_);
res_.depth = query_.determineCoverage(this->uic_);
res_.isResolved = true;
}
/** @internal algorithm to resolve this UICoord path against the actual UI topology. */
bool pathResolution();
};
}}// namespace gui::interact
#endif /*GUI_INTERACT_UI_COORD_RESOLVER_H*/