<?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_ContextMenu" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM ===			The ScriptForge library and its associated libraries are part of the LibreOffice project.				===
REM	===						The SFWidgets library is one of the associated libraries.									===
REM ===					Full documentation is available on https://help.libreoffice.org/								===
REM =======================================================================================================================

Option Compatible
Option ClassModule

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_ContextMenu
&apos;&apos;&apos;	==============
&apos;&apos;&apos;		Complete pr replace a predefined context menu with new items.
&apos;&apos;&apos;
&apos;&apos;&apos;		A context menu is obtained by a right-click on several areas of a document.
&apos;&apos;&apos;		Each area determines its own context menu.
&apos;&apos;&apos;		(Consider right-clicking on a cell or on a sheet tab in a Calc document).
&apos;&apos;&apos;		Each component model has its own set of context menus.
&apos;&apos;&apos;
&apos;&apos;&apos;		A context menu is usually predefined at LibreOffice installation.
&apos;&apos;&apos;		Customization is done statically with the Tools + Customize dialog.
&apos;&apos;&apos;		The actual service provides tools to configure a context menu preset in an open document.
&apos;&apos;&apos;		Context menus configured at LibreOffice installation level are not included.
&apos;&apos;&apos;		The service provides a mean to
&apos;&apos;&apos;			- make temporary additions at the bottom of a context menu
&apos;&apos;&apos;			- replace a context menu.
&apos;&apos;&apos;		The changes are lost when the document is closed.
&apos;&apos;&apos;		The context menu may be emptied, which gives the illusion that the right-click is inactive.
&apos;&apos;&apos;
&apos;&apos;&apos;		The name of a context menu is the last component of the resource URL:
&apos;&apos;&apos;			&quot;private:resource/popupmenu/the-name-here&quot;
&apos;&apos;&apos;
&apos;&apos;&apos;		Context menu items are either:
&apos;&apos;&apos;			- usual items
&apos;&apos;&apos;			- line separators
&apos;&apos;&apos;		Checkboxes or radio buttons are not supported.
&apos;&apos;&apos;
&apos;&apos;&apos;		A context menu is setup in next sequence:
&apos;&apos;&apos;			1. Define each menu item, submenu or line separator with AddItem(...)
&apos;&apos;&apos;			2. Activate() the local menu to make it visible and active
&apos;&apos;&apos;			3. If meaningful, it can be temporarily deactivated with Activate(False)
&apos;&apos;&apos;			   In that case, the menu defined at LibreOffice level takes the lead.
&apos;&apos;&apos;			4. Items can be optionally added, and the menu might be made active again
&apos;&apos;&apos;			5, RemoveAllItems(...) optionally removes all standard and all added items
&apos;&apos;&apos;			6. Dispose() cleans the memory resources
&apos;&apos;&apos;
&apos;&apos;&apos;		Definitions:
&apos;&apos;&apos;			SubmenuCharacter: the character or the character string that identifies how menus are cascading
&apos;&apos;&apos;				Default = &quot;&gt;&quot;
&apos;&apos;&apos;				Can be set when invoking the ContextMenu service
&apos;&apos;&apos;			ShortcutCharacter: the underline access key character
&apos;&apos;&apos;				Default = &quot;~&quot;
&apos;&apos;&apos;
&apos;&apos;&apos;		Service invocation:
&apos;&apos;&apos;			From a document, calc, writer, formdocument or datasheet service:
&apos;&apos;&apos;				Dim MenusList As Variant, oCtxMenu As Object
&apos;&apos;&apos;				MenusList = doc.ContextMenus()
&apos;&apos;&apos;												&apos;	Returns a list of available context menus as strings
&apos;&apos;&apos;				Set oCtxMenu = doc.ContextMenus(ContextMenuName, SubmenuChar = &quot;&gt;&gt;&quot;)
&apos;&apos;&apos;												&apos;	Returns a context menu service instance
&apos;&apos;&apos;
&apos;&apos;&apos;		Menus and submenus
&apos;&apos;&apos;			To create a context menu with submenus, use the character defined in the
&apos;&apos;&apos;			SubmenuCharacter property while creating the menu entry to define where it will be
&apos;&apos;&apos;			placed. For instance, consider the following menu/submenu hierarchy.
&apos;&apos;&apos;				Item A
&apos;&apos;&apos;				Item B &gt; Item B.1
&apos;&apos;&apos;						 Item B.2
&apos;&apos;&apos;				------							(line separator)
&apos;&apos;&apos;				Item C &gt; Item C.1 &gt; Item C.1.1
&apos;&apos;&apos;									Item C.1.2
&apos;&apos;&apos;				Item C &gt; ------					(line separator)
&apos;&apos;&apos;				Item C &gt; Item C.2 &gt; Item C.2.1
&apos;&apos;&apos;									Item C.2.2
&apos;&apos;&apos;			Next code will create the menu/submenu hierarchy
&apos;&apos;&apos;				With myMenu
&apos;&apos;&apos;					.AddItem(&quot;Item A&quot;, Command := ...)
&apos;&apos;&apos;					.AddItem(&quot;Item B&gt;Item B.1&quot;, Script := ...)
&apos;&apos;&apos;					.AddItem(&quot;Item B&gt;Item B.2&quot;, ...)
&apos;&apos;&apos;					.AddItem(&quot;---&quot;)
&apos;&apos;&apos;					.AddItem(&quot;Item C&gt;Item C.1&gt;Item C.1.1&quot;, ...)
&apos;&apos;&apos;					.AddItem(&quot;Item C&gt;Item C.1&gt;Item C.1.2&quot;, ...)
&apos;&apos;&apos;					.AddItem(&quot;Item C&gt;---&quot;)
&apos;&apos;&apos;					.AddItem(&quot;Item C&gt;Item C.2&gt;Item C.2.1&quot;, ...)
&apos;&apos;&apos;					.AddItem(&quot;Item C&gt;Item C.2&gt;Item C.2.2&quot;, ...)
&apos;&apos;&apos;				End With
&apos;&apos;&apos;
&apos;&apos;&apos;		Example: Add 2 items to the general context menu of a Writer document
&apos;&apos;&apos;			Sub SetContextMenu(oWriter As Object)
&apos;&apos;&apos;			Dim oMenu As Object
&apos;&apos;&apos;				Set oMenu = oWriter.ContextMenus(&quot;text&quot;)
&apos;&apos;&apos;				With oMenu
&apos;&apos;&apos;					.AddItem(&quot;About&quot;, Command := &quot;.uno:About&quot;)
&apos;&apos;&apos;					.AddItem(&quot;Run&quot;, Script := &quot;vnd.sun.star.script:Standard.Module1.MyFunction?language=Basic&amp;location=document&quot;)
&apos;&apos;&apos;							&apos;	MyFunction is a Sub without argument
&apos;&apos;&apos;					.Activate()
&apos;&apos;&apos;					.Dispose()
&apos;&apos;&apos;				End With
&apos;&apos;&apos;			End Sub
&apos;&apos;&apos;
&apos;&apos;&apos;
&apos;&apos;&apos;		Detailed user documentation:
&apos;&apos;&apos;			https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_contextmenu.html?DbPAR=BASIC
&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 ============================================================= PRIVATE MEMBERS

