Context menu trigger intermittent on Mac OS X - python

I'm using right mouse clicks to display a context-sensitive menu within a QTreeView-based GUI. The context menu works correctly on Windows and Ubuntu. On Mac OS X, the context menu displays correctly but when the user selects an option from the context menu the corresponding operation is only performed about once every 10 times the menu is invoked and a selection made.
The environment is Python V3.9.6 and PyQt V5.12.3 on all machines, installed using conda. The Ubuntu machine is 20.04, Windows 10 is a virtual machine (VBox) under the Ubuntu host and the Mac OS X machine is a MacInCloud server running Catalina.
I've included an MRE below that puts up a basic tree view. Right clicking on an entry will display the context menu and selecting a context menu entry should print a message in the console. This works correctly on Ubuntu and Windows but only intermittently on Mac OS X.
import sys
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class main_ui(QtWidgets.QWidget):
def __init__(self):
super(main_ui, self).__init__()
# set the window title and size
self.setWindowTitle("Context menu trigger")
self.resize(500, 300)
# set the model
self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Variable"])
# set the view and link it to the model
self.view = QtWidgets.QTreeView()
self.view.setModel(self.model)
self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.view.customContextMenuRequested.connect(self.context_menu)
self.view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
# define the context menu
self.context_menu = QtWidgets.QMenu()
self.context_menu.plot_time_series = QtWidgets.QAction(self)
self.context_menu.plot_time_series.setText("Plot time series")
self.context_menu.plot_time_series.triggered.connect(self.plot_time_series)
self.context_menu.addAction(self.context_menu.plot_time_series)
# create the model
self.create_model()
# set the layout
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.view)
def context_menu(self, position):
print("Displaying context menu")
self.context_menu.exec_(self.view.viewport().mapToGlobal(position))
def create_model(self):
keys = ["Variable1", "Variable2", "Variable3"]
for key in keys:
self.model.appendRow([QtGui.QStandardItem(key)])
def plot_time_series(self):
print("In plot_time_series")
if (__name__ == '__main__'):
app = QtWidgets.QApplication(["Test"])
ui = main_ui()
ui.show()
sys.exit(app.exec_())
I have tried reverting to earlier versions of Python 3 and PyQt but the problem persists and I've been unable to find a related question here or elsewhere.

Further debugging of this problem showed that the intermittent behaviour of the right-click context menu only occurred on a "cloud-based" Mac running Catalina. The problem did not occur on a "cloud-based" Mac running High Sierra or on a physical Mac running Catalina.

Related

How to attach and detach an external app with PyQT5 or dock an external application?

