wizards/Package_scriptforge.mk                   |    4 
 wizards/source/scriptforge/SF_FileSystem.xba     |   17 
 wizards/source/scriptforge/SF_PythonHelper.xba   |  752 +++++++++++++++++++++++
 wizards/source/scriptforge/SF_Root.xba           |   49 +
 wizards/source/scriptforge/SF_Session.xba        |    2 
 wizards/source/scriptforge/SF_Timer.xba          |    4 
 wizards/source/scriptforge/po/ScriptForge.pot    |    6 
 wizards/source/scriptforge/po/en.po              |    6 
 wizards/source/scriptforge/python/scriptforge.py |  676 ++++++++++++++++++++
 wizards/source/scriptforge/script.xlb            |    1 
 10 files changed, 1498 insertions(+), 19 deletions(-)

New commits:
commit f66a479225b35e9f1fd3621eef7496462088308a
Author:     Jean-Pierre Ledure <[email protected]>
AuthorDate: Thu Mar 4 17:19:27 2021 +0100
Commit:     Jean-Pierre Ledure <[email protected]>
CommitDate: Fri Mar 5 10:26:51 2021 +0100

    ScriptForge - (scriptforge.py) Python-Basic machinery
    
    Python scripts can now invoke usual Basic builtin functions
    Example:
        from ScriptForge import CreateScriptService
        bas = CreateScriptService('Basic')
        bas.MsgBox('This is the text to be displayed', bas.MB_ICONEXCLAMATION)
    
    Python scripts can use most ScriptForge services written in Basic
    Example:
        from scriptforge import CreateScriptService
        FSO = CreateScriptService('FileSystem')
        a = FSO.BuildPath('/tmp', 'xyz')
    Syntax and semantic are as close as possible to the Basic syntax
    
    Implemented are a SFServices class and its subclasses representing each
    a ScriptForge service and where the interfaces are defined (properties,
    methods and arguments)
    
    Their execution goes through the "machinery", i.e. a set of python
    and basic routines that manage the call from the python process to
    the appropriate service in an as much agnostic and generic way
    
    Only a limited set of services are implemented so far: SF_FileSystem
    (partially) and SF_Timer: they served as prototypes and initial tests
    
    Change-Id: I0b383b59359c12710e7165139e498cca5a7856bb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111971
    Tested-by: Jean-Pierre Ledure <[email protected]>
    Tested-by: Jenkins
    Reviewed-by: Jean-Pierre Ledure <[email protected]>

