diff --git a/ChangeLog b/ChangeLog index edd0e00d8b..671bee51d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ * Added animation timeline editor * Added screen blend mode * Disabled built-in server pending rewrite + * Added `/alert` chat command 2021-09-12 Version 2.1.20 * Updated Portugese translations diff --git a/src/desktop/chat/chatbox.cpp b/src/desktop/chat/chatbox.cpp index ef7171a866..df7a1ab06e 100644 --- a/src/desktop/chat/chatbox.cpp +++ b/src/desktop/chat/chatbox.cpp @@ -59,6 +59,11 @@ ChatBox::ChatBox(Document *doc, QWidget *parent) connect(m_chatWidget, &ChatWidget::message, this, &ChatBox::message); connect(m_chatWidget, &ChatWidget::detachRequested, this, &ChatBox::detachFromParent); + connect(m_chatWidget, &ChatWidget::expandRequested, this, [this]() { + if(m_state == State::Collapsed) { + emit expandPlease(); + } + }); connect(doc, &Document::canvasChanged, this, &ChatBox::onCanvasChanged); connect(doc, &Document::serverLoggedIn, this, &ChatBox::onServerLogin); diff --git a/src/desktop/chat/chatbox.h b/src/desktop/chat/chatbox.h index 6c7c944f6a..21c0dd05f2 100644 --- a/src/desktop/chat/chatbox.h +++ b/src/desktop/chat/chatbox.h @@ -60,6 +60,9 @@ private slots: //! The chatbox was either expanded or collapsed void expandedChanged(bool isExpanded); + //! Request that the chatbox be expanded + void expandPlease(); + //! Detached chat box should be re-attached and reparented (or it will be destroyed) void reattachNowPlease(); diff --git a/src/desktop/chat/chatwidget.cpp b/src/desktop/chat/chatwidget.cpp index addda847bc..289cc75a7a 100644 --- a/src/desktop/chat/chatwidget.cpp +++ b/src/desktop/chat/chatwidget.cpp @@ -62,6 +62,7 @@ struct Chat { "color: #eff0f1;" "margin: 1px 0 1px 0" "}" + ".alert { background: #da4453 }" ".shout { background: #34292c }" ".shout .tab { background: #da4453 }" ".action { font-style: italic }" @@ -71,6 +72,7 @@ struct Chat { ".op { color: #f47750 }" ".mod { color: #ed1515 }" ".timestamp { color: #8d8d8d }" + ".alert .timestamp { color: #eff0f1 }" "a:link { color: #1d99f3 }" ); } @@ -79,6 +81,7 @@ struct Chat { void appendMessage(int userId, const QString &usernameSpan, const QString &message, bool shout); void appendMessageCompact(int userId, const QString &usernameSpan, const QString &message, bool shout); void appendAction(const QString &usernameSpan, const QString &message); + void appendAlert(const QString &usernameSpan, const QString &message); void appendNotification(const QString &message); }; @@ -418,6 +421,32 @@ void Chat::appendMessage(int userId, const QString &usernameSpan, const QString lastMessageTs = ts; } +void Chat::appendAlert(const QString &usernameSpan, const QString &message) +{ + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::End); + + lastAppendedId = -2; + + cursor.insertHtml(QStringLiteral( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
%1%2
%3
" + ).arg( + usernameSpan, + timestamp(), + htmlutils::newlineToBr(message) + ) + ); +} + void Chat::appendAction(const QString &usernameSpan, const QString &message) { QTextCursor cursor(doc); @@ -557,15 +586,15 @@ void ChatWidget::receiveMessage(int sender, int recipient, uint8_t tflags, uint8 Q_ASSERT(d->chats.contains(chatId)); Chat &chat = d->chats[chatId]; - if(oflags & rustpile::ChatMessage_OFLAGS_ACTION) { + if(tflags & rustpile::ChatMessage_TFLAGS_ALERT) { + chat.appendAlert(d->usernameSpan(sender), safetext); + emit expandRequested(); + } else if(oflags & rustpile::ChatMessage_OFLAGS_ACTION) chat.appendAction(d->usernameSpan(sender), safetext); - - } else { - if(d->compactMode) - chat.appendMessageCompact(sender, d->usernameSpan(sender), safetext, oflags & rustpile::ChatMessage_OFLAGS_SHOUT); - else - chat.appendMessage(sender, d->usernameSpan(sender), safetext, oflags & rustpile::ChatMessage_OFLAGS_SHOUT); - } + else if(d->compactMode) + chat.appendMessageCompact(sender, d->usernameSpan(sender), safetext, oflags & rustpile::ChatMessage_OFLAGS_SHOUT); + else + chat.appendMessage(sender, d->usernameSpan(sender), safetext, oflags & rustpile::ChatMessage_OFLAGS_SHOUT); if(chatId != d->currentChat) { for(int i=0;itabs->count();++i) { @@ -590,16 +619,20 @@ void ChatWidget::setPinnedMessage(const QString &message) void ChatWidget::systemMessage(const QString& message, bool alert) { - Q_UNUSED(alert); const bool wasAtEnd = d->isAtEnd(); - d->publicChat().appendNotification(message.toHtmlEscaped()); + if(alert) { + d->publicChat().appendAlert(QString(), message); + emit expandRequested(); + } else + d->publicChat().appendNotification(message.toHtmlEscaped()); + if(wasAtEnd) d->scrollToEnd(0); } void ChatWidget::sendMessage(const QString &msg) { - const uint8_t tflags = d->preserveChat ? rustpile::ChatMessage_TFLAGS_BYPASS : 0; + uint8_t tflags = d->preserveChat ? rustpile::ChatMessage_TFLAGS_BYPASS : 0; uint8_t oflags = 0; auto chatmsg = msg; @@ -623,6 +656,12 @@ void ChatWidget::sendMessage(const QString &msg) oflags = rustpile::ChatMessage_OFLAGS_SHOUT; } + } else if(cmd == QStringLiteral("alert")) { + if(msg.length() > 2) { + chatmsg = params; + tflags |= rustpile::ChatMessage_TFLAGS_ALERT; + } + } else if(cmd == QStringLiteral("me")) { if(!params.isEmpty()) { oflags = rustpile::ChatMessage_OFLAGS_ACTION; @@ -656,6 +695,7 @@ void ChatWidget::sendMessage(const QString &msg) "/help - show this message\n" "/clear - clear chat window\n" "/! - make an announcement (recorded in session history)\n" + "/alert - send a high priority alert\n" "/me - send action type message\n" "/pin - pin a message to the top of the chat box (Ops only)\n" "/unpin - remove pinned message\n" diff --git a/src/desktop/chat/chatwidget.h b/src/desktop/chat/chatwidget.h index f22b40d6d7..e90cb02680 100644 --- a/src/desktop/chat/chatwidget.h +++ b/src/desktop/chat/chatwidget.h @@ -84,6 +84,7 @@ private slots: signals: void message(const net::Envelope &msg); void detachRequested(); + void expandRequested(); private: struct Private; diff --git a/src/desktop/mainwindow.cpp b/src/desktop/mainwindow.cpp index ea8b29e83e..446267daa2 100644 --- a/src/desktop/mainwindow.cpp +++ b/src/desktop/mainwindow.cpp @@ -2539,6 +2539,7 @@ void MainWindow::setupActions() connect(m_chatbox, &widgets::ChatBox::expandedChanged, toggleChat, &QAction::setChecked); connect(m_chatbox, &widgets::ChatBox::expandedChanged, m_statusChatButton, &QToolButton::hide); + connect(m_chatbox, &widgets::ChatBox::expandPlease, toggleChat, &QAction::trigger); connect(toggleChat, &QAction::triggered, this, [this](bool show) { QList sizes; if(show) { diff --git a/src/dpcore/src/protocol/message.rs b/src/dpcore/src/protocol/message.rs index 45a6f6b300..20c8eb6c04 100644 --- a/src/dpcore/src/protocol/message.rs +++ b/src/dpcore/src/protocol/message.rs @@ -110,7 +110,8 @@ pub struct ChatMessage { impl ChatMessage { pub const TFLAGS_BYPASS: u8 = 0x1; - pub const TFLAGS: &'static [&'static str] = &["bypass"]; + pub const TFLAGS_ALERT: u8 = 0x2; + pub const TFLAGS: &'static [&'static str] = &["bypass", "alert"]; pub const OFLAGS_SHOUT: u8 = 0x1; pub const OFLAGS_ACTION: u8 = 0x2; pub const OFLAGS_PIN: u8 = 0x4; @@ -1690,6 +1691,9 @@ pub enum CommandMessage { /// Specifying a sublayer requires session operator privileges. Currently, it is used /// only when sublayers are needed at canvas initialization. /// + /// Note: the `fixed` flag is unused since version 2.2. It's functionality is replaced + /// by the custom timeline feature. + /// LayerAttributes(u8, LayerAttributesMessage), /// Change a layer's title diff --git a/src/dpcore/src/protocol/protocol.yaml b/src/dpcore/src/protocol/protocol.yaml index 1433bc649d..993a088389 100644 --- a/src/dpcore/src/protocol/protocol.yaml +++ b/src/dpcore/src/protocol/protocol.yaml @@ -110,7 +110,7 @@ Chat: (Typically a Command message is used for server announcements, but the Chat message is used for those messages that must be stored in the session history.) fields: - - tflags flags: [bypass] + - tflags flags: [bypass, alert] - oflags flags: [shout, action, pin] - message utf8 diff --git a/src/libserver/session.cpp b/src/libserver/session.cpp index 5160f57f38..ddf9f2e3b4 100644 --- a/src/libserver/session.cpp +++ b/src/libserver/session.cpp @@ -646,6 +646,8 @@ void Session::handleClientMessage(Client &client, protocol::MessagePtr msg) case protocol::MSG_CHAT: { if(client.isMuted()) return; + if(!client.isOperator() && msg.cast().isAlert()) + return; if(msg.cast().isBypass()) { directToAll(msg); return; diff --git a/src/libshared/net/meta.h b/src/libshared/net/meta.h index dc66bc8bf1..6f817de3f4 100644 --- a/src/libshared/net/meta.h +++ b/src/libshared/net/meta.h @@ -160,6 +160,7 @@ class Chat : public Message { public: // Transparent flags: these affect serverside behavior static const uint8_t FLAG_BYPASS = 0x01; // bypass session history and send directly to logged in users + static const uint8_t FLAG_ALERT = 0x02; // high priority alert (can be send by operators only) // Opaque flags: the server doesn't know anything about these static const uint8_t FLAG_SHOUT = 0x01; // public announcement @@ -197,6 +198,11 @@ class Chat : public Message { */ bool isBypass() const { return m_tflags & FLAG_BYPASS; } + /** + * @brief Is this an alert message? + */ + bool isAlert() const { return m_tflags & FLAG_ALERT; } + /** * @brief Is this a shout? * diff --git a/src/rustpile/rustpile.h b/src/rustpile/rustpile.h index cb73ced3c6..aaa001543d 100644 --- a/src/rustpile/rustpile.h +++ b/src/rustpile/rustpile.h @@ -42,6 +42,8 @@ static const uint8_t ChatMessage_OFLAGS_PIN = 4; static const uint8_t ChatMessage_OFLAGS_SHOUT = 1; +static const uint8_t ChatMessage_TFLAGS_ALERT = 2; + static const uint8_t ChatMessage_TFLAGS_BYPASS = 1; enum class AnimationExportMode {