Copyright (c) Prolog Developemnt Center SPb

Application Frame. Control Panels (Ribbons)

AppFrame as the main body of the application control panel uses
ribbons mechanism included in the PFC and submitted package pfc\gui\controls\ribbonControl\ribbonControl.pack .

The ribbon configuration in AppFrame can be defined either directly in the code or as an XML structure.
The ribbon definition in the code can be seen in the example included with Visual Prolog (ribbonDemo).
The rules for representing a ribbon as an XML structure are described in the Application Frame.RibbonScript document .
In the example SpbExamples\Febe, you can see examples of the definition of panels and the technique of their usage.
The definition of the panel in the form of an XML structure is not only an alternative to the definition in the code, but also improves the adaptability of the application to the immediate needs of the user.

How it works

The script panel is an xml file external to the AppFrame code, however its contents are interpreted by the AppFrame code.
Part of the xml representation are static definitions such as label , id.
Part of the xml representation defines dynamic parameters, such as the render-menu or a function called to define tooltip text.
Dynamic parameters in AppFrame ribbon script are represented by text function names. These text function names in the AppFrame code correspond to predicates by defining a text-name-predicate pair. Implementation details of the use of these pairs depend on the type of dynamic parameter in each case.

Ribbonloader

SpbVipTools\Packs\Gui\ribbonLoader\ribbonLoader.pack  and
SpbVipTools\Packs\ Gui\ribbonLoader\cmdPerformer\cmdPerformer.pack

are responsible for reading the ribbon script file . Before loading the ribbon, an object of class cmdPerformer is created.

        ContextObj = cmdPerformers :: new ()

The
execution context of the loading procedure is transferred to ribbonLoader as setting the properties of the object ContextObj.

Property declarationxml-nodeDescription
useDictionary_P:booleandefines whether dictionary must be used
dictionary_P:dictionarydefines object of the class fe_Dictionary
noIconRender_P:function{binary}menudefines function, which must return the binary content of the icon, if no IconRender found
setRunner:(string RunnerID,predicate{command})cmddefines predicate, which must be invoked, when the command button with the RunnerID is pressed
setChangeStateHandler:(string StateHandlerID,predicate{command})cmddefines predicate, which must be invoked, when the command state is changed
setTooltipRender:(string TooltipRenderID, function{string})tooltipdefines function, which must return the tooltip text
setMenuRender:(string MenuRenderID, function{menuCommand::menuItem*})menudefines function, which must return the list of menuItems
setIconByIDRender:(string IconRenderID,function{string IconKey, binary})icondefines function, which must return the binary content of the icon with the given IconKey
setCustomFactory:(string CustomFactoryID,
function{string ControlID, function{control}})
customdefines function, which must create and return the control Object for the given ControlID

An example of the installation of such functions is below:
    setCommonPerformers(ContextObj):-
            ContextObj:useDictionary_P:=true,
            ContextObj:dictionary_P:=fe_Dictionary(),
            ContextObj:setRunner("test",fe_Tests():runTest),
            ContextObj:setRunner("pzl-extension",fe_CoreTasks():runPlugin),
            ContextObj:setCustomFactory("test-factory",fe_Tests():controlFactory),
            ContextObj:setMenuRender("language-list",languageList_Menu),
            ContextObj:noIconRender_P:=noIconRender,
            ContextObj:setIconByIDRender("default",fe_CoreTasks():getIconByID).

Then 
ribbonLoader is called
       ...
    RibbonLoader=ribbonLoader::new(...),
    RibbonLoader:loadXmlData(XmlBinaryData),
    Layout=RibbonLoader:getLayout(ContextObj),
     ...


The loadXmlData(XmlBinaryData) function accepts the xml representation of the ribbon as bynary and getLayout(ContextObj) returns the Layout corresponding to the domain

domains

    layout  =  section*
.

In the process of conversion, all commands and menus are created, and layout determines only their location on the ribbon.

The use of dictionaries

The xml presentation of the ribbon contains an indication of nameSpace and a dictionary file. When creating a ribbon, RibbonLoader refers to an object of class fe_Dictionary and reports the name nameSpace and the name of the dictionary file.
If this nameSpace absent in fe_Dictionary , the fe_Dictionary requests through fe_CoreTasks Dictionary at BackEnd as described in the document DictionariesSupport.html .
If the dictionary is not found, then ribbonLoader sets nameSpace "NoNameSpace" for this ribbon fragment.

In the xml description the tooltip and menuLabel  do not have identifiers. To search for appropriate dictionary forms for these entities, the rule is applied - adding the suffix ".tooltip" and ".menulabel" to the cmd or menu identifier of these entities. Respectively, in the dictionaries these suffixes should be added to the keys of the corresponding word forms.
The key for text phrases of cmd and menu entities has one more feature - this key is an entity identifier. An entity identifier is formed by adding the prefix "<nameSpace>\" to the declared entity identifier. This allows you to mix elements from different nameSpace on one ribbon.

For example, if
nameSpace in the xml view is declared as "basic" , and the command identifier ( cmd ) in the xml view is declared as "file.open" then the panel command will have the identifier  "basic\file.open" .

Options for creating panels in Application Frame

