2
* Hedgewars, a free turn based strategy game
3
* Copyright (c) 2007 Igor Ulyanov <iulyanov@gmail.com>
4
* Copyright (c) 2007-2011 Andrey Korotaev <unC0Rr@gmail.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; version 2 of the License
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20
#include <QDesktopServices>
21
#include <QTextBrowser>
24
#include <QTextDocument>
28
#include <QTextStream>
32
#include <QItemSelectionModel>
36
#include "gameuiconfig.h"
37
#include "chatwidget.h"
39
ListWidgetNickItem::ListWidgetNickItem(const QString& nick, bool isFriend, bool isIgnored) : QListWidgetItem(nick)
41
this->aFriend = isFriend;
42
this->isIgnored = isIgnored;
45
void ListWidgetNickItem::setFriend(bool isFriend)
47
this->aFriend = isFriend;
50
void ListWidgetNickItem::setIgnored(bool isIgnored)
52
this->isIgnored = isIgnored;
55
bool ListWidgetNickItem::isFriend()
60
bool ListWidgetNickItem::ignored()
65
bool ListWidgetNickItem::operator< (const QListWidgetItem & other) const
67
// case in-sensitive comparison of the associated strings
68
// chars that are no letters are sorted at the end of the list
70
ListWidgetNickItem otherNick = const_cast<ListWidgetNickItem &>(dynamic_cast<const ListWidgetNickItem &>(other));
72
// ignored always down
73
if (isIgnored != otherNick.ignored())
77
if (aFriend != otherNick.isFriend())
80
QString txt1 = text().toLower();
81
QString txt2 = other.text().toLower();
83
bool firstIsShorter = (txt1.size() < txt2.size());
84
int len = firstIsShorter?txt1.size():txt2.size();
86
for (int i = 0; i < len; i++)
88
if (txt1[i] == txt2[i])
90
if (txt1[i].isLetter() != txt2[i].isLetter())
91
return txt1[i].isLetter();
92
return (txt1[i] < txt2[i]);
95
return firstIsShorter;
98
const char* HWChatWidget::STYLE =
100
a { color:#c8c8ff; }\
101
.nick { text-decoration: none; }\
102
.UserChat .nick { color:#ffec20; }\
103
.FriendChat { color: #08e008; }\
104
.FriendChat .nick { color: #20ff20; }\
105
.UserJoin { color: #c0c0c0; }\
106
.UserJoin .nick { color: #d0d0d0; }\
107
.FriendJoin { color: #c0e0c0; }\
108
.FriendJoin .nick { color: #d0f0d0; }\
109
.UserAction { color: #ff80ff; }\
110
.UserAction .nick { color: #ffa0ff; }\
111
.FriendAction { color: #ff00ff; }\
112
.FriendAction .nick { color: #ff30ff; }\
115
HWChatWidget::HWChatWidget(QWidget* parent, QSettings * gameSettings, SDLInteraction * sdli, bool notify) :
119
this->gameSettings = gameSettings;
121
this->notify = notify;
122
if(notify && gameSettings->value("frontend/sound", true).toBool()) {
124
sdli->SDLMusicInit();
125
for(int i=0;i<4;i++) {
126
tmpfile.setFileName(cfgdir->absolutePath() + "/Data/Sounds/voices/Classic/Hello.ogg");
127
if (tmpfile.exists()) sound[i] = Mix_LoadWAV(QFileInfo(tmpfile).absoluteFilePath().toLocal8Bit().constData());
128
else sound[i] = Mix_LoadWAV(QString(datadir->absolutePath() +
129
"/Sounds/voices/Classic/Hello.ogg").toLocal8Bit().constData());
133
mainLayout.setSpacing(1);
134
mainLayout.setMargin(1);
135
mainLayout.setSizeConstraint(QLayout::SetMinimumSize);
136
mainLayout.setColumnStretch(0, 76);
137
mainLayout.setColumnStretch(1, 24);
139
chatEditLine = new QLineEdit(this);
140
chatEditLine->setMaxLength(300);
141
connect(chatEditLine, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
143
mainLayout.addWidget(chatEditLine, 2, 0);
145
chatText = new QTextBrowser(this);
146
chatText->document()->setDefaultStyleSheet(STYLE);
147
chatText->setMinimumHeight(20);
148
chatText->setMinimumWidth(10);
149
chatText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
150
chatText->setOpenLinks(false);
151
connect(chatText, SIGNAL(anchorClicked(const QUrl&)),
152
this, SLOT(linkClicked(const QUrl&)));
153
mainLayout.addWidget(chatText, 0, 0, 2, 1);
155
chatNicks = new QListWidget(this);
156
chatNicks->setMinimumHeight(10);
157
chatNicks->setMinimumWidth(10);
158
chatNicks->setSortingEnabled(true);
159
chatNicks->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
160
chatNicks->setContextMenuPolicy(Qt::ActionsContextMenu);
161
connect(chatNicks, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
162
this, SLOT(chatNickDoubleClicked(QListWidgetItem *)));
163
connect(chatNicks, SIGNAL(currentRowChanged(int)),
164
this, SLOT(chatNickSelected(int)));
166
mainLayout.addWidget(chatNicks, 0, 1, 3, 1);
168
acInfo = new QAction(QAction::tr("Info"), chatNicks);
169
acInfo->setIcon(QIcon(":/res/info.png"));
170
connect(acInfo, SIGNAL(triggered(bool)), this, SLOT(onInfo()));
171
acKick = new QAction(QAction::tr("Kick"), chatNicks);
172
acKick->setIcon(QIcon(":/res/kick.png"));
173
connect(acKick, SIGNAL(triggered(bool)), this, SLOT(onKick()));
174
acBan = new QAction(QAction::tr("Ban"), chatNicks);
175
acBan->setIcon(QIcon(":/res/ban.png"));
176
connect(acBan, SIGNAL(triggered(bool)), this, SLOT(onBan()));
177
acFollow = new QAction(QAction::tr("Follow"), chatNicks);
178
acFollow->setIcon(QIcon(":/res/follow.png"));
179
connect(acFollow, SIGNAL(triggered(bool)), this, SLOT(onFollow()));
180
acIgnore = new QAction(QAction::tr("Ignore"), chatNicks);
181
acIgnore->setIcon(QIcon(":/res/ignore.png"));
182
connect(acIgnore, SIGNAL(triggered(bool)), this, SLOT(onIgnore()));
183
acFriend = new QAction(QAction::tr("Add friend"), chatNicks);
184
acFriend->setIcon(QIcon(":/res/addfriend.png"));
185
connect(acFriend, SIGNAL(triggered(bool)), this, SLOT(onFriend()));
187
chatNicks->insertAction(0, acFriend);
188
chatNicks->insertAction(0, acInfo);
189
chatNicks->insertAction(0, acIgnore);
195
void HWChatWidget::linkClicked(const QUrl & link)
197
if (link.scheme() == "http")
198
QDesktopServices::openUrl(link);
199
if (link.scheme() == "hwnick")
202
const QString& nick = QString::fromUtf8(QByteArray::fromBase64(link.encodedQuery()));
203
QList<QListWidgetItem *> items = chatNicks->findItems(nick, Qt::MatchExactly);
204
if (items.size() < 1)
206
QMenu * popup = new QMenu(this);
207
// selecting an item will automatically scroll there, so let's save old position
208
QScrollBar * scrollBar = chatNicks->verticalScrollBar();
209
int oldScrollPos = scrollBar->sliderPosition();
210
// select the nick which we want to see the actions for
211
chatNicks->setCurrentItem(items[0], QItemSelectionModel::Clear);
212
// selecting an item will automatically scroll there, so let's save old position
213
scrollBar->setSliderPosition(oldScrollPos);
215
popup->addActions(chatNicks->actions());
216
// display menu popup at mouse cursor position
217
popup->popup(QCursor::pos());
221
void HWChatWidget::setShowFollow(bool enabled)
224
if (!(chatNicks->actions().contains(acFollow)))
225
chatNicks->insertAction(acFriend, acFollow);
228
if (chatNicks->actions().contains(acFollow))
229
chatNicks->removeAction(acFollow);
233
void HWChatWidget::loadList(QStringList & list, const QString & file)
236
QFile txt(cfgdir->absolutePath() + "/" + file);
237
if(!txt.open(QIODevice::ReadOnly))
239
QTextStream stream(&txt);
240
stream.setCodec("UTF-8");
242
while(!stream.atEnd())
244
QString str = stream.readLine();
245
if(str.startsWith(";") || str.length() == 0)
247
list << str.trimmed();
249
//readd once we require newer Qt than 4.4
250
//list.removeDuplicates();
254
void HWChatWidget::saveList(QStringList & list, const QString & file)
256
QFile txt(cfgdir->absolutePath() + "/" + file);
257
if(!txt.open(QIODevice::WriteOnly | QIODevice::Truncate))
259
QTextStream stream(&txt);
260
stream.setCodec("UTF-8");
262
stream << "; this list is used by Hedgewars - do not edit it unless you know what you're doing!" << endl;
263
for(int i = 0; i < list.size(); i++)
264
stream << list[i] << endl;
268
void HWChatWidget::updateNickItem(QListWidgetItem *nickItem)
270
QString nick = nickItem->text();
271
ListWidgetNickItem * item = dynamic_cast<ListWidgetNickItem*>(nickItem);
273
item->setFriend(friendsList.contains(nick, Qt::CaseInsensitive));
274
item->setIgnored(ignoreList.contains(nick, Qt::CaseInsensitive));
278
item->setIcon(QIcon(showReady ? (item->data(Qt::UserRole).toBool() ? ":/res/chat_ignore_on.png" : ":/res/chat_ignore_off.png") : ":/res/chat_ignore.png"));
279
item->setForeground(Qt::gray);
281
else if(item->isFriend())
283
item->setIcon(QIcon(showReady ? (item->data(Qt::UserRole).toBool() ? ":/res/chat_friend_on.png" : ":/res/chat_friend_off.png") : ":/res/chat_friend.png"));
284
item->setForeground(Qt::green);
288
item->setIcon(QIcon(showReady ? (item->data(Qt::UserRole).toBool() ? ":/res/chat_default_on.png" : ":/res/chat_default_off.png") : ":/res/chat_default.png"));
289
item->setForeground(QBrush(QColor(0xff, 0xcc, 0x00)));
293
void HWChatWidget::updateNickItems()
295
for(int i = 0; i < chatNicks->count(); i++)
296
updateNickItem(chatNicks->item(i));
298
chatNicks->sortItems();
301
void HWChatWidget::loadLists(const QString & nick)
303
loadList(ignoreList, nick.toLower() + "_ignore.txt");
304
loadList(friendsList, nick.toLower() + "_friends.txt");
308
void HWChatWidget::saveLists(const QString & nick)
310
saveList(ignoreList, nick.toLower() + "_ignore.txt");
311
saveList(friendsList, nick.toLower() + "_friends.txt");
314
void HWChatWidget::returnPressed()
316
emit chatLine(chatEditLine->text());
317
chatEditLine->clear();
321
void HWChatWidget::onChatString(const QString& str)
323
onChatString("", str);
326
const QRegExp HWChatWidget::URLREGEXP = QRegExp("(http://)?(www\\.)?(hedgewars\\.org(/[^ ]*)?)");
328
void HWChatWidget::onChatString(const QString& nick, const QString& str)
330
bool isFriend = false;
332
if (!nick.isEmpty()) {
333
// don't show chat lines that are from ignored nicks
334
if (ignoreList.contains(nick, Qt::CaseInsensitive))
336
// friends will get special treatment, of course
337
isFriend = friendsList.contains(nick, Qt::CaseInsensitive);
340
if (chatStrings.size() > 250)
341
chatStrings.removeFirst();
343
QString formattedStr = Qt::escape(str.mid(1));
344
// make hedgewars.org urls actual links
345
formattedStr = formattedStr.replace(URLREGEXP, "<a href=\"http://\\3\">\\3</a>");
347
// "link" nick, but before that encode it in base64 to make sure it can't intefere with html/url syntax
348
// the nick is put as querystring as putting it as host would convert it to it's lower case variant
350
formattedStr.replace("|nick|",QString("<a href=\"hwnick://?%1\" class=\"nick\">%2</a>").arg(QString(nick.toUtf8().toBase64())).arg(nick));
352
QString cssClass("UserChat");
354
// check first character for color code and set color properly
355
switch (str[0].toAscii()) {
357
cssClass = (isFriend ? "FriendJoin" : "UserJoin");
360
cssClass = (isFriend ? "FriendAction" : "UserAction");
364
cssClass = "FriendChat";
367
formattedStr = QString("<span class=\"%2\">%1</span>").arg(formattedStr).arg(cssClass);
369
chatStrings.append(formattedStr);
371
chatText->setHtml(chatStrings.join("<br>"));
373
chatText->moveCursor(QTextCursor::End);
376
void HWChatWidget::onServerMessage(const QString& str)
378
if (chatStrings.size() > 250)
379
chatStrings.removeFirst();
381
chatStrings.append("<hr>" + str + "<hr>");
383
chatText->setHtml(chatStrings.join("<br>"));
385
chatText->moveCursor(QTextCursor::End);
388
void HWChatWidget::nickAdded(const QString& nick, bool notifyNick)
390
QListWidgetItem * item = new ListWidgetNickItem(nick, friendsList.contains(nick, Qt::CaseInsensitive), ignoreList.contains(nick, Qt::CaseInsensitive));
391
updateNickItem(item);
392
chatNicks->addItem(item);
394
emit nickCountUpdate(chatNicks->count());
396
if(notifyNick && notify && gameSettings->value("frontend/sound", true).toBool()) {
397
Mix_PlayChannel(-1, sound[rand()%4], 0);
401
void HWChatWidget::nickRemoved(const QString& nick)
403
foreach(QListWidgetItem * item, chatNicks->findItems(nick, Qt::MatchExactly))
404
chatNicks->takeItem(chatNicks->row(item));
406
emit nickCountUpdate(chatNicks->count());
409
void HWChatWidget::clear()
416
void HWChatWidget::onKick()
418
QListWidgetItem * curritem = chatNicks->currentItem();
420
emit kick(curritem->text());
423
void HWChatWidget::onBan()
425
QListWidgetItem * curritem = chatNicks->currentItem();
427
emit ban(curritem->text());
430
void HWChatWidget::onInfo()
432
QListWidgetItem * curritem = chatNicks->currentItem();
434
emit info(curritem->text());
437
void HWChatWidget::onFollow()
439
QListWidgetItem * curritem = chatNicks->currentItem();
441
emit follow(curritem->text());
444
void HWChatWidget::onIgnore()
446
QListWidgetItem * curritem = chatNicks->currentItem();
450
if(ignoreList.contains(curritem->text(), Qt::CaseInsensitive)) // already on list - remove him
452
ignoreList.removeAll(curritem->text().toLower());
453
onChatString(HWChatWidget::tr("%1 *** %2 has been removed from your ignore list").arg('\x03').arg(curritem->text()));
455
else // not on list - add
457
// don't consider ignored people friends
458
if(friendsList.contains(curritem->text(), Qt::CaseInsensitive))
461
// scroll down on first ignore added so that people see where that nick went to
462
if (ignoreList.isEmpty())
463
chatNicks->scrollToBottom();
465
ignoreList << curritem->text().toLower();
466
onChatString(HWChatWidget::tr("%1 *** %2 has been added to your ignore list").arg('\x03').arg(curritem->text()));
468
updateNickItem(curritem); // update icon/sort order/etc
469
chatNicks->sortItems();
470
chatNickSelected(0); // update context menu
473
void HWChatWidget::onFriend()
475
QListWidgetItem * curritem = chatNicks->currentItem();
479
if(friendsList.contains(curritem->text(), Qt::CaseInsensitive)) // already on list - remove him
481
friendsList.removeAll(curritem->text().toLower());
482
onChatString(HWChatWidget::tr("%1 *** %2 has been removed from your friends list").arg('\x03').arg(curritem->text()));
484
else // not on list - add
486
// don't ignore the new friend
487
if(ignoreList.contains(curritem->text(), Qt::CaseInsensitive))
490
// scroll up on first friend added so that people see where that nick went to
491
if (friendsList.isEmpty())
492
chatNicks->scrollToTop();
494
friendsList << curritem->text().toLower();
495
onChatString(HWChatWidget::tr("%1 *** %2 has been added to your friends list").arg('\x03').arg(curritem->text()));
497
updateNickItem(curritem); // update icon/sort order/etc
498
chatNicks->sortItems();
499
chatNickSelected(0); // update context menu
502
void HWChatWidget::chatNickDoubleClicked(QListWidgetItem * item)
506
QList<QAction *> actions = chatNicks->actions();
507
actions.first()->activate(QAction::Trigger);
510
void HWChatWidget::chatNickSelected(int index)
514
QListWidgetItem* item = chatNicks->currentItem();
518
// update context menu labels according to possible action
519
if(ignoreList.contains(item->text(), Qt::CaseInsensitive))
521
acIgnore->setText(QAction::tr("Unignore"));
522
acIgnore->setIcon(QIcon(":/res/unignore.png"));
526
acIgnore->setText(QAction::tr("Ignore"));
527
acIgnore->setIcon(QIcon(":/res/ignore.png"));
530
if(friendsList.contains(item->text(), Qt::CaseInsensitive))
532
acFriend->setText(QAction::tr("Remove friend"));
533
acFriend->setIcon(QIcon(":/res/remfriend.png"));
537
acFriend->setText(QAction::tr("Add friend"));
538
acFriend->setIcon(QIcon(":/res/addfriend.png"));
542
void HWChatWidget::setShowReady(bool s)
547
void HWChatWidget::setReadyStatus(const QString & nick, bool isReady)
549
QList<QListWidgetItem *> items = chatNicks->findItems(nick, Qt::MatchExactly);
550
if (items.size() != 1)
552
qWarning("Bug: cannot find user in chat");
556
items[0]->setData(Qt::UserRole, isReady); // bulb status
557
updateNickItem(items[0]);
559
// ensure we're still showing the status bulbs
563
void HWChatWidget::adminAccess(bool b)
565
chatNicks->removeAction(acKick);
566
chatNicks->removeAction(acBan);
570
chatNicks->insertAction(0, acKick);
571
// chatNicks->insertAction(0, acBan);