Toolbar with functional ComboBox, DropdownBox etc

I try to create the ComboBox in the toolbar with functional mouse selecting and keyboard selecting. I found 2 examples.

  1. The extension Complex Toolbar - but there isn’t the possibility how to detect the selected item.

  2. Somewhere from OOo forum I reconstructed the example with the BeanShell. It is partially functional - OK for mouse selecting in the ComboBox, but no for Enter. And the Libre will crash with the closing the sheet.
    ComplexToolbar-combobox-BeanShell.ods (20.7 kB)

In second example, I’m not able to rewrite the BeanShell script to the Python. But I think the better usage is the ProtocolHandler in the first example, but I’m not able to create the needy listeners for the toolbar.

The idea is to have the ComboBox for the Font selection, but only with the elected fonts. I don’t want to have in the font list the fonts with the restrictions like Windows C-fonts etc. So I try to create the customable ComboBox only with the friendly fonts :slight_smile: (like the fonts with the OFL license, for example Liberation Serif)

To get the list with the fonts is easy.

Sub fontsInfo
	dim oDoc as object, oWindow as object, oFonts()
	oDoc=ThisComponent
	oWindow=oDoc.CurrentController.Frame.ContainerWindow
	oFonts=oWindow.FontDescriptors
	xray oFonts
End Sub

What version of LibreOffice you use?

Hi,

on the second point: The standard awt combobox window has no mechanism for detecting Enter or other keystrokes. You have to write that yourself. I was too lazy to do that. See the below snippet as a hint where to go …

Good luck,

ms777

Apache Office 4.1.6. on Win10x64

function generateCBOXWindow(wParent as com.sun.star.awt.XWindow) as com.sun.star.awt.XWindow
'the itemlistener, which is added to the toolbar combobox
listItemListener = createUnoListener("CBOX_ITEM_", "com.sun.star.awt.XItemListener")
textListener = createUnoListener("CBOX_TEXT_", "com.sun.star.awt.XTextListener")
keyListener = createUnoListener("CBOX_KEY_", "com.sun.star.awt.XKeyListener")
oWindow = makeAwtWindow(wParent, 0, 0, 230, 20, "combobox", com.sun.star.awt.WindowAttribute.SHOW or com.sun.star.awt.VclWindowPeerAttribute.DROPDOWN)
'xray oWindow
oWindow.addItemListener(listItemListener)
oWindow.addTextListener(textListener)   
oWindow.addKeyListener(keyListener)   

oWindow.setText("Enter Command Here")
oWindow.addItems(Array( "test", "foo", "bar", "test2", "foo2", "bar2" ), 0)
oWindow.setDropDownLineCount(6)
generateCBOXWindow = oWindow
end function


' the com.sun.star.awt.XItemListener to get the combobox events back to OO Basic
sub CBOX_ITEM_itemStateChanged(event as com.sun.star.awt.ItemEvent)
lSelected = event.selected
s = "selection change to item # " + lSelected
if lSelected >=0 then s = s & ": " & event.source.items(lSelected)
logMessage(s)
end sub

' the com.sun.star.awt.XTextListener to get the Text events back to OO Basic
sub CBOX_TEXT_textChanged(event as com.sun.star.awt.TextEvent)
s = "text event" 
logMessage(s)
end sub

' the com.sun.star.awt.XKeyListener to get the Text events back to OO Basic
sub CBOX_KEY_keyPressed(event as com.sun.star.awt.KeyEvent)
s = "key pressed, code " + event.KeyCode 
logMessage(s)
if event.KeyCode = com.sun.star.awt.Key.RETURN then
	xray event
'here, you have to manually match the text entered with the options and update the selcted item, if appropriate
end if
end sub

' the com.sun.star.awt.XKeyListener to get the Text events back to OO Basic
sub CBOX_KEY_keyReleased(event as com.sun.star.awt.KeyEvent)
s = "key released, code " + event.KeyCode 
logMessage(s)
'xray event
end sub

Thanks a lot :-). It is functional and I understand how it works in Basic.

The remaining problem for me is to convert BeanShell to Python :slight_smile:

… for Python have look at Apache OpenOffice Community Forum - Write own Toolbars in Python - (View topic).
Good luck,
ms777

I ran function doItAll and it shows errors with constants in LibreOffice (7.4.1.1 Win10x64)
bug1

so I substituted ones to normal numbers

line 66: uno.getConstantByName("com.sun.star.awt.WindowClass.SIMPLE") -> 3
line 78: uno.getConstantByName("com.sun.star.awt.WindowClass.TOP") -> 0
line 172: uno.getConstantByName('com.sun.star.ui.DockingArea.DOCKINGAREA_TOP') -> 0

