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>
20#include <QTextFrameFormat>
23using namespace TextAutogenerateText;
24TextAutogenerateListViewDelegate::TextAutogenerateListViewDelegate(
QListView *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")))
31 , mTextSelection(new TextAutogenerateListViewTextSelection(this, this))
33 mSizeHintCache.setMaxEntries(32);
34 connect(mTextSelection, &TextAutogenerateListViewTextSelection::repaintNeeded,
this, &TextAutogenerateListViewDelegate::updateView);
35 connect(&TextAutogenerateColorsAndMessageViewStyle::self(),
36 &TextAutogenerateColorsAndMessageViewStyle::needToUpdateColors,
38 &TextAutogenerateListViewDelegate::slotUpdateColors);
42TextAutogenerateListViewDelegate::~TextAutogenerateListViewDelegate() =
default;
44void TextAutogenerateListViewDelegate::slotUpdateColors()
46 const KColorScheme scheme = TextAutogenerateColorsAndMessageViewStyle::self().schemeView();
51void TextAutogenerateListViewDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index)
const
57 const MessageLayout layout = doLayout(option, index);
58 if (layout.textRect.isValid()) {
60 const TextAutoGenerateMessage::Sender
sender = index.
data(TextAutoGenerateChatModel::SenderRole).
value<TextAutoGenerateMessage::Sender>();
61 const bool isUser = (
sender == TextAutoGenerateMessage::Sender::User);
64 auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](
const IndexBackgroundColor &key) {
65 return key.index == index;
67 QColor messageBackgroundColor;
68 if (it != mIndexBackgroundColorList.cend()) {
69 messageBackgroundColor = it->color;
71 if (index.
data(TextAutoGenerateChatModel::EditingRole).
toBool()) {
72 messageBackgroundColor = mEditingColorMode;
77 painter->
setBrush(QBrush(messageBackgroundColor));
86 QRect(layout.decoRect.topLeft(), QSize(layout.decoRect.width(), layout.decoRect.height() - TextAutogenerateDelegateUtils::spacingText() - 5)),
87 TextAutogenerateDelegateUtils::roundRectValue(),
88 TextAutogenerateDelegateUtils::roundRectValue());
90 draw(painter, layout, index, option);
100void TextAutogenerateListViewDelegate::draw(QPainter *painter,
const MessageLayout &layout,
const QModelIndex &index,
const QStyleOptionViewItem &option)
const
102 QRect rect = layout.textRect;
103 auto *doc = documentForIndex(index, rect.
width());
109 const QRect clip(0, 0, rect.
width(), rect.
height());
111 QAbstractTextDocumentLayout::PaintContext ctx;
112 if (mTextSelection) {
113 const QList<QAbstractTextDocumentLayout::Selection> selections = TextAutogenerateDelegateUtils::selection(mTextSelection, doc, index, option);
115 ctx.selections = selections;
116 if (clip.isValid()) {
121 doc->documentLayout()->draw(painter, ctx);
123 drawDateAndIcons(painter, index, option, layout);
124 drawInProgressIndicator(painter, index, option, layout);
127void TextAutogenerateListViewDelegate::drawInProgressIndicator(QPainter *painter,
128 const QModelIndex &index,
129 const QStyleOptionViewItem &option,
130 const MessageLayout &layout)
const
133 auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(), mIndexScaleAndOpacitiesList.cend(), [index](
const IndexScaleAndOpacities &key) {
134 return key.index == index;
136 if (it == mIndexScaleAndOpacitiesList.cend()) {
139 const auto scaleAndOpacities = (*it).scaleAndOpacities;
143 const int dotSize = 5;
144 const int spacing = TextAutogenerateDelegateUtils::iconSpacing();
146 for (
int i = 0; i < scaleAndOpacities.count(); ++i) {
147 const TextAutogenerateText::TextAutogenerateMessageWaitingAnswerAnimation::ScaleAndOpacity value = scaleAndOpacities.value(i);
151 painter->
translate(layout.inProgressRect.x() + spacing + i * (dotSize + spacing), layout.inProgressRect.top() + layout.inProgressRect.height() / 2);
153 painter->
scale(value.scale, value.scale);
155 painter->
drawEllipse(-dotSize / 2, -dotSize / 2, dotSize, dotSize);
161void TextAutogenerateListViewDelegate::drawDateAndIcons(QPainter *painter,
162 const QModelIndex &index,
163 const QStyleOptionViewItem &option,
164 const MessageLayout &layout)
const
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();
181 QColor lightColor(painter->
pen().
color());
182 lightColor.setAlpha(60);
183 painter->
setPen(lightColor);
184 painter->
drawText(dateTextRect, dateStr);
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();
191 iconSize = 2 * iconSize + TextAutogenerateDelegateUtils::iconSpacing();
193 painter->
drawLine(dateTextRect.
right() + margin, lineY, layout.dateAreaRect.right() - iconSize, lineY);
199 if (layout.editedIconRect.isValid()) {
200 mEditedIcon.paint(painter, layout.editedIconRect);
202 if (layout.removeIconRect.isValid()) {
203 mRemoveIcon.paint(painter, layout.removeIconRect);
205 if (layout.copyIconRect.isValid()) {
206 mCopyIcon.paint(painter, layout.copyIconRect);
208 if (layout.cancelIconRect.isValid()) {
209 mCancelIcon.paint(painter, layout.cancelIconRect);
214QSize TextAutogenerateListViewDelegate::sizeHint(
const QStyleOptionViewItem &option,
const QModelIndex &index)
const
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;
224 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
226 int additionalHeight = 0;
229 additionalHeight += 4;
232 const QSize size = {layout.decoRect.
width(), layout.decoRect.
height() + additionalHeight + (layout.dateSize.
isValid() ? layout.dateSize.
height() : 16)};
234 mSizeHintCache.insert(uuid, size);
239void TextAutogenerateListViewDelegate::clearCache()
241 clearSizeHintCache();
242 mDocumentCache.clear();
245void TextAutogenerateListViewDelegate::clearSizeHintCache()
247 mSizeHintCache.clear();
250void TextAutogenerateListViewDelegate::removeMessageCache(
const QByteArray &uuid)
252 mDocumentCache.remove(uuid);
253 mSizeHintCache.remove(uuid);
256int TextAutogenerateListViewDelegate::buttonIconSize(
const QStyleOptionViewItem &option)
const
261TextAutogenerateListViewDelegate::MessageLayout TextAutogenerateListViewDelegate::doLayout(
const QStyleOptionViewItem &option,
const QModelIndex &index)
const
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();
270 const QString dateStr = index.
data(TextAutoGenerateChatModel::DateTimeStrRole).
toString();
277 int maxWidth = qMax(30, option.rect.width() - indent - TextAutogenerateDelegateUtils::rightIndent());
278 const QSize textSize = sizeHint(index, maxWidth, option, &layout.baseLine);
280 maxWidth = qMax(30, textSize.
width() + TextAutogenerateDelegateUtils::rightIndent() + TextAutogenerateDelegateUtils::marginText());
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);
289 layout.decoRect = QRect(layout.textRect.
x() - TextAutogenerateDelegateUtils::rightIndent(),
290 usableRect.
top() + TextAutogenerateDelegateUtils::spacingText(),
292 layout.textRect.
height() + TextAutogenerateDelegateUtils::spacingText() * 3);
295 layout.textRect = QRect(indent + TextAutogenerateDelegateUtils::marginText(),
296 usableRect.
top() + TextAutogenerateDelegateUtils::spacingText() * 2,
297 maxWidth - TextAutogenerateDelegateUtils::marginText() * 2,
298 textSize.
height() + TextAutogenerateDelegateUtils::spacingText() * 2);
300 layout.decoRect = QRect(indent,
301 usableRect.
top() + TextAutogenerateDelegateUtils::spacingText(),
303 layout.textRect.
height() + TextAutogenerateDelegateUtils::spacingText() * 3);
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));
310 int positionIcon = layout.decoRect.
right() - iconSize;
311 const int lineY = (layout.dateAreaRect.
top() + layout.dateAreaRect.
bottom() - iconSize) / 2;
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();
318 const bool inProgress = index.
data(TextAutoGenerateChatModel::FinishedRole).
toBool();
320 layout.cancelIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
321 positionIcon -= iconSize + TextAutogenerateDelegateUtils::iconSpacing();
324 layout.copyIconRect = QRect(positionIcon, lineY, iconSize, iconSize);
327 layout.inProgressRect = QRect(layout.textRect.
x(), layout.textRect.
y() + layout.textRect.
height() - 20, 60, 20);
332QSize TextAutogenerateListViewDelegate::sizeHint(
const QModelIndex &index,
int maxWidth,
const QStyleOptionViewItem &option, qreal *pBaseLine)
const
335 auto *doc = documentForIndex(index, maxWidth);
336 return textSizeHint(doc, pBaseLine);
339QSize TextAutogenerateListViewDelegate::textSizeHint(QTextDocument *doc, qreal *pBaseLine)
const
347 *pBaseLine = line.
y() + line.
ascent();
352void TextAutogenerateListViewDelegate::selectAll(
const QStyleOptionViewItem &option,
const QModelIndex &index)
355 mTextSelection->selectMessage(index);
356 mListView->update(index);
357 TextAutogenerateDelegateUtils::setClipboardSelection(mTextSelection);
360bool TextAutogenerateListViewDelegate::mouseEvent(QEvent *event,
const QStyleOptionViewItem &option,
const QModelIndex &index)
364 auto mev =
static_cast<QMouseEvent *
>(
event);
365 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
366 if (handleMouseEvent(mev, layout.decoRect, option, index)) {
370 auto mev =
static_cast<QMouseEvent *
>(
event);
372 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
373 if (handleMouseEvent(mev, layout.decoRect, option, index)) {
381bool TextAutogenerateListViewDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view,
const QStyleOptionViewItem &option,
const QModelIndex &index)
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());
395 const QPoint pos = helpEvent->pos() - layout.textRect.
topLeft();
396 QString formattedTooltip;
397 if (TextAutogenerateDelegateUtils::generateToolTip(doc, pos, formattedTooltip)) {
403 if (layout.removeIconRect.
contains(helpEventPos)) {
407 if (layout.editedIconRect.
contains(helpEventPos)) {
411 if (layout.copyIconRect.
contains(helpEventPos)) {
415 if (layout.cancelIconRect.
contains(helpEventPos)) {
423QTextDocument *TextAutogenerateListViewDelegate::documentForIndex(
const QModelIndex &index,
int width)
const
426 const QByteArray uuid = index.
data(TextAutoGenerateChatModel::UuidRole).
toByteArray();
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);
437 const QString text = index.
data(TextAutoGenerateChatModel::MessageRole).
toString();
441 auto doc = createTextDocument(text, width);
442 auto ret = doc.get();
443 mDocumentCache.insert(uuid, std::move(doc));
447std::unique_ptr<QTextDocument> TextAutogenerateListViewDelegate::createTextDocument(
const QString &text,
int width)
const
449 std::unique_ptr<QTextDocument> doc(
new QTextDocument);
453 QTextFrame *frame = doc->frameAt(0);
454 QTextFrameFormat frameFormat = frame->
frameFormat();
459QString TextAutogenerateListViewDelegate::selectedText()
const
461 return mTextSelection->selectedText(TextAutogenerateListViewTextSelection::Format::Text);
464bool TextAutogenerateListViewDelegate::hasSelection()
const
466 return mTextSelection->hasSelection();
469bool TextAutogenerateListViewDelegate::maybeStartDrag(QMouseEvent *event,
const QStyleOptionViewItem &option,
const QModelIndex &index)
471 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
472 if (maybeStartDrag(
event, layout.textRect, option, index)) {
478bool TextAutogenerateListViewDelegate::maybeStartDrag(QMouseEvent *mouseEvent, QRect messageRect,
const QStyleOptionViewItem &option,
const QModelIndex &index)
480 if (!mTextSelection->mightStartDrag()) {
483 if (mTextSelection->hasSelection()) {
484 const QPoint pos = mouseEvent->pos() - messageRect.
topLeft();
485 const auto *doc = documentForIndex(index, messageRect.
width());
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);
494 mTextSelection->setMightStartDrag(
false);
500bool TextAutogenerateListViewDelegate::handleMouseEvent(QMouseEvent *mouseEvent,
502 const QStyleOptionViewItem &option,
503 const QModelIndex &index)
506 if (!messageRect.
contains(mouseEvent->pos())) {
510 const QPoint pos = mouseEvent->pos() - messageRect.
topLeft();
516 mTextSelection->setMightStartDrag(
false);
517 if (
const auto *doc = documentForIndex(index, messageRect.
width())) {
519 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) <<
"pressed at pos" << charPos;
524 mTextSelection->setMightStartDrag(
true);
531 mTextSelection->setTextSelectionStart(index, charPos);
534 mTextSelection->clear();
538 if (!mTextSelection->mightStartDrag()) {
539 if (
const auto *doc = documentForIndex(index, messageRect.
width())) {
543 mTextSelection->setTextSelectionEnd(index, charPos);
550 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) <<
"released";
551 const TextAutogenerateListViewDelegate::MessageLayout layout = doLayout(option, index);
552 TextAutogenerateDelegateUtils::setClipboardSelection(mTextSelection);
554 if (!mTextSelection->hasSelection()) {
555 if (
const auto *doc = documentForIndex(index, messageRect.
width())) {
557 if (!
link.isEmpty()) {
562 }
else if (mTextSelection->mightStartDrag()) {
564 mTextSelection->clear();
566 if (layout.editedIconRect.
contains(mouseEvent->pos())) {
567 Q_EMIT editMessage(index);
569 }
else if (layout.copyIconRect.
contains(mouseEvent->pos())) {
570 Q_EMIT copyMessage(index);
572 }
else if (layout.removeIconRect.
contains(mouseEvent->pos())) {
573 Q_EMIT removeMessage(index);
575 }
else if (layout.cancelIconRect.
contains(mouseEvent->pos())) {
576 Q_EMIT cancelRequest(index);
583 if (!mTextSelection->hasSelection()) {
584 if (
const auto *doc = documentForIndex(index, messageRect.
width())) {
586 qCDebug(TEXTAUTOGENERATETEXT_WIDGET_LOG) <<
"double-clicked at pos" << charPos;
590 mTextSelection->selectWordUnderCursor(index, charPos);
602void TextAutogenerateListViewDelegate::needUpdateIndexBackground(
const QPersistentModelIndex &index,
const QColor &color)
604 removeNeedUpdateIndexBackground(index);
605 const IndexBackgroundColor
back{.index = index, .color = color};
606 mIndexBackgroundColorList.append(std::move(back));
609void TextAutogenerateListViewDelegate::removeNeedUpdateIndexBackground(
const QPersistentModelIndex &index)
611 auto it = std::find_if(mIndexBackgroundColorList.cbegin(), mIndexBackgroundColorList.cend(), [index](
const IndexBackgroundColor &key) {
612 return key.index == index;
614 if (it != mIndexBackgroundColorList.cend()) {
615 mIndexBackgroundColorList.erase(it);
619void TextAutogenerateListViewDelegate::needUpdateWaitingAnswerAnimation(
620 const QPersistentModelIndex &index,
621 const QList<TextAutogenerateMessageWaitingAnswerAnimation::ScaleAndOpacity> &scaleAndOpacities)
623 removeNeedUpdateWaitingAnswerAnimation(index);
624 const IndexScaleAndOpacities
back{.index = index, .scaleAndOpacities = scaleAndOpacities};
625 mIndexScaleAndOpacitiesList.append(std::move(back));
628void TextAutogenerateListViewDelegate::removeNeedUpdateWaitingAnswerAnimation(
const QPersistentModelIndex &index)
630 auto it = std::find_if(mIndexScaleAndOpacitiesList.cbegin(), mIndexScaleAndOpacitiesList.cend(), [index](
const IndexScaleAndOpacities &key) {
631 return key.index == index;
633 if (it != mIndexScaleAndOpacitiesList.cend()) {
634 mIndexScaleAndOpacitiesList.erase(it);
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
virtual bool event(QEvent *e)
QObject * sender() const const
void drawEllipse(const QPoint ¢er, 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 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
bool contains(const QPoint &point, bool proper) const const
QPoint topLeft() const const
bool isEmpty() const const
bool isValid() const const
bool isEmpty() const const
QRect alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSize &size, const QRect &rectangle)
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
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