PyQt4: Trouble making a custom dock TitleBarWidget - python

I was hoping someone could help me with creating a custom title bar widget for a dock widget in a PyQt4 GUI program. All I want to do is emulate the exact same look and function of the default title bar, but with an extra, custom button. I couldn't find an easy way to do this as I don't know if there's a default title bar widget I can add stuff to, so I made a custom dock title bar widget:
from PyQt4 import QtGui, QtCore
class DockTitleBar(QtGui.QFrame):
def __init__(self, parent):
super(DockTitleBar, self).__init__(parent)
# Is this the only way to give the title bar a border?
self.setFrameStyle(QtGui.QFrame.Raised | QtGui.QFrame.StyledPanel)
# Layout for title box
layout = QtGui.QHBoxLayout(self)
layout.setSpacing(1)
layout.setMargin(1)
self.label = QtGui.QLabel(parent.windowTitle())
icon_size = QtGui.QApplication.style().standardIcon(
QtGui.QStyle.SP_TitleBarNormalButton).actualSize(
QtCore.QSize(100, 100))
button_size = icon_size + QtCore.QSize(5, 5)
# Custom button I want to add
self.button = QtGui.QToolButton(self)
self.button.setAutoRaise(True)
self.button.setMaximumSize(button_size)
self.button.setIcon(QtGui.QApplication.style().standardIcon(
QtGui.QStyle.SP_TitleBarContextHelpButton))
self.button.clicked.connect(self.do_something)
# Close dock button
self.close_button = QtGui.QToolButton(self)
self.close_button.setAutoRaise(True)
self.close_button.setMaximumSize(button_size)
self.close_button.setIcon(QtGui.QApplication.style().standardIcon(
QtGui.QStyle.SP_DockWidgetCloseButton))
self.close_button.clicked.connect(self.close_parent)
# Setup layout
layout.addWidget(self.label)
layout.addStretch()
layout.addWidget(self.button)
layout.addWidget(self.close_button)
def do_something(self):
# Do something when custom button is pressed
pass
def close_parent(self):
self.parent().hide()
It seems to work okay, except for when the dock is dragged around in its floating state. Normally there are borders and even the title bar is highlighted, but with my janky version there is no frame for the floating dock so it's hard to tell where it is, and the title bar isn't highlighted. Is there something I can fix/add or should I be doing this in an entirely different way?

Related

QVideoWidget and QStackedWidget creating problem in PySide6

I am trying to build a GUI using PySide6 on Windows 10 - Python V 3.10.2.
Basically, I wanted to create a vertical box on left with buttons to switch between the stacked widgets. And a video widget with play button & scroll bar on the right side with a stacked widget underneath the video widget. On the stacked widget, I intend to put some buttons which will link files to play on the video widget.
The Problem: The video widget does works perfectly when commenting out the stacked widget. However, if I add the stacked widget, sound does play nicely but the videowidget can't be seen.
See the code snippet below.
class mainwindow(QMainWindow):
def __init__(self):
super().__init__()
self.audio = QAudioOutput()
self.video = QVideoWidget()
self.player = QMediaPlayer()
self.player.setAudioOutput(self.audio)
self.player.setVideoOutput(self.video)
side_layout = QVBoxLayout()
side_layout.addWidget(QLabel('First Label'))
side_layout.addWidget(QPushButton('First Button'))
# I have not named buttons for now for conciseness.
vid_control_layout = QHBoxLayout()
play_btn = QPushButton('Play')
play_btn.clicked.connect(self.play_video)
vid_control_layout.addWidget(play_btn)
vid_control_layout.addWidget(QSlider(orientation = Qt.Horizontal))
stack_widget = QStackedWidget()
stack_widget.addWidget(QWidget())
stack_widget.addWidget(QWidget())
video_layout = QVBoxLayout()
video_layout.addWidget(self.video)
video_layout.addLayout(vid_control_layout)
video_layout.addWidget(stack_widget)
mainlayout = QHBoxLayout()
mainlayout.addLayout(side_layout)
mainlayout.addLayout(video_layout)
mainwidget = QWidget()
mainwidget.setLayout(mainlayout)
self.setCentralWidget(mainwidget)
def play_video(self):
self.player.setSource('111.mp4')
self.player.play()
app = QApplication(sys.argv)
win = mainwindow()
win.show()
sys.exit(app.exec())
I am a self learner of Python and started learning PyQt5 quite recently. As there was too many version problem in PyQt5 and QMediaPlayer, I switched to PySide6.
Let me know if this detail will help in fixing the problem.

