chatwindow.cpp 58.1 KB
Newer Older
Andrei Karas's avatar
Andrei Karas committed
1
/*
Andrei Karas's avatar
Andrei Karas committed
2
 *  The ManaPlus Client
Andrei Karas's avatar
Andrei Karas committed
3 4
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
Andrei Karas's avatar
Andrei Karas committed
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
Andrei Karas's avatar
Andrei Karas committed
6
 *
Andrei Karas's avatar
Andrei Karas committed
7
 *  This file is part of The ManaPlus Client.
Andrei Karas's avatar
Andrei Karas committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 *  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; either version 2 of the License, or
 *  any later version.
 *
 *  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, see <http://www.gnu.org/licenses/>.
 */

23
#include "gui/windows/chatwindow.h"
Andrei Karas's avatar
Andrei Karas committed
24

25
#include "actormanager.h"
Andrei Karas's avatar
Andrei Karas committed
26
#include "game.h"
Andrei Karas's avatar
Andrei Karas committed
27 28
#include "guild.h"
#include "party.h"
29
#include "settings.h"
30
#include "spellmanager.h"
Andrei Karas's avatar
Andrei Karas committed
31

32 33
#include "being/localplayer.h"
#include "being/playerinfo.h"
34
#include "being/playerrelations.h"
35

36 37
#include "const/gui/chat.h"

38
#include "fs/virtfs/tools.h"
39

40 41
#include "input/inputmanager.h"

42
#include "gui/focushandler.h"
Andrei Karas's avatar
Andrei Karas committed
43
#include "gui/gui.h"
44
#include "gui/skin.h"
45
#include "gui/viewport.h"
46

47 48
#include "gui/models/colorlistmodel.h"

49 50
#include "gui/popups/popupmenu.h"

51
#include "gui/windows/setupwindow.h"
52
#include "gui/windows/whoisonline.h"
Andrei Karas's avatar
Andrei Karas committed
53

54
#include "gui/widgets/button.h"
55
#include "gui/widgets/chatinput.h"
56
#include "gui/widgets/createwidget.h"
Andrei Karas's avatar
Andrei Karas committed
57 58 59
#include "gui/widgets/dropdown.h"
#include "gui/widgets/itemlinkhandler.h"
#include "gui/widgets/scrollarea.h"
60
#include "gui/widgets/tabbedarea.h"
61

62 63
#include "gui/widgets/tabs/chat/battletab.h"
#include "gui/widgets/tabs/chat/channeltab.h"
64
#include "gui/widgets/tabs/chat/gmtab.h"
65
#include "gui/widgets/tabs/chat/langtab.h"
66
#include "gui/widgets/tabs/chat/tradetab.h"
67
#include "gui/widgets/tabs/chat/whispertab.h"
Andrei Karas's avatar
Andrei Karas committed
68

69
#include "render/opengl/opengldebug.h"
70

71 72
#include "resources/db/textdb.h"

Andrei Karas's avatar
Andrei Karas committed
73
#include "net/chathandler.h"
74
#include "net/net.h"
Andrei Karas's avatar
Andrei Karas committed
75

76
#include "utils/copynpaste.h"
77
#include "utils/delete2.h"
78
#include "utils/foreach.h"
Andrei Karas's avatar
Andrei Karas committed
79

80 81
#include "utils/translation/podict.h"

Andrei Karas's avatar
Andrei Karas committed
82 83
#include <sys/stat.h>

84
#include <fstream>
Andrei Karas's avatar
Andrei Karas committed
85 86
#include <sstream>

87 88
#include "debug.h"

89 90
ChatWindow *chatWindow = nullptr;

Andrei Karas's avatar
Andrei Karas committed
91
static const char *const ACTION_COLOR_PICKER = "color picker";
Andrei Karas's avatar
Andrei Karas committed
92

93
ChatWindow::ChatWindow(const std::string &name) :
94
    // TRANSLATORS: chat window name
95
    Window(_("Chat"), Modal_false, nullptr, "chat.xml"),
96
    ActionListener(),
97
    KeyListener(),
Andrei Karas's avatar
Andrei Karas committed
98
    AttributeListener(),
99
    mItemLinkHandler(new ItemLinkHandler),
100
    mChatTabs(CREATEWIDGETR(TabbedArea, this)),
Andrei Karas's avatar
Andrei Karas committed
101
    mChatInput(new ChatInput(this)),
102
    mRainbowColor(0U),
103
    mWhispers(),
104
    mChannels(),
105 106 107 108 109
    mHistory(),
    mCurHist(),
    mCommands(),
    mCustomWords(),
    mTradeFilter(),
110
    mColorListModel(new ColorListModel),
111 112
    mColorPicker(new DropDown(this, mColorListModel,
        false, Modal_false, nullptr, std::string())),
113
    mChatButton(new Button(this, ":)", "openemote", BUTTON_SKIN, this)),
114 115
    mAwayLog(),
    mHighlights(),
Andrei Karas's avatar
Andrei Karas committed
116
    mGlobalsFilter(),
117
    mChatColor(config.getIntValue("chatColor")),
118 119 120 121
    mEmoteButtonSpacing(mSkin != nullptr ?
                        mSkin->getOption("emoteButtonSpacing", 2) : 2),
    mEmoteButtonY(mSkin != nullptr ?
                  mSkin->getOption("emoteButtonY", -2) : -2),
122 123
    mChatHistoryIndex(0),
    mReturnToggles(config.getBoolValue("ReturnToggles")),
124 125
    mGMLoaded(false),
    mHaveMouse(false),
126 127
    mAutoHide(config.getBoolValue("autohideChat")),
    mShowBattleEvents(config.getBoolValue("showBattleEvents")),
128
    mShowAllLang(serverConfig.getValue("showAllLang", 0) != 0),
129
    mEnableTradeFilter(config.getBoolValue("enableTradeFilter")),
Andrei Karas's avatar
Andrei Karas committed
130
    mTmpVisible(false)