Private [Me]				As Object
Private ObjectType			As String		&apos; Must be ContextMenu
Private ServiceName 		As String


&apos;	Menu descriptors
Private Component			As Object		&apos; com.sun.star.lang.XComponent
Private DocumentType		As String		&apos; &quot;Calc&quot;, &quot;Writer&quot;, ...
Private ResourceURL			As String		&apos; private:resource/popupmenu/...
Private ConfigManager		As Object		&apos; com.sun.star.ui.XUIConfigurationManager
Private MenuTree			As Variant		&apos; Dictionary nodename - com.sun.star.ui.ItemDescriptor pair
Private SubmenuChar			As String		&apos; Delimiter in menu trees
Private MenuContainer		As Object		&apos; com.sun.star.container.XIndexAccess

REM ============================================================ MODULE CONSTANTS

Private Const _UnderlineAccessKeyChar	= &quot;~&quot;
Private Const _DefaultSubmenuChar		= &quot;&gt;&quot;
Private Const _SeparatorChar			= &quot;---&quot;
Private Const cstUnoPrefix				= &quot;.uno:&quot;

REM ====================================================== CONSTRUCTOR/DESTRUCTOR

REM -----------------------------------------------------------------------------
Private Sub Class_Initialize()
	Set [Me] = Nothing
	ObjectType = &quot;ContextMenu&quot;
	ServiceName = &quot;SFWidgets.ContextMenu&quot;
	Set Component = Nothing
	DocumentType = &quot;&quot;
	Set ConfigManager = Nothing
	ResourceURL = &quot;&quot;
	Set MenuTree = Nothing
	SubmenuChar = _DefaultSubmenuChar
	Set MenuContainer = Nothing
