Find item in QApplication by only the objectname - python

i want to find any object by a objectname string name inside of the QApplication
Something like
QApplication.instance().findByClassName("codeEditor")
which should return a list of widgets with this classname that i can iterate over if there is more then one
[QPushButton (QPushButton at: 0x0000008EA3B3DD80), QWidget (QWidget at: 0x0000008EA3F33F40)]
I have read this but it requires a object and i want something like *
This is something i came up with for testing:
def findWidget(name):
name = name.lower()
widgets = self.topLevelWidgets()
widgets = widgets + self.allWidgets()
ret = dict()
c = 0
for x in widgets:
c += 1
if name in x.objectName.lower() or name in str(x.__class__).lower():
ret["class:"+str(x.__class__)+str(c)] = "obj:"+x.objectName;continue
if hasattr(x, "text"):
if name in x.text.lower():
ret["class:"+str(x.__class__)+str(c)] = "obj:"+x.objectName
return ret
It doesn't even find the 'InfoFrame' which is clearly there:
>>> widget("info")

{}

I came up with this which works quite well
def getWidgetByClassName(name):
widgets = QApplication.instance().topLevelWidgets()
widgets = widgets + QApplication.instance().allWidgets()
for x in widgets:
if name in str(x.__class__).replace("<class '","").replace("'>",""):
return x
def getWidgetByObjectName(name):
widgets = QApplication.instance().topLevelWidgets()
widgets = widgets + QApplication.instance().allWidgets()
for x in widgets:
if str(x.objectName) == name:
return x
def getObjects(name, cls=True):
import gc
objects = []
for obj in gc.get_objects():
if (isinstance(obj, PythonQt.private.QObject) and
((cls and obj.inherits(name)) or
(not cls and obj.objectName() == name))):
objects.append(obj)
return objects

In Python, this can be done for any class using the gc module. It provides a method for retrieving the references of all objects tracked by the garbage-collector. This is obviously a quite inefficient approach, but it does (almost) guarantee that any type of object can be found.
Here's a function to get a list of all QObject instances either by class-name or object-name:
def getObjects(name, cls=True):
objects = []
for obj in gc.get_objects():
if (isinstance(obj, QtCore.QObject) and
((cls and obj.inherits(name)) or
(not cls and obj.objectName() == name))):
objects.append(obj)
return objects
This is only really a debugging tool, though - for a large application, there could easily be several hundred thousand objects to check.
If you only need objects which are subclasses of QWidget, use this function:
def getWidgets(name, cls=True):
widgets = []
for widget in QtGui.QApplication.allWidgets():
if ((cls and widget.inherits(name)) or
(not cls and widget.objectName() == name)):
widgets.append(widget)
return widgets
PS:
If you want to find all objects which are subclasses of QObject, this can only be achieved if you can somehow ensure that all the instances in your application have a valid parent (which, by definition, must also be a QObject). With that in place, you can then use root_object.findChildren(QObject) to get the full list. It is also possible to use findChild or findChildren to search for individual objects by object-name (optionally using a regular-expression, if desirable).

It is not possible to find all QObject instances in general. Qt does not keep track of them since objects can be used in multiple threads and the overhead of tracking them would be unnecessarily high.
Qt does keep track of all widgets, though, since widgets can only exist in the main thread, and they are fairly heavy objects so tracking them has comparably little overhead.
So, you could search all widgets you get from QApp.allWidgets(), and all of their children. You can also look through children of objects you otherwise have access to. But if a given object is parentless, or is not owned by a widget, then you won't find it that way.

Related

Python PyQt6 contextMenuEvent strange behavior I don't understand