Andrei Karas's avatar
Andrei Karas committed
131
{
132
    setWindowName(name);
Andrei Karas's avatar
Andrei Karas committed
133

134
    if (setupWindow != nullptr)
Andrei Karas's avatar
Andrei Karas committed
135 136 137 138 139 140
        setupWindow->registerWindowForReset(this);

    setShowTitle(false);
    setResizable(true);
    setDefaultVisible(true);
    setSaveVisible(true);
141 142
    setStickyButtonLock(true);

143
    int w = 600;
144
#ifdef ANDROID
145 146 147 148 149
    if (mainGraphics->getWidth() < 710)
        w = mainGraphics->getWidth() - 110;
    if (w < 100)
        w = 100;
    if (mainGraphics->getHeight() < 480)
150
        setDefaultSize(w, 90, ImagePosition::UPPER_LEFT, -110, -35);
151
    else
152
        setDefaultSize(w, 123, ImagePosition::UPPER_LEFT, -110, -35);
153 154
#else  // ANDROID

155 156 157 158
    if (mainGraphics->getWidth() < 600)
        w = mainGraphics->getWidth() - 10;
    if (w < 100)
        w = 100;
159
    setDefaultSize(w, 123, ImagePosition::LOWER_LEFT, 0, 0);
160 161
#endif  // ANDROID

Andrei Karas's avatar
Andrei Karas committed
162 163 164
    setMinWidth(150);
    setMinHeight(90);

165 166
    setTitleBarHeight(getPadding() + getTitlePadding());

167
    if (emoteWindow != nullptr)
168 169
        emoteWindow->addListeners(this);

170 171
    mChatButton->adjustSize();

172
    mChatTabs->enableScrollButtons(true);
173
    mChatTabs->setFollowDownScroll(true);
174
    mChatTabs->setResizeHeight(false);
Andrei Karas's avatar
Andrei Karas committed
175

176 177
    mChatInput->setActionEventId("chatinput");
    mChatInput->addActionListener(this);
178
    mChatInput->setAllowSpecialActions(false);
179

Andrei Karas's avatar
Andrei Karas committed
180 181 182 183
    mColorPicker->setActionEventId(ACTION_COLOR_PICKER);
    mColorPicker->addActionListener(this);
    mColorPicker->setSelected(mChatColor);

184 185
    mItemLinkHandler->setAllowCommands(false);

Andrei Karas's avatar
Andrei Karas committed
186 187 188
    loadWindowState();

    mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
Andrei Karas's avatar
Andrei Karas committed
189
        - 2 * mPadding - 8 - 16, mPadding);
Andrei Karas's avatar
Andrei Karas committed
190 191 192 193

    // Add key listener to chat input to be able to respond to up/down
    mChatInput->addKeyListener(this);
    mCurHist = mHistory.end();
194 195
    mColorPicker->setVisible(fromBool(config.getBoolValue(
        "showChatColorsList"), Visible));
196
    updateTabsMargin();
Andrei Karas's avatar
Andrei Karas committed
197 198

    fillCommands();
199
    if ((localPlayer != nullptr) && localPlayer->isGM())
200
        loadGMCommands();
Andrei Karas's avatar
Andrei Karas committed
201
    initTradeFilter();
202
    loadCustomList();
203
    parseHighlights();
Andrei Karas's avatar
Andrei Karas committed
204
    parseGlobalsFilter();
205 206

    config.addListener("autohideChat", this);
207
    config.addListener("showBattleEvents", this);
Andrei Karas's avatar
Andrei Karas committed
208
    config.addListener("globalsFilter", this);
209
    config.addListener("enableTradeFilter", this);
210

211
    enableVisibleSound(true);
Andrei Karas's avatar
Andrei Karas committed
212 213 214 215
}

ChatWindow::~ChatWindow()
{
216
    config.removeListeners(this);
217
    CHECKLISTENERS
218
    saveState();
Andrei Karas's avatar
Andrei Karas committed
219 220
    config.setValue("ReturnToggles", mReturnToggles);
    removeAllWhispers();
221
    removeAllChannels();
222 223 224
    delete2(mItemLinkHandler)
    delete2(mColorPicker)
    delete2(mColorListModel)
Andrei Karas's avatar
Andrei Karas committed
225 226
}

227 228
void ChatWindow::postInit()
{
229
    Window::postInit();
230 231 232
    add(mChatTabs);
    add(mChatInput);
    add(mColorPicker);
233
    add(mChatButton);
234 235 236
    updateVisibility();
}

Andrei Karas's avatar
Andrei Karas committed
237
void ChatWindow::loadCommandsFile(const std::string &name)
Andrei Karas's avatar
Andrei Karas committed
238
{
239
    StringVect list;
240
    VirtFs::loadTextFile(name, list);
241
    StringVectCIter it = list.begin();
242
    const StringVectCIter it_end = list.end();
243 244 245 246 247 248 249 250 251 252

    while (it != it_end)
    {
        const std::string str = *it;
        if (!str.empty())
            mCommands.push_back(str);
        ++ it;
    }
}

Andrei Karas's avatar
Andrei Karas committed
253
void ChatWindow::fillCommands()
Andrei Karas's avatar
Andrei Karas committed
254
{
Andrei Karas's avatar
Andrei Karas committed
255
    loadCommandsFile("chatcommands.txt");
256
    inputManager.addChatCommands(mCommands);
Andrei Karas's avatar
Andrei Karas committed
257 258 259 260 261 262 263 264 265
}

void ChatWindow::loadGMCommands()
{
    if (mGMLoaded)
        return;

    loadCommandsFile("gmcommands.txt");
    mGMLoaded = true;
Andrei Karas's avatar
Andrei Karas committed
266 267
}

268 269
void ChatWindow::updateTabsMargin()
{
270
    if (mColorPicker->mVisible == Visible_true)
271 272 273 274 275
        mChatTabs->setRightMargin(mColorPicker->getWidth() + 16 + 8);
    else
        mChatTabs->setRightMargin(8);
}