diff --git a/wizards/Package_scriptforge.mk b/wizards/Package_scriptforge.mk
index 81be6041fd84..13742b29b250 100644
--- a/wizards/Package_scriptforge.mk
+++ b/wizards/Package_scriptforge.mk
@@ -26,6 +26,7 @@ $(eval $(call 
gb_Package_add_files,wizards_basicsrvscriptforge,$(LIBO_SHARE_FOLD
        SF_FileSystem.xba \
        SF_L10N.xba \
        SF_Platform.xba \
+       SF_PythonHelper.xba \
        SF_Root.xba \
        SF_Services.xba \
        SF_Session.xba \
@@ -52,4 +53,7 @@ $(eval $(call 
gb_Package_add_files,wizards_basicsrvscriptforge,$(LIBO_SHARE_FOLD
        po/en.po \
 ))
 
+$(eval $(call 
gb_Package_add_files,wizards_basicsrvscriptforge,$(LIBO_LIB_PYUNO_FOLDER),\
+       python/scriptforge.py \
+))
 # vim: set noet sw=4 ts=4:
diff --git a/wizards/source/scriptforge/SF_FileSystem.xba 
b/wizards/source/scriptforge/SF_FileSystem.xba
index 7fd5e8fff562..f626eba6fd92 100644
--- a/wizards/source/scriptforge/SF_FileSystem.xba
+++ b/wizards/source/scriptforge/SF_FileSystem.xba
@@ -1091,14 +1091,14 @@ Check:
 
 Try:
        Select Case UCase(PropertyName)
-               Case &quot;ConfigFolder&quot;                   :       
GetProperty = ConfigFolder
-               Case &quot;ExtensionsFolder&quot;               :       
GetProperty = ExtensionsFolder
-               Case &quot;FileNaming&quot;                     :       
GetProperty = FileNaming
-               Case &quot;HomeFolder&quot;                     :       
GetProperty = HomeFolder
-               Case &quot;InstallFolder&quot;          :       GetProperty = 
InstallFolder
-               Case &quot;TemplatesFolder&quot;                :       
GetProperty = TemplatesFolder
-               Case &quot;TemporaryFolder&quot;                :       
GetProperty = TemporaryFolder
-               Case &quot;UserTemplatesFolder&quot;    :       GetProperty = 
UserTemplatesFolder
+               Case UCase(&quot;ConfigFolder&quot;)                    :       
GetProperty = ConfigFolder
+               Case UCase(&quot;ExtensionsFolder&quot;)                :       
GetProperty = ExtensionsFolder
+               Case UCase(&quot;FileNaming&quot;)                      :       
GetProperty = FileNaming
+               Case UCase(&quot;HomeFolder&quot;)                      :       
GetProperty = HomeFolder
+               Case UCase(&quot;InstallFolder&quot;)                   :       
GetProperty = InstallFolder
+               Case UCase(&quot;TemplatesFolder&quot;)         :       
GetProperty = TemplatesFolder
+               Case UCase(&quot;TemporaryFolder&quot;)         :       
GetProperty = TemporaryFolder
+               Case UCase(&quot;UserTemplatesFolder&quot;)     :       
GetProperty = UserTemplatesFolder
                Case Else
        End Select
 
@@ -1611,6 +1611,7 @@ Check:
 
 Try:
        Select Case UCase(PropertyName)
+               Case UCase(&quot;FileNaming&quot;)              :       
FileNaming = Value      
                Case Else
        End Select
 
diff --git a/wizards/source/scriptforge/SF_PythonHelper.xba 
b/wizards/source/scriptforge/SF_PythonHelper.xba
new file mode 100644
index 000000000000..443a75d4afdb
--- /dev/null
+++ b/wizards/source/scriptforge/SF_PythonHelper.xba
@@ -0,0 +1,752 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" 
"module.dtd">
+<script:module xmlns:script="http://openoffice.org/2000/script"; 
script:name="SF_PythonHelper" script:language="StarBasic" 
script:moduleType="normal">REM 
=======================================================================================================================
+REM ===                        The ScriptForge library and its associated 
libraries are part of the LibreOffice project.                               ===
+REM ===                                        Full documentation is available 
on https://help.libreoffice.org/                                                
                ===
+REM 
=======================================================================================================================
+
+Option Compatible
+Option Explicit
+
+&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
+&apos;&apos;&apos;     SF_PythonHelper (aka Basic)
+&apos;&apos;&apos;     ===============
+&apos;&apos;&apos;             Singleton class implementing the 
&quot;ScriptForge.Basic&quot; service
+&apos;&apos;&apos;             Implemented as a usual Basic module
+&apos;&apos;&apos;
+&apos;&apos;&apos;             The &quot;Basic&quot; service must be called 
ONLY from a PYTHON script
+&apos;&apos;&apos;             Service invocations: Next Python code lines are 
equivalent:
+&apos;&apos;&apos;                     bas = 
CreateScriptService(&apos;ScriptForge.Basic&apos;)
+&apos;&apos;&apos;                     bas = 
CreateScriptService(&apos;Basic&apos;)
+&apos;&apos;&apos;
+&apos;&apos;&apos;             This service proposes a collection of methods 
to be executed in a Python context
+&apos;&apos;&apos;             to simulate the exact behaviour of the 
identical Basic buitin method.
+&apos;&apos;&apos;             Typical example:
+&apos;&apos;&apos;                     bas.MsgBox(&apos;This has to be 
displayed in a message box&apos;)
+&apos;&apos;&apos;
+&apos;&apos;&apos;             The service includes also an agnostic 
&quot;Python Dispatcher&quot; function.
+&apos;&apos;&apos;             It dispatches Python script requests to execute 
Basic services to the
+&apos;&apos;&apos;             appropriate properties and methods via dynamic 
call techniques
+&apos;&apos;&apos;
+&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
+
+REM ================================================================== 
EXCEPTIONS
+
+REM ============================================================ MODULE 
CONSTANTS
+
+REM ===================================================== 
CONSTRUCTOR/DESTRUCTOR
+
+REM 
-----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+       Set Dispose = Nothing
+End Function   &apos;  ScriptForge.SF_PythonHelper Explicit destructor
+
+REM ================================================================== 
PROPERTIES
+
+REM 
-----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos;     Only to enable object representation
+       ObjectType = &quot;SF_PythonHelper&quot;
+End Property   &apos;  ScriptForge.SF_PythonHelper.ObjectType
+
+REM 
-----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos;     Internal use
+       ServiceName = &quot;ScriptForge.Basic&quot;
+End Property   &apos;  ScriptForge.SF_PythonHelper.ServiceName
+
+REM ============================================================== PUBLIC 
METHODS
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyConvertFromUrl(ByVal FileName As Variant) As String
+&apos;&apos;&apos;     Convenient function to replicate ConvertFromUrl() in 
Python scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             FileName: a string representing a file in URL 
format
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The same file name in native operating system 
notation
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = 
bas.ConvertFromUrl(&apos;file:////boot.sys&apos;)
+
+Dim sFileName As String                        &apos;  Return value
+Const cstThisSub = &quot;Basic.ConvertFromUrl&quot;
+Const cstSubArgs = &quot;filename&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       sFileName = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       sFileName = ConvertFromUrl(FileName)
+
+Finally:
+       PyConvertFromUrl = sFileName
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyConvertFromUrl
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyConvertToUrl(ByVal FileName As Variant) As String
+&apos;&apos;&apos;     Convenient function to replicate ConvertToUrl() in 
Python scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             FileName: a string representing a file in 
native operating system notation
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The same file name in URL format
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = bas.ConvertToUrl(&apos;C:\boot.sys&apos;)
+
+Dim sFileName As String                        &apos;  Return value
+Const cstThisSub = &quot;Basic.ConvertToUrl&quot;
+Const cstSubArgs = &quot;filename&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       sFileName = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       sFileName = ConvertToUrl(FileName)
+
+Finally:
+       PyConvertToUrl = sFileName
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyConvertToUrl
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyCreateUnoService(ByVal UnoService As Variant) As Variant
+&apos;&apos;&apos;     Convenient function to replicate CreateUnoService() in 
Python scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             UnoService: a string representing the service 
to create
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             A UNO object
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = 
bas.CreateUnoService(&apos;com.sun.star.i18n.CharacterClassification&apos;)
+
+Dim vUno As Variant                            &apos;  Return value
+Const cstThisSub = &quot;Basic.CreateUnoService&quot;
+Const cstSubArgs = &quot;unoservice&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       Set vUno = Nothing
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       Set vUno = CreateUnoService(UnoService)
+
+Finally:
+       Set PyCreateUnoService = vUno
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyCreateUnoService
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyDateAdd(ByVal Add As Variant _
+                                                       , ByVal Count As 
Variant _
+                                                       , ByVal DateArg As 
Variant _
+                                                       ) As Variant
+&apos;&apos;&apos;     Convenient function to replicate DateAdd() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Add: The unit to add
+&apos;&apos;&apos;             Count: how many times to add (might be negative)
+&apos;&apos;&apos;             DateArg: a date as a string in iso format
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The new date date as a string in iso format
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = bas.DateAdd(&apos;d&apos;, 1, bas.Now())    
        &apos;  Tomorrow
+
+Dim vNewDate As Variant                                &apos;  Return value
+Dim vDate As Date                                      &apos;  Alias of DateArg
+Const cstThisSub = &quot;Basic.DateAdd&quot;
+Const cstSubArgs = &quot;add, count, datearg&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       vNewDate = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       vDate = SF_Utils._CStrToDate(DateArg)
+       vNewDate = DateAdd(Add, Count, vDate)
+
+Finally:
+       If VarType(vNewDate) = V_DATE Then PyDateAdd = 
SF_Utils._CDateToIso(vNewDate) Else PyDateAdd = vNewDate
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyDateAdd
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyDateDiff(ByVal Add As Variant _
+                                                       , ByVal Date1 As 
Variant _
+                                                       , ByVal Date2 As 
Variant _
+                                                       , ByVal WeekStart As 
Variant _
+                                                       , ByVal YearStart As 
Variant _
+                                                       ) As Long
+&apos;&apos;&apos;     Convenient function to replicate DateDiff() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Add: The unit of the date interval
+&apos;&apos;&apos;             Date1, Date2: the two dates to be compared
+&apos;&apos;&apos;             WeekStart: the starting day of a week
+&apos;&apos;&apos;             YearStart: the starting week of a year
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The number of intervals expressed in Adds
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = bas.DateDiff(&apos;d&apos;, 
bas.DateAdd(&apos;d&apos;, 1, bas.Now()), bas.Now())            &apos;  -1 day
+
+Dim lDiff As Long                                      &apos;  Return value
+Dim vDate1 As Date                                     &apos;  Alias of Date1
+Dim vDate2 As Date                                     &apos;  Alias of Date2
+Const cstThisSub = &quot;Basic.DateDiff&quot;
+Const cstSubArgs = &quot;add, date1, date2, [weekstart=1], [yearstart=1]&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       lDiff = 0
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       vDate1 = SF_Utils._CStrToDate(Date1)
+       vDate2 = SF_Utils._CStrToDate(Date2)
+       lDiff = DateDiff(Add, vDate1, vDate2, WeekStart, YearStart)
+
+
+Finally:
+       PyDateDiff = lDiff
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyDateDiff
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyDatePart(ByVal Add As Variant _
+                                                       , ByVal DateArg As 
Variant _
+                                                       , ByVal WeekStart As 
Variant _
+                                                       , ByVal YearStart As 
Variant _
+                                                       ) As Long
+&apos;&apos;&apos;     Convenient function to replicate DatePart() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Add: The unit of the date interval
+&apos;&apos;&apos;             DateArg: The date from which to extract a part
+&apos;&apos;&apos;             WeekStart: the starting day of a week
+&apos;&apos;&apos;             YearStart: the starting week of a year
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The specified part of the date
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = bas.DatePart(&apos;y&apos;, bas.Now())      
        &apos;  day of year
+
+Dim lPart As Long                                      &apos;  Return value
+Dim vDate As Date                                      &apos;  Alias of DateArg
+Const cstThisSub = &quot;Basic.DatePart&quot;
+Const cstSubArgs = &quot;add, datearg, [weekstart=1], [yearstart=1]&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       lPart = 0
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       vDate = SF_Utils._CStrToDate(DateArg)
+       lPart = DatePart(Add, vDate, WeekStart, YearStart)
+
+
+Finally:
+       PyDatePart = lPart
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyDatePart
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyDateValue(ByVal DateArg As Variant) As Variant
+&apos;&apos;&apos;     Convenient function to replicate DateValue() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             DateArg: a date as a string
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The converted date as a string in iso format
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;             a = bas.DateValue(&apos;2021-02-18&apos;)
+
+Dim vDate As Variant                           &apos;  Return value
+Const cstThisSub = &quot;Basic.DateValue&quot;
+Const cstSubArgs = &quot;datearg&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       vDate = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       vDate = DateValue(DateArg)
+
+Finally:
+       If VarType(vDate) = V_DATE Then PyDateValue = 
SF_Utils._CDateToIso(vDate) Else PyDateValue = vDate
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyDateValue
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyFormat(ByVal Value As Variant _
+                                                       , ByVal Pattern As 
Variant _
+                                                       ) As String
+&apos;&apos;&apos;     Convenient function to replicate Format() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Value: a date or a number
+&apos;&apos;&apos;             Pattern: the format to apply
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The formatted value
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         MsgBox bas.Format(6328.2, &apos;##,##0.00&apos;)
+
+Dim sFormat As String                                  &apos;  Return value
+Dim vValue As Variant                          &apos;  Alias of Value
+Const cstThisSub = &quot;Basic.Format&quot;
+Const cstSubArgs = &quot;value, oattern&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       sFormat = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       If VarType(Value) = V_DATE Then vValue = SF_Utils._CStrToDate(Value) 
ELse vValue = Value
+       If IsEmpty(Pattern) Or Len(Pattern) = 0 Then sFormat = Str(vValue) Else 
sFormat = Format(vValue, Pattern)
+
+
+Finally:
+       PyFormat = sFormat
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyFormat
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyGetGuiType() As Integer
+&apos;&apos;&apos;     Convenient function to replicate GetGuiType() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The GetGuiType value
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         MsgBox bas.GetGuiType()
+
+Const cstThisSub = &quot;Basic.GetGuiType&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       PyGetGuiType = GetGuiType()
+
+
+Finally:
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyGetGuiType
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyGetSystemTicks() As Long
+&apos;&apos;&apos;     Convenient function to replicate GetSystemTicks() in 
Python scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The GetSystemTicks value
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         MsgBox bas.GetSystemTicks()
+
+Const cstThisSub = &quot;Basic.GetSystemTicks&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       PyGetSystemTicks = GetSystemTicks()
+
+
+Finally:
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyGetSystemTicks
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyGlobalScope(ByVal Library As Variant) As Object
+&apos;&apos;&apos;     Convenient function to replicate GlobalScope() in 
Python scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Library: &quot;Basic&quot; or &quot;Dialog&quot;
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The GlobalScope value
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         MsgBox bas.GlobalScope.BasicLibraries()
+
+Const cstThisSub = &quot;Basic.GlobalScope.BasicLibraries&quot;        &apos;  
or DialogLibraries
+Const cstSubArgs = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       Select Case Library
+               Case &quot;Basic&quot;
+                       PyGlobalScope = GlobalScope.BasicLibraries()
+               Case &quot;Dialog&quot;
+                       PyGlobalScope = GlobalScope.DialogLibraries()
+               Case Else
+       End Select
+
+Finally:
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyGlobalScope
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyInputBox(ByVal Msg As Variant _
+                                                               , ByVal Title 
As Variant _
+                                                               , ByVal Default 
As Variant _
+                                                               , Optional 
ByVal XPos As Variant _
+                                                               , Optional 
ByVal YPos As Variant _
+                                                               ) As String
+&apos;&apos;&apos;     Convenient function to replicate InputBox() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Msg: String expression displayed as the message 
in the dialog box
+&apos;&apos;&apos;             Title: String expression displayed in the title 
bar of the dialog box
+&apos;&apos;&apos;             Default: String expression displayed in the 
text box as default if no other input is given
+&apos;&apos;&apos;             XPos: Integer expression that specifies the 
horizontal position of the dialog
+&apos;&apos;&apos;             YPos: Integer expression that specifies the 
vertical position of the dialog
+&apos;&apos;&apos;                     If XPos and YPos are omitted, the 
dialog is centered on the screen
+&apos;&apos;&apos;                     The position is specified in twips.
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The entered value or &quot;&quot; if the user 
pressed the Cancel button
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         a = bas.InputBox (&apos;Please enter a 
phrase:&apos;, &apos;Dear User&apos;)
+
+Dim sInput As String           &apos;  Return value
+Const cstThisSub = &quot;Basic.InputBox&quot;
+Const cstSubArgs = &quot;msg, [title=&apos;&apos;], [default=&apos;&apos;], 
[xpos], [ypos]&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       sInput = &quot;&quot;
+
+Check:
+       If IsMissing(YPos) Then YPos = 1
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       If IsMissing(XPos) Then
+               sInput = InputBox(Msg, Title, Default)
+       Else
+               sInput = InputBox(Msg, Title, Default, XPos, YPos)
+       End If
+
+Finally:
+       PyInputBox = sInput
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyInputBox
+
+REM 
-----------------------------------------------------------------------------
+Public Function PyMsgBox(ByVal Text As Variant _
+                                                               , ByVal 
DialogType As Variant _
+                                                               , ByVal 
DialogTitle As Variant _
+                                                               ) As Integer
+&apos;&apos;&apos;     Convenient function to replicate MsgBox() in Python 
scripts
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             Text: String expression displayed as a message 
in the dialog box
+&apos;&apos;&apos;             DialogType: Any integer expression that defines 
the number and type of buttons or icons displayed
+&apos;&apos;&apos;             DialogTitle: String expression displayed in the 
title bar of the dialog
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             The pressed button
+&apos;&apos;&apos;     Example: (Python code)
+&apos;&apos;&apos;         a = bas.MsgBox (&apos;Please press a button:&apos;, 
bas.MB_EXCLAMATION, &apos;Dear User&apos;)
+
+Dim sMsg As String             &apos;  Return value
+Const cstThisSub = &quot;Basic.MsgBox&quot;
+Const cstSubArgs = &quot;text, [dialogtype=0], [dialogtitle]&quot;
+
+       If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       sMsg = &quot;&quot;
+
+Check:
+       SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+       sMsg = MsgBox(Text, DialogType, DialogTitle)
+
+Finally:
+       PyMsgBox = sMsg
+       SF_Utils._ExitFunction(cstThisSub)
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper.PyMsgBox
+
+REM ============================================================= PRIVATE 
METHODS
+
+REM 
-----------------------------------------------------------------------------
+Public Function _PythonDispatcher(ByRef BasicObject As Variant _
+                                                                       , ByVal 
CallType As Variant _
+                                                                       , ByVal 
Script As Variant _
+                                                                       , 
ParamArray Args() As Variant _
+                                                               ) As Variant
+&apos;&apos;&apos;     Called from Python only
+&apos;&apos;&apos;     The method calls the method Script associated with the 
BasicObject class or module
+&apos;&apos;&apos;     with the given arguments
+&apos;&apos;&apos;     The invocation of the method can be a Property Get, 
Property Let or a usual call
+&apos;&apos;&apos;     NB: arguments and return values must not be 2D arrays
+&apos;&apos;&apos;     The implementation intends to be as AGNOSTIC as 
possible in termes of objects nature and methods called
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             BasicObject: a module or a class instance - May 
also be the reserved string: &quot;SF_Services&quot;
+&apos;&apos;&apos;             CallType: one of the constants applicable to a 
CallByName statement + optional protocol flags
+&apos;&apos;&apos;             Script: the name of the method or property
+&apos;&apos;&apos;             Args: the arguments to pass to the method. 
Input arguments can contain symbolic constants for Null, Missing, etc.
+&apos;&apos;&apos;     Returns:
+&apos;&apos;&apos;             A 1D array:
+&apos;&apos;&apos;                     [0]             The returned value - 
scalar, object or 1D array
+&apos;&apos;&apos;                     [1]             The VarType() of the 
returned value
+&apos;&apos;&apos;                                             Null, Empty and 
Nothing have different vartypes but return all None to Python
+&apos;&apos;&apos;                     Additionally, when array:
+&apos;&apos;&apos;                     [2]             Number of dimensions in 
Basic
+&apos;&apos;&apos;                     Additionally, when Basic object:
+&apos;&apos;&apos;                     [2]             Module (1), Class 
instance (2) or UNO (3)
+&apos;&apos;&apos;                     [3]             The object&apos;s 
ObjectType
+&apos;&apos;&apos;                     [4]             The object&apos;s 
service name
+&apos;&apos;&apos;                     [5]             The object&apos;s name
+&apos;&apos;&apos;             When an error occurs Python receives None as a 
scalar. This determines the occurence of a failure
+
+Dim vReturn As Variant                         &apos;  The value returned by 
the invoked property or method
+Dim vReturnArray As Variant                    &apos;  Return value
+Dim vBasicObject As Variant                    &apos;  Alias of BasicObject to 
avoid &quot;Object reference not set&quot; error
+Dim iNbArgs As Integer                         &apos;  Number of valid input 
arguments
+Dim vArg As Variant                                    &apos;  Alias for a 
single argument
+Dim vArgs() As Variant                         &apos;  Alias for Args()
+Dim sScript As String                          &apos;  Argument of 
ExecuteBasicScript()
+Dim vParams As Variant                         &apos;  Array of arguments to 
pass to a ParamArray
+Dim sObjectType As String                      &apos;  Alias of 
object.ObjectType
+Dim bBasicClass As Boolean                     &apos;  True when BasicObject 
is a class
+Dim sLibrary As String                         &apos;  Library where the 
object belongs to
+Dim bUno As Boolean                                    &apos;  Return value is 
a UNO object
+Dim sess As Object                                     :       Set sess = 
ScriptForge.SF_Session
+Dim i As Long
+
+&apos; Conventional special input or output values
+Const cstNoArgs = &quot;+++NOARGS+++&quot;, cstSymEmpty = 
&quot;+++EMPTY+++&quot;, cstSymNull = &quot;+++NULL+++&quot;, cstSymMissing = 
&quot;+++MISSING+++&quot;
+
+&apos; 
https://support.office.com/en-us/article/CallByName-fonction-49ce9475-c315-4f13-8d35-e98cfe98729a
+&apos; Determines the CallType
+Const vbGet = 2, vbLet = 4, vbMethod = 1, vbSet = 8
+&apos; Protocol flags
+Const cstArgArray = 512                &apos;  1st argument can be a 2D array
+Const cstRetArray = 1024       &apos;  Return value can be an array
+Const cstUno = 256                     &apos;  Return value can be a UNO object
+&apos; Object nature in returned array
+Const objMODULE = 1, objCLASS = 2, objUNO = 3
+
+Check:
+       &apos;If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+       _PythonDispatcher = Null
+
+       &apos;  Ignore Null basic objects (Null = Null or Nothing)
+       If IsNull(BasicObject) Or IsEmpty(BasicObject) Then GoTo Catch
+
+       &apos;  Reinterprete arguments one by one into vArgs, examine iso-dates 
and conventional NoArgs/Empty/Null values
+       iNbArgs = -1
+       vArgs = Array()
+       If UBound(Args) &gt;= 0 Then
+               For i = 0 To UBound(Args)
+                       vArg = Args(i)
+                       If i = 0 And VarType(vArg) = V_STRING Then
+                               If vArg = cstNoArgs Then Exit For
+                       End If
+                       If VarType(vArg) = V_STRING Then
+                               If vArg = cstSymEmpty Then
+                                       vArg = Empty
+                               ElseIf vArg = cstSymNull Then
+                                       vArg = Null
+                               ElseIf vArg = cstSymMissing Then
+                                       Exit For        &apos;  Next arguments 
must be missing also
+                               Else
+                                       vArg = SF_Utils._CStrToDate(vArg)
+                                       If vArg &lt; 0 Then vArg = Args(i)      
&apos;Conversion of iso format failed =&gt; forget
+                               End If
+                       End If
+                       iNbArgs = iNbArgs + 1
+
+                       ReDim Preserve vArgs(iNbArgs)
+                       vArgs(iNbArgs) = vArg
+               Next i
+       End If
+
+Try:
+       &apos;  Dispatching strategy: based on next constraints
+       &apos;                  (1) Bug 
https://bugs.documentfoundation.org/show_bug.cgi?id=138155
+       &apos;                          The CallByName function fails when 
returning an array
+       &apos;                  (2) Python has tuples and tuple of tuples, not 
2D arrays
+       &apos;                  (3) Passing 2D arrays through a script provider 
always transform it into a sequence of sequences
+       &apos;  1.      Methods in usual modules are called by 
ExecuteBasicScript() except if they use a ParamArray
+       &apos;  2.      Properies in any service are got and set with 
obj.GetProperty/SetProperty(...)
+       &apos;  3.      Methods in class modules are invoked with CallByName
+       &apos;  4.      Methods in class modules using a 2D array or returning 
arrays, or methods using ParamArray,
+&apos;&apos;&apos;                     are hardcoded as exceptions or are not 
implemented
+       &apos;  5.      Methods returning a 1D array when no arguments and a 
scalar otherwise (e.g. SF_Dialog.Controls())
+       &apos;          may be considered as properties when no argument
+&apos;                 Requires Python and Basic update in the concerned 
library but is transparent for this dispatcher
+
+       Select case VarType(BasicObject)
+               Case V_STRING
+                       &apos;  Special entry for CreateScriptService()
+                       vBasicObject = BasicObject
+                       If vBasicObject = &quot;SF_Services&quot; Then
+                               If UBound(vArgs) = 0 Then vParams = Array() 
Else vParams = SF_Array.Slice(vArgs, 1)
+                               Select Case UBound(vParams)
+                                       Case -1 :       vReturn = 
SF_Services.CreateScriptService(vArgs(0))
+                                       Case 0  :       vReturn = 
SF_Services.CreateScriptService(vArgs(0), vParams(0))
+                                       Case 1  :       vReturn = 
SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1))
+                                       Case 2  :       vReturn = 
SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2))
+                                       Case 3  :       vReturn = 
SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2), 
vParams(3))
+                                       Case 4  :       vReturn = 
SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2), 
vParams(3), vParams(4))
+                               End Select
+                       End If
+                       If VarType(vReturn) = V_OBJECT And Not IsNull(vReturn) 
Then
+                               vBasicObject = vReturn
+                               sObjectType = vBasicObject.ObjectType
+                               bBasicClass = ( Left(sObjectType, 3) &lt;&gt; 
&quot;SF_&quot; )
+                       End If
+               
+               &apos;  Implement dispatching strategy
+               Case V_INTEGER
+                       If BasicObject &lt; 0 Or Not 
IsArray(_SF_.PythonStorage) Then GoTo Catch
+                       If BasicObject &gt; UBound(_SF_.PythonStorage) Then 
GoTo Catch
+                       vBasicObject = _SF_.PythonStorage(BasicObject)
+                       sObjectType = vBasicObject.ObjectType
+                       &apos;  Basic modules have type = &quot;SF_*&quot;
+                       bBasicClass = ( Left(sObjectType, 3) &lt;&gt; 
&quot;SF_&quot; )
+                       sLibrary = Split(vBasicObject.ServiceName, 
&quot;.&quot;)(0)
+                       
+                       &apos;  Methods in usual modules are called by 
ExecuteBasicScript() except if they use a ParamArray
+                       If Not bBasicClass And (CallType And vbMethod) = 
vbMethod Then
+                               sScript = sLibrary &amp; &quot;.&quot; &amp; 
sObjectType &amp; &quot;.&quot; &amp; Script
+                               Select Case UBound(vArgs)
+                                       Case -1 :       vReturn = 
sess.ExecuteBasicScript(, sScript)
+                                       Case 0  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0))
+                                       Case 1  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1))
+                                       Case 2  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2))
+                                       Case 3  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3))
+                                       Case 4  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), 
vArgs(4))
+                                       Case 5  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), 
vArgs(4), vArgs(5))
+                                       Case 6  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), 
vArgs(4), vArgs(5), vArgs(6))
+                                       Case 7  :       vReturn = 
sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), 
vArgs(4), vArgs(5), vArgs(6), vArgs(7))
+                               End Select
+                       
+                       &apos;  Properies in any service are got and set with 
obj.GetProperty/SetProperty(...)
+                       ElseIf (CallType And vbGet) = vbGet Then
+                               &apos;  vReturn = sess.ExecuteBasicScript(, 
sLibrary &amp; &quot;.&quot; &amp; sObjectType &amp; &quot;.GetProperty&quot;, 
Script)
+                               vReturn = vBasicObject.GetProperty(Script)
+                       ElseIf (CallType And vbLet) = vbLet Then
+                               &apos;  vReturn = sess.ExecuteBasicScript(, 
sLibrary &amp; &quot;.&quot; &amp; sObjectType &amp; &quot;.SetProperty&quot;, 
Script, vArgs(0))
+                               vReturn = vBasicObject.SetProperty(Script, 
vArgs(0))
+                       
+                       &apos;  Methods in class modules using a 2D array or 
returning arrays are hardcoded as exceptions
+                       ElseIf ((CallType And vbMethod) + (CallType And 
cstArgArray)) = vbMethod + cstArgArray Or _
+                                  ((CallType And vbMethod) + (CallType And 
cstRetArray)) = vbMethod + cstRetArray Then
+                               Select Case sLibrary
+                               End Select
+                       
+                       &apos;  Methods in class modules are invoked with 
CallByName
+                       ElseIf bBasicClass And ((CallType And vbMethod) = 
vbMethod) Then
+                               Select Case UBound(vArgs)
+                                       Case -1 :       vReturn = 
CallByName(vBasicObject, Script, vbMethod)
+                                               &apos;  Special case: Dispose() 
must update the cache for class objects created in Python scripts
+                                               If Script = &quot;Dispose&quot; 
Then Set _SF_.PythonStorage(BasicObject) = Nothing
+                                       Case 0  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0))
+                                       Case 1  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1))
+                                       Case 2  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2))
+                                       Case 3  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), 
vArgs(3))
+                                       Case 4  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), 
vArgs(3), vArgs(4))
+                                       Case 5  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), 
vArgs(3), vArgs(4), vArgs(5))
+                                       Case 6  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), 
vArgs(3), vArgs(4), vArgs(5), vArgs(6))
+                                       Case 7  :       vReturn = 
CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), 
vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7))
+                               End Select
+                       End If
+               Case Else
+       End Select
+       
+       &apos;  Format the returned array
+       vReturnArray = Array()
+       &apos;  Distinguish:    Basic object
+       &apos;                                  UNO object
+       &apos;                                  Array
+       &apos;                                  Scalar
+       If IsArray(vReturn) Then
+               ReDim vReturnArray(0 To 2)
+               vReturnArray(0) = vReturn               &apos;  2D arrays are 
flattened by the script provider when returning to Python
+               vReturnArray(1) = VarType(vReturn)
+               vReturnArray(2) = SF_Array.CountDims(vReturn)
+       ElseIf VarType(vReturn) = V_OBJECT And Not IsNull(vReturn) Then
+               &apos;  Uno or not Uno ?BuildPath
+               bUno = False
+               If (CallType And cstUno) = cstUno Then          &apos;  UNO 
considered only when pre-announced in CallType
+                       If Len(sess.UnoObjectType(vReturn)) &gt; 0 Then bUno = 
True
+               End If
+               If bUno Then
+                       ReDim vReturnArray(0 To 2)
+                       Set vReturnArray(0) = vReturn
+               Else
+                       ReDim vReturnArray(0 To 5)
+                       vReturnArray(0) = _SF_._AddToPythonSTorage(vReturn)
+               End If
+               vReturnArray(1) = V_OBJECT
+               vReturnArray(2) = Iif(bUno, objUNO, Iif(bBasicClass, objCLASS, 
objMODULE))
+               If Not bUno Then
+                       vReturnArray(3) = vReturn.ObjectType
+                       vReturnArray(4) = vReturn.ServiceName
+                       If SF_Array.Contains(vReturn.Properties(), 
&quot;Name&quot;, SortOrder := &quot;ASC&quot;) Then vReturnArray(5) = 
vReturn.Name Else vReturnArray(5) = &quot;&quot;
+               End If
+       Else    &apos;  Scalar or Nothing
+               ReDim vReturnArray(0 To 1)
+               vReturnArray(0) = vReturn
+               vReturnArray(1) = VarType(vReturn)
+       End If
+
+       _PythonDispatcher = vReturnArray
+               
+Finally:
+       Exit Function
+Catch:
+       GoTo Finally
+End Function   &apos;  ScriptForge.SF_PythonHelper._PythonDispatcher
+
+REM 
-----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos;     Convert the Basic instance to a readable string, 
typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;     Return:
+&apos;&apos;&apos;             &quot;[PythonHelper]&quot;
+
+       _Repr = &quot;[PythonHelper]&quot;
+
+End Function   &apos;  ScriptForge.SF_PythonHelper._Repr
+
+REM ================================================= END OF 
SCRIPTFORGE.SF_PythonHelper
+</script:module>
\ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Root.xba 
b/wizards/source/scriptforge/SF_Root.xba
index 334e4798018c..379cb3586a2b 100644
--- a/wizards/source/scriptforge/SF_Root.xba
+++ b/wizards/source/scriptforge/SF_Root.xba
@@ -73,6 +73,7 @@ Private Interface                     As Object       &apos; 
ScriptForge own L10N service
 Private OSName                         As String       &apos; WIN, LINUX, MACOS
 Private SFDialogs                      As Variant      &apos; Persistent 
