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"));