Andrei Karas's avatar
Andrei Karas committed
276 277
void ChatWindow::adjustTabSize()
{
278
    const Rect area = getChildrenArea();
Andrei Karas's avatar
Andrei Karas committed
279

Andrei Karas's avatar
Andrei Karas committed
280 281 282 283
    const int aw = area.width;
    const int ah = area.height;
    const int frame = mChatInput->getFrameSize();
    const int inputHeight = mChatInput->getHeight();
284 285 286 287 288 289 290 291
    const bool showEmotes = config.getBoolValue("showEmotesButton");
    int maxHeight = inputHeight;
    if (showEmotes)
    {
        const int buttonHeight = mChatButton->getHeight();
        if (buttonHeight > maxHeight)
            maxHeight = buttonHeight;
    }
Andrei Karas's avatar
Andrei Karas committed
292 293
    const int frame2 = 2 * frame;
    const int awFrame2 = aw - frame2;
294
    int y = ah - maxHeight - frame;
295
    mChatInput->setPosition(frame, y);
Andrei Karas's avatar
Andrei Karas committed
296
    mChatTabs->setWidth(awFrame2);
297
    const int height = ah - frame2 - (maxHeight + frame2);
298 299 300
    if (mChatInput->mVisible == Visible_true ||
        !config.getBoolValue("hideChatInput"))
    {
301
        mChatTabs->setHeight(height);
302
    }
303
    else
304
    {
305
        mChatTabs->setHeight(height + maxHeight);
306
    }
307
    updateTabsMargin();
Andrei Karas's avatar
Andrei Karas committed
308

309 310
    if (showEmotes)
    {
311
        const int chatButtonSize = mChatButton->getWidth();
312
        int w = awFrame2 - chatButtonSize;
Andrei Karas's avatar
Andrei Karas committed
313
        const int x = aw - frame - chatButtonSize;
314 315
        w -= mEmoteButtonSpacing;
        y += mEmoteButtonY;
316
        mChatInput->setWidth(w);
317
        mChatButton->setVisible(mChatInput->mVisible);
318
        mChatButton->setPosition(x, y);
319 320 321
    }
    else
    {
322
        mChatInput->setWidth(awFrame2);
323
        mChatButton->setVisible(Visible_false);
324 325
    }

326
    const ChatTab *const tab = getFocused();
327
    if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
328
    {
329
        Widget *const content = tab->mScrollArea;
330
        if (content != nullptr)
Andrei Karas's avatar
Andrei Karas committed
331
        {
Andrei Karas's avatar
Andrei Karas committed
332 333 334
            const int contentFrame2 = 2 * content->getFrameSize();
            content->setSize(mChatTabs->getWidth() - contentFrame2,
                mChatTabs->getContainerHeight() - contentFrame2);
Andrei Karas's avatar
Andrei Karas committed
335 336 337 338 339
            content->logic();
        }
    }

    mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
Andrei Karas's avatar
Andrei Karas committed
340
        - 2 * mPadding - 8 - 16, mPadding);
Andrei Karas's avatar
Andrei Karas committed
341 342

    mChatTabs->adjustSize();
Andrei Karas's avatar
Andrei Karas committed
343 344
}

345
void ChatWindow::widgetResized(const Event &event)
Andrei Karas's avatar
Andrei Karas committed
346 347 348 349 350 351 352 353 354 355 356
{
    Window::widgetResized(event);

    adjustTabSize();
}

ChatTab *ChatWindow::getFocused() const
{
    return static_cast<ChatTab*>(mChatTabs->getSelectedTab());
}

357
void ChatWindow::clearTab(ChatTab *const tab)
Andrei Karas's avatar
Andrei Karas committed
358
{
359
    if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
360 361 362
        tab->clearText();
}

Andrei Karas's avatar
Andrei Karas committed
363
void ChatWindow::clearTab() const
Andrei Karas's avatar
Andrei Karas committed
364 365 366 367 368 369
{
    clearTab(getFocused());
}

void ChatWindow::prevTab()
{
370
    if (mChatTabs == nullptr)
Andrei Karas's avatar
Andrei Karas committed
371 372 373 374
        return;

    int tab = mChatTabs->getSelectedTabIndex();

375
    if (tab <= 0)
Andrei Karas's avatar
Andrei Karas committed
376 377 378
        tab = mChatTabs->getNumberOfTabs();
    tab--;

Andrei Karas's avatar
Andrei Karas committed
379
    mChatTabs->setSelectedTabByIndex(tab);
Andrei Karas's avatar
Andrei Karas committed
380 381 382 383
}

void ChatWindow::nextTab()
{
384
    if (mChatTabs == nullptr)
Andrei Karas's avatar
Andrei Karas committed
385 386 387 388 389 390 391 392
        return;

    int tab = mChatTabs->getSelectedTabIndex();

    tab++;
    if (tab == mChatTabs->getNumberOfTabs())
        tab = 0;

Andrei Karas's avatar
Andrei Karas committed
393
    mChatTabs->setSelectedTabByIndex(tab);
Andrei Karas's avatar
Andrei Karas committed
394
}
395

396
void ChatWindow::selectTabByType(const ChatTabTypeT &type)
397
{
398
    if (mChatTabs == nullptr)
399 400 401 402 403 404 405
        return;

    int sz = mChatTabs->getNumberOfTabs();
    for (int f = 0; f < sz; f ++)
    {
        ChatTab *const tab = dynamic_cast<ChatTab*>(
            mChatTabs->getTabByIndex(f));
406
        if ((tab != nullptr) && tab->getType() == type)
407 408 409 410 411 412
        {
            mChatTabs->setSelectedTab(tab);
            break;
        }
    }
}
Andrei Karas's avatar
Andrei Karas committed
413

414
void ChatWindow::closeTab() const
415
{
416
    if (mChatTabs == nullptr)
417 418
        return;

419 420
    ChatTab *const tab = dynamic_cast<ChatTab*>(mChatTabs->getTabByIndex(
        mChatTabs->getSelectedTabIndex()));
421
    if (tab == nullptr)
422
        return;
423
    const ChatTabTypeT &type = tab->getType();
424 425
    if (type == ChatTabType::WHISPER || type == ChatTabType::CHANNEL)
        tab->handleCommand("close", "");
426 427
}

