Qt QSystemTrayIcon change menu items - python

I am using Pyqt however c++ code is fine. I am trying to change a menu item in QSystemTrayIcon using the QT framework in Linux (Ubuntu 11.10). Currently I have tried to reset the QMenu that I initially set:
self.tray = QSystemTrayIcon()
m = QMenu()
m.addAction('First')
m.addAction('Second')
tray.setContextMenu(m)
I place this in my class and make tray a class variable. I was thinking that if I just change the tray to set a new menu it would update:
new_m = QMenu()
new_m.addAction('First')
new_m.addAction('Third')
self.tray.setContextMenu(new_m)
However that doesn't work and the tray menu is still the same as it was initially made. How could I be able to rebuild the menu to change it?

I tested with the following code and it seems to work fine :
from PyQt4.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.tray = QSystemTrayIcon(QApplication.style().standardIcon(QStyle.SP_DriveDVDIcon), self)
m = QMenu()
m.addAction('First')
m.addAction('Second')
self.tray.setContextMenu(m)
self.tray.show()
p = QPushButton("test", self)
self.setCentralWidget(p)
p.clicked.connect(self.onClick)
def onClick(self):
new_m = QMenu()
new_m.addAction('First')
new_m.addAction('Third')
self.tray.setContextMenu(new_m)
app = QApplication(sys.argv)
w = MainWindow()
w.show();
sys.exit(app.exec_())
Are you sure there is only one QSystemTrayIcon object ? (In your snippets, there is both self.tray and tray).

Related

Where is the correct place to create/destroy and show/hide a QMdiSubWindow menu?

