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

2
    mItemLinkHandler(new ItemLinkHandler),
100


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

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

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

2
    mChatButton(new Button(this, ":)", "openemote", BUTTON_SKIN, this)),
114
    mAwayLog(),
115
    mHighlights(),
116
    mGlobalsFilter(),
117

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

4
    mEmoteButtonSpacing(mSkin != nullptr ?
119

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

4
    mEmoteButtonY(mSkin != nullptr ?
121

8
                  mSkin->getOption("emoteButtonY", -2) : -2),
122
    mChatHistoryIndex(0),
123

8
    mReturnToggles(config.getBoolValue("ReturnToggles")),
124
    mGMLoaded(false),
125
    mHaveMouse(false),
126

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

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

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

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



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

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

8
    mColorPicker->setVisible(fromBool(config.getBoolValue(
195
2
        "showChatColorsList"), Visible));
196
2
    updateTabsMargin();
197
198
2
    fillCommands();
199

4
    if ((localPlayer != nullptr) && localPlayer->isGM())
200
        loadGMCommands();
201
2
    initTradeFilter();
202
2
    loadCustomList();
203
2
    parseHighlights();
204
2
    parseGlobalsFilter();
205
206

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

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

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

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

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

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


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

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

4
         iter_end = mChannels.end(); iter != iter_end && num < 50; ++iter)
1905
    {
1906
        if (iter->second == nullptr)
1907
            return;
1908
        if (!saveTab(num, iter->second))
1909
            continue;
1910
        num ++;
1911
    }
1912
1913
6
    for (TabMap::const_iterator iter = mWhispers.begin(),
1914

4
         iter_end = mWhispers.end(); iter != iter_end && num < 50; ++iter)
1915
    {
1916
        if (iter->second == nullptr)
1917
            return;
1918
        if (!saveTab(num, iter->second))
1919
            continue;
1920
        num ++;
1921
    }
1922
1923
202
    while (num < 50)
1924
    {
1925

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

300
        serverConfig.deleteKey("chatWhisperFlags" + toString(num));
1927
100
        num ++;
1928
    }
1929
}
1930
1931
bool ChatWindow::saveTab(const int num,
1932
                         const ChatTab *const tab) const
1933
{
1934
    if (tab == nullptr)
1935
        return false;
1936
1937
    serverConfig.setValue("chatWhisper" + toString(num),
1938
        tab->getChannelName());
1939
1940
    serverConfig.setValue("chatWhisperFlags" + toString(num),
1941
        CAST_S32(tab->getAllowHighlight())
1942
        + (2 * CAST_S32(tab->getRemoveNames()))
1943
        + (4 * CAST_S32(tab->getNoAway())));
1944
1945
    return true;
1946
}
1947
1948
std::string ChatWindow::doReplace(const std::string &msg)
1949
{
1950
    std::string str = msg;
1951
    replaceSpecialChars(str);
1952
    return str;
1953
}
1954
1955
2
void ChatWindow::loadCustomList()
1956
{
1957
4
    std::ifstream listFile;
1958
    struct stat statbuf;
1959
1960
    std::string listName = settings.serverConfigDir
1961
4
        + "/customwords.txt";
1962
1963

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

1
    if (!mAutoHide || mHaveMouse)
2085
    {
2086
        GLDEBUG_START("ChatWindow::draw");
2087
1
        Window::draw(graphics);
2088
        GLDEBUG_END();
2089
    }
2090
    BLOCK_END("ChatWindow::draw")
2091
1
}
2092
2093
void ChatWindow::safeDraw(Graphics *const graphics)
2094
{
2095
    BLOCK_START("ChatWindow::draw")
2096
    if (!mAutoHide || mHaveMouse)
2097
    {
2098
        GLDEBUG_START("ChatWindow::draw");
2099
        Window::safeDraw(graphics);
2100
        GLDEBUG_END();
2101
    }
2102
    BLOCK_END("ChatWindow::draw")
2103
}
2104
2105
2
void ChatWindow::updateVisibility()
2106
{
2107
2
    if (gui == nullptr)
2108
        return;
2109
2110
2
    int mouseX = 0;
2111
2
    int mouseY = 0;
2112
2
    int x = 0;
2113
2
    int y = 0;
2114
2
    Gui::getMouseState(mouseX, mouseY);
2115
2
    getAbsolutePosition(x, y);
2116
4
    if (mChatInput->isVisible())
2117
    {
2118
        mHaveMouse = true;
2119
    }
2120
    else
2121
    {
2122
8
        mHaveMouse = mouseX >= x && mouseX <= x + getWidth()
2123

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

3
}