Andrei Karas's avatar
Andrei Karas committed
428 429
void ChatWindow::defaultTab()
{
430
    if (mChatTabs != nullptr)
431
        mChatTabs->setSelectedTabByIndex(CAST_U32(0));
Andrei Karas's avatar
Andrei Karas committed
432 433
}

434
void ChatWindow::action(const ActionEvent &event)
Andrei Karas's avatar
Andrei Karas committed
435
{
436 437
    const std::string &eventId = event.getId();
    if (eventId == "chatinput")
Andrei Karas's avatar
Andrei Karas committed
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
    {
        std::string message = mChatInput->getText();

        if (!message.empty())
        {
            // If message different from previous, put it in the history
            if (mHistory.empty() || message != mHistory.back())
                mHistory.push_back(message);

            // Reset history iterator
            mCurHist = mHistory.end();

            // Send the message to the server
            chatInput(addColors(message));

            // Clear the text from the chat input
            mChatInput->setText("");
        }

        if (message.empty() || !mReturnToggles)
        {
            // Remove focus and hide input
460
            mChatInput->unprotectFocus();
461
            if (mFocusHandler != nullptr)
Andrei Karas's avatar
Andrei Karas committed
462 463 464 465 466
                mFocusHandler->focusNone();

            // If the chatWindow is shown up because you want to send a message
            // It should hide now
            if (mTmpVisible)
467
                setVisible(Visible_false);
Andrei Karas's avatar
Andrei Karas committed
468 469
        }
    }
470 471
    else if (eventId == "emote")
    {
472
        if (emoteWindow != nullptr)
473 474 475 476 477 478 479 480 481
        {
            const std::string str = emoteWindow->getSelectedEmote();
            if (!str.empty())
            {
                addInputText(str, false);
                emoteWindow->clearEmote();
            }
        }
    }
482 483
    else if (eventId == "openemote")
    {
484
        if (emoteWindow != nullptr)
485
        {
486
            if (emoteWindow->mVisible == Visible_true)
487 488 489 490 491
                emoteWindow->hide();
            else
                emoteWindow->show();
        }
    }
492 493
    else if (eventId == "color")
    {
494
        if (emoteWindow != nullptr)
495 496 497 498 499 500 501 502 503
        {
            const std::string str = emoteWindow->getSelectedColor();
            if (!str.empty())
            {
                addInputText(str, false);
                emoteWindow->clearColor();
            }
        }
    }
504 505
    else if (eventId == "font")
    {
506
        if (emoteWindow != nullptr)
507 508 509 510 511 512 513 514 515
        {
            const std::string str = emoteWindow->getSelectedFont();
            if (!str.empty())
            {
                addInputText(str, false);
                emoteWindow->clearFont();
            }
        }
    }
516 517
    else if (eventId == "text")
    {
518
        if ((emoteWindow != nullptr) && (reverseDictionary != nullptr))
519 520 521 522 523 524 525 526 527 528 529
        {
            const int idx = emoteWindow->getSelectedTextIndex();
            if (idx >= 0)
            {
                const std::string str = TextDb::getByIndex(idx);
                const std::string enStr = reverseDictionary->getStr(str);
                addInputText(enStr, false);
            }
            emoteWindow->clearText();
        }
    }
530
    else if (eventId == ACTION_COLOR_PICKER)
Andrei Karas's avatar
Andrei Karas committed
531
    {
532
        if (mColorPicker != nullptr)
533 534 535 536
        {
            mChatColor = mColorPicker->getSelected();
            config.setValue("chatColor", mChatColor);
        }
Andrei Karas's avatar
Andrei Karas committed
537 538
    }

539
    if (mColorPicker != nullptr)
Andrei Karas's avatar
Andrei Karas committed
540
    {
541 542 543 544
        const Visible vis = fromBool(config.getBoolValue(
            "showChatColorsList"), Visible);
        if (mColorPicker->mVisible != vis)
            mColorPicker->setVisible(vis);
Andrei Karas's avatar
Andrei Karas committed
545 546 547 548 549 550
    }
}

bool ChatWindow::requestChatFocus()
{
    // Make sure chatWindow is visible
551
    if (!isWindowVisible())
Andrei Karas's avatar
Andrei Karas committed
552
    {
553
        setVisible(Visible_true);
Andrei Karas's avatar
Andrei Karas committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567

        /*
         * This is used to hide chatWindow after sending the message. There is
         * a trick here, because setVisible will set mTmpVisible to false, you
         * have to put this sentence *after* setVisible, not before it
         */
        mTmpVisible = true;
    }

    // Don't do anything else if the input is already visible and has focus
    if (mChatInput->isVisible() && mChatInput->isFocused())
        return false;

    // Give focus to the chat input
568
    mChatInput->processVisible(Visible_true);
569
    unHideWindow();
Andrei Karas's avatar
Andrei Karas committed
570 571 572 573 574 575 576 577 578
    mChatInput->requestFocus();
    return true;
}

bool ChatWindow::isInputFocused() const
{
    return mChatInput->isFocused();
}

579
void ChatWindow::removeTab(ChatTab *const tab)
Andrei Karas's avatar
Andrei Karas committed
580 581 582 583
{
    mChatTabs->removeTab(tab);
}

584
void ChatWindow::addTab(ChatTab *const tab)
Andrei Karas's avatar
Andrei Karas committed
585
{
586
    if (tab == nullptr)
587 588
        return;

Andrei Karas's avatar
Andrei Karas committed
589 590 591 592 593 594 595 596 597 598 599
    mChatTabs->addTab(tab, tab->mScrollArea);
    logic();
}

void ChatWindow::removeWhisper(const std::string &nick)
{
    std::string tempNick = nick;
    toLower(tempNick);
    mWhispers.erase(tempNick);
}

600 601 602 603 604
void ChatWindow::removeChannel(const std::string &name)
{
    std::string tempName = name;
    toLower(tempName);
    mChannels.erase(tempName);
605
    chatHandler->partChannel(name);
606 607
}