I am learning Python by creating an MDI application in PyQt5. This application contains a class derived from QMdiSubWindow. These sub-windows need their own menu to be added to the main menu-bar. Where is the 'correct' place to create and show/hide that part of the menu which is only relevant to the sub-window when it's in focus? And where should the menu be destroyed (if it doesn't happen automatically because ownership is taken by the parent)? My attempt at detecting when the sub-window gains/loses focus causes infinite recursion, presumably because the newly visible menu steals the focus back from the sub-window.
This is probably such a common requirement that it's not mentioned in the tutorials, but the only reference in the docs to sub-window menus seems to just refer to the system menu, and not the main menu-bar. Most other Q&A's just refer to activating other sub-windows from the main menu. Several hours of searching haven't quite got what I need, so thank you for your help in either pointing me to the right place in the docs, or by improving my code ... or even both!
A minimal app to illustrate the problem:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class TestSubWin(QMdiSubWindow):
def __init__(self, parent=None):
super().__init__()
self.setWidget(QLabel("Hello world"))
self.own_menu = QMenu("Sub win menu")
parent.menuBar().addMenu(self.own_menu)
# Add sub-window actions to the menu here
## Causes infinite recursion
# def focusOutEvent(self, event):
# self.own_menu.setVisible(False)
#
# def focusInEvent(self, event):
# self.own_menu.setVisible(True)
class MainWindow(QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.triggered[QAction].connect(self.windowaction)
self.setWindowTitle("MDI demo")
def windowaction(self, q):
if q.text() == "New":
sub = TestSubWin(self)
self.mdi.addSubWindow(sub)
sub.show()
def main():
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
QMenu and QMenuBar don't take ownership of QActions (and QMenus), unless when created with the functions that accepts icon/title arguments.
This also means that you shall not need to destroy the menus, but only remove them from the menu bar.
The solution is to connect to the subWindowActivated signal, remove the previously added menu, retrieve the menu for the newly active sub window (if any) and add it.
Note that in order to remove a menu from QMenuBar you have to use removeAction() along with the menuAction(), which is the action associated with the menu and shown as menubar title for the menu (or item in a menu for sub menus).
In the following example I'm creating a base subclass for any mdi subwindows that will support menubar menus, and further subclasses for different window types.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MenuSubWin(QMdiSubWindow):
own_menu = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
def menu(self):
return self.own_menu
class TestSubWin1(MenuSubWin):
def __init__(self):
super().__init__()
self.setWidget(QLabel("Hello world"))
self.own_menu = QMenu("Sub win menu 1")
self.own_menu.addAction('Test 1')
class TestSubWin2(MenuSubWin):
def __init__(self):
super().__init__()
self.setWidget(QLabel("How are you?"))
self.own_menu = QMenu("Sub win menu 2")
self.own_menu.addAction('Test 2')
class MainWindow(QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
fileMenu = bar.addMenu("File")
new1Action = fileMenu.addAction("New 1")
new1Action.setData(TestSubWin1)
new2Action = fileMenu.addAction("New 2")
new2Action.setData(TestSubWin2)
fileMenu.triggered.connect(self.newWindow)
self.setWindowTitle("MDI demo")
self.subWinMenu = None
self.mdi.subWindowActivated.connect(self.subWindowActivated)
def subWindowActivated(self, subWindow):
if self.subWinMenu:
self.menuBar().removeAction(self.subWinMenu.menuAction())
self.subWinMenu = None
if subWindow is None or not hasattr(subWindow, 'menu'):
return
self.subWinMenu = subWindow.menu()
if self.subWinMenu:
self.menuBar().addMenu(self.subWinMenu)
def newWindow(self, action):
cls = action.data()
if not cls:
return
sub = cls()
self.mdi.addSubWindow(sub)
sub.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Notes:
you shall always set the WA_DeleteOnClose attribute when directly adding a QMdiSubWindow (as opposed to adding a QWidget), otherwise the window will still exist for the MDI area and listed in the subWindowList(), thus preventing proper focus switching (and menu removal) upon closure;
for simplicity, I used the setData() feature of QAction with the class of the window that has to be created;
specifying the signature of signals is only required when signals do have overrides, which is unnecessary for triggered() since it has no overrides; note that Qt is gradually removing signal overrides, preferring explicit and unique signals instead;

How to disable default context menu of QTableView in pyqt?

I am trying to disable a default context menu of QTableView in pyqt.
I have re-implemented the contextMenuEvent but it works on 1st time right click. When I click on the same Item 2nd time the default context menu reappears. (Image attached below for referance.)
I tried "QTableView.setContextMenuPolicy(Qt.NoContextMenu)" but it didn't work. Also referred the answers of similar type questions but still the issue is unresolved.
Any idea?
Ex. showing Re-implemented context menu in QTableView.
def contextMenuEvent(self, event):
menu = QMenu(self)
CutAction = QAction(self.view)
CutAction.setText("&Cut")
menu.addAction(CutAction)
CutAction.setIcon(QIcon(":/{0}.png".format("Cut")))
CutAction.setShortcut("Ctrl+X")
self.connect(CutAction, SIGNAL("triggered()"), self.cut)
with the code that shows I can not reproduce your problem, even so the solution is to use Qt::CustomContextMenu by enabling the signal customContextMenuRequested, and in the corresponding slot you have to implement the logic:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class TableView(QTableView):
def __init__(self, *args, **kwargs):
super(TableView, self).__init__(*args, **kwargs)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
def onCustomContextMenuRequested(self, pos):
menu = QMenu()
CutAction = menu.addAction("&Cut")
menu.addAction(CutAction)
CutAction.setIcon(QIcon(":/{0}.png".format("Cut")))
CutAction.setShortcut("Ctrl+X")
CutAction.triggered.connect(self.cut)
menu.exec_(self.mapToGlobal(pos))
def cut(self):
pass
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = TableView()
model = QStandardItemModel(10, 10, w)
w.setModel(model)
w.show()
sys.exit(app.exec_())

Notification when QDockWidget's tab is clicked?

I need to execute a block of code when the user clicks on the tab of a tabbified QDockWidget. So far I've been doing this via a hack using the "visibilityChanged" event but this is now causing issues (for example, if I have several tabbified dock widgets and I drag one out so that it is floating, the tabbified one underneath will fire its "visibilityChanged" event which I will mistakenly interpret as the user clicking the tab). How can I receive proper notification when a user clicks on a QDockWidgets' tab? I've experimented with the "focusInEvent" of QDockWidget but it doesn't seem to fire when the tab is clicked.
When you use tabifyDockWidget() method QMainWindow creates a QTabBar, this is not directly accessible but using findChild() you can get it, and then use the tabBarClicked signal
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
first_dock = None
for i in range(10):
dock = QtGui.QDockWidget("title {}".format(i), self)
dock.setWidget(QtGui.QTextEdit()) # testing
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock)
if first_dock:
self.tabifyDockWidget(first_dock, dock)
else:
first_dock = dock
dock.raise_()
tabbar = self.findChild(QtGui.QTabBar, "")
tabbar.tabBarClicked.connect(self.onTabBarClicked)
def onTabBarClicked(self, index):
tabbar = self.sender()
text = tabbar.tabText(index)
print("index={}, text={}".format(index, text))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

PySide: Setting setDisabled(True) for a child menu entry not working in Mac

I am creating a system tray application in Mac (El Capitan 10.11.3) using Pyside. However, I am not able to set certain menu entry as disabled, particularly when its a child menu. The setDisabled(True) works for parent menu entry though.
The same code works in Ubuntu/Linux and the child menu entry is disabled.
As the original code is too long to display here, I have made a simple sys tray application that exhibits the same problem.
import sys
from PySide import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
menu.addAction("Item 1")
menu.addAction("Item 2")
disabledItem = menu.addAction("Item 3 Disabled")
disabledItem.setDisabled(True)
subMenu = menu.addMenu("Item 4 with sub menu")
subMenu.addAction("SubMenu 1")
disabledSubMenu = subMenu.addAction("SubMenu 2 Disabled (Not Working)")
disabledSubMenu.setDisabled(True)
disabledSubMenu2 = subMenu.addMenu("SubMenu Disabled")
disabledSubMenu2.setDisabled(True)
self.setContextMenu(menu)
def main():
app = QtGui.QApplication(sys.argv)
style = app.style()
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_FileIcon)))
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
From the above example, Item 3 Disabled (which is a parent menu) is disabled when using setDisabled(True). But SubMenu 2 Disabled is not working, which is a child menu to Item 4 with sub menu.
One thing to note is SubMenu Disabled is working when its set as addMenu item than using addAction.
Any help regarding this is highly appreciated. Thanks!
http://doc.qt.io/qt-4.8/qaction.html#setDisabled
the name of the method is same with qmenu.But qmenu's set disabled is inherite from qwidget while qaction's method is only for itself and it's not for the user interface. also qaction is abstract what you can see is a qmenu actually.
#This is the example code for Setting setDisabled(True) for a child .
#If your are not expecting this answer, sorry.
#It is PyQt4, but you can try with PySide with small changes.
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class Window (QtGui.QWidget):
def __init__(self, parent=None):[![enter image description here][1]][1]
super(Window, self).__init__(parent)
menubar = QtGui.QMenuBar (self)
menubar.setObjectName('menubar')
menu = QtGui.QMenu(menubar)
menu.setObjectName('menu')
menu.setTitle ('File')
menubar.addAction (menu.menuAction())
menu.addAction ("Item 1")
menu.addAction ("Item 2")
disabledItem = menu.addAction("Item 3 Disabled")
disabledItem.setDisabled(True)
subMenu = menu.addMenu("Item 4 with sub menu")
subMenu.addAction ("SubMenu 1")
disabledSubMenu = subMenu.addAction("SubMenu 2 Disabled (Not Working)")
disabledSubMenu.setDisabled (True)
disabledSubMenu2 = subMenu.addMenu("SubMenu Disabled")
disabledSubMenu2.setDisabled(True)
self.resize(513, 203)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())

