@ -5,6 +5,7 @@
# include <array>
# include <future>
# include <QColor>
# include <QFutureWatcher>
# include <QImage>
# include <QList>
# include <QLocale>
@ -12,6 +13,7 @@
# include <QMessageBox>
# include <QMetaType>
# include <QTime>
# include <QUrl>
# include <QtConcurrent/QtConcurrentRun>
# include "citra_qt/game_list_p.h"
# include "citra_qt/multiplayer/chat_room.h"
@ -19,6 +21,9 @@
# include "common/logging/log.h"
# include "core/announce_multiplayer_session.h"
# include "ui_chat_room.h"
# ifdef ENABLE_WEB_SERVICE
# include "web_service/web_backend.h"
# endif
class ChatMessage {
public :
@ -27,14 +32,21 @@ public:
QLocale locale ;
timestamp = locale . toString ( ts . isValid ( ) ? ts : QTime : : currentTime ( ) , QLocale : : ShortFormat ) ;
nickname = QString : : fromStdString ( chat . nickname ) ;
username = QString : : fromStdString ( chat . username ) ;
message = QString : : fromStdString ( chat . message ) ;
}
/// Format the message using the players color
QString GetPlayerChatMessage ( u16 player ) const {
auto color = player_color [ player % 16 ] ;
QString name ;
if ( username . isEmpty ( ) | | username = = nickname ) {
name = nickname ;
} else {
name = QString ( " %1 (%2) " ) . arg ( nickname , username ) ;
}
return QString ( " [%1] <font color='%2'><%3></font> %4 " )
. arg ( timestamp , color , nickname . toHtmlEscaped ( ) , message . toHtmlEscaped ( ) ) ;
. arg ( timestamp , color , n ame. toHtmlEscaped ( ) , message . toHtmlEscaped ( ) ) ;
}
private :
@ -44,6 +56,7 @@ private:
QString timestamp ;
QString nickname ;
QString username ;
QString message ;
} ;
@ -67,22 +80,54 @@ private:
QString message ;
} ;
class PlayerListItem : public QStandardItem {
public :
static const int NicknameRole = Qt : : UserRole + 1 ;
static const int UsernameRole = Qt : : UserRole + 2 ;
static const int AvatarUrlRole = Qt : : UserRole + 3 ;
static const int GameNameRole = Qt : : UserRole + 4 ;
PlayerListItem ( ) = default ;
explicit PlayerListItem ( const std : : string & nickname , const std : : string & username ,
const std : : string & avatar_url , const std : : string & game_name ) {
setEditable ( false ) ;
setData ( QString : : fromStdString ( nickname ) , NicknameRole ) ;
setData ( QString : : fromStdString ( username ) , UsernameRole ) ;
setData ( QString : : fromStdString ( avatar_url ) , AvatarUrlRole ) ;
if ( game_name . empty ( ) ) {
setData ( QObject : : tr ( " Not playing a game " ) , GameNameRole ) ;
} else {
setData ( QString : : fromStdString ( game_name ) , GameNameRole ) ;
}
}
QVariant data ( int role ) const override {
if ( role ! = Qt : : DisplayRole ) {
return QStandardItem : : data ( role ) ;
}
QString name ;
const QString nickname = data ( NicknameRole ) . toString ( ) ;
const QString username = data ( UsernameRole ) . toString ( ) ;
if ( username . isEmpty ( ) | | username = = nickname ) {
name = nickname ;
} else {
name = QString ( " %1 (%2) " ) . arg ( nickname , username ) ;
}
return QString ( " %1 \n %2 " ) . arg ( name , data ( GameNameRole ) . toString ( ) ) ;
}
} ;
ChatRoom : : ChatRoom ( QWidget * parent ) : QWidget ( parent ) , ui ( std : : make_unique < Ui : : ChatRoom > ( ) ) {
ui - > setupUi ( this ) ;
// set the item_model for player_view
enum {
COLUMN_NAME ,
COLUMN_GAME ,
COLUMN_COUNT , // Number of columns
} ;
player_list = new QStandardItemModel ( ui - > player_view ) ;
ui - > player_view - > setModel ( player_list ) ;
ui - > player_view - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
player_list - > insertColumns ( 0 , COLUMN_COUNT ) ;
player_list - > setHeaderData ( COLUMN_NAME , Qt : : Horizontal , tr ( " Name " ) ) ;
player_list - > setHeaderData ( COLUMN_GAME , Qt : : Horizontal , tr ( " Game " ) ) ;
// set a header to make it look better though there is only one column
player_list - > insertColumns( 0 , 1 ) ;
player_list - > setHeaderData ( 0 , Qt : : Horizontal , tr ( " Members " ) ) ;
ui - > chat_history - > document ( ) - > setMaximumBlockCount ( max_chat_lines ) ;
@ -157,7 +202,8 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
auto members = room - > GetMemberInformation ( ) ;
auto it = std : : find_if ( members . begin ( ) , members . end ( ) ,
[ & chat ] ( const Network : : RoomMember : : MemberInformation & member ) {
return member . nickname = = chat . nickname ;
return member . nickname = = chat . nickname & &
member . username = = chat . username ;
} ) ;
if ( it = = members . end ( ) ) {
LOG_INFO ( Network , " Chat message received from unknown player. Ignoring it. " ) ;
@ -184,12 +230,14 @@ void ChatRoom::OnSendChat() {
return ;
}
auto nick = room - > GetNickname ( ) ;
Network : : ChatEntry chat { nick , message } ;
auto username = room - > GetUsername ( ) ;
Network : : ChatEntry chat { nick , username , message } ;
auto members = room - > GetMemberInformation ( ) ;
auto it = std : : find_if ( members . begin ( ) , members . end ( ) ,
[ & chat ] ( const Network : : RoomMember : : MemberInformation & member ) {
return member . nickname = = chat . nickname ;
return member . nickname = = chat . nickname & &
member . username = = chat . username ;
} ) ;
if ( it = = members . end ( ) ) {
LOG_INFO ( Network , " Cannot find self in the player list when sending a message. " ) ;
@ -202,20 +250,64 @@ void ChatRoom::OnSendChat() {
}
}
void ChatRoom : : UpdateIconDisplay ( ) {
for ( int row = 0 ; row < player_list - > invisibleRootItem ( ) - > rowCount ( ) ; + + row ) {
QStandardItem * item = player_list - > invisibleRootItem ( ) - > child ( row ) ;
const std : : string avatar_url =
item - > data ( PlayerListItem : : AvatarUrlRole ) . toString ( ) . toStdString ( ) ;
if ( icon_cache . count ( avatar_url ) ) {
item - > setData ( icon_cache . at ( avatar_url ) , Qt : : DecorationRole ) ;
}
}
}
void ChatRoom : : SetPlayerList ( const Network : : RoomMember : : MemberList & member_list ) {
// TODO(B3N30): Remember which row is selected
player_list - > removeRows ( 0 , player_list - > rowCount ( ) ) ;
for ( const auto & member : member_list ) {
if ( member . nickname . empty ( ) )
continue ;
QList < QStandardItem * > l ;
std : : vector < std : : string > elements = { member . nickname , member . game_info . name } ;
for ( const auto & item : elements ) {
QStandardItem * child = new QStandardItem ( QString : : fromStdString ( item ) ) ;
child - > setEditable ( false ) ;
l . append ( child ) ;
QStandardItem * name_item = new PlayerListItem ( member . nickname , member . username ,
member . avatar_url , member . game_info . name ) ;
if ( ! icon_cache . count ( member . avatar_url ) ) {
// Emplace a default question mark icon as avatar
icon_cache . emplace ( member . avatar_url , QIcon : : fromTheme ( " no_avatar " ) . pixmap ( 48 ) ) ;
if ( ! member . avatar_url . empty ( ) ) {
# ifdef ENABLE_WEB_SERVICE
// Start a request to get the member's avatar
const QUrl url ( QString : : fromStdString ( member . avatar_url ) ) ;
QFuture < std : : string > future = QtConcurrent : : run ( [ url ] {
WebService : : Client client (
QString ( " %1://%2 " ) . arg ( url . scheme ( ) , url . host ( ) ) . toStdString ( ) , " " , " " ) ;
auto result = client . GetImage ( url . path ( ) . toStdString ( ) , true ) ;
if ( result . returned_data . empty ( ) ) {
LOG_ERROR ( WebService , " Failed to get avatar " ) ;
}
return result . returned_data ;
} ) ;
auto * future_watcher = new QFutureWatcher < std : : string > ( this ) ;
connect ( future_watcher , & QFutureWatcher < std : : string > : : finished , this ,
[ this , future_watcher , avatar_url = member . avatar_url ] {
const std : : string result = future_watcher - > result ( ) ;
if ( result . empty ( ) )
return ;
QPixmap pixmap ;
if ( ! pixmap . loadFromData ( reinterpret_cast < const u8 * > ( result . data ( ) ) ,
result . size ( ) ) )
return ;
icon_cache [ avatar_url ] = pixmap . scaled ( 48 , 48 , Qt : : IgnoreAspectRatio ,
Qt : : SmoothTransformation ) ;
// Update all the displayed icons with the new icon_cache
UpdateIconDisplay ( ) ;
} ) ;
future_watcher - > setFuture ( future ) ;
# endif
}
}
player_list - > invisibleRootItem ( ) - > appendRow ( l ) ;
name_item - > setData ( icon_cache . at ( member . avatar_url ) , Qt : : DecorationRole ) ;
player_list - > invisibleRootItem ( ) - > appendRow ( name_item ) ;
}
// TODO(B3N30): Restore row selection
}
@ -230,7 +322,8 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
if ( ! item . isValid ( ) )
return ;
std : : string nickname = player_list - > item ( item . row ( ) ) - > text ( ) . toStdString ( ) ;
std : : string nickname =
player_list - > item ( item . row ( ) ) - > data ( PlayerListItem : : NicknameRole ) . toString ( ) . toStdString ( ) ;
if ( auto room = Network : : GetRoomMember ( ) . lock ( ) ) {
// You can't block yourself
if ( nickname = = room - > GetNickname ( ) )