How to get current state of menu item in Impress?

Hello!
I would like to ask you help me to figure out how to get the current state of the menu item in LibreOffice Impress.

Its path is View > SnapGuides > Snap Guides to Front

I know its command .uno:HelplinesFront, but it only works as a switch, toggling the current state to the opposite. I need to always turn this option off when executing a macro, and for this, I need to check its current state. Where is it stored?

I tried to do it according to the guide from the topic Apache OpenOffice Community Forum - [Solved] Check the state of a toolbar and then execute code - (View topic), using AccessibleStateSet and it almost works, because I can get the state of the button, but only if any of the menubar items was opened through the GUI (I can provide more details if necessary).

My question is very similar to this Apache OpenOffice Community Forum - [LibreOffice] [Writer] Get/Set state of a Check-SubMenu - (View topic), but in this topic, there was no suitable answer for Impress.

I will be grateful for any help on this issue.

1 Like

Hi,

in

oViewData = ThisComponent.ViewData.getByIndex(0)

you can find 44 view properties, see below. Unfortunately, the property you want seems not to be included. But maybe that helps for a workaround
Good luck,
ms777

ViewId                      "view1" 
GridIsVisible               False 
GridIsFront                 False 
IsSnapToGrid                True 
IsSnapToPageMargins         True 
IsSnapToSnapLines           True 
IsSnapToObjectFrame         True 
IsSnapToObjectPoints        True 
IsPlusHandlesAlwaysVisible  False 
IsFrameDragSingles          True 
EliminatePolyPointLimitAngle  1500 
IsEliminatePolyPoints       False 
VisibleLayers 
PrintableLayers 
LockedLayers                <empty> 
NoAttribs                   False 
NoColors                    True 
RulerIsVisible              True 
PageKind                    0 
SelectedPage                0 
IsLayerMode                 False 
IsDoubleClickTextEdit       True 
IsClickChangeRotation       True 
SlidesPerRow                4 
EditMode                    0 
VisibleAreaTop              -239 
VisibleAreaLeft             -1158 
VisibleAreaWidth            30465 
VisibleAreaHeight           16299 
GridCoarseWidth             2540 
GridCoarseHeight            2540 
GridFineWidth               635 
GridFineHeight              635 
GridSnapWidthXNumerator     2540 
GridSnapWidthXDenominator   4 
GridSnapWidthYNumerator     2540 
GridSnapWidthYDenominator   4 
IsAngleSnapEnabled          False 
SnapAngle                   1500 
ZoomOnPage                  True 
AnchoredTextOverflowLegacy  False 
LegacySingleLineFontwork    False 
ConnectorUseSnapRect        False 
IgnoreBreakAfterMultilineField  False 

Just listed to the status of the UNO command directly, instead of trying to get something from a UI element (menu in this case, which may be customized, by the way).

Modify the following code as you need. Likely you will run StartControl in the beginning of the processing that needs the function to be disabled; and then, you will call EndControl in the end (making sure that it also executes in case of an error, using on error statement).

Since registering a state listener automatically calls the first “status changes” event, the provided HelplinesFront_statusChanged handler will automatically detect, if the status is incorrect, and dispatch the command. That will toggle the function, calling the handler again, but this time, it will see the disabled state, and do nothing.

Indeed, you may want to return the created listener from the StartControl function, instead of storing it in a global variable; that would be preferable in case of a scoped task.

If you need to restore the previous state, you would need to have another Variant variable, which would be set to the active command state in the HelplinesFront_statusChanged handler (iif it was empty, i.e. in the very first call). Then EndControl could check if it needs to dispatch the command again to restore the state back.

global g_listener

sub HelplinesFront_statusChanged(State)
  if (State.FeatureURL.Main = ".uno:HelplinesFront") then
    if (State.State) then
      xDispatch = getDispatch(State.FeatureURL)
      xDispatch.dispatch(State.FeatureURL, array())
    end if
  end if
end sub

sub HelplinesFront_disposing(Source)
  g_listener = Empty
end sub

function getURLObject(urlString)
  dim url as new com.sun.star.util.URL
  url.Complete = urlString
  xURLTransformer = CreateUnoService("com.sun.star.util.URLTransformer")
  xURLTransformer.parseStrict(url)
  getURLObject = url
end function

function getDispatch(url)
  xComponent = ThisComponent
  xController = xComponent.getCurrentController()
  getDispatch = xController.queryDispatch(url, "_self", 0)
end function

sub StartControl
  if (not IsEmpty(g_listener)) then EndControl
  url = getURLObject(".uno:HelplinesFront")
  xDispatch = getDispatch(url)
  g_listener = CreateUnoListener("HelplinesFront_", "com.sun.star.frame.XStatusListener")
  xDispatch.addStatusListener(g_listener, url)
end sub

sub EndControl
  if (IsEmpty(g_listener)) then exit sub
  url = getURLObject(".uno:HelplinesFront")
  xDispatch = getDispatch(url)
  xDispatch.removeStatusListener(g_listener, url)
  g_listener = Empty
