citra_qt/multiplayer: Add user ping support
The user would be notified if the message contains "@" followed by the user's nickname or forum username. An alert would be shown, and the icon and message in the status bar would be changed. All notification is only shown if the chat window currently does not have focus. Also added a connected_notification icon for showing in the status bar.
This commit is contained in:
parent
6feeaed77e
commit
8b8b39ec0e
|
@ -4,6 +4,7 @@ Icon Name | License | Origin/Author
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
qt_themes/default/icons/16x16/checked.png | Free for non-commercial use
|
qt_themes/default/icons/16x16/checked.png | Free for non-commercial use
|
||||||
qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/default/icons/16x16/failed.png | Free for non-commercial use
|
qt_themes/default/icons/16x16/failed.png | Free for non-commercial use
|
||||||
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
@ -16,6 +17,7 @@ qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from
|
||||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
|
qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
|
||||||
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
|
qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
|
||||||
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
@ -27,6 +29,7 @@ qt_themes/qdarkstyle/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.c
|
||||||
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||||
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/colorful/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/colorful/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
qt_themes/colorful/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/colorful/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/colorful/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 607 B |
|
@ -2,6 +2,7 @@
|
||||||
<qresource prefix="icons/colorful">
|
<qresource prefix="icons/colorful">
|
||||||
<file alias="index.theme">icons/index.theme</file>
|
<file alias="index.theme">icons/index.theme</file>
|
||||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||||
|
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<qresource prefix="icons/colorful_dark">
|
<qresource prefix="icons/colorful_dark">
|
||||||
<file alias="index.theme">icons/index.theme</file>
|
<file alias="index.theme">icons/index.theme</file>
|
||||||
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
|
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
|
||||||
|
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
|
||||||
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
|
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
|
||||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||||
|
|
||||||
|
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||||
|
|
||||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||||
|
|
||||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 517 B |
Binary file not shown.
After Width: | Height: | Size: 526 B |
|
@ -3,6 +3,7 @@
|
||||||
<file alias="index.theme">icons/index.theme</file>
|
<file alias="index.theme">icons/index.theme</file>
|
||||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||||
|
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||||
|
|
|
@ -345,6 +345,7 @@ Icon Name | License | Origin/Author
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
checked.png | Free for non-commercial use
|
checked.png | Free for non-commercial use
|
||||||
connected.png | CC BY-ND 3.0 | https://icons8.com
|
connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
failed.png | Free for non-commercial use
|
failed.png | Free for non-commercial use
|
||||||
lock.png | CC BY-ND 3.0 | https://icons8.com
|
lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
|
|
@ -34,6 +34,24 @@ public:
|
||||||
nickname = QString::fromStdString(chat.nickname);
|
nickname = QString::fromStdString(chat.nickname);
|
||||||
username = QString::fromStdString(chat.username);
|
username = QString::fromStdString(chat.username);
|
||||||
message = QString::fromStdString(chat.message);
|
message = QString::fromStdString(chat.message);
|
||||||
|
|
||||||
|
// Check for user pings
|
||||||
|
QString cur_nickname, cur_username;
|
||||||
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
|
cur_nickname = QString::fromStdString(room->GetNickname());
|
||||||
|
cur_username = QString::fromStdString(room->GetUsername());
|
||||||
|
}
|
||||||
|
if (message.contains(QString("@").append(cur_nickname)) ||
|
||||||
|
(!cur_username.isEmpty() && message.contains(QString("@").append(cur_username)))) {
|
||||||
|
|
||||||
|
contains_ping = true;
|
||||||
|
} else {
|
||||||
|
contains_ping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContainsPing() const {
|
||||||
|
return contains_ping;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format the message using the players color
|
/// Format the message using the players color
|
||||||
|
@ -45,19 +63,28 @@ public:
|
||||||
} else {
|
} else {
|
||||||
name = QString("%1 (%2)").arg(nickname, username);
|
name = QString("%1 (%2)").arg(nickname, username);
|
||||||
}
|
}
|
||||||
return QString("[%1] <font color='%2'><%3></font> %4")
|
|
||||||
.arg(timestamp, color, name.toHtmlEscaped(), message.toHtmlEscaped());
|
QString style;
|
||||||
|
if (ContainsPing()) {
|
||||||
|
// Add a background color to these messages
|
||||||
|
style = QString("background-color: %1").arg(ping_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("[%1] <font color='%2'><%3></font> <font style='%4'>%5</font>")
|
||||||
|
.arg(timestamp, color, name.toHtmlEscaped(), style, message.toHtmlEscaped());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr std::array<const char*, 16> player_color = {
|
static constexpr std::array<const char*, 16> player_color = {
|
||||||
{"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
|
{"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
|
||||||
"#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
|
"#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
|
||||||
|
static constexpr char ping_color[] = "#FFFF00";
|
||||||
|
|
||||||
QString timestamp;
|
QString timestamp;
|
||||||
QString nickname;
|
QString nickname;
|
||||||
QString username;
|
QString username;
|
||||||
QString message;
|
QString message;
|
||||||
|
bool contains_ping;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StatusMessage {
|
class StatusMessage {
|
||||||
|
@ -240,6 +267,9 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||||
}
|
}
|
||||||
auto player = std::distance(members.begin(), it);
|
auto player = std::distance(members.begin(), it);
|
||||||
ChatMessage m(chat);
|
ChatMessage m(chat);
|
||||||
|
if (m.ContainsPing()) {
|
||||||
|
emit UserPinged();
|
||||||
|
}
|
||||||
AppendChatMessage(m.GetPlayerChatMessage(player));
|
AppendChatMessage(m.GetPlayerChatMessage(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ public slots:
|
||||||
signals:
|
signals:
|
||||||
void ChatReceived(const Network::ChatEntry&);
|
void ChatReceived(const Network::ChatEntry&);
|
||||||
void StatusMessageReceived(const Network::StatusMessageEntry&);
|
void StatusMessageReceived(const Network::StatusMessageEntry&);
|
||||||
|
void UserPinged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u32 max_chat_lines = 1000;
|
static constexpr u32 max_chat_lines = 1000;
|
||||||
|
|
|
@ -49,6 +49,7 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent)
|
||||||
});
|
});
|
||||||
ui->moderation->setDefault(false);
|
ui->moderation->setDefault(false);
|
||||||
ui->moderation->setAutoDefault(false);
|
ui->moderation->setAutoDefault(false);
|
||||||
|
connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification);
|
||||||
UpdateView();
|
UpdateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ public slots:
|
||||||
signals:
|
signals:
|
||||||
void RoomInformationChanged(const Network::RoomInformation&);
|
void RoomInformationChanged(const Network::RoomInformation&);
|
||||||
void StateChanged(const Network::RoomMember::State&);
|
void StateChanged(const Network::RoomMember::State&);
|
||||||
|
void ShowNotification();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
#include <QApplication>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
@ -49,6 +50,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
|
||||||
|
|
||||||
connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
||||||
connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
||||||
|
|
||||||
|
connect(static_cast<QApplication*>(QApplication::instance()), &QApplication::focusChanged, this,
|
||||||
|
[this](QWidget* /*old*/, QWidget* now) {
|
||||||
|
if (client_room && client_room->isAncestorOf(now)) {
|
||||||
|
HideNotification();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiplayerState::~MultiplayerState() {
|
MultiplayerState::~MultiplayerState() {
|
||||||
|
@ -173,7 +181,9 @@ void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiplayerState::UpdateThemedIcons() {
|
void MultiplayerState::UpdateThemedIcons() {
|
||||||
if (current_state == Network::RoomMember::State::Joined) {
|
if (show_notification) {
|
||||||
|
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
||||||
|
} else if (current_state == Network::RoomMember::State::Joined) {
|
||||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||||
} else {
|
} else {
|
||||||
status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16));
|
||||||
|
@ -225,11 +235,28 @@ bool MultiplayerState::OnCloseRoom() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiplayerState::ShowNotification() {
|
||||||
|
if (client_room && client_room->isAncestorOf(QApplication::focusWidget()))
|
||||||
|
return; // Do not show notification if the chat window currently has focus
|
||||||
|
show_notification = true;
|
||||||
|
QApplication::alert(nullptr);
|
||||||
|
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
||||||
|
status_text->setText(tr("New Messages Received"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiplayerState::HideNotification() {
|
||||||
|
show_notification = false;
|
||||||
|
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||||
|
status_text->setText(tr("Connected"));
|
||||||
|
}
|
||||||
|
|
||||||
void MultiplayerState::OnOpenNetworkRoom() {
|
void MultiplayerState::OnOpenNetworkRoom() {
|
||||||
if (auto member = Network::GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
if (member->IsConnected()) {
|
if (member->IsConnected()) {
|
||||||
if (client_room == nullptr) {
|
if (client_room == nullptr) {
|
||||||
client_room = new ClientRoomWindow(this);
|
client_room = new ClientRoomWindow(this);
|
||||||
|
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
||||||
|
&MultiplayerState::ShowNotification);
|
||||||
}
|
}
|
||||||
const std::string host_username = member->GetRoomInformation().host_username;
|
const std::string host_username = member->GetRoomInformation().host_username;
|
||||||
if (host_username.empty()) {
|
if (host_username.empty()) {
|
||||||
|
|
|
@ -48,6 +48,8 @@ public slots:
|
||||||
void OnDirectConnectToRoom();
|
void OnDirectConnectToRoom();
|
||||||
void OnAnnounceFailed(const Common::WebResult&);
|
void OnAnnounceFailed(const Common::WebResult&);
|
||||||
void UpdateThemedIcons();
|
void UpdateThemedIcons();
|
||||||
|
void ShowNotification();
|
||||||
|
void HideNotification();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void NetworkStateChanged(const Network::RoomMember::State&);
|
void NetworkStateChanged(const Network::RoomMember::State&);
|
||||||
|
@ -69,6 +71,8 @@ private:
|
||||||
bool has_mod_perms = false;
|
bool has_mod_perms = false;
|
||||||
Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
|
Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
|
||||||
Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
|
Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
|
||||||
|
|
||||||
|
bool show_notification = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Common::WebResult);
|
Q_DECLARE_METATYPE(Common::WebResult);
|
||||||
|
|
Loading…
Reference in New Issue