GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/chatwindow.cpp Lines: 208 1040 20.0 %
Date: 2017-11-29 Branches: 134 1542 8.7 %

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-2017  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 <sstream>
85
86
#include "debug.h"
87
88
ChatWindow *chatWindow = nullptr;
89
90
static const char *const ACTION_COLOR_PICKER = "color picker";
91
92
4
ChatWindow::ChatWindow(const std::string &name) :
93
    // TRANSLATORS: chat window name
94
4
    Window(_("Chat"), Modal_false, nullptr, "chat.xml"),
95
    ActionListener(),
96
    KeyListener(),
97
    AttributeListener(),
98

4
    mItemLinkHandler(new ItemLinkHandler),
99

4
    mChatTabs(CREATEWIDGETR(TabbedArea, this)),
100

4
    mChatInput(new ChatInput(this)),
101
    mRainbowColor(0U),
102
    mWhispers(),
103
    mChannels(),
104
    mHistory(),
105
    mCurHist(),
106
    mCommands(),
107
    mCustomWords(),
108
    mTradeFilter(),
109
8
    mColorListModel(new ColorListModel),
110

4
    mColorPicker(new DropDown(this, mColorListModel)),
111

4
    mChatButton(new Button(this, ":)", "openemote", this)),
112
    mAwayLog(),
113
    mHighlights(),
114
    mGlobalsFilter(),
115

16
    mChatColor(config.getIntValue("chatColor")),
116

8
    mEmoteButtonSpacing(mSkin != nullptr ?
117

16
                        mSkin->getOption("emoteButtonSpacing", 2) : 2),
118

8
    mEmoteButtonY(mSkin != nullptr ?
119

16
                  mSkin->getOption("emoteButtonY", -2) : -2),
120
    mChatHistoryIndex(0),
121

16
    mReturnToggles(config.getBoolValue("ReturnToggles")),
122
    mGMLoaded(false),
123
    mHaveMouse(false),
124

16
    mAutoHide(config.getBoolValue("autohideChat")),
125

16
    mShowBattleEvents(config.getBoolValue("showBattleEvents")),
126

16
    mShowAllLang(serverConfig.getValue("showAllLang", 0) != 0),
127

16
    mEnableTradeFilter(config.getBoolValue("enableTradeFilter")),
128




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

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


16
    mColorPicker->setVisible(fromBool(config.getBoolValue(
193
        "showChatColorsList"), Visible));
194
4
    updateTabsMargin();
195
196
4
    fillCommands();
197

8
    if ((localPlayer != nullptr) && localPlayer->isGM())
198
        loadGMCommands();
199
4
    initTradeFilter();
200
4
    loadCustomList();
201
4
    parseHighlights();
202
4
    parseGlobalsFilter();
203
204

16
    config.addListener("autohideChat", this);
205

16
    config.addListener("showBattleEvents", this);
206

16
    config.addListener("globalsFilter", this);
207

16
    config.addListener("enableTradeFilter", this);
208
209
8
    enableVisibleSound(true);
