Commands: decide about the basic concept how commands are to be defined (#215)

The point in question is how to manage these definitions in practice,
since we're about to create a huge lot of them eventually. The solution
attempted here is heavily inspired by the boost-test framework
This commit is contained in:
Fischlurch 2017-03-18 01:55:45 +01:00
parent c251f9c2a9
commit b865acf758
7 changed files with 242 additions and 14 deletions

View file

@ -39,8 +39,7 @@
#ifndef PROC_CMD_H
#define PROC_CMD_H
#include "lib/error.hpp"
#include "proc/control/command.hpp"
#include "proc/control/command-setup.hpp"
//#include "lib/symbol.hpp"
//#include "proc/common.hpp"

View file

@ -1,5 +1,5 @@
/*
COMMAND-INSTANCE-MANAGER.hpp - Key abstraction for proc/edit operations and UNDO management
COMMAND-INSTANCE-MANAGER.hpp - Service to manage command instances for actual invocation
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
@ -38,8 +38,10 @@
** about to happen, a corresponding registration handle is transfered to the ProcDispatcher, where
** it is enqueued for execution.
**
** @see command-def.hpp
** @see command-setup.cpp service implementation
** @see command.hpp
** @see command-def.hpp
** @see command-setup.hpp
** @see command-accessor.hpp
** @see TODO_CommandInstanceManager_test
**

View file

@ -1,5 +1,5 @@
/*
CommandInstanceManager - Key abstraction for proc/edit operations and UNDO management
CommandSetup - Implementation of command registration and instance management
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
@ -21,12 +21,14 @@
* *****************************************************/
/** @file command-instance-manager.cpp
/** @file command-setup.cpp
** Implementation details of instance management for command invocation by the GUI.
**
** @see command-setup.hpp
** @see command-instance-manager.hpp
** @see TODO_CommandInstanceManager_test
** @see command.hpp
** @see command-registry.hpp
** @see TODO_CommandInstanceManager_test
**
*/
@ -60,6 +62,7 @@ namespace control {
// emit dtors of embedded objects here....
CommandInstanceManager::~CommandInstanceManager() { }
CommandSetup::~CommandSetup() { }
CommandInstanceManager::CommandInstanceManager() { }

View file

@ -0,0 +1,100 @@
/*
COMMAND-INSTANCE-SETUP.hpp - Key abstraction for proc/edit operations and UNDO management
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 command-setup.hpp
** Provision for setup of concrete commands for use by the UI.
** A *Proc-Layer command* is a functor, which can be parametrised with actual arguments.
** It needs to be [defined](command-def.hpp) beforehand, which means to establish an unique
** name and to supply three functions, one for the actual command operation, one to capture
** state and one to _undo_ the effect of the command invocation. CommandSetup allows to create
** series of such definitions with minimal effort. Since any access and mutation from the UI into
** the Session data must be performed by invoking such commands, a huge amount of individual command
** definitions need to be written eventually.
**
** The macro COMMAND_DEFINITION(name) allows to introduce a new definition with a single line,
** followed by a code block, which actually ends up as the body of a lambda function, and receives
** the bare CommandDef as single argument with name `cmd`. The `name` argument of the macro ends up
** both stringified as the value of the command-ID, and as an identifier holding a new CommandSetup
** instance. It is assumed that a header with corresponding _declarations_ (the header \ref cmd.hpp)
** is included by all UI elements actually to use, handle and invoke commands towards the
** session-command-facade.h
**
** @todo WIP-WIP 3/2017 initial draft
**
** @see command-def.hpp
** @see command.hpp
** @see command-accessor.hpp
** @see TODO_CommandSetup_test
**
*/
#ifndef CONTROL_COMMAND_SETUP_H
#define CONTROL_COMMAND_SETUP_H
#include "lib/error.hpp"
#include "proc/control/command.hpp"
//#include "lib/symbol.hpp"
//#include "proc/common.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
namespace proc {
namespace control {
// using std::string;
// using lib::Symbol;
//using std::shared_ptr;
/**
* @todo write type comment
*/
class CommandSetup
: boost::noncopyable
{
public:
~CommandSetup();
private:
};
/** */
}} // namespace proc::control
#endif /*CONTROL_COMMAND_SETUP_H*/

View file

@ -68,7 +68,7 @@
** while some producer threads are still alive -- because in this case the main thread might
** verify the checksum before all command instances have been triggered. To avoid this
** situation, make sure the delay between actions in the threads is not too long and
** start a sufficiently high nubmer of producer threads.
** start a sufficiently high number of producer threads.
**
*/

View file

@ -1472,7 +1472,7 @@ Commands are //defined// using a [[fluent API|http://en.wikipedia.org/wiki/Fluen
&amp;rarr; see CommandUsage
</pre>
</div>
<div title="CommandDefinition" modifier="Ichthyostega" created="200906140124" modified="201703171930" tags="SessionLogic spec draft decision design" changecount="2">
<div title="CommandDefinition" modifier="Ichthyostega" created="200906140124" modified="201703180036" tags="SessionLogic spec draft decision design" changecount="3">
<pre>Commands can be identified and accessed //by name// &amp;mdash; consequently there needs to be an internal command registry, including a link to the actual implementing function, thus allowing to re-establish the connection between command and implementing functions when de-serialising a persisted command. To create a command, we need to provide the following information
* operation function actually implementing the command
* function to [[undo|UndoManager]] the effect of the command
@ -1502,6 +1502,7 @@ Usually, parameters should be passed //by value// &amp;mdash; with the exception
!Actual command definition scripts
The actual scripts bound as functors into the aforementioned command definitions are located in translation units in {{{proc/cmd}}}
These definitions must be written in a way to ensure that just compiling those translation units causes registration of the corresponding command-~IDs
This is achieved by placing a series of CommandSetup helper instances into those command defining translation units.
</pre>
</div>
<div title="CommandHandling" modifier="Ichthyostega" created="200906072048" modified="201601162032" tags="SessionLogic spec draft decision design img" changecount="12">
@ -1604,6 +1605,15 @@ When a command has been executed (and maybe undone), it's best to leave it alone
State predicates are accessible through the Command (frontend); additionally there are static query functions in class {{{Command}}}
</pre>
</div>
<div title="CommandSetup" creator="Ichthyostega" modifier="Ichthyostega" created="201703180038" modified="201703180047" tags="SessionLogic spec draft impl" changecount="4">
<pre>//Helper facility to ease the creation of actual command definitions.//
A [[Proc-Layer command|CommandHandling]] is a functor, which can be parametrised with actual arguments. It needs to be [defined](command-def.hpp) beforehand, which means to establish an unique name and to supply three functions, one for the actual command operation, one to capture state and one to [[UNDO]] the effect of the command invocation.
The helper class {{{CommandSetup}}} allows to create series of such definitions with minimal effort. Since any access and mutation from the UI into the Session data must be performed by invoking such commands, a huge amount of individual command definitions need to be written eventually. These are organised into a series of implementation translation units with location {{{poc/cmd/*-cmd.cpp}}}.
Each of these files is specialised to defining a set of thematically related command, supplying the code for the actual command scripts. Each definition is introduced by a single line with the macro {{{COMMAND_DEFINITION(name)}}}, followed by a code block, which actually ends up as the body of a lambda function, and receives the bare [[CommandDef|CommandDefinition]] as single argument with name {{{cmd}}}. The {{{name}}} argument of the macro ends up both stringified as the value of the command-~ID, and as an identifier holding a new {{{CommandSetup}}} instance. It is assumed that a header with //corresponding declarations// (the header {{{cmd.hpp}}}) is included by all UI elements actually to use, handle and invoke commands towards the SessionSubsystem
</pre>
</div>
<div title="CommandUsage" modifier="Ichthyostega" created="200907212338" modified="201701090011" tags="SessionLogic draft operational" changecount="8">
<pre>//for now (7/09) I'll use this page to collect ideas how commands might be used...//
@ -2553,7 +2563,7 @@ In a typical editing application, the user can expect to get some visual clue re
To start with, mostly this means to avoid a naive approach, like having code in the UI to pull in some graphics from media files. We certainly won't just render every media channel blindly. Rather, we acknowledge that we'll have a //strategy,// depending on the media content and some further parameters of the clip. This might well just be a single ''pivot image'' chosen explicitly by the editor to represent a given take. And the actual implementation of content preview rendering will largely be postponed until we get our rendering engine into a roughly working state.
</pre>
</div>
<div title="GuiCommandBinding" creator="Ichthyostega" modifier="Ichthyostega" created="201511272246" modified="201703031818" tags="design decision GuiPattern GuiIntegration discuss" changecount="30">
<div title="GuiCommandBinding" creator="Ichthyostega" modifier="Ichthyostega" created="201511272246" modified="201703180038" tags="design decision GuiPattern GuiIntegration discuss" changecount="31">
<pre>The question //how to connect the notion of an ''interface action'' to the notion of a ''command'' issued towards the [[session model|HighLevelModel]].//
* actual design of command invocation in the UI &amp;rarr; GuiCommandCycle
* study of pivotal action invocation situations &amp;rarr; CommandInvocationAnalysis
@ -2591,9 +2601,10 @@ This contrastive approach attempts to keep knowledge and definition clustered in
''Lumera decides to take the latter apptoch'' -- resulting in a separation between immediate low-level UI element reactions, and anything of relevance for the behaviour of the UI. The widget code embodies the low-level UI element reactions and as such becomes more or less meaningless beyond local concerns of layout and presentation. If you want to find out about the behaviour of the UI, you need to know where to look, and you need to know how to read and understand those enablement rules. Another consequence is the build-up of dedicated yet rather abstract state tracking facilities, hooking like an octopus into various widgets and controllers, which might work counter to the intentions behind the design of common UI toolkit sets.
&amp;rarr; GuiCommandCycle
&amp;rarr; CommandSetup
</pre>
</div>
<div title="GuiCommandCycle" creator="Ichthyostega" modifier="Ichthyostega" created="201703031817" modified="201703171922" tags="design operational GuiPattern GuiIntegration draft discuss img" changecount="52">
<div title="GuiCommandCycle" creator="Ichthyostega" modifier="Ichthyostega" created="201703031817" modified="201703180039" tags="design operational GuiPattern GuiIntegration draft discuss img" changecount="53">
<pre>//the process of issuing a session command from the UI//
Within the Lumiera UI, we distinguish between core concerns and the //local mechanics of the UI.// The latter is addressed in the usual way, based on a variation of the [[MVC-Pattern|http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller]]. The UI toolkit set, here the GTK, affords ample ways to express actions and reactions within this framework, where widgets in the presentation view are wired with the corresponding controllers vice versa (GTK terms these connections as //&quot;signals&quot;//, we rely on {{{libSigC++}}} for implementation).
A naive approach would extend these mature mechanisms to also cover the actual functionality of the application. This compelling solution allows quickly to get &quot;something tangible&quot; up and running, yet -- on the long run -- inevitably leads to core concerns being tangled into the presentation layer, which in turn becomes hard to maintain and loaded with &quot;code behind&quot;. Since we are here &quot;for the long run&quot;, we immediately draw the distinction between UI mechanics and core concerns. The latter are, by decree and axiom, required to perform without even an UI layer running. This decision gives rise to the challenge how to form and integrate the invocation of ''core commands'' into the presentation layer.
@ -2641,6 +2652,7 @@ Command instances are like prototypes -- thus each additional level of different
&amp;rarr; Command scripts are defined in translation units in {{{proc/cmd}}}
&amp;rarr; They reside in the corresponding namespace, which is typically aliased as {{{cmd}}}
&amp;rarr; definitions and usage include the common header {{{proc/cmd.hpp}}}
see the description in &amp;rarr; CommandSetup
</pre>
</div>
<div title="GuiConnection" modifier="Ichthyostega" created="200812050543" modified="201703031816" tags="GuiIntegration overview" changecount="9">

View file

@ -11823,11 +11823,11 @@
<node CREATED="1489717960331" ID="ID_1849715968" MODIFIED="1489717991603" TEXT="viel wichtiger sind die Aktivierungs-Regeln"/>
</node>
</node>
<node CREATED="1489546998918" ID="ID_668687712" MODIFIED="1489547001785" TEXT="Aktivierung">
<node CREATED="1489546998918" HGAP="43" ID="ID_668687712" MODIFIED="1489781267877" TEXT="Aktivierung">
<node CREATED="1489547002918" ID="ID_728456926" MODIFIED="1489547015360" TEXT="Callback installieren"/>
<node CREATED="1489547016564" ID="ID_41325273" MODIFIED="1489547028606" TEXT="Anfrage: ist dies aktivierbar?"/>
</node>
<node CREATED="1489547086458" ID="ID_1858100202" MODIFIED="1489547091270" TEXT="Argumente">
<node CREATED="1489547086458" HGAP="48" ID="ID_1858100202" MODIFIED="1489781264701" TEXT="Argumente" VSHIFT="-1">
<node CREATED="1489547096857" ID="ID_184341736" MODIFIED="1489547103300" TEXT="&quot;gib mir die Argumente!&quot;">
<node CREATED="1489547170463" ID="ID_1981926549" MODIFIED="1489547178266" TEXT="wirklich?">
<icon BUILTIN="help"/>
@ -11876,7 +11876,7 @@
<node CREATED="1489547588102" ID="ID_985840704" MODIFIED="1489547597465" TEXT="wodurch die Argument-Typen dokumentiert sind"/>
</node>
</node>
<node CREATED="1489548252324" HGAP="78" ID="ID_1622068086" MODIFIED="1489719025991" TEXT="das k&#xf6;nnte der InvocationTrail sein" VSHIFT="20">
<node CREATED="1489548252324" HGAP="40" ID="ID_1622068086" MODIFIED="1489781254726" TEXT="das k&#xf6;nnte der InvocationTrail sein" VSHIFT="26">
<linktarget COLOR="#4d2a67" DESTINATION="ID_1622068086" ENDARROW="Default" ENDINCLINATION="-9;-36;" ID="Arrow_ID_717312728" SOURCE="ID_1063184971" STARTARROW="None" STARTINCLINATION="15;49;"/>
<icon BUILTIN="idea"/>
<node CREATED="1489719178053" ID="ID_1509284806" MODIFIED="1489719184696" TEXT="hat Cmd-ID + eigene ID"/>
@ -11894,6 +11894,118 @@
</node>
</node>
</node>
<node CREATED="1489781392694" ID="ID_1312397991" MODIFIED="1489781396114" TEXT="Festlegungen">
<node CREATED="1489781404925" ID="ID_1918933171" MODIFIED="1489781408881" TEXT="Command-ID">
<node CREATED="1489781409876" ID="ID_1973020417" MODIFIED="1489781419119" TEXT="Bais-ID ist ein string"/>
</node>
<node CREATED="1489781426978" ID="ID_1543445405" MODIFIED="1489781434517" TEXT="konkrete Definition">
<node CREATED="1489781443184" ID="ID_966382785" MODIFIED="1489781445387" TEXT="Ort">
<node CREATED="1489781447639" ID="ID_588776773" MODIFIED="1489781474176" TEXT="proc/cmd/*-cmd.cpp"/>
<node CREATED="1489781476355" ID="ID_1953562265" MODIFIED="1489781480998" TEXT="Namespace proc::cmd"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1489781611257" ID="ID_179887792" MODIFIED="1489781650026" TEXT="brauche">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1489781616257" ID="ID_1645536558" MODIFIED="1489781625283" TEXT="Schema f&#xfc;r ID-Konstanten"/>
<node CREATED="1489781628743" ID="ID_1733247268" MODIFIED="1489781635194" TEXT="Mechanismus, der die Definition treibt"/>
<node CREATED="1489781758389" ID="ID_1124650374" MODIFIED="1489781765521" TEXT="gew&#xfc;nscht">
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
<node CREATED="1489781775443" ID="ID_379002135" MODIFIED="1489785133742" TEXT="Name und Definition nahe zusammen"/>
<node CREATED="1489781769500" ID="ID_998243238" MODIFIED="1489781774456" TEXT="keine Redundanz"/>
<node CREATED="1489781902770" ID="ID_1568226956" MODIFIED="1489781907366" TEXT="gut lesbar"/>
<node CREATED="1489781907906" ID="ID_1206063892" MODIFIED="1489781915492" TEXT="debuggbar"/>
<node CREATED="1489781923640" ID="ID_241861600" MODIFIED="1489781930043" TEXT="kommentierbar (Doxygen)"/>
<node CREATED="1489785114574" ID="ID_241665479" MODIFIED="1489785123487" TEXT="nur unsichtbare Magie"/>
</node>
<node CREATED="1489785137083" ID="ID_1466955376" MODIFIED="1489785144694" TEXT="M&#xf6;glichkeiten">
<node CREATED="1489785163216" ID="ID_1992894903" MODIFIED="1489785168547" TEXT="zu Fu&#xdf;...">
<node CREATED="1489785199131" ID="ID_111396262" MODIFIED="1489785207662" TEXT="ID-Konstanten im Header deklarieren"/>
<node CREATED="1489785208298" ID="ID_315886293" MODIFIED="1489785222188" TEXT="ID-Konstanten in der jeweiligen translation-Unit definieren"/>
<node CREATED="1489785250500" ID="ID_464247309" MODIFIED="1489785259671" TEXT="Lifecycle-Hook installieren..."/>
<node CREATED="1489785260523" ID="ID_1306260885" MODIFIED="1489785270590" TEXT="..der eine Registrierungs-Funktion aufruft"/>
<node CREATED="1489785276137" ID="ID_1526475921" MODIFIED="1489785285307" TEXT="in dieser die CommandDef absetzen"/>
<node CREATED="1489785285872" ID="ID_1304462599" MODIFIED="1489785296938" TEXT="und als Command-ID die definierte Konstante verwenden"/>
</node>
<node CREATED="1489786040203" ID="ID_1417335273" MODIFIED="1489786045870" TEXT="Vereinfachungen">
<node CREATED="1489786055057" ID="ID_565306146" MODIFIED="1489786059852" TEXT="decl/def">
<node CREATED="1489786238273" ID="ID_1957761383" MODIFIED="1489786331600" TEXT="marker-Makro"/>
<node CREATED="1489786245024" ID="ID_430222749" MODIFIED="1489786255810" TEXT="alle Definitionen in Makros einwickeln"/>
<node CREATED="1489786393908" ID="ID_1345826013" MODIFIED="1489786660959" TEXT="spezielle Inclusion-Rituale">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
vermutlich l&#228;uft es immer darauf hinaus
</p>
<ul>
<li>
da&#223; cmd.hpp die Implementierungs-Einheiten includiert
</li>
<li>
oder da&#223; in einer ausgezeichneten Impl-Einheit das marker-Makro gesetzt wird und dann cmd.hpp includiert wird
</li>
</ul>
</body>
</html>
</richcontent>
</node>
</node>
<node CREATED="1489789880260" ID="ID_768121026" MODIFIED="1489789887822" TEXT="Template-Spezialisierung?">
<node CREATED="1489790030727" ID="ID_1780788892" MODIFIED="1489790046961" TEXT="k&#xf6;nnte const char* nehmen"/>
<node CREATED="1489790053044" ID="ID_1594786428" MODIFIED="1489790070534" TEXT="k&#xf6;nnte dann nach Literal konvertibel sein"/>
<node CREATED="1489790158335" ID="ID_1828195626" MODIFIED="1489790166017" TEXT="Template k&#xf6;nnte in Header definiert sein"/>
<node CREATED="1489790167669" ID="ID_156312862" MODIFIED="1489790182247" TEXT="und nur die explizite Spezialisierung generiert Code"/>
</node>
<node CREATED="1489790425251" ID="ID_1099709668" MODIFIED="1489790430606" TEXT="auto-Registrierung">
<node CREATED="1489790431810" ID="ID_1253214974" MODIFIED="1489790442380" TEXT="variable statisch initialisieren"/>
<node CREATED="1489790522958" ID="ID_1454213723" MODIFIED="1489790532345" TEXT="Lambda an Basis-Ctor geben"/>
</node>
<node CREATED="1489790555393" ID="ID_1281815871" MODIFIED="1489790574611" TEXT="DSL wie boost-test">
<node CREATED="1489790576831" ID="ID_84712572" MODIFIED="1489790584658" TEXT="definiert Klasse o.&#xe4;"/>
<node CREATED="1489790588749" ID="ID_1302974476" MODIFIED="1489790599656" TEXT="User schreibt direkt dahinter einen Codeblock"/>
<node CREATED="1489790689536" ID="ID_605065689" MODIFIED="1489790702050" TEXT="tats&#xe4;chlich wird das eine out-of-class definierte Methode"/>
<node CREATED="1489791695217" ID="ID_1529943414" MODIFIED="1489791705036" TEXT="...und kann sogar per Doxygen kommentiert werden"/>
</node>
<node CREATED="1489791741539" ID="ID_175521756" MODIFIED="1489791746686" TEXT="im Build-Proze&#xdf; parsen">
<node CREATED="1489791753074" ID="ID_1141751955" MODIFIED="1489791761893" TEXT="und z.B. den Header daraus generieren"/>
<node CREATED="1489791763313" ID="ID_1622970394" MODIFIED="1489791766588" TEXT="warum nicht...?"/>
<node CREATED="1489791777102" ID="ID_102161218" MODIFIED="1489791786937" TEXT="empfinde ich besser als komplex verschachtelte Makros"/>
</node>
</node>
</node>
<node CREATED="1489791729957" ID="ID_1986771449" MODIFIED="1489791734248" TEXT="kombinierte Magie">
<node CREATED="1489791798116" ID="ID_197500257" MODIFIED="1489791804727" TEXT="Definitions-Klasse">
<node CREATED="1489791805675" ID="ID_1517636242" MODIFIED="1489791817237" TEXT="ist konvertierbar nach Literal"/>
<node CREATED="1489791817873" ID="ID_361574240" MODIFIED="1489791838610" TEXT="nimmt Lambda als Konstruktor-Argument"/>
<node CREATED="1489791849245" ID="ID_262482724" MODIFIED="1489791860879" TEXT="bietet interne DSL"/>
<node CREATED="1489795277835" ID="ID_663997649" MODIFIED="1489795286417" TEXT="Name gesucht...?">
<icon BUILTIN="help"/>
<node CREATED="1489795288354" ID="ID_960791386" MODIFIED="1489795346577" TEXT="CommandInstance">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1489795292689" ID="ID_1059834523" MODIFIED="1489795346578" TEXT="CommandDefinition">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1489795318614" ID="ID_437740295" MODIFIED="1489795346578" TEXT="CommandName">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1489795321669" ID="ID_115829263" MODIFIED="1489795346579" TEXT="CommandItem">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1489795298832" ID="ID_1272194079" MODIFIED="1489795342763" TEXT="CommandSetup">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node CREATED="1489791864787" ID="ID_891631217" MODIFIED="1489791877373" TEXT="in den Impl-Einheiten eine Folge von Definitionen"/>
<node CREATED="1489791877913" ID="ID_387208356" MODIFIED="1489793604385" TEXT="zugh&#xf6;rigen Header proc/cmd.hpp im Build-Proze&#xdf; regenerieren">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>
</node>
</node>
</node>
<node CREATED="1488672593812" ID="ID_1454459609" MODIFIED="1488672598271" TEXT="konkret"/>