GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/chatwindow.cpp Lines: 213 1055 20.2 %
Date: 2020-06-04 Branches: 138 1610 8.6 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "gui/windows/chatwindow.h"
24
25
#include "actormanager.h"
26
#include "game.h"
27
#include "guild.h"
28
#include "party.h"
29
#include "settings.h"
30
#include "spellmanager.h"
31
32
#include "being/localplayer.h"
33
#include "being/playerinfo.h"
34
#include "being/playerrelations.h"
35
36
#include "const/gui/chat.h"
37
38
#include "fs/virtfs/tools.h"
39
40
#include "input/inputmanager.h"
41
42
#include "gui/focushandler.h"
43
#include "gui/gui.h"
44
#include "gui/skin.h"
45
#include "gui/viewport.h"
46
47
#include "gui/models/colorlistmodel.h"
48
49
#include "gui/popups/popupmenu.h"
50
51
#include "gui/windows/setupwindow.h"
52
#include "gui/windows/whoisonline.h"
53
54
#include "gui/widgets/button.h"
55
#include "gui/widgets/chatinput.h"
56
#include "gui/widgets/createwidget.h"
57
#include "gui/widgets/dropdown.h"
58
#include "gui/widgets/itemlinkhandler.h"
59
#include "gui/widgets/scrollarea.h"
60
#include "gui/widgets/tabbedarea.h"
61
62
#include "gui/widgets/tabs/chat/battletab.h"
63
#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"
68
69
#include "render/opengl/opengldebug.h"
70
71
#include "resources/db/textdb.h"
72
73
#include "net/chathandler.h"
74
#include "net/net.h"
75
76
#include "utils/copynpaste.h"
77
#include "utils/delete2.h"
78
#include "utils/foreach.h"
79
80
#include "utils/translation/podict.h"
81
82
#include <sys/stat.h>
83
84
#include <fstream>
85
#include <sstream>
86
87
#ifdef __SWITCH__
88
#include "enums/input/keyvalue.h"
89
#endif
90
91
#include "debug.h"
92
93
ChatWindow *chatWindow = nullptr;
94
95
static const char *const ACTION_COLOR_PICKER = "color picker";
96
97
2
ChatWindow::ChatWindow(const std::string &name) :
98
    // TRANSLATORS: chat window name
99
2
    Window(_("Chat"), Modal_false, nullptr, "chat.xml"),
100
    ActionListener(),
101
    KeyListener(),
102
    AttributeListener(),
103

2
    mItemLinkHandler(new ItemLinkHandler),
104


2
    mChatTabs(CREATEWIDGETR(TabbedArea, this)),
105

2
    mChatInput(new ChatInput(this)),
106
    mRainbowColor(0U),
107
    mWhispers(),
108
    mChannels(),
109
    mHistory(),
110
    mCurHist(),
111
    mCommands(),
112
    mCustomWords(),
113
    mTradeFilter(),
114
4
    mColorListModel(new ColorListModel),
115
2
    mColorPicker(new DropDown(this, mColorListModel,
116

6
        false, Modal_false, nullptr, std::string())),
117

2
    mChatButton(new Button(this, ":)", "openemote", BUTTON_SKIN, this)),
118
    mAwayLog(),
119
    mHighlights(),
120
    mGlobalsFilter(),
121

8
    mChatColor(config.getIntValue("chatColor")),
122

4
    mEmoteButtonSpacing(mSkin != nullptr ?
123

8
                        mSkin->getOption("emoteButtonSpacing", 2) : 2),
124

4
    mEmoteButtonY(mSkin != nullptr ?
125

8
                  mSkin->getOption("emoteButtonY", -2) : -2),
126
    mChatHistoryIndex(0),
127

8
    mReturnToggles(config.getBoolValue("ReturnToggles")),
128
    mGMLoaded(false),
129
    mHaveMouse(false),
130

8
    mAutoHide(config.getBoolValue("autohideChat")),
131

6
    mShowBattleEvents(config.getBoolValue("showBattleEvents")),
132

8
    mShowAllLang(serverConfig.getValue("showAllLang", 0) != 0),
133

8
    mEnableTradeFilter(config.getBoolValue("enableTradeFilter")),
134



88
    mTmpVisible(false)
135
{
136
4
    setWindowName(name);
137
138
2
    if (setupWindow != nullptr)
139
1
        setupWindow->registerWindowForReset(this);
140
141
4
    setShowTitle(false);
142
2
    setResizable(true);
143
4
    setDefaultVisible(true);
144
4
    setSaveVisible(true);
145
2
    setStickyButtonLock(true);
146
147
2
    int w = 600;
148
#ifdef ANDROID
149
    if (mainGraphics->getWidth() < 710)
150
        w = mainGraphics->getWidth() - 110;
151
    if (w < 100)
152
        w = 100;
153
    if (mainGraphics->getHeight() < 480)
154
        setDefaultSize(w, 90, ImagePosition::UPPER_LEFT, -110, -35);
155
    else
156
        setDefaultSize(w, 123, ImagePosition::UPPER_LEFT, -110, -35);
157
#else  // ANDROID
158
159

2
    if (mainGraphics->getWidth() < 600)
160
        w = mainGraphics->getWidth() - 10;
161
2
    if (w < 100)
162
        w = 100;
163
2
    setDefaultSize(w, 123, ImagePosition::LOWER_LEFT, 0, 0);
164
#endif  // ANDROID
165
166
2
    setMinWidth(150);
167
2
    setMinHeight(90);
168
169
6
    setTitleBarHeight(getPadding() + getTitlePadding());
170
171
2
    if (emoteWindow != nullptr)
172
        emoteWindow->addListeners(this);
173
174
2
    mChatButton->adjustSize();
175
176
2
    mChatTabs->enableScrollButtons(true);
177
4
    mChatTabs->setFollowDownScroll(true);
178
4
    mChatTabs->setResizeHeight(false);
179
180
10
    mChatInput->setActionEventId("chatinput");
181
2
    mChatInput->addActionListener(this);
182
4
    mChatInput->setAllowSpecialActions(false);
183
184
10
    mColorPicker->setActionEventId(ACTION_COLOR_PICKER);
185
2
    mColorPicker->addActionListener(this);
186
2
    mColorPicker->setSelected(mChatColor);
187
188
4
    mItemLinkHandler->setAllowCommands(false);
189
190
2
    loadWindowState();
191
192
8
    mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
193
4
        - 2 * mPadding - 8 - 16, mPadding);
194
195
    // Add key listener to chat input to be able to respond to up/down
196
2
    mChatInput->addKeyListener(this);
197
4
    mCurHist = mHistory.end();
198

8
    mColorPicker->setVisible(fromBool(config.getBoolValue(
199
2
        "showChatColorsList"), Visible));
200
2
    updateTabsMargin();
201
202
2
    fillCommands();
203

4
    if ((localPlayer != nullptr) && localPlayer->isGM())
204
        loadGMCommands();
205
2
    initTradeFilter();
206
2
    loadCustomList();
207
2
    parseHighlights();
208
2
    parseGlobalsFilter();
209
210

8
    config.addListener("autohideChat", this);
211

8
    config.addListener("showBattleEvents", this);
212

8
    config.addListener("globalsFilter", this);
213

8
    config.addListener("enableTradeFilter", this);
214
215
4
    enableVisibleSound(true);