End Sub		&apos;	SFWidgets.SF_ContextMenu Constructor

REM -----------------------------------------------------------------------------
Private Sub Class_Terminate()
	Call Class_Initialize()
End Sub		&apos;	SFWidgets.SF_ContextMenu Destructor

REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
	If Not IsNull(MenuTree) Then Set MenuTree = MenuTree.Dispose()
	Call Class_Terminate()
	Set Dispose = Nothing
End Function	&apos;	SFWidgets.SF_ContextMenu Explicit Destructor

REM ================================================================== PROPERTIES

REM -----------------------------------------------------------------------------
Property Get ParentDocument() As Variant
&apos;&apos;&apos;	The ParentDocument property returns a document class (or one of its subclasses) instance
	ParentDocument = _PropertyGet(&quot;ParentDocument&quot;)
End Property	&apos;	SFWidgets.SF_ContextMenu.ParentDocument (get)

REM -----------------------------------------------------------------------------
Property Get ShortcutCharacter() As Variant
&apos;&apos;&apos;	The ShortcutCharacter property specifies character preceding the underline access key
	ShortcutCharacter = _PropertyGet(&quot;ShortcutCharacter&quot;)
End Property	&apos;	SFWidgets.SF_ContextMenu.ShortcutCharacter (get)

REM -----------------------------------------------------------------------------
Property Get SubmenuCharacter() As Variant
&apos;&apos;&apos;	The SubmenuCharacter property specifies the character string indicating
&apos;&apos;&apos;	a sub-menu in a popup menu item
	SubmenuCharacter = _PropertyGet(&quot;SubmenuCharacter&quot;)
End Property	&apos;	SFWidgets.SF_ContextMenu.SubmenuCharacter (get)

REM ===================================================================== METHODS

REM -----------------------------------------------------------------------------
Public Sub Activate(Optional ByVal Enable As Variant) As Variant
&apos;&apos;&apos;	Make the added items of the context menu stored in the document available for execution,
&apos;&apos;&apos;	or, at the opposite, disable them, depending on the argument.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Enable: When True (default), the local menu stored in the document is made active.
&apos;&apos;&apos;			When False, the global menu defined at LibreOffice level takes the precedence.
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		None
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		Sub SetContextMenu(oWriter As Object)	&apos;	oWriter is a SFDocuments.SF_Writer service instance
&apos;&apos;&apos;		Dim oMenu As Object
&apos;&apos;&apos;			Set oMenu = oWriter.ContextMenus(&quot;text&quot;)
&apos;&apos;&apos;			With oMenu
&apos;&apos;&apos;				.AddItem(&quot;About&quot;, Command := &quot;.uno:About&quot;)
&apos;&apos;&apos;				.AddItem(&quot;Run&quot;, Script := &quot;vnd.sun.star.script:Standard.Module1.MyFunction?language=Basic&amp;location=document&quot;)
&apos;&apos;&apos;						&apos;	MyFunction is a Sub without argument
&apos;&apos;&apos;				.Activate()
&apos;&apos;&apos;				.Dispose()		&apos;	Do not dispose() if you plan later on to Activate(Enable := False) the context menu
&apos;&apos;&apos;			End With
&apos;&apos;&apos;		End Sub

