I've been struggling with this for some times now, and I can't seem to find the right way to do this.
What I would like is the ability to use an animated icon as a decoration for some of my items (typically to show that some processing is occuring for this particular item). I have a custom table model, that I display in a QTableView
.
My first idea was to create a custom delegate that would take care of displaying the animation. When passed a QMovie
for the decoration role, the delegate would connect to the QMovie
in order to update the display every time a new frame is available (see code below). However, the painter does not seem to remain valid after the call to the delegate's paint
method (I get an error when calling the painter's save
method, probably because the pointer no longer points to valid memory).
Another solution would be to emit the dataChanged
signal of the item every time a new frame is available, but 1) that would induce many unnecessary overhead, since the data is not really changed; 2) it does not seem really clean to handle the movie at the model level: it should be the responsibility of the display tier (QTableView
or the delegate) to handle the display of new frames.
Does anyone know a clean (and preferably efficient) way to display animation in Qt views?
For those interested, here is the code of the delegate I developped (which does not work at the moment).
// Class that paints movie frames every time they change, using the painter
// and style options provided
class MoviePainter : public QObject
{
Q_OBJECT
public: // member functions
MoviePainter( QMovie * movie,
QPainter * painter,
const QStyleOptionViewItem & option );
public slots:
void paint( ) const;
private: // member variables
QMovie * movie_;
QPainter * painter_;
QStyleOptionViewItem option_;
};
MoviePainter::MoviePainter( QMovie * movie,
QPainter * painter,
const QStyleOptionViewItem & option )
: movie_( movie ), painter_( painter ), option_( option )
{
connect( movie, SIGNAL( frameChanged( int ) ),
this, SLOT( paint( ) ) );
}
void MoviePainter::paint( ) const
{
const QPixmap & pixmap = movie_->currentPixmap();
painter_->save();
painter_->drawPixmap( option_.rect, pixmap );
painter_->restore();
}
//-------------------------------------------------
//Custom delegate for handling animated decorations.
class MovieDelegate : public QStyledItemDelegate
{
Q_OBJECT
public: // member functions
MovieDelegate( QObject * parent = 0 );
~MovieDelegate( );
void paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const;
private: // member functions
QMovie * qVariantToPointerToQMovie( const QVariant & variant ) const;
private: // member variables
mutable std::map< QModelIndex, detail::MoviePainter * > map_;
};
MovieDelegate::MovieDelegate( QObject * parent )
: QStyledItemDelegate( parent )
{
}
MovieDelegate::~MovieDelegate( )
{
typedef std::map< QModelIndex, detail::MoviePainter * > mapType;
mapType::iterator it = map_.begin();
const mapType::iterator end = map_.end();
for ( ; it != end ; ++it )
{
delete it->second;
}
}
void MovieDelegate::paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QStyledItemDelegate::paint( painter, option, index );
const QVariant & data = index.data( Qt::DecorationRole );
QMovie * movie = qVariantToPointerToQMovie( data );
// Search index in map
typedef std::map< QModelIndex, detail::MoviePainter * > mapType;
mapType::iterator it = map_.find( index );
// if the variant is not a movie
if ( ! movie )
{
// remove index from the map (if needed)
if ( it != map_.end() )
{
delete it->second;
map_.erase( it );
}
return;
}
// create new painter for the given index (if needed)
if ( it == map_.end() )
{
map_.insert( mapType::value_type(
index, new detail::MoviePainter( movie, painter, option ) ) );
}
}
QMovie * MovieDelegate::qVariantToPointerToQMovie( const QVariant & variant ) const
{
if ( ! variant.canConvert< QMovie * >() ) return NULL;
return variant.value< QMovie * >();
}
Simply right-click on a desktop icon and select Appearance. On Display's Change Icon dialog allows you to select ANI files for desktop icons. On Display will prompt you to select your animation preferences the first time...
The best solution is to use QSvgRenderer within delegate.
It's very easy to implement and unlike gif, SVG is lightweight and supports transparency.
TableViewDelegate::TableViewDelegate(TableView* view, QObject* parent)
: QStyledItemDelegate(parent), m_view(view)
{
svg_renderer = new QSvgRenderer(QString{ ":/res/img/spinning_icon.svg" }, m_view);
connect(svg_renderer, &QSvgRenderer::repaintNeeded,
[this] {
m_view->viewport()->update();
});
}
void TableViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyleOptionViewItem opt{ option };
initStyleOption(&opt, index);
if (index.column() == 0) {
if (condition)
{
// transform bounds, otherwise fills the whole cell
auto bounds = opt.rect;
bounds.setWidth(28);
bounds.moveTo(opt.rect.center().x() - bounds.width() / 2,
opt.rect.center().y() - bounds.height() / 2);
svg_renderer->render(painter, bounds);
}
}
QStyledItemDelegate::paint(painter, opt, index);
}
Here's a nice website where you can generate your own spinning icon and export in SVG.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With