Text scrolling (Marquee) in QLabel
label, marquee, qt, scroll, text
Solution
For this purpose, I once wrote a class.
Example screenshot showing the text "This is an example text. It will be scrolled horizontally.". Note the alpha blending at both sides.
The code:
scrolltext.h:
#ifndef SCROLLTEXT_H
#define SCROLLTEXT_H
#include <QWidget>
#include <QStaticText>
#include <QTimer>
class ScrollText : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(QString separator READ separator WRITE setSeparator)
public:
explicit ScrollText(QWidget *parent = 0);
public slots:
QString text() const;
void setText(QString text);
QString separator() const;
void setSeparator(QString separator);
protected:
virtual void paintEvent(QPaintEvent *);
virtual void resizeEvent(QResizeEvent *);
private:
void updateText();
QString _text;
QString _separator;
QStaticText staticText;
int singleTextWidth;
QSize wholeTextSize;
int leftMargin;
bool scrollEnabled;
int scrollPos;
QImage alphaChannel;
QImage buffer;
QTimer timer;
private slots:
virtual void timer_timeout();
};
#endif // SCROLLTEXT_H
scrolltext.cpp:
#include "scrolltext.h"
#include <QPainter>
ScrollText::ScrollText(QWidget *parent) :
QWidget(parent), scrollPos(0)
{
staticText.setTextFormat(Qt::PlainText);
setFixedHeight(fontMetrics().height());
leftMargin = height() / 3;
setSeparator(" --- ");
connect(&timer, SIGNAL(timeout()), this, SLOT(timer_timeout()));
timer.setInterval(50);
}
QString ScrollText::text() const
{
return _text;
}
void ScrollText::setText(QString text)
{
_text = text;
updateText();
update();
}
QString ScrollText::separator() const
{
return _separator;
}
void ScrollText::setSeparator(QString separator)
{
_separator = separator;
updateText();
update();
}
void ScrollText::updateText()
{
timer.stop();
singleTextWidth = fontMetrics().width(_text);
scrollEnabled = (singleTextWidth > width() - leftMargin);
if(scrollEnabled)
{
scrollPos = -64;
staticText.setText(_text + _separator);
timer.start();
}
else
staticText.setText(_text);
staticText.prepare(QTransform(), font());
wholeTextSize = QSize(fontMetrics().width(staticText.text()), fontMetrics().height());
}
void ScrollText::paintEvent(QPaintEvent*)
{
QPainter p(this);
if(scrollEnabled)
{
buffer.fill(qRgba(0, 0, 0, 0));
QPainter pb(&buffer);
pb.setPen(p.pen());
pb.setFont(p.font());
int x = qMin(-scrollPos, 0) + leftMargin;
while(x < width())
{
pb.drawStaticText(QPointF(x, (height() - wholeTextSize.height()) / 2) + QPoint(2, 2), staticText);
x += wholeTextSize.width();
}
//Apply Alpha Channel
pb.setCompositionMode(QPainter::CompositionMode_DestinationIn);
pb.setClipRect(width() - 15, 0, 15, height());
pb.drawImage(0, 0, alphaChannel);
pb.setClipRect(0, 0, 15, height());
//initial situation: don't apply alpha channel in the left half of the image at all; apply it more and more until scrollPos gets positive
if(scrollPos < 0)
pb.setOpacity((qreal)(qMax(-8, scrollPos) + 8) / 8.0);
pb.drawImage(0, 0, alphaChannel);
//pb.end();
p.drawImage(0, 0, buffer);
}
else
{
p.drawStaticText(QPointF(leftMargin, (height() - wholeTextSize.height()) / 2), staticText);
}
}
void ScrollText::resizeEvent(QResizeEvent*)
{
//When the widget is resized, we need to update the alpha channel.
alphaChannel = QImage(size(), QImage::Format_ARGB32_Premultiplied);
buffer = QImage(size(), QImage::Format_ARGB32_Premultiplied);
//Create Alpha Channel:
if(width() > 64)
{
//create first scanline
QRgb* scanline1 = (QRgb*)alphaChannel.scanLine(0);
for(int x = 1; x < 16; ++x)
scanline1[x - 1] = scanline1[width() - x] = qRgba(0, 0, 0, x << 4);
for(int x = 15; x < width() - 15; ++x)
scanline1[x] = qRgb(0, 0, 0);
//copy scanline to the other ones
for(int y = 1; y < height(); ++y)
memcpy(alphaChannel.scanLine(y), (uchar*)scanline1, width() * 4);
}
else
alphaChannel.fill(qRgb(0, 0, 0));
//Update scrolling state
bool newScrollEnabled = (singleTextWidth > width() - leftMargin);
if(newScrollEnabled != scrollEnabled)
updateText();
}
void ScrollText::timer_timeout()
{
scrollPos = (scrollPos + 2)
% wholeTextSize.width();
update();
}
Problem
I'm studying WidgetMarqueeLabel class: ``` #include "WidgetMarqueeLabel.h" #include <QPainter> #include <QWidget> WidgetMarqueeLabel::WidgetMarqueeLabel(QWidget *parent)//*parent) { px = 0; py = 10; speed = 1; direction = RightToLeft; connect(&timer3, SIGNAL(timeout()), this, SLOT(refreshLabel())); timer3.start(10); } void WidgetMarqueeLabel::refreshLabel() { repaint(); } WidgetMarqueeLabel::~WidgetMarqueeLabel() {} void WidgetMarqueeLabel::show() { QLabel::show(); } void WidgetMarqueeLabel::setAlignment(Qt::Alignment al) { m_align = al; updateCoordinates(); QLabel::setAlignment(al); } void WidgetMarqueeLabel::paintEvent(QPaintEvent *evt) { QPainter p(this); if(direction==RightToLeft) { px -= speed; if(px <= (-textLength)) px = width(); } else { px += speed; if(px >= width()) px = - textLength; } p.drawText(px, py+fontPointSize, text()); p.translate(px,0); } void WidgetMarqueeLabel::resizeEvent(QResizeEvent *evt) { updateCoordinates(); QLabel::resizeEvent(evt); } void WidgetMarqueeLabel::updateCoordinates() { switch(m_align) { case Qt::AlignTop: py = 10; break; case Qt::AlignBottom: py = height()-10; break; case Qt::AlignVCenter: py = height()/2; break; } fontPointSize = font().pointSize()/2; textLength = fontMetrics().width(text()); } void WidgetMarqueeLabel::setSpeed(int s) { speed = s; } int WidgetMarqueeLabel::getSpeed() { return speed; } void WidgetMarqueeLabel::setDirection(int d) { direction = d; if (direction==RightToLeft) px = width() - textLength; else px = 0; refreshLabel(); } void WidgetMarqueeLabel::close() { QLabel::close(); } ``` I was wondering if it was possible to make the text reappear before the text that reaches the end of the last letter on the right. I want something like this: for example (white space are 25): ``` WidgetMarqueeLabel tMarqueeLabel Widge eLabel WidgetMarque el WidgetMarqueeLa WidgetMarqueeLabel WidgetMarqueeLabel WidgetMarqueeLabel WidgetMarqueeLabel ``` Is this possible?