Right-to-left alignment of PyQt4 menus - python

I use PyQt4 for designing GUI.
I want to know is there a way to sort menu items from right to left instead left to right for languages such as Arabic or Persian?

You have to use the setLayoutDirection() method of QApplicacion to indicate the Qt.RightToLeft address as shown below:
class menudemo(QMainWindow):
def __init__(self, parent = None):
super(menudemo, self).__init__(parent)
layout = QHBoxLayout()
bar = self.menuBar()
file = bar.addMenu("ملف")
file.addAction("الجديد")
save = QAction("حفظ",self)
file.addAction(save)
edit = file.addMenu("تصحيح")
edit.addAction("نسخ")
edit.addAction("معجون")
quit = QAction("استقال",self)
file.addAction(quit)
file.triggered[QAction].connect(self.processtrigger)
self.setLayout(layout)
self.setWindowTitle("RTL")
def processtrigger(self,q):
print(q.text()+" is triggered")
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setLayoutDirection(Qt.RightToLeft)
ex = menudemo()
ex.show()
sys.exit(app.exec_())
Screenshot:

Related

The layout is incorrect after remove widget

I am implement my project using pyqt5. Currently, I have a window including many widget. Now, I want to remove some widgets. The window looks like:
Now, I want to remove the 'name1' widget including the QLabel and QPushButton.
However, after removing all 'name1' widgets, the 'name2' widgets including QLabel and QPushButton can not self-adapte with the window, like:
All my code is:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subText = QLabel(name)
subText.setObjectName(name)
subBtn = QPushButton(name)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
def remove(self, checked=False):
name = self.__removeText.text()
while True:
child = self.__ROIsLayout.takeAt(0)
if child == None:
break
while True:
subChild = child.takeAt(0)
if subChild == None:
break
obName = subChild.widget().objectName()
if name == obName:
widget = subChild.widget()
widget.setParent(None)
child.removeWidget(widget)
self.__ROIsLayout.removeWidget(widget)
del widget
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
update:
Actually, the issue may be the takeAt. The following code is workable:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subLayout.setObjectName(name)
subText = QLabel(name, parent=self)
subText.setObjectName(name)
subBtn = QPushButton(name, parent=self)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
print(name, subLayout, subText, subBtn)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
self.record = [subLayout, subText, subBtn]
def remove(self, checked=False):
layout = self.record[0]
txt = self.record[1]
btn = self.record[2]
layout.removeWidget(txt)
txt.setParent(None)
txt.deleteLater()
layout.removeWidget(btn)
btn.setParent(None)
btn.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
But, I have printed the QLabel/QPushButton in the self.record, and I find it is the same with that from child.takeAt(0).widget().
The main issue in your code is that you're constantly using takeAt(). The result is that all items in the __ROIsLayout layout will be removed from it (but not deleted), which, in your case, are the sub layouts. This is clearly not a good approach: only the widgets with the corresponding object name will be actually deleted, while the others will still be "owned" by their previous parent, will still be visible at their previous position and their geometries won't be updated since they're not managed by the layout anymore.
There are multiple solutions to your question, all depending on your needs.
If you need to remove rows from a layout, I'd consider setting the object name on the layout instead, and look for it using self.findChild().
Also consider that, while Qt allows setting the same object name for more than one object, that's not suggested.
Finally, while using del is normally enough, it's usually better to call deleteLater() for all Qt objects, which ensures that Qt correctly removes all objects (and related parentship/connections).
Another possibility, for this specific case, is to use a QFormLayout.

PyQt5 move QDockWidget by dragging tab

