How to show context menu at desired position in pyqt5? - python

My aim is to display context menu at desired position, base on mainwindow. Based on user mood, Mainwindow may be dragged from one position to another or shrink or grow. At this situation, context menu display position will change. If Mainwindow dragged to right most of screen, then the context menu will display left to qcombo box. if Mainwindow dragged to left most of the screen, then the context menu will display right to Qcombox position. If mainwindow is centre, then context menu will apper on right to qcombobox. simply context menu to display in available screen. (Not under the cursor or at a fixed postition)
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QComboBox, QPushButton, QVBoxLayout,QMenu
from PyQt5.QtCore import Qt,QPoint
class CheckableComboBox(QComboBox):
def __init__(self, xposi,yposi):
super().__init__()
self.xpo = xposi
self.ypo = yposi
self.setEditable(True)
self.lineEdit().setReadOnly(True)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
self.setStyleSheet("QComboBox QAbstractItemView::item {border: none;padding-left: 5px;}")
self.lineEdit().installEventFilter(self)
self.closeOnLineEditClick = True
self.double_click = None
def showMenu(self,pos):
menu = QMenu()
clear_action = menu.addAction("Clear Selection")
copy_action = menu.addAction(("Copy"))
action = menu.exec_(self.mapToGlobal(QPoint(self.xpo,self.ypo)))
if action == clear_action:
self.combo.setCurrentIndex(1)
class MyApp(QWidget):
def __init__(self):
super().__init__()
mainLayout = QVBoxLayout()
self.setLayout(mainLayout)
self.data = ['America', 'Russia', 'China', 'Germany', 'France', 'India']
self.combo = CheckableComboBox(100,100) #self.pos_x,self.pos_y)
self.combo.setFixedSize(600,60)
btn = QPushButton('Print Values')
btn.clicked.connect(self.getValue)
mainLayout.addWidget(self.combo)
mainLayout.addWidget(btn)
self.combo.addItems(self.data)
self.siz = None
def moveEvent(self,e):
self.siz = self.pos()
self.pos_x = self.siz.x()
self.pos_y = self.siz_y()
# super().moveEvent(e)
def getValue(self):
print(self.combo.currentData())
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet('''QWidget {font-size: 20px;}''')
myApp = MyApp()
myApp.show()
app.exit(app.exec_())
.

Related

Align Icon in QPushButton

