How to keep the shortcuts of a hidden widget in PyQt5? - qt

I have a menu bar with a shortcut associated to it. I want to hide the menu bar but in that case the associated shortcut will be disabled. Here is an example:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QAction
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.InitWindow()
def InitWindow(self):
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu("&File")
mainMenu.hide() # comment it and the shortcut 'q' will work
quitItem = QAction("Quit", self)
quitItem.setShortcut("Q")
quitItem.triggered.connect(self.close)
fileMenu.addAction(quitItem)
if __name__ == "__main__":
App = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec())
If you put the line mainMenu.hide() in a comment, i.e. if the menu bar is shown, then the app. will quit with the shortcut 'q'. How could I keep the shortcuts of a hidden widget?
In the app. I want to add full-screen support, and in that case I want to hide the menu bar, but also, I want to keep the shortcuts in full-screen mode.

I found a working solution. The idea is the following: the main window has a shortcut ('q' in the example), and the menu bar also has this shortcut. To avoid conflict, disable the window's shortcut if the menu bar is present. If the menu bar is hidden, enable the window's shortcut.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QShortcut
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.shortcutQuit = QShortcut(QKeySequence("q"), self)
self.shortcutQuit.activated.connect(self.close)
self.shortcutQuit.setEnabled(False) # disable it if the menu bar is visible
self.InitWindow()
def InitWindow(self):
self.mainMenu = self.menuBar()
fileMenu = self.mainMenu.addMenu("&File")
hideItem = QAction("Hide Menu Bar", self)
hideItem.setShortcut("h")
hideItem.triggered.connect(self.my_hide)
quitItem = QAction("Quit", self)
quitItem.setShortcut("Q")
quitItem.triggered.connect(self.close)
fileMenu.addAction(hideItem)
fileMenu.addAction(quitItem)
def my_hide(self):
self.mainMenu.hide()
self.shortcutQuit.setEnabled(True)
if __name__ == "__main__":
App = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec())

Related

Custom title bar with dockable toolbar

I want to create a custom title bar for my PyQt application, and my application uses dockable toolbars. To be dockable, the toolbars should be added to the MainWindow. However, with my custom toolbar being a Widget added to a frameless window, the toolbars dock themselves around the title bar. They can be docked above the title bar, and whenever docked on the sides, they push the title bar, which is not the expected behavior. I understand that this is due to the fact the the toolbar areas are always around the central widget of the window, and my custom title bar is inside the central widget. However, I don't see how I can make this work the way I want. Here is a MWE (I'm using PyQt=5.12.3) :
import sys
from typing import Optional
from PyQt5.QtCore import Qt, QSize, QPoint
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QMainWindow, QApplication, QVBoxLayout, QPushButton, QWidget, QToolBar, QHBoxLayout, QLabel, \
QToolButton
class CustomTitleBar(QWidget):
def __init__(self, title: str, parent: Optional[QWidget] = None):
super().__init__(parent=parent)
self.window_parent = parent
layout = QHBoxLayout()
self.setObjectName("CustomTitleBar")
self.setLayout(layout)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.setFixedHeight(40)
self.title = title
self.title_label = QLabel(self.title)
self.title_label.setObjectName("TitleBarLabel")
layout.addWidget(self.title_label)
layout.addStretch(1)
but_minimize = QToolButton()
but_minimize.setText("🗕")
but_minimize.setObjectName("MinimizeButton")
layout.addWidget(but_minimize)
but_minimize.clicked.connect(self.window().showMinimized)
self.but_resize = QToolButton()
if self.window().isMaximized():
self.but_resize.setText("🗗")
else:
self.but_resize.setText("🗖")
layout.addWidget(self.but_resize)
self.but_resize.clicked.connect(self.toggle_maximized)
self.but_resize.setObjectName("ResizeButton")
but_close = QToolButton()
but_close.setText("🗙")
layout.addWidget(but_close)
but_close.clicked.connect(self.window().close)
but_close.setObjectName("CloseButton")
self.m_pCursor = QPoint(0, 0)
self.moving = False
def toggle_maximized(self):
if self.window().isMaximized():
self.but_resize.setText("🗖")
self.window().showNormal()
else:
self.but_resize.setText("🗗")
self.window().showMaximized()
def mousePressEvent(self, event: QMouseEvent) -> None:
pass
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
pass
def mouseMoveEvent(self, event: QMouseEvent) -> None:
pass
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
pass
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)
self.resize(QSize(800, 600))
main_widget = QWidget(self)
self.setCentralWidget(main_widget)
layout = QVBoxLayout(self)
main_widget.setLayout(layout)
titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
layout.addWidget(titlebar)
layout.addWidget(QPushButton("Hello world"))
layout.addStretch(1)
my_toolbar = QToolBar(self)
self.addToolBar(Qt.RightToolBarArea, my_toolbar)
my_toolbar.addWidget(QPushButton("A"))
my_toolbar.addWidget(QPushButton("B"))
my_toolbar.addWidget(QPushButton("C"))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
The resulting window is as follow:
How can I get the dockable toolbars to behave around my custom title bar the way they should behave around a standard title bar ?
Since the title bar should be put outside the standard contents, you cannot put it inside the central widget.
The solution is to setContentsMargins() using the height of the title bar for the top margin. Then, since the title bar is not managed by any layout, you need to resize it by overriding the resizeEvent():
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
self.setContentsMargins(0, self.titlebar.sizeHint().height(), 0, 0)
def resizeEvent(self, event):
super().resizeEvent(event)
self.titlebar.resize(self.width(), self.titlebar.sizeHint().height())
As I guess, firstly you need to look QDockWidget Because you put your CustomTitleBar into centeralWidget in MainWindow and it states in Docking area, this is expected behaviour.
You need to create a Vertical Layout which would your , and put CustomTitleBar into it, you don't need MainWindow in your code. In main you can try something like that:
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
layout = QHBoxLayout()
titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
layout.addWidget(titlebar)
window.setLayout(layout)
main_widget = QWidget(self)
layout.addWidget(main_widget)
main_widget_layout = QVBoxLayout()
main_widget.setLayout(main_widget_layout)
my_toolbar = QToolBar(self)
main_widget.addToolBar(Qt.RightToolBarArea, my_toolbar)
my_toolbar.addWidget(QPushButton("A"))
my_toolbar.addWidget(QPushButton("B"))
my_toolbar.addWidget(QPushButton("C"))
main_widget.addWidget(QPushButton("Hello world"))
main_widget_layout.addStretch(1)
layout.addStretch(1)
window.show()
app.exec_()

