GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/tabs/chat/chattab.cpp Lines: 26 241 10.8 %
Date: 2021-03-17 Branches: 20 444 4.5 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "gui/widgets/tabs/chat/chattab.h"
25
26
#include "chatlogger.h"
27
#include "configuration.h"
28
#include "settings.h"
29
#include "soundmanager.h"
30
31
#include "being/localplayer.h"
32
33
#include "const/sound.h"
34
35
#include "gui/chatlog.h"
36
#include "gui/windowmanager.h"
37
38
#include "gui/windows/chatwindow.h"
39
#include "gui/windows/helpwindow.h"
40
41
#include "gui/widgets/scrollarea.h"
42
#include "gui/widgets/itemlinkhandler.h"
43
#include "gui/widgets/tabbedarea.h"
44
45
#include "input/inputmanager.h"
46
47
#include "net/chathandler.h"
48
#include "net/net.h"
49
50
#include "utils/chatutils.h"
51
#include "utils/delete2.h"
52
#include "utils/gettext.h"
53
54
#ifdef WIN32
55
#include <sys/time.h>
56
#endif  // WIN32
57
58
#include <sstream>
59
60
#include "debug.h"
61
62
ChatTab *localChatTab = nullptr;
63
ChatTab *debugChatTab = nullptr;
64
65
static const unsigned int MAX_WORD_SIZE = 50;
66
67
1
ChatTab::ChatTab(const Widget2 *const widget,
68
                 const std::string &name,
69
                 const std::string &channel,
70
                 const std::string &logName,
71
1
                 const ChatTabTypeT &type) :
72
    Tab(widget),
73
    mTextOutput(new BrowserBox(this, Opaque_true,
74

1
       "browserbox.xml")),
75
    mScrollArea(new ScrollArea(this,
76

2
        mTextOutput, Opaque_false, std::string())),
77
    mChannelName(channel),
78
    mLogName(logName),
79
1
    mType(type),
80
    mAllowHightlight(true),
81
    mRemoveNames(false),
82
    mNoAway(false),
83
10
    mShowOnline(false)
84
{
85
1
    setCaption(name);
86
87
2
    mTextOutput->setOpaque(Opaque_false);
88

5
    mTextOutput->setMaxRow(config.getIntValue("ChatLogLength"));
89
1
    if (chatWindow != nullptr)
90
1
        mTextOutput->setLinkHandler(chatWindow->mItemLinkHandler);
91
2
    mTextOutput->setAlwaysUpdate(false);
92
93
1
    mScrollArea->setScrollPolicy(ScrollArea::SHOW_NEVER,
94
1
        ScrollArea::SHOW_ALWAYS);
95
1
    mScrollArea->setScrollAmount(0, 1);
96
97
1
    if (chatWindow != nullptr)
98
1
        chatWindow->addTab(this);
99
1
    mTextOutput->updateSize(true);
100
1
}
101
102
5
ChatTab::~ChatTab()
103
{
104
1
    if (chatWindow != nullptr)
105
1
        chatWindow->removeTab(this);
106
107
1
    delete2(mTextOutput)
108
1
    delete2(mScrollArea)
109
2
}
110
111
void ChatTab::chatLog(std::string line,
112
                      ChatMsgTypeT own,
113
                      const IgnoreRecord ignoreRecord,
114
                      const TryRemoveColors tryRemoveColors)
