How do I connect WindowContextHelpButtonHint to a widget click? - qt

I can add self.setWindowFlags(Qt.Window | Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint) to the constructor of my QMainWindow which adds the '?' button. Now I want to add contextual help information to virtually all UI components. How do I connect a click on the '?' button and then a click on some ui element (say a qlistview) such that a help message pops up?
Below is part of my program stripped to make it easy. How do I make it such that when the user clicks the '?' and then either a QDoubleSpinBox or the QListView or any of the QPushButtons that a help message displays?
I have searched for this but it seems the vast amount of questions and answers are about how to remove the '?' button or how to add it. None actually deal with how to alter the button's behaviour.
I've also tried using setInputMethodHints() to assign hints to ui components but this method doesn't accept custom strings as I assumed it would.
Btw: I also would like the '?' button to not replace the minimization button. I have yet to figure out how to achieve this either.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
APPNAME = 'Mesoscale Brain Explorer'
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle(APPNAME)
self.sidebar = Sidebar()
self.setWindowFlags(Qt.Window | Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint)
self.setup_ui()
def setup_ui(self):
self.pl_frame = QFrame()
splitter = QSplitter(self)
self.enable = lambda yes: splitter.setEnabled(yes)
splitter.setHandleWidth(3)
splitter.setStyleSheet('QSplitter::handle {background: #cccccc;}')
splitter.addWidget(self.sidebar)
splitter.addWidget(self.pl_frame)
self.setCentralWidget(splitter)
self.menu = self.menuBar()
m = self.menu.addMenu('&File')
a = QAction('&New project', self)
a.setShortcut('Ctrl+N')
a.setStatusTip('Create new project')
m.addAction(a)
a = QAction('&Open project', self)
a.setShortcut('Ctrl+O')
a.setStatusTip('Open project')
m.addAction(a)
a = QAction("&Quit", self)
a.setShortcut("Ctrl+Q")
a.setStatusTip('Leave The App')
a.setIcon(QIcon('pics/quit.png'))
m.addAction(a)
about_action = QAction('&About ' + APPNAME, self)
about_action.setStatusTip('About ' + APPNAME)
about_action.setShortcut('F1')
m = self.menu.addMenu('&Project')
m.setEnabled(False)
a = QAction("&Close", self)
a.setStatusTip('Close project')
m.addAction(a)
self.project_menu = m
help_menu = self.menu.addMenu('&Help')
help_menu.addAction(about_action)
class Sidebar(QWidget):
open_pipeconf_requested = pyqtSignal()
open_datadialog_requested = pyqtSignal()
automate_pipeline_requested = pyqtSignal()
x_origin_changed = pyqtSignal(float)
y_origin_changed = pyqtSignal(float)
units_per_pixel_changed = pyqtSignal(float)
def __init__(self, parent=None):
super(Sidebar, self).__init__(parent)
self.x_origin = QDoubleSpinBox()
self.y_origin = QDoubleSpinBox()
self.units_per_pixel = QDoubleSpinBox()
self.setup_ui()
def setup_ui(self):
self.setContentsMargins(4, 6, 5, 0)
vbox = QVBoxLayout()
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Origin:'))
hbox.addWidget(QLabel('Units per pixel:'))
vbox.addLayout(hbox)
hbox2 = QHBoxLayout()
hhbox = QHBoxLayout()
hhbox2 = QHBoxLayout()
hhbox.addWidget(QLabel("X:"))
hhbox.addWidget(self.x_origin)
hhbox.addWidget(QLabel("Y:"))
hhbox.addWidget(self.y_origin)
hhbox2.addWidget(self.units_per_pixel)
hbox2.addLayout(hhbox)
hbox2.addLayout(hhbox2)
vbox.addLayout(hbox2)
self.units_per_pixel.setDecimals(5)
self.units_per_pixel.setMaximum(100000)
self.x_origin.setMaximum(100000)
self.y_origin.setMaximum(100000)
self.pl_list = QListView()
self.pl_list.setIconSize(QSize(18, 18))
self.pl_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.pl_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
vbox.addWidget(QLabel('Pipeline:'))
vbox.addWidget(self.pl_list)
pb = QPushButton('&Automation')
pb.clicked.connect(self.automate_pipeline_requested)
vbox.addWidget(pb)
vbox.addSpacerItem(QSpacerItem(0, 1, QSizePolicy.Minimum, QSizePolicy.Expanding))
pb = QPushButton('&Configure Pipeline')
pb.clicked.connect(self.open_pipeconf_requested)
vbox.addWidget(pb)
pb = QPushButton('&Manage Data')
pb.clicked.connect(self.open_datadialog_requested)
vbox.addWidget(pb)
vbox.setStretch(0, 0)
vbox.setStretch(1, 0)
vbox.setStretch(2, 0)
self.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setApplicationName(APPNAME)
app.setOrganizationName('University of British Columbia')
w = MainWindow()
w.resize(1060, 660)
w.show()
app.exec_()
app.deleteLater()
del w
sys.exit()

