Macros/ScriptForge/BasicMacroOrganizer
"Basic Macro Organizer" clone - WiP
The objective is to illustrate that ‘Tabbed Dialogs’ can be designed with ScriptForge in a modular eased way. This reduces the need to develop dynamic dialogs scripts/macros - such as those using com.sun.star.awt.tab services - that require numerous and tedious graphical adjustments.
As far as ‘Basic Macro Organizer’ is concerned, this example serves no purpose at all.

How to run it in BASIC
- Download & Open the Writer file
- Select Tools-Macros-Run macro...
- Run Organizer.View.Main() Basic macro
How to run it in Python - WiP
- Download & Open the Writer file
- Select Tools-Macros-Run macro...
- Run document organizer.view.__init__.Main() script
Cloning "Basic Macro Organizer" Code
The above tabbed dialog embeds three pre-defined dialogs – ’Dialogs’, ’Libraries’ and ’Modules' – for the sake of design simplicity. Basic scripts are adapted from ”Query and update events actions” by JesperB and rewritten using ScriptForge. Routines are grouped in modules figuring out the Model-View-Controller design pattern. Tab click events are handled, tabs keys shortcuts are not supported. Most buttons controls are intentionally left unset.
Embedded controls versus Imported Controls
Using Abstract Windowing Toolkit tabbed dialog Uno Api requires to build a dynamic dialog in order to embed controls in API defined tabs. This is error-prone and subject to numerous graphical adjustments.
This example suggests to design each tab as individual dialogs via Basic IDE dialog editor. Dialog controls can be imported – using ScriptForge facilities - including associated event routines.
REM SCRIPTFORGE WIKI EXAMPLE
REM How to simulate a tabbed interface in a dialog ?
REM Minimal required version: LibreOffice 25.8
REM Used services
REM Dialog, DialogControl
Option Explicit
Option Compatible
Private SF_dialog As Object ' SFDialogs.Dialog
Private ui As Object ' ScriptForge.UI
'*******************************************************************
'* Run the demo below
'*******************************************************************
Public Sub Main(Optional Event As Object)
''' The VIEW part of the Model-View-Controller design pattern '''
'GlobalScope.BasicLibraries.loadLibrary("XrayTool")
GlobalScope.BasicLibraries.loadLibrary("ScriptForge")
ui = CreateScriptService("ScriptForge.UI") ' To display a progress status bar
ui.setStatusBar("🥁 Loading Macro Organizer 🥁")
ui.showProgressBar(,"🥁 Loading Macro Organizer 🥁")
Organizer.View.setupCompoundDialog()
Organizer.Controller.startListeners()
SF_dialog.Page = 1 : SF_dialog.Controls("Tabs").Value = 1
Organizer.Model.setModulesContent(SF_dialog) ' default tab 1
ui.setStatusBar() ' clear progress status bar
ui.showProgressBar() ' clear progress bar dialog
Organizer.View.SF_dialog.Execute() ' Tabbed compound dialog
Organizer.View.SF_dialog.Terminate
End Sub ' Main
'*******************************************************************
'* May be defined with the Basic IDE, except steps # 2-3
'*******************************************************************
Private Sub setupCompoundDialog
''' Setup 'Macro Organizer' dialog visual layout '''
Const MARGIN = 6
Const DLG_Width = 255 : Const DLG_Height = 245
Const BTN_Width = 45 : Const BTN_Height = 20
Dim btn As Object ' SFDialogs.DialogControl: Button
Dim tpc As Object ' SFDialogs.DialogControl: TabPageContainer
' 0. Dialog definition
SF_dialog = CreateScriptService("NewDialog", _
"MacroOrganizer", Array(250,60,DLG_Width,DLG_Height) )
With SF_dialog 'dialogModel
.Caption = "🥁 Basic Macro Organizer 🥁"
.Name = .Caption+ " dialog"
End With
' 1. Add two Buttons at bottom of dialog
btn = SF_dialog.createButton("Help", Array(MARGIN,220,BTN_Width,BTN_Height))
btn.Caption = btn.Name : btn.TabIndex = 3
btn = SF_dialog.createButton("Close", Array(34*MARGIN,220,BTN_Width,BTN_Height),push:="OK")
btn.Caption = btn.Name : btn.TabIndex = 4
' 2. Import tab controls from sub-dialog
Organizer.View.importDialogControls(1, "Organizer.Modules")
Organizer.View.importDialogControls(2, "Organizer.Dialogs")
Organizer.View.importDialogControls(3, "Organizer.Libraries")
' 3. Add TabPageContainer control
tpc = SF_dialog.createTabPageContainer( "Tabs", _
Place := Array(MARGIN,MARGIN,5*BTN_Width,BTN_Height), _
TabHeaders := Array("🧻 ~Modules","💬 ~Dialogs","📚 ~Libraries"), _
Border := "NONE" )
'Place := Array(MARGIN,MARGIN,DLG_Width - 15*MARGIN,DLG_Height - 35)
With tpc ' TabPageContainer
.TipText = "Use Ctrl+PgUp/Down to navigate tabs."
.Text = "Click to navigate tabs"
End With ' TabPageContainer
End Sub ' Organizer.View.SetupCompoundDialog
Private Sub importDialogControls(tabIdx%, subDlgName As String)
''' Embed sub-dialog controls into specified Page/Tab number
' Arguments:
' tabIdx Tab number [1..] aka dialog Page
' subDlgName A fully qualified 'document' dialog name
'''
Const OFFSET = 25, CTRLS_PERCENT = 1/20*100 ' 20 controls to import
Static progress As Single
Dim subDlg As Object ' SFDialogs.Dialog
Dim libraryName As String, dialogName As String, ctl As Object ' SFDialogs.Control: any
Dim ctrl As String, currentCtrl As Object ' SFDialogs.Control: any
libraryName = Split(subDlgName,".")(0) : dialogName = Split(subDlgName,".")(1)
subDlg = CreateScriptService( "Dialog", ThisComponent, libraryName, dialogName )
For Each ctrl In subDlg.Controls
currentCtrl = subDlg.Controls(ctrl) ' SFDialogs.DialogControl: Any
ctl = SF_dialog.importControl(subDlg, currentCtrl.Name, _
offsetY:=OFFSET, Page:=tabIdx, IncludeOnProperties:=True )
ctl.Page = tabIdx ' Define which controls belong to each page/tab
progress = progress + CTRLS_PERCENT
ui.setStatusBar("🥁 Loading Macro Organizer 🥁", progress)
ui.showProgressBar(, "🥁 Loading Macro Organizer 🥁", progress)
Next ' SFDialogs.DialogControl: any
End Sub ' Organizer.View.importDialogControls
### SCRIPTFORGE WIKI EXAMPLE
### How to simulate a tabbed interface in a dialog ?
### Minimal required version: LibreOffice 25.8
### Used services
### Dialog, DialogControl
Completing Dialog Content
’Modules’, ’Dialogs’ and ’Libraries’ tabs are edited as separate dialogs in order to be dynamically imported in main tabbed dialog. They are designed and tested individually, then embedded in the main tabbed dialog.
’Modules’ and ’Dialogs’ tree controls and ’Libraries’ grid control contents are filled using ScriptForge. Associated event routines transfer seamlessly when imported with ScriptForge.
Option Explicit
Private oLibraries As Object, library As String, libNode As Object
Sub setModulesContent( SF_dialog As Object ) ' SFDialog.Dialog object
Static isInitialized As Boolean
If IsInitialized Then Exit Sub
Dim tree As Object ' SFDialogs.DialogControl: TreeControl
Dim userRoot As Object, applRoot As Object, docRoot As Object, _
oLibrary As Object, module As String, modNode As Object
REM 1. Modules tab page content
tree = Sf_dialog.Controls("modTreeCtrl")
userRoot = tree.createRoot("My Macros", "My Macros")
applRoot = tree.createRoot("Application Macros", "Application Macros")
oLibraries = GlobalScope.BasicLibraries
With oLibraries ' Let's explore libraries
For Each library in .ElementNames
If .isLibraryReadOnly(library) Then ' "Application Macros"
libNode = tree.addSubNode(applRoot, library, library)
Else ' "My Macros"
libNode = tree.addSubNode(userRoot, library, library)
EndIf
oLibrary = .getByName(library)
With oLibrary ' Let's explore modules
For Each module in .ElementNames
modNode = tree.addSubNode(libNode, module, library &"."& module)
Next
End With ' Basic modules
Next
End With ' GlobalScope.BasicLibraries
docRoot = tree.createRoot(ThisComponent.Title, ThisComponent.URL)
oLibraries = BasicLibraries
With oLibraries ' Let's explore libraries
For Each library In .ElementNames
libNode = tree.addSubNode(docRoot, library, library)
oLibrary = .getByName(library)
With oLibrary ' Let's explore modules
For Each module in .ElementNames
modNode = tree.addSubNode(libNode, module, library &"."& module)
Next
End With ' Basic modules
Next
End With ' ThisComponent BasicLibraries
isInitialized = True
End Sub ' Organizer.Model.setModulesContent
Sub setDialogsContent( SF_dialog As Object ) ' SFDialog.Dialog object
Static isInitialized As Boolean
If IsInitialized Then Exit Sub
REM 2. Dialogs tab page content
Dim tree As Object ' SFDialogs.DialogControl: TreeControl
Dim userRoot As Object, applRoot As Object, docRoot As Object, _
oLibrary As Object, dialog As String, dlgNode As Object
REM 1. Dialogs tab page content
tree = Sf_dialog.Controls("dlgTreeCtrl")
userRoot = tree.createRoot("My Dialogs", "My Dialogs")
applRoot = tree.createRoot("Application Dialogs", "Application Dialogs")
oLibraries = GlobalScope.DialogLibraries
With oLibraries ' Let's explore libraries
For Each library in .ElementNames
If .isLibraryReadOnly(library) Then ' "Application Dialogs"
libNode = tree.addSubNode(applRoot, library, library)
Else ' "My Dialogs"
libNode = tree.addSubNode(userRoot, library, library)
EndIf
oLibrary = .getByName(library)
With oLibrary ' Let's explore dialogs
For Each dialog in .ElementNames
dlgNode = tree.addSubNode(libNode, dialog, library &"."& dialog)
Next
End With ' Dialogs
Next
End With ' GlobalScope.DialogLibraries
docRoot = tree.createRoot(ThisComponent.Title, ThisComponent.URL)
oLibraries = DialogLibraries
With oLibraries ' Let's explore libraries
For Each library In .ElementNames
libNode = tree.addSubNode(docRoot, library, library)
oLibrary = .getByName(library)
With oLibrary ' Let's explore dialogs
For Each dialog in .ElementNames
dlgNode = tree.addSubNode(libNode, dialog, library &"."& dialog)
Next
End With ' Dialogs
Next
End With ' ThisComponent DialogLibraries
isInitialized = True
End Sub ' Organizer.Model.setDialogsContent
Sub setLocation( SF_dialog As Object ) ' SFDialog.Dialog object
Dim location As Object ' SFDialog.DialogControl: ListBox
Dim ui As Object ' com.sun.star.awt.XlistBox
location = SF_dialog.Controls("libLocation")
ui = location.XControlView 'com.sun.star.awt.XlistBox
If ui.ItemCount = 2 Then
ui.addItem(ThisComponent.Title,2)
EndIf
End Sub
Sub setLibrariesContent( SF_dialog As Object ) ' SFDialog.Dialog object
Static isInitialized As Boolean
If IsInitialized Then Exit Sub
Dim location As Object ' SFDialog.DialogControl: ListBox
Dim ui As Object ' com.sun.star.awt.XlistBox
' 3. Libraries grid/table content
location = SF_dialog.Controls("libLocation")
ui = location.XControlView 'com.sun.star.awt.XlistBox
Organizer.Model.refreshLibraries(ui.SelectedItemPos)
setLocation(SF_dialog)
isInitialized = True
End Sub ' Organizer.Model.setLibrariesContent
Private Sub refreshLibraries( selectedItemPos As Integer )
' Triggered upon location UI selection
Dim grid As Object ' SFDialogs.DialogControl: TabPageContainer
Dim svc As Object ' ScriptForge.Array
Dim oContainer As Object
Dim location As String, tableData As Variant
grid = Organizer.View.SF_dialog.Controls("libGridCtrl")
svc = CreateScriptService("ScriptForge.Array")
tableData = Array("Name", "Location") ' Grid headers
oContainer = Switch( _
selectedItemPos <= 1, GlobalScope.BasicLibraries, _
selectedItemPos >= 2, BasicLibraries)
With oContainer ' library container
Select Case SelectedItemPos ' .selectedIem
Case 0 ' USER "My Macros & Dialogs"
For Each library in .ElementNames
If .isLibraryLink(library) Then
location = .getLibraryLinkURL(library)
Else
location = ""
End If
If Not .isLibraryReadOnly(library) Then _
tableData = svc.AppendRow(tableData, Array(library,location))
Next ' library name
Case 1 ' SHARED "Application Macros & Dialogs"
For Each library in .ElementNames
If .isLibraryLink(library) Then
location = .getLibraryLinkURL(library)
Else
location = ""
End If
If .isLibraryReadOnly(library) Then _
tableData = svc.AppendRow(tableData, Array(library,location))
Next ' library name
Case 2 ' DOCUMENT ThisComponent.Title
For Each library in .ElementNames
tableData = svc.AppendRow(tableData, Array(library,""))
Next ' library name
Case Else : tableData = svc.AppendRow(tableData, Array("🧨 bang!","Unexpected library location"))
End Select
End With ' oContainer library container
grid.setTableData(tableData, widths:=Array(1,2), alignments:="LL")
End Sub ' Organizer.Model.refreshLibraries
# to be continued
Handling Dialog Events
Dialog Editor Events
Events defined with Basic IDE Dialog Editor – during individual dialog tests - are incompatible with ScriptForge. They need to be removed from dialogs – before import – and their code may require minor ScriptForge adaptation.
Dialog Event Handlers
Dialog Editor event handlers can not be used, although they offer coding advantages.
Controls Listeners
API listeners can be used. However ScriptForge ’SFDialogs’ library simplifies the management of events.
Option Explicit
''' DIALOG EDITOR EVENTS '''''''''''''''''''''''''''''''''''''''''''
Sub notImplemented(event As com.sun.star.lang.EventObject)
Print "Not implemented yet", ',, event.Source.Name
End Sub
Sub IDESelectedLocation(event As com.sun.star.awt.ItemEvent)
'xray event
Select Case event.Selected
Case 0,1,2 : Organizer.Model.refreshLibraries(event.Source.selectedItemPos)
Case Else : notImplemented(event)
End Select
End Sub
''' SCRIPTFORGE EVENTS '''''''''''''''''''''''''''''''''''''''''''''
Sub TabSwitch(Optional event As com.sun.star.awt.tab.TabPageActivatedEvent)
''' Fired by a tab selection (OnTabSelected) '''
Dim tab As Object ' SFDialogs.DialogControl: TabPageContainer
Set tab = CreateScriptService("DialogEvent", event)
If IsNull(event) Then Exit Sub
' The Value property returns the selected tab
If Not IsNull(tab) Then
SF_dialog.Page = tab.Value
Select Case tab.Value
Case 1 : Organizer.Model.SetModulesContent(tab.Parent)
Case 2 : Organizer.Model.SetDialogsContent(tab.Parent)
Case 3 : Organizer.Model.SetLibrariesContent(tab.Parent)
End Select
EndIf
End Sub
Sub onNodeExpanded(event As com.sun.star.awt.tree.TreeExpansionEvent)
Dim node As Object, parent As Object, branch As String, path As String
Set node = event.Node : Set parent = node.Parent
branch = node.DisplayValue ': path = node.DataValue
'Print branch, 'path, ' output when finished script
End Sub
Sub onNodeSelected(event As com.sun.star.lang.EventObject)
''' Triggered by the OnNodeSelected event '''
Dim treecontrol As Object ' A SFDialogs.DialogControl service instance
Dim node As Object ' com.sun.star.awt.tree.XTreeNode
If IsNull(event) Then Exit Sub
Set treecontrol = CreateScriptService("DialogEvent", event)
If isEmpty(treecontrol.currentNode) Then Exit Sub ' This is a root
' Display the content of the selected node when it is not expandable
node = treecontrol.CurrentNode
If node.ChildCount = 0 Then
'Print "Selection is ", node.DisplayValue,
End If
End Sub
'''
Private Sub setupEvents(objName As String)
''' ScriptForge handled events: Tabs, Modules, Dialogs ... '''
Dim tree As Object ' SFDialogs.DialogControl: TreeControl
Select Case objName
Case "Tabs"
Organizer.View.SF_dialog.Controls("Tabs").OnTabSelected = _
"vnd.sun.star.script:Organizer.Controller.TabSwitch?"& _
"language=Basic&location=document"
Case "Modules"
tree = Sf_dialog.Controls("modTreeCtrl")
tree.OnNodeSelected = _
"vnd.sun.star.script:Organizer.Controller.OnNodeSelected"& _
"?language=Basic&location=document"
tree.OnNodeExpanded = _
"vnd.sun.star.script:Organizer.Controller.OnNodeExpanded"& _
"?language=Basic&location=document"
Case "Dialogs"
tree = Organizer.View.Sf_dialog.Controls("dlgTreeCtrl")
tree.OnNodeSelected = _
"vnd.sun.star.script:Organizer.Controller.OnNodeSelected"& _
"?language=Basic&location=document"
tree.OnNodeExpanded = _
"vnd.sun.star.script:Organizer.Controller.OnNodeExpanded"& _
"?language=Basic&location=document"
Case "Libraries"
' Event originally validated using IDE Dialog Editor events
Organizer.View.SF_dialog.Controls("libLocation").OnItemStateChanged = _
"vnd.sun.star.script:Organizer.Controller.IDESelectedLocation"& _
"?language=Basic&location=document"
End Select
End Sub
Private Sub startListeners
setupEvents("Tabs")
setupEvents("Modules")
setupEvents("Dialogs")
setupEvents("Libraries")
End Sub ' startListeners
Sub stopListeners(Optional dlg As com.sun.star.awt.UnoControlDialog)
''' Stop event handlers at dialog closing time
'''
End Sub ' stopListeners
''' API LISTENERS BELOW ''''''''''''''''''''''''''''''''''''''''''''
# to be continued
Importing document-based libraries - Python only - WiP
REM not applicable
Open Document to run example dialog
- Download example Writer file containing Basic/Python scripts