Andrei Karas's avatar
Andrei Karas committed
608 609 610 611
void ChatWindow::removeAllWhispers()
{
    std::list<ChatTab*> tabs;

612
    FOR_EACH (TabMap::iterator, iter, mWhispers)
Andrei Karas's avatar
Andrei Karas committed
613 614 615 616 617 618 619 620 621 622 623
        tabs.push_back(iter->second);

    for (std::list<ChatTab*>::iterator it = tabs.begin();
         it != tabs.end(); ++it)
    {
        delete *it;
    }

    mWhispers.clear();
}

624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
void ChatWindow::removeAllChannels()
{
    std::list<ChatTab*> tabs;

    FOR_EACH (ChannelMap::iterator, iter, mChannels)
        tabs.push_back(iter->second);

    for (std::list<ChatTab*>::iterator it = tabs.begin();
         it != tabs.end(); ++it)
    {
        delete *it;
    }

    mChannels.clear();
}

Andrei Karas's avatar
Andrei Karas committed
640 641
void ChatWindow::ignoreAllWhispers()
{
642 643 644
    for (TabMap::iterator iter = mWhispers.begin();
         iter != mWhispers.end();
         ++ iter)
Andrei Karas's avatar
Andrei Karas committed
645
    {
646
        WhisperTab *const tab = iter->second;
647
        if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
648
        {
649
            if (playerRelations.getRelation(tab->getNick())
650
                != Relation::IGNORED)
651
            {
652
                playerRelations.setRelation(tab->getNick(),
653
                    Relation::IGNORED);
654 655
            }
            tab->handleCommand("close", "");
Andrei Karas's avatar
Andrei Karas committed
656 657 658 659
        }
    }
}

660
void ChatWindow::chatInput(const std::string &message) const
Andrei Karas's avatar
Andrei Karas committed
661
{
662
    ChatTab *tab = nullptr;
Andrei Karas's avatar
Andrei Karas committed
663 664 665 666 667 668
    std::string msg = message;
    trim(msg);

    if (config.getBoolValue("allowCommandsInChatTabs")
        && msg.length() > 1
        && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@')
669
        && (localChatTab != nullptr))
Andrei Karas's avatar
Andrei Karas committed
670 671 672 673 674 675
    {
        tab = localChatTab;
    }
    else
    {
        tab = getFocused();
676 677
        if (tab == nullptr)
            tab = localChatTab;
Andrei Karas's avatar
Andrei Karas committed
678
    }
679
    if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
680
        tab->chatInput(msg);
681
    Game *const game = Game::instance();
682
    if (game != nullptr)
683
        game->setValidSpeed();
Andrei Karas's avatar
Andrei Karas committed
684 685
}