storage for the SFDialogs library
 Private SFForms                                As Variant      &apos; 
Persistent storage for the SF_Form class in the SFDocuments library
+Private PythonStorage          As Variant      &apos; Persistent storage for 
the objects created and processed in Python
 
 REM ====================================================== 
CONSTRUCTOR/DESTRUCTOR
 
@@ -120,6 +121,7 @@ Private Sub Class_Initialize()
        OSName = &quot;&quot;
        SFDialogs = Empty
        SFForms = Empty
+       PythonStorage = Empty
 End Sub                &apos;  ScriptForge.SF_Root Constructor
 
 REM 
-----------------------------------------------------------------------------
@@ -168,6 +170,49 @@ Dim sLine As String                        &apos;  Alias 
of psLine
 
 End Sub                &apos;  ScriptForge.SF_Root._AddToConsole
 
+REM 
-----------------------------------------------------------------------------
+Public Function _AddToPythonStorage(ByRef poObject As Object) As Long
+&apos;&apos;&apos;     Insert a newly created object in the Python persistent 
storage
+&apos;&apos;&apos;     and return the index of the used entry
+&apos;&apos;&apos;     The persistent storage is a simple array of objects
+&apos;&apos;&apos;     Args:
+&apos;&apos;&apos;             poObject: the object to insert
+
+Dim lIndex As Long                     &apos;  Return value
+Dim lSize As Long                      &apos;  UBound of the persistent storage
+Dim i As Long
+
+Check:
+       lIndex = -1
+       If IsNull(poObject) Then Exit Function
+       On Local Error GoTo Finally
+       If IsEmpty(PythonStorage) Then PythonStorage = Array()
+       lSize = UBound(PythonStorage)
+
+Try:
+       &apos;  Can an empty entry be reused ?
+       For i = 0 To lSize
+               If IsNull(PythonStorage(i)) Then
+                       lIndex = i
+                       Exit For
+               End If
+       Next i
+
+       &apos;  Resize Python storage if no empty space
+       If lIndex &lt; 0 Then
+               lSize = lSize + 1
+               ReDim Preserve PythonStorage(0 To lSize)
+               lIndex = lSize
+       End If
+
+       &apos;  Insert new object
+       Set PythonStorage(lIndex) = poObject
+
+Finally:
+       _AddToPythonStorage = lIndex
+       Exit Function
+End Function   &apos;  ScriptForge.SF_Root._AddToPythonStorage
+
 REM 