end sub
1 Like

[removed, should have been a comment on the OP ]

As I understand Mike’s answer (it would be good to test my macros further). :slightly_smiling_face:

Option Explicit

Dim oFeatureStateEvent As Object

' Return FeatureStateEvent structure or Nothing.
' - obj   Object supporting XModel or XDispatchProvider interfaces.
' - sUrl  describe the feature which should be supported by returned dispatch object.
'         For uno commands prefix ".uno:" may be omitted.   
Function GetFeatureState(Byval obj As Object, ByVal sUrl As String) As Object
  Dim oListener As Object, oDisp As Object, url As Object
  
  GetFeatureState=Nothing
  
  If Not HasUnoInterfaces(obj, "com.sun.star.frame.XDispatchProvider") Then
    If HasUnoInterfaces(obj, "com.sun.star.frame.XModel") Then
      obj=obj.CurrentController
    End If
    If Not HasUnoInterfaces(obj, "com.sun.star.frame.XDispatchProvider") Then Exit Function
  End If
  
  oFeatureStateEvent=Nothing
  If Instr(1, sUrl, ":")=0 Then sUrl=".uno:" & sUrl
  url=getURLObject(sUrl)
  oDisp=obj.queryDispatch(url, "_self", 0)
  oListener=CreateUnoListener("statusList_", "com.sun.star.frame.XStatusListener")
  oDisp.addStatusListener(oListener, url)
  oDisp.removeStatusListener(oListener, url)

  GetFeatureState=oFeatureStateEvent
End Function

Function getURLObject(Byval urlString As String) As Object
  Dim url As New com.sun.star.util.URL
  url.Complete = urlString
  CreateUnoService("com.sun.star.util.URLTransformer").parseStrict(url)
  getURLObject = url
End Function


' statusList_ Subs
Sub statusList_statusChanged(oEvent)
  oFeatureStateEvent=oEvent
End Sub  

Sub statusList_disposing(oEvent)
End Sub

Sub TestFeature
  Dim oEvent As Object, unoCmd As String
  unoCmd="HelplinesFront"
  oEvent=GetFeatureState(ThisComponent, unoCmd)
  If Not (oEvent Is Nothing) Then
    Msgbox unoCmd & " state: " & oEvent.State 
  End If  
End Sub

… cool, works for me. Seems like adding a StatusListener automatically triggers it

Yes, this is written in the documentation:

The first notification should be performed synchronously from XDispatch::addStatusListener()


The approach indicated by @mikekaganski opens the way to solving many problems.
For example, we can determine for each sheet of a Calc document whether a grid is visible on it. To do this, we need to activate the sheet and analyze the feature state .uno:ToggleSheetGrid.

1 Like

@mikekaganski and @sokol92 - thank you so much for your solution, it works like a charm!

Let me put my two cents in as well. The code below does the same thing, but in Python.

import uno
import unohelper
from com.sun.star.util import URL
from com.sun.star.frame import XStatusListener


class StatusListener(unohelper.Base, XStatusListener):
    def __init__(self):
        self.feature_state_event = None

    def statusChanged(self, ev):
        self.feature_state_event = ev

    def disposing(self, ev):
        pass


def get_context():
    return uno.getComponentContext()


def has_uno_interface(obj, interface_name):
    smgr = get_context().getServiceManager()
    introspection = smgr.createInstanceWithContext("com.sun.star.beans.Introspection", get_context())
    obj_info = introspection.inspect(obj)
    obj_methods = obj_info.getMethods(uno.getConstantByName("com.sun.star.beans.MethodConcept.ALL"))

    for method in obj_methods:
        if method.getDeclaringClass().getName() == interface_name:
            return True
    return False


def get_feature_state(obj, s_url):

    if not has_uno_interface(obj, "com.sun.star.frame.XDispatchProvider"):
        if has_uno_interface(obj, "com.sun.star.frame.XModel"):
            obj = obj.CurrentController

    if not has_uno_interface(obj, "com.sun.star.frame.XDispatchProvider"):
        return

    a_url = URL()
    a_url.Complete = s_url if ":" in s_url else ".uno:" + s_url
    listener = StatusListener()
    ctx = get_context()
    smgr = ctx.getServiceManager()
    url_transformer = smgr.createInstanceWithContext("com.sun.star.util.URLTransformer", ctx)
    _, a_url = url_transformer.parseStrict(a_url)

    dispatcher = obj.queryDispatch(a_url, "_self", 0)
    dispatcher.addStatusListener(listener, a_url)
    dispatcher.removeStatusListener(listener, a_url)

    return listener.feature_state_event


def test_feature():
    uno_cmd = "HelplinesFront"

    smgr = get_context().getServiceManager()
    doc = smgr.createInstanceWithContext(
        "com.sun.star.frame.Desktop", get_context()
    ).getCurrentComponent()
    event = get_feature_state(doc, uno_cmd)
    if event:
        print(f"{uno_cmd} state: {event.State}")  # TODO Use your own msgbox implementation
1 Like