Move icon to right side of text in a QCheckBox - python

I'm trying to move the icon of a QCheckBox from the left of the label to immediately on the right.
I've checked these posts:
PyQt4 QPushButton text and icon alignment
Align icon on the right and center the text in a QPushButton
But neither seem to work for a QCheckBox. Actually, the second solution is the best I have so far, but I would like the icon to be just to the right of the label, not aligned all the way to the right of the widget.
Here are my experiments:
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QStyle, QApplication, QLabel, QHBoxLayout
class CheckBoxWIcon(QCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
icon = self.icon()
icon_size = self.iconSize()
# remove icon
self.setIcon(QtGui.QIcon())
label_icon = QLabel()
label_icon.setAttribute(Qt.WA_TranslucentBackground)
label_icon.setAttribute(Qt.WA_TransparentForMouseEvents)
lay = QHBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(label_icon, alignment=Qt.AlignRight)
label_icon.setPixmap(icon.pixmap(icon_size))
app = QApplication([])
mw = QWidget()
layout = QVBoxLayout()
test1 = QCheckBox("Default")
test1.setIcon(app.style().standardIcon(QStyle.SP_MediaSkipForward))
test1.setStyleSheet("""QCheckBox { text-align: right; }""")
test2 = QCheckBox("Using style-sheet")
test2.setIcon(app.style().standardIcon(QStyle.SP_MediaSkipForward))
test2.setStyleSheet("""QCheckBox { text-align: left; }""")
test3 = QCheckBox("Using layout direction")
test3.setIcon(app.style().standardIcon(QStyle.SP_MediaSkipForward))
test3.setLayoutDirection(Qt.RightToLeft)
test4 = CheckBoxWIcon("Custom class", icon=QApplication.style().standardIcon(QStyle.SP_MediaSkipForward))
layout.addWidget(test1)
layout.addWidget(test2)
layout.addWidget(test3)
layout.addWidget(test4)
mw.setLayout(layout)
mw.show()
app.exec()
Desired output:

A possible solution is to use a QProxyStyle to modify the painting:
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QCheckBox, QProxyStyle, QStyle, QWidget
class IconProxyStyle(QProxyStyle):
def drawControl(self, element, option, painter, widget=None):
if element == QStyle.CE_CheckBoxLabel:
offset = 4
icon = QIcon(option.icon)
option.icon = QIcon()
super().drawControl(element, option, painter, widget)
alignment = self.visualAlignment(
option.direction, Qt.AlignLeft | Qt.AlignVCenter
)
if not self.proxy().styleHint(QStyle.SH_UnderlineShortcut, option, widget):
alignment |= Qt.TextHideMnemonic
r = painter.boundingRect(
option.rect, alignment | Qt.TextShowMnemonic, option.text
)
option.rect.setLeft(r.right() + offset)
option.text = ""
option.icon = icon
super().drawControl(element, option, painter, widget)
def main():
import sys
app = QApplication(sys.argv)
app.setStyle("fusion")
app.setStyle(IconProxyStyle(app.style()))
button = QCheckBox(
"Test\nwith\nQProxyStyle",
icon=QApplication.style().standardIcon(QStyle.SP_MediaSkipForward),
)
# button.setStyle(IconProxyStyle(button.style()))
button.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Related

Problem integrating QFontComboBox in Qmenu

I'm trying to integrate QFontComboBox within Qmenu.
I try to make two things happen when selecting a particular font from the menu:
The Qmenu will close.
print the selected font.
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QIcon, QFont, QCursor
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QFontComboBox, QWidgetAction, QMenu, QPushButton
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Pyside2 FontCombo Box")
self.setGeometry(300,200,300,250)
self.setFontBox()
self.setIcon()
self.show()
def setIcon(self):
appIcon = QIcon("icon.png")
self.setWindowIcon(appIcon)
def setFontBox(self):
self.font_button = QPushButton(self)
self.font_button.setFixedWidth(300)
self.font_button.clicked.connect(lambda: self.setFontmenu())
vbox = QVBoxLayout()
vbox.addWidget(self.font_button)
self.setLayout(vbox)
def setFontmenu(self):
font_menu = QMenu()
font_submenu = QFontComboBox()
font_submenu.setCurrentFont(QFont("Arial"))
objectTest = QObject()
widget = QWidgetAction(objectTest)
widget.setDefaultWidget(font_submenu)
font_menu.addAction(widget)
font_menu.exec_(QCursor.pos())
menu = font_menu
menu.addSeparator()
font_submenu.showPopup()
font_submenu.setFocus()
font_submenu.currentFontChanged.connect(self._changed)
def _changed(self):
font = self.currentFont().family()
print(font)
return
myapp = QApplication(sys.argv)
window = Window()
myapp.exec_()
sys.exit()

Adding or removing a QWidget without affecting any other widgets

I have a PyQT application with a toolbar, a set of buttons, and a bottom row of additional buttons. I'd like to add a TextEdit underneath the bottom row that the user can hide or show. I would like the TextEdit to extend the bottom portion when being shown but, when the user hides it, I would like that bottom portion removed without affecting the height, width, or sizing of any other of the buttons. Imagine just taking a pair of scissors to the TextEdit section when the user hides it but then gluing it back on when the user wants it back. Is this even possible to do in PyQt? The closest I've found is the implementation below which resizes all the buttons.
from PyQt5.QtCore import Qt, QPoint, QTimer, QThread, QSize
from PyQt5.QtGui import QFont, QImage, QPainter, QPen, QPixmap
from PyQt5.QtWidgets import (
QAction, QApplication, QCheckBox, QFileDialog, QHBoxLayout, QLabel,
QMainWindow, QMenu, QMenuBar, QPlainTextEdit, QPushButton, QSpacerItem,
QSizePolicy, QFrame,
QTextEdit, QVBoxLayout, QWidget, QGridLayout, QToolButton, QComboBox
)
from PyQt5.QtWidgets import QApplication
import sys
class AppWindow(QMainWindow):
def __init__(self, main_widget):
super(AppWindow, self).__init__()
self.main_widget = main_widget
self.setCentralWidget(self.main_widget)
class AppWidget(QWidget):
def __init__(self, panels=[]):
super(AppWidget, self).__init__()
self.panels = panels
self.main_layout = QVBoxLayout(self)
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.toolbar_frame = QFrame(self)
self.toolbar_frame_layout = QHBoxLayout(self.toolbar_frame)
self.toolbar_frame_layout.addStretch()
self.log_button = QToolButton(self.toolbar_frame)
self.log_button.setText('Toggle Log')
self.toolbar_frame_layout.addWidget(self.log_button)
self.toolbar_frame.setLayout(self.toolbar_frame_layout)
self.project_frame = QFrame(self)
self.project_frame_layout = QHBoxLayout(self.project_frame)
self.project_dropdown = QComboBox(self.project_frame)
self.project_dropdown.setMinimumSize(20, 0)
self.project_refresh = QToolButton(self.project_frame)
self.project_refresh.setText('Refresh')
self.project_frame_layout.addWidget(self.project_dropdown)
self.project_frame_layout.addWidget(self.project_refresh)
self.project_frame.setLayout(self.project_frame_layout)
self.panel_frame = QFrame(self)
self.panel_frame_layout = QVBoxLayout(self.panel_frame)
for panel in panels:
self.panel_frame_layout.addWidget(panel)
self.panel_frame.setLayout(self.panel_frame_layout)
self.bottom_frame = QFrame(self)
self.bottom_frame_layout = QHBoxLayout(self.bottom_frame)
self.bottom_frame_layout.addStretch()
self.sg_button = QToolButton()
self.sg_button.setText('Extra Stuff')
self.bottom_frame_layout.addWidget(self.sg_button)
self.bottom_frame.setLayout(self.bottom_frame_layout)
self.log = QTextEdit()
self.log_frame = QFrame(self)
self.log_frame_layout = QHBoxLayout(self.log_frame)
self.log_frame_layout.addWidget(self.log)
self.log_frame.setLayout(self.log_frame_layout)
self.main_layout.addWidget(self.toolbar_frame)
self.main_layout.addWidget(self.project_frame)
self.main_layout.addWidget(self.panel_frame)
self.main_layout.addWidget(self.bottom_frame)
self.app_widgets = QWidget(self)
self.app_widgets.setLayout(self.main_layout)
self.log_widget = QWidget(self)
self.log_widget.setLayout(self.log_frame_layout)
self.total_layout = QVBoxLayout(self)
self.total_layout.addWidget(self.app_widgets)
self.total_layout.addWidget(self.log_widget)
self.setLayout(self.total_layout)
self.log_button.clicked.connect(self.toggle_log)
def toggle_log(self):
if self.log_widget.isHidden():
self.log_widget.show()
QTimer.singleShot(0, self.resize_show)
else:
self.log_widget.hide()
QTimer.singleShot(0, self.resize_hide)
# self.adjustSize() Also does not work.
def resize_show(self):
self.resize(self.width(), self.sizeHint().height())
def resize_hide(self):
self.resize(self.width(), self.minimumSizeHint().height())
class AppPanel(QWidget):
def __init__(self, sections=[]):
super(AppPanel, self).__init__()
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.layout = QVBoxLayout(self)
self.setLayout(self.layout)
self.sections = sections
for section in self.sections:
self.layout.addWidget(section)
class AppSection(QWidget):
def __init__(self, buttons=[]):
super(AppSection, self).__init__()
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.buttons = buttons
self.layout = QGridLayout()
for i, button in enumerate(self.buttons):
col = i % 2
row = i // 2
self.layout.addWidget(button, row, col)
self.setLayout(self.layout)
class AppButton(QToolButton):
def __init__(self, text=''):
super(AppButton, self).__init__()
self.setText(text)
self.setFocusPolicy(Qt.NoFocus)
self.setIconSize(QSize(50, 50))
self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
if __name__ == '__main__':
app = QApplication(sys.argv)
app_buttons = [AppButton(text='APPS ' + str(i)) for i in range(5)]
custom_btns = [AppButton(text='Custom ' + str(i)) for i in range(5)]
app_section = AppSection(buttons=app_buttons)
custom_section = AppSection(buttons=custom_btns)
panels = [AppPanel(sections=[app_section, custom_section])]
ex = AppWidget(panels=panels)
lw = AppWindow(main_widget=ex)
lw.show()
app.exec_()
Resizing the widget alone is not a valid solution, because it only overrides the geometry set by the layout without notifying the parent widget.
This is also important as you should not resize the widget based on its hint alone when showing the log: if you increase the size of the window while the log is hidden and then show it again, it will not occupy all the available space.
What you need to do is to access the top level window, force its layout to lay out its contents again, and use its hint to for the resize.
def resize_hide(self):
self.window().layout().activate()
self.window().resize(
self.window().width(),
self.window().minimumSizeHint().height()
)
You can set the alignment policy for your top widget:
[...]
self.total_layout.setAlignment(self.app_widgets, Qt.AlignTop)
self.setLayout(self.total_layout)
[...]
The app_widget will not be resized anymore when you hide your text edit.

Why my QToolButton which I try to make collapsible doesn't collapse properly?

guys.
Asking for your help to troubleshoot my test script.
I am practicing to make collapsible button with widgets inside.
Script was mainly taken from another question in stackoverflow about collapsible buttons.
So I am trying to put under QTabWidget my class CollpsibleBox(QWidget). Problem is that my CollapsibleBox is acting very weird - buttons are jumping , sometimes it doesn't open/close properly.
I was wondering if it's some mistake in placing correctly my widget under QTabWidget or is there some problem with animation?
import random
from PySide2.QtGui import QPixmap, QBrush, QColor, QIcon, QPainterPath, QPolygonF, QPen, QTransform
from PySide2.QtCore import QSize, Qt, Signal, QPointF, QRect, QPoint, QParallelAnimationGroup, QPropertyAnimation, QAbstractAnimation
from PySide2.QtWidgets import QMainWindow, QDialog, QVBoxLayout, QHBoxLayout, QGraphicsView, QGraphicsScene, QFrame, \
QSizePolicy, QGraphicsPixmapItem, QApplication, QRubberBand, QMenu, QMenuBar, QTabWidget, QWidget, QPushButton, \
QSlider, QGraphicsPolygonItem, QToolButton, QScrollArea, QLabel
extraDict = {'buttonSetA': ['test'], 'buttonSetB': ['test']}
tabList = ['Main', 'Extra']
_ui = dict()
class MainWindow(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent=parent)
self.create()
def create(self, **kwargs):
_ui['mainLayout'] = QVBoxLayout()
_ui['tabWidget'] = QTabWidget()
_ui['mainLayout'].addWidget(_ui['tabWidget'])
for tab in tabList:
_ui['tab' + tab] = QWidget()
_ui['tabWidget'].addTab(_ui['tab' + tab], tab)
_ui['tabExtra'].layout = QVBoxLayout()
_ui['tabExtra'].setLayout(_ui['tabExtra'].layout)
_ui['content'] = QWidget()
_ui['tabExtra'].layout.addWidget(_ui['content'])
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box)
lay = QVBoxLayout()
for j in range(8):
label = QLabel("{}".format(j))
color = QColor(*[random.randint(0, 255) for _ in range(3)])
label.setStyleSheet(
"background-color: {}; color : white;".format(color.name())
)
label.setAlignment(Qt.AlignCenter)
lay.addWidget(label)
box.setContentLayout(lay)
self.setLayout(_ui['mainLayout'])
class CollapsibleBox(QWidget):
def __init__(self, name):
super(CollapsibleBox, self).__init__()
self.toggle_button = QToolButton(text=name, checkable=True, checked=False)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setArrowType(Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.toggle_animation = QParallelAnimationGroup(self)
self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0)
self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.content_area.setFrameShape(QFrame.NoFrame)
lay = QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self.content_area, b"maximumHeight"))
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(Qt.DownArrow if not checked else Qt.RightArrow)
self.toggle_animation.setDirection(QAbstractAnimation.Forward
if not checked
else QAbstractAnimation.Backward
)
self.toggle_animation.start()
def setContentLayout(self, layout):
lay = self.content_area.layout()
del lay
self.content_area.setLayout(layout)
collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight())
content_height = layout.sizeHint().height()
for i in range(self.toggle_animation.animationCount()):
animation = self.toggle_animation.animationAt(i)
animation.setDuration(500)
animation.setStartValue(collapsed_height)
animation.setEndValue(collapsed_height + content_height)
content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1)
content_animation.setDuration(500)
content_animation.setStartValue(0)
content_animation.setEndValue(content_height)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.setGeometry(500, 100, 500, 500)
window.show()
sys.exit(app.exec_())
The problem is that you are only adding two widgets to the full layout, and the layout will try to place them as better as possible (tipically at the center of the area that is available for each widget, based on its size hints).
You could either set the alignment of the widget for the layout (placing the buttons on top of their available space):
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box, alignment=Qt.AlignTop)
Or add a stretch to the bottom of the layout:
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
# ...
vlay.addStretch(1)
which will position all buttons on top of the layout.
As a side note, I'd suggest you to avoid the dictionary logic for the ui, as it might become very confusing and prone to errors. If you really need to do that for some (I hope, very good) reason that's ok, but please avoid it when asking questions: it makes really hard to read your code, and people might end up just ignoring your question at all.