How to disable and re-enable QPushButton

I made MainWindow and Dialog with Qt-designer.The MainWindow and Dialog have one QPushButton. Clicking a button in the MainWindow disables the button and opens a Dialog Window. When you click the Dialog button, the Dialog window closes and the MainWindow's button is activated again.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5 import uic
form_mainwindow = uic.loadUiType("dialog_mainWindow.ui")[0]
form_dialog = uic.loadUiType("Dialog__.ui")[0]
class dialog(QDialog, form_dialog) :
def __init__(self):
super(dialog, self).__init__()
self.setupUi(self)
self.closeBtn.clicked.connect(self.close)
self.closeBtn.clicked.connect(self.closeFN)
def closeFN(self):
main = mainwindow()
main.pushButton.setEnabled(True)
class mainwindow(QtWidgets.QMainWindow, form_mainwindow) :
def __init__(self):
super(mainwindow, self).__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.dia)
def dia(self,checked):
d = dialog()
self.pushButton.setEnabled(False)
d.show()
d.exec_()
if __name__ == "__main__" :
app = QtWidgets.QApplication(sys.argv)
Window = mainwindow()
Window.show()
sys.exit(app.exec_())
This is my code. However, my code is that when the Dialog window is closed, the button in the MainWindow is not activated again. Why??
You are calling mainwindow() in your dialogs closeFN method, which creates a new mainwindow widget. So the button that you are setting to be enabled is not the same one that you used to create the dialog in the first place. You are setting a button on a new window that isn't visible yet because you never call .show() or similar method that makes top level windows visible, and you also are not keeping a reference to the new window so it would be garbage collected anyway.
A solution to this is to have your mainwindow connect to the dialogs closebtn.clicked signal which could then trigger it's own method that set's the pushbutton to be enabled.
For example:
...
class dialog(QDialog) :
def __init__(self):
super(dialog, self).__init__()
...
# self.closeFN is no longer necessary
self.closeBtn.clicked.connect(self.close)
class mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super(mainwindow, self).__init__()
...
self.pushButton.clicked.connect(self.dia)
def dia(self):
d = dialog()
self.pushButton.setEnabled(False)
# connect to closeBtn signal to enable the pushButton
d.closeBtn.clicked.connect(lambda: self.pushButton.setEnabled(True))
d.exec_()
...

How to define the icon of a Dock in pyqtgraph?

In pyqtgraph, Docks can be torn out of the DockArea by dragging or double clicking. The popups use a default icon. I would like to define my own icon. In the code below I set the application window. The same code has no effect on the dock, though there is no error message.
import sys
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout
from pyqtgraph.dockarea import Dock, DockArea
from PyQt5.QtGui import QIcon
class Foo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowIcon(QIcon('direction'))
lay = QVBoxLayout(self)
da = DockArea()
d = Dock("Dock")
d.setWindowIcon(QIcon('direction')) # no effect
da.addDock(d)
lay.addWidget(da)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
I looked in the source code of Dock and DockArea of pyqtgraph and found out I had to overwrite floatDock function.
I created a function
def floatDockPatched(self, dock):
"""Removes *dock* from this DockArea and places it in a new window."""
area = self.addTempArea()
area.win.resize(dock.size())
area.win.setWindowIcon(QIcon("res/haip.png"))
area.win.setWindowTitle(dock.label.text())
area.moveDock(dock, 'top', None)
and assigned it to the class as overwrite
DockArea.floatDock = floatDockPatched

Change highlight color for a specific item (QAction type) on a QMenuBar

