Related
I am developing an app for memorizing text using PyQt4. I want to show all the words in bubbles so that you see how long the word is. But when I have all the bubbles in my QScrollArea, they are aligned one under the other. I would like to have them aligned side-by-side, but with word-wrap.
I got the bubbles to work using a QLabel with rounded borders. But now that I have the words in QLabel's, PyQt doesn't consider them as words - but as widgets. So PyQt puts one widget under the other. I would like the widgets to be aligned side-by-side until they reach the end of the line, and then they should wrap around to the next line - meaning the QLabel's should act like words in a text document.
Here is my code so far:
f = open(r'myFile.txt')
class Bubble(QtGui.QLabel):
def __init__(self, text):
super(Bubble, self).__init__(text)
self.word = text
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, e):
p = QtGui.QPainter(self)
p.setRenderHint(QtGui.QPainter.Antialiasing,True)
p.drawRoundedRect(0,0,self.width()-1,self.height()-1,5,5)
super(Bubble, self).paintEvent(e)
class MainWindow(QtGui.QMainWindow):
def __init__(self, text, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.MainArea = QtGui.QScrollArea
self.widget = QtGui.QWidget()
vbox = QtGui.QVBoxLayout()
self.words = []
for t in re.findall(r'\b\w+\b', text):
label = Bubble(t)
label.setFont(QtGui.QFont('SblHebrew', 18))
label.setFixedWidth(label.sizeHint().width())
self.words.append(label)
vbox.addWidget(label)
self.widget.setLayout(vbox)
self.MainArea.setWidget(self.widget)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
myWindow = MainWindow(f.read(), None)
myWindow.show()
sys.exit(app.exec_())
When I run this I get:
But I would like the words (the Qlabel's containing the words) to be next to each other, not under each other, like this (photoshopped):
I've been doing a lot of research, but no answers help me align the widgets next to each other.
Here's a PyQt5 version of the Flow Layout demo script:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class FlowLayout(QtWidgets.QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
super(FlowLayout, self).__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
self._items = []
self.setContentsMargins(margin, margin, margin, margin)
def __del__(self):
del self._items[:]
def addItem(self, item):
self._items.append(item)
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
return self.smartSpacing(
QtWidgets.QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
return self.smartSpacing(
QtWidgets.QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
def itemAt(self, index):
if 0 <= index < len(self._items):
return self._items[index]
def takeAt(self, index):
if 0 <= index < len(self._items):
return self._items.pop(index)
def expandingDirections(self):
return QtCore.Qt.Orientations(0)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.doLayout(QtCore.QRect(0, 0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QtCore.QSize()
for item in self._items:
size = size.expandedTo(item.minimumSize())
left, top, right, bottom = self.getContentsMargins()
size += QtCore.QSize(left + right, top + bottom)
return size
def doLayout(self, rect, testonly):
left, top, right, bottom = self.getContentsMargins()
effective = rect.adjusted(+left, +top, -right, -bottom)
x = effective.x()
y = effective.y()
lineheight = 0
for item in self._items:
widget = item.widget()
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Horizontal)
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Vertical)
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x()
y = y + lineheight + vspace
nextX = x + item.sizeHint().width() + hspace
lineheight = 0
if not testonly:
item.setGeometry(
QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineheight = max(lineheight, item.sizeHint().height())
return y + lineheight - rect.y() + bottom
def smartSpacing(self, pm):
parent = self.parent()
if parent is None:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()
class Bubble(QtWidgets.QLabel):
def __init__(self, text):
super(Bubble, self).__init__(text)
self.word = text
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawRoundedRect(
0, 0, self.width() - 1, self.height() - 1, 5, 5)
super(Bubble, self).paintEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, text, parent=None):
super(MainWindow, self).__init__(parent)
self.mainArea = QtWidgets.QScrollArea(self)
self.mainArea.setWidgetResizable(True)
widget = QtWidgets.QWidget(self.mainArea)
widget.setMinimumWidth(50)
layout = FlowLayout(widget)
self.words = []
for word in text.split():
label = Bubble(word)
label.setFont(QtGui.QFont('SblHebrew', 18))
label.setFixedWidth(label.sizeHint().width())
self.words.append(label)
layout.addWidget(label)
self.mainArea.setWidget(widget)
self.setCentralWidget(self.mainArea)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow('Harry Potter is a series of fantasy literature')
window.show()
sys.exit(app.exec_())
I thought it might be possible to use html in a QTextBrowser widget for this, but Qt's rich-text engine doesn't support the border-radius CSS property which would be needed for the bubble labels.
So it looks like you need a PyQt port of the Flow Layout example. This can "word-wrap" a collection of widgets inside a container, and also allows the margins and horizontal/vertical spacing to be adjusted.
Here is a demo script that implements the FlowLayout class and shows how to use it with your example:
import sys
from PyQt4 import QtCore, QtGui
class FlowLayout(QtGui.QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
super(FlowLayout, self).__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
self._items = []
self.setContentsMargins(margin, margin, margin, margin)
def __del__(self):
del self._items[:]
def addItem(self, item):
self._items.append(item)
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
return self.smartSpacing(
QtGui.QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
return self.smartSpacing(
QtGui.QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
def itemAt(self, index):
if 0 <= index < len(self._items):
return self._items[index]
def takeAt(self, index):
if 0 <= index < len(self._items):
return self._items.pop(index)
def expandingDirections(self):
return QtCore.Qt.Orientations(0)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.doLayout(QtCore.QRect(0, 0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QtCore.QSize()
for item in self._items:
size = size.expandedTo(item.minimumSize())
left, top, right, bottom = self.getContentsMargins()
size += QtCore.QSize(left + right, top + bottom)
return size
def doLayout(self, rect, testonly):
left, top, right, bottom = self.getContentsMargins()
effective = rect.adjusted(+left, +top, -right, -bottom)
x = effective.x()
y = effective.y()
lineheight = 0
for item in self._items:
widget = item.widget()
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QtGui.QSizePolicy.PushButton,
QtGui.QSizePolicy.PushButton, QtCore.Qt.Horizontal)
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QtGui.QSizePolicy.PushButton,
QtGui.QSizePolicy.PushButton, QtCore.Qt.Vertical)
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x()
y = y + lineheight + vspace
nextX = x + item.sizeHint().width() + hspace
lineheight = 0
if not testonly:
item.setGeometry(
QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineheight = max(lineheight, item.sizeHint().height())
return y + lineheight - rect.y() + bottom
def smartSpacing(self, pm):
parent = self.parent()
if parent is None:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()
class Bubble(QtGui.QLabel):
def __init__(self, text):
super(Bubble, self).__init__(text)
self.word = text
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawRoundedRect(
0, 0, self.width() - 1, self.height() - 1, 5, 5)
super(Bubble, self).paintEvent(event)
class MainWindow(QtGui.QMainWindow):
def __init__(self, text, parent=None):
super(MainWindow, self).__init__(parent)
self.mainArea = QtGui.QScrollArea(self)
self.mainArea.setWidgetResizable(True)
widget = QtGui.QWidget(self.mainArea)
widget.setMinimumWidth(50)
layout = FlowLayout(widget)
self.words = []
for word in text.split():
label = Bubble(word)
label.setFont(QtGui.QFont('SblHebrew', 18))
label.setFixedWidth(label.sizeHint().width())
self.words.append(label)
layout.addWidget(label)
self.mainArea.setWidget(widget)
self.setCentralWidget(self.mainArea)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow('Harry Potter is a series of fantasy literature')
window.show()
sys.exit(app.exec_())
I want to move the ligne of labels to the left by adding to their coordinate 100 every time I push a specific button.
I tied to call the show tab function inside the move_ligne_one() function but I cant refer to it
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'Simple window'
self.left = 100
self.top = 100
self.width = 1100
self.height = 800
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.setWindowIcon(QIcon("img/icon.png"))
def show_tab(left_cord):
ligne_1_left_cord = left_cord
label_ligne_1_1 = QtWidgets.QLabel(self)
#pic_signe_x =QPixmap('img/x.png')
#label_ligne_1_1.setPixmap(pic_signe_x)
label_ligne_1_1.setText("X")
label_ligne_1_1.move(ligne_1_left_cord,300)
label_ligne_1_2 = QtWidgets.QLabel(self)
#pic_signe_x =QPixmap('img/x.png')
#label_ligne_1_1.setPixmap(pic_signe_x)
label_ligne_1_2.setText("X")
label_ligne_1_2.move( ligne_1_left_cord +200,300)
label_ligne_1_3 = QtWidgets.QLabel(self)
#pic_signe_x =QPixmap('img/x.png')
#label_ligne_1_1.setPixmap(pic_signe_x)
label_ligne_1_3.setText("X")
label_ligne_1_3.move( ligne_1_left_cord +400,300)
label_ligne_1_4 = QtWidgets.QLabel(self)
#pic_signe_x =QPixmap('img/x.png')
#label_ligne_1_1.setPixmap(pic_signe_x)
label_ligne_1_4.setText("X")
label_ligne_1_4.move( ligne_1_left_cord +600,300)
buttonStart = QPushButton('START', self)
buttonStart.setGeometry(380, 700, 250, 61)
buttonStart.clicked.connect(self.on_click)
button_left_1 = QPushButton('<', self)
button_left_1.setGeometry(45,300,30,30)
button_left_1.clicked.connect(self.move_ligne_one)
show_tab(275)
self.show()
#pyqtSlot()
def move_ligne_one(self):
print(' button click')
If you want to move a widget then the first thing is to access that widget so you have to do it class attribute:
# ...
self.label_ligne_1_1 = QtWidgets.QLabel(self)
# ...
self.label_ligne_1_2 = QtWidgets.QLabel(self)
# ...
self.label_ligne_1_3 = QtWidgets.QLabel(self)
# ...
self.label_ligne_1_4 = QtWidgets.QLabel(self)
# ...
And then modify the position:
#pyqtSlot()
def move_ligne_one(self):
for btn in (
self.label_ligne_1_1,
self.label_ligne_1_2,
self.label_ligne_1_3,
self.label_ligne_1_4,
):
p = btn.pos()
p += QtCore.QPoint(100, 0)
btn.move(p)
user can draw rectangles by this application by giving clicking twice on grey picture. But this application dont saves last rectangles, instead of saving its drawing(updateing) a new rectangle by 2 next points. how i can solve this problem? how can i save previouse rectangles too?
class myQLabel(QLabel):
def __init__(self,parent):
super(myQLabel, self).__init__(parent)
self.x = 0
self.y = 0
self.trafficlines = []
def mousePressEvent(self, QMouseEvent):
#print mode
self.x = QMouseEvent.x()
self.y = QMouseEvent.y()
if self.x != 0 and self.y != 0:
self.trafficlines.append(copy.deepcopy([self.x,self.y]))
print "______"
print self.x
print self.y
print "("+str(mode)+")"
print "______"
def paintEvent(self, QPaintEvent):
super(myQLabel, self).paintEvent(QPaintEvent)
painter = QPainter(self)
if mode == 0:
painter.setPen(QPen(Qt.red,3))
elif mode == 1:
painter.setPen(QPen(Qt.blue,3))
elif mode == 2:
painter.setPen(QPen(Qt.green,3))
elif mode == 3:
painter.setPen(QPen(Qt.magenta,3))
if len(self.trafficlines)%2==1 and len(self.trafficlines)>0:
painter.drawPoint(self.trafficlines[-1][0],self.trafficlines[-1][1])
if len(self.trafficlines)%2==0 and len(self.trafficlines)>0 and mode!=0:
painter.drawLine( self.trafficlines[-2][0],self.trafficlines[-2][1],self.trafficlines[-1][0],self.trafficlines[-1][1] )
if len(self.trafficlines)%2==0 and len(self.trafficlines)>0 and mode==0:
x1=self.trafficlines[-2][0]
y1=self.trafficlines[-2][1]
x2=self.trafficlines[-1][0]
y2=self.trafficlines[-1][1]
painter.drawLine( x1,y1,x1,y2)
painter.drawLine( x1,y2,x2,y2)
painter.drawLine( x2,y2,x2,y1)
painter.drawLine( x2,y1,x1,y1)
self.update()
This is all the code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys, os
import copy
mode = 5
class Example(QWidget):
def __init__(self,parent):
super(Example, self).__init__()
self.main_image_name="C:\Python27\project\main_image.png"
self.initUI()
def initUI(self):
File_name = QLabel('Setup file name')
File_name_edit = QLineEdit()
QToolTip.setFont(QFont('SansSerif', 10))
#QMainWindow.statusBar().showMessage('Ready')
self.setGeometry(300, 300, 250, 150)
self.resize(640, 360)
#self.setFixedSize(640, 360)
self.center()
self.main_image = myQLabel(self)
self.main_image.setPixmap(QPixmap(self.main_image_name))
btn = QPushButton("Make setup file")
btn.setToolTip('Press <b>Detect</b> button for detecting objects by your settings')
btn.resize(btn.sizeHint())
btn.clicked.connect(QCoreApplication.instance().quit)
btn_browse = QPushButton("Browse")
btn_browse.clicked.connect(self.browse)
btn_set = QPushButton("Set name")
#fullscreen
#self.main_image.setScaledContents(True)
#just centered
self.main_image.setAlignment(Qt.AlignCenter)
#Layout
box_File_name = QHBoxLayout()
box_File_name.addWidget(File_name)
box_File_name.addWidget(File_name_edit)
box_File_name.addWidget(btn_set)
grid = QGridLayout()
grid.setSpacing(10)
grid.addLayout(box_File_name, 1, 0)
#grid.addWidget(File_name_edit, 1, 1)
grid.addWidget(self.main_image, 2, 0)
grid.addWidget(btn_browse, 3 , 0)
grid.addWidget(btn, 4, 0)
box_number = QVBoxLayout()
number_group=QButtonGroup() # Number group
r0=QRadioButton("Traffic Lights")
number_group.addButton(r0)
r1=QRadioButton("Direction")
number_group.addButton(r1)
r2=QRadioButton("Traffic Lines H")
number_group.addButton(r2)
r3=QRadioButton("Traffic Lines V")
number_group.addButton(r3)
box_number.addWidget(r0)
box_number.addWidget(r1)
box_number.addWidget(r2)
box_number.addWidget(r3)
r0.toggled.connect(self.radio0_clicked)
r1.toggled.connect(self.radio1_clicked)
r2.toggled.connect(self.radio2_clicked)
r3.toggled.connect(self.radio3_clicked)
box_road_sign = QHBoxLayout()
road_sign_label = QLabel('Road signs', self)
road_sign = QComboBox()
road_sign.addItem("None")
road_sign.addItem("ex1")
road_sign.addItem("ex2")
road_sign.addItem("ex3")
road_sign.addItem("ex4")
road_sign.addItem("ex5")
box_road_sign.addWidget(road_sign_label)
box_road_sign.addWidget(road_sign)
grid.addLayout(box_road_sign, 1, 1)
grid.addLayout(box_number, 2, 1)
self.setLayout(grid)
self.show()
def browse(self):
w = QWidget()
w.resize(320, 240)
w.setWindowTitle("Select Picture")
filename = QFileDialog.getOpenFileName(w, 'Open File', '/')
self.main_image_name = filename
self.main_image.setPixmap(QPixmap(self.main_image_name))
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def radio0_clicked(self, enabled):
if enabled:
print("0")
global mode
mode=0
def radio1_clicked(self, enabled):
if enabled:
print("1")
global mode
mode=1
def radio2_clicked(self, enabled):
if enabled:
print("2")
global mode
mode=2
def radio3_clicked(self, enabled):
if enabled:
print("3")
global mode
mode=3
class myQLabel(QLabel):
def __init__(self,parent):
super(myQLabel, self).__init__(parent)
self.x = 0
self.y = 0
self.trafficlines = []
def mousePressEvent(self, QMouseEvent):
#print mode
self.x = QMouseEvent.x()
self.y = QMouseEvent.y()
if self.x != 0 and self.y != 0:
self.trafficlines.append(copy.deepcopy([self.x,self.y]))
print "______"
print self.x
print self.y
print "("+str(mode)+")"
print "______"
def paintEvent(self, QPaintEvent):
super(myQLabel, self).paintEvent(QPaintEvent)
painter = QPainter(self)
if mode == 0:
painter.setPen(QPen(Qt.red,3))
elif mode == 1:
painter.setPen(QPen(Qt.blue,3))
elif mode == 2:
painter.setPen(QPen(Qt.green,3))
elif mode == 3:
painter.setPen(QPen(Qt.magenta,3))
if len(self.trafficlines)%2==1 and len(self.trafficlines)>0:
painter.drawPoint(self.trafficlines[-1][0],self.trafficlines[-1][1])
if len(self.trafficlines)%2==0 and len(self.trafficlines)>0 and mode!=0:
painter.drawLine( self.trafficlines[-2][0],self.trafficlines[-2][1],self.trafficlines[-1][0],self.trafficlines[-1][1] )
if len(self.trafficlines)%2==0 and len(self.trafficlines)>0 and mode==0:
x1=self.trafficlines[-2][0]
y1=self.trafficlines[-2][1]
x2=self.trafficlines[-1][0]
y2=self.trafficlines[-1][1]
painter.drawLine( x1,y1,x1,y2)
painter.drawLine( x1,y2,x2,y2)
painter.drawLine( x2,y2,x2,y1)
painter.drawLine( x2,y1,x1,y1)
self.update()
class menubarex(QMainWindow):
def __init__(self, parent=None):
super(menubarex, self).__init__(parent)
self.form_widget = Example(self)
self.setCentralWidget(self.form_widget)
self.initUI()
def initUI(self):
exitAction = QAction(QIcon('exit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(qApp.quit)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
#self.toolbar = self.addToolBar('Exit')
#self.toolbar.addAction(exitAction)
self.statusBar().showMessage('Ready')
self.setWindowTitle('mi ban')
self.setWindowIcon(QIcon('C:\Python27\project\icon.png'))
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QApplication(sys.argv)
#ex = Example()
menubar = menubarex()
menubar.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
paintEvent() redraws the entire widget, so it does not save memory from the previous drawing, so the rectangles or previous lines are not saved. The solution is to store those states and redraw everything again, for this we can store the mode and points in trafficlines as shown below:
class myQLabel(QLabel):
def __init__(self,parent):
super(myQLabel, self).__init__(parent)
self.trafficlines = []
self.mode = 0
self.start_point = QPoint()
def setMode(self, mode):
self.mode = mode
def mousePressEvent(self, event):
if self.start_point.isNull():
self.start_point = event.pos()
else:
self.trafficlines.append((self.mode,[self.start_point, event.pos()]))
self.start_point = QPoint()
self.update()
def paintEvent(self, event):
super(myQLabel, self).paintEvent(event)
painter = QPainter(self)
colors = [Qt.red, Qt.blue, Qt.green, Qt.magenta]
for mode, points in self.trafficlines:
painter.setPen(QPen(colors[mode],3))
if mode != 0:
painter.drawLine(*points)
else:
rect = QRect(*points)
painter.drawRect(rect)
if not self.start_point.isNull():
painter.setPen(QPen(colors[self.mode],3))
painter.drawPoint(self.start_point)
Note: do not use global variables, they are difficult to debug, in addition to unnecessary many times.
Also I take the liberty to improve your code by making it more readable with fewer lines. The complete code is in the following part:
import sys
import os
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Example(QWidget):
def __init__(self,parent):
super(Example, self).__init__()
self.main_image_name="C:\Python27\project\main_image.png"
self.initUI()
def initUI(self):
File_name = QLabel('Setup file name')
File_name_edit = QLineEdit()
self.resize(640, 360)
self.center()
self.main_image = myQLabel(self)
self.main_image.setPixmap(QPixmap(self.main_image_name))
btn = QPushButton("Make setup file")
btn.setToolTip('Press <b>Detect</b> button for detecting objects by your settings')
btn.resize(btn.sizeHint())
btn.clicked.connect(QCoreApplication.instance().quit)
btn_browse = QPushButton("Browse")
btn_browse.clicked.connect(self.browse)
btn_set = QPushButton("Set name")
self.main_image.setAlignment(Qt.AlignCenter)
#Layout
box_File_name = QHBoxLayout()
box_File_name.addWidget(File_name)
box_File_name.addWidget(File_name_edit)
box_File_name.addWidget(btn_set)
grid = QGridLayout(self)
grid.setSpacing(10)
grid.addLayout(box_File_name, 1, 0)
#grid.addWidget(File_name_edit, 1, 1)
grid.addWidget(self.main_image, 2, 0)
grid.addWidget(btn_browse, 3 , 0)
grid.addWidget(btn, 4, 0)
box_number = QVBoxLayout()
number_group = QButtonGroup(self) # Number group
for i, text in enumerate(["Traffic Lights", "Direction", "Traffic Lines H", "Traffic Lines V"]):
rb = QRadioButton(text)
box_number.addWidget(rb)
number_group.addButton(rb, i)
number_group.buttonClicked[int].connect(self.main_image.setMode)
number_group.button(0).setChecked(True)
box_road_sign = QHBoxLayout()
road_sign_label = QLabel('Road signs', self)
road_sign = QComboBox()
road_sign.addItems(["None", "ex1", "ex2","ex3", "ex4", "ex5"])
box_road_sign.addWidget(road_sign_label)
box_road_sign.addWidget(road_sign)
grid.addLayout(box_road_sign, 1, 1)
grid.addLayout(box_number, 2, 1)
def browse(self):
filename = QFileDialog.getOpenFileName(self, 'Open File', '/')
self.main_image_name = filename
self.main_image.setPixmap(QPixmap(self.main_image_name))
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
class myQLabel(QLabel):
def __init__(self,parent):
super(myQLabel, self).__init__(parent)
self.trafficlines = []
self.mode = 0
self.start_point = QPoint()
def setMode(self, mode):
self.mode = mode
def mousePressEvent(self, event):
if self.start_point.isNull():
self.start_point = event.pos()
else:
self.trafficlines.append((self.mode,[self.start_point, event.pos()]))
self.start_point = QPoint()
self.update()
def paintEvent(self, event):
super(myQLabel, self).paintEvent(event)
painter = QPainter(self)
colors = [Qt.red, Qt.blue, Qt.green, Qt.magenta]
for mode, points in self.trafficlines:
painter.setPen(QPen(colors[mode],3))
if mode != 0:
painter.drawLine(*points)
else:
rect = QRect(*points)
painter.drawRect(rect)
if not self.start_point.isNull():
painter.setPen(QPen(colors[self.mode],3))
painter.drawPoint(self.start_point)
class menubarex(QMainWindow):
def __init__(self, parent=None):
super(menubarex, self).__init__(parent)
self.form_widget = Example(self)
self.setCentralWidget(self.form_widget)
self.initUI()
def initUI(self):
exitAction = QAction(QIcon('exit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(qApp.quit)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
self.statusBar().showMessage('Ready')
self.setWindowTitle('mi ban')
self.setWindowIcon(QIcon('C:\Python27\project\icon.png'))
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QApplication(sys.argv)
#ex = Example()
menubar = menubarex()
menubar.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
So I am building a node based interface using PyQt for a project I am working on and I am having some issues getting objects that belong to the base not to follow it in space. I would like when the user drags the base node, the child objects (inputs and output boxes) to follow it. I have a drag-able node that works but the child objects are not following properly. Any ideas?
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is the base py file for the GUI
"""
import sys
from PyQt4 import QtGui, QtCore
from array import *
"""
Base class for a node. Contains all the initialization, drawing, and containing inputs and outputs
"""
class node():
width = 100
height = 100
color = 1
x = 90
y = 60
inputs=[]
outputs=[]
def __init__(self, nWidth, nHeight):
self.width = nWidth
self.height = nHeight
self.iniNodeData()
"""
This is where inputs and outputs will be created
"""
def iniNodeData(self):
for j in range(5):
this = self
x = input(this,90, 0+(j*10))
self.inputs.append(x)
"""Draw the node then the input and output objects"""
def draw(self, drawObj):
item = drawObj.addRect(self.x, self.y, self.width, self.height)
item.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
for curInput in self.inputs:
curInput.draw(drawObj)
print("(", self.x, ",", self.y, ")")
"""
Nodes will evaluate from the last node to the first node, therefore inputs are evaluted
"""
class input():
currentConnectedNode = None
currentConnectedOutput = None
parentNode = None
width = 10
height = 10
x = 1
y = 1
color = 1
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
def draw(self, drawObj):
item = drawObj.addRect(self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height)
class output():
parentNode = None
class MainWindow(QtGui.QGraphicsView):
nodes = []
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
for j in range(1):
x = node(100,100)
self.nodes.append(x)
self.setScene(QtGui.QGraphicsScene())
self.setWindowTitle('RIS RIB Generator')
self.setGeometry(800, 600, 800, 850)
self.initNodes()
self.show()
def initNodes(self):
for curNode in self.nodes:
curNode.draw(self.scene())
def main():
app = QtGui.QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
if __name__ == '__main__':
main()
Ok so after a week I figured it out. You need to do a few things.
ensure the flags are correct:
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
Once those are set you can use the built in event handelers but these over ride the original ones. so from inside your user defined one, you need to call the event handeler from the base class. Example:
def mousePressEvent(self, e):
print("Square got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
Here is my working example of my progress.
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is the base py file for the GUI
Todo list
-----------------
- Pop up menu for adding new Nodes
- node connectivity
- create data structure for storing
"""
import sys
from PyQt4 import QtGui, QtCore
from array import *
"""
Base class for a node. Contains all the inilization, drawing, and containing inputs and outputs
"""
class node(QtGui.QGraphicsRectItem):
width = 100
height = 100
color = 1
x = 90
y = 60
inputs=[]
outputs=[]
viewObj = None
def __init__(self, n_x, n_y, n_width,n_height):
QtGui.QGraphicsRectItem.__init__(self, n_x, n_y, n_width, n_height)
self.width = n_width
self.height = n_height
self.x = n_x
self.y = n_y
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.iniNodeData()
def mousePressEvent(self, e):
print("Square got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
print("Square got mouse release event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsRectItem.mouseReleaseEvent(self, e)
"""
This is where inputs and outputs will be created based on node type
"""
def iniNodeData(self):
print('making node data')
for j in range(5):
this = self
x = input(this,0, 0+(j*10))
self.inputs.append(x)
for k in range(5):
this = self
x = output(this,self.x+self.width, self.y+(k*10))
self.outputs.append(x)
def mouseMoveEvent(self, event):
print('Dragging#')
QtGui.QGraphicsRectItem.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
print('moving!')
"""
Nodes will evaluate from the last node to the first node, therefore inputs are evaluted
"""
class input(QtGui.QGraphicsRectItem):
currentConnectedNode = None
currentConnectedOutput = None
parentNode = None
width = 10
height = 10
x = 1
y = 1
color = 1
drawItem = None
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
QtGui.QGraphicsRectItem.__init__(self, self.x+self.parentNode.x, self.y+self.parentNode.y, self.width, self.height, self.parentNode)
'''
Output value from a node
'''
class output(node):
parentNode = None
width = 10
height = 10
x = 1
y = 1
def __init__(self, pnode, posX, posY):
self.parentNode = pnode
self.x = posX
self.y = posY
self.color = 1
QtGui.QGraphicsRectItem.__init__(self, self.x-self.width, self.y, self.width, self.height, self.parentNode)
'''
Check Click events on the scene Object
'''
class Scene(QtGui.QGraphicsScene):
nodes = []
def mousePressEvent(self, e):
print("Scene got mouse press event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mousePressEvent(self, e)
def mouseReleaseEvent(self, e):
print("Scene got mouse release event")
print("Event came to us accepted: %s"%(e.isAccepted(),))
QtGui.QGraphicsScene.mouseReleaseEvent(self, e)
def dragMoveEvent(self, e):
print('Scene got drag move event')
def addNode(self):
newNode = self.addItem(node(250,250,100,150))
self.nodes.append(newNode)
'''
Main Window Object
'''
class MainWindowUi(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('RIS RIB Generator')
self.scene = Scene(0, 0, 800, 850, self)
self.view = QtGui.QGraphicsView()
self.setCentralWidget(self.view)
self.view.setScene(self.scene)
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
newNodeAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'New Node', self)
newNodeAction.setStatusTip('Add a blank node')
newNodeAction.triggered.connect(self.scene.addNode)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(newNodeAction)
fileMenu.addAction(exitAction)
'''
Start Point
'''
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindowUi()
win.show()
sys.exit(app.exec_())
I present here a simple pyqt code, but I developed my whole design and algo upon this. The problem, is that PyQt is unable to manage a large number of objects when created. This is simple code for drawing many rectangles inside a big one. And it has Zoom and pan feature too.
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
item = None
class Rectangle(QGraphicsItem):
def __init__(self, parent, x, y, width, height, scene = None, fill=None):
self.parent = parent
super(Rectangle, self).__init__()
self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
rect = QRectF(x,y,width,height)
self.dimen = [x,y,width,height]
self.rect = rect
scene.addItem(self)
self.show_its_ui()
#Function to get relative position
def get_relative_location(self,loc):
self.loc = QPointF(loc[0],loc[1])
global item
self.loc = self.mapFromItem(item, self.loc)
self.loc = (self.loc.x(),self.loc.y())
self.loc = list(self.loc)
self.dimen[0] = self.loc[0]
self.dimen[1] = self.loc[1]
#Showing its UI in the form of a rectangle
def show_its_ui(self):
#First gets its relative location and set it.
if(item is not None):
self.get_relative_location([self.dimen[0],self.dimen[1]])
#Make a Final rectangle
rect = QRectF(self.dimen[0],self.dimen[1],self.dimen[2],self.dimen[3])
self.rect = rect
def boundingRect(self):
return self.rect.adjusted(-2, -2, 2, 2)
def parentWidget(self):
return self.scene().views()[0]
def paint(self, painter, option, widget):
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setBrush(QtGui.QColor(255, 50, 90, 200))
painter.drawRect(self.rect)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
def wheelEvent(self, event):
factor = 1.41 ** (event.delta() / 240.0)
self.scale(factor, factor)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.Width = 800
self.Height = 500
self.view = GraphicsView()
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, self.Width, self.Height)
self.view.setScene(self.scene)
self.initUI()
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.view)
mainWidget = QtGui.QWidget()
mainWidget.setLayout(hbox)
self.setCentralWidget(mainWidget)
def initUI(self):
self.group = QGraphicsItemGroup()
self.group.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
global item
item = self.group
self.group.setFiltersChildEvents(True)
self.group.setHandlesChildEvents(False)
self._link1 = Rectangle(self, 10, 10, self.Width, self.Height,self.scene)
self._link1.setFiltersChildEvents(True)
self._link1.setHandlesChildEvents(False)
self.group.addToGroup(self._link1)
self.scene.addItem(self.group)
self.addObjects()
#Here I added objects in the big canvas
def addObjects(self):
xpos = 20
ypos = 20
#Change the below to 5 each
width = 50
height = 50
#Generate many rectangles
while(ypos < 450):
xpos = 20
while(xpos < 750):
t = Rectangle(self._link1,xpos,ypos,width,height,self.scene,True)
self.group.addToGroup(t)
xpos += (width+2)
ypos += (height+2)
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
method = mainWindow
mainWindow.show()
sys.exit(app.exec_())
Try changing, the width and height values present in addObjects function of the mainwindow class, say to 5 and 5 respectively from 50. Then the whole application run at very slow speed, when you pan/zoom. Please share your suggestions in rectifying this.