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

2
    mItemLinkHandler(new ItemLinkHandler),
105


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

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

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

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

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

4
    mEmoteButtonSpacing(mSkin != nullptr ?
124

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

4
    mEmoteButtonY(mSkin != nullptr ?
126

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

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

6
    mAutoHide(config.getBoolValue("autohideChat")),
132

8
    mShowBattleEvents(config.getBoolValue("showBattleEvents")),
133

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

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



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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

3
}