216
2
}
217
218
24
ChatWindow::~ChatWindow()
219
{
220
2
    config.removeListeners(this);
221
    CHECKLISTENERS
222
2
    saveState();
223
10
    config.setValue("ReturnToggles", mReturnToggles);
224
2
    removeAllWhispers();
225
2
    removeAllChannels();
226
2
    delete2(mItemLinkHandler)
227
2
    delete2(mColorPicker)
228
4
    delete2(mColorListModel)
229
4
}
230
231
2
void ChatWindow::postInit()
232
{
233
2
    Window::postInit();
234
2
    add(mChatTabs);
235
2
    add(mChatInput);
236
2
    add(mColorPicker);
237
2
    add(mChatButton);
238
2
    updateVisibility();
239
2
}
240
241
2
void ChatWindow::loadCommandsFile(const std::string &name)
242
{
243
4
    StringVect list;
244
2
    VirtFs::loadTextFile(name, list);
245
4
    StringVectCIter it = list.begin();
246
4
    const StringVectCIter it_end = list.end();
247
248
2
    while (it != it_end)
249
    {
250
        const std::string str = *it;
251
        if (!str.empty())
252
            mCommands.push_back(str);
253
        ++ it;
254
    }
255
2
}
256
257
2
void ChatWindow::fillCommands()
258
{
259
8
    loadCommandsFile("chatcommands.txt");
260
2
    InputManager::addChatCommands(mCommands);
261
2
}
262
263
void ChatWindow::loadGMCommands()
264
{
265
    if (mGMLoaded)
266
        return;
267
268
    loadCommandsFile("gmcommands.txt");
269
    mGMLoaded = true;
270
}
271
272
void ChatWindow::updateTabsMargin()
273
{
274

4
    if (mColorPicker->mVisible == Visible_true)
275
8
        mChatTabs->setRightMargin(mColorPicker->getWidth() + 16 + 8);
276
    else
277
        mChatTabs->setRightMargin(8);
278
}
279
280
2
void ChatWindow::adjustTabSize()
281
{
282
4
    const Rect area = getChildrenArea();
283
284
2
    const int aw = area.width;
285
2
    const int ah = area.height;
286
4
    const int frame = mChatInput->getFrameSize();
287
4
    const int inputHeight = mChatInput->getHeight();
288
8
    const bool showEmotes = config.getBoolValue("showEmotesButton");
289
2
    int maxHeight = inputHeight;
290
2
    if (showEmotes)
291
    {
292
4
        const int buttonHeight = mChatButton->getHeight();
293
2
        if (buttonHeight > maxHeight)
294
2
            maxHeight = buttonHeight;
295
    }
296
2
    const int frame2 = 2 * frame;
297
2
    const int awFrame2 = aw - frame2;
298
2
    int y = ah - maxHeight - frame;
299
2
    mChatInput->setPosition(frame, y);
300
2
    mChatTabs->setWidth(awFrame2);
301
2
    const int height = ah - frame2 - (maxHeight + frame2);
302

6
    if (mChatInput->mVisible == Visible_true ||
303


8
        !config.getBoolValue("hideChatInput"))
304
    {
305
        mChatTabs->setHeight(height);
306
    }
307
    else
308
    {
309
2
        mChatTabs->setHeight(height + maxHeight);
310
    }
311
2
    updateTabsMargin();
312
313
2
    if (showEmotes)
314
    {
315
4
        const int chatButtonSize = mChatButton->getWidth();
316
2
        int w = awFrame2 - chatButtonSize;
317
2
        const int x = aw - frame - chatButtonSize;
318
2
        w -= mEmoteButtonSpacing;
319
2
        y += mEmoteButtonY;
320
2
        mChatInput->setWidth(w);
321
2
        mChatButton->setVisible(mChatInput->mVisible);
322
2
        mChatButton->setPosition(x, y);
323
    }
324
    else
325
    {
326
        mChatInput->setWidth(awFrame2);
327
        mChatButton->setVisible(Visible_false);
328
    }
329
330
2
    const ChatTab *const tab = getFocused();
331
2
    if (tab != nullptr)
332
    {
333
        Widget *const content = tab->mScrollArea;
334
        if (content != nullptr)
335
        {
336
            const int contentFrame2 = 2 * content->getFrameSize();
337
            content->setSize(mChatTabs->getWidth() - contentFrame2,
338
                mChatTabs->getContainerHeight() - contentFrame2);
339
            content->logic();
340
        }
341
    }
342
343
8
    mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
344
4
        - 2 * mPadding - 8 - 16, mPadding);
345
346
2
    mChatTabs->adjustSize();