-----------------------------------------------------------------------------
 Public Sub _LoadLocalizedInterface(Optional ByVal psMode As String)
 &apos;&apos;&apos;     Build the user interface in a persistent L10N object
@@ -380,13 +425,13 @@ Try:
        &apos;  SF_Array.ExtractColumn, ExtractRow
                        .AddText(       Context := &quot;ARRAYINDEX2&quot; _
                                                , MsgId := &quot;The given 
slice limits do not fit within the bounds of the array.\n\n&quot; _
-                                                                       &amp; 
&quot;\t« Array_2D » = %1\n&quot; _
+                                                                       &amp; 
&quot;\t« Array_1D » = %1\n&quot; _
                                                                        &amp; 
&quot;\t« From »     = %2\n&quot; _
                                                                        &amp; 
&quot;\t« UpTo »     = %3&quot; _
                                                , Comment :=    
&quot;SF_Array.ExtractColumn (...) error message\n&quot; _
                                                                        &amp;   
&quot;%1: &apos;Column&apos; or &apos;Row&apos; of a matrix\n&quot; _
                                                                        &amp;   
&quot;%2, %3: array contents\n&quot; _
-                                                                       &amp;   
&quot;&apos;Array_2D&apos;, &apos;From&apos; and &apos;UpTo&apos; should not be 
translated&quot; _
+                                                                       &amp;   
&quot;&apos;Array_1D&apos;, &apos;From&apos; and &apos;UpTo&apos; should not be 
translated&quot; _
                                        )
        &apos;  SF_Array.ImportFromCSVFile
                        .AddText(       Context := &quot;CSVPARSING&quot; _
diff --git a/wizards/source/scriptforge/SF_Session.xba 
b/wizards/source/scriptforge/SF_Session.xba
index 84351de24add..2a56e91f1a55 100644
--- a/wizards/source/scriptforge/SF_Session.xba
+++ b/wizards/source/scriptforge/SF_Session.xba
@@ -166,7 +166,7 @@ Try:
        &apos;  Execute script
        Set oScript = SF_Session._GetScript(&quot;Basic&quot;, Scope, Script)
        On Local Error GoTo CatchExec
-       If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs(), Array(), 
Array())
+       If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs, Array(), 
Array())
 
 Finally:
        ExecuteBasicScript = vReturn
