summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean-Pierre Ledure <jp@ledure.be>2021-03-04 17:19:27 +0100
committerJean-Pierre Ledure <jp@ledure.be>2021-03-05 10:26:51 +0100
commitf66a479225b35e9f1fd3621eef7496462088308a (patch)
tree8499d1565c6f9ac39355e3a36aac84abfd8b2d1e
parenta04dde6b9c6d625ac80acbad1e599dbd8edf3579 (diff)
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 <jp@ledure.be> Tested-by: Jenkins Reviewed-by: Jean-Pierre Ledure <jp@ledure.be>
-rw-r--r--wizards/Package_scriptforge.mk4
-rw-r--r--wizards/source/scriptforge/SF_FileSystem.xba17
-rw-r--r--wizards/source/scriptforge/SF_PythonHelper.xba752
-rw-r--r--wizards/source/scriptforge/SF_Root.xba49
-rw-r--r--wizards/source/scriptforge/SF_Session.xba2
-rw-r--r--wizards/source/scriptforge/SF_Timer.xba4
-rw-r--r--wizards/source/scriptforge/po/ScriptForge.pot6
-rw-r--r--wizards/source/scriptforge/po/en.po6
-rw-r--r--wizards/source/scriptforge/python/scriptforge.py676
-rw-r--r--wizards/source/scriptforge/script.xlb1
10 files changed, 1498 insertions, 19 deletions
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 -----------------------------------------------------------------------------
@@ -169,6 +171,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
&apos;&apos;&apos; Executed - only once - at first ScriptForge invocation by a user script
@@ -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