347
2
}
348
349
2
void ChatWindow::widgetResized(const Event &event)
350
{
351
2
    Window::widgetResized(event);
352
353
2
    adjustTabSize();
354
2
}
355
356
ChatTab *ChatWindow::getFocused() const
357
{
358
4
    return static_cast<ChatTab*>(mChatTabs->getSelectedTab());
359
}
360
361
void ChatWindow::clearTab(ChatTab *const tab)
362
{
363
    if (tab != nullptr)
364
        tab->clearText();
365
}
366
367
void ChatWindow::clearTab() const
368
{
369
    clearTab(getFocused());
370
}
371
372
void ChatWindow::prevTab()
373
{
374
    if (mChatTabs == nullptr)
375
        return;
376
377
    int tab = mChatTabs->getSelectedTabIndex();
378
379
    if (tab <= 0)
380
        tab = mChatTabs->getNumberOfTabs();
381
    tab--;
382
383
    mChatTabs->setSelectedTabByIndex(tab);
384
}
385
386
void ChatWindow::nextTab()
387
{
388
    if (mChatTabs == nullptr)
389
        return;
390
391
    int tab = mChatTabs->getSelectedTabIndex();
392
393
    tab++;
394
    if (tab == mChatTabs->getNumberOfTabs())
395
        tab = 0;
396
397
    mChatTabs->setSelectedTabByIndex(tab);
398
}
399
400
void ChatWindow::selectTabByType(const ChatTabTypeT &type)
401
{
402
    if (mChatTabs == nullptr)
403
        return;
404
405
    int sz = mChatTabs->getNumberOfTabs();
406
    for (int f = 0; f < sz; f ++)
407
    {
408
        ChatTab *const tab = dynamic_cast<ChatTab*>(
409
            mChatTabs->getTabByIndex(f));
410
        if ((tab != nullptr) && tab->getType() == type)
411
        {
412
            mChatTabs->setSelectedTab(tab);
413
            break;
414
        }
415
    }
416
}
417
418
void ChatWindow::closeTab() const
419
{
420
    if (mChatTabs == nullptr)
421
        return;
422
423
    ChatTab *const tab = dynamic_cast<ChatTab*>(mChatTabs->getTabByIndex(
424
        mChatTabs->getSelectedTabIndex()));
425
    if (tab == nullptr)
426
        return;
427
    const ChatTabTypeT &type = tab->getType();
428
    if (type == ChatTabType::WHISPER || type == ChatTabType::CHANNEL)
429
        tab->handleCommand("close", "");
430
}
431
432
void ChatWindow::defaultTab()
433
{
434
    if (mChatTabs != nullptr)
435
        mChatTabs->setSelectedTabByIndex(CAST_U32(0));
436
}
437
438
void ChatWindow::action(const ActionEvent &event)
439
{
440
    const std::string &eventId = event.getId();
441
    if (eventId == "chatinput")
442
    {
443
        std::string message = mChatInput->getText();
444
445
        if (!message.empty())
446
        {
447
            // If message different from previous, put it in the history
448
            if (mHistory.empty() || message != mHistory.back())
449
                mHistory.push_back(message);
450
451
            // Reset history iterator
452
            mCurHist = mHistory.end();
453
454
            // Send the message to the server
455
            chatInput(addColors(message));
456
457
            // Clear the text from the chat input
458
            mChatInput->setText("");
459
        }
460
461
        if (message.empty() || !mReturnToggles)
462
        {
463
            // Remove focus and hide input
464
            mChatInput->unprotectFocus();
465
            if (mFocusHandler != nullptr)
466
                mFocusHandler->focusNone();
467
468
            // If the chatWindow is shown up because you want to send a message
469
            // It should hide now
470
            if (mTmpVisible)
471
                setVisible(Visible_false);
472
        }
473
    }
474
    else if (eventId == "emote")
475
    {
476
        if (emoteWindow != nullptr)
477
        {
478
            const std::string str = emoteWindow->getSelectedEmote();
479
            if (!str.empty())
480
            {
481
                addInputText(str, false);
482
                emoteWindow->clearEmote();
483
            }
484
        }
485
    }
486
    else if (eventId == "openemote")
487
    {
488
        if (emoteWindow != nullptr)
489
        {
490
            if (emoteWindow->mVisible == Visible_true)
491
                emoteWindow->hide();
492
            else
493
                emoteWindow->show();
494
        }
495
    }
496
    else if (eventId == "color")
497
    {
498
        if (emoteWindow != nullptr)
499
        {
500
            const std::string str = emoteWindow->getSelectedColor();
501
            if (!str.empty())
502
            {
503
                addInputText(str, false);
504
                emoteWindow->clearColor();
505
            }
506
        }
507
    }
508
    else if (eventId == "font")
509
    {
510
        if (emoteWindow != nullptr)
511
        {
512
            const std::string str = emoteWindow->getSelectedFont();
513
            if (!str.empty())
514
            {
515
                addInputText(str, false);
516
                emoteWindow->clearFont();
517
            }
518
        }
519
    }
520
    else if (eventId == "text")
521
    {
522
        if ((emoteWindow != nullptr) && (reverseDictionary != nullptr))
523
        {
524
            const int idx = emoteWindow->getSelectedTextIndex();
525
            if (idx >= 0)
526
            {
527
                const std::string str = TextDb::getByIndex(idx);
528
                const std::string enStr = reverseDictionary->getStr(str);
529
                addInputText(enStr, false);
530
            }
531
            emoteWindow->clearText();
532
        }
533
    }
534
    else if (eventId == ACTION_COLOR_PICKER)
535
    {
536
        if (mColorPicker != nullptr)
537
        {
538
            mChatColor = mColorPicker->getSelected();
539
            config.setValue("chatColor", mChatColor);
540
        }
541
    }
542
543
    if (mColorPicker != nullptr)
544
    {
545
        const Visible vis = fromBool(config.getBoolValue(
546
            "showChatColorsList"), Visible);
547
        if (mColorPicker->mVisible != vis)
548
            mColorPicker->setVisible(vis);
549
    }
550
}
551
552
bool ChatWindow::requestChatFocus()
553
{
554
    // Make sure chatWindow is visible
555
    if (!isWindowVisible())
556
    {
557
        setVisible(Visible_true);
558
559
        /*
560
         * This is used to hide chatWindow after sending the message. There is
561
         * a trick here, because setVisible will set mTmpVisible to false, you
562
         * have to put this sentence *after* setVisible, not before it
563
         */
564
        mTmpVisible = true;
565
    }
566
567
    // Don't do anything else if the input is already visible and has focus
568
    if (mChatInput->isVisible() && mChatInput->isFocused())
569
        return false;
570
571
    // Give focus to the chat input
572
    mChatInput->processVisible(Visible_true);
573
    unHideWindow();
574
    mChatInput->requestFocus();
575
    return true;
576
}
577
578
bool ChatWindow::isInputFocused() const
579
{
580
    return mChatInput->isFocused();
581
}
582
583
1
void ChatWindow::removeTab(ChatTab *const tab)
584
{
585
1
    mChatTabs->removeTab(tab);
586
1
}
587
588
1
void ChatWindow::addTab(ChatTab *const tab)
589
{
590
1
    if (tab == nullptr)
591
        return;
592
593
1
    mChatTabs->addTab(tab, tab->mScrollArea);
594
1
    logic();
595
}
596
597
void ChatWindow::removeWhisper(const std::string &nick)
598
{
599
    std::string tempNick = nick;
600
    toLower(tempNick);
601
    mWhispers.erase(tempNick);
602
}
603
604
void ChatWindow::removeChannel(const std::string &name)
605
{
606
    std::string tempName = name;
607
    toLower(tempName);
608
    mChannels.erase(tempName);
609
    chatHandler->partChannel(name);
610
}
611
612
2
void ChatWindow::removeAllWhispers()
613
{
614
4
    std::list<ChatTab*> tabs;
615
616
6
    FOR_EACH (TabMap::iterator, iter, mWhispers)
617
        tabs.push_back(iter->second);
618
619
6
    for (std::list<ChatTab*>::iterator it = tabs.begin();
620
2
         it != tabs.end(); ++it)
621
    {
622
        delete *it;
623
    }
624
625
4
    mWhispers.clear();
626
2
}
627
628
2
void ChatWindow::removeAllChannels()
629
{
630
4
    std::list<ChatTab*> tabs;
631
632
6
    FOR_EACH (ChannelMap::iterator, iter, mChannels)
633
        tabs.push_back(iter->second);
634
635
6
    for (std::list<ChatTab*>::iterator it = tabs.begin();
636
2
         it != tabs.end(); ++it)
637
    {
638
        delete *it;
639
    }
640
641
4
    mChannels.clear();
642
2
}
643
644
void ChatWindow::ignoreAllWhispers()
645
{
646
    for (TabMap::iterator iter = mWhispers.begin();
647
         iter != mWhispers.end();
648
         ++ iter)
649
    {
650
        WhisperTab *const tab = iter->second;
651
        if (tab != nullptr)
652
        {
653
            if (playerRelations.getRelation(tab->getNick())
654
                != Relation::IGNORED)
655
            {
656
                playerRelations.setRelation(tab->getNick(),
657
                    Relation::IGNORED);
658
            }
659
            tab->handleCommand("close", "");
660
        }
661
    }
662
}
663
664
void ChatWindow::chatInput(const std::string &message) const
665
{
666
    ChatTab *tab = nullptr;
667
    std::string msg = message;
668
    trim(msg);
669
670
    if (config.getBoolValue("allowCommandsInChatTabs")
671
        && msg.length() > 1
672
        && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@')
673
        && (localChatTab != nullptr))
674
    {
675
        tab = localChatTab;
676
    }
677
    else
678
    {
679
        tab = getFocused();
680
        if (tab == nullptr)
681
            tab = localChatTab;
682
    }
683
    if (tab != nullptr)
684
        tab->chatInput(msg);
685
    Game *const game = Game::instance();
686
    if (game != nullptr)
687
        game->setValidSpeed();
688
}
689
690
void ChatWindow::localChatInput(const std::string &msg) const
691
{
692
    if (localChatTab != nullptr)
693
        localChatTab->chatInput(msg);
694
    else
695
        chatInput(msg);
696
}
697
698
void ChatWindow::doPresent() const
699
{
700
    if (actorManager == nullptr)
701
        return;
702
703
    const ActorSprites &actors = actorManager->getAll();
704
    std::string response;
705
    int playercount = 0;
706
707
    FOR_EACH (ActorSpritesIterator, it, actors)
708
    {
709
        if ((*it)->getType() == ActorType::Player)
710
        {
711
            if (!response.empty())
712
                response.append(", ");
713
            response.append(static_cast<Being*>(*it)->getName());
714
            playercount ++;
715
        }
716
    }
717
718
    ChatTab *const tab = getFocused();
719
    if (tab != nullptr)
720
    {
721
        const std::string log = strprintf(
722
            // TRANSLATORS: chat message
723
            _("Present: %s; %d players are present."),
724
            response.c_str(), playercount);
725
        tab->chatLog(log,
726
            ChatMsgType::BY_SERVER,
727
            IgnoreRecord_false,
728
            TryRemoveColors_true);
729
    }
730
}
731
732
void ChatWindow::scroll(const int amount) const
733
{
734
    if (!isWindowVisible())
735
        return;
736
737
    ChatTab *const tab = getFocused();
738
    if (tab != nullptr)
739
        tab->scroll(amount);
740
}
741
742
void ChatWindow::mousePressed(MouseEvent &event)
743
{
744
    if (event.isConsumed())
745
        return;
746
747
    if (event.getButton() == MouseButton::RIGHT)
748
    {
749
        if (popupMenu != nullptr)
750
        {
751
            ChatTab *const cTab = dynamic_cast<ChatTab*>(
752
                mChatTabs->getSelectedTab());
753
            if (cTab != nullptr)
754
            {
755
                event.consume();
756
                if (inputManager.isActionActive(InputAction::CHAT_MOD))
757
                {
758
                    inputManager.executeChatCommand(
759
                        InputAction::CLOSE_CHAT_TAB,
760
                        std::string(), cTab);
761
                }
762
                else
763
                {
764
                    popupMenu->showChatPopup(viewport->mMouseX,
765
                        viewport->mMouseY,
766
                        cTab);
767
                }
768
            }
769
        }
770
    }
771
772
    Window::mousePressed(event);
773
774
    if (event.isConsumed())
775
        return;
776
777
    if (event.getButton() == MouseButton::LEFT)
778
    {
779
        const int clicks = event.getClickCount();
780
        if (clicks == 2)
781
        {
782
            toggleChatFocus();
783
            if (gui != nullptr)
784
                gui->resetClickCount();
785
        }
786
        else if (clicks == 1)
787
        {
788
            const ChatTab *const tab = getFocused();
789
            if (tab != nullptr)
790
                mMoved = !isResizeAllowed(event);
791
        }
792
    }
793
794
    mDragOffsetX = event.getX();
795
    mDragOffsetY = event.getY();
796
}
797
798
void ChatWindow::mouseDragged(MouseEvent &event)
799
{
800
    Window::mouseDragged(event);
801
802
    if (event.isConsumed())
803
        return;
804
805
    if (canMove() && isMovable() && mMoved)
806
    {
807
        int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
808
        int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
809
        newX = std::min(mainGraphics->mWidth - getWidth(), newX);
810
        newY = std::min(mainGraphics->mHeight - getHeight(), newY);
811
        setPosition(newX, newY);
812
    }
813
}
814
815
#define ifKey(key, str) \
816
    else if (actionId == (key)) \
817
    { \
818
        temp = str; \
819
    }