210
4
}
211
212
48
ChatWindow::~ChatWindow()
213
{
214
4
    config.removeListeners(this);
215
    CHECKLISTENERS
216
4
    saveState();
217
20
    config.setValue("ReturnToggles", mReturnToggles);
218
4
    removeAllWhispers();
219
4
    removeAllChannels();
220
4
    delete2(mItemLinkHandler);
221
4
    delete2(mColorPicker);
222
8
    delete2(mColorListModel);
223
8
}
224
225
4
void ChatWindow::postInit()
226
{
227
4
    Window::postInit();
228
4
    add(mChatTabs);
229
4
    add(mChatInput);
230
4
    add(mColorPicker);
231
4
    add(mChatButton);
232
4
    updateVisibility();
233
4
}
234
235
4
void ChatWindow::loadCommandsFile(const std::string &name)
236
{
237
8
    StringVect list;
238
4
    VirtFs::loadTextFile(name, list);
239
8
    StringVectCIter it = list.begin();
240
8
    const StringVectCIter it_end = list.end();
241
242
4
    while (it != it_end)
243
    {
244
        const std::string str = *it;
245
        if (!str.empty())
246
            mCommands.push_back(str);
247
        ++ it;
248
    }
249
4
}
250
251
4
void ChatWindow::fillCommands()
252
{
253

16
    loadCommandsFile("chatcommands.txt");
254
4
    inputManager.addChatCommands(mCommands);
255
4
}
256
257
void ChatWindow::loadGMCommands()
258
{
259
    if (mGMLoaded)
260
        return;
261
262
    loadCommandsFile("gmcommands.txt");
263
    mGMLoaded = true;
264
}
265
266
void ChatWindow::updateTabsMargin()
267
{
268

8
    if (mColorPicker->mVisible == Visible_true)
269
16
        mChatTabs->setRightMargin(mColorPicker->getWidth() + 16 + 8);
270
    else
271
        mChatTabs->setRightMargin(8);
272
}
273
274
4
void ChatWindow::adjustTabSize()
275
{
276
8
    const Rect area = getChildrenArea();
277
278
4
    const int aw = area.width;
279
4
    const int ah = area.height;
280
8
    const int frame = mChatInput->getFrameSize();
281
8
    const int inputHeight = mChatInput->getHeight();
282

16
    const bool showEmotes = config.getBoolValue("showEmotesButton");
283
4
    int maxHeight = inputHeight;
284
4
    if (showEmotes)
285
    {
286
8
        const int buttonHeight = mChatButton->getHeight();
287
4
        if (buttonHeight > maxHeight)
288
4
            maxHeight = buttonHeight;
289
    }
290
4
    const int frame2 = 2 * frame;
291
4
    const int awFrame2 = aw - frame2;
292
4
    int y = ah - maxHeight - frame;
293
4
    mChatInput->setPosition(frame, y);
294
4
    mChatTabs->setWidth(awFrame2);
295
4
    const int height = ah - frame2 - (maxHeight + frame2);
296

12
    if (mChatInput->mVisible == Visible_true ||
297

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

8
    if (stat(tradeListName.c_str(), &statbuf) == 0 &&
1775
        S_ISREG(statbuf.st_mode))
1776
    {
1777
        tradeFile.open(tradeListName.c_str(), std::ios::in);
1778
        if (tradeFile.is_open())
1779
        {
1780
            char line[100];
1781
            while (tradeFile.getline(line, 100))
1782
            {
1783
                const std::string str = line;
1784
                if (!str.empty())
1785
                    mTradeFilter.push_back(str);
1786
            }
1787
        }
1788
        tradeFile.close();
1789
    }
1790
4
}
1791
1792
void ChatWindow::updateOnline(const std::set<std::string> &onlinePlayers) const
1793
{
1794
    const Party *party = nullptr;
1795
    const Guild *guild = nullptr;
1796
    if (localPlayer != nullptr)
1797
    {
1798
        party = localPlayer->getParty();
1799
        guild = localPlayer->getGuild();
1800
    }
1801
    FOR_EACH (TabMap::const_iterator, iter, mWhispers)
1802
    {
1803
        if (iter->second == nullptr)
1804
            return;
1805
1806
        WhisperTab *const tab = static_cast<WhisperTab*>(iter->second);
1807
        if (tab == nullptr)
1808
            continue;
1809
1810
        if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end())
1811
        {
1812
            tab->setWhisperTabColors();
1813
        }
1814
        else
1815
        {
1816
            const std::string &nick = tab->getNick();
1817
            if (actorManager != nullptr)
1818
            {
1819
                const Being *const being = actorManager->findBeingByName(
1820
                    nick, ActorType::Player);
1821
                if (being != nullptr)
1822
                {
1823
                    tab->setWhisperTabColors();
1824
                    continue;
1825
                }
1826
            }
1827
            if (party != nullptr)
1828
            {
1829
                const PartyMember *const pm = party->getMember(nick);
1830
                if ((pm != nullptr) && pm->getOnline())
1831
                {
1832
                    tab->setWhisperTabColors();
1833
                    continue;
1834
                }
1835
            }
1836
            if (guild != nullptr)
1837
            {
1838
                const GuildMember *const gm = guild->getMember(nick);
1839
                if ((gm != nullptr) && gm->getOnline())
1840
                {
1841
                    tab->setWhisperTabColors();
1842
                    continue;
1843
                }
1844
            }
1845
            tab->setWhisperTabOfflineColors();
1846
        }
1847
    }
1848
}
1849
1850
void ChatWindow::loadState()
1851
{
1852
    int num = 0;
1853
    while (num < 50)
1854
    {
1855
        const std::string nick = serverConfig.getValue(
1856
            "chatWhisper" + toString(num), "");
1857
1858
        if (nick.empty())
1859
            break;
1860
1861
        const int flags = serverConfig.getValue(
1862
            "chatWhisperFlags" + toString(num), 1);
1863
1864
        ChatTab *const tab = addChatTab(nick, false, false);
1865
        if (tab != nullptr)
1866
        {
1867
            tab->setAllowHighlight((flags & 1) != 0);
1868
            tab->setRemoveNames(((flags & 2) / 2) != 0);
1869
            tab->setNoAway(((flags & 4) / 4) != 0);
1870
        }
1871
        num ++;
1872
    }
1873
}
1874
1875
4
void ChatWindow::saveState() const
1876
{
1877
4
    int num = 0;
1878
8
    for (ChannelMap::const_iterator iter = mChannels.begin(),
1879

8
         iter_end = mChannels.end(); iter != iter_end && num < 50; ++iter)
1880
    {
1881
        if (iter->second == nullptr)
1882
            return;
1883
        if (!saveTab(num, iter->second))
1884
            continue;
1885
        num ++;
1886
    }
1887
1888
8
    for (TabMap::const_iterator iter = mWhispers.begin(),
1889

12
         iter_end = mWhispers.end(); iter != iter_end && num < 50; ++iter)
1890
    {
1891
        if (iter->second == nullptr)
1892
            return;
1893
        if (!saveTab(num, iter->second))
1894
            continue;
1895
        num ++;
1896
    }
1897
1898
404
    while (num < 50)
1899
    {
1900

600
        serverConfig.deleteKey("chatWhisper" + toString(num));
1901

600
        serverConfig.deleteKey("chatWhisperFlags" + toString(num));
1902
200
        num ++;
1903
    }
1904
}
1905
1906
bool ChatWindow::saveTab(const int num,
1907
                         const ChatTab *const tab) const