The clip below shows dragging QDockWidgets between docking areas by dragging the tabs (not the title bar) - but when I try this with PyQt 5.15.0 it doesn't work, the tabs won't detach. How can I enable this behavior?
What I want:
https://www.screencast.com/t/lv83SoyVUhbd (from https://woboq.com/blog/qdockwidget-changes-in-56.html)
What I get:
https://www.screencast.com/t/bIUj4vLNTF
My code:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
if __name__ == "__main__":
app = QtWidgets.QApplication([])
main = QtWidgets.QMainWindow()
dock1 = QtWidgets.QDockWidget("Blue")
dock2 = QtWidgets.QDockWidget("Green")
dock3 = QtWidgets.QDockWidget("Red")
content1 = QtWidgets.QWidget()
content1.setStyleSheet("background-color:blue;")
content2 = QtWidgets.QWidget()
content2.setStyleSheet("background-color:green;")
content3 = QtWidgets.QWidget()
content3.setStyleSheet("background-color:red;")
dock1.setWidget(content1)
dock2.setWidget(content2)
dock3.setWidget(content3)
dock1.setAllowedAreas(Qt.AllDockWidgetAreas)
dock2.setAllowedAreas(Qt.AllDockWidgetAreas)
dock3.setAllowedAreas(Qt.AllDockWidgetAreas)
main.addDockWidget(Qt.LeftDockWidgetArea, dock1)
main.tabifyDockWidget(dock1, dock2)
main.addDockWidget(Qt.RightDockWidgetArea, dock3)
main.resize(400, 200)
main.show()
app.exec_()
The solution to my question was enabling the GroupedDragging with setDockOptions on the QMainWindow. I managed to get a really nice appearance & behavior exactly like I wanted with the code below.
Demo: https://www.screencast.com/t/GU5Z2ysT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class DockWidget(QtWidgets.QDockWidget):
def __init__(self, title: str):
super().__init__(title)
self.setTitleBarWidget(QtWidgets.QWidget())
self.dockLocationChanged.connect(self.on_dockLocationChanged)
def on_dockLocationChanged(self):
main: QtWidgets.QMainWindow = self.parent()
all_dock_widgets = main.findChildren(QtWidgets.QDockWidget)
for dock_widget in all_dock_widgets:
sibling_tabs = main.tabifiedDockWidgets(dock_widget)
# If you pull a tab out of a group the other tabs still see it as a sibling while dragging...
sibling_tabs = [s for s in sibling_tabs if not s.isFloating()]
if len(sibling_tabs) != 0:
# Hide title bar
dock_widget.setTitleBarWidget(QtWidgets.QWidget())
else:
# Re-enable title bar
dock_widget.setTitleBarWidget(None)
def minimumSizeHint(self) -> QtCore.QSize:
return QtCore.QSize(100, 100)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
main = QtWidgets.QMainWindow()
dock1 = DockWidget("Blue")
dock2 = DockWidget("Green")
dock3 = DockWidget("Red")
content1 = QtWidgets.QWidget()
content1.setStyleSheet("background-color:blue;")
content1.setMinimumSize(QtCore.QSize(50, 50))
content2 = QtWidgets.QWidget()
content2.setStyleSheet("background-color:green;")
content2.setMinimumSize(QtCore.QSize(50, 50))
content3 = QtWidgets.QWidget()
content3.setStyleSheet("background-color:red;")
content3.setMinimumSize(QtCore.QSize(50, 50))
dock1.setWidget(content1)
dock2.setWidget(content2)
dock3.setWidget(content3)
dock1.setAllowedAreas(Qt.AllDockWidgetAreas)
dock2.setAllowedAreas(Qt.AllDockWidgetAreas)
dock3.setAllowedAreas(Qt.AllDockWidgetAreas)
main.addDockWidget(Qt.LeftDockWidgetArea, dock1)
main.tabifyDockWidget(dock1, dock2)
main.addDockWidget(Qt.RightDockWidgetArea, dock3)
main.setDockOptions(main.GroupedDragging | main.AllowTabbedDocks | main.AllowNestedDocks)
main.setTabPosition(Qt.AllDockWidgetAreas, QtWidgets.QTabWidget.North)
main.resize(400, 200)
main.show()
app.exec_()
Posting this as a little extra to the accepted answer.
This version works with Qt5 and Qt6 and is able to detect corner cases like pulling out a tab and dropping it in the same group or merging a floating tab group with a docked window:
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import Qt
from typing import TypeVar, List, Optional
TDockWidget = TypeVar('TDockWidget', bound='DockWidget')
class DockWidget(QtWidgets.QDockWidget):
def __init__(self: TDockWidget, title: str, parent: Optional[QtWidgets.QWidget] = None) -> None:
super(_DockWidget, self).__init__(title, parent)
self.setTitleBarWidget(QtWidgets.QWidget())
self.visibilityChanged.connect(self.on_visibility_changed)
self.dockLocationChanged.connect(self.on_dock_location_changed)
#QtCore.Slot(bool)
def on_visibility_changed(self: TDockWidget, is_visible: bool) -> None:
# this visibility monitor is really only needed to detect merges of
# tabbed, floating windows with existing docked windows
if not is_visible and isinstance(self.parent(), QtWidgets.QMainWindow):
main_window: QtWidgets.QMainWindow = self.parent()
all_dockwidgets: List[QtWidgets.QDockWidget] = main_window.findChildren(QtWidgets.QDockWidget)
for dockwidget in all_dockwidgets:
if hasattr(dockwidget, 'on_dock_location_changed'):
dockwidget.on_dock_location_changed(main_window.dockWidgetArea(dockwidget), False)
#QtCore.Slot(Qt.DockWidgetArea)
def on_dock_location_changed(self: TDockWidget, area: Qt.DockWidgetArea, update_others: bool = True) -> None:
if not isinstance(self.parent(), QtWidgets.QMainWindow):
# mysterious parents call for a title
self.setTitleBarWidget(None)
return
main_window: QtWidgets.QMainWindow = self.parent()
if not main_window.tabifiedDockWidgets(self):
# if there's no siblings we ain't a tab!
self.setTitleBarWidget(None)
if not update_others:
# prevent infinite recursion
return
# force an update to all other docks that may now no longer be tabs
all_dockwidgets: List[QtWidgets.QDockWidget] = main_window.findChildren(QtWidgets.QDockWidget)
for dockwidget in all_dockwidgets:
if dockwidget != self and hasattr(dockwidget, 'on_dock_location_changed'):
dockwidget.on_dock_location_changed(main_window.dockWidgetArea(dockwidget), False)
return
# at this point the dockwidget is either a resting tab or a tab
# that is being dragged and hasn't been dropped yet (siblings are updated post-drop)
# collect all siblings of this dockwidget...
tab_siblings: List[QtWidgets.QDockWidget] = main_window.tabifiedDockWidgets(self)
# and filter for non-floating siblings in the same area
tab_siblings = [x for x in tab_siblings if main_window.dockWidgetArea(x) == area and not x.isFloating()]
if tab_siblings:
if self.titleBarWidget() is not None:
# no changes needed, prevent infinite recursion
return
# show a title if we're not floating (this tab is settled),
# hide it otherwise (this tab just became floating but wasn't dropped)
self.setTitleBarWidget(QtWidgets.QWidget() if not self.isFloating() else None)
# in this case it's also a good idea to tell to reconsider their situation
# since Qt won't notify them separately
for sibling in tab_siblings:
if hasattr(sibling, 'on_dock_location_changed'):
sibling.on_dock_location_changed(main_window.dockWidgetArea(sibling), True)
else:
self.setTitleBarWidget(None)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
main = QtWidgets.QMainWindow()
dock1 = DockWidget("Blue")
dock2 = DockWidget("Green")
dock3 = DockWidget("Red")
content1 = QtWidgets.QWidget()
content1.setStyleSheet("background-color:blue;")
content1.setMinimumSize(QtCore.QSize(50, 50))
content2 = QtWidgets.QWidget()
content2.setStyleSheet("background-color:green;")
content2.setMinimumSize(QtCore.QSize(50, 50))
content3 = QtWidgets.QWidget()
content3.setStyleSheet("background-color:red;")
content3.setMinimumSize(QtCore.QSize(50, 50))
dock1.setWidget(content1)
dock2.setWidget(content2)
dock3.setWidget(content3)
dock1.setAllowedAreas(Qt.AllDockWidgetAreas)
dock2.setAllowedAreas(Qt.AllDockWidgetAreas)
dock3.setAllowedAreas(Qt.AllDockWidgetAreas)
main.addDockWidget(Qt.LeftDockWidgetArea, dock1)
main.tabifyDockWidget(dock1, dock2)
main.addDockWidget(Qt.RightDockWidgetArea, dock3)
main.setDockOptions(main.GroupedDragging | main.AllowTabbedDocks | main.AllowNestedDocks)
main.setTabPosition(Qt.AllDockWidgetAreas, QtWidgets.QTabWidget.North)
main.resize(400, 200)
main.show()
app.exec_()

