KTextAddons

textautogeneratelistviewdelegate.cpp
1/*
2 SPDX-FileCopyrightText: 2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6#include "textautogeneratelistviewdelegate.h"
7#include "core/textautogeneratechatmodel.h"
8#include "textautogeneratecolorsandmessageviewstyle.h"
9#include "textautogeneratedelegateutils.h"
10#include "textautogeneratelistviewtextselection.h"
11#include "textautogeneratetextwidget_debug.h"
12#include <KLocalizedString>
13#include <QAbstractTextDocumentLayout>
14#include <QDesktopServices>
15#include <QDrag>
16#include <QListView>
17#include <QMimeData>
18#include <QPainter>
19#include <QTextFrame>
20#include <QTextFrameFormat>
21#include <QToolTip>
22
23using namespace TextAutogenerateText;
24TextAutogenerateListViewDelegate::TextAutogenerateListViewDelegate(QListView *view)
25 : QItemDelegate{view}
26 , mEditedIcon(QIcon::fromTheme(QStringLiteral("document-edit")))
27 , mRemoveIcon(QIcon::fromTheme(QStringLiteral("edit-delete")))
28 , mCopyIcon(QIcon::fromTheme(QStringLiteral("edit-copy")))
29 , mCancelIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")))
30 , mListView(view)
31 , mTextSelection(new TextAutogenerateListViewTextSelection(this, this))
32{
33 mSizeHintCache.setMaxEntries(32);
34 connect(mTextSelection, &TextAutogenerateListViewTextSelection::repaintNeeded, this, &TextAutogenerateListViewDelegate::updateView);
35 connect(&TextAutogenerateColorsAndMessageViewStyle::self(),
36 &TextAutogenerateColorsAndMessageViewStyle::needToUpdateColors,
37 this,
38 &TextAutogenerateListViewDelegate::slotUpdateColors);
39 slotUpdateColors();
40}
41
42TextAutogenerateListViewDelegate::~TextAutogenerateListViewDelegate() = default;
43
44void TextAutogenerateListViewDelegate::slotUpdateColors()
45{
46 const KColorScheme scheme = TextAutogenerateColorsAndMessageViewStyle::self().schemeView();
47 mEditingColorMode = scheme.foreground(KColorScheme::NegativeText).color();
48 // Q_EMIT updateView();
49}
50
51void TextAutogenerateListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
52{
53 painter->save();
54 drawBackground(painter, option, index);
55 painter->restore();
56
57 const MessageLayout layout = doLayout(option, index);
58 if (layout.textRect.isValid()) {
59 painter->save();
60 const TextAutoGenerateMessage::Sender sender = index.data(TextAutoGenerateChatModel::SenderRole).value<TextAutoGenerateMessage::Sender>();
61 const bool isUser = (sender == TextAutoGenerateMessage::Sender::User);
62 painter->setPen(QPen(Qt::NoPen));
63
64 auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](const IndexBackgroundColor &key) {
65 return key.index == index;
66 });
67 QColor messageBackgroundColor;
68 if (it != mIndexBackgroundColorList.cend()) {
69 messageBackgroundColor = it->color;
70 }
71 if (index.data(TextAutoGenerateChatModel::EditingRole).toBool()) {
72 messageBackgroundColor = mEditingColorMode;
73 }
74
75 if (isUser) {
76 if (messageBackgroundColor.isValid() && messageBackgroundColor != QColor(Qt::transparent)) {
77 painter->setBrush(QBrush(messageBackgroundColor));
78 } else {
79 painter->setBrush(QBrush(option.palette.color(QPalette::Inactive, QPalette::Midlight)));
80 }
81 } else {
82 painter->setBrush(QBrush(option.palette.color(QPalette::Active, QPalette::Mid)));
83 }
85 painter->drawRoundedRect(
86 QRect(layout.decoRect.topLeft(), QSize(layout.decoRect.width(), layout.decoRect.height() - TextAutogenerateDelegateUtils::spacingText() - 5)),
87 TextAutogenerateDelegateUtils::roundRectValue(),
88 TextAutogenerateDelegateUtils::roundRectValue());
89 painter->restore();
90 draw(painter, layout, index, option);
91 }
92 /*
93 painter->save();
94 painter->setPen(QPen(Qt::green));
95 painter->drawRect(layout.decoRect);
96 painter->restore();
97 */
98}
99
100void TextAutogenerateListViewDelegate::draw(QPainter *painter, const MessageLayout &layout, const QModelIndex &index, const QStyleOptionViewItem &option) const
101{
102 QRect rect = layout.textRect;
103 auto *doc = documentForIndex(index, rect.width());
104 if (!doc) {
105 return;
106 }
107 painter->save();
108 painter->translate(rect.left(), rect.top());
109 const QRect clip(0, 0, rect.width(), rect.height());
110
111 QAbstractTextDocumentLayout::PaintContext ctx;
112 if (mTextSelection) {
113 const QList<QAbstractTextDocumentLayout::Selection> selections = TextAutogenerateDelegateUtils::selection(mTextSelection, doc, index, option);
114 // Same as pDoc->drawContents(painter, clip) but we also set selections
115 ctx.selections = selections;
116 if (clip.isValid()) {
117 painter->setClipRect(clip);
118 ctx.clip = clip;
119 }
120 }
121 doc->documentLayout()->draw(painter, ctx);
122 painter->restore();
123 drawDateAndIcons(painter, index, option, layout);
124 drawInProgressIndicator(painter, index, option, layout);
125}
126
127void TextAutogenerateListViewDelegate::drawInProgressIndicator(QPainter *painter,
128 const QModelIndex &index,
129 const QStyleOptionViewItem &option,
130 const MessageLayout &layout) const
131{
132 Q_UNUSED(option);
133 auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(), mIndexScaleAndOpacitiesList.cend(), [index](const IndexScaleAndOpacities &key) {
134 return key.index == index;
135 });
136 if (it == mIndexScaleAndOpacitiesList.cend()) {
137 return;
138 }
139 const auto scaleAndOpacities = (*it).scaleAndOpacities;
140 painter->save();
142
143 const int dotSize = 5;
144 const int spacing = TextAutogenerateDelegateUtils::iconSpacing();
145
146 for (int i = 0; i < scaleAndOpacities.count(); ++i) {
147 const TextAutogenerateText::TextAutogenerateMessageWaitingAnswerAnimation::ScaleAndOpacity value = scaleAndOpacities.value(i);
148 painter->setOpacity(value.opacity);
149 // qDebug() << " value " << value;
150 painter->save();
151 painter->translate(layout.inProgressRect.x() + spacing + i * (dotSize + spacing), layout.inProgressRect.top() + layout.inProgressRect.height() / 2);
152 painter->rotate(45);
153 painter->scale(value.scale, value.scale);
154 painter->setBrush(Qt::black);
155 painter->drawEllipse(-dotSize / 2, -dotSize / 2, dotSize, dotSize);
156 painter->restore();
157 }
158 painter->restore();
159}
160
161void TextAutogenerateListViewDelegate::drawDateAndIcons(QPainter *painter,
162 const QModelIndex &index,
163 const QStyleOptionViewItem &option,
164 const MessageLayout &layout) const
165{
166 const bool isMouseOver = index.data(TextAutoGenerateChatModel::MouseHoverRole).toBool();
167 if (layout.dateSize.isValid()) {
168 const QPen origPen = painter->pen();
169 const qreal margin = TextAutogenerateDelegateUtils::leftLLMIndent();
170 const QString dateStr = index.data(TextAutoGenerateChatModel::DateTimeStrRole).toString();
171
172 /*
173 // qDebug() << " draw date" << dateAreaRect << layout.decoRect;
174 painter->save();
175 painter->setPen(QPen(Qt::yellow));
176 painter->drawRect(dateAreaRect);
177 painter->restore();
178 */
179
180 const QRect dateTextRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, Qt::AlignCenter, layout.dateSize, layout.dateAreaRect);
181 QColor lightColor(painter->pen().color());
182 lightColor.setAlpha(60);
183 painter->setPen(lightColor);
184 painter->drawText(dateTextRect, dateStr);
185 // qDebug() << " dateTextRect" << dateTextRect;
186 const int lineY = (layout.dateAreaRect.top() + layout.dateAreaRect.bottom()) / 2;
187 painter->drawLine(layout.dateAreaRect.left(), lineY, dateTextRect.left() - margin, lineY);
188 int iconSize = isMouseOver ? buttonIconSize(option) : 0;
189 const bool inProgress = index.data(TextAutoGenerateChatModel::FinishedRole).toBool();
190 if (!inProgress) {
191 iconSize = 2 * iconSize + TextAutogenerateDelegateUtils::iconSpacing();
192 }
193 painter->drawLine(dateTextRect.right() + margin, lineY, layout.dateAreaRect.right() - iconSize, lineY);
194 painter->setPen(origPen);
195 }
196
197 if (isMouseOver) {
198 // Draw the edited icon
199 if (layout.editedIconRect.isValid()) {
200 mEditedIcon.paint(painter, layout.editedIconRect);
201 }
202 if (layout.removeIconRect.isValid()) {
203 mRemoveIcon.paint(painter, layout.removeIconRect);
204 }
205 if (layout.copyIconRect.isValid()) {
206 mCopyIcon.paint(painter, layout.copyIconRect);
207 }
208 if (layout.cancelIconRect.isValid()) {
209 mCancelIcon.paint(painter, layout.cancelIconRect);
210 }
211 }
212}
213
214QSize TextAutogenerateListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
215{
216 const QByteArray uuid = index.data(TextAutoGenerateChatModel::UuidRole).toByteArray();
217 auto it = mSizeHintCache.find(uuid);
218 if (it != mSizeHintCache.end()) {
219 const QSize result = it->value;
220 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "TextAutogenerateListViewDelegate: SizeHint found in cache: " << result;
221 return result;
222 }
223
224 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
225
226 int additionalHeight = 0;
227 // A little bit of margin below the very last item, it just looks better
228 if (index.row() == index.model()->rowCount() - 1) {
229 additionalHeight += 4;
230 }
231
232 const QSize size = {layout.decoRect.width(), layout.decoRect.height() + additionalHeight + (layout.dateSize.isValid() ? layout.dateSize.height() : 16)};
233 if (!size.isEmpty()) {
234 mSizeHintCache.insert(uuid, size);
235 }
236 return size;
237}
238
239void TextAutogenerateListViewDelegate::clearCache()
240{
241 clearSizeHintCache();
242 mDocumentCache.clear();
243}
244
245void TextAutogenerateListViewDelegate::clearSizeHintCache()
246{
247 mSizeHintCache.clear();
248}
249
250void TextAutogenerateListViewDelegate::removeMessageCache(const QByteArray &uuid)
251{
252 mDocumentCache.remove(uuid);
253 mSizeHintCache.remove(uuid);
254}
255
256int TextAutogenerateListViewDelegate::buttonIconSize(const QStyleOptionViewItem &option) const
257{
258 return option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize);
259}
260
261TextAutogenerateListViewDelegate::MessageLayout TextAutogenerateListViewDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const
262{
263 const int iconSize = buttonIconSize(option);
264 TextAutogenerateListViewDelegate::MessageLayout layout;
265 QRect usableRect = option.rect;
266 const TextAutoGenerateMessage::Sender sender = index.data(TextAutoGenerateChatModel::SenderRole).value<TextAutoGenerateMessage::Sender>();
267 const bool isUser = (sender == TextAutoGenerateMessage::Sender::User);
268 const int indent = isUser ? TextAutogenerateDelegateUtils::leftUserIndent() : TextAutogenerateDelegateUtils::leftLLMIndent();
269 if (!isUser) {
270 const QString dateStr = index.data(TextAutoGenerateChatModel::DateTimeStrRole).toString();
271 layout.dateSize = option.fontMetrics.size(Qt::TextSingleLine, dateStr);
272 usableRect.setBottom(usableRect.bottom() + layout.dateSize.height());
273 } else {
274 usableRect.setBottom(usableRect.bottom() + iconSize);
275 }
276
277 int maxWidth = qMax(30, option.rect.width() - indent - TextAutogenerateDelegateUtils::rightIndent());
278 const QSize textSize = sizeHint(index, maxWidth, option, &layout.baseLine);
279 if (isUser) {
280 maxWidth = qMax(30, textSize.width() + TextAutogenerateDelegateUtils::rightIndent() + TextAutogenerateDelegateUtils::marginText());
281 }
282
283 if (isUser) {
284 layout.textRect = QRect(option.rect.width() - maxWidth,
285 usableRect.top() + TextAutogenerateDelegateUtils::spacingText() * 2,
286 maxWidth - TextAutogenerateDelegateUtils::marginText() * 2,
287 textSize.height() + TextAutogenerateDelegateUtils::spacingText() * 2);
288
289 layout.decoRect = QRect(layout.textRect.x() - TextAutogenerateDelegateUtils::rightIndent(),
290 usableRect.top() + TextAutogenerateDelegateUtils::spacingText(),
291 maxWidth,
292 layout.textRect.height() + TextAutogenerateDelegateUtils::spacingText() * 3);
293
294 } else {
295 layout.textRect = QRect(indent + TextAutogenerateDelegateUtils::marginText(),
296 usableRect.top() + TextAutogenerateDelegateUtils::spacingText() * 2,
297 maxWidth - TextAutogenerateDelegateUtils::marginText() * 2,
298 textSize.height() + TextAutogenerateDelegateUtils::spacingText() * 2);
299
300 layout.decoRect = QRect(indent,
301 usableRect.top() + TextAutogenerateDelegateUtils::spacingText(),
302 maxWidth,
303 layout.textRect.height() + TextAutogenerateDelegateUtils::spacingText() * 3);
304 }
305 layout.dateAreaRect = QRect(layout.decoRect.x(),
306 layout.textRect.y() + layout.textRect.height() + TextAutogenerateDelegateUtils::spacingText(),
307 layout.decoRect.width(),
308 qMax(layout.dateSize.height(), iconSize)); // the whole row
309
310 int positionIcon = layout.decoRect.right() - iconSize;
311 const int lineY = (layout.dateAreaRect.top() + layout.dateAreaRect.bottom() - iconSize) / 2;
312 if (isUser) {
313 layout.removeIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
314 positionIcon -= iconSize + TextAutogenerateDelegateUtils::iconSpacing();
315 layout.editedIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
316 positionIcon -= iconSize + TextAutogenerateDelegateUtils::iconSpacing();
317 } else {
318 const bool inProgress = index.data(TextAutoGenerateChatModel::FinishedRole).toBool();
319 if (!inProgress) {
320 layout.cancelIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
321 positionIcon -= iconSize + TextAutogenerateDelegateUtils::iconSpacing();
322 }
323 }
324 layout.copyIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
325
326 // TODO fix it
327 layout.inProgressRect = QRect(layout.textRect.x(), layout.textRect.y() + layout.textRect.height() - 20, 60, 20);
328
329 return layout;
330}
331
332QSize TextAutogenerateListViewDelegate::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option, qreal *pBaseLine) const
333{
334 Q_UNUSED(option)
335 auto *doc = documentForIndex(index, maxWidth);
336 return textSizeHint(doc, pBaseLine);
337}
338
339QSize TextAutogenerateListViewDelegate::textSizeHint(QTextDocument *doc, qreal *pBaseLine) const
340{
341 if (!doc) {
342 return {};
343 }
344 const QSize size(doc->idealWidth(), doc->size().height()); // do the layouting, required by lineAt(0) below
345
346 const QTextLine &line = doc->firstBlock().layout()->lineAt(0);
347 *pBaseLine = line.y() + line.ascent(); // relative
348 // qDebug() << " doc->" << doc->toPlainText() << " size " << size;
349 return size;
350}
351
352void TextAutogenerateListViewDelegate::selectAll(const QStyleOptionViewItem &option, const QModelIndex &index)
353{
354 Q_UNUSED(option);
355 mTextSelection->selectMessage(index);
356 mListView->update(index);
357 TextAutogenerateDelegateUtils::setClipboardSelection(mTextSelection);
358}
359
360bool TextAutogenerateListViewDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
361{
362 const QEvent::Type eventType = event->type();
363 if (eventType == QEvent::MouseButtonRelease) {
364 auto mev = static_cast<QMouseEvent *>(event);
365 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
366 if (handleMouseEvent(mev, layout.decoRect, option, index)) {
367 return true;
368 }
369 } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) {
370 auto mev = static_cast<QMouseEvent *>(event);
371 if (mev->buttons() & Qt::LeftButton) {
372 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
373 if (handleMouseEvent(mev, layout.decoRect, option, index)) {
374 return true;
375 }
376 }
377 }
378 return false;
379}
380
381bool TextAutogenerateListViewDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
382{
383 if (!index.isValid()) {
384 return false;
385 }
386 if (helpEvent->type() == QEvent::ToolTip) {
387 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
388 const QPoint helpEventPos{helpEvent->pos()};
389 if (layout.textRect.contains(helpEventPos)) {
390 const auto *doc = documentForIndex(index, layout.textRect.width());
391 if (!doc) {
392 return false;
393 }
394
395 const QPoint pos = helpEvent->pos() - layout.textRect.topLeft();
396 QString formattedTooltip;
397 if (TextAutogenerateDelegateUtils::generateToolTip(doc, pos, formattedTooltip)) {
398 QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view);
399 return true;
400 }
401 return true;
402 }
403 if (layout.removeIconRect.contains(helpEventPos)) {
404 QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Remove"), view);
405 return true;
406 }
407 if (layout.editedIconRect.contains(helpEventPos)) {
408 QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Edit..."), view);
409 return true;
410 }
411 if (layout.copyIconRect.contains(helpEventPos)) {
412 QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Copy"), view);
413 return true;
414 }
415 if (layout.cancelIconRect.contains(helpEventPos)) {
416 QToolTip::showText(helpEvent->globalPos(), i18nc("@info:tooltip", "Cancel"), view);
417 return true;
418 }
419 }
420 return false;
421}
422
423QTextDocument *TextAutogenerateListViewDelegate::documentForIndex(const QModelIndex &index, int width) const
424{
425 Q_ASSERT(index.isValid());
426 const QByteArray uuid = index.data(TextAutoGenerateChatModel::UuidRole).toByteArray();
427 Q_ASSERT(!uuid.isEmpty());
428 auto it = mDocumentCache.find(uuid);
429 if (it != mDocumentCache.end()) {
430 auto ret = it->value.get();
431 if (width != -1 && !qFuzzyCompare(ret->textWidth(), width)) {
432 ret->setTextWidth(width);
433 }
434 return ret;
435 }
436
437 const QString text = index.data(TextAutoGenerateChatModel::MessageRole).toString();
438 if (text.isEmpty()) {
439 return nullptr;
440 }
441 auto doc = createTextDocument(text, width);
442 auto ret = doc.get();
443 mDocumentCache.insert(uuid, std::move(doc));
444 return ret;
445}
446
447std::unique_ptr<QTextDocument> TextAutogenerateListViewDelegate::createTextDocument(const QString &text, int width) const
448{
449 std::unique_ptr<QTextDocument> doc(new QTextDocument);
450 // doc->setMarkdown(text);
451 doc->setHtml(text);
452 doc->setTextWidth(width);
453 QTextFrame *frame = doc->frameAt(0);
454 QTextFrameFormat frameFormat = frame->frameFormat();
455 frameFormat.setMargin(0);
456 frame->setFrameFormat(frameFormat);
457 return doc;
458}
459QString TextAutogenerateListViewDelegate::selectedText() const
460{
461 return mTextSelection->selectedText(TextAutogenerateListViewTextSelection::Format::Text);
462}
463
464bool TextAutogenerateListViewDelegate::hasSelection() const
465{
466 return mTextSelection->hasSelection();
467}
468
469bool TextAutogenerateListViewDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
470{
471 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
472 if (maybeStartDrag(event, layout.textRect, option, index)) {
473 return true;
474 }
475 return false;
476}
477
478bool TextAutogenerateListViewDelegate::maybeStartDrag(QMouseEvent *mouseEvent, QRect messageRect, const QStyleOptionViewItem &option, const QModelIndex &index)
479{
480 if (!mTextSelection->mightStartDrag()) {
481 return false;
482 }
483 if (mTextSelection->hasSelection()) {
484 const QPoint pos = mouseEvent->pos() - messageRect.topLeft();
485 const auto *doc = documentForIndex(index, messageRect.width());
486 const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
487 if (charPos != -1 && mTextSelection->contains(index, charPos)) {
488 auto mimeData = new QMimeData;
489 mimeData->setHtml(mTextSelection->selectedText(TextAutogenerateListViewTextSelection::Format::Html));
490 mimeData->setText(mTextSelection->selectedText(TextAutogenerateListViewTextSelection::Format::Text));
491 auto drag = new QDrag(const_cast<QWidget *>(option.widget));
492 drag->setMimeData(mimeData);
493 drag->exec(Qt::CopyAction);
494 mTextSelection->setMightStartDrag(false); // don't clear selection on release
495 return true;
496 }
497 }
498 return false;
499}
500bool TextAutogenerateListViewDelegate::handleMouseEvent(QMouseEvent *mouseEvent,
501 QRect messageRect,
502 const QStyleOptionViewItem &option,
503 const QModelIndex &index)
504{
505 Q_UNUSED(option)
506 if (!messageRect.contains(mouseEvent->pos())) {
507 return false;
508 }
509
510 const QPoint pos = mouseEvent->pos() - messageRect.topLeft();
511 const QEvent::Type eventType = mouseEvent->type();
512
513 // Text selection
514 switch (eventType) {
516 mTextSelection->setMightStartDrag(false);
517 if (const auto *doc = documentForIndex(index, messageRect.width())) {
518 const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
519 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "pressed at pos" << charPos;
520 if (charPos == -1) {
521 return false;
522 }
523 if (mTextSelection->contains(index, charPos) && doc->documentLayout()->hitTest(pos, Qt::ExactHit) != -1) {
524 mTextSelection->setMightStartDrag(true);
525 return true;
526 }
527
528 // QWidgetTextControl also has code to support selectBlockOnTripleClick, shift to extend selection
529 // (look there if you want to add these things)
530
531 mTextSelection->setTextSelectionStart(index, charPos);
532 return true;
533 } else {
534 mTextSelection->clear();
535 }
536 break;
538 if (!mTextSelection->mightStartDrag()) {
539 if (const auto *doc = documentForIndex(index, messageRect.width())) {
540 const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
541 if (charPos != -1) {
542 // QWidgetTextControl also has code to support isPreediting()/commitPreedit(), selectBlockOnTripleClick
543 mTextSelection->setTextSelectionEnd(index, charPos);
544 return true;
545 }
546 }
547 }
548 break;
550 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "released";
551 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
552 TextAutogenerateDelegateUtils::setClipboardSelection(mTextSelection);
553 // Clicks on links
554 if (!mTextSelection->hasSelection()) {
555 if (const auto *doc = documentForIndex(index, messageRect.width())) {
556 const QString link = doc->documentLayout()->anchorAt(pos);
557 if (!link.isEmpty()) {
558 QDesktopServices::openUrl(QUrl(link));
559 return true;
560 }
561 }
562 } else if (mTextSelection->mightStartDrag()) {
563 // clicked into selection, didn't start drag, clear it (like kwrite and QTextEdit)
564 mTextSelection->clear();
565 }
566 if (layout.editedIconRect.contains(mouseEvent->pos())) {
567 Q_EMIT editMessage(index);
568 return true;
569 } else if (layout.copyIconRect.contains(mouseEvent->pos())) {
570 Q_EMIT copyMessage(index);
571 return true;
572 } else if (layout.removeIconRect.contains(mouseEvent->pos())) {
573 Q_EMIT removeMessage(index);
574 return true;
575 } else if (layout.cancelIconRect.contains(mouseEvent->pos())) {
576 Q_EMIT cancelRequest(index);
577 return true;
578 }
579 // don't return true here, we need to send mouse release events to other helpers (ex: click on image)
580 break;
581 }
583 if (!mTextSelection->hasSelection()) {
584 if (const auto *doc = documentForIndex(index, messageRect.width())) {
585 const int charPos = doc->documentLayout()->hitTest(pos, Qt::FuzzyHit);
586 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) << "double-clicked at pos" << charPos;
587 if (charPos == -1) {
588 return false;
589 }
590 mTextSelection->selectWordUnderCursor(index, charPos);
591 return true;
592 }
593 }
594 break;
595 default:
596 break;
597 }
598
599 return false;
600}
601
602void TextAutogenerateListViewDelegate::needUpdateIndexBackground(const QPersistentModelIndex &index, const QColor &color)
603{
604 removeNeedUpdateIndexBackground(index);
605 const IndexBackgroundColor back{.index = index, .color = color};
606 mIndexBackgroundColorList.append(std::move(back));
607}
608
609void TextAutogenerateListViewDelegate::removeNeedUpdateIndexBackground(const QPersistentModelIndex &index)
610{
611 auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](const IndexBackgroundColor &key) {
612 return key.index == index;
613 });
614 if (it != mIndexBackgroundColorList.cend()) {
615 mIndexBackgroundColorList.erase(it);
616 }
617}
618
619void TextAutogenerateListViewDelegate::needUpdateWaitingAnswerAnimation(
620 const QPersistentModelIndex &index,
621 const QList<TextAutogenerateMessageWaitingAnswerAnimation::ScaleAndOpacity> &scaleAndOpacities)
622{
623 removeNeedUpdateWaitingAnswerAnimation(index);
624 const IndexScaleAndOpacities back{.index = index, .scaleAndOpacities = scaleAndOpacities};
625 mIndexScaleAndOpacitiesList.append(std::move(back));
626}
627
628void TextAutogenerateListViewDelegate::removeNeedUpdateWaitingAnswerAnimation(const QPersistentModelIndex &index)
629{
630 auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(), mIndexScaleAndOpacitiesList.cend(), [index](const IndexScaleAndOpacities &key) {
631 return key.index == index;
632 });
633 if (it != mIndexScaleAndOpacitiesList.cend()) {
634 mIndexScaleAndOpacitiesList.erase(it);
635 }
636}
637
638#include "moc_textautogeneratelistviewdelegate.cpp"
QBrush foreground(ForegroundRole=NormalText) const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KIOCORE_EXPORT CopyJob * link(const QList< QUrl > &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
const QList< QKeySequence > & back()
virtual int rowCount(const QModelIndex &parent) const const=0
QString anchorAt(const QPointF &position) const const
virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const const=0
const QColor & color() const const
bool isEmpty() const const
bool isValid() const const
bool openUrl(const QUrl &url)
void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
Q_EMITQ_EMIT
virtual bool event(QEvent *e)
QObject * sender() const const
void drawEllipse(const QPoint &center, int rx, int ry)
void drawLine(const QLine &line)
void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void drawText(const QPoint &position, const QString &text)
const QPen & pen() const const
void restore()
void rotate(qreal angle)
void save()
void scale(qreal sx, qreal sy)
void setBrush(Qt::BrushStyle style)
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void setOpacity(qreal opacity)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void translate(const QPoint &offset)
QColor color() const const
int bottom() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
int left() const const
int right() const const
void setBottom(int y)
int top() const const
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
int height() const const
bool isEmpty() const const
bool isValid() const const
int width() const const
bool isEmpty() const const
PM_ButtonIconSize
QRect alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSize &size, const QRect &rectangle)
AlignCenter
CopyAction
transparent
FuzzyHit
LayoutDirectionAuto
LeftButton
TextSingleLine
QTextLayout * layout() const const
QAbstractTextDocumentLayout * documentLayout() const const
QTextBlock firstBlock() const const
qreal idealWidth() const const
void setHtml(const QString &html)
void setTextWidth(qreal width)
QTextFrameFormat frameFormat() const const
void setFrameFormat(const QTextFrameFormat &format)
void setMargin(qreal margin)
QTextLine lineAt(int i) const const
qreal ascent() const const
qreal y() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
bool toBool() const const
QByteArray toByteArray() const const
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 12:06:03 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.