820
821
void ChatWindow::keyPressed(KeyEvent &event)
822
{
823
    const InputActionT actionId = event.getActionId();
824
    std::string temp;
825
#ifdef __SWITCH__
826
    if (event.getKey().getValue() == KeyValue::TEXTINPUT)
827
    {
828
        action(ActionEvent(this, "chatinput"));
829
        return;
830
    }
831
    else
832
#endif
833
    if (actionId == InputAction::GUI_DOWN)
834
    {
835
        if (mCurHist != mHistory.end())
836
        {
837
            // Move forward through the history
838
            const HistoryIterator prevHist = mCurHist++;
839
            addCurrentToHistory();
840
841
            if (mCurHist != mHistory.end())
842
            {
843
                mChatInput->setText(*mCurHist);
844
                mChatInput->setCaretPosition(CAST_U32(
845
                        mChatInput->getText().length()));
846
            }
847
            else
848
            {
849
                mChatInput->setText("");
850
                mCurHist = prevHist;
851
            }
852
        }
853
        else if (!mChatInput->getText().empty())
854
        {
855
            if (addCurrentToHistory())
856
                mCurHist = mHistory.end();
857
            mChatInput->setText("");
858
        }
859
    }
860
    else if (actionId == InputAction::GUI_UP &&
861
             mCurHist != mHistory.begin() &&
862
             !mHistory.empty())
863
    {
864
        // Move backward through the history
865
        --mCurHist;
866
        addCurrentToHistory();
867
        mChatInput->setText(*mCurHist);
868
        mChatInput->setCaretPosition(CAST_U32(
869
                mChatInput->getText().length()));
870
    }
871
    else if (actionId == InputAction::GUI_INSERT &&
872
             !mChatInput->getText().empty())
873
    {
874
        const std::string str = mChatInput->getText();
875
        // Add the current message to the history and clear the text
876
        if (mHistory.empty() || str != mHistory.back())
877
            mHistory.push_back(str);
878
        mCurHist = mHistory.end();
879
        mChatInput->setText(std::string());
880
    }
881
    else if (actionId == InputAction::GUI_TAB &&
882
             !mChatInput->getText().empty())
883
    {
884
        autoComplete();
885
        return;
886
    }
887
    else if (actionId == InputAction::GUI_CANCEL &&
888
             mChatInput->mVisible == Visible_true)
889
    {
890
        mChatInput->processVisible(Visible_false);
891
    }
892
    else if (actionId == InputAction::CHAT_PREV_HISTORY &&
893
             mChatInput->mVisible == Visible_true)
894
    {
895
        const ChatTab *const tab = getFocused();
896
        if ((tab != nullptr) && tab->hasRows())
897
        {
898
            if (mChatHistoryIndex == 0U)
899
            {
900
                mChatHistoryIndex = CAST_U32(
901
                    tab->getRows().size());
902
903
                mChatInput->setText("");
904
                mChatInput->setCaretPosition(0);
905
                return;
906
            }
907
908
            mChatHistoryIndex --;
909
910
            unsigned int f = 0;
911
            const std::list<std::string> &rows = tab->getRows();
912
            for (std::list<std::string>::const_iterator it = rows.begin(),
913
                 it_end = rows.end(); it != it_end; ++ it, f ++)
914
            {
915
                if (f == mChatHistoryIndex)
916
                    mChatInput->setText(*it);
917
            }
918
            mChatInput->setCaretPosition(CAST_U32(
919
                mChatInput->getText().length()));
920
        }
921
    }
922
    else if (actionId == InputAction::CHAT_NEXT_HISTORY &&
923
             mChatInput->mVisible == Visible_true)
924
    {
925
        const ChatTab *const tab = getFocused();
926
        if ((tab != nullptr) && tab->hasRows())
927
        {
928
            const std::list<std::string> &rows = tab->getRows();
929
            const size_t &tabSize = rows.size();
930
            if (CAST_SIZE(mChatHistoryIndex) + 1 < tabSize)
931
            {
932
                mChatHistoryIndex ++;
933
            }
934
            else if (CAST_SIZE(mChatHistoryIndex) < tabSize)
935
            {
936
                mChatHistoryIndex ++;
937
                mChatInput->setText("");
938
                mChatInput->setCaretPosition(0);
939
                return;
940
            }
941
            else
942
            {
943
                mChatHistoryIndex = 0;
944
            }
945
946
            unsigned int f = 0;
947
            for (std::list<std::string>::const_iterator
948
                 it = rows.begin(), it_end = rows.end();
949
                 it != it_end; ++it, f++)
950
            {
951
                if (f == mChatHistoryIndex)
952
                    mChatInput->setText(*it);
953
            }
954
            mChatInput->setCaretPosition(CAST_U32(
955
                    mChatInput->getText().length()));
956
        }
957
    }
958
    else if (actionId == InputAction::GUI_F1)
959
    {
960
        if (emoteWindow != nullptr)
961
        {
962
            if (emoteWindow->mVisible == Visible_true)
963
                emoteWindow->hide();
964
            else
965
                emoteWindow->show();
966
        }
967
    }
968
    ifKey(InputAction::GUI_F2, "\u2318")
969
    ifKey(InputAction::GUI_F3, "\u263A")
970
    ifKey(InputAction::GUI_F4, "\u2665")
971
    ifKey(InputAction::GUI_F5, "\u266A")
972
    ifKey(InputAction::GUI_F6, "\u266B")
973
    ifKey(InputAction::GUI_F7, "\u26A0")
974
    ifKey(InputAction::GUI_F8, "\u2622")
975
    ifKey(InputAction::GUI_F9, "\u262E")
976
    ifKey(InputAction::GUI_F10, "\u2605")
977
    ifKey(InputAction::GUI_F11, "\u2618")
978
    ifKey(InputAction::GUI_F12, "\u2592")
979
980
    if (inputManager.isActionActive(InputAction::GUI_CTRL))
981
    {
982
        if (actionId == InputAction::GUI_B)
983
        {
984
            std::string inputText = mChatInput->getTextBeforeCaret();
985
            toLower(inputText);
986
            const size_t idx = inputText.rfind("##b");
987
            if (idx == std::string::npos
988
                || mChatInput->getTextBeforeCaret().substr(idx, 3) == "##b")
989
            {
990
                temp = "##B";
991
            }
992
            else
993
            {
994
                temp = "##b";
995
            }
996
        }
997
    }
998
999
    if (!temp.empty())
1000
        addInputText(temp, false);
1001
}
1002
1003
#undef ifKey
1004
1005
bool ChatWindow::addCurrentToHistory()
1006
{
1007
    const std::string &str = mChatInput->getText();
1008
    if (str.empty())
1009
        return false;
1010
    FOR_EACH (HistoryIterator, it, mHistory)
1011
    {
1012
        if (*it == str)
1013
            return false;
1014
    }
1015
    mHistory.push_back(str);
1016
    return true;
1017
}
1018
1019
void ChatWindow::attributeChanged(const AttributesT id,
1020
                                  const int64_t oldVal,
1021
                                  const int64_t newVal)