The AppFrame core has three options for loading the panel:
The default option represents loading the panel represented by the package code Common\AppFrontEnd\fe_Ribbons\fe_RibbonDefault\fe_RibbonDefault.pack and is not related to the xml representation of the ribbon.
The loadable option represents loading the ribbon represented by any xml file specified in the AppFrame configuration. The download context is set by the package  Common\AppFrontEnd\fe_Ribbons\fe_RibbonLoadable\fe_RibbonLoadable.pack .
The embedded option represents loading the ribbon represented by any xml file included as an embeddedRibbonXml_C constant with the #bininclude directive in the fileCommon \ AppBackEnd \ be_Options \ be_Options.cl . The download context is set by the package  Common\AppFrontEnd\fe_Ribbons\fe_RibbonEmbedded\fe_RibbonEmbedded.pack .
The embedded option allows you to hide the ribbon description in operating conditions.

In the loadable and embedded variants, FrontEnd requests the contents of the xml view of the ribbon from BackEnd.

The method of loading the ribbon is reflected in the xml application configuration file  bin\<projectName>AppData\Options.xml.

Ribbon Extension

When creating the ribbonControl, as a container, it contains a set of elements whose location is determined by the layout structure.
Therefore, when the user interface is running (state isShown = true), you can add new elements and supplement the existing layout structure with new sections by concatenating lists.

domains

    layout  =  section *.


The AppFrame core provides for the possibility of expanding the working ribbon by loading in run-time mode new sections of the panel represented by xml files.
Panel extension is possible for any of the options for initial loading the panel (default,loadable,embedded).
Ribbon xml files should be located in the bin\<projectName> AppData directory .
Adding a panel is done by calling the addExtension()  predicate of the object of the class fe_CoreTasks.
The 
addExtension () predicate queries BackEnd for all ribbon xml files stored in the bin\<projectName>AppData directory and offers a dialog with a list of these files.



When you select a ribbon file, an object of the ribbonLoader class loads a new fragment of the ribbon and places it at the end of the panel. If necessary, the dictionary for this file is activated (if any). Ribbon extensions are saved in the configuration file. An example of an extension storage structure is given below.

      <ribbon startup="default">
        <loadable script="febeAppData\ribbon_Basic.xml">
          <ext>.\febeAppData\ribbonFile.xml</ext>
          <ext>.\febeAppData\ribbonLanguageRender.xml</ext>
          <ext>.\febeAppData\ribbonTests.xml</ext>
        </loadable>
        <embedded />
        <default>
          <ext>.\febeAppData\ribbonTests.xml</ext>
          <ext>.\febeAppData\ribbonLanguageRender.xml</ext>
          <ext>.\febeAppData\ribbonPlugins.xml</ext>
        </default>
      </ribbon>

The <embedded/> node for the bootstrap version of the same name has no extensions in this example .
All ribbon elements, including newly added fragments, can be moved in the Design dialog (a tool that is part of the ribbonControl package of the Visual Prolog system ).
AppFrame saves the new panel location in the configuration file bin\<projectName>AppData and restores it when the application starts.
To remove or add extension panels or change the start panel for the loadable option , simply edit the configuration file bin\<projectName> AppData\Options.xml with a text editor.
In this case, the previous arrangement of panel elements may be lost, and a new editing of the panel arrangement may be required.

Panel loading procedure

When you create the main application window before the processing the predicate show() predicate initContent (...) creates an object of class fe_Command and initiates panel

clauses
    initContent ( the FrontEnd ): -
        generatedInitialize ()
        ...
        fe_Command_P : = fe_Command :: new ( This , FrontEnd ),
        ...
        fe_Command_P : initRibbon ( false ), <- false -to restore the last state of the panel
        ...

In the object of the fe_Command class, if BackEnd is available, depending on the boot option, the predicate initRibbon (...): If it is found out that a dictionary should be applied to the panel, then all the text headers of the panel are adjusted taking into account the existing dictionaries by the predicate
updateRibbonLabels () .

Panel Command Processing

For the default boot option, the reactions to button clicks should be written in the fe_RibbonStartupDefault class , following the example ribbonDemo.
For loadable and embedded options, it is recommended that event handling from control elements be written in classes, respectively, RibbonStartupLoadable and RibbonStartupEmbedded.
For fragments that expand the panel, event processing can be placed in any convenient place at the discretion of the developer.

The standard response to pressing control buttons is defined by the ribbonControl package and has the form
predicates
    commandHandler :( command RibbonCommand ).

Given that the RibbonCommand object contains the command identifier as the Id property , the prefix of which is <nameSpace>\ , it is proposed to select the initial command identifier (presented in both the script panel declaration and script dictionary declarations as shown below

predicates

    commandHandler :( command  RibbonCommand ).
clauses
    commandHandler( RibbonCommand ): -
        string :: frontToken ( RibbonCommand : id , NameSpace , CommandIDWithSlash ),
        string :: frontChar ( CommandIDWithSlash , _ , CommandID ),
        nextLevel CommandHandler ( CommandID , NameSpace , RibbonCommand : id ),
        !.
    commandHandler( _RibbonCommand ).

predicates
    nextLevel CommandHandler:( string  CommandID , string  NameSpace , string SourceRibbonCommand )  multi .
clauses
    nextLevel CommandHandler( "ribbon.cmd.design-ribbon" , _NameSpace , _
 Source RibbonCommand ): -
        designRibbonLayout ().
    nextLevel CommandHandler( "ribbon.cmd.restore-ribbon-layout" , _NameSpace , _
 Source RibbonCommand ): -
        tryBackEndAvailable (),
    ...