Dim bSettings As Boolean		&apos;	When True, the menu is already stored

Const cstThisSub = &quot;SFWidgets.ContextMenu.Activate&quot;
Const cstSubArgs = &quot;[Enable=True]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(Enable) Or IsEmpty(Enable) Then Enable = True
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(Enable, &quot;Enable&quot;, ScriptForge.V_BOOLEAN) Then GoTo Catch
	End If
	If IsNull(ConfigManager) Or IsNull(MenuContainer) Then GoTo Finally

Try:
	With ConfigManager
		bSettings = .hasSettings(ResourceURL)
		If Enable And bSettings Then
			.replaceSettings(ResourceURL, MenuContainer)
		ElseIf Enable And Not bSettings Then
			.insertSettings(ResourceURL, MenuContainer)
		ElseIf Not Enable And bSettings Then
			.removeSettings(ResourceURL)
		Else
			&apos;	Nothing to deactivate
		End If
	End With

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
End Sub			&apos;	SFWidgets.SF_ContextMenu.Activate

REM -----------------------------------------------------------------------------
Public Sub AddItem(Optional ByVal MenuItem As Variant _
								, Optional ByVal Command As Variant _
								, Optional ByVal Script As Variant _
								) As Integer
&apos;&apos;&apos;	Insert in the context menu a new entry
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		MenuItem: The text to be displayed in the menu entry.
&apos;&apos;&apos;			It determines also the hierarchy of the popup menu
&apos;&apos;&apos;			It is made up of all the components (separated by the &quot;SubmenuCharacter&quot;) of the menu branch
&apos;&apos;&apos;				Example:	A&gt;B&gt;C		means &quot;C&quot; is a new entry in submenu &quot;A =&gt; B =&gt;&quot;
&apos;&apos;&apos;			If the last component is equal to &quot;---&quot;, a line separator is inserted and all other arguments are ignored
&apos;&apos;&apos;		Command: A menu command like &quot;.uno:About&quot;. The validity of the command is not checked.
&apos;&apos;&apos;		Script: a Basic or Python script (determined by its URI notation) to be run when the item is clicked
&apos;&apos;&apos;			Read https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
&apos;&apos;&apos;			No argument will be passed to the called script.
&apos;&apos;&apos;		Arguments Command and Script are mutually exclusive.
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		None
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myMenu.AddItem(&quot;Menu top&gt;Item 1&quot;, Command := &quot;About&quot;)
&apos;&apos;&apos;		myMenu.AddItem(&quot;Menu top&gt;Item 2&quot;, Script := &quot;vnd.sun.star.script:myLib.Module1.ThisSub?language=Basic&amp;location=document&quot;)

Dim sCommand As String			&apos;	Alias of either Command or Script
Dim vSplit As Variant			&apos;	Split menu item
Dim sMenu As String				&apos;	Submenu where to attach the new item, as a string
Dim oMenu As Object				&apos;	Submenu where to attach the new item, as an object
Dim sName As String				&apos;	The text displayed in the menu box
Dim vEntry As Variant			&apos;	com.sun.star.ui.ItemDescriptor

Const cstThisSub = &quot;SFWidgets.ContextMenu.AddItem&quot;
Const cstSubArgs = &quot;MenuItem, [Command=&quot;&quot;&quot;&quot;], [Script=&quot;&quot;&quot;&quot;]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(Command) Or IsEmpty(Command) Then Command = &quot;&quot;
	If IsMissing(Script) Or IsEmpty(Script) Then Script = &quot;&quot;
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(MenuItem, &quot;MenuItem&quot;, V_STRING) Then GoTo Catch
		If Not ScriptForge.SF_Utils._Validate(Command, &quot;Command&quot;, V_STRING) Then GoTo Catch
		If Not ScriptForge.SF_Utils._Validate(Script, &quot;Script&quot;, V_STRING) Then GoTo Catch
	End If

	If Len(Command) &gt; 0 Then
		If Left(Command, Len(cstUnoPrefix)) = cstUnoPrefix Then sCommand = Command Else sCommand = cstUnoPrefix &amp; Command
	Else
		sCommand = Script
	End If