I'm developing an GUI for multi-robot system using ROS, but i'm freezing in the last thing i want in my interface: embedding the RVIZ, GMAPPING or another screen in my application. I already put an terminal in the interface, but i can't get around of how to add an external application window to my app. I know that PyQt5 have the createWindowContainer, with uses the window ID to dock an external application, but i didn't find any example to help me with that.
If possible, i would like to drag and drop an external window inside of a tabbed frame in my application. But, if this is not possible or is too hard, i'm good with only opening the window inside a tabbed frame after the click of a button.
I already tried to open the window similar to the terminal approach (see the code bellow), but the RVIZ window opens outside of my app.
Already tried to translate the attaching/detaching code code to linux using the wmctrl command, but didn't work wither. See my code here.
Also already tried the rviz Python Tutorial but i'm receveing the error:
Traceback (most recent call last):
File "rvizTutorial.py", line 23, in
import rviz
File "/opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py", line 19, in
import librviz_shiboken
ImportError: No module named librviz_shiboken
# Frame where i want to open the external Window embedded
self.Simulation = QtWidgets.QTabWidget(self.Base)
self.Simulation.setGeometry(QtCore.QRect(121, 95, 940, 367))
self.Simulation.setTabPosition(QtWidgets.QTabWidget.North)
self.Simulation.setObjectName("Simulation")
self.SimulationFrame = QtWidgets.QWidget()
self.SimulationFrame.setObjectName("SimulationFrame")
self.Simulation.addTab(rviz(), "rViz")
# Simulation Approach like Terminal
class rviz(QtWidgets.QWidget):
def __init__(self, parent=None):
super(rviz, self).__init__(parent)
self.process = QtCore.QProcess(self)
self.rvizProcess = QtWidgets.QWidget(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.rvizProcess)
# Works also with urxvt:
self.process.start('rViz', [str(int(self.winId()))])
self.setGeometry(121, 95, 940, 367)
I've not tested this specifically, as I've an old version of Qt5 I can't upgrade right now, while from Qt5 5.10 startDetached also returns the pid along with the bool result from the started process.
In my tests I manually set the procId (through a static QInputBox.getInt()) before starting the while cycle that waits for the window to be created.
Obviously there are other ways to do this (and to get the xid of the window).
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
started, procId = proc.startDetached()
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!')
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
if w.get_pid() == procId:
window = QtGui.QWindow.fromWinId(w.get_xid())
container = QtWidgets.QWidget.createWindowContainer(window, self)
self.addTab(container, command)
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())
I couldn't get the code in the accepted answer to work on Ubuntu 18.04.3 LTS; even when I got rid of the exceptions preventing the code to run, I'd still get a separate PyQt5 window, and separate xterm window.
Finally after some tries, I got the xterm window to open inside the tab; here is my code working in Ubuntu 18.04.3 LTS (with all the misses commented):
#!/usr/bin/env python3
# (same code seems to run both with python3 and python2 with PyQt5 in Ubuntu 18.04.3 LTS)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
import time
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
#started, procId = proc.startDetached()
#pid = None
#started = proc.startDetached(pid)
# https://stackoverflow.com/q/31519215 : "overload" startDetached : give three arguments, get a tuple(boolean,PID)
# NB: we will get a failure `xterm: No absolute path found for shell: .` even if we give it an empty string as second argument; must be a proper abs path to a shell
started, procId = proc.startDetached(command, ["/bin/bash"], ".")
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!'.format(command), "Eh")
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# do a bit of sleep, else window is not really found
time.sleep(0.1)
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
print(attempts, w.get_pid(), procId, w.get_pid() == procId)
if w.get_pid() == procId:
self.window = QtGui.QWindow.fromWinId(w.get_xid())
#container = QtWidgets.QWidget.createWindowContainer(window, self)
proc.setParent(self)
#self.scrollarea = QtWidgets.QScrollArea()
#self.container = QtWidgets.QWidget.createWindowContainer(self.window)
# via https://vimsky.com/zh-tw/examples/detail/python-method-PyQt5.QtCore.QProcess.html
#pid = proc.pid()
#win32w = QtGui.QWindow.fromWinId(pid) # nope, broken window
win32w = QtGui.QWindow.fromWinId(w.get_xid()) # this finally works
win32w.setFlags(QtCore.Qt.FramelessWindowHint)
widg = QtWidgets.QWidget.createWindowContainer(win32w)
#self.container.layout = QtWidgets.QVBoxLayout(self)
#self.addTab(self.container, command)
self.addTab(widg, command)
#self.scrollarea.setWidget(self.container)
#self.container.setParent(self.scrollarea)
#self.scrollarea.setWidgetResizable(True)
#self.scrollarea.setFixedHeight(400)
#self.addTab(self.scrollarea, command)
self.resize(500, 400) # set initial size of window
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())

UI made in QT Designer shifts behind Title Bar [duplicate]

