File indexing completed on 2024-05-12 16:27:22

0001 /*
0002    SPDX-FileCopyrightText: 2022-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "messagelistviewbase.h"
0008 #include "model/messagesmodel.h"
0009 #include "room/plugins/plugintext.h"
0010 #include "room/plugins/plugintextinterface.h"
0011 #include "room/textpluginmanager.h"
0012 #include <QAbstractItemModel>
0013 #include <QApplication>
0014 #include <QClipboard>
0015 #include <QMouseEvent>
0016 #include <QScrollBar>
0017 
0018 MessageListViewBase::MessageListViewBase(QWidget *parent)
0019     : QListView(parent)
0020 {
0021 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0022     setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::BottomEdge}));
0023 #endif
0024     setSelectionMode(QAbstractItemView::NoSelection);
0025     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0026     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); // nicer in case of huge messages
0027     setWordWrap(true); // so that the delegate sizeHint is called again when the width changes
0028     // only the lineedit takes focus
0029     setFocusPolicy(Qt::NoFocus);
0030     scrollToBottom();
0031     setMouseTracking(true);
0032 
0033     const QVector<PluginText *> plugins = TextPluginManager::self()->pluginsList();
0034     for (PluginText *plugin : plugins) {
0035         connect(plugin, &PluginText::errorMessage, this, &MessageListViewBase::errorMessage);
0036         connect(plugin, &PluginText::successMessage, this, &MessageListViewBase::successMessage);
0037         mPluginTextInterface.append(plugin->createInterface(this));
0038     }
0039 }
0040 
0041 MessageListViewBase::~MessageListViewBase()
0042 {
0043     qDeleteAll(mPluginTextInterface);
0044 }
0045 
0046 void MessageListViewBase::resizeEvent(QResizeEvent *ev)
0047 {
0048     QListView::resizeEvent(ev);
0049 
0050     // Fix not being really at bottom when the view gets reduced by the header widget becoming taller
0051     checkIfAtBottom();
0052     maybeScrollToBottom(); // this forces a layout in QAIV, which then changes the vbar max value
0053     updateVerticalPageStep();
0054     Q_EMIT needToClearSizeHintCache();
0055 }
0056 
0057 void MessageListViewBase::checkIfAtBottom()
0058 {
0059     auto *vbar = verticalScrollBar();
0060     mAtBottom = vbar->value() == vbar->maximum();
0061 }
0062 
0063 void MessageListViewBase::maybeScrollToBottom()
0064 {
0065     if (mAtBottom) {
0066         scrollToBottom();
0067     }
0068 }
0069 
0070 void MessageListViewBase::updateVerticalPageStep()
0071 {
0072     verticalScrollBar()->setPageStep(viewport()->height() * 3 / 4);
0073 }
0074 
0075 void MessageListViewBase::handleMouseEvent(QMouseEvent *event)
0076 {
0077     const QPersistentModelIndex index = indexAt(event->pos());
0078     if (index.isValid()) {
0079         // When the cursor hovers another message, hide/show the reaction icon accordingly
0080         if (mCurrentIndex != index) {
0081             if (mCurrentIndex.isValid()) {
0082                 auto lastModel = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
0083                 lastModel->setData(mCurrentIndex, false, MessagesModel::HoverHighLight);
0084             }
0085             mCurrentIndex = index;
0086             auto model = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
0087             model->setData(mCurrentIndex, true, MessagesModel::HoverHighLight);
0088         }
0089 
0090         QStyleOptionViewItem options = listViewOptions();
0091         options.rect = visualRect(mCurrentIndex);
0092         if (mouseEvent(event, options, mCurrentIndex)) {
0093             update(mCurrentIndex);
0094         }
0095     }
0096 }
0097 
0098 void MessageListViewBase::mouseReleaseEvent(QMouseEvent *event)
0099 {
0100     handleMouseEvent(event);
0101 }
0102 
0103 void MessageListViewBase::mouseDoubleClickEvent(QMouseEvent *event)
0104 {
0105     handleMouseEvent(event);
0106 }
0107 
0108 void MessageListViewBase::mousePressEvent(QMouseEvent *event)
0109 {
0110     mPressedPosition = event->pos();
0111     handleMouseEvent(event);
0112 }
0113 
0114 void MessageListViewBase::mouseMoveEvent(QMouseEvent *event)
0115 {
0116     // Drag support
0117     const int distance = (event->pos() - mPressedPosition).manhattanLength();
0118     if (distance > QApplication::startDragDistance()) {
0119         mPressedPosition = {};
0120         const QPersistentModelIndex index = indexAt(event->pos());
0121         if (index.isValid()) {
0122             QStyleOptionViewItem options = listViewOptions();
0123             options.rect = visualRect(index);
0124             if (maybeStartDrag(event, options, index)) {
0125                 return;
0126             }
0127         }
0128     }
0129     handleMouseEvent(event);
0130 }
0131 
0132 void MessageListViewBase::leaveEvent(QEvent *event)
0133 {
0134     if (mCurrentIndex.isValid()) {
0135         auto lastModel = const_cast<QAbstractItemModel *>(mCurrentIndex.model());
0136         lastModel->setData(mCurrentIndex, false, MessagesModel::HoverHighLight);
0137         mCurrentIndex = QPersistentModelIndex();
0138     }
0139     QListView::leaveEvent(event);
0140 }
0141 
0142 QStyleOptionViewItem MessageListViewBase::listViewOptions() const
0143 {
0144 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0145     return QListView::viewOptions();
0146 #else
0147     QStyleOptionViewItem option;
0148     initViewItemOption(&option);
0149     return option;
0150 #endif
0151 }
0152 
0153 bool MessageListViewBase::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0154 {
0155     Q_UNUSED(event);
0156     Q_UNUSED(option);
0157     Q_UNUSED(index);
0158     return false;
0159 }
0160 
0161 bool MessageListViewBase::mouseEvent(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0162 {
0163     Q_UNUSED(event);
0164     Q_UNUSED(option);
0165     Q_UNUSED(index);
0166     return false;
0167 }
0168 
0169 void MessageListViewBase::addTextPlugins(QMenu *menu, const QString &selectedText)
0170 {
0171     for (PluginTextInterface *interface : std::as_const(mPluginTextInterface)) {
0172         interface->setSelectedText(selectedText);
0173         interface->addAction(menu);
0174     }
0175 }
0176 
0177 QString MessageListViewBase::selectedText(const QModelIndex &index)
0178 {
0179     Q_UNUSED(index);
0180     return {};
0181 }
0182 
0183 void MessageListViewBase::copyMessageToClipboard(const QModelIndex &index)
0184 {
0185     const QString messageText = selectedText(index);
0186     if (messageText.isEmpty()) {
0187         return;
0188     }
0189     QClipboard *clip = QApplication::clipboard();
0190     clip->setText(messageText, QClipboard::Clipboard);
0191     clip->setText(messageText, QClipboard::Selection);
0192 }
0193 
0194 #include "moc_messagelistviewbase.cpp"