QTooltip with absolute position that still moves relative to parent

I'm building a custom combobox, from a (subclassed) QLineEdit and QListWidget for the dropdown menu
I'm setting the window flags to QTool so that its a floating window but doesnt steal focus from the lineedit (since the user needs to be able to input text to filter the list). This works fine but the list is now completely detached from the parent widget, so I can drag the top menu bar and move it away from the list which I don't want.
Is there a way to use QTool or QTooltip but keep it parented to a widget?
One other method would be setting the window flags to QPopup, in which case the popup closes when the top menu bar is clicked so cannot be dragged away. However with QPopup it steals focus from the line edit
Below is a simple example illustrating the issue:
from PySide2 import QtCore, QtWidgets, QtGui
import sys
class LineEditClickable(QtWidgets.QLineEdit):
"""Custom QLineEdit to detect clicked, focus and key events
Signals: clicked, focusOut, arrowUp, arrowDown
"""
clicked = QtCore.Signal(QtGui.QMouseEvent)
def __init__(self, value=''):
super(LineEditClickable, self).__init__(value)
# remove border on Mac
self.setAttribute(QtCore.Qt.WA_MacShowFocusRect, 0)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
def mousePressEvent(self, event):
"""Emit clicked signal"""
self.clicked.emit(event)
super(LineEditClickable, self).mousePressEvent(event)
class popup(QtWidgets.QWidget):
def __init__(self, parent = None, widget=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout(self)
self.list = QtWidgets.QListWidget()
layout.addWidget(self.list)
# adjust the margins or you will get an invisible, unintended border
layout.setContentsMargins(0, 0, 0, 0)
self.adjustSize()
# tag this widget as a popup
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool)
# self.setWindowFlags(QtCore.Qt.Popup)
def update(self, widget):
# calculate the botoom right point from the parents rectangle
point = widget.rect().bottomRight()
# map that point as a global position
global_point = widget.mapToGlobal(point)
# by default, a widget will be placed from its top-left corner, so
# we need to move it to the left based on the widgets width
self.move(global_point - QtCore.QPoint(self.width(), 0))
def show_popup(self, widget):
self.update(widget)
self.show()
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.le = LineEditClickable(self)
self.le.clicked.connect(self.handleOpenDialog)
self.le.move(250, 50)
self.resize(600, 200)
self.popup = popup(self, self.le)
self.popup.list.addItems(['one','two','three'])
def handleOpenDialog(self):
self.popup.show_popup(self.le)
self.popup.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())```
The basic answer to your question is to use the correct flags and focus options.
If you look at how QCompleter implements setPopup(), you'll see the following:
popup->setWindowFlag(Qt::Popup);
popup->setFocusPolicy(Qt::NoFocus);
[...]
popup->setFocusProxy(d->widget);
As you've already experienced, Tool is not a good option: while avoids stealing focus from the line edit, it also has issues with any mouse click that happens outside the UI.
If you still want to use Tool, you could update the widget position, by installing an event filter on the top level window of the line edit and intercept its move events, but it's not guaranteed that it works and totally depends on the platform you're using it. For example, on certain Linux window managers you only receive it when the mouse is released after dragging the window.
class popup(QtWidgets.QWidget):
_widget = None
_window = None
# ...
def show_popup(self, widget):
if self._window:
self._window.removeEventFilter(self)
self.update(widget)
self.show()
self._widget = widget
self._window = widget.window()
self._window.installEventFilter(self)
def hideEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def closeEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Move:
self.update(self._widget)
return super().eventFilter(source, event)
Frankly, I'd suggest you to use what Qt already provides you without trying to reinvent the wheel. In your case, use a QCompleter and reimplement what you need for it's popup.
Note that if you want to show all items when the line edit gets focus and there's no text yet, you could change the completion mode.
class LineEdit(QtWidgets.QLineEdit):
def __init__(self, *args, **kwargs):
# ...
self.textChanged.connect(self.showCompleter)
def showCompleter(self):
completer = self.completer()
if not completer:
return
if not self.text():
completer.setCompletionMode(completer.UnfilteredPopupCompletion)
else:
completer.setCompletionMode(completer.PopupCompletion)
completer.complete()
You might want to do the same also in the keyPressEvent override, after calling the base class implementation and ensuring that the popup is not yet visible.

cornerWidget disappears when there isn't any tab

I'm working on a window which has an empty QTabWidget when created. The user can add some tabs with a QPushButton set as cornerWidget.
My problem is that the cornerWidget works fine when there are tabs, but disappears when there isn't any tab. See code below:
class myWindow (QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
tabW = QtGui.QTabWidget()
self.layout().addWidget(tabW)
#tabW.addTab(QtGui.QWidget(), 'tab1')
tabW.setCornerWidget(QtGui.QPushButton())
self.show()
myWIndow01 = myWindow()
It's the correct behaviour of QTabWidget: when it's empty, it displays nothing.
A workaround is to set the minimum size of the button, like this:
self.button=QtGui.QPushButton("my button")
tabWidget.setCornerWidget(self.button)
tabWidget.cornerWidget().setMinimumSize(self.button.sizeHint())

Button not displayed in the right place

I am using PyQt to create a desktop application. I am trying to create a button using hbox and vbox, but it doesn't display unless I give the specific command:
button1 = QtGui.QPushButton("Exit", self)
But, by doing this, the vbox and hbox functionality doesn't seem to function.
I need the button to be on the bottom-right corner of the window, which stays there even after window-resize.
With this code, it is positioned on the top-left corner.
from PyQt4 import QtGui, QtCore
import sys
class Trial(QtGui.QMainWindow):
def __init__(self):
super(Trial,self).__init__()
self.createUI()
def createUI(self):
button1 = QtGui.QPushButton("Exit",self)
button1.clicked.connect(self.close)
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1) #stretches it to the right end of the page
hbox.addWidget(button1)
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1) #stretches it to the bottom end of the page
vbox.addLayout(hbox)
self.setLayout(vbox)
button1.resize(button1.sizeHint())
self.setGeometry(300,200,750,450)
self.setWindowTitle('Testing')
self.show()
def main():
app= QtGui.QApplication(sys.argv)
w=Trial()
sys.exit(app.exec_())
if __name__=='__main__':
main()
If I use button1.move(420, 400), it moves the button to the position I want, but it doesn't stay there when I re-size the application window.
The example code doesn't work because you are trying to set a layout on the main window, which already has a layout.
Instead, you need to add a central widget, and then set the layout on that:
def createUI(self):
self.setCentralWidget(QtGui.QWidget(self))
...
vbox.addLayout(hbox)
self.centralWidget().setLayout(vbox)
self.setGeometry(300,200,750,450)
...

PyQt4: QLabel with clear button

First I'll show the code.
class XLineEdit(QtGui.QLineEdit):
'''QLineEdit with clear button, which appears when user enters text.'''
def __init__(self, pixmap, parent=None):
QtGui.QLineEdit.__init__(self, parent)
self.layout = QtGui.QHBoxLayout(self)
self.image = QtGui.QLabel(self)
self.image.setCursor(QtCore.Qt.ArrowCursor)
self.image.setFocusPolicy(QtCore.Qt.NoFocus)
self.image.setStyleSheet("border: none;")
self.image.setPixmap(pixmap)
self.image.setSizePolicy(
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.image.adjustSize()
self.image.setScaledContents(True)
self.layout.addWidget(
self.image, alignment=QtCore.Qt.AlignRight)
self.textChanged.connect(self.changed)
self.image.hide()
def changed(self, text):
if len(text) > 0:
self.image.show()
else: # if entry is empty
self.image.hide()
That creates QLineEdit object with custom button from QLabel at the right side of QLineEdit. I have only two problems:
If I change the font of XLineEdit ("XLineEdit object".setFont(QFont)), image button will look good by vertical, but will look ugly by horizontal. It seems that vertical size changes on changing the size of QLineEdit's font, but horizontal size is not. How can I fix this? Is there any other way to create QLineEdit with clear button? I've tried to create QPushButton with custom QIcon, but icon doesn't change it's size at all (neither vertical, nor horizontal).
How can I create a new signal when user clicks on QLabel? It seems that there is no analog for QPushButton's 'clicked'.
Thanks!
While #reclosedev already commented on your question with a link to a C++ example for the clear button aspect, I wanted to add information about your second question...
You can create a clickable QLabel by overloading the MousePressEvent and emitting your own custom signal.
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtGui import QLabel, QStyle
class ClickLabel(QLabel):
clicked = pyqtSignal()
def __init__(self, *args, **kwargs)
super(ClickLabel, self).__init__(*args, **kwargs)
def mousePressEvent(self, event):
event.accept()
self.clicked.emit()
A comment about the C++ link that was provided in the other comment. Instead of using an HBoxLayout, they are just directly parenting the button to the QLabel widget, and using the resizeEvent to always move it to the right side of the QLabel.

Categories