diff --git a/wizards/source/scriptforge/SF_Timer.xba 
b/wizards/source/scriptforge/SF_Timer.xba
index 3bdcaa6b701e..f1c718fca7b1 100644
--- a/wizards/source/scriptforge/SF_Timer.xba
+++ b/wizards/source/scriptforge/SF_Timer.xba
@@ -417,9 +417,9 @@ Dim cstSubArgs As String
                        End Select
                        _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000
                Case UCase(&quot;IsStarted&quot;)
-                       _PropertyGet = ( _TimerStatus = STATUSSTARTED Or 
_TimerStatus = STATUSSUSPENDED )
+                       _PropertyGet = CBool( _TimerStatus = STATUSSTARTED Or 
_TimerStatus = STATUSSUSPENDED )
                Case UCase(&quot;IsSuspended&quot;)
-                       _PropertyGet = ( _TimerStatus = STATUSSUSPENDED )
+                       _PropertyGet = CBool( _TimerStatus = STATUSSUSPENDED )
                Case UCase(&quot;SuspendDuration&quot;)
                        Select Case _TimerStatus
                                Case STATUSINACTIVE             :       
dDuration = 0.0
diff --git a/wizards/source/scriptforge/po/ScriptForge.pot 
b/wizards/source/scriptforge/po/ScriptForge.pot
index a4c6ff514bc4..06f0c77c6e1a 100644
--- a/wizards/source/scriptforge/po/ScriptForge.pot
+++ b/wizards/source/scriptforge/po/ScriptForge.pot
@@ -14,7 +14,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: 
https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n";
-"POT-Creation-Date: 2021-02-03 15:55:36\n"
+"POT-Creation-Date: 2021-03-04 16:31:25\n"
 "PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
@@ -263,13 +263,13 @@ msgstr ""
 #. SF_Array.ExtractColumn (...) error message
 #. %1: 'Column' or 'Row' of a matrix
 #. %2, %3: array contents
-#. 'Array_2D', 'From' and 'UpTo' should not be translated
+#. 'Array_1D', 'From' and 'UpTo' should not be translated
 #, kde-format
 msgctxt "ARRAYINDEX2"
 msgid  ""
 "The given slice limits do not fit within the bounds of the array.\n"
 "\n"
-"    « Array_2D » = %1\n"
+"    « Array_1D » = %1\n"
 "    « From »     = %2\n"
 "    « UpTo »     = %3"
 msgstr ""
diff --git a/wizards/source/scriptforge/po/en.po 
b/wizards/source/scriptforge/po/en.po
index a4c6ff514bc4..06f0c77c6e1a 100644
--- a/wizards/source/scriptforge/po/en.po
+++ b/wizards/source/scriptforge/po/en.po
@@ -14,7 +14,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: 
https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n";
-"POT-Creation-Date: 2021-02-03 15:55:36\n"
+"POT-Creation-Date: 2021-03-04 16:31:25\n"
 "PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
@@ -263,13 +263,13 @@ msgstr ""
 #. SF_Array.ExtractColumn (...) error message
 #. %1: 'Column' or 'Row' of a matrix
 #. %2, %3: array contents
-#. 'Array_2D', 'From' and 'UpTo' should not be translated
+#. 'Array_1D', 'From' and 'UpTo' should not be translated
 #, kde-format
 msgctxt "ARRAYINDEX2"
 msgid  ""
 "The given slice limits do not fit within the bounds of the array.\n"
 "\n"
-"    « Array_2D » = %1\n"
+"    « Array_1D » = %1\n"
 "    « From »     = %2\n"
 "    « UpTo »     = %3"
 msgstr ""
diff --git a/wizards/source/scriptforge/python/scriptforge.py 
b/wizards/source/scriptforge/python/scriptforge.py
new file mode 100644
index 000000000000..189a7f3049a4
--- /dev/null
+++ b/wizards/source/scriptforge/python/scriptforge.py
@@ -0,0 +1,676 @@
+# -*- coding: utf-8 -*-
+
+#     Copyright 2020-2021 Jean-Pierre LEDURE, Alain ROMEDENNE
+
+# 
=====================================================================================================================
+# ===           The ScriptForge library and its associated libraries are part 
of the LibreOffice project.           ===
+# ===                   Full documentation is available on 
https://help.libreoffice.org/                            ===
+# 
=====================================================================================================================
+
+# ScriptForge 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.
+
+# ScriptForge is free software; you can redistribute it and/or modify it under 
the terms of either (at your option):
+
+# 1) The Mozilla Public License, v. 2.0. If a copy of the MPL was not
+# distributed with this file, you can obtain one at 
http://mozilla.org/MPL/2.0/ .
+
+# 2) The GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version. If a copy of the LGPL was not
+# distributed with this file, see http://www.gnu.org/licenses/ .
+
+"""
+    ScriptForge libraries are an extensible and robust collection of macro 
scripting resources for LibreOffice
+    to be invoked from user Basic or Python macros. Users familiar with other 
BASIC macro variants often face hard
+    times to dig into the extensive LibreOffice Application Programming 
Interface even for the simplest operations.
+    By collecting most-demanded document operations in a set of easy to use, 
easy to read routines, users can now
+    program document macros with much less hassle and get quicker results.
+
+    ScriptForge abundant methods are organized in reusable modules that 
cleanly isolate Basic/Python programming
+    language constructs from ODF document content accesses and user 
interface(UI) features.
+
+    The scriptforge.py module
+        - implements a protocol between Python (user) scripts and the 
ScriptForge Basic library
+        - contains the interfaces (classes and attributes) to be used in 
Python user scripts
+          to run the services implemented in the standard libraries shipped 
with LibreOffice
+
+    Usage:
+
+        When Python and LibreOffice run in the same process (usual case): 
either
+            from scriptforge import *   # or, better ...
+            from scriptforge import CreateScriptService
+
+        When Python and LibreOffice are started in separate processes,
+        LibreOffice being started from console ... (example for Linux with 
port = 2021)
+            ./soffice --accept='socket,host=localhost,port=2021;urp;'
+        then use next statement:
+            from scriptforge import *   # or, better ...
+            from scriptforge import CreateScriptService, ScriptForge
+            ScriptForge(hostname = 'localhost', port = 2021)
+
+    Specific documentation about the use of ScriptForge from Python scripts:
+        TBD
+    """
+
+import uno
+
+from platform import system as _opsys
+import datetime
+import os
+
+
+class _Singleton(type):
+    """
+        A Singleton metaclass design pattern
+        Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly
+        """
+    instances = {}
+
+    def __call__(cls, *args, **kwargs):
+        if cls not in cls.instances:
+            cls.instances[cls] = super(_Singleton, cls).__call__(*args, 
**kwargs)
+        return cls.instances[cls]
+
+
+# 
#####################################################################################################################
+#                           ScriptForge CLASS                                  
                                     ###