But I don’t know how to repair next error :-(.

The code with repaired constants

import uno
import unohelper
from subprocess import Popen

from com.sun.star.util import XUpdatable # type:ignore

from com.sun.star.frame import XToolbarController # type:ignore
from com.sun.star.frame import XStatusListener # type:ignore

from com.sun.star.lang import XInitialization # type:ignore

from com.sun.star.awt import Point # type:ignore
from com.sun.star.awt import XItemListener # type:ignore
from com.sun.star.awt import XAdjustmentListener # type:ignore

from com.sun.star.beans import PropertyValue # type:ignore


# define ctx, doc, smgr for (i) invocation from VS Code or (ii) invocation from OO execute macro
if __name__ == '__main__': 
    # invocation from VS Code
    localContext = uno.getComponentContext()
    resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext )
    # start OO
    lo_proc = Popen('"C:\Program Files (x86)\OpenOffice 4\program\soffice.exe" -accept=socket,host=localhost,port=2002;urp;', shell=True)
    ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )

    smgr = ctx.ServiceManager
    desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
    doc = desktop.loadComponentFromURL('private:factory/scalc', '_default', 0, ())

elif __name__ == 'ooo_script_framework':
    # invocation from OO execute macro
    doc = XSCRIPTCONTEXT.getDocument() # type: ignore
    ctx = XSCRIPTCONTEXT.getComponentContext() # type: ignore
    smgr = ctx.ServiceManager
else:
    raise Exception('__name__ is unknown: ' + __name__) 

def xray(target):
    global ctx
    mspf = ctx.ServiceManager.createInstanceWithContext("com.sun.star.script.provider.MasterScriptProviderFactory", ctx)
    script_provider = mspf.createScriptProvider("")
    script = script_provider.getScript("vnd.sun.star.script:XrayTool._Main.Xray?language=Basic&location=application")
    script.invoke((target,), (), ())

def logMessage(mes):
    global logSheet
    global logRow
    logSheet.getCellByPosition(0,logRow).String = mes
    logRow += 1


# wParent: uno.com.sun.star.awt.XWindow
# x, y, width, height: int
# wServiceName: str
# wAttribute: long
# returns -> uno.com.sun.star.awt..XWindow
def makeAwtWindow(wParent, x, y, width, height, wServiceName, wAttribute):
    global smgr
    global ctx
    oToolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)

    # create WindowDescriptor
    wd = uno.createUnoStruct("com.sun.star.awt.WindowDescriptor")
    wd.Type = 3 #uno.getConstantByName("com.sun.star.awt.WindowClass.SIMPLE")
    wd.Parent = wParent
    wd.Bounds = uno.createUnoStruct("com.sun.star.awt.Rectangle", x, y, width, height)
    wd.ParentIndex = -1
    wd.WindowAttributes = wAttribute
    wd.WindowServiceName = wServiceName

    return oToolkit.createWindow(wd)

def TestAwt():
    global doc
    parentWindow = doc.CurrentController.Frame.ContainerWindow
    wAttribute = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SHOW") | 0 #uno.getConstantByName("com.sun.star.awt.WindowClass.TOP")
    oWindow = makeAwtWindow(parentWindow, 100, 100, 300, 200, "combobox", wAttribute)
    oWindow.setBackground(0XFF00FF)

class ToolbarItem(unohelper.Base, XInitialization, XUpdatable, XToolbarController, XStatusListener):
    def __init__( self, ctx, propModuleIdentifier, propFrame, propServiceManager, propParentWindow, propIdentifier, propCommandUrl ):
        self.sCommandUrl = propCommandUrl.Value
        print('in ToolbarItem constructor: ' + self.sCommandUrl)
        return
# com.sun.star.lang.XInitialization
    def initialize(self, args):
        logMessage("initialize " + self.sCommandUrl)
# com.sun.star.util.XUpdatable
    def update(self):
        logMessage("update " + self.sCommandUrl)
# com.sun.star.frame.XStatusListener
    def statusChanged(self, state):
        logMessage("statusChanged " + self.sCommandUrl)

# com.sun.star.frame.XToolbarController
    def execute(self, KeyModifier):
        logMessage("execute " + self.sCommandUrl)

    def click(self):
        logMessage("click " + self.sCommandUrl)

    def doubleClick(self):
        logMessage("doubleclick " + self.sCommandUrl)

    def createPopupWindow(self): 
        logMessage("createPopupWindow " + self.sCommandUrl)
        return None

    def createItemWindow(self, wParent):
        pass


