I am trying to use a Qlabel as a message center for relaying messages to users of the application. Some messages might be longer than allowed for the Qlabel and I want it to just scroll horizontally until the end of the text. How can I do this in a Qlabel? I cannot seem to find anything in designer and don't want to work out some sort of truncation method in code that just takes off pieces from the front of the string, that seems silly.
What you are after is commonly known as Marquee widget. Here is a very simple and rusty implementation which make use of a QLabel, but it can be done with a QWidget too. I overridden the setText method from which i use a QTextDocument, with parent the QLabel itself, which holds the text. If the text is bigger than the size of the QLabel a QTimer triggers a translation method that moves the text:
import sys
from PyQt5.QtCore import QEvent, QTimer, pyqtSlot
from PyQt5.QtGui import QTextDocument, QPainter, QFontMetrics
from PyQt5.QtWidgets import QLabel, QApplication
class Marquee(QLabel):
x = 0
paused = False
document = None
speed = 50
timer = None
def __init__(self, parent=None):
super().__init__(parent)
self.fm = QFontMetrics(self.font())
self.setFixedSize(200, 20)
def setText(self, value):
self.x = 0
self.document = QTextDocument(self)
self.document.setPlainText(value)
# I multiplied by 1.06 because otherwise the text goes on 2 lines
self.document.setTextWidth(self.fm.width(value) * 1.06)
self.document.setUseDesignMetrics(True)
if self.document.textWidth() > self.width():
self.timer = QTimer(self)
self.timer.timeout.connect(self.translate)
self.timer.start((1 / self.speed) * 1000)
#pyqtSlot()
def translate(self):
if not self.paused:
if self.width() - self.x < self.document.textWidth():
self.x -= 1
else:
self.timer.stop()
self.repaint()
def event(self, event):
if event.type() == QEvent.Enter:
self.paused = True
elif event.type() == QEvent.Leave:
self.paused = False
return super().event(event)
def paintEvent(self, event):
if self.document:
p = QPainter(self)
p.translate(self.x, 0)
self.document.drawContents(p)
return super().paintEvent(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Marquee()
w.setText('Lorem ipsum dolor sit amet, consectetur adipiscing elit...')
w.show()
sys.exit(app.exec_())
With a little bit changing #Daniele Pantaleone's code.I have implemented 3 mode of
Animations
i.e Left-to-Right,Right-to-Left & Left+Right .
It also supports Text Color and Marquee only work if text length exceeds the Label width.
Here is my code:
import sys
from PyQt5.QtCore import QEvent, QTimer, pyqtSlot,QRectF
from PyQt5.QtGui import QTextDocument, QPainter, QFontMetrics,QFont,QColor,QAbstractTextDocumentLayout,QPalette
from PyQt5.QtWidgets import QLabel, QApplication
class Document(QTextDocument):
def __init__(self, parent=None):
super().__init__(parent)
def drawContents(self, p, rect=QRectF()):
p.save()
ctx=QAbstractTextDocumentLayout.PaintContext ()
ctx.palette.setColor(QPalette.Text, p.pen().color())
if (rect.isValid()) :
p.setClipRect(rect)
ctx.clip = rect
self.documentLayout().draw(p, ctx)
p.restore()
class Marquee(QLabel):
paused = False
speed = 60
x=0
def __init__(self, parent=None):
super().__init__(parent)
self.document = None
self.timer = QTimer(self)
self.timer.timeout.connect(self.translate)
def setText(self, value,mode="LR"):
f=self.font()
self.fm=QFontMetrics(f)
if self.document is None:
self.document = Document(self)
self.document.setUseDesignMetrics(True)
self.document.setDefaultFont(f)
self.document.setDocumentMargin(0)
if self.timer.isActive() :
self.timer.stop()
if self.fm.width(value) > self.width():
self.nl = int(self.width()/self.fm.horizontalAdvance(" "))
val=' '*self.nl +value+' '*self.nl
self.document.setTextWidth(self.fm.width(val)+22 )
self.document.clear()
self.document.setPlainText(val)
self.setMode(mode)
self.timer.start((1 / 60) * 1000)
else:
self.x=(self.width()/2)-(self.fm.width(value)/2)
self.document.clear()
self.document.setPlainText(value)
self.repaint()
def setMode(self,val):
if val=="RL":
self.x = 0
elif val=="LR" :
self.x =-(self.document.textWidth()-self.fm.width(" "*self.nl)-10)
else:
self.x =-(self.document.textWidth()-self.fm.width(" "*self.nl)-10)
self.fstr=True
self.mode=val
#pyqtSlot()
def translate(self):
if not self.paused:
if self.mode=="RL":
if self.width() - self.x < self.document.textWidth():
self.x -= 1
else:
self.x=0
elif self.mode=="LR" :
if self.x<=0:
self.x+= 1
else:
self.x =-(self.document.textWidth()-self.fm.width(" "*self.nl)-10)
else:
if self.fstr:
if self.x<=0:
self.x+= 1
else:
self.x =0
self.fstr=False
else:
if self.width() - self.x < self.document.textWidth():
self.x -= 1
else:
self.x=-(self.document.textWidth()-self.fm.width(" "*self.nl)-10)
self.fstr=True
self.repaint()
def event(self, event):
if event.type() == QEvent.Enter:
self.paused = True
elif event.type() == QEvent.Leave:
self.paused = False
return super().event(event)
def getColor(self)->QColor:
if self.styleSheet()=='':
return QColor("grey")
else:
style =self.styleSheet().split(";")
color= "".join([s.split(":")[1] for s in style if s.startswith("color")])
return QColor(color)
def paintEvent(self, event):
if self.document:
p = QPainter(self)
self.getColor()
p.setPen(self.getColor())
p.translate(self.x, 0)
self.document.drawContents(p)
return super().paintEvent(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Marquee()
w.setFixedSize(250, 60)
w.setStyleSheet("background-color:black;color:yellow")
f=QFont()
f.setPointSize(20)
f.setBold(True)
f.setItalic(True)
f.setFamily("Courier")
w.setFont(f)
w.setText("consectggftr_conversion_we want",mode="L+R") # or "RL" or "LR"
There is also better version of this Code provided by #eyllanesc Here
There's nothing in Qt that does that by default. You will indeed need to create an animation that changes the text.
You can use QFontMetrics (label.fontMetrics()) to determine if the label text is larger than then QLabel (to know if you need to scroll it or not). You need a way to repaint the QLabel every half second or so to animate the scrolling. The easiest way is probably a QTimer. The easiest method would probably be to subclass QLabel and have it check if it needs to scroll itself and reset the text every half second or so to simulate the scrolling.
If you wanted scrolling to be even smoother (at a sub-character level), you'd have to override the paint method and paint the text yourself translating and clipping it as necessary.
Related
Struggle is that I am capable of rendering one line of text with Sprites in Pygame, I am also capable of rendering multi-line text, but without the Sprites, but I can't figure out way how to render multi-line text using Sprites.
I've got this for rendering one line text using Sprites (I am going to skip the basic for running Pygame code - background, init etc.):
class Score(pygame.sprite.Sprite):
.
.
def update(self, lives, score):
.
.
self.text = "LIVES: %d SCORE: %d" % (lives, score)
self.image = self.font.render(self.text, True, self.color)
self.rect = self.image.get_rect()
.
.
.
.
def main():
.
.
score = pygame.sprite.RenderUpdates()
score.add(Score())
.
.
while 1:
.
.
score.clear(screen, background)
score_list = score.draw(screen)
pygame.display.update(score_list)
score.update(lives, score)
.
.
I just want to know if it's even possible to do it using my render method or if I should focus on doing it some other way?
And maybe one related question; Is this method the right way to render objects (images) in Pygame?
Thank you for any help.
You could render the strings with FONT.render first, then create a transparent surface and blit the separate text surfaces onto it. To figure out the width of the transparent base image, you can use the .get_width() method of the separate text surfaces and for the height you can use FONT.get_height().
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
FONT = pg.font.Font(None, 40)
BLUE = pg.Color('dodgerblue1')
class Score(pg.sprite.Sprite):
def __init__(self, pos):
super(Score, self).__init__()
self.lives = 3
self.score = 0
self.rect = pg.Rect(pos, (1, 1))
self.update_image()
def update_image(self):
height = FONT.get_height()
# Put the rendered text surfaces into this list.
text_surfaces = []
for txt in ('lives {}'.format(self.lives),
'score {}'.format(self.score)):
text_surfaces.append(FONT.render(txt, True, BLUE))
# The width of the widest surface.
width = max(txt_surf.get_width() for txt_surf in text_surfaces)
# A transparent surface onto which we blit the text surfaces.
self.image = pg.Surface((width, height*2), pg.SRCALPHA)
for y, txt_surf in enumerate(text_surfaces):
self.image.blit(txt_surf, (0, y*height))
# Get a new rect (if you maybe want to make the text clickable).
self.rect = self.image.get_rect(topleft=self.rect.topleft)
def main():
score = Score((100, 100))
all_sprites = pg.sprite.Group(score)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_s:
# Increment the score and update the image.
# It would be nicer to turn the score into a
# property and update the image automatically.
score.score += 1
score.update_image()
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Alright,
I've looked everywhere and still can't figure out PyGame collisions. I have two sprites: A lander and a moon surface. The moon is an uneven surface with a transparent background, while the lander is a sprite that changes through images with L,D,UP keypresses.
I'm doing something wrong when it comes to collision detection. Otherwise, everything in my program is going swimmingly
This is my lander class:
class ShipClass(pygame.sprite.Sprite):
def __init__(self, image_file, position):
pygame.sprite.Sprite.__init__(self)
self.imageMaster = pygame.image.load(image_file)
self.image = self.imageMaster
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface(self.imageMaster)
self.rect.topleft = position
## This doesn't work
# def checkCollision(self, Mask):
# if Mask.overlap_area
def update(self):
self.rect = self.image.get_rect()
And this is my lunar surface class:
class MoonSurface(pygame.sprite.Sprite):
def __init__(self, image_file, position):
pygame.sprite.Sprite.__init__(self)
self.imageMaster = pygame.image.load(image_file).convert_alpha()
self.image = self.imageMaster
self.rect = self.image.get_rect()
self.rect.topleft = position
What do I need to add to this so that while in my game if the lander hits the moon it sets variable stop to 1 and crash to 1? I plan on also adding landing pads with the MoonSurface class and when it hits them only sets stop to 1.
My sprite is moved as follows:
speed = [0,0]
position = [width-524,height-718]
gravity = 0.05
StartingFuel = 100
done = False
while not done:
event = pygame.event.poll()
if event.type == QUIT:
pygame.quit()
sys.exit(0)
elif event.type == pygame.KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit(0)
else:
lander = ShipClass('lander.png', position)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
lander = ShipClass('landerLeft.png', position)
# lander.set_colorkey(BLACK)
speed[0] += 0.3
fuel -= 1
if keys[pygame.K_RIGHT]:
lander = ShipClass('landerRight.png', position)
# lander.set_colorkey(BLACK)
speed[0] -= 0.3
fuel -= 1
if keys[pygame.K_UP]:
lander = ShipClass('landerUp.png', position)
# lander.set_colorkey(BLACK)
speed[1] -= 0.4
fuel -= 1
speed[1] += gravity
position[0] += speed[0]
position[1] += speed[1]
if position[0] < 0:
position[0] = width
elif position[0] > width:
position[0] = 0
screen.blit(lander.image, position)
It looks like you want to check if the two objects overlap. Luckily Pygame has a built- in function colliderect which checks if two rectangles are overlapping. I've put it into a class method here, as I saw your objects had Pygame rectangle variables named rect:
def overlaps (self, other_rect):
return self.rect.colliderect(other_rect)
Here's the documentation: pygame.Rect
EDIT: When I change the main window size, I need cut the text of QLabel() that includes in QStatusBar() to fit the size of the form. As shown in the diagram below.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class MyMainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setCentralWidget(QLabel("main widget"))
statusBar = QStatusBar()
self.setStatusBar(statusBar)
statusBar.addWidget(QLabel("first message"))
statusBar.addWidget(QLabel("second message"))
statusBar.addWidget(QLabel("a thrid long long long long long message"))
app = QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
The key to this is to set an appropriate minimum width on the labels, which will then allow them to resize smaller than the width of their text. However, this will simply crop the text on the right, rather eliding it (i.e. so that ... appears at the end).
The blog post Status bar and elided label explains how to get elided text, but the solution is written in C++. Below is an equivalent version written in PyQt4:
import sys
from PyQt4 import QtCore, QtGui
class ElidedLabel(QtGui.QLabel):
_width = _text = _elided = None
def __init__(self, text='', width=40, parent=None):
super(ElidedLabel, self).__init__(text, parent)
self.setMinimumWidth(width if width > 0 else 1)
def elidedText(self):
return self._elided or ''
def paintEvent(self, event):
painter = QtGui.QPainter(self)
self.drawFrame(painter)
margin = self.margin()
rect = self.contentsRect()
rect.adjust(margin, margin, -margin, -margin)
text = self.text()
width = rect.width()
if text != self._text or width != self._width:
self._text = text
self._width = width
self._elided = self.fontMetrics().elidedText(
text, QtCore.Qt.ElideRight, width)
option = QtGui.QStyleOption()
option.initFrom(self)
self.style().drawItemText(
painter, rect, self.alignment(), option.palette,
self.isEnabled(), self._elided, self.foregroundRole())
class MyMainWindow(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow, self).__init__()
self.setCentralWidget(QtGui.QLabel("main widget"))
statusBar = QtGui.QStatusBar()
self.setStatusBar(statusBar)
statusBar.addWidget(ElidedLabel("first message"))
statusBar.addWidget(ElidedLabel("second message"))
statusBar.addWidget(ElidedLabel("a third long long long long long message"))
app = QtGui.QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
My programm create a Frame with three panels in an horizontal boxsizer. A menu with "new window" item for create a seconde Frame. I give the seconde panel as parent of the seconde window. I wante the seconde Frame stays in the seconde panel area of my first frame.
if user move one of the two windows, the seconde stays in the panel screen area.
Do you know a way or something for that?
I tried a little something, but using is not very aesthetic.
and
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
#Widgets
panel_gch = wx.Panel(self,-1,size = (150,-1))
panel_gch.SetBackgroundColour('white')
self.panel=wx.Panel(self,-1,size=(300,400))
self.panel.SetBackgroundColour((200,230,200))
panel_drt = wx.Panel(self,-1,size = (150,-1))
panel_drt.SetBackgroundColour('white')
box = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(box)
#Add
box.Add(panel_gch,0,wx.EXPAND)
box.Add(self.panel,1,wx.EXPAND)
box.Add(panel_drt,0,wx.EXPAND)
#Menu
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
ID_FILE_NEW = 1
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
menubar.Append(file_menu,"File")
self.SetMenuBar(menubar)
#bind and layout
self.Bind(wx.EVT_MENU, self.get_new_window)
panel_gch.Layout()
self.panel.Layout()
panel_drt.Layout()
self.Layout()
def get_new_window(self,event): # create new window
self.new = NewWindow(self.panel,-1)
self.new.Show(True)
self.new.Bind(wx.EVT_MOVE,self.window2_on_move)
def window2_on_move(self,event): # Window2 must stay in
x, y = event.GetPosition()
v,w =self.panel.GetScreenPosition()
s,t = self.panel.GetClientSizeTuple()
if x < v:
self.new.Move((v,-1))
if y < w:
self.new.Move((-1,w))
if x+200 > v+s:
self.new.Move((v+s-200,-1))
if y+200 > w+t:
self.new.Move((-1,w+t-200))
class NewWindow(wx.MiniFrame):
def __init__(self,MainWindow,id):
wx.MiniFrame.__init__(self, MainWindow, id, 'New Window', size=(200,200),\
style = wx.MINIMIZE | wx.CAPTION | wx.CLOSE_BOX | wx.CLOSE_BOX)
self.CenterOnParent()
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
What you probably want is AUI. I personally recommend the wx.lib.agw.aui set rather than wx.aui as the former is pure Python and has had a LOT more recent work done on it. There are multiple examples in the wxPython demo package. You can also read about it here:
http://wxpython.org/Phoenix/docs/html/lib.agw.aui.framemanager.AuiManager.html
Thanks you very much Mike, exactly what I needed.
With wxpython I found This way:
the child stays in the panel area and it follows the window parent when moving.
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
self.new = None
#Widgets
self.position = (0,0)
panel_gch = wx.Panel(self,-1,size = (150,-1))
panel_gch.SetBackgroundColour('white')
self.panel=wx.Panel(self,-1,size=(300,400))
self.panel.SetBackgroundColour((200,230,200))
panel_drt = wx.Panel(self,-1,size = (150,-1))
panel_drt.SetBackgroundColour('white')
box = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(box)
#Add
box.Add(panel_gch,0,wx.EXPAND)
box.Add(self.panel,1,wx.EXPAND)
box.Add(panel_drt,0,wx.EXPAND)
#Menu
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
ID_FILE_NEW = 1
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
menubar.Append(file_menu,"File")
self.SetMenuBar(menubar)
#bind and layout
self.Bind(wx.EVT_MENU, self.get_new_window)
panel_gch.Layout()
self.panel.Layout()
panel_drt.Layout()
self.Layout()
def get_new_window(self,event): # create new window
if self.new == None:
self.win_one_move = False
self.new = NewWindow(self.panel,-1)
self.new.Show(True)
self.new.Bind(wx.EVT_MOVE,self.window2_on_move)
self.Bind(wx.EVT_MOVE,self.window1_on_move)
v,w =self.GetPosition()
x, y = self.new.GetPosition()
self.get_windows_position((x-v),(y-w))
def get_windows_position(self,x,y):
self.position = (x,y)
print "check",self.position
def window2_on_move(self,event): # Window2 must stay in
if self.win_one_move == False:
x, y = event.GetPosition()
v,w =self.panel.GetScreenPosition()
s,t = self.panel.GetClientSizeTuple()
new_x,new_y = self.new.GetClientSizeTuple()
if x < v:
self.new.Move((v,-1))
if y < w:
self.new.Move((-1,w))
if x+new_x > v+s:
self.new.Move((v+s-new_x,-1))
if y+new_y > w+t:
self.new.Move((-1,w+t-new_y))
v,w =self.GetPosition()
x,y = self.new.GetPosition()
self.get_windows_position((x-v),(y-w))
if self.win_one_move == True:
self.win_one_move = False
def window1_on_move(self,event):
self.win_one_move = True
print "1 move"
x,y = self.GetPosition()
self.new.Move((x+self.position[0],y+self.position[1]))
print self.position
class NewWindow(wx.MiniFrame):
def __init__(self,MainWindow,id):
wx.MiniFrame.__init__(self, MainWindow, id, 'New Window', size=(200,200),\
style = wx.CAPTION | wx.CLOSE_BOX | wx.CLOSE_BOX)
self.CenterOnParent()
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
Can be use by another.Thanks
I want to center the text of my QTextEdit horizontally and vertically.
I tried this, but it didn't work.
m_myTextEdit = new QTextEdit("text edit", m_ui->centralWidget);
m_myTextEdit->setGeometry(5, 50, 400, 250);
m_myTextEdit->setReadOnly(true);
m_myTextEdit->setAlignment(Qt::AlignCenter);
Is there a opportunity to set it centered with a StyleSheet?
If you only need one line, you can use a QLineEdit instead:
QLineEdit* lineEdit = new QLineEdit("centered text");
lineEdit->setAlignment(Qt::AlignCenter);
If you only want to display the text, not allow the user to edit it, you can use a QLabel instead. This works with line wrapping, too:
QLabel* label = new QLabel("centered text");
lineEdit->setWordWrap(true);
lineEdit->setAlignment(Qt::AlignCenter);
Here is code from PySide that I use for this, for those that need to use QTextEdit rather than QLineEdit. It is based on my answer here:
https://stackoverflow.com/a/34569735/1886357
Here is the code, but the explanation is at the link:
import sys
from PySide import QtGui, QtCore
class TextLineEdit(QtGui.QTextEdit):
topMarginCorrection = -4 #not sure why needed
returnPressed = QtCore.Signal()
def __init__(self, fontSize = 10, verticalMargin = 2, parent = None):
QtGui.QTextEdit.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFontPointSize(fontSize)
self.setViewportMargins(0, self.topMarginCorrection , 0, 0) #left, top, right, bottom
#Set up document with appropriate margins and font
document = QtGui.QTextDocument()
currentFont = self.currentFont()
currentFont.setPointSize(fontSize)
document.setDefaultFont(currentFont)
document.setDocumentMargin(verticalMargin)
self.setFixedHeight(document.size().height())
self.setDocument(document)
def keyPressEvent(self, event):
'''stops retun from returning newline'''
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.returnPressed.emit()
event.accept()
else:
QtGui.QTextEdit.keyPressEvent(self, event)
def main():
app = QtGui.QApplication(sys.argv)
myLine = TextLineEdit(fontSize = 15, verticalMargin = 8)
myLine.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()