Try:
	&apos;	Run through the upper menu tree
	vSplit = _SplitMenuItem(MenuItem)

	&apos;	Create and determine the menu to which to attach the new item
	sMenu = vSplit(0)
	Set oMenu = _GetContextMenu(sMenu)	&apos;	Run through the upper menu tree and retain the last branch

	&apos;	Insert the new item
	sName = vSplit(1)
	With ScriptForge.SF_Utils
		vEntry = Array(	._MakePropertyValue(&quot;Type&quot;, Iif(sName = _SeparatorChar, _
														com.sun.star.ui.ItemType.SEPARATOR_LINE, _
														com.sun.star.ui.ItemType.DEFAULT)) _
											, ._MakePropertyValue(&quot;Label&quot;, Iif(sName = _SeparatorChar, &quot;&quot;, sName)) _
											, ._MakePropertyValue(&quot;CommandURL&quot;, sCommand) _
											, ._MakePropertyValue(&quot;HelpURL&quot;, &quot;&quot;) _
											, ._MakePropertyValue(&quot;Style&quot;, _
													Iif(Len(Script) &gt; 0, 0, com.sun.star.ui.ItemStyle.ICON)) _
											, ._MakePropertyValue(&quot;ItemDescriptorContainer&quot;, Null) _
											)
		oMenu.insertByIndex(oMenu.Count, vEntry)
	End With

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
End Sub			&apos;	SFWidgets.SF_ContextMenu.AddItem

REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
&apos;&apos;&apos;	Return the actual value of the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The actual value of the property
&apos;&apos;&apos;		If the property does not exist, returns Null
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		see the exceptions of the individual properties
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myModel.GetProperty(&quot;MyProperty&quot;)

Const cstThisSub = &quot;SFWidgets.ContextMenu.GetProperty&quot;
Const cstSubArgs = &quot;&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	GetProperty = Null

Check:
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	GetProperty = _PropertyGet(PropertyName)

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFWidgets.SF_ContextMenu.GetProperty

REM -----------------------------------------------------------------------------
Public Function Methods() As Variant
&apos;&apos;&apos;	Return the list of public methods of the Model service as an array

	Methods = Array( _
					&quot;Activate&quot; _
					, &quot;AddItem&quot; _
					, &quot;Execute&quot; _
					, &quot;RemoveAllItems&quot; _
					)

End Function	&apos;	SFWidgets.SF_ContextMenu.Methods

REM -----------------------------------------------------------------------------
Public Function Properties() As Variant
&apos;&apos;&apos;	Return the list or properties of the Timer a.AddItem(&quot;B&gt;B1&quot;)class as an array

	Properties = Array( _
					&quot;ParentDocument&quot; _
					, &quot;ShortcutCharacter&quot; _
					, &quot;SubmenuCharacter&quot; _
					)

End Function	&apos;	SFWidgets.SF_ContextMenu.Properties