Starting the program, the QIcon is aligned on the left (it's standard i guess) with the text right to it.
Instead I want the icon to be centered on top with the text below it.
I tried using setStyleSheet with show_all.setStyleSheet("QIcon { vertical-align: top }") and show_all.setStyleSheet("QPushButton { text-align: bottom }").
How can I achieve this?
QPushButton doesn't allow to choose the layout of its icon and label. Also, remember that while Qt features style sheets to style widgets, not all CSS known properties and selectors are available. Furthermore, style sheets only work on widgets, so using the QIcon selector isn't supported, since QIcon is not a QWidget subclass.
The most simple solution is to use a QToolButton and set the toolButtonStyle correctly:
self.someButton = QtWidgets.QToolButton()
# ...
self.someButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
The alternative is to subclass the button, provide a customized paint method and reimplement both sizeHint() and paintEvent(); the first is to ensure that the button is able to resize itself whenever required, while the second is to paint the button control (without text!) and then paint both the icon and the text.
Here's a possible implementation:
from PyQt5 import QtCore, QtGui, QtWidgets
class CustomButton(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._icon = self.icon()
if not self._icon.isNull():
super().setIcon(QtGui.QIcon())
def sizeHint(self):
hint = super().sizeHint()
if not self.text() or self._icon.isNull():
return hint
style = self.style()
opt = QtWidgets.QStyleOptionButton()
self.initStyleOption(opt)
margin = style.pixelMetric(style.PM_ButtonMargin, opt, self)
spacing = style.pixelMetric(style.PM_LayoutVerticalSpacing, opt, self)
# get the possible rect required for the current label
labelRect = self.fontMetrics().boundingRect(
0, 0, 5000, 5000, QtCore.Qt.TextShowMnemonic, self.text())
iconHeight = self.iconSize().height()
height = iconHeight + spacing + labelRect.height() + margin * 2
if height > hint.height():
hint.setHeight(height)
return hint
def setIcon(self, icon):
# setting an icon might change the horizontal hint, so we need to use a
# "local" reference for the actual icon and go on by letting Qt to *think*
# that it doesn't have an icon;
if icon == self._icon:
return
self._icon = icon
self.updateGeometry()
def paintEvent(self, event):
if self._icon.isNull() or not self.text():
super().paintEvent(event)
return
opt = QtWidgets.QStyleOptionButton()
self.initStyleOption(opt)
opt.text = ''
qp = QtWidgets.QStylePainter(self)
# draw the button without any text or icon
qp.drawControl(QtWidgets.QStyle.CE_PushButton, opt)
rect = self.rect()
style = self.style()
margin = style.pixelMetric(style.PM_ButtonMargin, opt, self)
iconSize = self.iconSize()
iconRect = QtCore.QRect((rect.width() - iconSize.width()) / 2, margin,
iconSize.width(), iconSize.height())
if self.underMouse():
state = QtGui.QIcon.Active
elif self.isEnabled():
state = QtGui.QIcon.Normal
else:
state = QtGui.QIcon.Disabled
qp.drawPixmap(iconRect, self._icon.pixmap(iconSize, state))
spacing = style.pixelMetric(style.PM_LayoutVerticalSpacing, opt, self)
labelRect = QtCore.QRect(rect)
labelRect.setTop(iconRect.bottom() + spacing)
qp.drawText(labelRect,
QtCore.Qt.TextShowMnemonic|QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop,
self.text())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = CustomButton('Alles anzeigen', icon=QtGui.QIcon.fromTheme('document-new'))
w.setIconSize(QtCore.QSize(32, 32))
w.show()
sys.exit(app.exec_())
Alternatively, try it:
import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout,
QToolBar, QAction)
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
add_action = QAction(QIcon("img/add.png"), "Add", self)
add_action.triggered.connect(self.addValue)
sub_action = QAction(QIcon("img/min.png"), "Sub", self)
sub_action.triggered.connect(self.subValue)
toolbar = QToolBar()
toolbar.setContentsMargins(0, 0, 0, 0)
toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon | Qt.AlignLeading)
toolbar.setIconSize(QSize(50, 50))
toolbar.addAction(add_action)
toolbar.addAction(sub_action)
rootGrid = QGridLayout(self)
rootGrid.addWidget(toolbar)
def addValue(self):
print("def addValue:")
def subValue(self):
print("def subValue:")
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Widget()
main.show()
sys.exit(app.exec_())

Recursion Error on PyQt QStackedWidget when using enterEvent and leaveEvent