Related

How to swap the position of the 2 buttons by dragging in PyQt5 [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I am trying to make small program in Python,using PyQt5.The program will as have many button has arranged neatly.When dragging one of the buttons to another button, you can swap their positions while the buttons are still arranged neatly.I tried many ways, but in the end the buttons couldn’t be arranged neatly.What should I do to achieve this effect
Sounds like a job for QEventFilter.
Simple version:
from PyQt5.QtCore import QEvent, QObject
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QPushButton
class Filter(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._pressed = None
self._drag = False
def setDragMode(self, active):
""" When drag mode activated buttons are not clickable (mouse events are filtered) """
self._drag = active
def setLayout(self, layout):
self._layout = layout
for i in range(layout.count()):
item = layout.itemAt(i)
if item.widget():
item.widget().installEventFilter(self)
def eventFilter(self, obj, event):
if not self._drag:
return False
if event.type() == QEvent.MouseButtonPress:
if isinstance(obj, QPushButton):
self._pressed = obj
return True
elif event.type() == QEvent.MouseMove:
button = self._pressed
layout = self._layout
if button is None:
return True
pos = obj.mapToParent(event.pos())
index = layout.indexOf(button)
geometry = button.geometry()
if pos.x() < geometry.topLeft().x():
if index - 1 >= 0:
prev = layout.itemAt(index - 1).widget().geometry()
if prev.topRight().x() > pos.x():
layout.insertWidget(index - 1, button)
elif pos.x() > geometry.topRight().x():
if index + 1 < layout.count():
next_ = layout.itemAt(index + 1).widget().geometry()
if next_.topLeft().x() < pos.x():
layout.insertWidget(index + 1, button)
return True
elif event.type() == QEvent.MouseButtonRelease:
self._pressed = None
return True
return False
if __name__ == "__main__":
app = QApplication([])
widget = QWidget()
layout = QHBoxLayout()
buttons = [QPushButton("button {}".format(i)) for i in range(5)]
for button in buttons:
layout.addWidget(button)
widget.setLayout(layout)
widget.show()
filter_ = Filter()
filter_.setLayout(layout)
filter_.setDragMode(True)
app.exec_()
More intuitive and interactive version:
from PyQt5.QtCore import QEvent, QObject
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QPushButton
class Overlay(QWidget):
def __init__(self, parent = None):
super().__init__(parent)
self._widget = None
self._pressPos = None
self._topLeftPos = None
self._movePos = None
self._pixmap = None
def setDragWidget(self, widget, pressPos, topLeftPos):
self._widget = widget
self._pressPos = pressPos
self._movePos = pressPos
self._topLeftPos = topLeftPos
self._pixmap = widget.grab()
def mouseMoved(self, pos):
self._movePos = pos
self.update()
def mouseReleased(self):
self._pixmap = None
self.update()
def paintEvent(self, event):
if self._pixmap is None:
return
painter = QPainter(self)
painter.drawPixmap(self._topLeftPos + self._movePos - self._pressPos, self._pixmap)
def swap_widgets(layout, index1, index2):
if index1 == index2:
return
widget1 = layout.itemAt(index1).widget()
widget2 = layout.itemAt(index2).widget()
layout.insertWidget(index1, widget2)
layout.insertWidget(index2, widget1)
class Filter(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._pressed = None
self._drag = False
self._overlay = None
def setDragMode(self, active):
""" When drag mode activated buttons are not clickable (mouse events are filtered) """
self._drag = active
def setLayout(self, layout):
self._layout = layout
for i in range(layout.count()):
item = layout.itemAt(i)
if item.widget():
item.widget().installEventFilter(self)
def eventFilter(self, obj, event):
if not self._drag:
return False
if event.type() == QEvent.MouseButtonPress:
if isinstance(obj, QPushButton):
self._pressed = obj
parent = obj.parent()
overlay = Overlay(parent)
overlay.setGeometry(parent.rect())
pressPos = obj.mapToParent(event.pos())
overlay.setDragWidget(obj, pressPos, obj.geometry().topLeft())
overlay.show()
self._overlay = overlay
return True
elif event.type() == QEvent.MouseMove:
button = self._pressed
layout = self._layout
if button is None:
return True
pos = obj.mapToParent(event.pos())
overlay = self._overlay
if overlay is not None:
overlay.mouseMoved(pos)
return True
elif event.type() == QEvent.MouseButtonRelease:
overlay = self._overlay
button = self._pressed
layout = self._layout
if overlay is not None:
overlay.mouseReleased()
overlay.hide()
pos = obj.mapToParent(event.pos())
index1 = layout.indexOf(button)
for index2 in range(layout.count()):
widget = layout.itemAt(index2).widget()
if widget is None:
continue
if widget.geometry().contains(pos):
swap_widgets(layout, index1, index2)
break
self._pressed = None
return True
return False
if __name__ == "__main__":
app = QApplication([])
widget = QWidget()
layout = QHBoxLayout()
buttons = [QPushButton("button {}".format(i)) for i in range(5)]
for button in buttons:
layout.addWidget(button)
widget.setLayout(layout)
widget.show()
filter_ = Filter()
filter_.setLayout(layout)
filter_.setDragMode(True)
app.exec_()

Python has stopped working while adding sizer in scrolled panel in wxpython

import wx
import wx.adv
from wx.lib.masked import NumCtrl
import wx.lib.scrolledpanel
import wx.lib.masked as masked
from wx.lib.masked import Field, BaseMaskedTextCtrl
class mainWindow(wx.Frame):
def __init__(self, camera):
#inheritence
wx.Frame.__init__(self, None, style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN, size=(1370,720),pos=(0,0))
self.Title = "Registration"
self.SetSize(1370,720)
self.Maximize(True)
self.SetBackgroundColour('#f0f0f0')
menubar = wx.MenuBar()
#main ui
self.mainpanel = wx.Panel(self, size=(1370,720), pos=(0,0))
self.mainpanel.SetBackgroundColour('#f0f0f0')
mid_font = wx.Font(22, wx.DECORATIVE,wx.ITALIC,wx.BOLD)
#------------form panel-------------
self.scroll_panel = wx.lib.scrolledpanel.ScrolledPanel(self.mainpanel,size=(380,540), pos=(30,20),style=wx.NO_BORDER)
self.namepanel = wx.Panel(self.scroll_panel,size=(350,800), pos=(0,0),style=wx.DOUBLE_BORDER)
self.namepanel.SetForegroundColour('#1b262c')
self.Sizer = wx.BoxSizer( wx.VERTICAL )
self.scroll_panel.SetSizer(self.Sizer)
small_font = wx.Font(11, wx.DECORATIVE,wx.ITALIC,wx.BOLD)
fname = wx.StaticText(self.namepanel, -1, "First Name",pos=(30,70))
fname.SetFont(small_font)
self.fname = wx.TextCtrl(self.namepanel, pos=(155,70), size=(160, 25))
mname = wx.StaticText(self.namepanel, -1, "Middle Name",pos=(30,110))
mname.SetFont(small_font)
self.mname = wx.TextCtrl(self.namepanel, pos=(155,110), size=(160, 25))
lname = wx.StaticText(self.namepanel, -1, "Last Name",pos=(30,150))
lname.SetFont(small_font)
self.lname = wx.TextCtrl(self.namepanel, pos=(155,150), size=(160, 25))
button_font = wx.Font(12, wx.DECORATIVE,wx.NORMAL,wx.BOLD)
self.add = wx.Button(self.namepanel, -1, "Confirm", pos=(110,200),size=(130, 30),style=wx.DOUBLE_BORDER)
#self.add.Bind(wx.EVT_BUTTON, self.addinfo)
self.add.SetBackgroundColour('#FFFFFF')
self.add.SetForegroundColour('#1b262c')
self.add.SetFont(button_font)
self.Sizer.Add( self.namepanel, 0, wx.CENTER|wx.ALL, 5 )
self.scroll_panel.SetupScrolling(scrollToTop=False)
class all_module(wx.Frame):
def __init__(self):
#inheritence
wx.Frame.__init__(self, None, style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN,pos=(0,0))
self.infopanel = wx.Panel(self,size=(1000,780), pos=(0, 0),style=wx.DOUBLE_BORDER)
self.registration = wx.Button(self.infopanel, -1, "Registration", pos=(0,0),size=(130, 30),style=wx.DOUBLE_BORDER)
self.registration.Bind(wx.EVT_BUTTON, self.register)
def register(self, event):
window = mainWindow(camera)
window.Show()
app = wx.App()
window = all_module()
window.Show()
app.MainLoop()
This is our code in which there is a simple registration scrolled panel and it is added in boxsizer. when i press close button i got this error that python has stopped working. but when i remove the sizer and i place all static text and textctrl out side the sizer, the program closes smoothly. But if i add in sizer i got the error i tried all the ways like self.Close() and self.Destroy() but nothing has worked. Any suggestion would be appreciated for us, Thanks in advance!!
Python has stopped working
.

how to override qscrollbar onclick default behaviour

When you click on QScrollBar's "page control area" ('c' on the image), it will scroll one page. What I want is to make it scroll to the full, just like when you choose "Scroll here" context menu item.
That's quite an interesting question. To find out an answer, we need to take a look at QScrollBar source and find out two things:
How to determine which part of the scroll bar has been clicked;
How to trigger "Scroll Here" behavior.
The answer to the first question lies in QScrollBar::mousePressEvent implementation. It turns out that QStyle::hitTestComplexControl does just what we need. What for the second question, just search "Scroll here" and you'll see that QScrollBarPrivate::pixelPosToRangeValue is used to convert event position to slider value. Unfortunately, we don't have access to functions of this private class, so we're forced to reimplement it. Now let's apply gained knowledge and implement new behavior in a subclass:
import sys
from PyQt4 import QtCore, QtGui
class ModifiedScrollBar(QtGui.QScrollBar):
def __init__(self, parent = None):
super(ModifiedScrollBar, self).__init__(parent)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
opt = QtGui.QStyleOptionSlider()
self.initStyleOption(opt)
control = self.style().hitTestComplexControl(QtGui.QStyle.CC_ScrollBar, opt,
event.pos(), self)
if (control == QtGui.QStyle.SC_ScrollBarAddPage or
control == QtGui.QStyle.SC_ScrollBarSubPage):
# scroll here
gr = self.style().subControlRect(QtGui.QStyle.CC_ScrollBar, opt,
QtGui.QStyle.SC_ScrollBarGroove, self)
sr = self.style().subControlRect(QtGui.QStyle.CC_ScrollBar, opt,
QtGui.QStyle.SC_ScrollBarSlider, self)
if self.orientation() == QtCore.Qt.Horizontal:
pos = event.pos().x()
sliderLength = sr.width()
sliderMin = gr.x()
sliderMax = gr.right() - sliderLength + 1
if (self.layoutDirection() == QtCore.Qt.RightToLeft):
opt.upsideDown = not opt.upsideDown
else:
pos = event.pos().y()
sliderLength = sr.height()
sliderMin = gr.y()
sliderMax = gr.bottom() - sliderLength + 1
self.setValue(QtGui.QStyle.sliderValueFromPosition(
self.minimum(), self.maximum(), pos - sliderMin,
sliderMax - sliderMin, opt.upsideDown))
return
return super(ModifiedScrollBar, self).mousePressEvent(event)
def main():
app = QtGui.QApplication(sys.argv)
edit = QtGui.QTextEdit()
#uncomment for testing horizontal scrollbar
#edit.setLineWrapMode(QtGui.QTextEdit.NoWrap)
edit.setPlainText("Lorem ipsum...")
edit.setVerticalScrollBar(ModifiedScrollBar())
edit.setHorizontalScrollBar(ModifiedScrollBar())
edit.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Works for me, python 3.4 pyqt 5.4.
pixelPosToRangeValue is taken from qt source
def wrapEF(ef):
w = QObject()
w.eventFilter = ef
return w
def sbEventFilter(s, e):
q = s
if (e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton
or e.type() == QEvent.MouseButtonDblClick):
#pixelPosToRangeValue(pos)
opt = QStyleOptionSlider()
q.initStyleOption(opt)
gr = q.style().subControlRect(QStyle.CC_ScrollBar, opt,
QStyle.SC_ScrollBarGroove, q)
sr = q.style().subControlRect(QStyle.CC_ScrollBar, opt,
QStyle.SC_ScrollBarSlider, q)
if q.orientation() == Qt.Horizontal:
sliderLength = sr.width()
sliderMin = gr.x()
sliderMax = gr.right() - sliderLength + 1
if q.layoutDirection() == Qt.RightToLeft:
opt.upsideDown = not opt.upsideDown
dt = sr.width()/2
pos = e.pos().x()
else:
sliderLength = sr.height()
sliderMin = gr.y()
sliderMax = gr.bottom() - sliderLength + 1
dt = sr.height()/2
pos = e.pos().y()
r = QStyle.sliderValueFromPosition(q.minimum(), q.maximum(),
pos - sliderMin - dt,
sliderMax - sliderMin, opt.upsideDown)
#pixelPosToRangeValue,
q.setValue(r)
return q.eventFilter(s, e)
self.scrollBarEF = wrapEF(sbEventFilter)
self.hscrollbar.installEventFilter(self.scrollBarEF)
self.vscrollbar.installEventFilter(self.scrollBarEF)

How do I make a QTreeView always sort items of a certain category first?

I would like to display "folders" and "files" in a QTreeView. Folders are meant to be able to contain files, and due to this relationship I wish for folder items to be displayed above file items in the tree view. The view should be sortable. How do I make sure that folder items are displayed above file items at all times in the tree view?
The below code provides an example of a tree view with folder and file items:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def _create_item(text, is_folder):
item = QStandardItem(text)
item.setData(is_folder, Qt.UserRole)
return item
def _folder_row(name, date):
return [_create_item(text, True) for text in (name, date)]
def _file_row(name, date):
return [_create_item(text, False) for text in (name, date)]
class _Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
self.__view = QTreeView()
layout = QVBoxLayout(widget)
layout.addWidget(self.__view)
self.setCentralWidget(widget)
model = QStandardItemModel()
model.appendRow(_file_row('File #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #2', '02.09.2014'))
model.appendRow(_file_row('File #2', '03.09.2014'))
model.setHorizontalHeaderLabels(['Name', 'Date'])
self.__view.setModel(model)
self.__view.setSortingEnabled(True)
app = QApplication([])
w = _Window()
w.show()
app.exec_()
A solution is to wrap the model in a QSortFilterProxyModel, and reimplement the proxy's lessThan method to make it so that folder items are always placed before file items:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def _create_item(text, is_folder):
item = QStandardItem(text)
item.setData(is_folder, Qt.UserRole)
return item
def _folder_row(name, date):
return [_create_item(text, True) for text in (name, date)]
def _file_row(name, date):
return [_create_item(text, False) for text in (name, date)]
class _SortProxyModel(QSortFilterProxyModel):
"""Sorting proxy model that always places folders on top."""
def __init__(self, model):
super().__init__()
self.setSourceModel(model)
def lessThan(self, left, right):
"""Perform sorting comparison.
Since we know the sort order, we can ensure that folders always come first.
"""
left_is_folder = left.data(Qt.UserRole)
left_data = left.data(Qt.DisplayRole)
right_is_folder = right.data(Qt.UserRole)
right_data = right.data(Qt.DisplayRole)
sort_order = self.sortOrder()
if left_is_folder and not right_is_folder:
result = sort_order == Qt.AscendingOrder
elif not left_is_folder and right_is_folder:
result = sort_order != Qt.AscendingOrder
else:
result = left_data < right_data
return result
class _Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
self.__view = QTreeView()
layout = QVBoxLayout(widget)
layout.addWidget(self.__view)
self.setCentralWidget(widget)
model = QStandardItemModel()
model.appendRow(_file_row('File #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #2', '02.09.2014'))
model.appendRow(_file_row('File #2', '03.09.2014'))
model.setHorizontalHeaderLabels(['Name', 'Date'])
proxy_model = _SortProxyModel(model)
self.__view.setModel(proxy_model)
self.__view.setSortingEnabled(True)
app = QApplication([])
w = _Window()
w.show()
app.exec_()

Custom Qt QMenu

Is there a way in Qt to add a layout or widgets to a QMenu to create a custom menu?
The example below (left) is what I have, and I'd like to aim for something similar to the mock-up on the right, by adding non-menu widgets. If it can't be done by QMenu, are there guides to produce similar results (perhaps by having a more standard widget act as a context menu) anywhere?
Sure there is! In Qt, if there is a a will there is a way.
You will probably need to make your own class that uses QMenu and uses a member QListWidget.
You will then need to generate the layout and overload all the correct QLayout functions for size recalculation.
And then you'll need to use this layout (think QHBoxLayout) to display both a QMenu and a QListWidget side by side.
That should be enough to point you in the right direction.
EDIT:
As a commenter pointed out, you can't inherit two QObject things so I updated the
answer accordingly.
To customize menu items you can use QWidgetAction class. But you want to customize menu to look like popup widget. So you may subclass QMenu and try to improve layout of menu for your needs (QMenu is QWidget). You questoin is not clear.
I wrote a script, you can try it.
but I am not subclass QMenu.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class MenuItem(QWidget):
"""docstring for MenuItem"""
def __init__(self, text='test', icon=None, parent=None):
super(MenuItem, self).__init__(parent)
hbox = QHBoxLayout(self)
# hbox.setContentsMargins(0, 0, 0, 0)
label = QLabel(text)
btn = QPushButton()
if icon:
btn.setIcon(icon)
hbox.addWidget(label)
hbox.addStretch()
hbox.addWidget(btn)
self.setMinimumWidth(parent.width())
class MyMenu(QWidget):
"""docstring for MyMenu"""
def __init__(self, parent=None):
super(MyMenu, self).__init__(parent)
self.main_width = 200
self.main_height = 150
self.close_menu = False
self.parent = parent
self.setGeometry(0, 0, 200, 150)
self.initUI()
self.setWindowFlags(Qt.Popup)
# self.setWindowModality(Qt.WindowModal)
def initUI(self):
main_frame = QWidget(self)
main_v_layout = QVBoxLayout(main_frame)
main_v_layout.setContentsMargins(0, 0, 0, 0)
item_1 = MenuItem('item 1', parent=self)
item_2 = MenuItem('item 2', parent=self)
item_3 = MenuItem('item 3', parent=self)
main_v_layout.addWidget(item_1)
main_v_layout.addWidget(item_2)
main_v_layout.addWidget(item_3)
def animationShow(self):
self.close_menu = False
self.start_close_menu = True
self.show()
# PyQt4.QtCore.QRect(0, 0, 400, 23)
rect = self.parent.rect()
# PyQt4.QtCore.QPoint(199, 11)
center_pos = rect.center()
# PyQt4.QtCore.QPoint(654, 465)
global_center_pos = self.parent.mapToGlobal(center_pos)
height = rect.height()
show_pos = QPoint(
global_center_pos.x() - (self.width() / 2),
global_center_pos.y() + height)
# print show_pos
self.move(show_pos)
self.inAnimation(show_pos)
def inAnimation(self, show_pos=None):
start_height = QSize(self.main_width, 0)
end_height = QSize(self.main_width, self.main_height)
size_anim = QPropertyAnimation(self, 'size')
size_anim.setStartValue(start_height)
size_anim.setEndValue(end_height)
size_anim.setDuration(160)
size_anim.setEasingCurve(QEasingCurve.OutQuad)
opacity_anim = QPropertyAnimation(self, 'windowOpacity')
opacity_anim.setStartValue(0.0)
opacity_anim.setEndValue(1.0)
opacity_anim.setDuration(260)
opacity_anim.setEasingCurve(QEasingCurve.OutQuad)
self.in_anim_group = QParallelAnimationGroup()
self.in_anim_group.addAnimation(size_anim)
self.in_anim_group.addAnimation(opacity_anim)
self.in_anim_group.start()
def outAnimation(self):
try:
end_size = QSize(self.size().width(), 0)
pos_anim = QPropertyAnimation(self, 'size')
pos_anim.setEndValue(end_size)
pos_anim.setDuration(200)
pos_anim.setEasingCurve(QEasingCurve.InQuad)
opacity_anim = QPropertyAnimation(self, 'windowOpacity')
opacity_anim.setStartValue(1.0)
opacity_anim.setEndValue(0.0)
opacity_anim.setDuration(200)
opacity_anim.setEasingCurve(QEasingCurve.InQuad)
self.out_anim_group = QParallelAnimationGroup()
self.out_anim_group.addAnimation(pos_anim)
self.out_anim_group.addAnimation(opacity_anim)
self.out_anim_group.finished.connect(self.closeMenu)
self.out_anim_group.start()
except RuntimeError as e:
pass
except Exception as e:
print e
def closeMenu(self):
self.close_menu = True
self.setVisible(False)
def closeEvent(self, event):
# super(MyMenu, self).closeEvent(event)
if self.start_close_menu:
self.outAnimation()
self.start_close_menu = False
def hideEvent(self, event):
# print 'hideEvent', event
super(MyMenu, self).hideEvent(event)
def setVisible(self, visible):
if self.close_menu:
visible = False
elif not visible:
visible = True
super(MyMenu, self).setVisible(visible)
class Win(QWidget):
"""docstring for Win"""
def __init__(self):
super(Win, self).__init__()
vbox = QVBoxLayout(self)
btn = QPushButton('call menu')
vbox.addWidget(btn)
self.menu = MyMenu(btn)
btn.clicked.connect(self.menu.animationShow)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Win()
win.show()
sys.exit(app.exec_())

Resources