/*
* Hedgewars, a free turn based strategy game
* Copyright (c) 2007 Igor Ulyanov <iulyanov@gmail.com>
* Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <QDesktopServices>
#include <QTextBrowser>
#include <QAction>
#include <QFile>
#include <QTextStream>
#include <QMenu>
#include <QCursor>
#include <QItemSelectionModel>
#include <QDateTime>
#include <QTime>
#include <QListView>
#include <QModelIndexList>
#include <QSortFilterProxyModel>
#include <QMenu>
#include "DataManager.h"
#include "hwconsts.h"
#include "gameuiconfig.h"
#include "playerslistmodel.h"
#include "chatwidget.h"
QString * HWChatWidget::s_styleSheet = NULL;
QStringList * HWChatWidget::s_displayNone = NULL;
bool HWChatWidget::s_isTimeStamped = true;
QString HWChatWidget::s_tsFormat = ":mm:ss";
const QString & HWChatWidget::styleSheet()
{
if (s_styleSheet != NULL)
return *s_styleSheet;
setStyleSheet();
return *s_styleSheet;
}
void HWChatWidget::setStyleSheet(const QString & styleSheet)
{
QString orgStyleSheet = styleSheet;
QString style = QString(orgStyleSheet);
// no stylesheet supplied, search for one or use default
if (orgStyleSheet.isEmpty())
{
// load external stylesheet if there is any
QFile extFile("physfs://css/chat.css");
QFile resFile(":/res/css/chat.css");
QFile & file = (extFile.exists()?extFile:resFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream in(&file);
while (!in.atEnd())
{
style.append(in.readLine()+"\n");
}
orgStyleSheet = style;
file.close();
}
}
// let's parse display:none; ...
// prepare for MAGIC :D
// matches (multi-)whitespaces (for replacement with simple space)
QRegExp ws("\\s+");
// matches comments (for removal)
QRegExp rem("/\\*([^*]|\\*(?!/))*\\*/");
// strip comments and multi-whitespaces to compress the style-sheet a bit
style = style.remove(rem).replace(ws," ");
// now let's see what messages the user does not want to be displayed
// by checking for display:none; (since QTextBrowser does not support it)
// MOAR MAGIC :DDD
// matches definitions lacking display:none; (for removal)
QRegExp displayed(
"([^{}]*\\{)(?!([^}]*;)* ?display ?: ?none ?(;[^}]*)?\\})[^}]*\\}");
// matches all {...} and , (used as seperator for splitting into names)
QRegExp split(" *(\\{[^}]*\\}|,) *");
// matches class names that are referenced without hierachy
QRegExp nohierarchy("^.[^ .]+$");
QStringList victims = QString(style).
remove(displayed). // remove visible stuff
trimmed().
split(split). // get a list of the names
filter(nohierarchy). // only direct class names
replaceInStrings(QRegExp("^."),""); // crop .
if (victims.contains("timestamp"))
{
s_isTimeStamped = false;
victims.removeAll("timestamp");
}
else
{
s_isTimeStamped = true;
s_tsFormat =
((victims.contains("timestamp:hours"))?"":"hh:") +
QString("mm") +
((victims.contains("timestamp:seconds"))?"":":ss");
}
victims.removeAll("timestamp:hours");
victims.removeAll("timestamp:seconds");
victims.removeDuplicates();
QStringList * oldDisplayNone = s_displayNone;
QString * oldStyleSheet = s_styleSheet;
s_displayNone = new QStringList(victims);
s_styleSheet = new QString(orgStyleSheet);
if (oldDisplayNone != NULL)
delete oldDisplayNone;
if (oldStyleSheet != NULL)
delete oldStyleSheet;
}
void HWChatWidget::displayError(const QString & message)
{
addLine("msg_Error", " !!! " + message);
// scroll to the end
chatText->moveCursor(QTextCursor::End);
}
void HWChatWidget::displayNotice(const QString & message)
{
addLine("msg_Notice", " *** " + message);
// scroll to the end
chatText->moveCursor(QTextCursor::End);
}
void HWChatWidget::displayWarning(const QString & message)
{
addLine("msg_Warning", " *!* " + message);
// scroll to the end
chatText->moveCursor(QTextCursor::End);
}
HWChatWidget::HWChatWidget(QWidget* parent, bool notify) :
QWidget(parent),
mainLayout(this)
{
this->gameSettings = gameSettings;
this->notify = notify;
m_isAdmin = false;
m_autoKickEnabled = false;
{
QStringList vpList =
QStringList() << "Classic" << "Default" << "Mobster" << "Russian";
foreach (QString vp, vpList)
{
m_helloSounds.append(QString("physfs://Sounds/voices/%1/Hello.ogg").arg(vp));
}
m_hilightSound = "physfs://Sounds/beep.ogg";
}
mainLayout.setSpacing(1);
mainLayout.setMargin(1);
mainLayout.setSizeConstraint(QLayout::SetMinimumSize);
mainLayout.setColumnStretch(0, 76);
mainLayout.setColumnStretch(1, 24);
chatEditLine = new SmartLineEdit(this);
chatEditLine->setMaxLength(300);
connect(chatEditLine, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
mainLayout.addWidget(chatEditLine, 2, 0);
chatText = new QTextBrowser(this);
chatText->document()->setDefaultStyleSheet(styleSheet());
chatText->setMinimumHeight(20);
chatText->setMinimumWidth(10);
chatText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chatText->setOpenLinks(false);
connect(chatText, SIGNAL(anchorClicked(const QUrl&)),
this, SLOT(linkClicked(const QUrl&)));
mainLayout.addWidget(chatText, 0, 0, 2, 1);
chatNicks = new QListView(this);
chatNicks->setIconSize(QSize(24, 16));
chatNicks->setSelectionMode(QAbstractItemView::SingleSelection);
chatNicks->setEditTriggers(QAbstractItemView::NoEditTriggers);
chatNicks->setMinimumHeight(10);
chatNicks->setMinimumWidth(10);
chatNicks->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chatNicks->setContextMenuPolicy(Qt::CustomContextMenu);
connect(chatNicks, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(chatNickDoubleClicked(QModelIndex)));
connect(chatNicks, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(nicksContextMenuRequested(QPoint)));
mainLayout.addWidget(chatNicks, 0, 1, 3, 1);
// the userData is used to flag things that are even available when user
// is offline
acInfo = new QAction(QAction::tr("Info"), chatNicks);
acInfo->setIcon(QIcon(":/res/info.png"));
acInfo->setData(QVariant(false));
connect(acInfo, SIGNAL(triggered(bool)), this, SLOT(onInfo()));
acKick = new QAction(QAction::tr("Kick"), chatNicks);
acKick->setIcon(QIcon(":/res/kick.png"));
acKick->setData(QVariant(false));
connect(acKick, SIGNAL(triggered(bool)), this, SLOT(onKick()));
acBan = new QAction(QAction::tr("Ban"), chatNicks);
acBan->setIcon(QIcon(":/res/ban.png"));
acBan->setData(QVariant(true));
connect(acBan, SIGNAL(triggered(bool)), this, SLOT(onBan()));
acFollow = new QAction(QAction::tr("Follow"), chatNicks);
acFollow->setIcon(QIcon(":/res/follow.png"));
acFollow->setData(QVariant(false));
connect(acFollow, SIGNAL(triggered(bool)), this, SLOT(onFollow()));
acIgnore = new QAction(QAction::tr("Ignore"), chatNicks);
acIgnore->setIcon(QIcon(":/res/ignore.png"));
acIgnore->setData(QVariant(true));
connect(acIgnore, SIGNAL(triggered(bool)), this, SLOT(onIgnore()));
acFriend = new QAction(QAction::tr("Add friend"), chatNicks);
acFriend->setIcon(QIcon(":/res/addfriend.png"));
acFriend->setData(QVariant(true));
connect(acFriend, SIGNAL(triggered(bool)), this, SLOT(onFriend()));
chatNicks->insertAction(0, acFriend);
chatNicks->insertAction(0, acInfo);
chatNicks->insertAction(0, acIgnore);
setShowFollow(true);
setAcceptDrops(true);
m_nicksMenu = new QMenu(this);
clear();
}
void HWChatWidget::setSettings(QSettings * settings)
{
gameSettings = settings;
}
void HWChatWidget::linkClicked(const QUrl & link)
{
if (link.scheme() == "http")
QDesktopServices::openUrl(link);
if (link.scheme() == "hwnick")
{
// decode nick
QString nick = QString::fromUtf8(QByteArray::fromBase64(link.encodedQuery()));
QModelIndexList mil = chatNicks->model()->match(chatNicks->model()->index(0, 0), Qt::DisplayRole, nick);
bool isOffline = (mil.size() < 1);
if (isOffline)
{
m_clickedNick = nick;
chatNicks->selectionModel()->clearSelection();
}
else
{
chatNicks->selectionModel()->select(mil[0], QItemSelectionModel::ClearAndSelect);
}
nicksContextMenuRequested(chatNicks->mapFromGlobal(QCursor::pos()));
}
}
void HWChatWidget::setShowFollow(bool enabled)
{
if (enabled)
{
if (!(chatNicks->actions().contains(acFollow)))
chatNicks->insertAction(acFriend, acFollow);
}
else
{
if (chatNicks->actions().contains(acFollow))
chatNicks->removeAction(acFollow);
}
}
void HWChatWidget::setIgnoreListKick(bool enabled)
{
m_autoKickEnabled = enabled;
}
void HWChatWidget::returnPressed()
{
QStringList lines = chatEditLine->text().split('\n');
chatEditLine->rememberCurrentText();
foreach (const QString &line, lines)
{
// skip empty/whitespace lines
if (line.trimmed().isEmpty())
continue;
if (!parseCommand(line))
emit chatLine(line);
}
chatEditLine->clear();
}
// "link" nick, but before that encode it in base64 to make sure it can't
// intefere with html/url syntax the nick is put as querystring as putting
// it as host would convert it to it's lower case variant
QString HWChatWidget::linkedNick(const QString & nickname)
{
if (nickname != m_userNick)
return QString("<a href=\"hwnick://?%1\" class=\"nick\">%2</a>").arg(
QString(nickname.toUtf8().toBase64())).arg(Qt::escape(nickname));
// unlinked nick (if own one)
return QString("<span class=\"nick\">%1</span>").arg(Qt::escape(nickname));
}
void HWChatWidget::onChatString(const QString& str)
{
onChatString("", str);
}
const QRegExp HWChatWidget::URLREGEXP = QRegExp("(http://)?(www\\.)?(hedgewars\\.org(/[^ ]*)?)");
void HWChatWidget::onChatString(const QString& nick, const QString& str)
{
QSortFilterProxyModel * playersSortFilterModel = qobject_cast<QSortFilterProxyModel *>(chatNicks->model());
if(!playersSortFilterModel)
return;
PlayersListModel * players = qobject_cast<PlayersListModel *>(playersSortFilterModel->sourceModel());
if(!players)
return;
if (!nick.isEmpty())
{
// don't show chat lines that are from ignored nicks
if (players->isFlagSet(nick, PlayersListModel::Ignore))
return;
}
bool isFriend = (!nick.isEmpty()) && players->isFlagSet(nick, PlayersListModel::Friend);
QString formattedStr = Qt::escape(str.mid(1));
// make hedgewars.org urls actual links
formattedStr = formattedStr.replace(URLREGEXP, "<a href=\"http://\\3\">\\3</a>");
// link the nick
if(!nick.isEmpty())
formattedStr.replace("|nick|", linkedNick(nick));
QString cssClass("msg_UserChat");
// check first character for color code and set color properly
char c = str[0].toAscii();
switch (c)
{
case 3:
cssClass = (isFriend ? "msg_FriendJoin" : "msg_UserJoin");
break;
case 2:
cssClass = (isFriend ? "msg_FriendAction" : "msg_UserAction");
break;
default:
if (isFriend)
cssClass = "msg_FriendChat";
}
bool isHL = false;
if ((c != 3) && (!nick.isEmpty()) &&
(nick != m_userNick) && (!m_userNick.isEmpty()))
{
QString lcStr = str.toLower();
foreach (const QRegExp & hl, m_highlights)
{
if (lcStr.contains(hl))
{
isHL = true;
break;
}
}
}
addLine(cssClass, formattedStr, isHL);
}
void HWChatWidget::addLine(const QString & cssClass, QString line, bool isHighlight)
{
if (s_displayNone->contains(cssClass))
return; // the css forbids us to display this line
if (chatStrings.size() > 250)
chatStrings.removeFirst();
if (s_isTimeStamped)
{
QString tsMarkUp = "<span class=\"timestamp\">[%1]</span> ";
QTime now = QDateTime::currentDateTime().time();
line = tsMarkUp.arg(now.toString(s_tsFormat)) + line;
}
line = QString("<span class=\"%1\">%2</span>").arg(cssClass).arg(line);
if (isHighlight)
{
line = QString("<span class=\"highlight\">%1</span>").arg(line);
SDLInteraction::instance().playSoundFile(m_hilightSound);
}
chatStrings.append(line);
chatText->setHtml("<html><body>"+chatStrings.join("<br>")+"</body></html>");
chatText->moveCursor(QTextCursor::End);
}
void HWChatWidget::onServerMessage(const QString& str)
{
if (chatStrings.size() > 250)
chatStrings.removeFirst();
chatStrings.append("<hr>" + str + "<hr>");
chatText->setHtml("<html><body>"+chatStrings.join("<br>")+"</body></html>");
chatText->moveCursor(QTextCursor::End);
}
void HWChatWidget::nickAdded(const QString & nick, bool notifyNick)
{
QSortFilterProxyModel * playersSortFilterModel = qobject_cast<QSortFilterProxyModel *>(chatNicks->model());
if(!playersSortFilterModel)
return;
PlayersListModel * players = qobject_cast<PlayersListModel *>(playersSortFilterModel->sourceModel());
if(!players)
return;
bool isIgnored = players->isFlagSet(nick, PlayersListModel::Ignore);
if (isIgnored && m_isAdmin && m_autoKickEnabled)
{
emit kick(nick);
return;
}
if ((!isIgnored) && (nick != m_userNick)) // don't auto-complete own name
chatEditLine->addNickname(nick);
emit nickCountUpdate(chatNicks->model()->rowCount());
if(notifyNick && notify && gameSettings->value("frontend/sound", true).toBool())
{
SDLInteraction::instance().playSoundFile(
m_helloSounds.at(rand() % m_helloSounds.size()));
}
}
void HWChatWidget::nickRemoved(const QString& nick)
{
chatEditLine->removeNickname(nick);
emit nickCountUpdate(chatNicks->model()->rowCount());
}
void HWChatWidget::clear()
{
chatEditLine->reset();
// add default commands
QStringList cmds;
cmds << "/me" << "/discardStyleSheet" << "/saveStyleSheet";
chatEditLine->addCommands(cmds);
chatText->clear();
chatStrings.clear();
//chatNicks->clear();
// clear and re compile regexp for highlighting
m_highlights.clear();
QString hlRegExp("^(.* )?%1[^-a-z0-9_]*( .*)?$");
QRegExp whitespace("\\s");
m_highlights.append(QRegExp(hlRegExp.arg(m_userNick.toLower())));
QFile file(cfgdir->absolutePath() + "/" + m_userNick.toLower() + "_highlight.txt");
if (file.exists() && (file.open(QIODevice::ReadOnly | QIODevice::Text)))
{
QTextStream in(&file);
while (!in.atEnd())
{
QString line = in.readLine();
QStringList list = line.split(whitespace);
foreach (QString word, list)
{
m_highlights.append(QRegExp(
hlRegExp.arg(QRegExp::escape(word.toLower()))));
}
}
if (file.isOpen())
file.close();
}
QFile file2(cfgdir->absolutePath() + "/" + m_userNick.toLower() + "_hlregexp.txt");
if (file2.exists() && (file2.open(QIODevice::ReadOnly | QIODevice::Text)))
{
QTextStream in(&file2);
while (!in.atEnd())
{
m_highlights.append(QRegExp(in.readLine().toLower()));
}
if (file2.isOpen())
file2.close();
}
}
void HWChatWidget::onKick()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
if(mil.size())
emit kick(mil[0].data().toString());
}
void HWChatWidget::onBan()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
if(mil.size())
emit ban(mil[0].data().toString());
}
void HWChatWidget::onInfo()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
if(mil.size())
emit info(mil[0].data().toString());
}
void HWChatWidget::onFollow()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
if(mil.size())
emit follow(mil[0].data().toString());
}
void HWChatWidget::onIgnore()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
QString nick;
if(mil.size())
nick = mil[0].data().toString();
else
nick = m_clickedNick;
QSortFilterProxyModel * playersSortFilterModel = qobject_cast<QSortFilterProxyModel *>(chatNicks->model());
if(!playersSortFilterModel)
return;
PlayersListModel * players = qobject_cast<PlayersListModel *>(playersSortFilterModel->sourceModel());
if(!players)
return;
if(players->isFlagSet(nick, PlayersListModel::Ignore))
{
players->setFlag(nick, PlayersListModel::Ignore, false);
chatEditLine->addNickname(nick);
displayNotice(tr("%1 has been removed from your ignore list").arg(linkedNick(nick)));
}
else // not on list - add
{
// don't consider ignored people friends
if(players->isFlagSet(nick, PlayersListModel::Friend))
emit onFriend();
players->setFlag(nick, PlayersListModel::Ignore, true);
chatEditLine->removeNickname(nick);
displayNotice(tr("%1 has been added to your ignore list").arg(linkedNick(nick)));
}
if(mil.size())
chatNicks->scrollTo(chatNicks->selectionModel()->selectedRows()[0]);
}
void HWChatWidget::onFriend()
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
QString nick;
if(mil.size())
nick = mil[0].data().toString();
else
nick = m_clickedNick;
QSortFilterProxyModel * playersSortFilterModel = qobject_cast<QSortFilterProxyModel *>(chatNicks->model());
if(!playersSortFilterModel)
return;
PlayersListModel * players = qobject_cast<PlayersListModel *>(playersSortFilterModel->sourceModel());
if(!players)
return;
if(players->isFlagSet(nick, PlayersListModel::Friend))
{
players->setFlag(nick, PlayersListModel::Friend, false);
chatEditLine->removeNickname(nick);
displayNotice(tr("%1 has been removed from your friends list").arg(linkedNick(nick)));
}
else // not on list - add
{
if(players->isFlagSet(nick, PlayersListModel::Ignore))
emit onIgnore();
players->setFlag(nick, PlayersListModel::Friend, true);
chatEditLine->addNickname(nick);
displayNotice(tr("%1 has been added to your friends list").arg(linkedNick(nick)));
}
if(mil.size())
chatNicks->scrollTo(chatNicks->selectionModel()->selectedRows()[0]);
}
void HWChatWidget::chatNickDoubleClicked(const QModelIndex &index)
{
m_clickedNick = index.data().toString();
QList<QAction *> actions = chatNicks->actions();
actions.first()->activate(QAction::Trigger);
}
void HWChatWidget::adminAccess(bool b)
{
chatNicks->removeAction(acKick);
chatNicks->removeAction(acBan);
m_isAdmin = b;
if(b)
{
chatNicks->insertAction(0, acKick);
chatNicks->insertAction(0, acBan);
}
}
void HWChatWidget::dragEnterEvent(QDragEnterEvent * event)
{
if (event->mimeData()->hasUrls())
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() == 1)
{
QUrl url = urls[0];
static QRegExp localFileRegExp("file://.*\\.css$");
localFileRegExp.setCaseSensitivity(Qt::CaseInsensitive);
if (url.toString().contains(localFileRegExp))
event->acceptProposedAction();
}
}
}
void HWChatWidget::dropEvent(QDropEvent * event)
{
const QString path(event->mimeData()->urls()[0].toString());
QFile file(event->mimeData()->urls()[0].toLocalFile());
if (file.exists() && (file.open(QIODevice::ReadOnly | QIODevice::Text)))
{
QString style;
QTextStream in(&file);
while (!in.atEnd())
{
QString line = in.readLine();
style.append(line + "\n");
}
setStyleSheet(style);
chatText->document()->setDefaultStyleSheet(*s_styleSheet);
displayNotice(tr("Stylesheet imported from %1").arg(path));
displayNotice(tr("Enter %1 if you want to use the current StyleSheet in future, enter %2 to reset!").arg("/saveStyleSheet").arg("/discardStyleSheet"));
if (file.isOpen())
file.close();
event->acceptProposedAction();
}
else
displayError(tr("Couldn't read %1").arg(event->mimeData()->urls()[0].toString()));
}
void HWChatWidget::discardStyleSheet()
{
setStyleSheet();
chatText->document()->setDefaultStyleSheet(*s_styleSheet);
displayNotice(tr("StyleSheet discarded"));
}
void HWChatWidget::saveStyleSheet()
{
QString dest = "physfs://css/chat.css";
QFile file(dest);
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream out(&file);
QStringList lines = s_styleSheet->split("\n", QString::KeepEmptyParts);
// strip trailing empty lines
while (lines.last().isEmpty())
lines.takeLast();
foreach (const QString & line, lines)
{
out << line << endl;
}
out << endl;
file.close();
displayNotice(tr("StyleSheet saved to %1").arg(dest));
}
else
displayError(tr("Failed to save StyleSheet to %1").arg(dest));
}
bool HWChatWidget::parseCommand(const QString & line)
{
if (line[0] == '/')
{
QString tline = line.trimmed();
if (tline.startsWith("/me"))
return false; // not a real command
else if (tline == "/discardStyleSheet")
discardStyleSheet();
else if (tline == "/saveStyleSheet")
saveStyleSheet();
else
{
static QRegExp post("\\s.*$");
tline.remove(post);
displayWarning(tr("%1 is not a valid command!").arg(tline));
}
return true;
}
return false;
}
void HWChatWidget::setUser(const QString & nickname)
{
m_userNick = nickname;
nickRemoved(nickname);
clear();
}
void HWChatWidget::setUsersModel(QAbstractItemModel *model)
{
chatNicks->selectionModel()->deleteLater();
chatNicks->setModel(model);
chatNicks->setModelColumn(0);
}
void HWChatWidget::nicksContextMenuRequested(const QPoint &pos)
{
QModelIndexList mil = chatNicks->selectionModel()->selectedRows();
QString nick;
if(mil.size())
nick = mil[0].data().toString();
else
nick = m_clickedNick;
QSortFilterProxyModel * playersSortFilterModel = qobject_cast<QSortFilterProxyModel *>(chatNicks->model());
if(!playersSortFilterModel)
return;
PlayersListModel * players = qobject_cast<PlayersListModel *>(playersSortFilterModel->sourceModel());
if(!players)
return;
bool isSelf = (nick == m_userNick);
acFollow->setVisible(!isSelf);
// update context menu labels according to possible action
if(players->isFlagSet(nick, PlayersListModel::Ignore))
{
acIgnore->setText(QAction::tr("Unignore"));
acIgnore->setIcon(QIcon(":/res/unignore.png"));
}
else
{
acIgnore->setText(QAction::tr("Ignore"));
acIgnore->setIcon(QIcon(":/res/ignore.png"));
acIgnore->setVisible(!isSelf);
}
if(players->isFlagSet(nick, PlayersListModel::Friend))
{
acFriend->setText(QAction::tr("Remove friend"));
acFriend->setIcon(QIcon(":/res/remfriend.png"));
}
else
{
acFriend->setText(QAction::tr("Add friend"));
acFriend->setIcon(QIcon(":/res/addfriend.png"));
acFriend->setVisible(!isSelf);
}
if (m_isAdmin)
{
acKick->setVisible(!isSelf);
acBan->setVisible(!isSelf);
}
m_nicksMenu->clear();
foreach(QAction * action, chatNicks->actions())
m_nicksMenu->addAction(action);
m_nicksMenu->popup(chatNicks->mapToGlobal(pos));
}