1022
{
1023
    if (!mShowBattleEvents)
1024
        return;
1025
    PRAGMA45(GCC diagnostic push)
1026
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
1027
    switch (id)
1028
    {
1029
        case Attributes::PLAYER_EXP:
1030
        {
1031
            if (oldVal > newVal)
1032
                break;
1033
            const int64_t change = newVal - oldVal;
1034
            if (change != 0)
1035
            {
1036
                battleChatLog(std::string("+").append(toString(
1037
                    CAST_U64(change))).append(" xp"),
1038
                    ChatMsgType::BY_SERVER,
1039
                    IgnoreRecord_false,
1040
                    TryRemoveColors_true);
1041
            }
1042
            break;
1043
        }
1044
        case Attributes::PLAYER_BASE_LEVEL:
1045
            battleChatLog(std::string(
1046
                "Level: ").append(toString(CAST_S32(
1047
                newVal))),
1048
                ChatMsgType::BY_SERVER,
1049
                IgnoreRecord_false,
1050
                TryRemoveColors_true);
1051
            break;
1052
        case Attributes::PLAYER_JOB_EXP:
1053
        {
1054
            if (!config.getBoolValue("showJobExp"))
1055
                return;
1056
            if (oldVal > newVal ||
1057
                PlayerInfo::getAttribute(
1058
                Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
1059
            {
1060
                return;
1061
            }
1062
            const int64_t change = newVal - oldVal;
1063
            if (change != 0)
1064
            {
1065
                battleChatLog(std::string("+").append(toString(CAST_U64(
1066
                    change))).append(" job"),
1067
                    ChatMsgType::BY_SERVER,
1068
                    IgnoreRecord_false,
1069
                    TryRemoveColors_true);
1070
            }
1071
            break;
1072
        }
1073
        default:
1074
            break;
1075
    }
1076
    PRAGMA45(GCC diagnostic pop)
1077
}
1078
1079
void ChatWindow::addInputText(const std::string &text,
1080
                              const bool space)
1081
{
1082
    const int caretPos = mChatInput->getCaretPosition();
1083
    const std::string &inputText = mChatInput->getText();
1084
1085
    std::ostringstream ss;
1086
    ss << inputText.substr(0, caretPos) << text;
1087
    if (space)
1088
        ss << " ";
1089
1090
    ss << inputText.substr(caretPos);
1091
    mChatInput->setText(ss.str());
1092
    mChatInput->setCaretPosition(caretPos + CAST_S32(
1093
        text.length()) + CAST_S32(space));
1094
    requestChatFocus();
1095
}
1096
1097
void ChatWindow::addItemText(const std::string &item)
1098
{
1099
    std::ostringstream text;
1100
    text << "[" << item << "]";
1101
    addInputText(text.str(),
1102
        true);
1103
}
1104
1105
3
void ChatWindow::setVisible(Visible visible)
1106
{
1107
3
    Window::setVisible(visible);
1108
1109
    /*
1110
     * For whatever reason, if setVisible is called, the mTmpVisible effect
1111
     * should be disabled.
1112
     */
1113
3
    mTmpVisible = false;
1114
3
}
1115
1116
void ChatWindow::addWhisper(const std::string &restrict nick,
1117
                            const std::string &restrict mes,
1118
                            const ChatMsgTypeT own)
1119
{
1120
    if (mes.empty() || (localPlayer == nullptr))
1121
        return;
1122
1123
    std::string playerName = localPlayer->getName();
1124
    std::string tempNick = nick;
1125
1126
    toLower(playerName);
1127
    toLower(tempNick);
1128
1129
    if (tempNick == playerName)
1130
        return;
1131
1132
    WhisperTab *tab = nullptr;
1133
    const TabMap::const_iterator i = mWhispers.find(tempNick);
1134
1135
    if (i != mWhispers.end())
1136
    {
1137
        tab = i->second;
1138
    }
1139
    else if (config.getBoolValue("whispertab"))
1140
    {
1141
        tab = addWhisperTab(nick, nick, false);
1142
        if (tab != nullptr)
1143
            saveState();
1144
    }
1145
1146
    if (tab != nullptr)
1147
    {
1148
        if (own == ChatMsgType::BY_PLAYER)
1149
        {
1150
            tab->chatInput(mes);
1151
        }
1152
        else if (own == ChatMsgType::BY_SERVER)
1153
        {
1154
            tab->chatLog(mes,
1155
                ChatMsgType::BY_SERVER,
1156
                IgnoreRecord_false,
1157
                TryRemoveColors_true);
1158
        }
1159
        else
1160
        {
1161
            if (tab->getRemoveNames())
1162
            {
1163
                std::string msg = mes;
1164
                const size_t idx = mes.find(':');
1165
                if (idx != std::string::npos && idx > 0)
1166
                {
1167
                    std::string nick2 = msg.substr(0, idx);
1168
                    msg = msg.substr(idx + 1);
1169
                    nick2 = removeColors(nick2);
1170
                    nick2 = trim(nick2);
1171
                    if (config.getBoolValue("removeColors"))
1172
                        msg = removeColors(msg);
1173
                    msg = trim(msg);
1174
                    tab->chatLog(nick2, msg);
1175
                }
1176
                else
1177
                {
1178
                    if (config.getBoolValue("removeColors"))
1179
                        msg = removeColors(msg);
1180
                    tab->chatLog(msg,
1181
                        ChatMsgType::BY_SERVER,
1182
                        IgnoreRecord_false,
1183
                        TryRemoveColors_true);
1184
                }
1185
            }
1186
            else
1187
            {
1188
                tab->chatLog(nick, mes);
1189
            }
1190
            localPlayer->afkRespond(tab, nick);
1191
        }
1192
    }
1193
    else if (localChatTab != nullptr)
1194
    {
1195
        if (own == ChatMsgType::BY_PLAYER)
1196
        {
1197
            chatHandler->privateMessage(nick, mes);
1198
1199
            // TRANSLATORS: chat message
1200
            localChatTab->chatLog(strprintf(_("Whispering to %s: %s"),
1201
                nick.c_str(), mes.c_str()),
1202
                ChatMsgType::BY_PLAYER,
1203
                IgnoreRecord_false,
1204
                TryRemoveColors_true);
1205
        }
1206
        else
1207
        {
1208
            localChatTab->chatLog(std::string(nick).append(
1209
                " : ").append(mes),
1210
                ChatMsgType::ACT_WHISPER,
1211
                IgnoreRecord_false,
1212
                TryRemoveColors_true);
1213
            if (localPlayer != nullptr)
1214
                localPlayer->afkRespond(nullptr, nick);
1215
        }
1216
    }
1217
}
1218
1219
WhisperTab *ChatWindow::addWhisperTab(const std::string &caption,
1220
                                      const std::string &nick,
1221
                                      const bool switchTo)
1222
{
1223
    if (localPlayer == nullptr)
1224
        return nullptr;
1225
1226
    std::string playerName = localPlayer->getName();
1227
    std::string tempNick = nick;
1228
1229
    toLower(playerName);
1230
    toLower(tempNick);
1231
1232
    const TabMap::const_iterator i = mWhispers.find(tempNick);
1233
    WhisperTab *ret = nullptr;
1234
1235
    if (tempNick == playerName)
1236
        return nullptr;
1237
1238
    if (i != mWhispers.end())
1239
    {
1240
        ret = i->second;
1241
    }
1242
    else
1243
    {
1244
        ret = new WhisperTab(this, caption, nick);
1245
        if ((gui != nullptr) && !playerRelations.isGoodName(nick))
1246
            ret->setLabelFont(gui->getSecureFont());
1247
        mWhispers[tempNick] = ret;
1248
        if (config.getBoolValue("showChatHistory"))
1249
            ret->loadFromLogFile(nick);
1250
    }
1251
1252
    if (switchTo)
1253
        mChatTabs->setSelectedTab(ret);
1254
1255
    return ret;
1256
}
1257
1258
WhisperTab *ChatWindow::getWhisperTab(const std::string &nick) const
1259
{
1260
    if (localPlayer == nullptr)
1261
        return nullptr;
1262
1263
    std::string playerName = localPlayer->getName();
1264
    std::string tempNick = nick;
1265
1266
    toLower(playerName);
1267
    toLower(tempNick);
1268
1269
    const TabMap::const_iterator i = mWhispers.find(tempNick);
1270
    WhisperTab *ret = nullptr;
1271
1272
    if (tempNick == playerName)
1273
        return nullptr;
1274
1275
    if (i != mWhispers.end())
1276
        ret = i->second;
1277
1278
    return ret;
1279
}
1280
1281
ChatTab *ChatWindow::addSpecialChannelTab(const std::string &name,
1282
                                          const bool switchTo)
1283
{
1284
    ChatTab *ret = nullptr;
1285
    if (name == TRADE_CHANNEL)
1286
    {
1287
        if (tradeChatTab == nullptr)
1288
        {
1289
            tradeChatTab = new TradeTab(chatWindow);
1290
            tradeChatTab->setAllowHighlight(false);
1291
            chatHandler->joinChannel(tradeChatTab->getChannelName());
1292
        }
1293
        ret = tradeChatTab;
1294
    }
1295
    else if (name == GM_CHANNEL)
1296
    {
1297
        if (gmChatTab == nullptr)
1298
        {
1299
            gmChatTab = new GmTab(chatWindow);
1300
            chatHandler->joinChannel(gmChatTab->getChannelName());
1301
        }
1302
        ret = gmChatTab;
1303
    }
1304
    if (switchTo)
1305
        mChatTabs->setSelectedTab(ret);
1306
1307
    return ret;
1308
}
1309
1310
ChatTab *ChatWindow::addChannelTab(const std::string &name,
1311
                                   const bool switchTo)
1312
{
1313
    std::string tempName = name;
1314
    toLower(tempName);
1315
1316
    ChatTab *const tab = addSpecialChannelTab(name, switchTo);
1317
    if (tab != nullptr)
1318
        return tab;
1319
1320
    const ChannelMap::const_iterator i = mChannels.find(tempName);
1321
    ChannelTab *ret = nullptr;
1322
1323
    if (i != mChannels.end())
1324
    {
1325
        ret = i->second;
1326
    }
1327
    else
1328
    {
1329
        ret = new ChannelTab(this, name);
1330
        mChannels[tempName] = ret;
1331
        ret->setAllowHighlight(false);
1332
        if (config.getBoolValue("showChatHistory"))
1333
            ret->loadFromLogFile(name);
1334
    }
1335
1336
    if (switchTo)
1337
        mChatTabs->setSelectedTab(ret);
1338
1339
    return ret;
1340
}
1341
1342
ChatTab *ChatWindow::addChatTab(const std::string &name,
1343
                                const bool switchTo,
1344
                                const bool join)
1345
{
1346
    if (Net::getNetworkType() != ServerType::TMWATHENA &&
1347
        name.size() > 1 &&
1348
        name[0] == '#')
1349
    {
1350
        ChatTab *const tab = addChannelTab(name, switchTo);
1351
        if ((tab != nullptr) && join)
1352
            chatHandler->joinChannel(name);
1353
        return tab;
1354
    }
1355
    return addWhisperTab(name, name, switchTo);
1356
}
1357
1358
void ChatWindow::postConnection()
1359
{
1360
    FOR_EACH (ChannelMap::const_iterator, iter, mChannels)
1361
    {
1362
        ChatTab *const tab = iter->second;
1363
        if (tab == nullptr)
1364
            return;
1365
        chatHandler->joinChannel(tab->getChannelName());
1366
    }
1367
    if (langChatTab != nullptr)
1368
        chatHandler->joinChannel(langChatTab->getChannelName());
1369
}
1370
1371
#define changeColor(fun) \
1372
    { \
1373
        msg = removeColors(msg); \
1374
        int skip = 0; \
1375
        const size_t sz = msg.length(); \
1376
        for (size_t f = 0; f < sz; f ++) \
1377
        { \
1378
            if (skip > 0) \
1379
            { \
1380
                newMsg += msg.at(f); \
1381
                skip --; \
1382
                continue; \
1383
            } \
1384
            const unsigned char ch = CAST_U8(msg.at(f)); \
1385
            if (f + 2 < sz && msg.substr(f, 2) == "%%") \
1386
            { \
1387
                newMsg += msg.at(f); \
1388
                skip = 2; \
1389
            } \
1390
            else if (ch > 0xc0 || ch < 0x80) \
1391
            { \
1392
                newMsg += "##" + toString(fun) + msg.at(f); \
1393
                if (mRainbowColor > 9U) \
1394
                    mRainbowColor = 0U; \
1395
            } \
1396
            else \
1397
            { \
1398
                newMsg += msg.at(f); \
1399
            } \
1400
        } \
1401
    }
1402
1403
std::string ChatWindow::addColors(std::string &msg)
1404
{
1405
    // default color or chat command
1406
    if (mChatColor == 0 || msg.length() == 0 || msg.at(0) == '#'
1407
        || msg.at(0) == '/' || msg.at(0) == '@' || msg.at(0) == '!')
1408
    {
1409
        return msg;
1410
    }
1411
1412
    std::string newMsg;
1413
    const int cMap[] = {1, 4, 5, 2, 3, 6, 7, 9, 0, 8};
1414
1415
    // rainbow
1416
    switch (mChatColor)
1417
    {
1418
        case 11:
1419
            changeColor(mRainbowColor++)
1420
            return newMsg;
1421
        case 12:
1422
            changeColor(cMap[mRainbowColor++])
1423
            return newMsg;
1424
        case 13:
1425
            changeColor(cMap[9-mRainbowColor++])
1426
            return newMsg;
1427
        default:
1428
            break;
1429
    }
1430
1431
    // simple colors
1432
    return std::string("##").append(toString(mChatColor - 1)).append(msg);
1433
}
1434
1435
#undef changeColor
1436
1437
void ChatWindow::autoComplete()
1438
{
1439
    const int caretPos = mChatInput->getCaretPosition();
1440
    int startName = 0;
1441
    const std::string &inputText = mChatInput->getText();
1442
    std::string name = inputText.substr(0, caretPos);
1443
1444
    for (int f = caretPos - 1; f > -1; f --)
1445
    {
1446
        if (isWordSeparator(inputText[f]))
1447
        {
1448
            startName = f + 1;
1449
            name = inputText.substr(f + 1, caretPos - f);
1450
            break;
1451
        }
1452
    }
1453
1454
    if (caretPos - 1 + 1 == startName)
1455
        return;
1456
1457
    const ChatTab *const cTab = static_cast<ChatTab*>(
1458
        mChatTabs->getSelectedTab());
1459
    StringVect nameList;
1460
1461
    if (cTab != nullptr)
1462
        cTab->getAutoCompleteList(nameList);
1463
    std::string newName = autoComplete(nameList, name);
1464
    if (!newName.empty() && (startName == 0))
1465
        secureChatCommand(newName);
1466
1467
    if ((cTab != nullptr) && newName.empty())
1468
    {
1469
        cTab->getAutoCompleteCommands(nameList);
1470
        newName = autoComplete(nameList, name);
1471
    }
1472
1473
    if (newName.empty() && (actorManager != nullptr))
1474
    {
1475
        actorManager->getPlayerNames(nameList, NpcNames_true);
1476
        newName = autoComplete(nameList, name);
1477
        if (!newName.empty() && (startName == 0))
1478
            secureChatCommand(newName);
1479
    }
1480
    if (newName.empty())
1481
        newName = autoCompleteHistory(name);
1482
    if (newName.empty() && (spellManager != nullptr))
1483
        newName = spellManager->autoComplete(name);
1484
    if (newName.empty())
1485
        newName = autoComplete(name, &mCommands);
1486
    if (newName.empty() && (actorManager != nullptr))
1487
    {
1488
        actorManager->getMobNames(nameList);
1489
        newName = autoComplete(nameList, name);
1490
    }
1491
    if (newName.empty())
1492
        newName = autoComplete(name, &mCustomWords);
1493
    if (newName.empty())
1494
    {
1495
        whoIsOnline->getPlayerNames(nameList);
1496
        newName = autoComplete(nameList, name);
1497
    }
1498
1499
    if (!newName.empty())
1500
    {
1501
        mChatInput->setText(inputText.substr(0, startName).append(newName)
1502
            .append(inputText.substr(caretPos,
1503
            inputText.length() - caretPos)));
1504
1505
        const int len = caretPos - CAST_S32(name.length())
1506
            + CAST_S32(newName.length());
1507
1508
        if (startName > 0)
1509
            mChatInput->setCaretPosition(len + 1);
1510
        else
1511
            mChatInput->setCaretPosition(len);
1512
    }
1513
}
1514
1515
std::string ChatWindow::autoComplete(const StringVect &names,
1516
                                     std::string partName)
1517
{
1518
    StringVectCIter i = names.begin();
1519
    const StringVectCIter i_end = names.end();
1520
    toLower(partName);
1521
    std::string newName;
1522
1523
    while (i != i_end)
1524
    {
1525
        if (!i->empty())
1526
        {
1527
            std::string name = *i;
1528
            toLower(name);
1529
1530
            const size_t pos = name.find(partName, 0);
1531
            if (pos == 0)
1532
            {
1533
                if (!newName.empty())
1534
                    newName = findSameSubstringI(*i, newName);
1535
                else
1536
                    newName = *i;
1537
            }
1538
        }
1539
        ++i;
1540
    }
1541
1542
    return newName;
1543
}
1544
1545
std::string ChatWindow::autoComplete(const std::string &partName,
1546
                                     const History *const words)
1547
{
1548
    if (words == nullptr)
1549
        return "";
1550
1551
    ChatCommands::const_iterator i = words->begin();
1552
    const ChatCommands::const_iterator i_end = words->end();
1553
    StringVect nameList;
1554
1555
    while (i != i_end)
1556
    {
1557
        const std::string line = *i;
1558
        if (line.find(partName, 0) == 0)
1559
            nameList.push_back(line);
1560
        ++i;
1561
    }
1562
    return autoComplete(nameList, partName);
1563
}
1564
1565
/*
1566
void ChatWindow::moveTabLeft(ChatTab *tab)
1567
{
1568
    mChatTabs->moveLeft(tab);
1569
}
1570
1571
void ChatWindow::moveTabRight(ChatTab *tab)
1572
{
1573
    mChatTabs->moveRight(tab);
1574
}
1575
*/
1576
1577
std::string ChatWindow::autoCompleteHistory(const std::string &partName) const
1578
{
1579
    History::const_iterator i = mHistory.begin();
1580
    const History::const_iterator i_end = mHistory.end();
1581
    StringVect nameList;
1582
1583
    while (i != i_end)
1584
    {
1585
        std::string line = *i;
1586
        unsigned int f = 0;
1587
        while (f < line.length() && !isWordSeparator(line.at(f)))
1588
            f++;
1589
1590
        line = line.substr(0, f);
1591
        if (!line.empty())
1592
            nameList.push_back(line);
1593
1594
        ++i;
1595
    }
1596
    return autoComplete(nameList, partName);
1597
}
1598
1599
bool ChatWindow::resortChatLog(std::string line,
1600
                               ChatMsgTypeT own,
1601
                               const std::string &channel,
1602
                               const IgnoreRecord ignoreRecord,
1603
                               const TryRemoveColors tryRemoveColors)
1604
{
1605
    if (own == ChatMsgType::BY_UNKNOWN)
1606
    {
1607
        const size_t pos = line.find(" : ");
1608
        if (pos != std::string::npos)
1609
        {
1610
            if (line.length() <= pos + 3)
1611
                own = ChatMsgType::BY_SERVER;
1612
            else if (line.substr(0, pos) == localPlayer->getName())
1613
                own = ChatMsgType::BY_PLAYER;
1614
            else
1615
                own = ChatMsgType::BY_OTHER;
1616
        }
1617
        else
1618
        {
1619
            own = ChatMsgType::BY_SERVER;
1620
        }
1621
    }
1622
1623
    std::string prefix;
1624
    if (!channel.empty())
1625
    {
1626
        prefix = std::string("##3").append(channel).append("##0");
1627
    }
1628
    else if (mEnableTradeFilter &&
1629
             (tradeChatTab != nullptr) &&
1630
             findI(line, mTradeFilter) != std::string::npos)
1631
    {
1632
        // TRANSLATORS: prefix for moved message to trade tab.
1633
        tradeChatTab->chatLog(std::string("##S") +  _("Moved: ") + line,
1634
            own,
1635
            ignoreRecord,
1636
            tryRemoveColors);
1637
        if (own == ChatMsgType::BY_PLAYER)
1638
        {
1639
            own = ChatMsgType::BY_SERVER;
1640
            // TRANSLATORS: moved message to trade tab warning.
1641
            line = _("Your message was moved to trade tab");
1642
        }
1643
        else
1644
        {
1645
            return false;
1646
        }
1647
    }
1648
1649
    size_t idx2 = line.find(": ");
1650
    if (idx2 != std::string::npos)
1651
    {
1652
        std::string tmpNick = line.substr(0, idx2);
1653
        if (tmpNick.find('#') != std::string::npos ||
1654
            tmpNick.find(':') != std::string::npos ||
1655
            tmpNick.find('%') != std::string::npos ||
1656
            tmpNick.find('@') != std::string::npos ||
1657
            tmpNick.size() < 5 ||
1658
            tmpNick[0] == '@' ||
1659
            tmpNick[0] == '/' ||
1660
            tmpNick[0] == '!'
1661
            )
1662
        {
1663
            replaceAll(tmpNick, "#", "_");
1664
            replaceAll(tmpNick, "%", "_");
1665
            // TRANSLATORS: error message
1666
            line = _("Broken nick detected: ") + line;
1667
            own = ChatMsgType::BY_SERVER;
1668
        }
1669
        const size_t idx = line.find(": \302\202");
1670
        if (idx == idx2)
1671
        {
1672
            if (line.find(": \302\202\302") != std::string::npos)
1673
            {
1674
                if (line.find(": \302\202\302e") != std::string::npos)
1675
                {
1676
                    // Do nothing. Before was local pet emote
1677
                }
1678
                else if (line.find(": \302\202\302m") != std::string::npos)
1679
                {
1680
                    // Do nothing. Before was local pet move
1681
                }
1682
                else if (line.find(": \302\202\302d") != std::string::npos)
1683
                {
1684
                    // Do nothing. Before was local pet direction
1685
                }
1686
                else if (line.find(": \302\202\302a") != std::string::npos)
1687
                {
1688
                    // Do nothing. Before was local pet ai enable/disable
1689
                }
1690
                // ignore other special message formats.
1691
                return false;
1692
            }
1693
1694
            // pet talk message detected
1695
            if (line.find(": \302\202\303 ") != std::string::npos)
1696
            {
1697
                // Do nothing. Before was local pet talk
1698
                return false;
1699
            }
1700
            if (line.find(": \302\202\304") != std::string::npos)
1701
            {
1702
                replaceAll(line, ": \302\202\304", ": ");
1703
            }
1704
1705
            if (tradeChatTab != nullptr)
1706
            {
1707
                line = line.erase(idx + 2, 2);
1708
                tradeChatTab->chatLog(prefix + line,
1709
                    own,
1710
                    ignoreRecord,
1711
                    tryRemoveColors);
1712
            }
1713
            else if (localChatTab != nullptr)
1714
            {
1715
                line = line.erase(idx + 2, 2);
1716
                localChatTab->chatLog(prefix + line,
1717
                    own,
1718
                    ignoreRecord,
1719
                    tryRemoveColors);
1720
            }
1721
            return false;
1722
        }
1723
    }
1724
1725
    if (!channel.empty())
1726
    {
1727
        if (langChatTab != nullptr)
1728
        {
1729
            if (langChatTab->getChannelName() == channel)
1730
            {
1731
                langChatTab->chatLog(line,
1732
                    own,
1733
                    ignoreRecord,
1734
                    tryRemoveColors);
1735
            }
1736
            else if (mShowAllLang)
1737
            {
1738
                langChatTab->chatLog(prefix + line,
1739
                    own,
1740
                    ignoreRecord,
1741
                    tryRemoveColors);
1742
            }
1743
        }
1744
        else if (Net::getNetworkType() != ServerType::TMWATHENA)
1745
        {
1746
            channelChatLog(channel, line, own, ignoreRecord, tryRemoveColors);
1747
            return false;
1748
        }
1749
        else if (mShowAllLang)
1750
        {
1751
            localChatTab->chatLog(prefix + line,
1752
                own,
1753
                ignoreRecord,
1754
                tryRemoveColors);
1755
        }
1756
    }
1757
    else if (localChatTab != nullptr)
1758
    {
1759
        localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1760
    }
1761
    return true;
1762
}
1763
1764
void ChatWindow::battleChatLog(const std::string &line, ChatMsgTypeT own,
1765
                               const IgnoreRecord ignoreRecord,
1766
                               const TryRemoveColors tryRemoveColors)
1767
{
1768
    if (own == ChatMsgType::BY_UNKNOWN)
1769
        own = ChatMsgType::BY_SERVER;
1770
    if (battleChatTab != nullptr)
1771
        battleChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1772
    else if (debugChatTab != nullptr)
1773
        debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1774
}
1775
1776
void ChatWindow::channelChatLog(const std::string &channel,
1777
                                const std::string &line,
1778
                                ChatMsgTypeT own,
1779
                                const IgnoreRecord ignoreRecord,
1780
                                const TryRemoveColors tryRemoveColors)
1781
{
1782
    std::string tempChannel = channel;
1783
    toLower(tempChannel);
1784
1785
    ChatTab *tab = nullptr;
1786
    const ChannelMap::const_iterator i = mChannels.find(tempChannel);
1787
1788
    if (i != mChannels.end())
1789
    {
1790
        tab = i->second;
1791
    }
1792
    else
1793
    {
1794
        tab = addChannelTab(channel, false);
1795
        if (tab != nullptr)
1796
            saveState();
1797
    }
1798
1799
    if (tab != nullptr)
1800
        tab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1801
}
1802
1803
2
void ChatWindow::initTradeFilter()
1804
{
1805
    const std::string tradeListName = settings.serverConfigDir
1806
4
        + "/tradefilter.txt";
1807
1808
4
    std::ifstream tradeFile;
1809
    struct stat statbuf;
1810
1811

4
    if (stat(tradeListName.c_str(), &statbuf) == 0 &&
1812
        S_ISREG(statbuf.st_mode))
1813
    {
1814
        tradeFile.open(tradeListName.c_str(), std::ios::in);
1815
        if (tradeFile.is_open())
1816
        {
1817
            char line[100];
1818
            while (tradeFile.getline(line, 100))
1819
            {
1820
                const std::string str = line;
1821
                if (!str.empty())
1822
                    mTradeFilter.push_back(str);
1823
            }
1824
        }
1825
        tradeFile.close();
1826
    }
1827
2
}
1828
1829
void ChatWindow::updateOnline(const std::set<std::string> &onlinePlayers) const
1830
{
1831
    const Party *party = nullptr;
1832
    const Guild *guild = nullptr;
1833
    if (localPlayer != nullptr)
1834
    {
1835
        party = localPlayer->getParty();
1836
        guild = localPlayer->getGuild();
1837
    }
1838
    FOR_EACH (TabMap::const_iterator, iter, mWhispers)
1839
    {
1840
        if (iter->second == nullptr)
1841
            return;
1842
1843
        WhisperTab *const tab = static_cast<WhisperTab*>(iter->second);
1844
        if (tab == nullptr)
1845
            continue;
1846
1847
        if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end())
1848
        {
1849
            tab->setWhisperTabColors();
1850
        }
1851
        else
1852
        {
1853
            const std::string &nick = tab->getNick();
1854
            if (actorManager != nullptr)
1855
            {
1856
                const Being *const being = actorManager->findBeingByName(
1857
                    nick, ActorType::Player);
1858
                if (being != nullptr)
1859
                {
1860
                    tab->setWhisperTabColors();
1861
                    continue;
1862
                }
1863
            }
1864
            if (party != nullptr)
1865
            {
1866
                const PartyMember *const pm = party->getMember(nick);
1867
                if ((pm != nullptr) && pm->getOnline())
1868
                {
1869
                    tab->setWhisperTabColors();
1870
                    continue;
1871
                }
1872
            }
1873
            if (guild != nullptr)
1874
            {
1875
                const GuildMember *const gm = guild->getMember(nick);
1876
                if ((gm != nullptr) && gm->getOnline())
1877
                {
1878
                    tab->setWhisperTabColors();
1879
                    continue;
1880
                }
1881
            }
1882
            tab->setWhisperTabOfflineColors();
1883
        }
1884
    }
1885
}
1886
1887
void ChatWindow::loadState()
1888
{
1889
    int num = 0;
1890
    while (num < 50)
1891
    {
1892
        const std::string nick = serverConfig.getValue(
1893
            "chatWhisper" + toString(num), "");
1894
1895
        if (nick.empty())
1896
            break;
1897
1898
        ChatTab *const tab = addChatTab(nick, false, false);
1899
        if (tab != nullptr)
1900
        {
1901
            const int flags = serverConfig.getValue(
1902
                "chatWhisperFlags" + toString(num), 1);
1903
1904
            tab->setAllowHighlight((flags & 1) != 0);
1905
            tab->setRemoveNames(((flags & 2) / 2) != 0);
1906
            tab->setNoAway(((flags & 4) / 4) != 0);
1907
        }
1908
        num ++;
1909
    }
1910
}
1911
1912
2
void ChatWindow::saveState() const
1913
{
1914
2
    int num = 0;
1915
6
    for (ChannelMap::const_iterator iter = mChannels.begin(),
1916

4
         iter_end = mChannels.end(); iter != iter_end && num < 50; ++iter)
1917
    {
1918
        if (iter->second == nullptr)
1919
            return;
1920
        if (!saveTab(num, iter->second))
1921
            continue;
1922
        num ++;
1923
    }
1924
1925
6
    for (TabMap::const_iterator iter = mWhispers.begin(),
1926

4
         iter_end = mWhispers.end(); iter != iter_end && num < 50; ++iter)
1927
    {
1928
        if (iter->second == nullptr)
1929
            return;
1930
        if (!saveTab(num, iter->second))
1931
            continue;
1932
        num ++;
1933
    }
1934
1935
202
    while (num < 50)
1936
    {
1937

300
        serverConfig.deleteKey("chatWhisper" + toString(num));
1938

300
        serverConfig.deleteKey("chatWhisperFlags" + toString(num));
1939
100
        num ++;
1940
    }
1941
}
1942
1943
bool ChatWindow::saveTab(const int num,
1944
                         const ChatTab *const tab)
1945
{
1946
    if (tab == nullptr)
1947
        return false;
1948
1949
    serverConfig.setValue("chatWhisper" + toString(num),
1950
        tab->getChannelName());
1951
1952
    serverConfig.setValue("chatWhisperFlags" + toString(num),
1953
        CAST_S32(tab->getAllowHighlight())
1954
        + (2 * CAST_S32(tab->getRemoveNames()))
1955
        + (4 * CAST_S32(tab->getNoAway())));
1956
1957
    return true;
1958
}
1959
1960
std::string ChatWindow::doReplace(const std::string &msg)
1961
{
1962
    std::string str = msg;
1963
    replaceSpecialChars(str);
1964
    return str;
1965
}
1966
1967
2
void ChatWindow::loadCustomList()
1968
{
1969
4
    std::ifstream listFile;
1970
    struct stat statbuf;
1971
1972
    std::string listName = settings.serverConfigDir
1973
4
        + "/customwords.txt";
1974
1975

4
    if ((stat(listName.c_str(), &statbuf) == 0) && S_ISREG(statbuf.st_mode))
1976
    {
1977
        listFile.open(listName.c_str(), std::ios::in);
1978
        if (listFile.is_open())
1979
        {
1980
            char line[101];
1981
            while (listFile.getline(line, 100))
1982
            {
1983
                std::string str = line;
1984
                if (!str.empty())
1985
                    mCustomWords.push_back(str);
1986
            }
1987
        }
1988
        listFile.close();
1989
    }
1990
2
}
1991
1992
void ChatWindow::addToAwayLog(const std::string &line)
1993
{
1994
    if (!settings.awayMode)
1995
        return;
1996
1997
    if (mAwayLog.size() > 20)
1998
        mAwayLog.pop_front();
1999
2000
    if (findI(line, mHighlights) != std::string::npos)
2001
        mAwayLog.push_back("##aaway:" + line);
2002
}
2003
2004
void ChatWindow::displayAwayLog() const
2005
{
2006
    if (localChatTab == nullptr)
2007
        return;
2008
2009
    std::list<std::string>::const_iterator i = mAwayLog.begin();
2010
    const std::list<std::string>::const_iterator i_end = mAwayLog.end();
2011
2012
    while (i != i_end)
2013
    {
2014
        std::string str = *i;
2015
        localChatTab->addNewRow(str);
2016
        ++i;
2017
    }
2018
}
2019
2020
2
void ChatWindow::parseHighlights()
2021
{
2022
2
    mHighlights.clear();
2023
2
    if (localPlayer == nullptr)
2024
        return;
2025
2026
10
    splitToStringVector(mHighlights, config.getStringValue(
2027
2
        "highlightWords"), ',');
2028
2029
4
    mHighlights.push_back(localPlayer->getName());
2030
}
2031
2032
2
void ChatWindow::parseGlobalsFilter()
2033
{
2034
2
    mGlobalsFilter.clear();
2035
2
    if (localPlayer == nullptr)
2036
        return;
2037
2038
10
    splitToStringVector(mGlobalsFilter, config.getStringValue(
2039
2
        "globalsFilter"), ',');
2040
2041
4
    mHighlights.push_back(localPlayer->getName());
2042
}
2043
2044
bool ChatWindow::findHighlight(const std::string &str)
2045
{
2046
    return findI(str, mHighlights) != std::string::npos;
2047
}
2048
2049
void ChatWindow::copyToClipboard(const int x, const int y) const
2050
{
2051
    const ChatTab *const tab = getFocused();
2052
    if (tab == nullptr)
2053
        return;
2054
2055
    const BrowserBox *const text = tab->mTextOutput;
2056
    if (text == nullptr)
2057
        return;
2058
2059
    std::string str = text->getTextAtPos(x, y);
2060
    sendBuffer(str);
2061
}
2062
2063
void ChatWindow::optionChanged(const std::string &name)
2064
{
2065
    if (name == "autohideChat")
2066
        mAutoHide = config.getBoolValue("autohideChat");
2067
    else if (name == "showBattleEvents")
2068
        mShowBattleEvents = config.getBoolValue("showBattleEvents");
2069
    else if (name == "globalsFilter")
2070
        parseGlobalsFilter();
2071
    else if (name == "enableTradeFilter")
2072
        mEnableTradeFilter = config.getBoolValue("enableTradeFilter");
2073
}
2074
2075
void ChatWindow::mouseMoved(MouseEvent &event)
2076
{
2077
    mHaveMouse = true;
2078
    Window::mouseMoved(event);
2079
}
2080
2081
void ChatWindow::mouseEntered(MouseEvent& event)
2082
{
2083
    mHaveMouse = true;
2084
    Window::mouseEntered(event);
2085
}
2086
2087
void ChatWindow::mouseExited(MouseEvent& event)
2088
{
2089
    updateVisibility();
2090
    Window::mouseExited(event);
2091
}
2092
2093
1
void ChatWindow::draw(Graphics *const graphics)
2094
{
2095
    BLOCK_START("ChatWindow::draw")
2096

1
    if (!mAutoHide || mHaveMouse)
2097
    {
2098
        GLDEBUG_START("ChatWindow::draw");
2099
1
        Window::draw(graphics);
2100
        GLDEBUG_END();
2101
    }
2102
    BLOCK_END("ChatWindow::draw")
2103
1
}
2104
2105
void ChatWindow::safeDraw(Graphics *const graphics)
2106
{
2107
    BLOCK_START("ChatWindow::draw")
2108
    if (!mAutoHide || mHaveMouse)
2109
    {
2110
        GLDEBUG_START("ChatWindow::draw");
2111
        Window::safeDraw(graphics);
2112
        GLDEBUG_END();
2113
    }
2114
    BLOCK_END("ChatWindow::draw")
2115
}
2116
2117
2
void ChatWindow::updateVisibility()
2118
{
2119
2
    if (gui == nullptr)
2120
        return;
2121
2122
2
    int mouseX = 0;
2123
2
    int mouseY = 0;
2124
2
    int x = 0;
2125
2
    int y = 0;
2126
2
    Gui::getMouseState(mouseX, mouseY);
2127
2
    getAbsolutePosition(x, y);
2128
4
    if (mChatInput->isVisible())
2129
    {
2130
        mHaveMouse = true;
2131
    }
2132
    else
2133
    {
2134
8
        mHaveMouse = mouseX >= x && mouseX <= x + getWidth()
2135

4
            && mouseY >= y && mouseY <= y + getHeight();
2136
    }
2137
}
2138
2139
void ChatWindow::unHideWindow()
2140
{
2141
    mHaveMouse = true;
2142
}
2143
2144
#ifdef USE_PROFILER
2145
void ChatWindow::logicChildren()
2146
{
2147
    BLOCK_START("ChatWindow::logicChildren")
2148
    BasicContainer::logicChildren();
2149
    BLOCK_END("ChatWindow::logicChildren")
2150
}
2151
#endif  // USE_PROFILER
2152
2153
void ChatWindow::addGlobalMessage(const std::string &line)
2154
{
2155
    if (debugChatTab != nullptr &&
2156
        findI(line, mGlobalsFilter) != std::string::npos)
2157
    {
2158
        debugChatTab->chatLog(line,
2159
            ChatMsgType::BY_OTHER,
2160
            IgnoreRecord_false,
2161
            TryRemoveColors_true);
2162
    }
2163
    else
2164
    {
2165
        localChatTab->chatLog(line,
2166
            ChatMsgType::BY_GM,
2167
            IgnoreRecord_false,
2168
            TryRemoveColors_true);
2169
    }
2170
}
2171
2172
bool ChatWindow::isTabPresent(const ChatTab *const tab) const
2173
{
2174
    return mChatTabs->isTabPresent(tab);
2175
}
2176
2177
void ChatWindow::debugMessage(const std::string &msg)
2178
{
2179
    if (debugChatTab != nullptr)
2180
    {
2181
        debugChatTab->chatLog(msg,
2182
            ChatMsgType::BY_SERVER,
2183
            IgnoreRecord_false,
2184
            TryRemoveColors_true);
2185
    }
2186
}
2187
2188
void ChatWindow::showGMTab()
2189
{
2190
    if ((gmChatTab == nullptr) &&
2191
        config.getBoolValue("enableGmTab") &&
2192
        localPlayer->getGroupId() >= paths.getIntValue("gmTabMinimalLevel"))
2193
    {
2194
        addSpecialChannelTab(GM_CHANNEL, false);
2195
    }
2196
}
2197
2198
void ChatWindow::toggleChatFocus()
2199
{
2200
    if (mChatInput->isVisible() && mChatInput->isFocused())
2201
        mChatInput->processVisible(Visible_false);
2202
    else
2203
        requestChatFocus();
2204
}
2205
2206
void ChatWindow::joinRoom(const bool isJoin)
2207
{
2208
    Tab *const tab = mChatTabs->getTabByIndex(0);
2209
    if (tab != nullptr)
2210
    {
2211
        std::string name;
2212
        if (isJoin)
2213
        {
2214
            name = PlayerInfo::getRoomName();
2215
        }
2216
        else
2217
        {
2218
            // TRANSLATORS: chat tab name
2219
            name = _("General");
2220
        }
2221
        tab->setCaption(name);
2222
    }
2223
}
2224
2225
void ChatWindow::scheduleDelete()
2226
{
2227
    DebugMessageListener::removeListener(this);
2228
    Window::scheduleDelete();
2229

3
}