686
void ChatWindow::localChatInput(const std::string &msg) const
Andrei Karas's avatar
Andrei Karas committed
687
{
688
    if (localChatTab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
689 690 691 692 693
        localChatTab->chatInput(msg);
    else
        chatInput(msg);
}

694
void ChatWindow::doPresent() const
Andrei Karas's avatar
Andrei Karas committed
695
{
696
    if (actorManager == nullptr)
Andrei Karas's avatar
Andrei Karas committed
697 698
        return;

699
    const ActorSprites &actors = actorManager->getAll();
700
    std::string response;
Andrei Karas's avatar
Andrei Karas committed
701 702
    int playercount = 0;

703
    FOR_EACH (ActorSpritesIterator, it, actors)
Andrei Karas's avatar
Andrei Karas committed
704
    {
705
        if ((*it)->getType() == ActorType::Player)
Andrei Karas's avatar
Andrei Karas committed
706 707
        {
            if (!response.empty())
708 709
                response.append(", ");
            response.append(static_cast<Being*>(*it)->getName());
Andrei Karas's avatar
Andrei Karas committed
710
            playercount ++;
Andrei Karas's avatar
Andrei Karas committed
711 712 713
        }
    }

714 715 716 717 718 719 720
    ChatTab *const tab = getFocused();
    if (tab != nullptr)
    {
        const std::string log = strprintf(
            // TRANSLATORS: chat message
            _("Present: %s; %d players are present."),
            response.c_str(), playercount);
721 722 723 724
        tab->chatLog(log,
            ChatMsgType::BY_SERVER,
            IgnoreRecord_false,
            TryRemoveColors_true);
725
    }
Andrei Karas's avatar
Andrei Karas committed
726 727
}

728
void ChatWindow::scroll(const int amount) const
Andrei Karas's avatar
Andrei Karas committed
729
{
730
    if (!isWindowVisible())
Andrei Karas's avatar
Andrei Karas committed
731 732
        return;

733
    ChatTab *const tab = getFocused();
734
    if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
735 736 737
        tab->scroll(amount);
}

738
void ChatWindow::mousePressed(MouseEvent &event)
Andrei Karas's avatar
Andrei Karas committed
739
{
740 741 742
    if (event.isConsumed())
        return;

743
    if (event.getButton() == MouseButton::RIGHT)
Andrei Karas's avatar
Andrei Karas committed
744
    {
745
        if (popupMenu != nullptr)
Andrei Karas's avatar
Andrei Karas committed
746
        {
747 748
            ChatTab *const cTab = dynamic_cast<ChatTab*>(
                mChatTabs->getSelectedTab());
749
            if (cTab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
750
            {
751
                event.consume();
752
                if (inputManager.isActionActive(InputAction::CHAT_MOD))
753
                {
754 755 756
                    inputManager.executeChatCommand(
                        InputAction::CLOSE_CHAT_TAB,
                        std::string(), cTab);
757 758 759
                }
                else
                {
Andrei Karas's avatar
Andrei Karas committed
760 761 762
                    popupMenu->showChatPopup(viewport->mMouseX,
                        viewport->mMouseY,
                        cTab);
763
                }
Andrei Karas's avatar
Andrei Karas committed
764 765 766 767 768 769 770 771 772
            }
        }
    }

    Window::mousePressed(event);

    if (event.isConsumed())
        return;

773
    if (event.getButton() == MouseButton::LEFT)
Andrei Karas's avatar
Andrei Karas committed
774
    {
775 776 777 778
        const int clicks = event.getClickCount();
        if (clicks == 2)
        {
            toggleChatFocus();
779
            if (gui != nullptr)
780 781 782 783 784
                gui->resetClickCount();
        }
        else if (clicks == 1)
        {
            const ChatTab *const tab = getFocused();
785
            if (tab != nullptr)
786 787
                mMoved = !isResizeAllowed(event);
        }
Andrei Karas's avatar
Andrei Karas committed
788 789 790 791 792 793
    }

    mDragOffsetX = event.getX();
    mDragOffsetY = event.getY();
}

794
void ChatWindow::mouseDragged(MouseEvent &event)
Andrei Karas's avatar
Andrei Karas committed
795 796 797 798 799 800
{
    Window::mouseDragged(event);

    if (event.isConsumed())
        return;

801
    if (canMove() && isMovable() && mMoved)
Andrei Karas's avatar
Andrei Karas committed
802 803 804
    {
        int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
        int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
805 806
        newX = std::min(mainGraphics->mWidth - getWidth(), newX);
        newY = std::min(mainGraphics->mHeight - getHeight(), newY);
Andrei Karas's avatar
Andrei Karas committed
807 808 809 810
        setPosition(newX, newY);
    }
}

811
#define ifKey(key, str) \
812
    else if (actionId == (key)) \
813 814 815
    { \
        temp = str; \
    }
Andrei Karas's avatar
Andrei Karas committed
816

817
void ChatWindow::keyPressed(KeyEvent &event)
Andrei Karas's avatar
Andrei Karas committed
818
{
819
    const InputActionT actionId = event.getActionId();
820
    std::string temp;
821
    if (actionId == InputAction::GUI_DOWN)
Andrei Karas's avatar
Andrei Karas committed
822 823 824 825
    {
        if (mCurHist != mHistory.end())
        {
            // Move forward through the history
826
            const HistoryIterator prevHist = mCurHist++;
827
            addCurrentToHistory();
Andrei Karas's avatar
Andrei Karas committed
828 829 830 831

            if (mCurHist != mHistory.end())
            {
                mChatInput->setText(*mCurHist);
832
                mChatInput->setCaretPosition(CAST_U32(
Andrei Karas's avatar
Andrei Karas committed
833 834 835 836 837 838 839 840
                        mChatInput->getText().length()));
            }
            else
            {
                mChatInput->setText("");
                mCurHist = prevHist;
            }
        }
841
        else if (!mChatInput->getText().empty())
Andrei Karas's avatar
Andrei Karas committed
842
        {
843 844
            if (addCurrentToHistory())
                mCurHist = mHistory.end();
Andrei Karas's avatar
Andrei Karas committed
845 846 847
            mChatInput->setText("");
        }
    }
848 849 850
    else if (actionId == InputAction::GUI_UP &&
             mCurHist != mHistory.begin() &&
             !mHistory.empty())
Andrei Karas's avatar
Andrei Karas committed
851 852
    {
        // Move backward through the history
853
        --mCurHist;
854
        addCurrentToHistory();
Andrei Karas's avatar
Andrei Karas committed
855
        mChatInput->setText(*mCurHist);
856
        mChatInput->setCaretPosition(CAST_U32(
Andrei Karas's avatar
Andrei Karas committed
857 858
                mChatInput->getText().length()));
    }
859
    else if (actionId == InputAction::GUI_INSERT &&
860
             !mChatInput->getText().empty())
Andrei Karas's avatar
Andrei Karas committed
861
    {
Andrei Karas's avatar
Andrei Karas committed
862
        const std::string str = mChatInput->getText();
Andrei Karas's avatar
Andrei Karas committed
863
        // Add the current message to the history and clear the text
Andrei Karas's avatar
Andrei Karas committed
864 865
        if (mHistory.empty() || str != mHistory.back())
            mHistory.push_back(str);
Andrei Karas's avatar
Andrei Karas committed
866
        mCurHist = mHistory.end();
Andrei Karas's avatar
Andrei Karas committed
867
        mChatInput->setText(std::string());
Andrei Karas's avatar
Andrei Karas committed
868
    }
869
    else if (actionId == InputAction::GUI_TAB &&
Andrei Karas's avatar
Andrei Karas committed
870
             !mChatInput->getText().empty())
Andrei Karas's avatar
Andrei Karas committed
871 872 873 874
    {
        autoComplete();
        return;
    }
875
    else if (actionId == InputAction::GUI_CANCEL &&
876
             mChatInput->mVisible == Visible_true)
Andrei Karas's avatar
Andrei Karas committed
877
    {
878
        mChatInput->processVisible(Visible_false);
Andrei Karas's avatar
Andrei Karas committed
879
    }
880
    else if (actionId == InputAction::CHAT_PREV_HISTORY &&
881
             mChatInput->mVisible == Visible_true)
Andrei Karas's avatar
Andrei Karas committed
882
    {
883
        const ChatTab *const tab = getFocused();
884
        if ((tab != nullptr) && tab->hasRows())
Andrei Karas's avatar
Andrei Karas committed
885
        {
Andrei Karas's avatar
Andrei Karas committed
886
            if (mChatHistoryIndex == 0U)
Andrei Karas's avatar
Andrei Karas committed
887
            {
888
                mChatHistoryIndex = CAST_U32(
Andrei Karas's avatar
Andrei Karas committed
889 890 891 892 893 894
                    tab->getRows().size());

                mChatInput->setText("");
                mChatInput->setCaretPosition(0);
                return;
            }
Andrei Karas's avatar
Andrei Karas committed
895 896

            mChatHistoryIndex --;
Andrei Karas's avatar
Andrei Karas committed
897 898

            unsigned int f = 0;
899
            const std::list<std::string> &rows = tab->getRows();
Andrei Karas's avatar
Andrei Karas committed
900 901
            for (std::list<std::string>::const_iterator it = rows.begin(),
                 it_end = rows.end(); it != it_end; ++ it, f ++)
Andrei Karas's avatar
Andrei Karas committed
902 903 904 905
            {
                if (f == mChatHistoryIndex)
                    mChatInput->setText(*it);
            }
906
            mChatInput->setCaretPosition(CAST_U32(
Andrei Karas's avatar
Andrei Karas committed
907
                mChatInput->getText().length()));
Andrei Karas's avatar
Andrei Karas committed
908 909
        }
    }
910
    else if (actionId == InputAction::CHAT_NEXT_HISTORY &&
911
             mChatInput->mVisible == Visible_true)
Andrei Karas's avatar
Andrei Karas committed
912
    {
913
        const ChatTab *const tab = getFocused();
914
        if ((tab != nullptr) && tab->hasRows())
Andrei Karas's avatar
Andrei Karas committed
915
        {
Andrei Karas's avatar
Andrei Karas committed
916 917
            const std::list<std::string> &rows = tab->getRows();
            const size_t &tabSize = rows.size();
918
            if (CAST_SIZE(mChatHistoryIndex) + 1 < tabSize)
Andrei Karas's avatar
Andrei Karas committed
919 920 921
            {
                mChatHistoryIndex ++;
            }
922
            else if (CAST_SIZE(mChatHistoryIndex) < tabSize)
Andrei Karas's avatar
Andrei Karas committed
923 924 925 926 927 928 929 930 931 932 933 934
            {
                mChatHistoryIndex ++;
                mChatInput->setText("");
                mChatInput->setCaretPosition(0);
                return;
            }
            else
            {
                mChatHistoryIndex = 0;
            }

            unsigned int f = 0;
935
            for (std::list<std::string>::const_iterator
Andrei Karas's avatar
Andrei Karas committed
936
                 it = rows.begin(), it_end = rows.end();
937
                 it != it_end; ++it, f++)
Andrei Karas's avatar
Andrei Karas committed
938 939 940 941
            {
                if (f == mChatHistoryIndex)
                    mChatInput->setText(*it);
            }
942
            mChatInput->setCaretPosition(CAST_U32(
Andrei Karas's avatar
Andrei Karas committed
943 944 945
                    mChatInput->getText().length()));
        }
    }
946
    else if (actionId == InputAction::GUI_F1)
Andrei Karas's avatar
Andrei Karas committed
947
    {
948
        if (emoteWindow != nullptr)
949
        {
950
            if (emoteWindow->mVisible == Visible_true)
951 952 953 954
                emoteWindow->hide();
            else
                emoteWindow->show();
        }
Andrei Karas's avatar
Andrei Karas committed
955
    }
956 957 958 959 960 961 962 963 964 965 966 967
    ifKey(InputAction::GUI_F2, "\u2318")
    ifKey(InputAction::GUI_F3, "\u263A")
    ifKey(InputAction::GUI_F4, "\u2665")
    ifKey(InputAction::GUI_F5, "\u266A")
    ifKey(InputAction::GUI_F6, "\u266B")
    ifKey(InputAction::GUI_F7, "\u26A0")
    ifKey(InputAction::GUI_F8, "\u2622")
    ifKey(InputAction::GUI_F9, "\u262E")
    ifKey(InputAction::GUI_F10, "\u2605")
    ifKey(InputAction::GUI_F11, "\u2618")
    ifKey(InputAction::GUI_F12, "\u2592")

968
    if (inputManager.isActionActive(InputAction::GUI_CTRL))
969
    {
970
        if (actionId == InputAction::GUI_B)
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
        {
            std::string inputText = mChatInput->getTextBeforeCaret();
            toLower(inputText);
            const size_t idx = inputText.rfind("##b");
            if (idx == std::string::npos
                || mChatInput->getTextBeforeCaret().substr(idx, 3) == "##b")
            {
                temp = "##B";
            }
            else
            {
                temp = "##b";
            }
        }
    }

Andrei Karas's avatar
Andrei Karas committed
987
    if (!temp.empty())
988
        addInputText(temp, false);
Andrei Karas's avatar
Andrei Karas committed
989 990
}

991 992
#undef ifKey

993 994
bool ChatWindow::addCurrentToHistory()
{
Andrei Karas's avatar
Andrei Karas committed
995
    const std::string &str = mChatInput->getText();
996 997 998 999 1000 1001 1002 1003 1004 1005 1006
    if (str.empty())
        return false;
    FOR_EACH (HistoryIterator, it, mHistory)
    {
        if (*it == str)
            return false;
    }
    mHistory.push_back(str);
    return true;
}

1007
void ChatWindow::attributeChanged(const AttributesT id,
1008 1009
                                  const int64_t oldVal,
                                  const int64_t newVal)
Andrei Karas's avatar
Andrei Karas committed
1010
{
1011 1012
    if (!mShowBattleEvents)
        return;
1013 1014
    PRAGMA45(GCC diagnostic push)
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
Andrei Karas's avatar
Andrei Karas committed
1015 1016
    switch (id)
    {
1017
        case Attributes::PLAYER_EXP:
Andrei Karas's avatar
Andrei Karas committed
1018 1019 1020
        {
            if (oldVal > newVal)
                break;
1021
            const int64_t change = newVal - oldVal;
Andrei Karas's avatar
Andrei Karas committed
1022 1023 1024
            if (change != 0)
            {
                battleChatLog(std::string("+").append(toString(
1025
                    CAST_U64(change))).append(" xp"),
1026 1027 1028
                    ChatMsgType::BY_SERVER,
                    IgnoreRecord_false,
                    TryRemoveColors_true);
Andrei Karas's avatar
Andrei Karas committed
1029 1030 1031
            }
            break;
        }
1032
        case Attributes::PLAYER_BASE_LEVEL:
1033 1034 1035
            battleChatLog(std::string(
                "Level: ").append(toString(CAST_S32(
                newVal))),
1036 1037 1038
                ChatMsgType::BY_SERVER,
                IgnoreRecord_false,
                TryRemoveColors_true);
Andrei Karas's avatar
Andrei Karas committed
1039
            break;
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
        case Attributes::PLAYER_JOB_EXP:
        {
            if (!config.getBoolValue("showJobExp"))
                return;
            if (oldVal > newVal ||
                PlayerInfo::getAttribute(
                Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
            {
                return;
            }
            const int64_t change = newVal - oldVal;
            if (change != 0)
            {
                battleChatLog(std::string("+").append(toString(CAST_U64(
                    change))).append(" job"),
1055 1056 1057
                    ChatMsgType::BY_SERVER,
                    IgnoreRecord_false,
                    TryRemoveColors_true);
1058 1059 1060
            }
            break;
        }
Andrei Karas's avatar
Andrei Karas committed
1061 1062
        default:
            break;
1063
    }
1064
    PRAGMA45(GCC diagnostic pop)
Andrei Karas's avatar
Andrei Karas committed
1065 1066
}

1067 1068
void ChatWindow::addInputText(const std::string &text,
                              const bool space)
Andrei Karas's avatar
Andrei Karas committed
1069
{
1070
    const int caretPos = mChatInput->getCaretPosition();
Andrei Karas's avatar
Andrei Karas committed
1071
    const std::string &inputText = mChatInput->getText();
Andrei Karas's avatar
Andrei Karas committed
1072 1073 1074 1075 1076 1077 1078 1079

    std::ostringstream ss;
    ss << inputText.substr(0, caretPos) << text;
    if (space)
        ss << " ";

    ss << inputText.substr(caretPos);
    mChatInput->setText(ss.str());
1080 1081
    mChatInput->setCaretPosition(caretPos + CAST_S32(
        text.length()) + CAST_S32(space));
Andrei Karas's avatar
Andrei Karas committed
1082 1083 1084 1085 1086 1087 1088
    requestChatFocus();
}

void ChatWindow::addItemText(const std::string &item)
{
    std::ostringstream text;
    text << "[" << item << "]";
1089 1090
    addInputText(text.str(),
        true);
Andrei Karas's avatar
Andrei Karas committed
1091 1092
}

1093
void ChatWindow::setVisible(Visible visible)
Andrei Karas's avatar
Andrei Karas committed
1094
{
1095
    Window::setVisible(visible);
Andrei Karas's avatar
Andrei Karas committed
1096 1097 1098 1099 1100 1101 1102 1103

    /*
     * For whatever reason, if setVisible is called, the mTmpVisible effect
     * should be disabled.
     */
    mTmpVisible = false;
}

1104
void ChatWindow::addWhisper(const std::string &restrict nick,
1105
                            const std::string &restrict mes,
1106
                            const ChatMsgTypeT own)
Andrei Karas's avatar
Andrei Karas committed
1107
{
1108
    if (mes.empty() || (localPlayer == nullptr))
Andrei Karas's avatar
Andrei Karas committed
1109 1110
        return;

1111
    std::string playerName = localPlayer->getName();
Andrei Karas's avatar
Andrei Karas committed
1112 1113 1114 1115 1116
    std::string tempNick = nick;

    toLower(playerName);
    toLower(tempNick);

1117
    if (tempNick == playerName)
Andrei Karas's avatar
Andrei Karas committed
1118 1119
        return;

1120
    WhisperTab *tab = nullptr;
1121
    const TabMap::const_iterator i = mWhispers.find(tempNick);
Andrei Karas's avatar
Andrei Karas committed
1122 1123

    if (i != mWhispers.end())
Andrei Karas's avatar
Andrei Karas committed
1124
    {
Andrei Karas's avatar
Andrei Karas committed
1125
        tab = i->second;
Andrei Karas's avatar
Andrei Karas committed
1126
    }
Andrei Karas's avatar
Andrei Karas committed
1127
    else if (config.getBoolValue("whispertab"))
1128
    {
1129
        tab = addWhisperTab(nick, nick, false);
1130
        if (tab != nullptr)
1131 1132
            saveState();
    }
Andrei Karas's avatar
Andrei Karas committed
1133

1134
    if (tab != nullptr)
Andrei Karas's avatar
Andrei Karas committed
1135
    {
1136
        if (own == ChatMsgType::BY_PLAYER)
Andrei Karas's avatar
Andrei Karas committed
1137 1138 1139
        {
            tab->chatInput(mes);
        }
1140
        else if (own == ChatMsgType::BY_SERVER)
Andrei Karas's avatar
Andrei Karas committed
1141
        {
1142 1143 1144 1145
            tab->chatLog(mes,
                ChatMsgType::BY_SERVER,
                IgnoreRecord_false,
                TryRemoveColors_true);
Andrei Karas's avatar
Andrei Karas committed
1146 1147 1148
        }
        else
        {
1149 1150 1151
            if (tab->getRemoveNames())
            {
                std::string msg = mes;
1152
                const size_t idx = mes.find(':');
Andrei Karas's avatar
Andrei Karas committed
1153
                if (idx != std::string::npos && idx > 0)
1154
                {
Andrei Karas's avatar
Andrei Karas committed
1155
                    std::string nick2 = msg.substr(0, idx);
1156
                    msg = msg.substr(idx + 1);
1157 1158
                    nick2 = removeColors(nick2);
                    nick2 = trim(nick2);
1159 1160
                    if (config.getBoolValue("removeColors"))
                        msg = removeColors(msg);
1161
                    msg = trim(msg);
1162 1163 1164 1165
                    tab->chatLog(nick2, msg);
                }
                else
                {
1166 1167
                    if (config.getBoolValue("removeColors"))
                        msg = removeColors(msg);
1168 1169 1170 1171
                    tab->chatLog(msg,
                        ChatMsgType::BY_SERVER,
                        IgnoreRecord_false,
                        TryRemoveColors_true);
1172 1173 1174 1175 1176 1177
                }
            }
            else
            {
                tab->chatLog(nick, mes);
            }
1178
            localPlayer->afkRespond(tab, nick);
Andrei Karas's avatar
Andrei Karas committed
1179 1180
        }
    }
1181
    else if (localChatTab != nullptr)
Andrei Karas's avatar