[GIS] Modify Attribute Table while opening using PyQGIS

attribute-tablepyqgis-3qgis

I have a problem related to this question and the script given as the answer. I can add some functionalities to Attribute Table (AT) by the script in the post. But according to the post, I have to open the AT using showAttributeTable method, because the method returns the reference of the AT created/opened.

Of course, I can get references of all opened ATs using the following line.

tables = [w for w in qApp.allWidgets() if w.objectName() == 'AttributeTable']

Let's say, I want to add a button to all ATs to be opened or sort by a column for a specific layer. Somehow, I think I need to catch "the AT's opening event" or "a widget/child was added to iface.mainWindow()" etc.

I have looked at Qt5 and QGIS API(QgsApplication) documentation, but I couldn't find anything helpful or I missed something.

A pseudo code for a possible solution:

def something_opened(something):
    if something is an attribute_table and active_layer is foo_bar:
        do something

main_window.addedsomething.connect(something_opened)

Note: @Ben's answer is pretty nice. But, it still requires me to focus on the AT.

Best Answer

Interesting question! I couldn't find any native signal emitted when an attribute table is opened or closed so I would call this solution a fairly inelegant workaround but it seems to work well enough. I found that QApplication has a focusChanged(old, new) signal which is emitted whenever the widget focus changes e.g. opening/ closing dialogs or clicking between non-modal windows etc. and returns the old and new widget objects.

class addAttributeTableAction(object):

    def __init__(self, app):
        self.app = app
        self.app.focusChanged.connect(self.attribute_dialog_opened)

    def __del__(self):
        self.app.focusChanged.disconnect(self.attribute_dialog_opened)

    def attribute_dialog_opened(self, old, new):
        if isinstance(new, QTableView):
            #I don't like the line below but I could't think of a better/quicker way to
            #return the Dialog object from the QTableView object returned by the 'new' parameter
            #of the focusChanged signal
            table_dialog = new.parent().parent().parent().parent()
            toolbar = [c for c in table_dialog.children() if isinstance(c, QToolBar)][0]
            # check if action has already been added to toolbar
            already_exists = [a for a in toolbar.actions() if a.objectName() == 'TestAction']
            if not already_exists:
                new_button = QAction('Test', table_dialog)
                new_button.setObjectName('TestAction')
                toolbar.addAction(new_button)
                new_button.triggered.connect(self.run_action)

    def run_action(self):
        '''Simple method to test action'''
        layer = iface.activeLayer()
        layer.selectByIds([1])

Test = addAttributeTableAction(qApp)
#Uncomment below andcomment above to stop listening for focusChanged signal
#del Test

Quick demo:

enter image description here

Related Question