I am trying to force my QTextEdit widget to span the whole cell of the QGridLayout.
I tried a lot of different combinations of QSizePolicy and sizeHint(), but nothing has the desired effect.
In the example below I have a main widget of minimum size 800x600 and another widget 'blue' of size 100x100 in column 1. So I want the QTextEdit in column 0 to be 700x600.
In general I want 'edit' to be (n-blue.width())xm if my main widget is resized to nxm.
import sys
from PyQt5 import QtWidgets, QtCore
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
mainWidget = QtWidgets.QWidget()
mainWidget.setMinimumSize(800, 600)
mainLayout = QtWidgets.QGridLayout(mainWidget)
blue = QtWidgets.QWidget(mainWidget)
blue.setStyleSheet('background-color: blue')
blue.setFixedSize(100, 100)
edit = QtWidgets.QTextEdit(mainWidget)
### what to do here?
policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
edit.setSizePolicy(policy)
###
mainLayout.addWidget(edit, 0, 0, 1, 1, QtCore.Qt.AlignCenter)
mainLayout.addWidget(blue, 0, 1, 1, 1, QtCore.Qt.AlignCenter)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 0)
mainWidget.setLayout(mainLayout)
mainWidget.show()
sys.exit(qApp.exec_())
I think you're just placing too many constraints on the QGridLayout. Try adding the widgets to the layout with just...
mainLayout.addWidget(edit, 0, 0)
mainLayout.addWidget(blue, 0, 1)
Also, you probably don't need to set the sizing policy explicitly -- it should just work as-is (it does for me).
The full, minimal working example would be something like...
import sys
from PyQt5 import QtWidgets, QtCore
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
mainWidget = QtWidgets.QWidget()
mainWidget.setMinimumSize(800, 600)
mainLayout = QtWidgets.QGridLayout(mainWidget)
blue = QtWidgets.QWidget(mainWidget)
blue.setStyleSheet('background-color: blue')
blue.setFixedSize(100, 100)
edit = QtWidgets.QTextEdit(mainWidget)
mainLayout.addWidget(edit, 0, 0)
mainLayout.addWidget(blue, 0, 1)
mainWidget.show()
sys.exit(qApp.exec_())
Related
I am trying to create a kivy application with live graph using 'garden graph'. There seems a bug which offset the screen to left while updating the graph. What could be the possible reason?
graph.py
from kivy.config import Config
Config.set('graphics', 'width', '1024')
Config.set('graphics', 'height', '600')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.clock import Clock, mainthread
from kivy.uix.screenmanager import ScreenManager, Screen
"""
Graph References:
https://github.com/kivy-garden/graph
https://stackoverflow.com/questions/30821127/using-kivy-garden-graph-in-kv-language#answer-46522799
"""
from math import sin
import random
from kivy_garden.graph import Graph, MeshLinePlot
"""
Class to manage Testing Screen
"""
class WindowGraph(Screen, Widget):
def __init__(self, **kw):
super().__init__(**kw)
self.graph = None
self.plot = None
self.x = 101
self.generate_graph()
# Clock.schedule_interval(self.update_graph, 1/10)
#mainthread
def generate_graph(self):
self.graph = self.ids['graph_test']
self.plot = MeshLinePlot(color=[1, 0, 0, 1])
# self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
self.graph.add_plot(self.plot)
#mainthread
def update_graph(self, *args):
self.plot.points.append((self.x, random.random()))
self.graph.xmin = max(self.x - 50, 0)
self.graph.xmax = self.x + 1
self.x += 10
def control_graph(self):
self.update_graph()
"""
Class to manage all screens
"""
class WindowManager(ScreenManager):
pass
# Load kv builder file
kv = Builder.load_file("graph.kv")
class CTControlApp(App):
def build(self):
Window.clearcolor = (205/255, 205/255, 205/255, 1)
return kv
CTControlApp().run()
graph.kv
WindowManager:
WindowGraph:
<WindowGraph>
name: "layout_graph"
FloatLayout:
size: root.width, root.height
canvas.before :
Color:
rgba: 1, 1, 1, 1
Line:
width: 0.5
rectangle: self.x, self.y, self.width, self.height
Label:
text: "TESTING"
font_size : 22
pos_hint : {"x":0.01369*14, "y":0.03448*27}
size_hint:0.01369*45, 0.03448*2
background_color :1, 1, 1, 0
color : 1, 1, 1, 1
Graph:
id: graph_test
xlabel:'X'
ylabel:'Y'
x_ticks_minor:5
x_tics_major:25
y_ticks_major:1
y_grid_label:True
x_grid_label:True
padding:5
x_grid:True
y_grid:True
xmin:-0
xmax:100
ymin:-1
ymax:1
pos_hint: {"x":0.01369*2, "y":0.03448*2}
size_hint: 0.01369*56, 0.03448*25
Button :
id: button_graph_control
text: "UPDATE"
on_press: root.control_graph()
font_size : 24
pos_hint : {"x":0.01369*60, "y":0.03448*10}
size_hint:0.01369*10, 0.03448*3
background_color :0.5, 0.5, 0.5, 0.3
color : 1, 1, 1, 1
Screen before updating graph
Screen after updating graph
Please help me resolve this issue dear friends
I have an application built on pyqt6 and wanted to add a feature ( displaying a panorama image),
so I found this code on the internet to display panorama image using pyqt5.
can someone help me convert it to pyqt6?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Panoramic(QtWidgets.QWidget):
def __init__(self, imagePath):
QtWidgets.QWidget.__init__(self)
self.setCursor(QtCore.Qt.CrossCursor)
# keep a reference of the original image
self.source = QtGui.QPixmap(imagePath)
self.pano = QtGui.QPixmap(self.source.width() * 3, self.source.height())
self.center = self.pano.rect().center()
# use a QPointF for precision
self.delta = QtCore.QPointF()
self.deltaTimer = QtCore.QTimer(interval=25, timeout=self.moveCenter)
self.sourceRect = QtCore.QRect()
# create a pixmap with three copies of the source;
# this could be avoided by smart repainting and translation of the source
# but since paintEvent automatically clips the painting, it should be
# faster then computing the new rectangle each paint cycle, at the cost
# of a few megabytes of memory.
self.setMaximumSize(self.source.size())
qp = QtGui.QPainter(self.pano)
qp.drawPixmap(0, 0, self.source)
qp.drawPixmap(self.source.width(), 0, self.source)
qp.drawPixmap(self.source.width() * 2, 0, self.source)
qp.end()
def moveCenter(self):
if not self.delta:
return
self.center += self.delta
# limit the vertical position
if self.center.y() < self.sourceRect.height() * .5:
self.center.setY(self.sourceRect.height() * .5)
elif self.center.y() > self.source.height() - self.height() * .5:
self.center.setY(self.source.height() - self.height() * .5)
# reset the horizontal position if beyond the center of the virtual image
if self.center.x() < self.source.width() * .5:
self.center.setX(self.source.width() * 1.5)
elif self.center.x() > self.source.width() * 2.5:
self.center.setX(self.source.width() * 1.5)
self.sourceRect.moveCenter(self.center.toPoint())
self.update()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() != QtCore.Qt.LeftButton:
return
delta = event.pos() - self.mousePos
# use a fraction to get small movements, and ensure we're not too fast
self.delta.setX(max(-25, min(25, delta.x() * .125)))
self.delta.setY(max(-25, min(25, delta.y() * .125)))
if not self.deltaTimer.isActive():
self.deltaTimer.start()
def mouseReleaseEvent(self, event):
self.deltaTimer.stop()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.drawPixmap(self.rect(), self.pano, self.sourceRect)
# resize and reposition the coordinates whenever the window is resized
def resizeEvent(self, event):
self.sourceRect.setSize(self.size())
self.sourceRect.moveCenter(self.center)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Panoramic('pano5.jpg')
w.show()
sys.exit(app.exec_())
Here is what I tried to do.
changed QtCore.Qt.CrossCursor to QtCore.Qt.CursorShape.CrossCursor
changed QtCore.Qt.LeftButton to QtCore.Qt.MouseButton.LeftButton
changed sys.exit(app.exec_()) to sys.exit(app.exec())
now I reached the point of getting this error
'line 32, in moveCenter
self.center += self.delta
TypeError: unsupported operand type(s) for +=: 'QPoint' and 'QPointF''
and could not find a way to work around it in pyqt6
Here are the changes that solved my issue:
Changed self.center = QPointF(self.pano.rect().center())) to self.center += self.delta.toPoint()).
and changed self.sourceRect.moveCenter(self.center.toPoint()) to self.sourceRect.moveCenter(self.center).
Now the code is successfully converted from pyqt5 to pyqt6.
I want to know if there is any workaround to the missing flow() property "BottomToTop"?
I'm currently working on a little pet project. Simply a QListWidget, containing a custom made QWidget item, added by the user. No problem with that part. I just want the item to be listed "BottomToTop". Any suggestions?
Do just need to insert the new elements at the bottom instead of the top of your list?
I assume what you call bottom in the beginning for your list?
If so, you can use QListWidget::insertItem
yourListWidget->(0, yourNewWidgetItem);
This is my closest solution to what I've been asking (Credit to -ymoreau for the Spacer idea).
But I'm not completely happy with it. When enough items are added and the scrollbar appears, an annoying (to my eye) white-space appears at the bottom. Any solution to that? And ofc. is there a more elegant solution to this? Seems abit overkill to me...
from PyQt5.QtWidgets import (QApplication, QWidget, QListWidget,
QListWidgetItem, QVBoxLayout, QHBoxLayout,
QPushButton, QSpacerItem, QLabel, QSizePolicy)
from PyQt5.QtCore import QSize
from PyQt5 import QtCore
import sys
class SpacerWidget(QWidget):
def __init__(self, parent=None):
super(SpacerWidget, self).__init__(parent)
self.spacer = QSpacerItem(self.width(), self.height(),
QSizePolicy.Expanding,
QSizePolicy.Expanding)
self.setStyleSheet("border: none;")
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addItem(self.spacer)
self.setLayout(self.layout)
class SpacerWidgetItem(QListWidgetItem):
def __init__(self, parent=None):
super(SpacerWidgetItem, self).__init__(parent)
def setSize(self, width, new_height):
if new_height < 10:
self.setSizeHint(QSize(0, 0))
else:
self.setSizeHint(QSize(width, new_height))
class ItemWidget(QWidget):
def __init__(self, parent=None):
super(ItemWidget, self).__init__(parent)
self.left_label = QLabel("Some info!")
self.left_label.setMaximumHeight(35)
self.left_label.setAlignment(QtCore.Qt.AlignLeft)
self.right_label = QLabel("Some other info!")
self.right_label.setMaximumHeight(35)
self.right_label.setAlignment(QtCore.Qt.AlignRight)
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.addWidget(self.left_label)
self.layout.addWidget(self.right_label)
self.setLayout(self.layout)
class Widget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(300, 320)
self.itemsinlist = 0
self.layout = QVBoxLayout()
self.add_btn = QPushButton("Add Item")
self.add_btn.setMinimumSize(QSize(300, 50))
self.add_btn.clicked.connect(self.addListItem)
self.list_w = QListWidget()
self.list_w.setMinimumSize(300, 270)
self.list_w.resize(300, 270)
self.list_w.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.list_w.setResizeMode(1)
self.spacer = SpacerWidgetItem(self.list_w)
self.spacer.setSizeHint(QSize(self.list_w.minimumWidth(),
self.list_w.minimumHeight()))
self.spacer_widget = SpacerWidget()
self.list_w.addItem(self.spacer)
self.list_w.setItemWidget(self.spacer, self.spacer_widget)
self.layout.addWidget(self.list_w)
self.layout.addWidget(self.add_btn)
self.layout.setContentsMargins(1, 1, 1, 1)
self.layout.setSpacing(0)
self.setLayout(self.layout)
self.show()
def addListItem(self):
new_label = QListWidgetItem(self.list_w)
new_label.setSizeHint(QSize(self.list_w.minimumWidth(), 35))
new_label_widget = ItemWidget()
self.list_w.addItem(new_label)
self.list_w.setItemWidget(new_label, new_label_widget)
self.list_w.scrollToBottom()
self.itemsinlist += 1
self.spacer.setSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2)
def resizeEvent(self, event):
self.spacer.setSizeHint(QSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2))
self.list_w.scrollToBottom()
def main():
app = QApplication(sys.argv)
wid = Widget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am trying to create this geometery:
_ ___
| | |
|1| 3 |
|_|___|
|_2_|
Where the 1 box is tall and skinny, the 2 box is short and wide. I can't quite get the layout correct. When I cange the rowSpan and columnSpan for 1 and 2 from 0 to 1 I see the bottom box in correct relative location but height is wrong, and width of box 1 is wrong.
This so qq helped but didn't fully fix my problem: How to arrange the items in QGridLayout as shown?
import sys
from PyQt4 import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
textEdit1 = QtGui.QTextEdit("LHS rectangle")
textEdit2 = QtGui.QTextEdit("Bottom rectangle")
textEdit3 = QtGui.QTextEdit("Central square")
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.addWidget(textEdit1, 0, 0, 2, 0)
self.gridLayout.addWidget(textEdit2, 2, 1, 0, 2)
self.gridLayout.addWidget(textEdit3, 0, 1, 2, 2)
self.setLayout(self.gridLayout)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to set an appropriate stretch factor to change the amount of space given to each row/column:
self.gridLayout.addWidget(textEdit1, 0, 0)
self.gridLayout.addWidget(textEdit2, 1, 1)
self.gridLayout.addWidget(textEdit3, 0, 1)
self.gridLayout.setColumnStretch(0, 1)
self.gridLayout.setColumnStretch(1, 3)
self.gridLayout.setRowStretch(0, 3)
self.gridLayout.setRowStretch(1, 1)
Result:
I was wondering how to write code that would detect the mouse clicking on a sprite. For example:
if #Function that checks for mouse clicked on Sprite:
print ("You have opened a chest!")
I assume your game has a main loop, and all your sprites are in a list called sprites.
In your main loop, get all events, and check for the MOUSEBUTTONDOWN or MOUSEBUTTONUP event.
while ... # your main loop
# get all events
ev = pygame.event.get()
# proceed events
for event in ev:
# handle MOUSEBUTTONUP
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos()
# get a list of all sprites that are under the mouse cursor
clicked_sprites = [s for s in sprites if s.rect.collidepoint(pos)]
# do something with the clicked sprites...
So basically you have to check for a click on a sprite yourself every iteration of the mainloop. You'll want to use mouse.get_pos() and rect.collidepoint().
Pygame does not offer event driven programming, as e.g. cocos2d does.
Another way would be to check the position of the mouse cursor and the state of the pressed buttons, but this approach has some issues.
if pygame.mouse.get_pressed()[0] and mysprite.rect.collidepoint(pygame.mouse.get_pos()):
print ("You have opened a chest!")
You'll have to introduce some kind of flag if you handled this case, since otherwise this code will print "You have opened a chest!" every iteration of the main loop.
handled = False
while ... // your loop
if pygame.mouse.get_pressed()[0] and mysprite.rect.collidepoint(pygame.mouse.get_pos()) and not handled:
print ("You have opened a chest!")
handled = pygame.mouse.get_pressed()[0]
Of course you can subclass Sprite and add a method called is_clicked like this:
class MySprite(Sprite):
...
def is_clicked(self):
return pygame.mouse.get_pressed()[0] and self.rect.collidepoint(pygame.mouse.get_pos())
So, it's better to use the first approach IMHO.
The MOUSEBUTTONDOWN event occurs once when you click the mouse button and the MOUSEBUTTONUP event occurs once when the mouse button is released. The pygame.event.Event() object has two attributes that provide information about the mouse event. pos is a tuple that stores the position that was clicked. button stores the button that was clicked. Each mouse button is associated a value. For instance the value of the attributes is 1, 2, 3, 4, 5 for the left mouse button, middle mouse button, right mouse button, mouse wheel up respectively mouse wheel down. When multiple keys are pressed, multiple mouse button events occur. Further explanations can be found in the documentation of the module pygame.event.
Use the rect attribute of the pygame.sprite.Sprite object and the collidepoint method to see if the Sprite was clicked.
Pass the list of events to the update method of the pygame.sprite.Group so that you can process the events in the Sprite class:
class SpriteObject(pygame.sprite.Sprite):
# [...]
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
# [...]
my_sprite = SpriteObject()
group = pygame.sprite.Group(my_sprite)
# [...]
run = True
while run:
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
# [...]
Minimal example: repl.it/#Rabbid76/PyGame-MouseClick
import pygame
class SpriteObject(pygame.sprite.Sprite):
def __init__(self, x, y, color):
super().__init__()
self.original_image = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.circle(self.original_image, color, (25, 25), 25)
self.click_image = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.circle(self.click_image, color, (25, 25), 25)
pygame.draw.circle(self.click_image, (255, 255, 255), (25, 25), 25, 4)
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.clicked = False
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
self.clicked = not self.clicked
self.image = self.click_image if self.clicked else self.original_image
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
sprite_object = SpriteObject(*window.get_rect().center, (128, 128, 0))
group = pygame.sprite.Group([
SpriteObject(window.get_width() // 3, window.get_height() // 3, (128, 0, 0)),
SpriteObject(window.get_width() * 2 // 3, window.get_height() // 3, (0, 128, 0)),
SpriteObject(window.get_width() // 3, window.get_height() * 2 // 3, (0, 0, 128)),
SpriteObject(window.get_width() * 2// 3, window.get_height() * 2 // 3, (128, 128, 0)),
])
run = True
while run:
clock.tick(60)
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
window.fill(0)
group.draw(window)
pygame.display.flip()
pygame.quit()
exit()
See further Creating multiple sprites with different update()'s from the same sprite class in Pygame
The current position of the mouse can be determined via pygame.mouse.get_pos(). The return value is a tuple that represents the x and y coordinates of the mouse cursor. pygame.mouse.get_pressed() returns a list of Boolean values that represent the state (True or False) of all mouse buttons. The state of a button is True as long as a button is held down. When multiple buttons are pressed, multiple items in the list are True. The 1st, 2nd and 3rd elements in the list represent the left, middle and right mouse buttons.
Detect evaluate the mouse states in the Update method of the pygame.sprite.Sprite object:
class SpriteObject(pygame.sprite.Sprite):
# [...]
def update(self, event_list):
mouse_pos = pygame.mouse.get_pos()
mouse_buttons = pygame.mouse.get_pressed()
if self.rect.collidepoint(mouse_pos) and any(mouse_buttons):
# [...]
my_sprite = SpriteObject()
group = pygame.sprite.Group(my_sprite)
# [...]
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
group.update(event_list)
# [...]
Minimal example: repl.it/#Rabbid76/PyGame-MouseHover
import pygame
class SpriteObject(pygame.sprite.Sprite):
def __init__(self, x, y, color):
super().__init__()
self.original_image = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.circle(self.original_image, color, (25, 25), 25)
self.hover_image = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.circle(self.hover_image, color, (25, 25), 25)
pygame.draw.circle(self.hover_image, (255, 255, 255), (25, 25), 25, 4)
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.hover = False
def update(self):
mouse_pos = pygame.mouse.get_pos()
mouse_buttons = pygame.mouse.get_pressed()
#self.hover = self.rect.collidepoint(mouse_pos)
self.hover = self.rect.collidepoint(mouse_pos) and any(mouse_buttons)
self.image = self.hover_image if self.hover else self.original_image
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
sprite_object = SpriteObject(*window.get_rect().center, (128, 128, 0))
group = pygame.sprite.Group([
SpriteObject(window.get_width() // 3, window.get_height() // 3, (128, 0, 0)),
SpriteObject(window.get_width() * 2 // 3, window.get_height() // 3, (0, 128, 0)),
SpriteObject(window.get_width() // 3, window.get_height() * 2 // 3, (0, 0, 128)),
SpriteObject(window.get_width() * 2// 3, window.get_height() * 2 // 3, (128, 128, 0)),
])
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
group.update()
window.fill(0)
group.draw(window)
pygame.display.flip()
pygame.quit()
exit()
The pygame documentation for mouse events is here.
You can either use the pygame.mouse.get_pressed method in collaboration with the pygame.mouse.get_pos (if needed).
Remember to use the mouse click event via a main event loop. The reason why the event loop is better is due to "short clicks". You may not notice these on normal machines, but computers that use tap-clicks on trackpads have excessively small click periods. Using the mouse events will prevent this.
EDIT:
To perform pixel perfect collisions use pygame.sprite.collide_rect() found on their docs for sprites.
I was looking for the same answer to this question and after much head scratching this is the answer I came up with:
# Python 3.4.3 with Pygame
from sys import exit
import pygame
pygame.init()
WIDTH = HEIGHT = 300
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Crash!')
# Draw Once
rectangle = pygame.draw.rect(window, (255, 0, 0), (100, 100, 100, 100))
pygame.display.update()
# Main Loop
while True:
# Mouse position and button clicking
pos = pygame.mouse.get_pos()
pressed1 = pygame.mouse.get_pressed()[0]
# Check if rectangle collided with pos and if the left mouse button was pressed
if rectangle.collidepoint(pos) and pressed1:
print("You have opened a chest!")
# Quit pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()