Home > database >  PyQt5 Context Menu w different actions for different applications
PyQt5 Context Menu w different actions for different applications

Time:02-17

I have multiple PyQt5 applications with QTableViews that inherit from a subclass "MyTable" of QTableView. In so doing, I am able to maintain the code for a context menu in one place (gen_context_menu) that's useful to all apps that use MyTable.

But today I encountered a need for a new application that will both get the benefit of the existing context menu in MyTable, but also get new/additional context menu options that I do NOT want grant legacy/universal applications access to.

In the MRE below, I wrote a couple dummy functions to show what was vaguely in my mind to accomplish that - I thought I'd encapsulate the getting of the 'standard menu' (with get_standard_menu), MyTable would call that in the legacy/universal case, and then for the new application, call a 'new' menu function (get_sending_menu) through a parameter or small subclass of MyTable perhaps. As I went to code that, I realized that since the 'actions' are coupled with the menu definition, it wasn't simple the way I thought it would be, and then I immediately thought 'there must be a better way'...

Is there?

import logging
import sys

from PyQt5 import QtWidgets, QtCore
from PyQt5.Qt import QAbstractTableModel, QVariant, Qt, QMainWindow

__log__ = logging.getLogger()


def get_sending_menu():
    menu = get_standard_menu()
    action_send = menu.addAction("Send!")
    return menu


def get_standard_menu():
    menu = QtWidgets.QMenu()
    action_get_ticks = menu.addAction("Get Ticks")
    action_get_events = menu.addAction("Get Events")
    action_get_overview = menu.addAction("Get Overview")
    return menu


class MyModel(QAbstractTableModel):
    rows = [('X'), ('Y'), ('Z')]
    columns = ['Letter']
    
    def rowCount(self, parent):
        return len(MyModel.rows)

    def columnCount(self, parent):
        return len(MyModel.columns)
    
    def data(self, index, role):
        if role != Qt.DisplayRole:
            return QVariant()
        return MyModel.rows[index.row()][index.column()]


class MyTable(QtWidgets.QTableView):
    
    def __init__(self, parent=None, ):
        super().__init__()
        self.tick_windows = []
        self.events_windows = []
        self.overview_windows = []
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.gen_context_menu)
        mre_model = MyModel()
        self.setModel(mre_model)
    
    def gen_context_menu(self, pos):
        index = self.indexAt(pos)
        if not index.isValid() or index.column() != 0:
            return
        input_value = index.sibling(index.row(), index.column()).data()
        menu = QtWidgets.QMenu()
        action_get_ticks = menu.addAction("Get Ticks")
        action_get_events = menu.addAction("Get Events")
        action_get_overview = menu.addAction("Get Overview")
        action = menu.exec_(self.viewport().mapToGlobal(pos))
        if action == action_get_ticks:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.tick_windows.append(model_window)
        elif action == action_get_events:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.events_windows.append(model_window)
        elif action == action_get_overview:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.overview_windows.append(model_window)
            
class App(QtWidgets.QApplication):
    
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main_view = MyTable()
        self.main_view.show()
            
if __name__ == '__main__':
    logging.basicConfig()
    app = App(sys.argv)
    try:
        sys.exit(app.exec_())
    except Exception as e:
        __log__.error('%s', e)```

CodePudding user response:

@musicamante i think this answer takes your advice - mostly. i am not sure if you were suggesting i subclass MyTable with MySpecialTable. if you were, i am not sure i am efficiently overloading gen_context_menu. finally, i am also not sure i implemented the pos/event stuff efficiently. however, this does seem to work and take your advice around standardContextMenu, triggered, and contextMenuEvent. To the extent this is an effort at best practice/good habits, please feel free to refine it further. Thank you.

import logging
import sys

from PyQt5 import QtWidgets
from PyQt5.Qt import QAbstractTableModel, QVariant, Qt, QMainWindow

__log__ = logging.getLogger()


class MyModel(QAbstractTableModel):
    rows = [('X'), ('Y'), ('Z')]
    columns = ['Letter']
    
    def rowCount(self, parent):
        return len(MyModel.rows)

    def columnCount(self, parent):
        return len(MyModel.columns)
    
    def data(self, index, role):
        if role != Qt.DisplayRole:
            return QVariant()
        return MyModel.rows[index.row()][index.column()]


class MyTable(QtWidgets.QTableView):
    
    def __init__(self, parent=None):
        super().__init__()
        self.tick_windows = []
        self.events_windows = []
        self.overview_windows = []
        mre_model = MyModel()
        self.setModel(mre_model)
        
    def get_input_value(self, pos):
        index = self.indexAt(pos)
        if not index.isValid() or index.column() != 0:
            return None
        input_value = index.sibling(index.row(), index.column()).data()
        return input_value

    def contextMenuEvent(self, event):
        pos = event.globalPos()
        input_value = self.get_input_value(self.viewport().mapFromGlobal(pos))
        if not input_value:
            return
        menu = self.gen_context_menu(input_value)
        menu.exec(event.globalPos())

    def get_ticks_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def get_events_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def get_overview_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def gen_context_menu(self, input_value):
        menu = QtWidgets.QMenu()
        menu = self.get_standard_menu(menu, input_value)
        return menu

    def get_standard_menu(self, menu, input_value):
        action_get_ticks = menu.addAction("Get Ticks")
        action_get_ticks.triggered.connect(lambda: self.get_ticks_triggered(input_value))
        action_get_events = menu.addAction("Get Events")
        action_get_events.triggered.connect(lambda: self.get_events_triggered(input_value))
        action_get_overview = menu.addAction("Get Overview")
        action_get_overview.triggered.connect(lambda: self.get_overview_triggered(input_value))
        return menu


class MyTableSpecial(MyTable):
    
    def get_special_menu(self, menu, input_value):
        action_send = menu.addAction("Send!")
        action_send.triggered.connect(lambda: self.send_triggered(input_value))
        return menu
    
    def send_triggered(self, input_value):
        __log__.info(input_value)
    
    def gen_context_menu(self, input_value):
        menu = QtWidgets.QMenu()
        menu = super().get_standard_menu(menu, input_value)
        menu = self.get_special_menu(menu, input_value)
        return menu
    

class App(QtWidgets.QApplication):
    
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main_view = MyTableSpecial()
        self.main_view.show()


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    app = App(sys.argv)
    try:
        sys.exit(app.exec_())
    except Exception as e:
        __log__.error('%s', e)
  • Related