class ToolbarItemComboBox(ToolbarItem, XItemListener):
    asOptions = ()
    sText = ""

    def createItemWindow(self, wParent):
        logMessage("createItemWindow start " + self.sCommandUrl)
        wAttribute = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SHOW") | uno.getConstantByName("com.sun.star.awt.VclWindowPeerAttribute.DROPDOWN")

        oWindow = makeAwtWindow(wParent, 0, 0, 230, 20, "combobox", wAttribute)
        oWindow.addItemListener(self)
        oWindow.setText(self.sText)
        oWindow.addItems(self.asOptions, 0)
        oWindow.setDropDownLineCount(6)
        logMessage("createItemWindow end " + self.sCommandUrl)
        return oWindow

    def itemStateChanged(self, event):
        self.lSelected = event.Selected
        s = "selection change to item # " + str(self.lSelected)
        if self.lSelected >= 0:
            s = s + ": " + event.Source.getItem(self.lSelected)
        s = s + ', ' + self.sCommandUrl
        logMessage(s)

class ToolbarItemComboBox2(ToolbarItemComboBox):
    pass

class ToolbarItemSchrollbar(ToolbarItem, XAdjustmentListener):

    def createItemWindow(self, wParent):
        logMessage("createItemWindow start " + self.sCommandUrl)
        wAttribute = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SHOW")
        oWindow = makeAwtWindow(wParent, 0, 0, 230, 20, "scrollbar", wAttribute)
        oWindow.addAdjustmentListener(self)
        oWindow.setOrientation(uno.getConstantByName("com.sun.star.awt.ScrollBarOrientation.HORIZONTAL"))
        oWindow.setValues(50, 10, 100)
        logMessage("createItemWindow end " + self.sCommandUrl)
        return oWindow

    def adjustmentValueChanged(self, event):
        s = "schrollbar changed to " + str(event.Value) + ', ' + self.sCommandUrl
        logMessage(s)


def createToolbar(toolbarResourceUrl, toolbarUIName):

# delete toolbar if existing
    oLM = doc.CurrentController.Frame.LayoutManager
    if oLM.getElement(toolbarResourceUrl) is not None:
        oLM.destroyElement(toolbarResourceUrl)
# create toolbar
    oLM.createElement(toolbarResourceUrl)
    oEL = oLM.getElement(toolbarResourceUrl)
    oToolbarSettings = oEL.getSettings(True)        
    oToolbarSettings.UIName = toolbarUIName 
    oEL.setSettings(oToolbarSettings) 
# dock the toolbar in the docking area. Not sure if necessary.
    oLM.dockWindow(toolbarResourceUrl, 0, Point(0, 0)) #oLM.dockWindow(toolbarResourceUrl, uno.getConstantByName('com.sun.star.ui.DockingArea.DOCKINGAREA_TOP'), Point(0, 0)) 

def insertToolbarItems(toolbarResourceUrl, aItem):
# register the itemService at the Toolbarfactory
    oo = smgr.createInstanceWithContext("com.sun.star.frame.ToolbarControllerFactory", ctx)
    oLM = doc.CurrentController.Frame.LayoutManager
    oEL = oLM.getElement(toolbarResourceUrl)
    oToolbarSettings = oEL.getSettings(True)

    for Item in aItem:
        if oo.hasController( Item.itemCommandUrl, ""):
            oo.deregisterController( Item.itemCommandUrl, "")
        oo.registerController( Item.itemCommandUrl, "", Item.itemService)

        prop1 = PropertyValue()
        prop1.Name = "CommandURL" 
        prop1.Value = Item.itemCommandUrl
        prop2 = PropertyValue()
        prop2.Name = "Label" 
        prop2.Value = Item.itemLabel

        uno.invoke( oToolbarSettings, "insertByIndex", (oToolbarSettings.Count, uno.Any("[]com.sun.star.beans.PropertyValue", (prop1, prop2)))) 

    oEL.setSettings(oToolbarSettings) 