I have the following code giving me trouble:
class TableView(qt.QTableView):
def __init__(self, param):
super().__init__()
self.model = param.model
self.view = self
self.mains = [QAction('Remove row'), QAction('Split expense')]
self.types = [QAction('Bills'), QAction('Vapors')]
def contextMenuEvent(self, event):
row = self.view.rowAt(event.y())
col = self.view.columnAt(event.x())
main_menu = qt.QMenu()
type_menu = qt.QMenu('Update')
main_menu.addActions(self.mains)
type_menu.addActions(self.types)
if col == 1:
main_menu.addMenu(type_menu)
#mains[0].triggered.connect(lambda: self.remove_row(row))
for e in self.types:
print(e)
e.triggered.connect(lambda: self.update_type(row, e))
main_menu.exec(QCursor.pos())
def remove_row(self, row):
self.model.removeRow(row)
def update_type(self, row, action):
print(action)
It should update print the correct QAction based on the chosen context menu. The loop returns...
<PyQt6.QtGui.QAction object at 0x7f77fd619480>
<PyQt6.QtGui.QAction object at 0x7f77fd619510>
...every time. <PyQt6.QtGui.QAction object at 0x7f77fd619480> should be tied to "Bills" and <PyQt6.QtGui.QAction object at 0x7f77fd619510> should be tied to "Vapors". When I run it, no matter what menu option I choose, it returns <PyQt6.QtGui.QAction object at 0x7f77fd619510>. To make matters worse, right-clicking should print the loop once, followed by the menu selection (which is always <PyQt6.QtGui.QAction object at 0x7f77fd619510>), but what happens after the first row in the table gets right-clicked, is <PyQt6.QtGui.QAction object at 0x7f77fd619510> is printed twice. What gives?
EDIT
Okay, I managed to fix part of the problem with the help of other posts.
for e in self.types:
e.triggered.connect(lambda d, e=e: self.update_type(row, e))
But I still have a problem. The signal fires each the number of times I press a context menu item per time the GUI is open. So, I launch the GUI, right-click and select some thing and it fires once. Then I right-click again and it fores twice, then three times and so on for the number of times I right-clicked.
Why?
There are two main problems with your code:
variables inside lambdas are evaluated at execution, so e always corresponds to the last reference assigned in the loop;
when a signal is emitted, functions are called as many times they have been connected: each time you create the menu, you're connecting the signal once again;
Depending on the situations, there are many ways to achieve what you need. Here are some possible options:
Compare the triggered action returned by exec()
QMenu.exec() always returns the action that has been triggered, knowing that you can just compare it and eventually decide what to do:
class TableView(QTableView):
def __init__(self, param):
super().__init__()
self.setModel(param.model)
self.mains = [QAction('Remove row'), QAction('Split expense')]
self.types = [QAction('Bills'), QAction('Vapors')]
def contextMenuEvent(self, event):
index = self.indexAt(event.pos())
main_menu = QMenu()
for action in self.mains:
main_menu.addAction(action)
action.setEnabled(index.isValid())
if index.column() == 1:
type_menu = main_menu.addMenu('Update')
type_menu.addActions(self.types)
action = main_menu.exec(event.globalPos())
if action in self.mains:
if action == self.mains[0]:
self.remove_row(index.row())
elif action in self.types:
self.update_type(index.row(), action)
def remove_row(self, row):
self.model().removeRow(row)
def update_type(self, row, action):
print(action)
Use the action.data() as argument
QActions supports setting arbitrary data, so we can set that data to the row. If we are using the action.triggered signal, we can retrieve the action through self.sender() (which returns the object that emitted the signal). Otherwise, we can use menu.triggered() to call the target function with the action that has triggered it as argument.
class TableView(QTableView):
def __init__(self, param):
super().__init__()
self.setModel(param.model)
self.mains = [QAction('Remove row'), QAction('Split expense')]
self.mains[0].triggered.connect(self.remove_row)
self.types = [QAction('Bills'), QAction('Vapors')]
def contextMenuEvent(self, event):
index = self.indexAt(event.pos())
main_menu = QMenu()
for action in self.mains:
main_menu.addAction(action)
action.setEnabled(index.isValid())
action.setData(index.row())
if index.column() == 1:
type_menu = main_menu.addMenu('Update')
type_menu.triggered.connect(self.update_type)
for action in self.types:
type_menu.addAction(action)
action.setData(index.row())
main_menu.exec(event.globalPos())
def remove_row(self):
sender = self.sender()
if isinstance(sender, QAction):
row = sender.data()
if row is not None:
self.model().removeRow(row)
def update_type(self, action):
print(action, action.data())
So, no lambda?
Lambdas can certainly be used, but considering what explained above, and that your requirement is to use dynamic arguments, that can be tricky.
You can use it for a fully dynamical menu (including creation of actions), otherwise you'd need to always try to disconnect() the signal, and that might be tricky:
using a lambda as target slot means that you don't have any previous reference to the function that has to be disconnected;
completely disconnecting the signal (using the generic signal.disconnect()) might not be a good choice, if the signal was previously connected to other functions;
A fully dynamical menu
The above solutions are based on the fact that the actions already existed at the time of the context menu event.
This is usually not a requirement. In fact, many widgets in Qt always create a brand new menu along with its actions. This is the case of all text-based widgets (QLineEdit, QTextEdit and even QLabels with the proper text interaction flags): the menu is always temporary.
With this in mind, we can take an approach based on what explained above, but without thinking about "changing" or "restoring" previous states, data or connections: the menu will be destroyed as soon as it's closed, along with any of its actions (since they've been created as children of the menu), so Python and Qt will take care of releasing resources that are not needed anymore.
While this continuous creation/destroy of objects might not seem optimal, memory/performance wise, it's actually conceptually better and quite effective: menus don't usually need extreme performance, and creating/destroying them is actually simpler than managing the behavior of a persistent set of menu/actions depending on the context.
class TableView(QTableView):
def __init__(self, param):
super().__init__()
self.setModel(param.model)
def contextMenuEvent(self, event):
index = self.indexAt(event.pos())
isValid = index.isValid()
main_menu = QMenu()
removeAction = main_menu.addAction('Remove row')
if isValid:
removeAction.triggered.connect(lambda:
self.remove_row(index.row()))
else:
removeAction.setEnabled(False)
splitAction = main_menu.addAction('Split expanse')
if isValid:
splitAction.triggered.connect(lambda:
self.split_expanse(index.row()))
else:
splitAction.setEnabled(False)
type_menu = main_menu.addMenu('Update')
if index.column() != 1:
type_menu.setEnabled(False)
else:
billsAction = type_menu.addAction('Bills')
billsAction.triggered.connect(lambda:
self.bills(index.row()))
vaporsAction = type_menu.addAction('Vapors')
vaporsAction.triggered.connect(lambda:
self.doVapors(index.row()))
main_menu.exec(event.globalPos())
Further options
There are occasions for which keeping persistent actions or menus is required, for instance a menu that has lots of items that require some amount of time to be created.
As already explained, signals can be connected to multiple functions at the same time (and even the same function more than once).
The issue with lambdas is that we usually use them "in line". Doing this, we always lose the reference to their connection:
self.someAction.triggered.connect(lambda: self.whatever(xyz))
While we could just use the generic signal.disconnect(), which disconnects the signal from any function or slot connected to it, that might not be a viable option: maybe the signal is also connected to some other function that is always required to be triggered, no matter of the context (such as a visual hint about the activation of actions). This means that we cannot specifically disconnect from a lambda used as above.
Luckily, as we know, in Python "everything is an object", including lambdas:
doWhatever = lambda: self.whatever(xyz)
self.someAction.triggered.connect(doWhatever)
# ...
menu.exec(pos)
self.someAction.triggered.disconnect(doWhatever)
In this way, we ensure that we only connect to the action in the context of the menu event, and disconnect it afterwards, no matter of the actual action that has been triggered.
Note that the above is actually the same as using a local function (which is what lambdas are, conceptually speaking):
def doWhatever():
self.whatever(xyz)
self.someAction.triggered.connect(doWhatever)
# ...
menu.exec(pos)
self.someAction.triggered.disconnect(doWhatever)
The benefit of the above approach is that a local function can be extended more easily than a simple lambda.
Conclusions
QAction is quite a strange class. It's not a widget, but it can be used for that purpose, it doesn't need a parent, and can be shared between many objects (menus, toolbars, etc.). As opposite to widgets, an action can appear in many places at the same time even in the same UI: a tool bar, a menubar, context menu, a QToolButton.
Nonetheless, setting the parent of a new action doesn't automatically add the action to that parent list of actions, so someObject.actions() won't list that action unless addAction() has been explicitly called.
The "migration" of Qt6 from QtWidgets to QtGui made these aspect partially more clear, but it can still create confusion.
Due to their "abstract" nature (and considering the above aspects), you can trigger an action in many ways, and a triggered action can call connected slots in unexpected ways if the whole QAction concept is clear to the developer.
It's extremely important to understand all that, as the implementation of their "triggering" might change dramatically, and awareness of those aspects is mandatory to properly implement their usage.
For instance, using a list that groups actions might not be the proper choice, and you may consider QActionGroup instead (no matter if the actions are checkable or the group is exclusive).