I want to mimic the behavior of the window top-right corner actions when hovering over the minimize-maximize-close buttons.
So I have created a QMenuBar that contains 3 QActions.
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
minimize_action.setIcon(_MINIMIZE_ICON)
maximize_action = QAction(self)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setIcon(_CLOSE_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
And set the background color when hovering with stylesheet (it is applied to the entire QApplication):
I want the close button to have a different highlight color, so I tried setting a boolean property on the exit_action to change the color only for that specific item, but it does not do any effect (in fact, if I add the property in the stylesheet it doesn't even consider the stylesheet values)
Any idea of how to achieve this?
To extend the question, I would not only use this for the minimize-maximize-close actions, but also to understand how to apply different hover/selected colors on QActions in the QMenuBar (not in the QMenu, which I already found a solution for that).
This is an running example:
from PySide2.QtWidgets import QMenuBar, QAction, QStyle, QApplication, QMainWindow
from PySide2.QtCore import Qt
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize_action.setIcon(_MINIMIZE_ICON)
minimize_action.setProperty('exit_action', False)
maximize_action = QAction(self)
maximize_action.setProperty('exit_action', False)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setProperty('exit_action', True)
exit_action.setIcon(_EXIT_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
self.setStyleSheet(
'QMenuBar::item:selected {'
'background-color: grey;'
'}'
'QMenuBar::item[exit_action=true]:selected {'
' background-color: red;'
'}')
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setMenuBar(WindowMenuActions())
main.show()
sys.exit(app.exec_())
To anyone interested, I sorted by inheriting from a regular QWidget instead of a QMenuBar and using QToolButton instead of QAction.
Working example:
from PySide2.QtWidgets import QMenuBar, QStyle, QApplication, QMainWindow, QWidget, QHBoxLayout, QToolButton, QMenu
from PySide2.QtCore import Qt
class MenuBar(QMenuBar):
def __init__(self):
super(MenuBar, self).__init__()
self.addMenu(QMenu('File'))
self.addMenu(QMenu('Help'))
self.setCornerWidget(WindowMenuActions(self))
class WindowMenuActions(QWidget):
def __init__(self, parent=None):
super(WindowMenuActions, self).__init__(parent)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize = QToolButton(self)
minimize.setIcon(_MINIMIZE_ICON)
maximize = QToolButton(self)
maximize.setIcon(_MAXIMIZE_ICON)
exit_action = QToolButton(self)
exit_action.setProperty('exit_button', True)
exit_action.setIcon(_EXIT_ICON)
layout = QHBoxLayout()
layout.addWidget(minimize)
layout.addWidget(maximize)
layout.addWidget(exit_action)
self.setLayout(layout)
self.setStyleSheet(
'QToolButton:hover {'
' background: grey;'
'}'
'QToolButton[exit_button=true]:hover {'
' background: red;'
'}'
)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setWindowFlags(Qt.FramelessWindowHint)
main.setMenuBar(MenuBar())
main.show()
sys.exit(app.exec_())

How can I specify seperators using QDockWidget?

I have the following example code:
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent=parent)
self.bgcolor = self.palette().color(self.backgroundRole()).name()
self.central = QtWidgets.QTextEdit(self)
self.central.setText('this is the central widget')
self.setCentralWidget(self.central)
self.setDockOptions(self.AnimatedDocks) #prevent tabbing
self.rightDock = QtWidgets.QDockWidget('right dock', self)
self.rightDock.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)
self.rightDock.setStyleSheet('QDockWidget::title{text-align:left;background:'+self.bgcolor+';}')
self.everywhereDock = QtWidgets.QDockWidget('everywhere dock',self)
self.everywhereDock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea | QtCore.Qt.TopDockWidgetArea | QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea)
self.everywhereDock.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable | QtWidgets.QDockWidget.DockWidgetMovable)
self.everywhereDock.setStyleSheet('QDockWidget::title{text-align:left;background:'+self.bgcolor+';}')
self.dockable = QtWidgets.QTextEdit(self.rightDock)
self.dockable.setText('this is dockable only on the right')
self.dockable2 = QtWidgets.QTextEdit(self.everywhereDock)
self.dockable2.setText('this is dockable everywhere, also its not closable')
self.rightDock.setWidget(self.dockable)
self.everywhereDock.setWidget(self.dockable2)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.rightDock)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.everywhereDock)
self.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtWidgets.QTabWidget.North)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
m = MainWindow()
m.show()
sys.exit(app.exec_())
The question is how to insert icons, where the borders between DockWidgetAreas are draggable, so that the user has a hint, that there is this functionality.
To clarify:
I want an icon between the black arrows:
The QDockWidget supports a "title widget" which is not a separator, but you can add it into every QDockWidget using QDockWidget::setTitleBarWidget(QWidget *widget).
So you can create a generic QWidget to hold this icon using a QHorizontalLayout or something, and put it into the title bar. The default mouse events handled by Qt (such as the drag events) should continue to works normally and you have a customizable title bar.
self.rightDock = QtWidgets.QDockWidget('right dock', self)
self.rightDock.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)
// add custom title widget
self.rightDock.setTitleBarWidget(self.titleWidget)
// add widget to dock widget
self.rightDock.setWidget(self.dockable)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.rightDock)

Resources