5252#include " quaternionroom.h"
5353#include " chatedit.h"
5454#include " htmlfilter.h"
55+ #include " models/messageeventmodel.h"
5556
5657static auto DefaultPlaceholderText ()
5758{
@@ -84,6 +85,13 @@ ChatRoomWidget::ChatRoomWidget(MainWindow* parent)
8485 m_hudCaption->setFont (f);
8586 m_hudCaption->setTextFormat (Qt::RichText);
8687
88+ m_modeIndicator = new QToolButton ();
89+ m_modeIndicator->setAutoRaise (true );
90+ m_modeIndicator->hide ();
91+ connect (m_modeIndicator, &QToolButton::clicked, this , [this ] {
92+ setDefaultMode ();
93+ });
94+
8795 auto attachButton = new QToolButton ();
8896 attachButton->setAutoRaise (true );
8997 m_attachAction = new QAction (QIcon::fromTheme (" mail-attachment" ),
@@ -207,6 +215,7 @@ ChatRoomWidget::ChatRoomWidget(MainWindow* parent)
207215 layout->addWidget (m_hudCaption);
208216 {
209217 auto inputLayout = new QHBoxLayout;
218+ inputLayout->addWidget (m_modeIndicator);
210219 inputLayout->addWidget (attachButton);
211220 inputLayout->addWidget (m_chatEdit);
212221 layout->addLayout (inputLayout);
@@ -262,6 +271,7 @@ void ChatRoomWidget::setRoom(QuaternionRoom* newRoom)
262271 }
263272 typingChanged ();
264273 encryptionChanged ();
274+ setDefaultMode ();
265275}
266276
267277void ChatRoomWidget::typingChanged ()
@@ -416,38 +426,79 @@ void ChatRoomWidget::sendFile()
416426 m_chatEdit->setPlaceholderText (DefaultPlaceholderText ());
417427}
418428
419- #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
420- void sendMarkdown (QuaternionRoom* room, const QTextDocumentFragment& text)
421- {
422- room->postHtmlText (text.toPlainText (),
423- HtmlFilter::toMatrixHtml (text.toHtml (), room,
424- HtmlFilter::ConvertMarkdown));
425- }
426- #endif
427-
428429void ChatRoomWidget::sendMessage ()
429430{
430431 if (m_chatEdit->toPlainText ().startsWith (" //" ))
431432 QTextCursor (m_chatEdit->document ()).deleteChar ();
432433
434+ QTextCursor c (m_chatEdit->document ());
435+ c.select (QTextCursor::Document);
436+ sendMessageFromFragment (c.selection ());
437+ }
438+
439+ void ChatRoomWidget::sendMessageFromFragment (const QTextDocumentFragment& text,
440+ enum TextFormat textFormat)
441+ {
442+ const auto & plainText = text.toPlainText ();
443+ const auto & htmlText =
444+ HtmlFilter::toMatrixHtml (text.toHtml (), currentRoom (),
433445#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
434- if (m_uiSettings. get ( " auto_markdown " , false )) {
435- sendMarkdown ( currentRoom (),
436- QTextDocumentFragment (m_chatEdit-> document ()));
437- return ;
438- }
446+ ((textFormat != Plaintext
447+ && m_uiSettings. get ( " auto_markdown " , false ))
448+ || textFormat == Markdown)
449+ ? HtmlFilter::ConvertMarkdown
450+ :
439451#endif
440- const auto & plainText = m_chatEdit->toPlainText ();
441- const auto & htmlText =
442- HtmlFilter::toMatrixHtml (m_chatEdit->toHtml (), currentRoom ());
452+ HtmlFilter::Default);
443453 Q_ASSERT (!plainText.isEmpty () && !htmlText.isEmpty ());
444454 // Send plain text if htmlText has no markup or just <br/> elements
445455 // (those are easily represented as line breaks in plain text)
446456 static const QRegularExpression MarkupRE { " <(?![Bb][Rr])" };
447- if (htmlText.contains (MarkupRE))
448- currentRoom ()->postHtmlText (plainText, htmlText);
449- else
450- currentRoom ()->postPlainText (plainText);
457+
458+ using namespace Quotient ;
459+ switch (mode) {
460+ case Editing:
461+ // Any quotation is ignored intentionally, see
462+ // https://spec.matrix.org/latest/client-server-api/#edits-of-replies
463+ {
464+ auto eventRelation = EventRelation::replace (
465+ referencedEventIndex ().data (MessageEventModel::EventIdRole).toString ()
466+ );
467+ EventContent::TextContent* textContent;
468+ if (htmlText.contains (MarkupRE)) {
469+ textContent = new EventContent::TextContent (htmlText,
470+ QStringLiteral (" text/html" ), eventRelation);
471+ } else {
472+ textContent = new EventContent::TextContent (" " ,
473+ QStringLiteral (" text/plain" ), eventRelation);
474+ }
475+ auto roomMessageEvent = new RoomMessageEvent (plainText,
476+ MessageEventType::Text, textContent);
477+ currentRoom ()->postEvent (roomMessageEvent);
478+ }
479+ break ;
480+ case Replying:
481+ {
482+ QString htmlQuotation, plainTextQuotation;
483+ auto reference = referencedEventIndex ();
484+ htmlQuotation = reference.data (MessageEventModel::HtmlQuotationRole).toString ();
485+ plainTextQuotation = reference.data (MessageEventModel::QuotationRole).toString ();
486+ auto textContent = new EventContent::TextContent (htmlQuotation + htmlText,
487+ QStringLiteral (" text/html" ),
488+ EventRelation::replyTo (
489+ reference.data (MessageEventModel::EventIdRole).toString ()
490+ ));
491+ auto roomMessageEvent = new RoomMessageEvent (plainTextQuotation + plainText,
492+ MessageEventType::Text, textContent);
493+ currentRoom ()->postEvent (roomMessageEvent);
494+ }
495+ break ;
496+ default :
497+ if (htmlText.contains (MarkupRE))
498+ currentRoom ()->postHtmlText (plainText, htmlText);
499+ else
500+ currentRoom ()->postPlainText (plainText);
501+ }
451502}
452503
453504static auto NothingToSendMsg ()
@@ -651,7 +702,8 @@ QString ChatRoomWidget::sendCommand(QStringView command,
651702 const auto & plainMsg = m_chatEdit->toPlainText ().mid (CmdLen);
652703 if (plainMsg.isEmpty ())
653704 return NothingToSendMsg ();
654- currentRoom ()->postPlainText (plainMsg);
705+ const auto & fragment = QTextDocumentFragment::fromPlainText (plainMsg);
706+ sendMessageFromFragment (fragment, Plaintext);
655707 return {};
656708 }
657709 if (command == u" html" )
@@ -670,9 +722,7 @@ QString ChatRoomWidget::sendCommand(QStringView command,
670722 .arg (errorPos).arg (errorString);
671723
672724 const auto & fragment = QTextDocumentFragment::fromHtml (cleanQtHtml);
673- currentRoom ()->postHtmlText (fragment.toPlainText (),
674- HtmlFilter::toMatrixHtml (fragment.toHtml (),
675- currentRoom ()));
725+ sendMessageFromFragment (fragment);
676726 return {};
677727 }
678728 if (command == u" md" ) {
@@ -682,7 +732,7 @@ QString ChatRoomWidget::sendCommand(QStringView command,
682732 QTextCursor c (m_chatEdit->document ());
683733 c.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, 4 );
684734 c.movePosition (QTextCursor::End, QTextCursor::KeepAnchor);
685- sendMarkdown ( currentRoom (), c.selection ());
735+ sendMessageFromFragment ( c.selection (), Markdown );
686736 return {};
687737#else
688738 return tr (" Your build of Quaternion doesn't support Markdown" );
@@ -731,6 +781,43 @@ void ChatRoomWidget::sendInput()
731781 }
732782
733783 m_chatEdit->saveInput ();
784+ setDefaultMode ();
785+ }
786+
787+ void ChatRoomWidget::setDefaultMode ()
788+ {
789+ mode = Default;
790+ emit m_timelineWidget->setCurrentIndex (-1 );
791+ referencedEventId = " " ;
792+ m_modeIndicator->hide ();
793+ }
794+
795+ bool ChatRoomWidget::setReferringMode (const int newMode, const QString& eventId,
796+ const char * icon_name)
797+ {
798+ Q_ASSERT ( newMode == Replying || newMode == Editing );
799+ // Actually, we could let the user to refer to pending events too but in
800+ // this case we would need a universal pointer instead of event id. Now the
801+ // user cannot start to edit a pending message which might be annoying if
802+ // transactions are acknowledged slowly.
803+ if (!m_timelineWidget->indexOf (eventId).isValid ())
804+ return false ;
805+ mode = newMode;
806+ referencedEventId = eventId;
807+ emit m_timelineWidget->setCurrentIndex (referencedEventIndex ().row ());
808+
809+ m_modeIndicator->setIcon (QIcon::fromTheme (icon_name));
810+
811+ m_modeIndicator->show ();
812+ return true ;
813+ }
814+
815+ QModelIndex ChatRoomWidget::referencedEventIndex ()
816+ {
817+ Q_ASSERT (!referencedEventId.isEmpty ());
818+ auto idx = m_timelineWidget->indexOf (referencedEventId);
819+ Q_ASSERT (idx.isValid ());
820+ return idx;
734821}
735822
736823ChatRoomWidget::completions_t
@@ -789,6 +876,36 @@ void ChatRoomWidget::quote(const QString& htmlText)
789876 m_chatEdit->insertPlainText (sendString);
790877}
791878
879+ void ChatRoomWidget::reply (const QString& eventId)
880+ {
881+ if (!setReferringMode (Replying, eventId, " mail-reply-sender" )) {
882+ setHudHtml (tr (" Referenced message not suitable for replying (yet)" ));
883+ return ;
884+ }
885+ setHudHtml (tr (" Reply message" ));
886+ }
887+
888+ void ChatRoomWidget::edit (const QString& eventId)
889+ {
890+ if (!setReferringMode (Editing, eventId, " edit-entry" )) {
891+ setHudHtml (tr (" Referenced message not suitable for editing (yet)" ));
892+ return ;
893+ }
894+
895+ auto htmlText = referencedEventIndex ()
896+ .data (MessageEventModel::NudeRichBodyRole)
897+ .toString ();
898+ m_chatEdit->clear ();
899+ // We can never be sure which input format was used to build this message.
900+ // It can be markdown, matrixhtml (`/html`), rich text paste or a mixture of
901+ // these. Perhaps the best solution is to introduce a generic format
902+ // converter into ChatEdit's contextmenu which can be used any time by the
903+ // user. By using it, the user could convert this rich text to the desired
904+ // format.
905+ m_chatEdit->insertHtml (htmlText);
906+ setHudHtml (tr (" Edit message" ));
907+ }
908+
792909void ChatRoomWidget::resizeEvent (QResizeEvent*)
793910{
794911 m_chatEdit->setMaximumHeight (maximumChatEditHeight ());
0 commit comments