how to tell filterAcceptsRow to not to filter parent if expanded

I have a searchable tree view that works very well for all root nodes. However, I have no idea how to stop it from removing the parent to search in the children. It only works if the parent and the child match the search criteria. For intance if I search for "fresh" in the example from the image it will not display the third line as the parent will be hidden.
in the filterAcceptsRow I only have access to the proxy model and I cannot check if something is expanded or not. At least I have no idea how to do it to simply ignore all expanded items from the filter to allow searching in their children.
the newer versions of QT have this functionality built in setRecursiveFilteringEnabled but unfortunately I'm stuck with an old one for a while.
def filterAcceptsRow(self, source_row, source_parent_index):
model = self.sourceModel()
source_index = model.index(source_row, 0, source_parent_index)
# my naive attempt that only works for views that dynamically populate the children
# that totally fails on statically popluated ones as it thinks that everythign is expanded
# if model.hasChildren(source_index) and not model.canFetchMore(source_index):
# return True
d = model.searchableData(source_index) #this simply returns a string that I can regex in the filter
return self.isVisible(d) #some custom regex magic not important here
ideally, I would love to keep the parent if the filter matches anything in the children (or in the parent itself)
You need to call the filter function recursively and return True if any of the children also returns True:
def filterAcceptsRow(self, source_row, source_parent_index):
model = self.sourceModel()
source_index = model.index(source_row, 0, source_parent_index)
for child_row in range(model.rowCount(source_index)):
if self.filterAcceptsRow(child_row, source_index):
return True
d = model.searchableData(source_index)
return self.isVisible(d)
Obviously, if the model is very extended, you should consider some caching, but you probably would need to use QPersistentModelIndex as keys, and override whatever functions you use to update the filter (assuming they're being called programmatically, since they are not virtual and overriding them for an internal Qt implementation, such as a QCompleter, will not work).

TreeItemId obtained from TreeEvent is not the same as obtained from AppendItem

I am building a program that will store some complex objects, and I am using wxPython for the UI. The objects hierarchy will have a tree representation (TreeCtrl). I am using a dictionary to map objects from the UI to the database, using the TreeItemIds returned by AppendItem as keys and the objects themselves as values (actually I am not using the objects as values, but it simplifies the problem). The following snippet exemplifies what I am trying to do:
import wx
class ComplexObject(object):
def __init__(self, name, otherdata):
self.name = name
self.otherdata = otherdata
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(TestFrame, self).__init__(*args, **kwargs)
self.tree = wx.TreeCtrl(self)
self.rootid = self.tree.AddRoot("Root")
self.tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.onrightclick)
self.objectmap = {}
def addobject(self, obj):
itemid = self.tree.AppendItem(self.rootid, obj.name)
self.objectmap[itemid] = obj
def onrightclick(self, event):
itemid = event.GetItem()
if itemid == self.rootid:
return
obj = self.objectmap[itemid]
print "Do something with ComplexObject {}".format(obj.name)
if __name__ == '__main__':
app = wx.App(False)
testframe = TestFrame(None)
for i in range(3):
obj = ComplexObject('obj{}'.format(i), i)
testframe.addobject(obj)
testframe.Show()
app.MainLoop()
When I right-click an entry in the tree I get a KeyError, because the object I get from the event (itemid = event.GetItem()) is not the same I get when I add an item (itemid = self.tree.AppendItem(self.rootid, obj.name)). Is this the expected behavior? How should I proceed to achieve what I am trying to do? I am starting to experiment with SetPyData and GetPyData, but I hope there is a better way to do that.
Thank you very much.
Platform Information: MS Windows 7, Python 2.7.9, wxPython 2.8.12.1
Yes, it is expected.
You can think of the TreeItemId as a closed box with an implementation dependent handle inside, and that the treectrl is the only one that can open the box to get the handle out. You may see different boxes at different times for the same tree item, but they will all have the same handle inside. But since the handle itself is an implementation detail there is no program access to it.
Using SetPyData to associate data to tree items is the proper way to do things like this. If you want to keep your associated data in a separate dictionary then you could generate unique dictionary keys when adding the items, and then pass they key to SetPyData, and use GetPyData to fetch the key later when you need to fetch the value object from the dictionary.

