wizards/source/scriptforge/SF_Services.xba | 3 wizards/source/scriptforge/python/scriptforge.py | 26 +++++ wizards/source/sfdialogs/SF_Dialog.xba | 46 +++++++- wizards/source/sfdialogs/SF_Register.xba | 118 +++++++++++++++++++++-- 4 files changed, 177 insertions(+), 16 deletions(-)
New commits: commit 0a1022b04c90d36e16dee121923ca4111386585b Author: Jean-Pierre Ledure <j...@ledure.be> AuthorDate: Sun May 21 17:06:05 2023 +0200 Commit: Jean-Pierre Ledure <j...@ledure.be> CommitDate: Mon May 22 14:08:20 2023 +0200 ScriptForge (SFDialogs: create dialogs on-the-fly A dialog service is returned by next statement dialog = CreateScriptService("newdialog", dialogname, place) All properties and methods applicable to predefined dialogs are available for such new dialogs. In particular the series of CreateXXX() methods for the addition of ne dialog controls. The functionality is available from Basic and Python user scripts. An update of the SFDialogs.SF_Dialog help page is required. A display rendering unstability (flickerings, delays, ..) has been observed when (all conditions must be met) - the user script is run from Python - from inside the LibreOffice process - the dialog is non-modal Change-Id: Id3f311cd04497fd79712ce712bdb2724b5caa861 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152071 Tested-by: Jean-Pierre Ledure <j...@ledure.be> Reviewed-by: Jean-Pierre Ledure <j...@ledure.be> diff --git a/wizards/source/scriptforge/SF_Services.xba b/wizards/source/scriptforge/SF_Services.xba index f5e0545546e6..f00ad9e94b21 100644 --- a/wizards/source/scriptforge/SF_Services.xba +++ b/wizards/source/scriptforge/SF_Services.xba @@ -128,7 +128,8 @@ Try: Select Case LCase(sService) Case "document", "calc", "writer", "base", "formdocument", "documentevent", "formevent" sLibrary = "SFDocuments" - Case "dialog", "dialogevent" : sLibrary = "SFDialogs" + Case "dialog", "dialogevent", "newdialog" + sLibrary = "SFDialogs" Case "database", "datasheet" : sLibrary = "SFDatabases" Case "unittest" : sLibrary = "SFUnitTests" Case "menu", "popupmenu", "toolbar", "toolbarbutton" diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py index 1b094c4711be..8b3a67fe2f60 100644 --- a/wizards/source/scriptforge/python/scriptforge.py +++ b/wizards/source/scriptforge/python/scriptforge.py @@ -1987,6 +1987,32 @@ class SFDialogs: def Terminate(self): return self.ExecMethod(self.vbMethod, 'Terminate') + # ######################################################################### + # SF_NewDialog CLASS + # ######################################################################### + class SF_NewDialog(SFServices): + """ + Pseudo service never returned from the Basic world. A SF_Dialog instance is returned instead. + Main purpose: manage the arguments of CreateScritService() for the creation of a dialog from scratch + """ + # Mandatory class properties for service registration + serviceimplementation = 'basic' + servicename = 'SFDialogs.NewDialog' + servicesynonyms = ('newdialog', 'sfdialogs.newdialog') + serviceproperties = dict() + + @classmethod + def ReviewServiceArgs(cls, dialogname = '', place = (0, 0, 0, 0)): + """ + Transform positional and keyword arguments into positional only + Add the XComponentContext as last argument + """ + outsideprocess = len(ScriptForge.hostname) > 0 and ScriptForge.port > 0 + if outsideprocess: + return dialogname, place, ScriptForge.componentcontext + else: + return dialogname, place + # ######################################################################### # SF_DialogControl CLASS # ######################################################################### diff --git a/wizards/source/sfdialogs/SF_Dialog.xba b/wizards/source/sfdialogs/SF_Dialog.xba index 20f1e81dc650..4979fa82a681 100644 --- a/wizards/source/sfdialogs/SF_Dialog.xba +++ b/wizards/source/sfdialogs/SF_Dialog.xba @@ -14,18 +14,22 @@ Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ''' SF_Dialog ''' ========= -''' Management of dialogs defined with the Basic IDE +''' Management of dialogs. They may bedefined with the Basic IDE or built from scratch ''' Each instance of the current class represents a single dialog box displayed to the user ''' ''' A dialog box can be displayed in modal or in non-modal modes -''' In modal mode, the box is displayed and the execution of the macro process is suspended -''' until one of the OK or Cancel buttons is pressed. In the meantime, other user actions -''' executed on the box can trigger specific actions. -''' In non-modal mode, the dialog box is "floating" on the user desktop and the execution -''' of the macro process continues normally -''' A dialog box disappears from memory after its explicit termination. +''' +''' In modal mode, the box is displayed and the execution of the macro process is suspended +''' until one of the OK or Cancel buttons is pressed. In the meantime, other user actions +''' executed on the box can trigger specific actions. +''' +''' In non-modal mode, the dialog box is "floating" on the user desktop and the execution +''' of the macro process continues normally +''' A dialog box disappears from memory after its explicit termination. ''' ''' Service invocation and usage: +''' +''' 1) when the dialog exists in some dialog libraries (= pre-defined with the Basic IDE): ''' Dim myDialog As Object, lButton As Long ''' Set myDialog = CreateScriptService("SFDialogs.Dialog", Container, Library, DialogName) ''' ' Args: @@ -42,6 +46,24 @@ Option Explicit ''' End If ''' myDialog.Terminate() ''' +''' 2) when the dialog is fully defined by code: +''' Dim myDialog As Object, oButton As Object lExec As Long +''' Set myDialog = CreateScriptService("SFDialogs.NewDialog", DialogName, Place) +''' ' Args: +''' ' DialogName: a case-sensitive string designating the dialog +''' Place: either +''' - an array with 4 elements: (X, Y, Width, Height) +''' - a com.sun.star.awt.Rectangle [X, Y, Width, Height] +''' (All elements are expressed in "Map AppFont" units). +''' ' ... Create controls with the CreateXXX(...) methods ..., e.g. +''' Set oButton = myDialog.CreateButton("OKButton", Place := Array(100, 100, 20, 10), Push := "OK") +''' lExec = myDialog.Execute() ' Default mode = Modal +''' If lExec = myDialog.OKBUTTON Then +''' ' ... Process controls and do what is needed +''' End If +''' myDialog.Terminate() +''' +''' ''' Detailed user documentation: ''' https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_dialog.html?DbPAR=BASIC ''' @@ -63,6 +85,8 @@ Private ServiceName As String ' Dialog location Private _Container As String Private _Library As String +Private _BuiltFromScratch As Boolean ' When True, dialog is not stored in a library +Private _BuiltInPython As Boolean ' Used only when _BuiltFromScratch = True Private _Name As String Private _CacheIndex As Long ' Index in cache storage @@ -145,6 +169,8 @@ Private Sub Class_Initialize() ServiceName = "SFDialogs.Dialog" _Container = "" _Library = "" + _BuiltFromScratch = False + _BuiltInPython = False _Name = "" _CacheIndex = -1 Set _DialogProvider = Nothing @@ -1895,7 +1921,8 @@ Try: Else _Modal = False _Displayed = True - _DialogModel.DesktopAsParent = True + ' To make visible an on-the-fly designed dialog when macro triggered from Python + _DialogModel.DesktopAsParent = Not ( _BuiltFromScratch And _BuiltInPython ) _DialogControl.setVisible(True) lExecute = 0 End If @@ -2600,7 +2627,8 @@ Check: If IsMissing(pbError) Then pbError = True Try: - bAlive = ( Not IsNull(_DialogProvider) And Not IsNull(_DialogControl) ) + bAlive = ( Not IsNull(_DialogProvider) Or _BuiltFromScratch ) + If bAlive Then bAlive = Not IsNull(_DialogControl) If Not bAlive Then GoTo Catch Finally: diff --git a/wizards/source/sfdialogs/SF_Register.xba b/wizards/source/sfdialogs/SF_Register.xba index e81dbb069fa8..d2986468ef95 100644 --- a/wizards/source/sfdialogs/SF_Register.xba +++ b/wizards/source/sfdialogs/SF_Register.xba @@ -56,9 +56,9 @@ Public Sub RegisterScriptServices() As Variant ''' "libraryname.modulename.function" With GlobalScope.ScriptForge.SF_Services - .RegisterService("Dialog", "SFDialogs.SF_Register._NewDialog") ' Reference to the function initializing the service - .RegisterEventManager("DialogEvent", "SFDialogs.SF_Register._EventManager") ' Reference to the events manager - 'TODO + .RegisterService("Dialog", "SFDialogs.SF_Register._NewDialog") ' Reference to the function initializing the service + .RegisterEventManager("DialogEvent", "SFDialogs.SF_Register._EventManager") ' Reference to the events manager + .RegisterEventManager("NewDialog", "SFDialogs.SF_Register._NewDialogFromScratch") ' Reference to the function initializing the service End With End Sub ' SFDialogs.SF_Register.RegisterScriptServices @@ -251,16 +251,18 @@ Const cstGlobal = "GlobalScope" Check: If IsMissing(pvArgs) Or IsEmpty(pvArgs) Then pvArgs = Array() - If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs) ' Needed when _NewDialog called from _EventManager + If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs) If UBound(pvArgs) >= 0 Then vContainer = pvArgs(0) Else vContainer = "" If UBound(pvArgs) >= 1 Then vLibrary = pvArgs(1) If IsEmpty(vLibrary) Then vLibrary = "Standard" If UBound(pvArgs) >= 2 Then vDialogName = pvArgs(2) Else vDialogName = Empty ' Use Empty to force mandatory status + If Not ScriptForge.SF_Utils._Validate(vContainer, "Container", Array(V_STRING, ScriptForge.V_OBJECT)) Then GoTo Finally If Not ScriptForge.SF_Utils._Validate(vLibrary, "Library", V_STRING) Then GoTo Finally If Not ScriptForge.SF_Utils._Validate(vDialogName, "DialogName", V_STRING) Then GoTo Finally - If UBound(pvArgs) >= 3 Then vContext = pvArgs(3) Else vContext = Nothing - If Not ScriptForge.SF_Utils._Validate(vContext, "DialogName", V_OBJECT) Then GoTo Finally + If UBound(pvArgs) >= 3 Then vContext = pvArgs(3) Else Set vContext = Nothing + If Not ScriptForge.SF_Utils._Validate(vContext, "Context", ScriptForge.V_OBJECT) Then GoTo Finally + Set oDialog = Nothing Try: @@ -344,5 +346,109 @@ CatchNotFound: GoTo Finally End Function ' SFDialogs.SF_Register._NewDialog +REM ----------------------------------------------------------------------------- +Private Function _NewDialogFromScratch(Optional ByVal pvArgs As Variant) As Object +''' Create a new instance of the SF_Dialog class describing a dynamically defined dialog box +''' Args: +''' DialogName: a symbolic name of the dialog to create, for information only. Not checked for unicity. +''' Place: either +''' - an array with 4 elements: (X, Y, Width, Height) +''' - a com.sun.star.awt.Rectangle [X, Y, Width, Height] +''' All elements are expressed in "Map AppFont" units. +''' Context: When called from Python, the context must be provided : XSCRIPTCONTEXT +''' Returns: the instance or Nothing + +Dim oDialog As Object ' Return value +Dim vDialogName As Variant ' The name is for information only +Dim vPlace As variant ' com.sun.star.awt.rectangle or array(X, Y, Width, Height) +Dim oPlace As Object ' com.sun.star.awt.rectangle +Dim oProcessManager As Object ' com.sun.star.lang.XMultiServiceFactory +Dim bBuiltInPython As Boolean ' True when context is present +Dim oModel As Object ' com.sun.star.awt.UnoControlDialogModel +Dim oView As Object ' com.sun.star.awt.UnoControlDialog +Dim vContext As Variant ' com.sun.star.uno.XComponentContext + +Const cstDialogModel = "com.sun.star.awt.UnoControlDialogModel" +Const cstDialogView = "com.sun.star.awt.UnoControlDialog" + + If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(pvArgs) Or IsEmpty(pvArgs) Then pvArgs = Array() + If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs) + If UBound(pvArgs) >= 0 Then vDialogName = pvArgs(0) Else vDialogName = Empty + If UBound(pvArgs) >= 1 Then vPlace = pvArgs(1) Else vPlace = Empty ' Use Empty to force the mandatory status + If IsMissing(vDialogName) Or IsEmpty(vDialogName) Then vDialogName = "DYNDIALOG" + If UBound(pvArgs) >= 2 Then vContext = pvArgs(2) Else Set vContext = Nothing + + If Not ScriptForge.SF_Utils._Validate(vDialogName, "DialogName", V_STRING) Then GoTo Finally + If IsArray(vPlace) Then + If Not ScriptForge.SF_Utils._ValidateArray(vPlace, "Place", 1, ScriptForge.V_NUMERIC, True) Then GoTo Finally + Else + If Not ScriptForge.SF_Utils._Validate(vPlace, "Place", ScriptForge.V_OBJECT) Then GoTo Finally + End If + If Not ScriptForge.SF_Utils._Validate(vContext, "Context", ScriptForge.V_OBJECT) Then GoTo Finally + + Set oDialog = Nothing + +Try: + ' Determine the process service manager and create the dialog model + If IsNull(vContext) Then ' Basic + Set oprocessManager = GetProcessServiceManager() + Set oModel = oProcessManager.createInstance(cstDialogModel) + bBuiltInPython = False + Else ' Python + Set oprocessManager = vContext.getServiceManager() + Set oModel = oProcessManager.createInstanceWithContext(cstDialogModel, vContext) + bBuiltInPython = True + End If + + oModel.Name = vDialogName + + ' Set dimension and position + With oModel + If IsArray(vPlace) Then + If UBound(vPlace) = 3 Then + .PositionX = vPlace(0) + .PositionY = vPlace(1) + .Width = vPlace(2) + .Height = vPlace(3) + End If + ElseIf ScriptForge.SF_Session.UnoObjectType(vPlace) = "com.sun.star.awt.Rectangle" Then + Set oPlace = vPlace + .PositionX = oPlace.X + .PositionY = oPlace.Y + .Width = oPlace.Width + .Height = oPlace.Height + Else + 'Leave everything to zero + End If + End With + + ' Create the view and associate model and view + Set oView = oprocessManager.createInstance(cstDialogView) + oView.setModel(oModel) + + ' Initialize the basic SF_Dialog instance to return to the user script + Set oDialog = New SF_Dialog + With oDialog + Set .[Me] = oDialog + ._Container = "" + ._Library = "" + ._BuiltFromScratch = True + ._BuiltInPython = bBuiltInPython + ._Name = vDialogName + Set ._DialogProvider = Nothing + Set ._DialogControl = oView + ._Initialize() + End With + +Finally: + Set _NewDialogFromScratch = oDialog + Exit Function +Catch: + GoTo Finally +End Function ' SFDialogs.SF_Register._NewDialogFromScratch + REM ============================================== END OF SFDIALOGS.SF_REGISTER </script:module> \ No newline at end of file