/* * Squawk messenger. * Copyright (C) 2019 Yury Gubich * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "messagedelegate.h" #include "messagefeed.h" #include "shared/defines.h" constexpr int textMargin = 2; constexpr int statusIconSize = 16; constexpr int bubbleMargin = 6; constexpr int bubbleBorderRadius = 3; int MessageDelegate::avatarHeight(50); int MessageDelegate::margin(6); MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), bodyFont(Shared::Global::getInstance()->defaultFont), nickFont(Shared::Global::getInstance()->headerFont), dateFont(Shared::Global::getInstance()->smallFont), nickMetrics(Shared::Global::getInstance()->headerFontMetrics), dateMetrics(Shared::Global::getInstance()->smallFontMetrics), bodyRenderer(), buttonHeight(0), buttonWidth(0), barHeight(0), buttons(), bars(), statusIcons(), pencilIcons(), encryptionIcons(), previews(), idsToKeep(), clearingWidgets(false), currentId(""), selection(0, 0) { bodyRenderer.setDocumentMargin(0); bodyRenderer.setDefaultFont(bodyFont); QPushButton btn(QCoreApplication::translate("MessageLine", "Download")); buttonHeight = btn.sizeHint().height(); buttonWidth = btn.sizeHint().width(); QProgressBar bar; barHeight = bar.sizeHint().height(); } MessageDelegate::~MessageDelegate() { for (const std::pair& pair: buttons) delete pair.second; for (const std::pair& pair: bars) delete pair.second; for (const std::pair& pair: statusIcons) delete pair.second; for (const std::pair& pair: pencilIcons) delete pair.second; for (const std::pair& pair: encryptionIcons) delete pair.second; for (const std::pair& pair: previews) delete pair.second; } void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QVariant vi = index.data(Models::MessageFeed::Bulk); if (!vi.isValid()) return; Models::FeedItem data = qvariant_cast(vi); painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); paintBubble(data, painter, option); bool ntds = needToDrawSender(index, data); if (ntds || option.rect.y() < 1) paintAvatar(data, index, option, painter); QStyleOptionViewItem opt = option; opt.rect = option.rect.adjusted(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2); if (!data.sentByMe) opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop; else opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; QRect rect; if (ntds) { painter->setFont(nickFont); painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); } painter->save(); switch (data.attach.state) { case Models::none: clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed break; //but it's a possible performance problem case Models::uploading: paintPreview(data, painter, opt); [[fallthrough]]; case Models::downloading: paintBar(getBar(data), painter, data.sentByMe, opt); break; case Models::remote: paintButton(getButton(data), painter, data.sentByMe, opt); break; case Models::ready: case Models::local: clearHelperWidget(data); paintPreview(data, painter, opt); break; case Models::errorDownload: { paintButton(getButton(data), painter, data.sentByMe, opt); paintComment(data, painter, opt); } break; case Models::errorUpload:{ clearHelperWidget(data); paintPreview(data, painter, opt); paintComment(data, painter, opt); } break; } painter->restore(); QWidget* vp = static_cast(painter->device()); paintBody(data, painter, opt); painter->setFont(dateFont); QColor q = painter->pen().color(); QString dateString = data.date.toLocalTime().toString("hh:mm"); q.setAlpha(180); painter->setPen(q); painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect); int currentY = opt.rect.y(); int statusOffset = statusIconSize; if (data.sentByMe) { QLabel* statusIcon = getStatusIcon(data); statusIcon->setParent(vp); statusIcon->move(opt.rect.left(), currentY); statusIcon->show(); opt.rect.adjust(0, statusIconSize + textMargin, 0, 0); statusOffset = statusIconSize + margin; } if (data.correction.corrected) { QLabel* pencilIcon = getPencilIcon(data); pencilIcon->setParent(vp); if (data.sentByMe) pencilIcon->move(opt.rect.left() + statusOffset, currentY); else pencilIcon->move(opt.rect.right() - statusOffset, currentY); pencilIcon->show(); statusOffset += statusIconSize + margin; } else { std::map::const_iterator itr = pencilIcons.find(data.id); if (itr != pencilIcons.end()) { delete itr->second; pencilIcons.erase(itr); } } if (data.encryption != Shared::EncryptionProtocol::none) { QLabel* shieldIcon = getEncryptionIcon(data); shieldIcon->setParent(vp); if (data.sentByMe) shieldIcon->move(opt.rect.left() + statusOffset, currentY); else shieldIcon->move(opt.rect.right() - statusOffset, currentY); shieldIcon->show(); statusOffset += statusIconSize + margin; } painter->restore(); if (clearingWidgets) idsToKeep.insert(data.id); } void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const { painter->save(); if (data.sentByMe) painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight)); else painter->setBrush(option.palette.brush(QPalette::Window)); painter->setPen(Qt::NoPen); painter->drawRoundedRect(option.rect, bubbleBorderRadius, bubbleBorderRadius); painter->restore(); } void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const { int currentRow = index.row(); int y = option.rect.y(); bool firstAttempt = true; QIcon icon(data.avatar); while (y < 0 && currentRow > 0) { QRect rect; if (firstAttempt) { firstAttempt = false; rect = option.rect; } else { QModelIndex ci = index.siblingAtRow(currentRow); if ( (ci.data(Models::MessageFeed::Sender).toString() != data.sender) || (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0) ) { break; } //TODO this is really bad, but for now I have no idea how else can I access the view; const QAbstractItemView* view = static_cast(option.styleObject); rect = view->visualRect(ci); } y = std::min(0, rect.bottom() - margin - avatarHeight); --currentRow; } QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight); QPainterPath path; int ax; if (data.sentByMe) ax = option.rect.x() + option.rect.width() + margin; else ax = margin; path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight); painter->save(); painter->setClipPath(path); painter->drawPixmap(ax, y + margin / 2, pixmap); painter->restore(); } bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const { return (option.rect.y() < 1) || needToDrawSender(index, data); } bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const { if (index.row() == index.model()->rowCount() - 1) { return true; } else { QModelIndex prevIndex = index.siblingAtRow(index.row() + 1); return (prevIndex.data(Models::MessageFeed::Sender).toString() != data.sender) || (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0); } } QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2); QStyleOptionViewItem opt = option; opt.rect = messageRect; QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); QSize messageSize(0, 0); if (data.text.size() > 0) { bodyRenderer.setPlainText(data.text); bodyRenderer.setTextWidth(messageRect.size().width()); QSizeF size = bodyRenderer.size(); size.setWidth(bodyRenderer.idealWidth()); messageSize = QSize(std::ceil(size.width()), std::ceil(size.height())); messageSize.rheight() += textMargin; } switch (data.attach.state) { case Models::none: break; case Models::uploading: messageSize.rheight() += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect).height() + textMargin; [[fallthrough]]; case Models::downloading: messageSize.rheight() += barHeight + textMargin; messageSize.setWidth(messageRect.width()); break; case Models::remote: messageSize.rheight() += buttonHeight + textMargin; messageSize.setWidth(std::max(messageSize.width(), buttonWidth)); break; case Models::ready: case Models::local: { QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect); messageSize.rheight() += aSize.height() + textMargin; messageSize.setWidth(std::max(messageSize.width(), aSize.width())); } break; case Models::errorDownload: { QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size(); messageSize.rheight() += commentSize.height() + buttonHeight + textMargin * 2; messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), buttonWidth))); } break; case Models::errorUpload: { QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect); QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size(); messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2; messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width()))); } break; } if (needToDrawSender(index, data)) { QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); messageSize.rheight() += senderSize.height() + textMargin; messageSize.setWidth(std::max(senderSize.width(), messageSize.width())); } QString dateString = data.date.toLocalTime().toString("hh:mm"); QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size(); messageSize.rheight() += bubbleMargin * 1.5; messageSize.rheight() += dateSize.height() > statusIconSize ? dateSize.height() : statusIconSize; int statusWidth = dateSize.width() + statusIconSize + margin; if (data.correction.corrected) statusWidth += statusIconSize + margin; if (data.encryption != Shared::EncryptionProtocol::none) statusWidth += statusIconSize + margin; messageSize.setWidth(std::max(statusWidth, messageSize.width())); messageSize.rwidth() += 2 * bubbleMargin; return messageSize; } QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const { QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2); if (needToDrawSender(index, data)) localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); int attachHeight = 0; switch (data.attach.state) { case Models::none: break; case Models::uploading: attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin; [[fallthrough]]; case Models::downloading: attachHeight += barHeight + textMargin; break; case Models::remote: attachHeight += buttonHeight + textMargin; break; case Models::ready: case Models::local: { QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); attachHeight += aSize.height() + textMargin; } break; case Models::errorDownload: { QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); attachHeight += commentSize.height() + buttonHeight + textMargin * 2; } break; case Models::errorUpload: { QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); attachHeight += aSize.height() + commentSize.height() + textMargin * 2; } break; } int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize); localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin)); return localHint; } QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(point)) { QPoint translated = point - localHint.topLeft(); bodyRenderer.setHtml(Shared::processMessageBody(data.text)); bodyRenderer.setTextWidth(localHint.size().width()); return bodyRenderer.documentLayout()->anchorAt(translated); } } return QString(); } void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { QString anchor = getAnchor(point, index, sizeHint); if (anchor.size() > 0) emit openLink(anchor); } QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(point)) { QPoint translated = point - localHint.topLeft(); bodyRenderer.setHtml(Shared::processMessageBody(data.text)); bodyRenderer.setTextWidth(localHint.size().width()); QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout(); int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit); QTextCursor cursor(&bodyRenderer); cursor.setPosition(position, QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); selection.first = cursor.anchor(); selection.second = cursor.position(); currentId = data.id; if (selection.first != selection.second) { return cursor.selectedText(); } } } return ""; } Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(point)) { QPoint translated = point - localHint.topLeft(); bodyRenderer.setHtml(Shared::processMessageBody(data.text)); bodyRenderer.setTextWidth(localHint.size().width()); QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout(); QString anchor = lay->anchorAt(translated); if (anchor.size() > 0) { return Shared::Hover::anchor; } else { int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit); if (position != -1) return Shared::Hover::text; } } } return Shared::Hover::nothing; } QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(start)) { QPoint tl = localHint.topLeft(); QPoint first = start - tl; QPoint last = end - tl; last.setX(std::max(last.x(), 0)); last.setX(std::min(last.x(), localHint.width() - 1)); last.setY(std::max(last.y(), 0)); last.setY(std::min(last.y(), localHint.height())); bodyRenderer.setHtml(Shared::processMessageBody(data.text)); bodyRenderer.setTextWidth(localHint.size().width()); selection.first = bodyRenderer.documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit); selection.second = bodyRenderer.documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit); currentId = data.id; if (selection.first != selection.second) { QTextCursor cursor(&bodyRenderer); cursor.setPosition(selection.first, QTextCursor::MoveAnchor); cursor.setPosition(selection.second, QTextCursor::KeepAnchor); return cursor.selectedText(); } } } return ""; } QString MessageDelegate::clearSelection() { QString lastSelectedId = currentId; currentId = ""; selection = std::pair(0, 0); return lastSelectedId; } bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { //qDebug() << event->type(); return QStyledItemDelegate::editorEvent(event, model, option, index); } int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start; if (sentByMe) start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()}; else start = option.rect.topLeft(); QWidget* vp = static_cast(painter->device()); btn->setParent(vp); btn->move(start); btn->show(); option.rect.adjust(0, buttonHeight + textMargin, 0, 0); return btn->width(); } int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { painter->setFont(dateFont); QColor q = painter->pen().color(); q.setAlpha(180); painter->setPen(q); QRect rect; painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); option.rect.adjust(0, rect.height() + textMargin, 0, 0); return rect.width(); } int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { SHARED_UNUSED(sentByMe); QPoint start = option.rect.topLeft(); bar->resize(option.rect.width(), barHeight); painter->translate(start); bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); option.rect.adjust(0, barHeight + textMargin, 0, 0); return option.rect.width(); } int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { Preview* preview = 0; std::map::iterator itr = previews.find(data.id); QSize size = option.rect.size(); QString path = Shared::resolvePath(data.attach.localPath); if (itr != previews.end()) { preview = itr->second; preview->actualize(path, size, option.rect.topLeft()); } else { QWidget* vp = static_cast(painter->device()); preview = new Preview(path, size, option.rect.topLeft(), vp); previews.insert(std::make_pair(data.id, preview)); } if (!preview->isFileReachable()) //this is the situation when the file preview couldn't be painted because the file was moved emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can //handle being invalid for as long as I need and can be even become valid again with a new path QSize pSize(preview->size()); option.rect.adjust(0, pSize.height() + textMargin, 0, 0); return pSize.width(); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const { std::map::const_iterator itr = buttons.find(data.id); FeedButton* result = 0; if (itr != buttons.end()) { result = itr->second; } else { std::map::const_iterator barItr = bars.find(data.id); if (barItr != bars.end()) { delete barItr->second; bars.erase(barItr); } } if (result == 0) { result = new FeedButton(); result->messageId = data.id; result->setText(QCoreApplication::translate("MessageLine", "Download")); buttons.insert(std::make_pair(data.id, result)); connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed); } return result; } QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const { std::map::const_iterator barItr = bars.find(data.id); QProgressBar* result = 0; if (barItr != bars.end()) { result = barItr->second; } else { std::map::const_iterator itr = buttons.find(data.id); if (itr != buttons.end()) { delete itr->second; buttons.erase(itr); } } if (result == 0) { result = new QProgressBar(); result->setRange(0, 100); bars.insert(std::make_pair(data.id, result)); } result->setValue(data.attach.progress * 100); return result; } QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const { std::map::const_iterator itr = statusIcons.find(data.id); QLabel* result = 0; if (itr != statusIcons.end()) { result = itr->second; } else { result = new QLabel(); statusIcons.insert(std::make_pair(data.id, result)); } QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast(data.state)])); QString tt = Shared::Global::getName(data.state); if (data.state == Shared::Message::State::error) { if (data.error.size() > 0) tt += ": " + data.error; } if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally result->setPixmap(q.pixmap(statusIconSize)); //it invokes an infinite cycle of repaint result->setToolTip(tt); //may be it's better to subclass and store last condition in int? } return result; } QLabel* MessageDelegate::getPencilIcon(const Models::FeedItem& data) const { std::map::const_iterator itr = pencilIcons.find(data.id); QLabel* result = 0; if (itr != pencilIcons.end()) { result = itr->second; } else { result = new QLabel(); QIcon icon = Shared::icon("edit-rename"); result->setPixmap(icon.pixmap(statusIconSize)); pencilIcons.insert(std::make_pair(data.id, result)); } result->setToolTip("Last time edited: " + data.correction.lastCorrection.toLocalTime().toString() + "\nOriginal message: " + data.correction.original); return result; } QLabel* MessageDelegate::getEncryptionIcon(const Models::FeedItem& data) const { std::map::const_iterator itr = encryptionIcons.find(data.id); QLabel* result = 0; if (itr != encryptionIcons.end()) { result = itr->second; } else { result = new QLabel(); QIcon icon = Shared::icon("secure"); result->setPixmap(icon.pixmap(statusIconSize)); encryptionIcons.insert(std::make_pair(data.id, result)); result->setToolTip("Encrypted: " + Shared::Global::getName(data.encryption)); } return result; } template void removeElements(std::map& elements, std::set& idsToKeep) { std::set toRemove; for (const std::pair& pair: elements) { if (idsToKeep.find(pair.first) == idsToKeep.end()) { delete pair.second; toRemove.insert(pair.first); } } for (const QString& key : toRemove) elements.erase(key); } int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { if (data.text.size() > 0) { bodyRenderer.setHtml(Shared::processMessageBody(data.text)); bodyRenderer.setTextWidth(option.rect.size().width()); painter->save(); painter->translate(option.rect.topLeft()); if (data.id == currentId) { QTextCursor cursor(&bodyRenderer); cursor.setPosition(selection.first, QTextCursor::MoveAnchor); cursor.setPosition(selection.second, QTextCursor::KeepAnchor); QTextCharFormat format = cursor.charFormat(); format.setBackground(option.palette.color(QPalette::Active, QPalette::Highlight)); format.setForeground(option.palette.color(QPalette::Active, QPalette::HighlightedText)); cursor.setCharFormat(format); } bodyRenderer.drawContents(painter); painter->restore(); QSize bodySize(std::ceil(bodyRenderer.idealWidth()), std::ceil(bodyRenderer.size().height())); option.rect.adjust(0, bodySize.height() + textMargin, 0, 0); return bodySize.width(); } return 0; } void MessageDelegate::beginClearWidgets() { idsToKeep.clear(); clearingWidgets = true; } void MessageDelegate::endClearWidgets() { if (clearingWidgets) { removeElements(buttons, idsToKeep); removeElements(bars, idsToKeep); removeElements(statusIcons, idsToKeep); removeElements(pencilIcons, idsToKeep); removeElements(encryptionIcons, idsToKeep); removeElements(previews, idsToKeep); idsToKeep.clear(); clearingWidgets = false; } } void MessageDelegate::onButtonPushed() const { FeedButton* btn = static_cast(sender()); emit buttonPushed(btn->messageId); } void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const { std::map::const_iterator itr = buttons.find(data.id); if (itr != buttons.end()) { delete itr->second; buttons.erase(itr); } else { std::map::const_iterator barItr = bars.find(data.id); if (barItr != bars.end()) { delete barItr->second; bars.erase(barItr); } } }