I am using a QStackedWidget which has its own enterEvent and leaveEvent. When I move my mouse to the QStackedWidget the enterEvent sets the current index to 1 and on the leaveEvent it sets the current index to 0 so that a different widget is shown on mouse enter and mouse leave in the area of QStackedWidget. It does what I want only if I quickly move my mouse in and out, if I place my mouse too long in the area I get RecursionError: maximum recursion depth exceeded.
Is this because the widgets are changing so fast that the internal stack can't keep up? My question is "How can I make sure this error doesn't occur? I want to display one widget as long as the mouse is over the QStackedWidget and when it is not I want to display the original widget."
The following is the code that I modified (Original Source used buttons to set the index and it is PyQt4)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import *
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self,event):
self.setCurrentIndex(1)
def leaveEvent(self,event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal=QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! "*100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())
The recursion occurs because when you start the FaderWidget it changes focus and enterEvent is called again which creates a new FaderWidget.
The solution is to verify that the old index is different from the new index to just create the FadeWidget:
import sys
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import (
QApplication,
QCalendarWidget,
QGridLayout,
QStackedWidget,
QTextEdit,
QWidget,
)
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.pixmap_opacity = 1.0
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.update()
class StackedWidget(QStackedWidget):
def setCurrentIndex(self, index):
if self.currentIndex() != index:
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self, event):
self.setCurrentIndex(1)
def leaveEvent(self, event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal = QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! " * 100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())

How to display self defined QSplitter Class onto QMainWindow

I am currently trying to display a split screen view onto my window using PyQt5 with QSplitter and QMainWindow. I need QMainWindow to be able to create the menu options for the application that I am working on and would need to use QSplitter to use three separate windows for the application. Currently based on my code, the window is showing up, but there is no split screen shown at all. The only thing on the display is a blank screen when there should be the numbers: 1, 2, 3, and 4 shown in each corner.
I have tried to implement the QSplitters into the main window but then found that that approach does not work as it cannot set the layout of the QSplitters on top of setting the layout of the main window from QMainWindow. The next approach I used was the class based model where I defined what exactly I wanted for each splitter and implemented the subwindows into the main window and used two splitters (horizontal and vertical).
import sys
#imports
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QAction, QVBoxLayout, QStackedWidget
from PyQt5.QtWidgets import QMessageBox, QFrame, QSplitter, QTextEdit, QHBoxLayout, QLineEdit, QLabel
from PyQt5.QtGui import QKeySequence
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QIcon #to import icon
'''
class widget1(QWidget):
"""docstring for widget1"""
def __init__(self):
QWidget.__init__(self)
hbox = QHBoxLayout()
#create split screen
#this is the top left frame of the window
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
#first splitter
splitter1 = QSplitter(Qt.Horizontal)
lineedit = QLineEdit()
splitter1.addWidget(topleft)
splitter1.addWidget(lineedit)
splitter1.setSizes([200,200])
#second splitter and it is located at the bottom of the screen
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
splitter2 = QSplitter(Qt.Horizontal)
#add the splitter to the layout
hbox.addWidget(splitter2)
'''
class SubWindow(QWidget):
def __init__(self, label):
super(SubWindow, self).__init__()
self.label = QLabel(label)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("QLabel {font-size:40px;}")
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(self.label)
self.setLayout(self.main_layout)
#GUI class to create window
class GUI(QMainWindow):
"""docstring for GUI"""
def __init__(self,):
# Understand and explain ...
super().__init__()
#initialize the UI using the initUI method
self.initUI()
def initUI(self):
#set the title of the window
self.setWindowTitle('Visualization')
#Set the status bar in the beginning
self.statusBar().showMessage("In Progress")
#create new menubar object at top of window
menubar = self.menuBar()
#add file button to menubar
file_menu = menubar.addMenu("File")
#add edit button to menubar
edit_menu = menubar.addMenu("Edit")
#add view button to menubar
view_menu = menubar.addMenu("View")
#path for new icon and create the icon
new_icon = QIcon('newfileicon.png')
open_icon =QIcon('openfileicon.png')
exitapp_icon = QIcon('exitappicon.png')
#create new subbutton for the file menu and add icon and shortcuts
#as well as connecting the methods for each file menu
new_action = QAction(new_icon,'New', self)
new_action.setShortcut('Ctrl+N')
new_action.triggered.connect(self.newCall)
open_action = QAction(open_icon,'Open', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.openCall)
exit_action = QAction(exitapp_icon,'Exit', self)
exit_action.setShortcut('Ctrl+X')
exit_action.triggered.connect(self.exitCall)
#add the action to the file menu button
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(exit_action)
#when hovering over the "new", "open", and "exit", update statusbar
new_action.setStatusTip("Create New File")
open_action.setStatusTip("Open a file")
exit_action.setStatusTip("Exit application")
#when clicking the exit, close the application
exit_action.triggered.connect(self.close)
self.SubWindow1 = SubWindow("1")
self.SubWindow2 = SubWindow("2")
self.SubWindow3 = SubWindow("3")
self.SubWindow4 = SubWindow("4")
self.subsplitter1 = QSplitter(Qt.Horizontal)
self.subsplitter1.addWidget(self.SubWindow1)
self.subsplitter1.addWidget(self.SubWindow2)
self.subsplitter2 = QSplitter(Qt.Horizontal)
self.subsplitter2.addWidget(self.SubWindow3)
self.subsplitter2.addWidget(self.SubWindow4)
self.subsplitter = QSplitter(Qt.Vertical)
self.subsplitter.addWidget(self.subsplitter1)
self.subsplitter.addWidget(self.subsplitter2)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(self.subsplitter)
self.setLayout(self.main_layout)
#hbox = widget1()
#self.view = QHBoxLayout(self)
#resize the window to a 900x800 window
self.resize(900, 800)
def newCall(self):
QMessageBox.about(self, "Confirmation", "Are you sure you want to create a new file?")
def openCall(self):
QMessageBox.about(self, "Confirmation", "Are you sure you want to open a file?")
#if yes to the messagebox, then close, else dont close and pass
def exitCall(self):
reply = QMessageBox.question(self, "Confirmation", "Are you sure you want to exit the application?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.close
sys.exit()
else:
pass
#main function
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = GUI()
gui.show()
sys.exit(app.exec_())
I expected there to be the results of having four split screens with the values of "1", "2", "3", and "4" on each corner of the screen. Instead the output is what I have gotten before which is just a blank screen with the menubar and status bar being functional.
QMainWindow unlike QWidget already has a predefined layout:
So you should not use a layout to set the QSplitter but use the setCentralWidget() method of QMainWindow:
# ...
self.subsplitter = QSplitter(Qt.Vertical)
self.subsplitter.addWidget(self.subsplitter1)
self.subsplitter.addWidget(self.subsplitter2)
self.setCentralWidget(self.subsplitter)
self.resize(900, 800)
# ...

Update PyQt menu

I am trying to dynamically update a menu with new items when I add new items via the form into Qsettings. For example, if you open my code and click the button and then click "new" it will open a QLineEdit with a button. When the button is clicked the list data gets stored via Qsettings.
I'd like to be able to update the menu somehow to show the items without restarting. I tried some things like calling repaint and update in a few areas with no luck.
Here is my code, I slimmed it down the best that I could for the example.
import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
QVBoxLayout, QLineEdit,QApplication, QWidgetAction, QTextBrowser, QAction, QMenu
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout()
self.menu_action = QAction(QIcon("icon.png"),"New", self)
self.menu_btn = QPushButton()
self.menu = MyMenu("Menu", self.menu_btn)
self.add_menu = self.menu.addMenu(QIcon("icon.png"), "Menu")
self.add_menu.addAction(self.menu_action)
self.menu_btn.setMenu(self.menu)
self.textBox = QTextBrowser(self)
action = QWidgetAction(self.menu_btn)
action.setDefaultWidget(self.textBox)
self.menu_btn.menu().addAction(action)
settings = QtCore.QSettings('test_org', 'my_app')
self.new_items = settings.value('new_item', [])
print('%s' % self.new_items)
for item in self.new_items:
self.create_action = QAction(QIcon("icon.png"), item[0], self)
self.create_action.setData(item)
self.add_menu.addAction(self.create_action)
self.create_action.triggered.connect(functools.partial(self.menu_clicked, self.create_action))
self.layout.addWidget(self.menu_btn)
self.setLayout(self.layout)
self.menu_action.triggered.connect(self.open_window)
def open_window(self):
self.create_menu_item = Create_Menu_Item()
self.create_menu_item.show()
def menu_clicked(self, item):
itmData = item.data()
print(itmData)
class Create_Menu_Item(QWidget):
def __init__(self, parent=None):
super(Create_Menu_Item, self).__init__(parent)
self.resize(200, 195)
self.layout = QVBoxLayout()
self.form = QLineEdit()
self.btn = QPushButton()
self.layout.addWidget(self.form)
self.layout.addWidget(self.btn)
self.btn.clicked.connect(self.save_new_item)
self.setLayout(self.layout)
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
class MyMenu(QMenu):
def event(self,event):
if event.type() == QtCore.QEvent.Show:
self.move(self.parent().mapToGlobal(QtCore.QPoint(0,0))-QtCore.QPoint(0,self.height()))
return super(MyMenu,self).event(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Anyone have any ideas? Thanks.
You can create a for loop to access the list.
Bare in mind it is a list of lists so a nested for loop is neccessary
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
# Add new menu items..
for x in self.new_item:
for y in x:
print(y)
The above will keep adding the WHOLE list of items every time you add a new item..
To just add the newest item, this is all you need (the last item added to the list)
w.add_menu.addAction(self.new_item[0][-1])
so
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
#ADD last item in the list
w.add_menu.addAction(self.new_item[0][-1])

How to customize QPushButton to only popup menu when clicked around the arrow?

I want to add a popup menu to QPushButton, but only popup it when you click near the arrow, if you click other area on the button, it calls the slot connected in main UI.
I know there is QToolButton, and you can set its ToolButtonPopupMode to MenuButtonPopup, but for some reason it looks different than then rest of the button on my UI, I assume I could somehow modify the style of it to make it look exactly like QPushButton, anyway in the end I decided to subclass QPushButton instead.
The problems in the following code are:
1. How do I get the rect of the arrow, maybe show a dashed rect around the arrow, I thought the "popup menu hotspot" area should be a little bit bigger than the arrow. right now I hardcoded 20px, but I think it should be retrieved from QStyle?
[solved] How to make the button look "pressed" when clicked not near the arrow, right now its look does not change, I guess it's because I did not call base class MousePressEvent, because I don't want the menu to popup when clicked elsewhere.
How to move the position of the arrow, in my applicaton it is too close to the right edge, how can I move it to the left a little bit?
code:
from PyQt4 import QtGui, QtCore
import sys
class MyButton(QtGui.QPushButton):
def __init__(self, parent=None):
super(MyButton, self).__init__(parent)
def mousePressEvent(self, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
# figure out press location
pos = event.pos
topRight = self.rect().topRight()
bottomRight = self.rect().bottomRight()
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
print topRight, bottomRight, frameWidth
# get the rect from QStyle instead of hardcode numbers here
arrowTopLeft = QtCore.QPoint(topRight.x()-20, topRight.y())
arrowRect = QtCore.QRect(arrowTopLeft, bottomRight)
if arrowRect.contains(event.pos()):
print 'clicked near arrow'
# event.accept()
QtGui.QPushButton.mousePressEvent(self, event)
else:
print 'clicked outside'
# call the slot connected, without popup the menu
# the following code now does not make
# the button pressed
self.clicked.emit(True)
event.accept()
class Main(QtGui.QDialog):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QtGui.QVBoxLayout()
pushbutton = MyButton('Popup Button')
layout.addWidget(pushbutton)
menu = QtGui.QMenu()
menu.addAction('This is Action 1', self.Action1)
menu.addAction('This is Action 2', self.Action2)
pushbutton.setMenu(menu)
self.setLayout(layout)
pushbutton.clicked.connect(self.button_press)
def button_press(self):
print 'You pressed button'
def Action1(self):
print 'You selected Action 1'
def Action2(self):
print 'You selected Action 2'
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()
edit:
it seems this will stop the menu from poping up if clicked on the left side of the button
else:
print 'clicked outside'
self.blockSignals(True)
QtGui.QPushButton.mousePressEvent(self, event)
self.blockSignals(False)
Have you thought on using a QComboBox?
Or maybe two buttons next to each other one for appearance only, and the other that calls your context?
Would work to use mask on your button through pixmap.
You also could make some use of setStyleSheet("") can make some use of these attributes.
Here is a little example:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QScrollArea
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
class WPopUpButton(QWidget):
"""WPopUpButton is a personalized QPushButton."""
w_container = None
v_layout_container = None
v_scroll_area = None
v_layout_preview = None
def __init__(self):
"""Init UI."""
super(WPopUpButton, self).__init__()
self.init_ui()
def init_ui(self):
"""Init all ui object requirements."""
self.button_that_do_nothing = QPushButton("Popup Button")
self.button_that_do_nothing.setStyleSheet("""
border: 0px;
background: gray;
""")
self.button_that_do_something = QPushButton("->")
#you can also set icon, to make it look better :D
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: gray;
""")
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0,0,0,0)
self.layout.addWidget(self.button_that_do_nothing)
self.layout.addWidget(self.button_that_do_something)
self.setLayout(self.layout)
self.create_connections()
def create_connections(self):
self.button_that_do_something.pressed.connect(self.btn_smtg_pressed)
self.button_that_do_something.released.connect(self.btn_smtg_released)
def btn_smtg_pressed(self):
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: blue;
""")
def btn_smtg_released(self):
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: gray;
""")
# HERE YOU DO WHAT YOU NEED
# FOR EXAMPLE CALL YOUR CONTEXT WHATEVER :D
def run():
app = QApplication(sys.argv)
GUI = WPopUpButton()
GUI.show()
sys.exit(app.exec_())
run()
By the way I'm using Pyqt5, you just gotta change your imports ")
Here's another option that may partially answer your question.
Instead of using the default menu, you can combine CustomContextMenu and custom arrow created by either QLabel and/or .png images.
setContentsMargins in the code will allow a much more flexible layout.
sample image
import os
from PyQt5.QtWidgets import (
QDialog,
QPushButton,
QApplication,
QVBoxLayout,
QMenu,
QStyle,
QHBoxLayout,
QLabel,
)
from PyQt5.QtCore import (
QEvent,
QPoint,
QRect,
Qt,
QSize,
)
from PyQt5.QtGui import (
QIcon,
QMouseEvent,
)
import sys
import functools
import copy
class MyButton(QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.clicked_near_arrow = None
# set icon by letter
self.label_icon = QLabel(" ▼ ")
self.label_icon.setAttribute(Qt.WA_TranslucentBackground)
self.label_icon.setAttribute(Qt.WA_TransparentForMouseEvents)
icon_size = QSize(19, 19)
# set icon by picture
self.pixmap_default = QIcon("default_button.png").pixmap(icon_size) # prepare images if necessary
self.pixmap_presssed = QIcon("pressed_button.png").pixmap(icon_size) # prepare images if necessary
self.pic_icon = QLabel()
self.pic_icon.setAttribute(Qt.WA_TranslucentBackground)
self.pic_icon.setAttribute(Qt.WA_TransparentForMouseEvents)
self.pic_icon.setPixmap(self.pixmap_default)
# layout
lay = QHBoxLayout(self)
lay.setContentsMargins(0, 0, 6, 3)
lay.setSpacing(0)
lay.addStretch(1)
lay.addWidget(self.pic_icon)
lay.addWidget(self.label_icon)
def set_icon(self, pressed):
if pressed:
self.label_icon.setStyleSheet("QLabel{color:white}")
self.pic_icon.setPixmap(self.pixmap_presssed)
else:
self.label_icon.setStyleSheet("QLabel{color:black}")
self.pic_icon.setPixmap(self.pixmap_default)
def mousePressEvent(self, event):
if event.type() == QEvent.MouseButtonPress:
self.set_icon(pressed=True)
# figure out press location
topRight = self.rect().topRight()
bottomRight = self.rect().bottomRight()
# get the rect from QStyle instead of hardcode numbers here
arrowTopLeft = QPoint(topRight.x()-19, topRight.y())
arrowRect = QRect(arrowTopLeft, bottomRight)
if arrowRect.contains(event.pos()):
self.clicked_near_arrow = True
self.blockSignals(True)
QPushButton.mousePressEvent(self, event)
self.blockSignals(False)
print('clicked near arrow')
self.open_context_menu()
else:
self.clicked_near_arrow = False
QPushButton.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.rect().contains(event.pos()):
self.set_icon(pressed=True)
else:
self.set_icon(pressed=False)
QPushButton.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
self.set_icon(pressed=False)
if self.clicked_near_arrow:
self.blockSignals(True)
QPushButton.mouseReleaseEvent(self, event)
self.blockSignals(False)
else:
QPushButton.mouseReleaseEvent(self, event)
def setMenu(self, menu):
self.menu = menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_context_menu)
# ContextMenueのlauncher
def open_context_menu(self, point=None):
point = QPoint(7, 23)
self.menu.exec_(self.mapToGlobal(point))
event = QMouseEvent(QEvent.MouseButtonRelease, QPoint(10, 10), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
self.mouseReleaseEvent(event)
class Main(QDialog):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
menu = QMenu()
menu.addAction('This is Action 1', self.Action1)
menu.addAction('This is Action 2', self.Action2)
pushbutton = MyButton('Popup Button')
pushbutton.setMenu(menu)
layout = QVBoxLayout()
layout.addWidget(pushbutton)
self.setLayout(layout)
# event connect
pushbutton.setAutoDefault(False)
pushbutton.clicked.connect(self.button_press)
def button_press(self):
print('You pressed button')
def Action1(self):
print('You selected Action 1')
def Action2(self):
print('You selected Action 2')
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
app.exec_()

Categories