REM -----------------------------------------------------------------------------
Public Sub RemoveAllItems()
&apos;&apos;&apos;	Remove all items, both
&apos;&apos;&apos;		- predefined with Tools + Customize and saved in the document
&apos;&apos;&apos;		- added by contextmenu.AddItem()
&apos;&apos;&apos;	Adding custom items (AddItem) remains possible.
&apos;&apos;&apos;	This action cannot be reverted except by closing and reopening the document.
&apos;&apos;&apos;	Example:	Associate next Sub with the on-right-click event of a sheet.
&apos;&apos;&apos;			=&gt;	The custom menu appears when right-clicking in column C, otherwise normal behaviour
&apos;&apos;&apos;		Sub OnRightClick1(Optional XRange)	&apos;	Xrange is a com.sun.star.table.XCellRange
&apos;&apos;&apos;		Dim calc, menu, bColumn
&apos;&apos;&apos;			Set calc = CreateScriptService(&quot;Calc&quot;, ThisComponent)
&apos;&apos;&apos;			Set menu = calc.ContextMenus(&quot;cell&quot;)
&apos;&apos;&apos;			menu.RemoveAllItems()
&apos;&apos;&apos;			bColumn =  ( Len(calc.Intersect(&quot;Sheet1.$C:$C&quot;, XRange.AbsoluteName)) &gt; 0 )
&apos;&apos;&apos;			If bColumn Then
&apos;&apos;&apos;				menu.AddItem(&quot;A&quot;, Script := &quot;vnd.sun.star.script:Standard.Module1.EnterA?language=Basic&amp;location=document&quot;)
&apos;&apos;&apos;				...
&apos;&apos;&apos;			End If
&apos;&apos;&apos;			menu.Activate(bColumn)
&apos;&apos;&apos;		End Sub

Dim i As Long

Const cstThisSub = &quot;SFWidgets.ContextMenu.RemoveAllItems&quot;
Const cstSubArgs = &quot;&quot;

Check:
	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

Try:
	For i = MenuContainer.Count - 1 To 0 Step -1
		MenuContainer.removeByIndex(i)
	Next i

	With ConfigManager
		If .hasSettings(ResourceURL) Then
&apos;			.removeSettings(ResourceURL)
			.replaceSettings(ResourceURL, MenuContainer)
		Else
			.insertSettings(ResourceURL, MenuContainer)
		End If
	End With

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
End Sub			&apos;	SFWidgets.SF_ContextMenu.RemoveAllItems

REM -----------------------------------------------------------------------------
Public Function SetProperty(Optional ByVal PropertyName As Variant _
								, Optional ByRef Value As Variant _
								) As Boolean
&apos;&apos;&apos;	Set a new value to the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;		Value: its new value
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist

Const cstThisSub = &quot;SFWidgets.ContextMenu.SetProperty&quot;
Const cstSubArgs = &quot;PropertyName, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	SetProperty = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	SetProperty = _PropertySet(PropertyName, Value)

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFWidgets.SF_ContextMenu.SetProperty

REM =========================================================== PRIVATE FUNCTIONS

REM -----------------------------------------------------------------------------
Private Function _GetContextMenu(ByVal psSubmenu As String) As Object
&apos;&apos;&apos;	Get the context menu node corresponding with the string in argument
&apos;&apos;&apos;	A context menu entry is represented by a set of property values
&apos;&apos;&apos;	grouped as a ItemDescriptor entry in above array.
&apos;&apos;&apos;	The menu node is stored in a com.cun.star.ItemDescriptor service
&apos;&apos;&apos;	If the menu entry exists, it is found in the MenuTree dictionary
&apos;&apos;&apos;	If it does not exist, it is created recursively.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psSubmenu: a string like &quot;A&gt;B&quot;
&apos;&apos;&apos;	Returns
&apos;&apos;&apos;		A com.sun.star.container.XIndexAccess object
&apos;&apos;&apos;	Example
&apos;&apos;&apos;		If psSubmenu = &quot;A&gt;B&gt;C&gt;D&quot;, and the initial menu container is empty,
&apos;&apos;&apos;			- &quot;A&quot;, &quot;A&gt;B&quot;, &quot;A&gt;B&gt;C&quot;, &quot;A&gt;B&gt;C&gt;D&quot; should be created
&apos;&apos;&apos;			- the popup menu corresponding with &quot;A&gt;B&gt;C&gt;D&quot; should be returned