I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.
Web Widget Commented Out
Web Widget Active
Example Code
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
class WebPage(QWebEngineView):
def __init__(self, parent=None):
QWebEngineView.__init__(self)
self.current_url = ''
self.load(QUrl("https://facebook.com"))
self.loadFinished.connect(self._on_load_finished)
def _on_load_finished(self):
print("Url Loaded")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# Initialize the Main Window
super(MainWindow, self).__init__(parent)
self.create_menu()
self.add_web_widet()
self.show()
def create_menu(self):
''' Creates the Main Menu '''
self.main_menu = self.menuBar()
self.main_menu_actions = {}
self.file_menu = self.main_menu.addMenu("Example File Menu")
self.file_menu.addAction(QAction("Testing Testing", self))
def add_web_widet(self):
self.web_widget = WebPage(self)
self.setCentralWidget(self.web_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.showMaximized()
sys.exit(app.exec_()) # only need one app, one running event loop
Development Environment
Windows 10, PyQt5, pyqt5-5.9
EDIT
The problem doesn't seem to be directly related to the menu bar. Even removing the menu bar the issue still occurs. That said, changing from showMaximized() to showFullScreen() does seem to solve the problem.
I no longer believe this is an issue with PyQt5 specifically but rather a problem with the graphics driver. Specifically, if you look at Atlassian's HipChat application it has a similar problem which is documented here:
https://jira.atlassian.com/browse/HCPUB-3177
Some individuals were able to solve the problem by running the application from the command prompt with the addendum "--disable-gpu" but that didn't work for my python application. On the other hand, rolling back the Intel(R) HD Graphics Driver did solve my problem. Version 21.20.16.4627 is the one that seems to be causing problems.

Running a Qt event loop at the R prompt

I'm trying to create a Qt application that can communicate with R via PyQt and the rPython package and remain responsive while the R prompt is live.
Here's a minimal PyQt4 script that creates a button in a window that prints "clicked" when you click it.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class Ui_simple(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Test")
Dialog.resize(200,200)
self.pushButton = QPushButton(Dialog)
QObject.connect(self.pushButton, SIGNAL("clicked()"), self.clicked)
def clicked(self):
print "Clicked"
class main(object):
def __init__(self):
self.app = QApplication([])
self.Dialog = QDialog()
self.ui = Ui_simple()
self.ui.setupUi(self.Dialog)
self.Dialog.show()
When run from the Python prompt, the application becomes active when the QDialog object is created and shown. The python prompt returns. I can then exec_ the application object, and I get the prompt back when I quit the dialog:
>>> import simple
>>> a = simple.main()
>>> Clicked [python prompt is back now, I'm clicking the button]
Clicked
>>> a.app.exec_()
Clicked
This behaviour - of being interactive while also waiting for Python text prompt input - is because PyQt4 installs an input hook that waits for keyboard input and processes events.
What I want to do is have this same behaviour with Qt dialogs called from R via the rPython package. The following displays the dialog but it does not appear until the exec_ call, and the R prompt does not return until the application is exited by quitting the dialog window:
> require(rPython)
Loading required package: rPython
Loading required package: RJSONIO
> python.load("simple.py")
> python.exec("a = main()") # nothing happens
> python.exec("a.app.exec_()") # dialog appears, R blocked
Clicked
Clicked # click the buttons
Clicked
0 # kill window now
> # back to R prompt
A solution to my problem would involve the dialog appearing when running a=main() and the R prompt reappearing at that point too.
There are some Qt-based R packages that appear to create dialogs that remain interactive when the R prompt is active, but they are mostly in C and I'm not sure if that make a big difference. See qtutils for example.
There is some event loop handling in qtbase but I'm not sure if something like that is possible for Qt apps called from rPython. All ideas welcome.

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.

Setting Application Menu name in GTK+/Python (fixing "Unknown Application Name")

When running GTK+ apps under Ubuntu 12.04, how do you set the application name that is displayed in the Application-level menu?
Here's an example app:
from gi.repository import GLib, Gtk, Gio
import sys
class MyApp(object):
def __init__(self):
GLib.set_application_name('My App')
self.app = Gtk.Application.new('org.example.test', 0)
self.app.connect('startup', self.on_app_startup)
self.app.connect('activate', self.on_app_activate)
self.app.connect('shutdown', self.on_app_shutdown)
def run(self, argv):
self.app.run(argv)
def on_app_startup(self, app):
self.window = Gtk.ApplicationWindow.new(app)
self.window.set_default_size(640, 480)
self.window.set_title('AppMenu Demo')
app.add_window(self.window)
# # App menu
app_menu = Gio.Menu()
section = Gio.Menu()
item = Gio.MenuItem.new('Quit', 'app.quit')
item.set_attribute_value("accel", GLib.Variant("s", "<Control>Q"))
section.append_item(item)
app_menu.append_section(None, section)
app.set_app_menu(app_menu)
# # Menu bar
menu_bar = Gio.Menu()
submenu = Gio.Menu()
section = Gio.Menu()
section.append_item(Gio.MenuItem.new('Help', 'app.help'))
submenu.append_section(None, section)
menu_bar.append_submenu('Help', submenu)
app.set_menubar(menu_bar)
action = Gio.SimpleAction.new('quit', None)
action.connect('activate', self.on_quit)
app.add_action(action)
def on_app_activate(self, app):
self.window.show_all()
def on_app_shutdown(self, app):
pass
def on_quit(self, action, data=None):
self.app.quit()
if __name__ == '__main__':
app = MyApp()
app.run(sys.argv)
If you run this code under Ubuntu 12.04, it pops up a window labeled "AppMenu Demo"; the control bar at the top of the screen shows this name as well. If you move your mouse to the control bar, two pulldown menus are displayed; the app menu, and a "Help" menu.
This is all fine - except that the name of the Application menu is "Unknown Application Name". I can't find any way to alter this name. GLib.set_application_name(name) doesn't do it. How do you set the application name?
Or: is this a case where GTK+ is ahead of what Ubuntu supports? Google searches for "unknown application name" point at a range of bug reports and merged patches, which suggests to me that this might be an area of current development, rather than stable API. A quick survey of apps installed in Ubuntu shows that most apps have a "File" menu, but nothing that would be identified as an "app" menu of the type that GTK+ seems to support. Should I just abandon app menus until they're better supported at an OS level?
It is a ubuntu specific/unity specific/ancient gtk+ 3.4.x/gtk+ 3.6.x bug.
With somewhat current (gtk+ 3.10.7 and Cinnamon 2.014 as DE) it works just fine.

Categories