How to reset an UI in PyQt5

I'm building a layout in pyqt5 where you can dynamically add or delete widgets. Below you can see a simplified version of my problem: after the successful deleting of the nested layout "nested_hbox" and its widgets it won't build up again and just shows an empty window.
Edit: In my real application I have a grid layout where you can dynamically add diagrams in shape of a matrix to view multiple incoming values from an interface. For that you can quantify the number of rows and columns. So, the grid must be refreshed, when the user actuates a button.
import sys
from PyQt5.QtWidgets import *
class Window(QWidget):
def __init__(self):
super().__init__()
self.layouts = []
self.main_content_ui()
self.init_ui()
def main_content_ui(self):
"""Build up content for the main layout "vbox" """
self.lbl_hello = QLabel()
self.lbl_hello.setObjectName("lbl_hello")
self.lbl_hello.setText("Hello StackOverflow")
self.btn_reset_ui = QPushButton()
self.btn_reset_ui = QPushButton()
self.btn_reset_ui.setObjectName("btn_reset_ui")
self.btn_reset_ui.clicked.connect(self.reset_ui)
self.btn_reset_ui.setText("Reset UI")
self.nested_hbox = QHBoxLayout()
self.nested_hbox.setObjectName("nested_hbox")
self.nested_hbox.addWidget(self.lbl_hello)
self.nested_hbox.addWidget(self.btn_reset_ui)
self.layouts.append(self.layouts)
# main layout
self.vbox = QVBoxLayout()
self.vbox.setObjectName("vbox_main")
self.layouts.append(self.vbox)
self.vbox.addLayout(self.nested_hbox)
def init_ui(self):
"""Set "vbox" as main layout
"""
self.setLayout(self.vbox)
self.show()
def delete_layout(self, layout):
"""Delete all layouts from list "layouts"
"""
try:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.delete_layout(item.layout())
except Exception as e:
print(e)
def reset_ui(self):
"""Clear and reinitalize main layouts content"""
for lay in self.layouts:
self.delete_layout(lay)
print("Layout deleted rebuild layout")
self.main_content_ui()
self.vbox.update()
QMainWindow.update(self)
app = QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
I expect the same Window as before clicking the reset-button but the window doesn't show anything.
There's a nice way presented by Qt Wiki on how to make your application restartable https://wiki.qt.io
EXIT_CODE_REBOOT = -11231351
def main():
exitCode = 0
while True:
try: app = QApplication(sys.argv)
except RuntimeError: app = QApplication.instance()
window = MainUI()
window.show()
exitCode = app.exec_()
if exitCode != EXIT_CODE_REBOOT: break
return exitCode