Dim oContext As Object				&apos;	Return value
Dim vSplit As Variant				&apos;	An array as returned by Split()
Dim sMenu As String					&apos;	The left part of psSubmenu
Dim vMenu As Variant				&apos;	Array of property values =&gt; com.sun.star.ui.ItemDescriptor
Dim oLastMenu As Object				&apos;	com.sun.star.ui.ItemDescriptor.ItemDescriptorContainer
Dim oNewMenu As Object				&apos;	com.sun.star.ui.ItemDescriptor.ItemDescriptorContainer
Dim i As Long

	Set oContext = Nothing

Try:
	With ScriptForge.SF_Utils
		If Len(psSubmenu) = 0 Then					&apos;	Menu starts at the root
			Set oContext = MenuContainer
		ElseIf MenuTree.Exists(psSubmenu) Then		&apos;	Shortcut: if the submenu exists, get it directly
			Set oContext = ._GetPropertyValue(MenuTree.Item(psSubmenu), &quot;ItemDescriptorContainer&quot;)
		Else										&apos;	Build the tree
			vSplit = Split(psSubmenu, SubmenuChar)
			&apos;	Search the successive submenus in the MenuTree dictionary, If not found, create a new entry
			Set oLastMenu = MenuContainer
			For i = 0 To UBound(vSplit)
				sMenu = Join(ScriptForge.SF_Array.Slice(vSplit, 0, i), SubmenuChar)
				If MenuTree.Exists(sMenu) Then
					Set oNewMenu = ._GetPropertyValue(MenuTree.Item(sMenu), &quot;ItemDescriptorContainer&quot;)
				Else
					&apos;	Insert the new menu tree item at the bottom of the tree branch
					Set oNewMenu = MenuContainer.createInstanceWithContext(GetDefaultContext())
					vMenu = Array(	._MakePropertyValue(&quot;Type&quot;, com.sun.star.ui.ItemType.DEFAULT) _
										, ._MakePropertyValue(&quot;Label&quot;, vSplit(i)) _
										, ._MakePropertyValue(&quot;CommandURL&quot;, &quot;&quot;) _
										, ._MakePropertyValue(&quot;HelpURL&quot;, &quot;&quot;) _
										, ._MakePropertyValue(&quot;Style&quot;, 0) _
										, ._MakePropertyValue(&quot;ItemDescriptorContainer&quot;, oNewMenu) _
										)
					oLastMenu.insertByIndex(oLastMenu.Count, vMenu)
					MenuTree.Add(sMenu, vMenu)
				End If
				Set oLastMenu = oNewMenu
			Next i
			Set oContext = oLastMenu
	End If
	End With

Finally:
	Set _GetContextMenu = oContext
	Exit Function
End Function	&apos;	SFWidgets.SF_ContextMenu._GetContextMenu

REM -----------------------------------------------------------------------------
Public Sub _Initialize(ByRef poComponent As Object _
						, ByVal psContextMenuName As String _
						, ByVal psSubmenuChar As String _
						)
&apos;&apos;&apos;	Complete the object creation process:
&apos;&apos;&apos;		- Initialize the dictionary
&apos;&apos;&apos;		- initialize the root popup menu
&apos;&apos;&apos;		- store the arguments for later use
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Component: the document&apos;s component requesting a context menu
&apos;&apos;&apos;		ContextMenuName: a private:resource/popupmenu/... reference
&apos;&apos;&apos;		SubmenuChar: Delimiter used in menu trees

Dim oSupplier As Object			&apos;	/singletons/com.sun.star.ui.theModuleUIConfigurationManagerSupplier
Dim sComponentType As String	&apos;	Argument to determine the system config manager, ex. &quot;com.sun.star.text.TextDocument&quot;
Dim MainConfigManager As Object	&apos;	com.sun.star.ui.XUIConfigurationManager