How to change Operator's label in Blender 2.63 depending on the context?

I'm writing an exporter for a game my friend and I are making and it involves setting custom properties and tags to objects which are then recognized in the game and dealt with accordingly. Our engine, which is written in C/C++ has been successfully tested with my current version of the export script, and I''m currently working on tidying it up.
The script uses Blender's feature of custom properties to write custom tags to output file. The model typically consists of multiple 'parts' (Blender mesh objects that are parented to form a tree, with one 'parent' and multiple 'child' objects) and some of those parts are simple Blender Empty objects (for only it's X, Y and Z coordinates are needed) with custom properties that mark where things like ship's propulsion (it's a 3D shooter) are placed, or where the flames/explosions appear when ship's been shot. Those empty parts are also parented to either 'root' object or any of it's children. So far it's been working good, I have written a generic Operator class and some extended classes that reside in a panel which set part's properties (pretty handy since you don't have to add those custom properties by hand).
Now I want to speed thing up even more, that is to be able to simply click on an operator of desired type, and it should automatically add it to the scene and parent it to the active/selected object. I know how to do that, but I can't get those operators to change their labels. Basically, what I want is to operator to say 'Bullet point' when an existing empty is selected (I've got that part done), and when there's a mesh object selected to say 'Add bullet point'. So I just need a way to dynamically change operators' labels depending on the context (as the title of the question states clearly :))
This is what I got so far:
class OBJECT_OT_tg_generic (bpy.types.Operator):
bl_label = "Sets Generic Part Type"
bl_idname = "rbm.set_generic_part_type"
OB_TYPE = None
#classmethod
def poll (cls, context):
act = context.active_object
if 'Type' in act.keys ():
if act['Type'] == cls.OB_TYPE:
cls.bl_label = 'foo'
print (cls.bl_label)
# this prints foo but doesn't change the label
return False
return True
def execute (self, context):
# TODO: add code to automatically place empties and parent them to active object
bpy.context.active_object['Type'] = self.OB_TYPE
return{"FINISHED"}
And an example of a subclass:
class OBJECT_OT_tg_bullet_point (OBJECT_OT_tg_generic):
bl_label = "Bullet Point"
bl_idname = "rbm.set_bullet_point"
OB_TYPE = OT_BULLET_POINT
Here's how it looks in Blender:
http://i.imgur.com/46RAS.png
Guess I solved it. When you're adding an operator to a panel, you can do something like this:
def draw (self, context):
layout = self.layout
row = layout.row()
row.operator("foo.bar", text="Whatever you want")
and the "Whatever you want" is going to be your button's label. But what I did was something else. I didn't change the operators' labels, but instead gave them a different icons depending on whether it's a mesh or an empty currently selected/active:
def draw (self, context):
# (...) we're skipping some code here, obviously
act = context.active_object
if act.type == 'MESH':
op_icon = 'ZOOMIN'
else:
op_icon = 'EMPTY_DATA'
row = layout.column(align=True)
row.operator('rbm.set_bullet_point', icon=op_icon)
row.operator('rbm.set_rocket_point', icon=op_icon)
# (...) rest of the code

Lists in pgu and python

I'm searching for help about lists in pgu gui for python. I need to know which methods and properties they have, so I can include them in my programs
My first suggestion
Use python's builtins to introspect on
import pgu.gui.List
import inspect
help(pgu.gui.List) # to read out the doc strings
print dir(pgu.gui.List) # to get the namespace of List class
# to get all methods of that class
all_functions = inspect.getmembers(pgu.gui.List, inspect.isfunction)
Always look at the source code, when it is available
http://code.google.com/p/pgu/source/browse/trunk/pgu/gui/area.py
From the code: You can create and clear a list.
class List(ScrollArea):
"""A list of items in an area.
<p>This widget can be a form element, it has a value set to whatever item is selected.</p>
<pre>List(width,height)</pre>
"""
....
def clear(self):
"""Clear the list.
<pre>List.clear()</pre>
"""
...
Usage :
# Create the actual widget
usagelist = gui.List(width, height)

Categories