How can I add a submit, output and input box using PyQt4?

I never tried creating a GUI with a language other than Java(kinda left it aside not long ago)
and started using Python.
made a simple program that calculates Pi to a certain digit as the user wishes.
Now, I created a window with PyQt4, made a button and got everything in place.
How can I add a input box so that the user could enter a number into it, make the button "Enter" the information and at the end of all that output it to the window instead of the terminal?
That's what I've got for now:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from decimal import *
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 800, 600)
self.setWindowTitle("Pi's Nth Digit")
self.setWindowIcon(QtGui.QIcon('icon.jpg'))
self.buttons()
def buttons(self):
btn = QtGui.QPushButton("Quit",self)
btn1 = QtGui.QPushButton("Get Pi",self)
btn.clicked.connect(QtCore.QCoreApplication.instance().quit)
btn1.clicked.connect(self.getpi())
btn1.resize(btn1.sizeHint())
btn.resize(btn.sizeHint())
btn1.move(350,500)
btn.move(450,500)
self.show()
def start():
app = QtGui.QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
start()
don't mind the getpi function.
Thanks! :)
You would want to use a QLineEdit or a QSpinBox for a number. If you want multiple things in a widget you would use a layout. A QMainWindow typically has one central widget and toolbars and dock widgets.
class Window(QtGui.QMainWindow):
def __init__(self):
super().__init__()
self.container = QtGui.QWidget()
self.setCentralWidget(self.container)
self.container_lay = QtGui.QVBoxLayout()
self.container.setLayout(self.container_lay)
# Input
self.le = QtGui.QLineEdit()
self.container_lay.addWidget(self.le)
# enter button
self.enter_btn = QtGui.QPushButton("Enter")
self.container_lay.addWidget(self.enter_btn)
self.enter_btn.clicked.connect(self.run) # No '()' on run you want to reference the method.
# display
self.container_lay.addWidget(QtGui.QLabel("Answer:"))
self.ans = QtGui.QLabel()
self.container_lay.addWidget(self.ans)
def run(self):
precision = self.le.text()
pi = str(round(math.pi, precision)) # probably different formatting
self.ans.setText(pi)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
You have almost everything, just add a QLineEdit to get the input and a QLabel where to show the result (with QLabel.setText).

Why won't QToolTips appear on QActions within a QMenu

