Search code examples
pythonqtpyqtsignals-slotsqcalendarwidget

PyQt: Emit signal when cell entered in QCalendarWidget


In my Qt application I'm using the QCalendarWidget and I would like to get notified when the mouse enters a new cell of the calendar. I know that the QCalendarWidget is using a QTableView internally which inherits from QAbstractItemView and this has an entered signal:

This signal is emitted when the mouse cursor enters the item specified by index. Mouse tracking needs to be enabled for this feature to work.

I tried to receive the signal with following code:

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyCalendar:

    def __init__(self):
        app = QApplication(sys.argv)

        window = QMainWindow()
        cal = QCalendarWidget(window)

        window.resize(320, 240)
        cal.resize(320, 240)

        table = cal.findChild(QTableView)
        table.setMouseTracking(True)
        table.entered.connect(self.onCellEntered)

        window.show()
        sys.exit(app.exec_())

    def onCellEntered(self, index):
        print("CellEntered")

if __name__ == "__main__":
    window = MyCalendar()

But my callback function is never called. Do you have any ideas why?


Solution

  • The QCalendarWidget class uses a custom table-view which bypasses the normal mouse-event handlers - so the enetered signal never gets emitted. However, it is possible to work-around that by using an event-filter to emit a custom signal that does the same thing.

    Here is a demo script that implements that:

    import sys
    from PyQt4 import QtCore, QtGui
    
    class Window(QtGui.QWidget):
        cellEntered = QtCore.pyqtSignal(object)
    
        def __init__(self):
            super(Window, self).__init__()
            self.calendar = QtGui.QCalendarWidget(self)
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.calendar)
            self._table = self.calendar.findChild(QtGui.QTableView)
            self._table.setMouseTracking(True)
            self._table.installEventFilter(self)
            self._index = None
            self.cellEntered.connect(self.handleCellEntered)
    
        def eventFilter(self, source, event):
            if source is self._table:
                if event.type() == QtCore.QEvent.MouseMove:
                    index = QtCore.QPersistentModelIndex(
                        source.indexAt(event.pos()))
                    if index != self._index:
                        self._index = index
                        self.cellEntered.emit(QtCore.QModelIndex(index))
                elif event.type() == QtCore.QEvent.Leave:
                    self._index = None
            return super(Window, self).eventFilter(source, event)
    
        def handleCellEntered(self, index):
            print(index.row(), index.column())
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        window = Window()
        window.setGeometry(600, 100, 300, 200)
        window.show()
        sys.exit(app.exec_())