lumiera_documentation/src/gui/model/element-access.hpp
Ichthyostega 8097485dbf ViewSpec: integrate the simple View access case (Unit test PASS)
This finishes the first round of design drafts in this area.
Right now it seems difficult to get any further, since most of
the actual view creation and management in the UI is not yet coded.
2018-06-15 18:02:08 +02:00

202 lines
7.6 KiB
C++

/*
ELEMENT-ACCESS.hpp - access to generic elements in the UI
Copyright (C) Lumiera.org
2015, 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 element-access.hpp
** Interface to discover and access raw UI elements in a cross cutting way.
** We have several orthogonal identification and access schemes within the UI.
** A naively written UI application just attaches the core logic below some widgets and
** controllers -- not only does this lead to a hard to maintain codebase, this approach
** is even outright impossible for Lumiera, since the core is able to run standalone and
** the UI is loaded as plug-in, which places us into the situation to connect a self
** contained core with a self contained UI. This is a binding, which, as a sideline, also
** generates a control structure of its own. And another kind of generic access happens
** when we _navigate_ the topological UI structure for focus management.
**
** This interface defines an abstract service to translate a generic element designation
** into a (C++ language level) access to internal structures of the UI toolkit (GTK in our case).
** This access to low-level structures proceeds in two stages:
** - navigate down the UI topology. Optionally, this may involve a mutation (create element)
** - evaluate the result (found, not found, element created) and access the target,
** possibly with conversion (which might fail)
**
** @note as of 4/2018 this is a first draft and WIP-WIP-WIP
** @todo WIP ///////////////////////TICKET #1134
**
** @see ElementAccess_test
** @see elem-access-dir.hpp implementation
**
*/
#ifndef GUI_MODEL_ELEMENT_ACCESS_H
#define GUI_MODEL_ELEMENT_ACCESS_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/result.hpp"
#include "include/limits.h"
#include "lib/variant.hpp"
#include "lib/meta/typelist-manip.hpp"
#include "lib/access-casted.hpp"
#include "gui/interact/ui-coord.hpp"
#include <string>
namespace Gtk {
class Widget;
}
namespace gui {
namespace model {
namespace error = lumiera::error;
using interact::UICoord;
using lib::meta::Types;
using std::string;
class Tangible;
/**
* Interface: access UI elements by navigating the UI topology.
*
* @see gui::interact::Navigator
* @see ElementAccess_test
*/
class ElementAccess
: util::NonCopyable
{
public:
virtual ~ElementAccess () { } ///< this is an interface
/* == Access by Location == */
template<class TAR>
lib::Result<TAR&> access (UICoord const& destination);
UICoord locate_or_create (UICoord const& destination, size_t limitCreation = LUMIERA_MAX_ORDINAL_NUMBER);
protected:
using RawResult = lib::Variant<Types<model::Tangible*, Gtk::Widget*>>;
/** @internal drill down according to coordinates, maybe create element */
virtual RawResult performAccessTo (UICoord::Builder &, size_t limitCreation) =0;
private:
template<class TAR>
struct TypeConverter;
};
/** @internal helper to perform conversion to the desired result type.
* This is a bit of tricky metaprogramming to deal with the problem that we can not
* assume a single base interface for all the UI elements or widgets accessible through
* UI-Coordinates. Rather we have to deal with a small set of possible base interfaces,
* and thus the actual [access function](\ref #performAccessTo) returns a _variant record_
* holding a pointer, and internally tagged with the base interface type to apply. Now the
* public API functions are templated to the _result type as expected by the invoking clinent_
* and thus we get a matrix of possible cases; when the expected result type is _reachable by
* dynamic downcast_ from the actual base interface type returned by the internal access function,
* we can perform this `dynamic_cast`. Otherwise the returned result proxy object is marked as empty.
* @remark technically this solution relies on the lib::Variant::Visitor to provide a NOP default
* implementation. The TypeConverter template is instantiated with the desired target type
* and thus only overwrites _the first case where an conversion is possible._ In all other
* cases the default implementation does nothing and thus retains the embedded result proxy
* in empty state.
*/
template<class TAR>
struct ElementAccess::TypeConverter
: RawResult::Visitor
{
lib::Result<TAR&> result{"not convertible to desired target widget"};
template<typename X> // note the "backward" use. We pick that base interface
using canUpcast = std::is_convertible<TAR*, X>; // into which our desired result type can be upcast, because
// we know the then following dynamic_cast (downcast) can succeed
using Base = typename RawResult::FirstMatching<canUpcast>::Type;
virtual void
handle (Base& pb) override
{
if (pb)
{
TAR* downcasted = dynamic_cast<TAR*> (pb);
if (downcasted)
result = *downcasted;
}
else
result = lib::Result<TAR&>{"access returns empty answer"};
}
};
/** Navigate the UI topology to access the designated component.
* @tparam TAR type of result element expected at the designated location
* @return suitably converted direct (language) reference to the desired element
* wrapped as _result proxy_
* @note when access was not possible because either the element does not exist,
* or is not convertible to the desired target type, the result proxy is empty
* and convertible to `bool(false)`
*/
template<class TAR>
inline lib::Result<TAR&>
ElementAccess::access (UICoord const& destination)
{
UICoord::Builder targetLocation{destination.rebuild()};
TypeConverter<TAR> converter;
RawResult targetElm = performAccessTo (targetLocation, 0);
targetElm.accept (converter);
return converter.result;
}
/** Navigate to the designated component, possibly create the element and parents
* @param limitCreation limit on the number of similar UI elements that can be created
* within a scope. A value of zero prohibits creating of new elements.
* @return location of the actual element to use, as UI-coordinates.
* @note when access was not possible because the element could not been created,
* the indicated location returned is an _empty coordinate spec._
*/
inline UICoord
ElementAccess::locate_or_create (UICoord const& destination, size_t limitCreation)
{
UICoord::Builder targetLocation{destination.rebuild()};
performAccessTo (targetLocation, limitCreation);
return targetLocation;
}
}} // namespace gui::model
#endif /*GUI_MODEL_ELEMENT_ACCESS_H*/