I'm doing an app with in GUI written with PySide. I set a QMenu on a QPushButton, added several QActions via QMenu.addAction. To further explain these actions to the user I added QToolTip's to these with QAction.setToolTip.
When I run the GUI now my QToolTip won't show. The example posted below reproduces the same issue, any ideas?
Thanks in advance
import sys
from PySide import QtGui
class Example(QtGui.QPushButton):
def __init__(self, parent = None):
super(Example, self).__init__(parent)
self.setText('TestMenu')
self.setToolTip('This is a Test Button')
menu = QtGui.QMenu(self)
action_1 = menu.addAction('Action1')
action_1.setToolTip('This is action 1')
action_2 = menu.addAction('Action2')
action_2.setToolTip('This is action 2')
action_3 = menu.addAction('Action3')
action_3.setToolTip('This is action 3')
action_4 = menu.addAction('Action4')
action_4.setToolTip('This is action 4')
self.setMenu(menu)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
app.exec_()
if __name__ == '__main__':
main()
In Qt-5.1 or later, you can simply use QMenu.setToolTipsVisible, and the menu items will show their tooltips as expected (see QTBUG-13663):
menu.setToolTipsVisible(True)
However, for Qt-4.* and Qt-5.0, the situation is different. If an action is added to a toolbar, its tooltip will be shown; but if the same action is added to a QMenu, it won't be, and there is no built-in API to change that. There are a couple of ways to work around this. One is to use status tips instead, which will show the menu-item information in the status-bar. The other is to implement the menu-item tooltip functionality yourself using the QMenu.hovered signal and QToolTip.showText:
self.menu = QtGui.QMenu(self)
...
self.menu.hovered.connect(self.handleMenuHovered)
def handleMenuHovered(self, action):
QtGui.QToolTip.showText(
QtGui.QCursor.pos(), action.toolTip(),
self.menu, self.menu.actionGeometry(action))
Actually you don't have to do any workaround to display your tooltip, since Qt 5.1, you can use QMenu's property toolTipsVisible, which is by default set to false.
See the related Qt suggestion.
With ekhumoro helping me on the way got to this solution. It's probably not the most beautiful thing, and the code below positions the menu and the tool tips somewhat arkward, but in my actual programm it looks quite neat.
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QPushButton):
def __init__(self, parent = None):
super(Example, self).__init__(parent)
self.setText('TestMenu')
self.setToolTip('This is a Test Button')
menu = QtGui.QMenu(self)
action_1 = menu.addAction('Action1')
action_1.setToolTip('This is action 1')
action_2 = menu.addAction('Action2')
action_2.setToolTip('This is action 2')
action_3 = menu.addAction('Action3')
action_3.setToolTip('This is action 3')
action_4 = menu.addAction('Action4')
action_4.setToolTip('This is action 4')
action_1.hovered.connect(lambda pos = [self], parent = action_1, index = 0: show_toolTip(pos, parent, index))
action_2.hovered.connect(lambda pos = [self], parent = action_2, index = 1: show_toolTip(pos, parent, index))
action_3.hovered.connect(lambda pos = [self], parent = action_3, index = 2: show_toolTip(pos, parent, index))
action_4.hovered.connect(lambda pos = [self], parent = action_4, index = 3: show_toolTip(pos, parent, index))
self.setMenu(menu)
self.show()
def show_toolTip(pos, parent, index):
'''
**Parameters**
pos: list
list of all parent widget up to the upmost
parent: PySide.QtGui.QAction
the parent QAction
index: int
place within the QMenu, beginning with zero
'''
position_x = 0
position_y = 0
for widget in pos:
position_x += widget.pos().x()
position_y += widget.pos().y()
point = QtCore.QPoint()
point.setX(position_x)
point.setY(position_y + index * 22) # set y Position of QToolTip
QtGui.QToolTip.showText(point, parent.toolTip())
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
app.exec_()
if __name__ == '__main__':
main()
I have to say I'm not perfectly happy with this, mainly because the show_toolTip function has to be global, for the lambda operator didn't recognize it when I had it in the class (self.show_toolTip). I'm still open for suggesetions if someone has a suggestion.
Instead of showing tooltip immediately one can just update the tooltip of the parent (menu) when hovering and wait for the tooltip to be shown! Therefore:
menu = QtGui.QMenu(self)
action_1 = menu.addAction('Action1')
action_1.setToolTip('This is action 1')
...
menu.hovered.connect(self.handleMenuHovered)
def handleMenuHovered(self, action):
action.parent().setToolTip(action.toolTip())

Categories