How to forward Qt Mouse event to QQuickView?

In my QMainWindow, I have a QFrame and a QWidget that wraps a QQuickView and displays the ui through a qml file.
I am trying to implement a drag and drop functionality where, on mouse-down and mouse-move in the QFrame, a thumbnail follows the cursor's position throughout until mouse-release. The mouse-release will happen within QQuickView.
The hover event within QQuickView has no issues and I can successfully get the hover event. The problem arises when on mouse-down within QFrame followed by a mouse-move into QQuickView, I am unable to get any mouse events in QQuickView.
On the left is the QFrame and the right is the QQuickView.
Hovering in QQuickView independently:
Mouse-down in QFrame and mouse-move into QQuickView:
Any mouse events can only be captured after mouse-release.
These are what I have written so far:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QFrame, QLabel, QGridLayout, QVBoxLayout
from PyQt5.QtCore import Qt, QMimeData, QUrl
from PyQt5.QtGui import QDrag, QPixmap
from PyQt5.QtQuick import QQuickView
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.centralWidget = QWidget(self)
gridlayout = QGridLayout(self.centralWidget)
gridlayout.setContentsMargins(0,0,0,0)
gridlayout.setHorizontalSpacing(0)
gridlayout.setVerticalSpacing(0)
self.setCentralWidget(self.centralWidget)
self.leftPanel = QVBoxLayout()
self.rightPanel = QVBoxLayout()
gridlayout.addLayout(self.leftPanel, 0, 0, 1, 1)
gridlayout.addLayout(self.rightPanel, 0, 1, 1, 1)
gridlayout.setSpacing(0)
self.setStyleSheet("background:grey")
self.resize(300, 200)
self.show()
class Left(QFrame):
def __init__(self):
super().__init__()
self.resize(500, 500)
self.label = QLabel(self)
self.label.resize(50, 50)
def mouseMoveEvent(self, e):
mimeData = QMimeData()
drag = QDrag(self)
self.thumbnail = QPixmap('./test.png').scaled(50, 50, Qt.KeepAspectRatio)
drag.setPixmap(self.thumbnail)
drag.setMimeData(mimeData)
drag.exec_(Qt.MoveAction)
class Right(QQuickView):
def __init__(self, parent=None):
super().__init__(parent)
self.rootContext().setContextProperty('Right', self)
self.setSource(QUrl('./drag.qml'))
self.setMinimumHeight(200)
self.setMinimumWidth(150)
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.leftPanel.addWidget(Left())
main_window.rightPanel.addWidget(QWidget.createWindowContainer(Right()))
app.exec_()
Based on what I have read from different sources and the Qt documentation, I suppose I have to forward the events from the QFrame to the QQuickView or there seems to be some form of global mouse events to be handled.
How can I go about achieving this?
turns out i was using the wrong qml element. DropArea should have been used in the qml.
import QtQuick 2.7
Rectangle {
id: root
anchors.fill: parent
color: 'transparent'
Column {
anchors.centerIn: parent
Rectangle {
width: 50
height: 50
color: 'red'
anchors.horizontalCenter: parent.horizontalCenter
DropArea {
anchors.fill: parent
onEntered: parent.color = 'blue'
onExited: parent.color = 'red'
onDropped: console.log('triggger this thing yo')
}
}
Text {
width: parent.parent.width
text: 'on hover over box, color changes from red to blue and vice versa when hover out'
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
}
}
hope this will be able to help someone out down the road. credits to raven-worx on Qt Forum for the solution.

QScrollArea does not attach to label

I am trying to display an image label with scrollbars within a Box layout.
However, the scroll area appears at the wrong place with the wrong size.
Could you please tell me what I am doing wrong?
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QScrollArea
from PyQt5.QtGui import QPixmap
class ApplicationWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
main_widget = QWidget(self)
btn = QPushButton("Bye", self)
btn.clicked.connect(self.close)
img = QPixmap("1.jpg")
label = QLabel(main_widget)
label.setPixmap(img)
scrollArea = QScrollArea(main_widget)
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(label)
l = QVBoxLayout(main_widget)
l.addWidget(label)
l.addWidget(btn)
self.setCentralWidget(main_widget)
def closeEvent(self, ce):
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
aw = ApplicationWindow()
aw.show()
app.exec_()
The result is:
The problem is that instead of adding the QLabel to QVBoxLayout you must add the QScrollArea. You must change:
l.addWidget(label)
to
l.addWidget(scrollArea)

Categories

Resources