+# 
#####################################################################################################################
+
+class ScriptForge(object, metaclass = _Singleton):
+    """
+        The ScriptForge (singleton) class encapsulates the core of the 
ScriptForge run-time
+            - Bridge with the LibreOffice process
+            - Implementation of the inter-language protocol with the Basic 
libraries
+            - Identification of the available services interfaces
+            - Dispatching of services
+            - Coexistence with UNO
+
+        It embeds the Service class that manages the protocol with Basic
+        """
+
+    # #########################################################################
+    # Class attributes
+    # #########################################################################
+    hostname = ''
+    port = 0
+    componentcontext = None
+    scriptprovider = None
+
+    # #########################################################################
+    # Class constants
+    # #########################################################################
+    library = 'ScriptForge'
+    Version = '7.2'  # Actual version number
+    #
+    # Basic dispatcher for Python scripts
+    basicdispatcher = 'ScriptForge.SF_PythonHelper._PythonDispatcher'
+    #
+    # VarType() constants
+    V_EMPTY, V_NULL, V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE = 0, 1, 2, 3, 4, 5
+    V_CURRENCY, V_DATE, V_STRING, V_OBJECT, V_BOOLEAN = 6, 7, 8, 9, 11
+    V_VARIANT, V_ARRAY, V_ERROR, V_UNO = 12, 8192, -1, 16
+    # Object types
+    objMODULE, objCLASS, objUNO = 1, 2, 3
+    # Special argument symbols
+    cstSymEmpty, cstSymNull, cstSymMissing = '+++EMPTY+++', '+++NULL+++', 
'+++MISSING+++'
+
+    def __init__(self, hostname = '', port = 0):
+        """
+            Because singleton, constructor is executed only once while Python 
active
+            Arguments are mandatory when Python and LibreOffice run in 
separate processes
+            :param hostname: probably 'localhost'
+            :param port: port number
+            """
+        ScriptForge.hostname = hostname
+        ScriptForge.port = port
+        # Determine main pyuno entry points
+        ScriptForge.componentcontext = self.ConnectToLOProcess(hostname, port) 
 # com.sun.star.uno.XComponentContext
