/* * 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" int MessageDelegate::avatarHeight(50); int MessageDelegate::margin(6); constexpr int textMargin = 2; constexpr int statusIconSize = 16; constexpr float nickFontMultiplier = 1.1; constexpr float dateFontMultiplier = 0.8; constexpr int bubbleMargin = 6; constexpr int bubbleBorderRadius = 3; MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), bodyFont(), nickFont(), dateFont(), bodyRenderer(new QTextDocument()), nickMetrics(nickFont), dateMetrics(dateFont), buttonHeight(0), buttonWidth(0), barHeight(0), buttons(new std::map()), bars(new std::map()), statusIcons(new std::map()), pencilIcons(new std::map()), previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false), currentId(""), selection(0, 0) { bodyRenderer->setDocumentMargin(0); 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: *previews){ delete pair.second; } delete statusIcons; delete pencilIcons; delete idsToKeep; delete buttons; delete bars; delete previews; delete bodyRenderer; } 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(); 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); } if (data.correction.corrected) { QLabel* pencilIcon = getPencilIcon(data); pencilIcon->setParent(vp); if (data.sentByMe) { pencilIcon->move(opt.rect.left() + statusIconSize + margin, currentY); } else { pencilIcon->move(opt.rect.right() - statusIconSize - margin, currentY); } pencilIcon->show(); } else { std::map::const_iterator itr = pencilIcons->find(data.id); if (itr != pencilIcons->end()) { delete itr->second; pencilIcons->erase(itr); } } 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; } 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; } void MessageDelegate::initializeFonts(const QFont& font) { bodyFont = font; nickFont = font; dateFont = font; nickFont.setBold(true); float ndps = nickFont.pointSizeF(); if (ndps != -1) { nickFont.setPointSizeF(ndps * nickFontMultiplier); } else { nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier); } dateFont.setItalic(true); float dps = dateFont.pointSizeF(); if (dps != -1) { dateFont.setPointSizeF(dps * dateFontMultiplier); } else { dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier); } bodyFont.setKerning(false); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); bodyRenderer->setDefaultFont(bodyFont); Preview::initializeFont(bodyFont); } 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 { 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 > 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; } 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(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); } } }