115
{
116
    // Trim whitespace
117
    trim(line);
118
119
    if (line.empty())
120
        return;
121
122
    if (tryRemoveColors == TryRemoveColors_true &&
123
        own == ChatMsgType::BY_OTHER &&
124
        config.getBoolValue("removeColors"))
125
    {
126
        line = removeColors(line);
127
        if (line.empty())
128
            return;
129
    }
130
131
    const unsigned lineLim = config.getIntValue("chatMaxCharLimit");
132
    if (lineLim > 0 && line.length() > lineLim)
133
        line = line.substr(0, lineLim);
134
135
    if (line.empty())
136
        return;
137
138
    CHATLOG tmp;
139
    tmp.own = own;
140
    tmp.nick.clear();
141
    tmp.text = line;
142
143
    const size_t pos = line.find(" : ");
144
    if (pos != std::string::npos)
145
    {
146
        if (line.length() <= pos + 3)
147
            return;
148
149
        tmp.nick = line.substr(0, pos);
150
        tmp.text = line.substr(pos + 3);
151
    }
152
    else
153
    {
154
        // Fix the owner of welcome message.
155
        if (line.length() > 7 && line.substr(0, 7) == "Welcome")
156
            own = ChatMsgType::BY_SERVER;
157
    }
158
159
    // *implements actions in a backwards compatible way*
160
    if ((own == ChatMsgType::BY_PLAYER || own == ChatMsgType::BY_OTHER) &&
161
        tmp.text.at(0) == '*' &&
162
        tmp.text.at(tmp.text.length()-1) == '*')
163
    {
164
        tmp.text[0] = ' ';
165
        tmp.text.erase(tmp.text.length() - 1);
166
        own = ChatMsgType::ACT_IS;
167
    }
168
169
    std::string lineColor("##C");
170
    switch (own)
171
    {
172
        case ChatMsgType::BY_GM:
173
            if (tmp.nick.empty())
174
            {
175
                // TRANSLATORS: chat message
176
                tmp.nick = std::string(_("Global announcement:")).append(" ");
177
                lineColor = "##G";
178
            }
179
            else
180
            {
181
                // TRANSLATORS: chat message
182
                tmp.nick = strprintf(_("Global announcement from %s:"),
183
                                     tmp.nick.c_str()).append(" ");
184
                lineColor = "##g";  // Equiv. to BrowserBox::RED
185
            }
186
            break;
187
        case ChatMsgType::BY_PLAYER:
188
            tmp.nick.append(": ");
189
            lineColor = "##Y";
190
            break;
191
        case ChatMsgType::BY_OTHER:
192
        case ChatMsgType::BY_UNKNOWN:
193
            tmp.nick.append(": ");
194
            lineColor = "##C";
195
            break;
196
        case ChatMsgType::BY_SERVER:
197
            // TRANSLATORS: chat message
198
            tmp.nick.clear();
199
            tmp.text = line;
200
            lineColor = "##S";
201
            break;
202
        case ChatMsgType::BY_CHANNEL:
203
            tmp.nick.clear();
204
            lineColor = "##2";  // Equiv. to BrowserBox::GREEN
205
            break;
206
        case ChatMsgType::ACT_WHISPER:
207
            // TRANSLATORS: chat message
208
            tmp.nick = strprintf(_("%s whispers: %s"), tmp.nick.c_str(), "");
209
            lineColor = "##W";
210
            break;
211
        case ChatMsgType::ACT_IS:
212
            lineColor = "##I";
213
            break;
214
        case ChatMsgType::BY_LOGGER:
215
            tmp.nick.clear();
216
            tmp.text = line;
217
            lineColor = "##L";
218
            break;
219
        default:
220
            break;
221
    }
222
223
    if (tmp.nick == ": ")
224
    {
225
        tmp.nick.clear();
226
        lineColor = "##S";
227
    }
228
229
    // if configured, move magic messages log to debug chat tab
230
    if (Net::getNetworkType() == ServerType::TMWATHENA
231
        && (localChatTab != nullptr) && this == localChatTab
232
        && ((config.getBoolValue("showMagicInDebug")
233
        && own == ChatMsgType::BY_PLAYER
234
        && tmp.text.length() > 1
235
        && tmp.text.at(0) == '#'
236
        && tmp.text.at(1) != '#')
237
        || (config.getBoolValue("serverMsgInDebug")
238
        && (own == ChatMsgType::BY_SERVER
239
        || tmp.nick.empty()))))
240
    {
241
        if (debugChatTab != nullptr)
242
            debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
243
        return;
244
    }
245
246
    // Get the current system time
247
    time_t t;
248
    time(&t);
249
250
    if (config.getBoolValue("useLocalTime"))
251
    {
252
        const tm *const timeInfo = localtime(&t);
253
        if (timeInfo != nullptr)
254
        {
255
            line = strprintf("%s[%02d:%02d] %s%s", lineColor.c_str(),
256
                timeInfo->tm_hour, timeInfo->tm_min, tmp.nick.c_str(),
257
                tmp.text.c_str());
258
        }
259
        else
260
        {
261
            line = strprintf("%s %s%s", lineColor.c_str(),
262
                tmp.nick.c_str(), tmp.text.c_str());
263
        }
264
    }
265
    else
266
    {
267
        // Format the time string properly
268
        std::stringstream timeStr;
269
        timeStr << "[" << ((((t / 60) / 60) % 24 < 10) ? "0" : "")
270
            << CAST_S32(((t / 60) / 60) % 24)
271
            << ":" << (((t / 60) % 60 < 10) ? "0" : "")
272
            << CAST_S32((t / 60) % 60)
273
            << "] ";
274
        line = std::string(lineColor).append(timeStr.str()).append(
275
            tmp.nick).append(tmp.text);
276
    }
277
278
    if (config.getBoolValue("enableChatLog"))
279
        saveToLogFile(line);
280
281
    mTextOutput->setMaxRow(config.getIntValue("chatMaxLinesLimit"));
282
283
    // We look if the Vertical Scroll Bar is set at the max before
284
    // adding a row, otherwise the max will always be a row higher
285
    // at comparison.
286
    if (mScrollArea->getVerticalScrollAmount() + 2 >=
287
        mScrollArea->getVerticalMaxScroll())
288
    {
289
        addRow(line);
290
        mScrollArea->setVerticalScrollAmount(
291
            mScrollArea->getVerticalMaxScroll());
292
    }
293
    else
294
    {
295
        addRow(line);
296
    }
297
298
    if ((chatWindow != nullptr) && this == localChatTab)
299
        chatWindow->addToAwayLog(line);
300
301
    mScrollArea->logic();
302
    if (own != ChatMsgType::BY_PLAYER)
303
    {
304
        if (own == ChatMsgType::BY_SERVER &&
305
            (getType() == ChatTabType::PARTY ||
306
            getType() == ChatTabType::CHANNEL ||
307
            getType() == ChatTabType::GUILD))
308
        {
309
            return;
310
        }
311
312
        const TabbedArea *const tabArea = getTabbedArea();
313
        if (tabArea == nullptr)
314
            return;
315
316
        const bool notFocused = WindowManager::getIsMinimized() ||
317
            (!settings.mouseFocused &&
318
            settings.inputFocused == KeyboardFocus::Unfocused);
319
320
        if (this != tabArea->getSelectedTab() || notFocused)
321
        {
322
            if (getFlash() == 0)
323
            {
324
                if (chatWindow != nullptr &&
325
                    chatWindow->findHighlight(tmp.text))
326
                {
327
                    setFlash(2);
328
                    soundManager.playGuiSound(SOUND_HIGHLIGHT);
329
                }
330
                else
331
                {
332
                    setFlash(1);
333
                }
334
            }
335
            else if (getFlash() == 2)
336
            {
337
                if (chatWindow != nullptr &&
338
                    chatWindow->findHighlight(tmp.text))
339
                {
340
                    soundManager.playGuiSound(SOUND_HIGHLIGHT);
341
                }
342
            }
343
        }
344
345
        if ((getAllowHighlight() ||
346
            own == ChatMsgType::BY_GM) &&
347
            (this != tabArea->getSelectedTab() ||
348
            notFocused))
349
        {
350
            if (own == ChatMsgType::BY_GM)
351
            {
352
                if (chatWindow != nullptr)
353
                    chatWindow->unHideWindow();
354
                soundManager.playGuiSound(SOUND_GLOBAL);
355
            }
356
            else if (own != ChatMsgType::BY_SERVER)
357
            {
358
                if (chatWindow != nullptr)
359
                    chatWindow->unHideWindow();
360
                playNewMessageSound();
361
            }
362
            WindowManager::newChatMessage();
363
        }
364
    }
365
}
366
367
void ChatTab::chatLog(const std::string &nick, std::string msg)
368
{
369
    if (localPlayer == nullptr)
370
        return;
371
372
    const ChatMsgTypeT byWho = (nick == localPlayer->getName()
373
        ? ChatMsgType::BY_PLAYER : ChatMsgType::BY_OTHER);
374
    if (byWho == ChatMsgType::BY_OTHER && config.getBoolValue("removeColors"))
375
        msg = removeColors(msg);
376
    chatLog(std::string(nick).append(" : ").append(msg),
377
        byWho,
378
        IgnoreRecord_false,
379
        TryRemoveColors_false);
380
}
381
382
void ChatTab::chatInput(const std::string &message)
383
{
384
    std::string msg = message;
385
    trim(msg);
386
387
    if (msg.empty())
388
        return;
389
390
    replaceItemLinks(msg);
391
    replaceVars(msg);
392
393
    switch (msg[0])
394
    {
395
        case '/':
396
            handleCommandStr(std::string(msg, 1));
397
            break;
398
        case '?':
399
            if (msg.size() > 1 &&
400
                msg[1] != '!' &&
401
                msg[1] != '?' &&
402
                msg[1] != '.' &&
403
                msg[1] != ' ' &&
404
                msg[1] != ',')
405
            {
406
                handleHelp(std::string(msg, 1));
407
            }
408
            else
409
            {
410
                handleInput(msg);
411
            }
412
            break;
413
        default:
414
            handleInput(msg);
415
            break;
416
    }
417
}
418
419
void ChatTab::scroll(const int amount)
420
{
421
    const int range = mScrollArea->getHeight() / 8 * amount;
422
    Rect scr;
423
    scr.y = mScrollArea->getVerticalScrollAmount() + range;
424
    scr.height = abs(range);
425
    mTextOutput->showPart(scr);
426
}
427
428
void ChatTab::clearText()
429
{
430
    mTextOutput->clearRows();
431
}
432
433
void ChatTab::handleInput(const std::string &msg)
434
{
435
    if (chatHandler)
436
    {
437
        chatHandler->talk(ChatWindow::doReplace(msg));
438
    }
439
}
440
441
void ChatTab::handleCommandStr(const std::string &msg)
442
{
443
    const size_t pos = msg.find(' ');
444
    const std::string type(msg, 0, pos);
445
    std::string args(msg, pos == std::string::npos ? msg.size() : pos + 1);
446
447
    args = trim(args);
448
    if (!handleCommand(type, args))
449
        inputManager.executeChatCommand(type, args, this);
450
}
451
452
void ChatTab::handleHelp(const std::string &msg)
453
{
454
    if (helpWindow != nullptr)
455
    {
456
        helpWindow->search(msg);
457
        helpWindow->requestMoveToTop();
458
    }
459
}
460
461
bool ChatTab::handleCommands(const std::string &type, const std::string &args)
462
{
463
    // need split to commands and call each
464
465
    return handleCommand(type, args);
466
}
467
468
void ChatTab::saveToLogFile(const std::string &msg) const
469
{
470
    if (chatLogger != nullptr)
471
    {
472
        if (getType() == ChatTabType::INPUT)
473
        {
474
            chatLogger->log(msg);
475
        }
476
        else if (getType() == ChatTabType::DEBUG)
477
        {
478
            if (config.getBoolValue("enableDebugLog"))
479
                chatLogger->log("#Debug", msg);
480
        }
481
        else if (!mLogName.empty())
482
        {
483
            chatLogger->log(mLogName, msg);
484
        }
485
    }
486
}
487
488
void ChatTab::addRow(std::string &line)
489
{
490
    if (line.find("[@@http") == std::string::npos)
491
    {
492
        size_t idx = 0;
493
        for (size_t f = 0; f < line.length(); f++)
494
        {
495
            if (line.at(f) == ' ')
496
            {
497
                idx = f;
498
            }
499
            else if (f - idx > MAX_WORD_SIZE)
500
            {
501
                line.insert(f, " ");
502
                idx = f;
503
            }
504
        }
505
    }
506
    mTextOutput->addRow(line,
507
        false);
508
}
509
510
void ChatTab::loadFromLogFile(const std::string &name)
511
{
512
    if (chatLogger != nullptr)
513
    {
514
        std::list<std::string> list;
515
        chatLogger->loadLast(name, list, 5);
516
        std::list<std::string>::const_iterator i = list.begin();
517
        while (i != list.end())
518
        {
519
            std::string line("##o" + *i);
520
            addRow(line);
521
            ++i;
522
        }
523
    }
524
}
525
526
void ChatTab::addNewRow(std::string &line)
527
{
528
    if (mScrollArea->getVerticalScrollAmount() >=
529
        mScrollArea->getVerticalMaxScroll())
530
    {
531
        addRow(line);
532
        mScrollArea->setVerticalScrollAmount(
533
            mScrollArea->getVerticalMaxScroll());
534
    }
535
    else
536
    {
537
        addRow(line);
538
    }
539
    mScrollArea->logic();
540
}
541
542
void ChatTab::playNewMessageSound() const
543
{
544
    soundManager.playGuiSound(SOUND_WHISPER);
545
}
546
547
void ChatTab::showOnline(const std::string &nick,
548
                         const Online online)
549
{
550
    if (!mShowOnline)
551
        return;
552
553
    if (online == Online_true)
554
    {
555
        // TRANSLATORS: chat message
556
        chatLog(strprintf(_("%s is now Online."), nick.c_str()),
557
            ChatMsgType::BY_SERVER,
558
            IgnoreRecord_false,
559
            TryRemoveColors_true);
560
    }
561
    else
562
    {
563
        // TRANSLATORS: chat message
564
        chatLog(strprintf(_("%s is now Offline."), nick.c_str()),
565
            ChatMsgType::BY_SERVER,
566
            IgnoreRecord_false,
567
            TryRemoveColors_true);
568
    }
569

3
}