Try:
	&apos;	Initialize the dictionary
	Set MenuTree = ScriptForge.SF_Services.CreateScriptService(&quot;Dictionary&quot;, True)	&apos;	with case-sensitive comparison of keys

	&apos;	Identify the container of the menu tree
	&apos;		The container is taken either from the system configuration manager of from the local (= in document) one
	&apos;		It is saved in the local version when the menu is Executed()
	Set oSupplier = ScriptForge.SF_Utils._GetUNOService(&quot;ModuleUIConfigurationManagerSupplier&quot;)
	sComponentType = ScriptForge.SF_UI._GetConfigurationManager(poComponent)
	Set MainConfigManager = oSupplier.getUIConfigurationManager(sComponentType)
	Set ConfigManager = poComponent.getUIConfigurationManager(sComponentType)
	&apos;	Copy from system config manager if not found in local (= in document) one
	If ConfigManager.hasSettings(psContextMenuName) Then
		Set MenuContainer = ConfigManager.getSettings(psContextMenuName, true)
	Else
		Set MenuContainer = MainConfigManager.getSettings(psContextMenuName, true)
	End If

	&apos;	Store the private instance properties
	Set Component = poComponent
	ResourceURL = psContextMenuName
	If Len(psSubmenuChar) &gt; 0 Then SubmenuChar = psSubmenuChar

Finally:
	Exit Sub
End Sub			&apos;	SFWidgets.SF_ContextMenu._Initialize

REM -----------------------------------------------------------------------------
Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant
&apos;&apos;&apos;	Return the value of the named property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psProperty: the name of the property

Dim vGet As Variant							&apos;	Return value
Dim oWindow As Object						&apos;	SF_UI.Window object
Dim cstThisSub As String
Const cstSubArgs = &quot;&quot;

	cstThisSub = &quot;SFWidgets.ContextMenu.get&quot; &amp; psProperty
	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

	ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
	_PropertyGet = Null

	Select Case UCase(psProperty)
		Case UCase(&quot;ParentDocument&quot;)
			If Not IsNull(Component) Then
				Set oWindow = ScriptForge.SF_UI._IdentifyWindow(Component)
				If Not IsNull(oWindow) Then Set _PropertyGet = CreateScriptService(oWindow.DocumentType, Component) Else Set _PropertyGet = Nothing
			End If
		Case UCase(&quot;ShortcutCharacter&quot;)
			_PropertyGet = _UnderlineAccessKeyChar
		Case UCase(&quot;SubmenuCharacter&quot;)
			_PropertyGet = SubmenuChar
		Case Else
			_PropertyGet = Null
	End Select

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFWidgets.SF_ContextMenu._PropertyGet

REM -----------------------------------------------------------------------------
Private Function _Repr() As String
&apos;&apos;&apos;	Convert the SF_ContextMenu instance to a readable string, typically for debugging purposes (DebugPrint ...)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Return:
&apos;&apos;&apos;		&quot;[ContextMenu]: Name, Menu entries
	_Repr = &quot;[ContextMenu]: &quot; &amp; ResourceURL &amp; &quot;, &quot; &amp; SF_String.Represent(MenuTree.Keys())

End Function	&apos;	SFWidgets.SF_ContextMenu._Repr

REM -----------------------------------------------------------------------------
Private Function _SplitMenuItem(ByVal psMenuItem As String ) As Variant
&apos;&apos;&apos;	Split a menu item given as a string and delimited by the submenu character
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psMenuItem: a string like &quot;A&gt;B&gt;C&quot;
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		An array:	[0] = &quot;A&gt;B&quot;
&apos;&apos;&apos;					[1] = &quot;C&quot;

Dim vReturn(0 To 1)	As String			&apos;	Return value
Dim vMenus()		As Variant			&apos;	Array of menus

Try:
	vMenus = Split(psMenuItem, SubmenuChar)
	vReturn(1) = vMenus(UBound(vMenus))
	vReturn(0) = Left(psMenuItem, Len(psMenuItem) - Iif(UBound(vMenus) &gt; 0, Len(SubmenuChar), 0) - Len(vReturn(1)))

Finally:
	_SplitMenuItem = vReturn
End Function	&apos;	SFWidgets.SF_ContextMenu._SplitMenuItem

REM ============================================ END OF SFWIDGETS.SF_CONTEXTMENU
</script:module>