def doItAll():
    global logSheet
    global logRow

    print('Hallo')

    #for the logging ...
    logRow = 0
    logSheet = doc.Sheets.getByIndex(0)
    logSheet.getCellRangeByPosition(0,0,0,1000).clearContents(uno.getConstantByName('com.sun.star.sheet.CellFlags.STRING'))

    #
    logMessage('Hallo')

    TestAwt()

    xSM = ctx.getValueByName("/singletons/com.sun.star.lang.theServiceManager")

    toolbarResourceUrl = "private:resource/toolbar/custom_ms777"
    toolbarUIName = "toolbarUIName ms777"

    ToolbarItemComboBox.asOptions = ( "test", "foo", "bar", "test2", "foo2", "bar2" )
    ToolbarItemComboBox.sText = "Combo Box 1"
    ToolbarItemComboBox.itemLabel = "LabelCombobox 1"
    ToolbarItemComboBox.itemCommandUrl = ".uno:ms777Combobox"
    ToolbarItemComboBox.itemService = "ms777.ToolbarCombobox"
    ToolbarItemComboBox.itemServiceImpl = "ms777.impl.ToolbarCombobox"

    ToolbarItemComboBox2.asOptions = ( "option A", "Karl Heinz")
    ToolbarItemComboBox2.sText = "Combo Box 2"
    ToolbarItemComboBox2.itemLabel = "LabelCombobox 2"
    ToolbarItemComboBox2.itemCommandUrl = ".uno:ms777Combobox2"
    ToolbarItemComboBox2.itemService = "ms777.ToolbarCombobox2"
    ToolbarItemComboBox2.itemServiceImpl = "ms777.impl.ToolbarCombobox2"

    ToolbarItemSchrollbar.itemLabel = "Label Scrollbar"
    ToolbarItemSchrollbar.itemCommandUrl = ".uno:ms777Scrollbar"
    ToolbarItemSchrollbar.itemService = "ms777.ToolbarScrollbar"
    ToolbarItemSchrollbar.itemServiceImpl = "ms777.impl.ToolbarScrollbar"


#    prop1 = PropertyValue()
#    prop1.Name = "CommandURL" 
#    prop1.Value = itemComboBoxCommandUrl 
#    xt = ssf.createInstanceWithArgumentsAndContext((1, 2, 3, 4, 5, prop1), ctx)
#    xray(xt)

#    xray(xSM)

    createToolbar(toolbarResourceUrl, toolbarUIName)

    for Item in (ToolbarItemComboBox, ToolbarItemComboBox2, ToolbarItemSchrollbar):
        ssf  = unohelper.createSingleServiceFactory( Item,  Item.itemServiceImpl,  (Item.itemService,) )
        xSM.insert(ssf)

    insertToolbarItems(toolbarResourceUrl, (ToolbarItemComboBox, ToolbarItemComboBox2, ToolbarItemSchrollbar))

if __name__ == '__main__':
    doItAll()
    lo_proc.wait() # when run from VS code, better wait until AO is finished. Otherwise, some calls goto nowhere ...

You forgot to declare c.s.s.awt.WindowClass.TOP

Please clean up the mess!

… everything was done on AO 4.1.6, see the original post. I just installed LO and made a LO version Apache OpenOffice Community Forum - Write own Toolbars in Python - (View topic)

One small progress for point 1.
I emailed with Gérard Deneux, the author of extension CADLO. This extension also use Complex Toolbar and there is for example ToggleDropDown Button. And Gérard wrote me there is the class XListenerHelper for handling it. But I’m not able to simplify it and add to the Complex Toolbar Extension, still too much complicated for me :-(.

The links with help from Gérard (he hadn’t time to help me more)
https://wiki.openoffice.org/wiki/Framework/Article/Generic_UNO_Interfaces_for_complex_toolbar_controls
http://www.openoffice.org/udk/python/python-bridge.html

Hi,
not really sure what kind of control you want to insert into the toolbar.

Did you test the latest version of Apache OpenOffice Community Forum - Write own Toolbars in Python - (View topic) ?

In principle, it is possible to insert each and every UnoControl into the toolbar, even a complete UnoDialog.
Please check the above link and let me know what is missing

Good luck, ms777

Edit: this is a minimum example on LO 7.4.0.3 (x64)
toolbar.ods (10.9 KB)

1 Like

Hello, I tested it, the control of toolbar is OK, but when I close the sheet and open new one, then new sheet isn’t visible :-(. Video with example (!rename odt to mp4!)
Record_2022_12_04_16_57_03_719.mp4.RENAMED.ODT (976.0 kB)
LibreOffice 7.4.3.2 (Win10x64)

And after reopen toolbar.ods it shows the error com.sun.star.lang.DisposedException.

The solution for point 1. Tested on Win10x64 Libre 7.4.3.2.
Rename odt to oxt and install it as Extension, it will add the Toolbar to Writer.
ComplexToolbar.oxt.RENAMED.ODT (5.3 kB)

It shows msgbox if some text is written in Editfield and pressed Enter. Or it changes the font for selected text if some item is selected in Combobox.

There is def dispatch in class SampleDispatch in file dispatch.py for the “listeners” for keyboard and mouse :-).