+        ScriptForge.scriptprovider = 
self.ScriptProvider(self.componentcontext)  # ...script.provider.XScriptProvider
+        #
+        # Establish a list of the available services as a dictionary 
(servicename, serviceclass)
+        ScriptForge.serviceslist = dict((cls.servicename, cls) for cls in 
SFServices.__subclasses__())
+        ScriptForge.servicesdispatcher = None
+
+    @classmethod
+    def ConnectToLOProcess(cls, hostname = '', port = 0):
+        """
+            Called by the ScriptForge class constructor to establish the 
connection with
+            the requested LibreOffice instance
+            The default arguments are for the usual interactive mode
+
+            :param hostname: probably 'localhost' or ''
+            :param port: port number or 0
+            :return: the derived component context
+            """
+        if len(hostname) > 0 and port > 0:  # Explicit connection request via 
socket
+            ctx = uno.getComponentContext()  # 
com.sun.star.uno.XComponentContext
+            resolver = ctx.ServiceManager.createInstanceWithContext(
+                'com.sun.star.bridge.UnoUrlResolver', ctx)  # 
com.sun.star.comp.bridge.UnoUrlResolver
+            try:
+                conn = 'socket,host=%s,port=%d' % (hostname, port)
+                url = 'uno:%s;urp;StarOffice.ComponentContext' % conn
+                ctx = resolver.resolve(url)
+            except Exception:  # thrown when LibreOffice specified instance 
isn't started
+                raise ConnectionError(
+                    'Connection to LibreOffice failed (host = ' + hostname + 
', port = ' + str(port) + ')')
+            return ctx
+        elif len(hostname) == 0 and port == 0:  # Usual interactive mode
+            return uno.getComponentContext()
+        else:
+            raise SystemExit('The creation of the ScriptForge() instance got 
invalid arguments: '
+                             + '(host = ' + hostname + ', port = ' + str(port) 
+ ')')
+
+    @classmethod
+    def ScriptProvider(cls, context = None):
+        """
+            Returns the general script provider
+            """
+        servicemanager = context.ServiceManager  # 
com.sun.star.lang.XMultiComponentFactory
+        masterscript = servicemanager.createInstanceWithContext(
+            "com.sun.star.script.provider.MasterScriptProviderFactory", 
context)
+        return masterscript.createScriptProvider("")
+
+    @classmethod
+    def InvokeSimpleScript(cls, script, *args):
+        """
+            Create a UNO object corresponding with the given Python or Basic 
script
+            The execution is done with the invoke() method applied on the 
created object
+            Implicit scope: Extensions and documents are excluded. Either
+                "application"            a shared library                      
         (BASIC)
+                "share"                  a library of LibreOffice Macros       
         (PYTHON)
+            :param script: Either
+                    [library.]module.method - Must not be a class module or 
method
+                    [directory/]module.py$method
+            :return: the script object as a 
com.sun.star.script.provider.XScript UNO object
+            """
+        #    Compute the URI specification described in
+        #    
https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification
+        if cls.servicesdispatcher is not None and script == 
ScriptForge.basicdispatcher:
+            xscript = cls.servicesdispatcher
+        elif len(script) > 0:
+            if '.py$' in script.lower():  # Python
+                uri = 'vnd.sun.star.script:' + script + 
'?language=Python&location=share'
+            else:  # Basic
+                lib = ''
+                if len(script.split('.')) < 3:
+                    lib = cls.library + '.'
+                uri = 'vnd.sun.star.script:' + lib + script + 
'?language=Basic&location=application'
+            # Get the script object
+            try:
+                xscript = cls.scriptprovider.getScript(uri)
+            except Exception:
+                raise SystemExit('The script ' + "'" + script + "'"
+                                 + ' could not be located in your LibreOffice 
installation')
+        else:  # Should not happen
+            return None
+        # Execute the script with the given arguments
+        # Packaging for script provider depends on presence of ParamArray 
arguments in the called Basic script
+        if script == ScriptForge.basicdispatcher:
+            # At 1st execution, buffer xscript
+            if cls.servicesdispatcher is None:
+                cls.servicesdispatcher = xscript
+            scriptreturn = xscript.invoke(args[0], (), ())
+        else:
+            scriptreturn = xscript.invoke(args, (), ())
+        #
+        return scriptreturn[0]  # Updatable arguments passed by reference are 
ignored
+
+    @classmethod
+    def InvokeBasicService(cls, basicobject, flags, method, *args):
+        """
+            Execute a given Basic script and interprete its result
+            This method has as counterpart the 
ScriptForge.SF_PythonHelper._PythonDispatcher() Basic method
+            :param basicobject: a Service subclass
+            :param flags: see the vb* and flg* constants below
+            :param method: the name of the method or property to invoke, as a 
string
+            :param args: the arguments of the method. Symbolic cst* constants 
may be necessary
+            :return: The invoked Basic counterpart script (with 
InvokeSimpleScript()) will return a tuple
+                [0]     The returned value - scalar, object reference or a 
tuple
+                [1]     The Basic VarType() of the returned value
+                        Null, Empty and Nothing have different vartypes but 
return all None to Python
+                Additionally, when [0] is a tuple:
+                [2]     Number of dimensions in Basic
+                Additionally, when [0] is a UNO or Basic object:
+                [2]     Module (1), Class instance (2) or UNO (3)
+                [3]     The object's ObjectType
+                [4]     The object's ServiceName
+                [5]     The object's name
+                When an error occurs Python receives None as a scalar. This 
determines the occurence of a failure
+                The method returns either
+                    - the 0th element of the tuple when scalar, tuple or UNO 
object
+                    - a new Service() object or one of its subclasses otherwise
+            """
+        # Constants
+        script = ScriptForge.basicdispatcher
+        cstNoArgs = '+++NOARGS+++'
+        cstValue, cstVarType, cstDims, cstClass, cstType, cstService, cstName 
= 0, 1, 2, 2, 3, 4, 5
+
+        #
+        # Run the basic script
+        # The targeted script has a ParamArray argument. Do not change next 4 
lines except if you know what you do !
+        if len(args) == 0:
+            args = (basicobject,) + (flags,) + (method,) + (cstNoArgs,)
+        else:
+            args = (basicobject,) + (flags,) + (method,) + args
+        returntuple = cls.InvokeSimpleScript(script, args)
+        #
+        # Interprete the result
+        # Did an error occur in the Basic world ?
+        if not isinstance(returntuple, (tuple, list)):
+            raise RuntimeError("The execution of the method '" + method + "' 
failed. Execution stops.")
+        #
+        # Analyze the returned tuple
+        if returntuple[cstVarType] == ScriptForge.V_OBJECT and 
len(returntuple) > cstClass:  # Avoid Nothing
+            if returntuple[cstClass] == ScriptForge.objUNO:
+                pass
+            else:
+                # Create the new class instance of the right subclass of 
Service()
+                servname = returntuple[cstService]
+                for subcls in SFServices.__subclasses__():
+                    if servname == subcls.servicename:
+                       return subcls(returntuple[cstValue], 
returntuple[cstType], returntuple[cstClass],
+                                      returntuple[cstName])
+                # When service not found
+                raise RuntimeError("The service '" + servname + "' is not 
available in Python. Execution stops.")
+        elif returntuple[cstVarType] >= ScriptForge.V_ARRAY:
+            pass
+        else:         # All scalar values
+            pass
+        return returntuple[cstValue]
+
+
+# 
#####################################################################################################################
+#                           SFServices CLASS    (ScriptForge services 
superclass)                                   ###
+# 
#####################################################################################################################
+
+class SFServices(object):
+    """
+        Generic implementation of a parent Service class
+        Every service must subclass this class to be recognized as a valid 
service
+        A service instance is created by the CreateScriptService method
+        It can have a mirror in the Basic world or be totally defined in Python
+
+        Every subclass must initialize 2 class properties:
+            servicename (e.g. ScriptForge.FileSystem, ScriptForge.Basic)
+            serviceimplementation: either 'python' or 'basic'
+        This is sufficient to register the set of services in the Python world
+
+        The communication with Basic is managed by 2 ScriptForge() methods:
+            InvokeSimpleScript(): low level invocation of a Basic script. This 
script must be located
+                in a usual Basic module. The result is passed as-is
+            InvokeSBasicService(): the result comes back encapsulated with 
additional info
+                The result is interpreted in the method
+                The invoked script can be a property or a method of a Basic 
class module
+        It is up to every service method to determine which method to use
+
+        For Basic services only:
+            Each instance is identified by its
+                - object reference: the real Basic object embedded as a UNO 
wrapper object
+                - objecttype ('SF_String', 'DICTIONARY', ...)
+                - name (form, control, ... name) - may be blank
+
+            The role of the Service() superclass is mainly to propose a 
generic properties management
+            Properties are got and set following next strategy:
+                1. Property names are controlled strictly ('Value' and not 
'value')
+                2. Getting a property value for the first time is always done 
via a Basic call
+                3. Next occurrences are fetched from the Python dictionary of 
the instance if the property
+                    is read-only, otherwise via a Basic call
+                4. Read-only properties may be modified or deleted 
exceptionally by the class
+                   when self.internal == True. The latter must immediately be 
reset after use
+
+            Each subclass must define its interface with the user scripts:
+            1.  The properties
+                     a dictionary named 'serviceProperties' with keys = 
(camel-cased) property names and value = boolean
+                        True = editable, False = read-only
+                     a list named 'localProperties' reserved to properties for 
internal use
+                        e.g. oDlg.Controls() is a method that uses '_Controls' 
to hold the list of available controls
+                serviceProperties are buffered in Python after their 1st get 
request to Basic
+                Only if there is a need to go to Basic at each get, then 
declare the property explicitly:
+                    @property
+                    def myProperty(self):
+                        return self.GetProperty('myProperty')
+            2   The methods
+                a usual def: statement
+                    def myMethod(self, arg1, arg2 = ''):
+                        return self.Execute(self.vbMethod, 'myMethod', arg1, 
arg2)
+                Method names are camel-cased, arguments are lower-cased
+                All arguments must be present and initialized before the call 
to Basic, if any
+        """
+    # Python-Basic protocol constants and flags
+    vbGet, vbLet, vbMethod, vbSet = 2, 4, 1, 8  # CallByName constants
+    flgArrayArg = 512  # 1st argument can be a 2D array
+    flgArrayRet = 1024  # Invoked service method can return an array
+    flgUno = 256  # Invoked service method/property can return a UNO object
+    # Basic class type
+    moduleClass, moduleStandard = 2, 1
+    #
+    # To operate dynamic property getting/setting it is necessary to
+    # enumerate all types of properties and adapt __getattr__() and 
__setattr__() according to their type
+    internal_attributes = ('objectreference', 'objecttype', 'name', 
'internal', 'servicename',
+                           'serviceimplementation', 'classmodule', 'EXEC', 
'SIMPLEEXEC')
+
+    def __init__(self, reference = -1, objtype = None, classmodule = 0, name = 
''):
+        """
+            Trivial initialization of internal properties
+            If the subclass has its own __init()__ method, a call to this one 
should be its first statement.
+            Afterwards localProperties should be filled with the list of its 
own propertties
+            """
+        self.objectreference = reference  # the index in the Python storage 
where the Basic object is stored
+        self.objecttype = objtype  # ('SF_String', 'DICTIONARY', ...)
+        self.classmodule = classmodule  # Module (1), Class instance (2)
+        self.name = name  # '' when no name
+        self.internal = False  # True to exceptionally allow assigning a new 
value to a read-only property
+        self.localProperties = ()  # the properties reserved for internal use 
(often empty)
+        self.SIMPLEEXEC = ScriptForge.InvokeSimpleScript  # Shortcuts to 
script provider interfaces
+        self.EXEC = ScriptForge.InvokeBasicService
+
+    def __getattr__(self, name):
+        """
+            Executed for EVERY property reference if name not yet in the 
instance dict
+            At the 1st get, the property value is always got from Basic
+            """
+        if self.serviceimplementation == 'basic':
+            if name in ('serviceProperties', 'localProperties', 
'internal_attributes'):
+                pass
+            elif name in self.serviceProperties:
+                # Get Property from Basic
+                return self.GetProperty(name)
+        # Execute the usual attributes getter
+        return super(SFServices, self).__getattribute__(name)
+
+    def __setattr__(self, name, value):
+        """
+            Executed for EVERY property assignment, including in __init__() !!
+            Setting a property requires for serviceProperties() to be executed 
in Basic
+            """
+        if self.serviceimplementation == 'basic':
+            if name in ('serviceProperties', 'localProperties', 
'internal_attributes'):
+                pass
+            elif name[0:2] == '__' or name in self.internal_attributes or name 
in self.localProperties:
+                pass
+            elif name in self.serviceProperties:
+                if self.internal:  # internal = True forces property local 
setting even if property is read-only
+                    pass
+                elif self.serviceProperties[name] is True:  # True == Editable
+                    self.SetProperty(name, value)
+                else:
+                    raise AttributeError(
+                        "type object '" + self.objecttype + "' has no editable 
property '" + name + "'")
+            else:
+                raise AttributeError("type object '" + self.objecttype + "' 
has no property '" + name + "'")
+        object.__setattr__(self, name, value)
+        return
+
+    def __repr__(self):
+        return self.serviceimplementation + '/' + self.servicename + '/' + 
str(self.objectreference) + '/' + \
+               super(SFServices, self).__repr__()
+
+    def Dispose(self):
+        if self.serviceimplementation == 'basic':
+            if self.classmodule == self.moduleClass and self.objectreference 
>= 0:
+                self.Execute(self.vbMethod, 'Dispose')
+                self.objectreference = -1
+
+    def Execute(self, flags = 0, methodname = '', *args):
+        if flags == 0:
+            flags = self.vbMethod
+        if len(methodname) > 0:
+            return self.EXEC(self.objectreference, flags, methodname, *args)
+
+    def GetProperty(self, propertyname):
+        """
+            Get the given property from the Basic world
+            """
+        return self.EXEC(self.objectreference, self.vbGet, propertyname)
+
+    def SetProperty(self, propertyname, value):
+        """
+            Set the given property to a new value in the Basic world
+            """
+        return self.EXEC(self.objectreference, self.vbLet, propertyname, value)
+
+
+# 
#####################################################################################################################
+#                       SFScriptForge CLASS    (alias of ScriptForge Basic 
library)                                 ###
+# 
#####################################################################################################################
+class SFScriptForge:
+    # #########################################################################
+    # SF_Basic CLASS
+    # #########################################################################
+    class SF_Basic(SFServices, metaclass = _Singleton):
+        """
+            This service proposes a collection of Basic methods to be executed 
in a Python context
+            to simulate the exact syntax and behaviour of the identical Basic 
builtin method.
+            Typical example:
+                SF_Basic.MsgBox('This has to be displayed in a message box')
+            """
+        # Mandatory class properties for service registration
+        serviceimplementation = 'python'
+        servicename = 'ScriptForge.Basic'
+        # Basic helper functions invocation
+        module = 'SF_PythonHelper'
+        # Message box constants
+        MB_ABORTRETRYIGNORE, MB_DEFBUTTON1, MB_DEFBUTTON2, MB_DEFBUTTON3 = 2, 
128, 256, 512
+        MB_ICONEXCLAMATION, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONSTOP = 
48, 64, 32, 16
+        MB_OK, MB_OKCANCEL, MB_RETRYCANCEL, MB_YESNO, MB_YESNOCANCEL = 0, 1, 
5, 4, 3
+        IDABORT, IDCANCEL, IDIGNORE, IDNO, IDOK, IDRETRY, IDYES = 3, 2, 5, 7, 
1, 4, 6
+
+        def ConvertFromUrl(self, filename):
+            return self.SIMPLEEXEC(self.module + '.PyConvertFromUrl', filename)
+
+        def ConvertToUrl(self, filename):
+            return self.SIMPLEEXEC(self.module + '.PyConvertToUrl', filename)
+
+        def CreateUnoService(self, unoservice):
+            return self.SIMPLEEXEC(self.module + '.PyCreateUnoService', 
unoservice)
+
+        def DateAdd(self, add, count, datearg):
+            if isinstance(datearg, datetime.datetime):
+                datearg = datearg.isoformat()
+            dateadd = self.SIMPLEEXEC(self.module + '.PyDateAdd', add, count, 
datearg)
+            return datetime.datetime.fromisoformat(dateadd)
+
+        def DateDiff(self, add, date1, date2, weekstart = 1, yearstart = 1):
+            if isinstance(date1, datetime.datetime):
+                date1 = date1.isoformat()
+            if isinstance(date2, datetime.datetime):
+                date2 = date2.isoformat()
+            return self.SIMPLEEXEC(self.module + '.PyDateDiff', add, date1, 
date2, weekstart, yearstart)
+
+        def DatePart(self, add, datearg, weekstart = 1, yearstart = 1):
+            if isinstance(datearg, datetime.datetime):
+                datearg = datearg.isoformat()
+            return self.SIMPLEEXEC(self.module + '.PyDatePart', add, datearg, 
weekstart, yearstart)
+
+        def DateValue(self, datearg):
+            if isinstance(datearg, datetime.datetime):
+                datearg = datearg.isoformat()
+            datevalue = self.SIMPLEEXEC(self.module + '.PyDateValue', datearg)
+            return datetime.datetime.fromisoformat(datevalue)
+
+        def Format(self, value, pattern = ''):
+            if isinstance(value, datetime.datetime):
+                value = value.isoformat()
+            return self.SIMPLEEXEC(self.module + '.PyFormat', value, pattern)
+
+        def GetGuiType(self):
+            return self.SIMPLEEXEC(self.module + '.PyGetGuiType')
+
+        def GetSystemTicks(self):
+            return self.SIMPLEEXEC(self.module + '.PyGetSystemTicks')
+
+        @staticmethod
+        def GetDefaultContext():
+            return ScriptForge.componentcontext
+
+        @staticmethod
+        def GetPathSeparator():
+            return os.sep
+
+        class GlobalScope(object, metaclass = _Singleton):
+            @classmethod  # Mandatory because the GlobalScope class is 
normally not instantiated
+            def BasicLibraries(cls):
+                return 
SFScriptForge.SF_Basic().SIMPLEEXEC(SFScriptForge.SF_Basic.module + 
'.PyGlobalScope', 'Basic')
+
+            @classmethod
+            def DialogLibraries(cls):
+                return 
SFScriptForge.SF_Basic().SIMPLEEXEC(SFScriptForge.SF_Basic.module + 
'.PyGlobalScope', 'Dialog')
+
+        def InputBox(self, msg, title = '', default = '', xpos = -1, ypos = 
-1):
+            if xpos < 0 or ypos < 0:
+                return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, 
title, default)
+            return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, title, 
default, xpos, ypos)
+
+        def MsgBox(self, text, dialogtype = 0, dialogtitle = ''):
+            return self.SIMPLEEXEC(self.module + '.PyMsgBox', text, 
dialogtype, dialogtitle)
+
+        @staticmethod
+        def Now():
+            return datetime.datetime.now()
+
+        @staticmethod
+        def RGB(red, green, blue):
+            return int('%02x%02x%02x' % (red, green, blue), 16)
+
+        def Xray(self, unoobject = None):
+            return self.SIMPLEEXEC('XrayTool._main.xray', unoobject)
+
+    # #########################################################################
+    # SF_String CLASS
+    # #########################################################################
+    class SF_String(SFServices, metaclass = _Singleton):
+        """
+            A collection of methods focussed on string manipulation, user 
input validation,
+            regular expressions, encodings, parsing and hashing algorithms.
+            Many of them are less efficient than their Python equivalents.
+            """
+        # Mandatory class properties for service registration
+        serviceimplementation = 'basic'
+        servicename = 'ScriptForge.String'
+
+    # #########################################################################
+    # SF_FileSystem CLASS
+    # #########################################################################
+    class SF_FileSystem(SFServices, metaclass = _Singleton):
+        """
+            The "FileSystem" service includes common file and folder handling 
routines.
+            """
+        # Mandatory class properties for service registration
+        serviceimplementation = 'basic'
+        servicename = 'ScriptForge.FileSystem'
+        serviceProperties = dict(FileNaming = True, ConfigFolder = False, 
ExtensionsFolder = False, HomeFolder = False,
+                                 InstallFolder = False, TemplatesFolder = 
False, TemporaryFolder = False,
+                                 UserTemplatesFolder = False)
+
+        @property
+        def ConfigFolder(self):
+            return self.GetProperty('ConfigFolder')
+
+        def BuildPath(self, foldername, name):
+            return self.Execute(self.vbMethod, 'BuildPath', foldername, name)
+
+        def FolderExists(self, foldername):
+            return self.Execute(self.vbMethod, 'FolderExists', foldername)
+
+    # #########################################################################
+    # SF_Timer CLASS
+    # #########################################################################
+    class SF_Timer(SFServices):
+        """
+            The "Timer" service measures the amount of time it takes to run 
user scripts..
+            """
+        # Mandatory class properties for service registration
+        serviceimplementation = 'basic'
+        servicename = 'ScriptForge.Timer'
+        serviceProperties = dict(Duration = False, IsStarted = False, 
IsSuspended = False,
+                                 SuspendDuration = False, TotalDuration = 
False)
+
+        @property
+        def Duration(self):
+            return self.GetProperty('Duration')
+
+        @property
+        def IsStarted(self):
+            return self.GetProperty('IsStarted')
+
+        @property
+        def SuspendDuration(self):
+            return self.GetProperty('SuspendDuration')
+
+        @property
+        def TotalDuration(self):
+            return self.GetProperty('TotalDuration')
+
+        def Continue(self):
+            return self.Execute(self.vbMethod, 'Continue')
+
+        def Restart(self):
+            return self.Execute(self.vbMethod, 'Restart')
+
+        def Start(self):
+            return self.Execute(self.vbMethod, 'Start')
+
+        def Suspend(self):
+            return self.Execute(self.vbMethod, 'Suspend')
+
+        def Terminate(self):
+            return self.Execute(self.vbMethod, 'Terminate')
+
+
+# 
##############################################False#######################################################################
+#                           CreateScriptService()                              
                                     ###
+# 
#####################################################################################################################
+def CreateScriptService(service, *args):
+    """
+        A service being the name of a collection of properties and methods,
+        this method returns the Python object mirror of the Basic object 
implementing
+        the requested service
+        As an exception to above, 'Basic' is accepted as a shortcut to the 
Basic service
+        which is implemented in Python
+        :param service: the name of the service as a string 'library.service' 
- cased exactly
+        :param args: the arguments to pass to the service constructor
+        :return: the service as a Python object
+        """
+    # Init at each CreateScriptService() invocation
+    #       CreateScriptService is usually the first statement in user scripts 
requesting ScriptForge services
+    #       ScriptForge() is optional in user scripts when Python process 
inside LibreOffice process
+    ScriptForge()
+
+    def ResolveSynonyms(servicename):
+        """
+            Synonyms within service names implemented in Python are resolved 
here
+            :param servicename: The short name of the service
+            :return: The official service name
+            """
+        if servicename.lower() in ('basic', 'scriptforge.basic'):
+            return 'ScriptForge.Basic'
+        return servicename
+
+    #
+    # Check the list of available services to examine if the requested service 
is within the Python world
+    scriptservice = ResolveSynonyms(service)
+    if scriptservice in ScriptForge.serviceslist:
+        serv = ScriptForge.serviceslist[scriptservice]
+        if serv.serviceimplementation == 'python':
+            return serv()
+    # The requested service is to be found in the Basic world
+    if len(args) == 0:
+        serv = ScriptForge.InvokeBasicService('SF_Services', 
SFServices.vbMethod, 'CreateScriptService', service)
+    else:
+        serv = ScriptForge.InvokeBasicService('SF_Services', 
SFServices.vbMethod, 'CreateScriptService', service, *args)
+    return serv
+
+# 
#####################################################################################################################
+#                           Services shortcuts                                 
                                     ###
+# 
#####################################################################################################################
+# SF_Basic = CreateScriptService('SFPython.Basic')
+# SF_String = _ScriptForge.SF_String
+
+
+# ######################################################################
+# lists the scripts, that shall be visible inside the Basic/Python IDE
+# ######################################################################
+
+g_exportedScripts = ()
\ No newline at end of file
diff --git a/wizards/source/scriptforge/script.xlb 
b/wizards/source/scriptforge/script.xlb
index d4c21b652ebe..fd4045f34666 100644
--- a/wizards/source/scriptforge/script.xlb
+++ b/wizards/source/scriptforge/script.xlb
@@ -18,4 +18,5 @@
  <library:element library:name="SF_Exception"/>
  <library:element library:name="SF_UI"/>
  <library:element library:name="SF_Platform"/>
+ <library:element library:name="SF_PythonHelper"/>
 </library:library>
\ No newline at end of file
_______________________________________________
Libreoffice-commits mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to