Determine the element which previously had focus?

In Java a FocusEvent has a method getOppositeComponent() which is where the focus came from or went to.

In PyQt5 is there any way to find what previously had focus when overriding focusInEvent?

MRE:

Actually this mechanism may work OK...

As explained in a note, I want to be able to start an edit session automatically when the table view gets focus, end an edit session by going Ctrl-E in the cell, leaving focus in the table view but without triggering another edit session.

import sys
from PyQt5 import QtWidgets, QtCore, QtGui

class TableViewDelegate( QtWidgets.QStyledItemDelegate ):
    def createEditor(self, parent, option, index):
        # QPlainTextEdit for multi-line text cells... 
        editor = QtWidgets.QPlainTextEdit( parent )
        table_view = parent.parent()
        def end_edit():
            print( f'end_edit...' )
            # clunky mechanism 
            table_view.do_not_start_edit = True
            self.closeEditor.emit( editor )
            
        self.end_edit_shortcut = QtWidgets.QShortcut( 'Ctrl+E', editor, context = QtCore.Qt.ShortcutContext.WidgetShortcut )
        self.end_edit_shortcut.activated.connect( end_edit )
        return editor

class TableViewModel( QtCore.QAbstractTableModel ):
    def __init__( self ):
        super().__init__()
        self._data = [
            [ 1, 2, ],
            [ 3, 4, ],
        ]
    
    def rowCount( self, *args ):
        return len( self._data )
    
    def columnCount(self, *args ):
        return 2
    
    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            # print( f'data... {self._data[index.row()][index.column()]}')
            return self._data[index.row()][index.column()]
        
    def flags( self, index ):
        result = super().flags( index )
        return QtCore.Qt.ItemFlag.ItemIsEditable | result
    
class TableView( QtWidgets.QTableView ):
    def __init__(self ):
        super().__init__()
        self.setTabKeyNavigation( False )
        self.setItemDelegate( TableViewDelegate() ) 
    
    def focusInEvent( self, event ):
        print( f'table view focus-in event')
        super().focusInEvent( event )
        
        if hasattr( self, 'do_not_start_edit' ):
            print( f'start of edit vetoed...')
            del self.do_not_start_edit
            return
        
        n_rows = self.model().rowCount()
        if n_rows == 0:
            return
        # go to last row, col 1
        cell1_index = self.model().index( n_rows - 1, 1 )
        self.edit( cell1_index )

class MainWindow( QtWidgets.QMainWindow ):
    def __init__( self ):
        super().__init__()
        self.setWindowTitle( 'Editor focus MRE' )
        layout = QtWidgets.QVBoxLayout()
        self.table_view = TableView()
        table_label = QtWidgets.QLabel( '&Table' )
        layout.addWidget( table_label )
        table_label.setBuddy( self.table_view )
        layout.addWidget( self.table_view )
        self.table_view.setModel( TableViewModel() )
        edit_box = QtWidgets.QLineEdit()
        layout.addWidget( edit_box )
        centralwidget = QtWidgets.QWidget( self )
        centralwidget.setLayout( layout )
        self.setCentralWidget( centralwidget )
        self.setGeometry( QtCore.QRect(400, 400, 400, 400) )
        
        edit_box.setFocus()
                
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())    

Start by going Alt-T: this moves focus to the QTableView and starts an edit of the bottom-right cell. Enter some text, and then press Ctrl-E. This stops the edit session and because of the clunky attribute do_not_start_edit, a new edit session is (as desired) vetoed. Another way to end the edit session is to click in the QLineEdit, for example.

I'm not sure that this rather clunky "added attribute" mechanism works in all circumstances. In fact it seems to work a bit better than I at first thought, hence this MRE... To me it doesn't seem very elegant.

1 answer

  • answered 2020-11-23 20:00 musicamante

    Since you want to start editing a cell only after specific focus in events, you have to check the reason() of the event.

    A problem with your approach is that you're using the buddy feature of the label to set the focus, which might be an issue if you want to start editing whenever the shortcut is activated. If the table already has focus, it cannot receive a focusInEvent.

    In order to achieve this, you can create a QAction with the wanted shortcut, add it to the widget and create a dedicated function in the table to start editing. Then you can "simulate" the mnemonic effect on the label using html tags.

    class TableView(QtWidgets.QTableView):
        def __init__(self):
            super().__init__()
            self.setTabKeyNavigation(False)
        
        def startEdit(self):
            n_rows = self.model().rowCount()
            if n_rows:
                cell1_index = self.model().index(n_rows - 1, 1)
                self.edit(cell1_index)
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            # ...
            table_label = QtWidgets.QLabel('<u>T</u>able')
            layout.addWidget(table_label)
            # this is not necessary anymore...
            # table_label.setBuddy(self.table_view)
    
            self.startEditAction = QtWidgets.QAction(self)
            self.startEditAction.setShortcut('alt+t')
            self.startEditAction.triggered.connect(self.table_view.startEditLastCell)
            self.addAction(self.startEditAction)