Update menu in QT system tray application

I need to update the existing menu items for a system tray application. At first when the app loads, there will be two menu items. Later when I click a button these menu items need to be replaced with new menu items. How can I achieve that ? Here is my code.
from PySide.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.tray = QSystemTrayIcon(QApplication.style().standardIcon(QStyle.SP_DriveDVDIcon), self)
self.m = QMenu()
self.m.addAction('First')
self.m.addAction('Second')
self.tray.setContextMenu(self.m)
self.tray.show()
p = QPushButton("Click Me", self)
self.setCentralWidget(p)
p.clicked.connect(self.onClick)
def onClick(self):
self.m.clear()
self.m.addAction('First')
self.m.addAction('Third')
self.tray.setContextMenu(self.m)
app = QApplication(sys.argv)
w = MainWindow()
w.show();
sys.exit(app.exec_())
However this is not working. If I try removing self.m.clear()the new menu items will append to the existing (Which is the normal behaviour in this case). Isn't menu.clear() clears the current menu & the new menu should be populated here ?
I have seen this similar question Qt QSystemTrayIcon change menu items and the solution doesn't work for me. I am running Ubuntu 14.04.
I figured it out, the problem is due to the self.tray.setContextMenu(self.m). Remove this line from onClick method. This should work fine on Ubuntu.

Categories