50 #include <QFileDialog>
52 #include <QMessageBox>
53 #include <QRegularExpression>
54 #include <QStringBuilder>
58 #include <KF5/SonnetUi/sonnet/spellcheckdecorator.h>
67 static const QSize FILE_FLYOUT_SIZE{24, 24};
68 static const short FOOT_BUTTONS_SPACING = 2;
69 static const short MESSAGE_EDIT_HEIGHT = 50;
70 static const short MAIN_FOOT_LAYOUT_SPACING = 5;
71 static const QString FONT_STYLE[]{
"normal",
"italic",
"oblique"};
79 static QString fontToCss(
const QFont& font,
const QString& name)
82 "font-family: \"%2\"; "
84 "font-style: \"%4\"; "
85 "font-weight: normal;}"};
86 return result.arg(name).arg(font.family()).arg(font.pixelSize()).arg(FONT_STYLE[font.style()]);
104 QString res = it->resolveToxPk(pk);
105 if (!res.isEmpty()) {
115 const QString STYLE_PATH = QStringLiteral(
"chatForm/buttons.css");
121 template <
class T,
class Fun>
122 QPushButton* createButton(
const QString& name, T*
self, Fun onClickSlot)
124 QPushButton* btn =
new QPushButton();
127 btn->setAttribute(Qt::WA_LayoutUsesWidgetRect);
128 btn->setObjectName(name);
129 btn->setProperty(
"state",
"green");
131 QObject::connect(btn, &QPushButton::clicked,
self, onClickSlot);
139 : QWidget(parent, Qt::Window)
141 , audioInputFlag(
false)
142 , audioOutputFlag(
false)
144 , messageDispatcher(messageDispatcher)
149 dateInfo =
new QLabel(
this);
150 chatWidget =
new ChatWidget(chatLog, core,
this);
152 dateInfo->setAlignment(Qt::AlignHCenter);
153 dateInfo->setVisible(
false);
161 #ifdef SPELL_CHECKING
163 decorator =
new Sonnet::SpellCheckDecorator(msgEdit);
177 QHBoxLayout* fileLayout =
new QHBoxLayout(fileFlyout);
178 fileLayout->addWidget(screenshotButton);
179 fileLayout->setContentsMargins(0, 0, 0, 0);
180 fileLayout->setSpacing(0);
181 fileLayout->setMargin(0);
183 msgEdit->setFixedHeight(MESSAGE_EDIT_HEIGHT);
184 msgEdit->setFrameStyle(QFrame::NoFrame);
186 bodySplitter =
new QSplitter(Qt::Vertical,
this);
187 QWidget* contentWidget =
new QWidget(
this);
188 bodySplitter->addWidget(contentWidget);
190 QVBoxLayout* mainLayout =
new QVBoxLayout();
191 mainLayout->addWidget(bodySplitter);
192 mainLayout->setMargin(0);
194 setLayout(mainLayout);
196 QVBoxLayout* footButtonsSmall =
new QVBoxLayout();
197 footButtonsSmall->setSpacing(FOOT_BUTTONS_SPACING);
198 footButtonsSmall->addWidget(emoteButton);
199 footButtonsSmall->addWidget(fileButton);
201 QHBoxLayout* mainFootLayout =
new QHBoxLayout();
202 mainFootLayout->addWidget(msgEdit);
203 mainFootLayout->addLayout(footButtonsSmall);
204 mainFootLayout->addSpacing(MAIN_FOOT_LAYOUT_SPACING);
205 mainFootLayout->addWidget(sendButton);
206 mainFootLayout->setSpacing(0);
208 contentLayout =
new QVBoxLayout(contentWidget);
209 contentLayout->addWidget(searchForm);
210 contentLayout->addWidget(dateInfo);
211 contentLayout->addWidget(chatWidget);
212 contentLayout->addLayout(mainFootLayout);
214 quoteAction = menu.addAction(QIcon(), QString(),
this, SLOT(quoteSelectedText()),
215 QKeySequence(Qt::ALT + Qt::Key_Q));
216 addAction(quoteAction);
219 goToCurrentDateAction = menu.addAction(QIcon(), QString(),
this, SLOT(goToCurrentDate()),
220 QKeySequence(Qt::CTRL + Qt::Key_G));
221 addAction(goToCurrentDateAction);
225 searchAction = menu.addAction(QIcon(), QString(),
this, SLOT(searchFormShow()),
226 QKeySequence(Qt::CTRL + Qt::Key_F));
227 addAction(searchAction);
231 menu.addActions(chatWidget->actions());
234 clearAction = menu.addAction(QIcon::fromTheme(
"edit-clear"), QString(),
235 this, SLOT(clearChatArea()),
236 QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_L));
237 addAction(clearAction);
239 copyLinkAction = menu.addAction(QIcon(), QString(),
this, SLOT(copyLink()));
242 loadHistoryAction = menu.addAction(QIcon(), QString(),
this, SLOT(onLoadHistory()));
244 menu.addAction(QIcon::fromTheme(
"document-save"), QString(),
this, SLOT(onExportChat()));
246 connect(chatWidget, &ChatWidget::customContextMenuRequested,
this,
262 fileFlyout->setFixedSize(FILE_FLYOUT_SIZE);
263 fileFlyout->setParent(
this);
264 fileButton->installEventFilter(
this);
265 fileFlyout->installEventFilter(
this);
285 fileFlyout->move(pos.x() - size.width(), pos.y());
308 const auto shouldUseTimestamp = [
this] (
ChatLogIdx idx) {
331 qWarning(
"Unexpected system message type %d",
static_cast<int>(
message.messageType));
338 if (shouldUseTimestamp(idx)) {
369 #if QT_VERSION < QT_VERSION_CHECK(5, 12, 4) && QT_VERSION > QT_VERSION_CHECK(5, 11, 0)
390 if (e->type() == QEvent::KeyPress) {
391 QKeyEvent* ke =
static_cast<QKeyEvent*
>(e);
392 if ((ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier)
393 && !ke->text().isEmpty()) {
403 return QWidget::event(e);
408 QWidget* sender =
static_cast<QWidget*
>(QObject::sender());
409 pos = sender->mapToGlobal(pos);
412 bool clickedOnLink =
false;
416 QString linkTarget = clickedText->
getLinkAt(scenePos);
417 if (!linkTarget.isEmpty()) {
418 clickedOnLink =
true;
429 auto msg =
msgEdit->toPlainText();
455 widget.installEventFilter(
this);
457 QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
460 -QPoint(widget.
sizeHint().width() / 2, widget.
sizeHint().height()) - QPoint(0, 10);
461 widget.exec(sender->mapToGlobal(pos));
468 QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
492 + fontToCss(font,
"QTextEdit"));
506 systemMessage.
args = std::move(messageArgs);
513 Timestamp*
const timestamp = qobject_cast<Timestamp*>(chatLine->getContent(2));
534 QMessageBox::StandardButton mboxResult =
535 QMessageBox::question(
this, tr(
"Confirmation"),
536 tr(
"Are you sure that you want to clear all displayed messages?"),
537 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
538 if (mboxResult == QMessageBox::No) {
557 QWidget::hideEvent(
event);
563 QWidget::resizeEvent(
event);
569 if (ev &&
event->type() == QEvent::KeyPress) {
570 QKeyEvent* key =
static_cast<QKeyEvent*
>(
event);
579 if (!qobject_cast<QWidget*>(
object)->isEnabled())
582 switch (
event->type()) {
587 case QEvent::Leave: {
588 QPoint flyPos =
fileFlyout->mapToGlobal(QPoint());
591 QPoint filePos =
fileButton->mapToGlobal(QPoint());
594 QRect region = QRect(flyPos, flySize).united(QRect(filePos, fileSize));
596 if (!region.contains(QCursor::pos()))
602 case QEvent::MouseButtonPress:
617 if (selectedText.isEmpty())
624 QString quote = selectedText;
626 quote.insert(0,
"> ");
627 quote.replace(QRegExp(QString(
"\r\n|[\r\n\u2028\u2029]")), QString(
"\n> "));
639 QApplication::clipboard()->setText(linkText);
660 QString path = QFileDialog::getSaveFileName(Q_NULLPTR, tr(
"Save chat log"));
661 if (path.isEmpty()) {
666 if (!
file.open(QIODevice::WriteOnly | QIODevice::Text)) {
677 QString timestamp = item.
getTimestamp().time().toString(
"hh:mm:ss");
678 QString datestamp = item.getTimestamp().date().toString(
"yyyy-MM-dd");
679 QString author = item.getDisplayName();
682 % QString{datestamp %
'\t' % timestamp %
'\t' % author %
'\t'
683 % item.getContentAsMessage().message.content %
'\n'};
685 file.write(buffer.toUtf8());
698 const auto effectiveTopLine = (
dateInfo->isVisible() && prevLine)
699 ? prevLine : topLine;
701 const auto date =
getTime(effectiveTopLine);
703 if (date.isValid() && date.date() != QDate::currentDate()) {
718 clearAction->setText(tr(
"Clear displayed messages"));