1908
{
1909
    if (tab == nullptr)
1910
        return false;
1911
1912
    serverConfig.setValue("chatWhisper" + toString(num),
1913
        tab->getChannelName());
1914
1915
    serverConfig.setValue("chatWhisperFlags" + toString(num),
1916
        CAST_S32(tab->getAllowHighlight())
1917
        + (2 * CAST_S32(tab->getRemoveNames()))
1918
        + (4 * CAST_S32(tab->getNoAway())));
1919
1920
    return true;
1921
}
1922
1923
std::string ChatWindow::doReplace(const std::string &msg)
1924
{
1925
    std::string str = msg;
1926
    replaceSpecialChars(str);
1927
    return str;
1928
}
1929
1930
4
void ChatWindow::loadCustomList()
1931
{
1932
8
    std::ifstream listFile;
1933
    struct stat statbuf;
1934
1935
    std::string listName = settings.serverConfigDir
1936
8
        + "/customwords.txt";
1937
1938

8
    if ((stat(listName.c_str(), &statbuf) == 0) && S_ISREG(statbuf.st_mode))
1939
    {
1940
        listFile.open(listName.c_str(), std::ios::in);
1941
        if (listFile.is_open())
1942
        {
1943
            char line[101];
1944
            while (listFile.getline(line, 100))
1945
            {
1946
                std::string str = line;
1947
                if (!str.empty())
1948
                    mCustomWords.push_back(str);
1949
            }
1950
        }
1951
        listFile.close();
1952
    }
1953
4
}
1954
1955
void ChatWindow::addToAwayLog(const std::string &line)
1956
{
1957
    if (!settings.awayMode)
1958
        return;
1959
1960
    if (mAwayLog.size() > 20)
1961
        mAwayLog.pop_front();
1962
1963
    if (findI(line, mHighlights) != std::string::npos)
1964
        mAwayLog.push_back("##aaway:" + line);
1965
}
1966
1967
void ChatWindow::displayAwayLog() const
1968
{
1969
    if (localChatTab == nullptr)
1970
        return;
1971
1972
    std::list<std::string>::const_iterator i = mAwayLog.begin();
1973
    const std::list<std::string>::const_iterator i_end = mAwayLog.end();
1974
1975
    while (i != i_end)
1976
    {
1977
        std::string str = *i;
1978
        localChatTab->addNewRow(str);
1979
        ++i;
1980
    }
1981
}
1982
1983
4
void ChatWindow::parseHighlights()
1984
{
1985
4
    mHighlights.clear();
1986
4
    if (localPlayer == nullptr)
1987
        return;
1988
1989

20
    splitToStringVector(mHighlights, config.getStringValue(
1990
        "highlightWords"), ',');
1991
1992
8
    mHighlights.push_back(localPlayer->getName());
1993
}
1994
1995
4
void ChatWindow::parseGlobalsFilter()
1996
{
1997
4
    mGlobalsFilter.clear();
1998
4
    if (localPlayer == nullptr)
1999
        return;
2000
2001

20
    splitToStringVector(mGlobalsFilter, config.getStringValue(
2002
        "globalsFilter"), ',');
2003
2004
8
    mHighlights.push_back(localPlayer->getName());
2005
}
2006
2007
bool ChatWindow::findHighlight(const std::string &str)
2008
{
2009
    return findI(str, mHighlights) != std::string::npos;
2010
}
2011
2012
void ChatWindow::copyToClipboard(const int x, const int y) const
2013
{
2014
    const ChatTab *const tab = getFocused();
2015
    if (tab == nullptr)
2016
        return;
2017
2018
    const BrowserBox *const text = tab->mTextOutput;
2019
    if (text == nullptr)
2020
        return;
2021
2022
    std::string str = text->getTextAtPos(x, y);
2023
    sendBuffer(str);
2024
}
2025
2026
void ChatWindow::optionChanged(const std::string &name)
2027
{
2028
    if (name == "autohideChat")
2029
        mAutoHide = config.getBoolValue("autohideChat");
2030
    else if (name == "showBattleEvents")
2031
        mShowBattleEvents = config.getBoolValue("showBattleEvents");
2032
    else if (name == "globalsFilter")
2033
        parseGlobalsFilter();
2034
    else if (name == "enableTradeFilter")
2035
        mEnableTradeFilter = config.getBoolValue("enableTradeFilter");
2036
}
2037
2038
void ChatWindow::mouseMoved(MouseEvent &event)
2039
{
2040
    mHaveMouse = true;
2041
    Window::mouseMoved(event);
2042
}
2043
2044
void ChatWindow::mouseEntered(MouseEvent& event)
2045
{
2046
    mHaveMouse = true;
2047
    Window::mouseEntered(event);
2048
}
2049
2050
void ChatWindow::mouseExited(MouseEvent& event)
2051
{
2052
    updateVisibility();
2053
    Window::mouseExited(event);
2054
}
2055
2056
2
void ChatWindow::draw(Graphics *const graphics)
2057
{
2058
    BLOCK_START("ChatWindow::draw")
2059

2
    if (!mAutoHide || mHaveMouse)
2060
    {
2061
        GLDEBUG_START("ChatWindow::draw");
2062
2
        Window::draw(graphics);
2063
        GLDEBUG_END();
2064
    }
2065
    BLOCK_END("ChatWindow::draw")
2066
2
}
2067
2068
void ChatWindow::safeDraw(Graphics *const graphics)
2069
{
2070
    BLOCK_START("ChatWindow::draw")
2071
    if (!mAutoHide || mHaveMouse)
2072
    {
2073
        GLDEBUG_START("ChatWindow::draw");
2074
        Window::safeDraw(graphics);
2075
        GLDEBUG_END();
2076
    }
2077
    BLOCK_END("ChatWindow::draw")
2078
}
2079
2080
4
void ChatWindow::updateVisibility()
2081
{
2082
4
    if (gui == nullptr)
2083
        return;
2084
2085
4
    int mouseX = 0;
2086
4
    int mouseY = 0;
2087
4
    int x = 0;
2088
4
    int y = 0;
2089
4
    Gui::getMouseState(mouseX, mouseY);
2090
4
    getAbsolutePosition(x, y);
2091
4
    if (mChatInput->isVisible())
2092
    {
2093
        mHaveMouse = true;
2094
    }
2095
    else
2096
    {
2097
16
        mHaveMouse = mouseX >= x && mouseX <= x + getWidth()
2098

8
            && mouseY >= y && mouseY <= y + getHeight();
2099
    }
2100
}
2101
2102
void ChatWindow::unHideWindow()
2103
{
2104
    mHaveMouse = true;
2105
}
2106
2107
#ifdef USE_PROFILER
2108
void ChatWindow::logicChildren()
2109
{
2110
    BLOCK_START("ChatWindow::logicChildren")
2111
    BasicContainer::logicChildren();
2112
    BLOCK_END("ChatWindow::logicChildren")
2113
}
2114
#endif  // USE_PROFILER
2115
2116
void ChatWindow::addGlobalMessage(const std::string &line)
2117
{
2118
    if (debugChatTab != nullptr &&
2119
        findI(line, mGlobalsFilter) != std::string::npos)
2120
    {
2121
        debugChatTab->chatLog(line, ChatMsgType::BY_OTHER);
2122
    }
2123
    else
2124
    {
2125
        localChatTab->chatLog(line, ChatMsgType::BY_GM);
2126
    }
2127
}
2128
2129
bool ChatWindow::isTabPresent(const ChatTab *const tab) const
2130
{
2131
    return mChatTabs->isTabPresent(tab);
2132
}
2133
2134
void ChatWindow::debugMessage(const std::string &msg)
2135
{
2136
    if (debugChatTab != nullptr)
2137
        debugChatTab->chatLog(msg, ChatMsgType::BY_SERVER);
2138
}
2139
2140
void ChatWindow::showGMTab()
2141
{
2142
    if ((gmChatTab == nullptr) &&
2143
        config.getBoolValue("enableGmTab") &&
2144
        localPlayer->getGroupId() >= paths.getIntValue("gmTabMinimalLevel"))
2145
    {
2146
        addSpecialChannelTab(GM_CHANNEL, false);
2147
    }
2148
}
2149
2150
void ChatWindow::toggleChatFocus()
2151
{
2152
    if (mChatInput->isVisible() && mChatInput->isFocused())
2153
        mChatInput->processVisible(Visible_false);
2154
    else
2155
        requestChatFocus();
2156
}
2157
2158
void ChatWindow::joinRoom(const bool isJoin)
2159
{
2160
    Tab *const tab = mChatTabs->getTabByIndex(0);
2161
    if (tab != nullptr)
2162
    {
2163
        std::string name;
2164
        if (isJoin)
2165
        {
2166
            name = PlayerInfo::getRoomName();
2167
        }
2168
        else
2169
        {
2170
            // TRANSLATORS: chat tab name
2171
            name = _("General");
2172
        }
2173
        tab->setCaption(name);
2174
    }
2175
}
2176
2177
void ChatWindow::scheduleDelete()
2178
{
2179
    DebugMessageListener::removeListener(this);
2180
    Window::scheduleDelete();
2181

6
}