1 /*
2 * The ManaPlus Client
3 * Copyright (C) 2004-2009 The Mana World Development Team
4 * Copyright (C) 2009-2010 The Mana Developers
5 * Copyright (C) 2011-2019 The ManaPlus Developers
6 * Copyright (C) 2019-2021 Andrei Karas
7 *
8 * This file is part of The ManaPlus Client.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "gui/windows/chatwindow.h"
25
26 #include "actormanager.h"
27 #include "game.h"
28 #include "guild.h"
29 #include "party.h"
30 #include "settings.h"
31 #include "spellmanager.h"
32
33 #include "being/localplayer.h"
34 #include "being/playerinfo.h"
35 #include "being/playerrelations.h"
36
37 #include "const/gui/chat.h"
38
39 #include "fs/virtfs/tools.h"
40
41 #include "input/inputmanager.h"
42
43 #include "gui/focushandler.h"
44 #include "gui/gui.h"
45 #include "gui/skin.h"
46 #include "gui/viewport.h"
47
48 #include "gui/models/colorlistmodel.h"
49
50 #include "gui/popups/popupmenu.h"
51
52 #include "gui/windows/setupwindow.h"
53 #include "gui/windows/whoisonline.h"
54
55 #include "gui/widgets/button.h"
56 #include "gui/widgets/chatinput.h"
57 #include "gui/widgets/createwidget.h"
58 #include "gui/widgets/dropdown.h"
59 #include "gui/widgets/itemlinkhandler.h"
60 #include "gui/widgets/scrollarea.h"
61 #include "gui/widgets/tabbedarea.h"
62
63 #include "gui/widgets/tabs/chat/battletab.h"
64 #include "gui/widgets/tabs/chat/channeltab.h"
65 #include "gui/widgets/tabs/chat/gmtab.h"
66 #include "gui/widgets/tabs/chat/langtab.h"
67 #include "gui/widgets/tabs/chat/tradetab.h"
68 #include "gui/widgets/tabs/chat/whispertab.h"
69
70 #include "render/opengl/opengldebug.h"
71
72 #include "resources/db/textdb.h"
73
74 #include "net/chathandler.h"
75 #include "net/net.h"
76
77 #include "utils/copynpaste.h"
78 #include "utils/delete2.h"
79 #include "utils/foreach.h"
80
81 #include "utils/translation/podict.h"
82
83 #include <sys/stat.h>
84
85 #include <fstream>
86 #include <sstream>
87
88 #ifdef __SWITCH__
89 #include "enums/input/keyvalue.h"
90 #endif
91
92 #include "debug.h"
93
94 ChatWindow *chatWindow = nullptr;
95
96 static const char *const ACTION_COLOR_PICKER = "color picker";
97
ChatWindow(const std::string & name)98 ChatWindow::ChatWindow(const std::string &name) :
99 // TRANSLATORS: chat window name
100 Window(_("Chat"), Modal_false, nullptr, "chat.xml"),
101 ActionListener(),
102 KeyListener(),
103 AttributeListener(),
104 mItemLinkHandler(new ItemLinkHandler),
105 mChatTabs(CREATEWIDGETR(TabbedArea, this)),
106 mChatInput(new ChatInput(this)),
107 mRainbowColor(0U),
108 mWhispers(),
109 mChannels(),
110 mHistory(),
111 mCurHist(),
112 mCommands(),
113 mCustomWords(),
114 mTradeFilter(),
115 mColorListModel(new ColorListModel),
116 mColorPicker(new DropDown(this, mColorListModel,
117 false, Modal_false, nullptr, std::string())),
118 mChatButton(new Button(this, ":)", "openemote", BUTTON_SKIN, this)),
119 mAwayLog(),
120 mHighlights(),
121 mGlobalsFilter(),
122 mChatColor(config.getIntValue("chatColor")),
123 mEmoteButtonSpacing(mSkin != nullptr ?
124 mSkin->getOption("emoteButtonSpacing", 2) : 2),
125 mEmoteButtonY(mSkin != nullptr ?
126 mSkin->getOption("emoteButtonY", -2) : -2),
127 mChatHistoryIndex(0),
128 mReturnToggles(config.getBoolValue("ReturnToggles")),
129 mGMLoaded(false),
130 mHaveMouse(false),
131 mAutoHide(config.getBoolValue("autohideChat")),
132 mShowBattleEvents(config.getBoolValue("showBattleEvents")),
133 mShowAllLang(serverConfig.getValue("showAllLang", 0) != 0),
134 mEnableTradeFilter(config.getBoolValue("enableTradeFilter")),
135 mTmpVisible(false)
136 {
137 setWindowName(name);
138
139 if (setupWindow != nullptr)
140 setupWindow->registerWindowForReset(this);
141
142 setShowTitle(false);
143 setResizable(true);
144 setDefaultVisible(true);
145 setSaveVisible(true);
146 setStickyButtonLock(true);
147
148 int w = 600;
149 #ifdef ANDROID
150 if (mainGraphics->getWidth() < 710)
151 w = mainGraphics->getWidth() - 110;
152 if (w < 100)
153 w = 100;
154 if (mainGraphics->getHeight() < 480)
155 setDefaultSize(w, 90, ImagePosition::UPPER_LEFT, -110, -35);
156 else
157 setDefaultSize(w, 123, ImagePosition::UPPER_LEFT, -110, -35);
158 #else // ANDROID
159
160 if (mainGraphics->getWidth() < 600)
161 w = mainGraphics->getWidth() - 10;
162 if (w < 100)
163 w = 100;
164 setDefaultSize(w, 123, ImagePosition::LOWER_LEFT, 0, 0);
165 #endif // ANDROID
166
167 setMinWidth(150);
168 setMinHeight(90);
169
170 setTitleBarHeight(getPadding() + getTitlePadding());
171
172 if (emoteWindow != nullptr)
173 emoteWindow->addListeners(this);
174
175 mChatButton->adjustSize();
176
177 mChatTabs->enableScrollButtons(true);
178 mChatTabs->setFollowDownScroll(true);
179 mChatTabs->setResizeHeight(false);
180
181 mChatInput->setActionEventId("chatinput");
182 mChatInput->addActionListener(this);
183 mChatInput->setAllowSpecialActions(false);
184
185 mColorPicker->setActionEventId(ACTION_COLOR_PICKER);
186 mColorPicker->addActionListener(this);
187 mColorPicker->setSelected(mChatColor);
188
189 mItemLinkHandler->setAllowCommands(false);
190
191 loadWindowState();
192
193 mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
194 - 2 * mPadding - 8 - 16, mPadding);
195
196 // Add key listener to chat input to be able to respond to up/down
197 mChatInput->addKeyListener(this);
198 mCurHist = mHistory.end();
199 mColorPicker->setVisible(fromBool(config.getBoolValue(
200 "showChatColorsList"), Visible));
201 updateTabsMargin();
202
203 fillCommands();
204 if ((localPlayer != nullptr) && localPlayer->isGM())
205 loadGMCommands();
206 initTradeFilter();
207 loadCustomList();
208 parseHighlights();
209 parseGlobalsFilter();
210
211 config.addListener("autohideChat", this);
212 config.addListener("showBattleEvents", this);
213 config.addListener("globalsFilter", this);
214 config.addListener("enableTradeFilter", this);
215
216 enableVisibleSound(true);
217 }
218
~ChatWindow()219 ChatWindow::~ChatWindow()
220 {
221 config.removeListeners(this);
222 CHECKLISTENERS
223 saveState();
224 config.setValue("ReturnToggles", mReturnToggles);
225 removeAllWhispers();
226 removeAllChannels();
227 delete2(mItemLinkHandler)
228 delete2(mColorPicker)
229 delete2(mColorListModel)
230 }
231
postInit()232 void ChatWindow::postInit()
233 {
234 Window::postInit();
235 add(mChatTabs);
236 add(mChatInput);
237 add(mColorPicker);
238 add(mChatButton);
239 updateVisibility();
240 }
241
loadCommandsFile(const std::string & name)242 void ChatWindow::loadCommandsFile(const std::string &name)
243 {
244 StringVect list;
245 VirtFs::loadTextFile(name, list);
246 StringVectCIter it = list.begin();
247 const StringVectCIter it_end = list.end();
248
249 while (it != it_end)
250 {
251 const std::string str = *it;
252 if (!str.empty())
253 mCommands.push_back(str);
254 ++ it;
255 }
256 }
257
fillCommands()258 void ChatWindow::fillCommands()
259 {
260 loadCommandsFile("chatcommands.txt");
261 InputManager::addChatCommands(mCommands);
262 }
263
loadGMCommands()264 void ChatWindow::loadGMCommands()
265 {
266 if (mGMLoaded)
267 return;
268
269 loadCommandsFile("gmcommands.txt");
270 mGMLoaded = true;
271 }
272
updateTabsMargin()273 void ChatWindow::updateTabsMargin()
274 {
275 if (mColorPicker->mVisible == Visible_true)
276 mChatTabs->setRightMargin(mColorPicker->getWidth() + 16 + 8);
277 else
278 mChatTabs->setRightMargin(8);
279 }
280
adjustTabSize()281 void ChatWindow::adjustTabSize()
282 {
283 const Rect area = getChildrenArea();
284
285 const int aw = area.width;
286 const int ah = area.height;
287 const int frame = mChatInput->getFrameSize();
288 const int inputHeight = mChatInput->getHeight();
289 const bool showEmotes = config.getBoolValue("showEmotesButton");
290 int maxHeight = inputHeight;
291 if (showEmotes)
292 {
293 const int buttonHeight = mChatButton->getHeight();
294 if (buttonHeight > maxHeight)
295 maxHeight = buttonHeight;
296 }
297 const int frame2 = 2 * frame;
298 const int awFrame2 = aw - frame2;
299 int y = ah - maxHeight - frame;
300 mChatInput->setPosition(frame, y);
301 mChatTabs->setWidth(awFrame2);
302 const int height = ah - frame2 - (maxHeight + frame2);
303 if (mChatInput->mVisible == Visible_true ||
304 !config.getBoolValue("hideChatInput"))
305 {
306 mChatTabs->setHeight(height);
307 }
308 else
309 {
310 mChatTabs->setHeight(height + maxHeight);
311 }
312 updateTabsMargin();
313
314 if (showEmotes)
315 {
316 const int chatButtonSize = mChatButton->getWidth();
317 int w = awFrame2 - chatButtonSize;
318 const int x = aw - frame - chatButtonSize;
319 w -= mEmoteButtonSpacing;
320 y += mEmoteButtonY;
321 mChatInput->setWidth(w);
322 mChatButton->setVisible(mChatInput->mVisible);
323 mChatButton->setPosition(x, y);
324 }
325 else
326 {
327 mChatInput->setWidth(awFrame2);
328 mChatButton->setVisible(Visible_false);
329 }
330
331 const ChatTab *const tab = getFocused();
332 if (tab != nullptr)
333 {
334 Widget *const content = tab->mScrollArea;
335 if (content != nullptr)
336 {
337 const int contentFrame2 = 2 * content->getFrameSize();
338 content->setSize(mChatTabs->getWidth() - contentFrame2,
339 mChatTabs->getContainerHeight() - contentFrame2);
340 content->logic();
341 }
342 }
343
344 mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth()
345 - 2 * mPadding - 8 - 16, mPadding);
346
347 mChatTabs->adjustSize();
348 }
349
widgetResized(const Event & event)350 void ChatWindow::widgetResized(const Event &event)
351 {
352 Window::widgetResized(event);
353
354 adjustTabSize();
355 }
356
getFocused() const357 ChatTab *ChatWindow::getFocused() const
358 {
359 return static_cast<ChatTab*>(mChatTabs->getSelectedTab());
360 }
361
clearTab(ChatTab * const tab)362 void ChatWindow::clearTab(ChatTab *const tab)
363 {
364 if (tab != nullptr)
365 tab->clearText();
366 }
367
clearTab() const368 void ChatWindow::clearTab() const
369 {
370 clearTab(getFocused());
371 }
372
prevTab()373 void ChatWindow::prevTab()
374 {
375 if (mChatTabs == nullptr)
376 return;
377
378 int tab = mChatTabs->getSelectedTabIndex();
379
380 if (tab <= 0)
381 tab = mChatTabs->getNumberOfTabs();
382 tab--;
383
384 mChatTabs->setSelectedTabByIndex(tab);
385 }
386
nextTab()387 void ChatWindow::nextTab()
388 {
389 if (mChatTabs == nullptr)
390 return;
391
392 int tab = mChatTabs->getSelectedTabIndex();
393
394 tab++;
395 if (tab == mChatTabs->getNumberOfTabs())
396 tab = 0;
397
398 mChatTabs->setSelectedTabByIndex(tab);
399 }
400
selectTabByType(const ChatTabTypeT & type)401 void ChatWindow::selectTabByType(const ChatTabTypeT &type)
402 {
403 if (mChatTabs == nullptr)
404 return;
405
406 int sz = mChatTabs->getNumberOfTabs();
407 for (int f = 0; f < sz; f ++)
408 {
409 ChatTab *const tab = dynamic_cast<ChatTab*>(
410 mChatTabs->getTabByIndex(f));
411 if ((tab != nullptr) && tab->getType() == type)
412 {
413 mChatTabs->setSelectedTab(tab);
414 break;
415 }
416 }
417 }
418
closeTab() const419 void ChatWindow::closeTab() const
420 {
421 if (mChatTabs == nullptr)
422 return;
423
424 ChatTab *const tab = dynamic_cast<ChatTab*>(mChatTabs->getTabByIndex(
425 mChatTabs->getSelectedTabIndex()));
426 if (tab == nullptr)
427 return;
428 const ChatTabTypeT &type = tab->getType();
429 if (type == ChatTabType::WHISPER || type == ChatTabType::CHANNEL)
430 tab->handleCommand("close", "");
431 }
432
defaultTab()433 void ChatWindow::defaultTab()
434 {
435 if (mChatTabs != nullptr)
436 mChatTabs->setSelectedTabByIndex(CAST_U32(0));
437 }
438
action(const ActionEvent & event)439 void ChatWindow::action(const ActionEvent &event)
440 {
441 const std::string &eventId = event.getId();
442 if (eventId == "chatinput")
443 {
444 std::string message = mChatInput->getText();
445
446 if (!message.empty())
447 {
448 // If message different from previous, put it in the history
449 if (mHistory.empty() || message != mHistory.back())
450 mHistory.push_back(message);
451
452 // Reset history iterator
453 mCurHist = mHistory.end();
454
455 // Send the message to the server
456 chatInput(addColors(message));
457
458 // Clear the text from the chat input
459 mChatInput->setText("");
460 }
461
462 if (message.empty() || !mReturnToggles)
463 {
464 // Remove focus and hide input
465 mChatInput->unprotectFocus();
466 if (mFocusHandler != nullptr)
467 mFocusHandler->focusNone();
468
469 // If the chatWindow is shown up because you want to send a message
470 // It should hide now
471 if (mTmpVisible)
472 setVisible(Visible_false);
473 }
474 }
475 else if (eventId == "emote")
476 {
477 if (emoteWindow != nullptr)
478 {
479 const std::string str = emoteWindow->getSelectedEmote();
480 if (!str.empty())
481 {
482 addInputText(str, false);
483 emoteWindow->clearEmote();
484 }
485 }
486 }
487 else if (eventId == "openemote")
488 {
489 if (emoteWindow != nullptr)
490 {
491 if (emoteWindow->mVisible == Visible_true)
492 emoteWindow->hide();
493 else
494 emoteWindow->show();
495 }
496 }
497 else if (eventId == "color")
498 {
499 if (emoteWindow != nullptr)
500 {
501 const std::string str = emoteWindow->getSelectedColor();
502 if (!str.empty())
503 {
504 addInputText(str, false);
505 emoteWindow->clearColor();
506 }
507 }
508 }
509 else if (eventId == "font")
510 {
511 if (emoteWindow != nullptr)
512 {
513 const std::string str = emoteWindow->getSelectedFont();
514 if (!str.empty())
515 {
516 addInputText(str, false);
517 emoteWindow->clearFont();
518 }
519 }
520 }
521 else if (eventId == "text")
522 {
523 if ((emoteWindow != nullptr) && (reverseDictionary != nullptr))
524 {
525 const int idx = emoteWindow->getSelectedTextIndex();
526 if (idx >= 0)
527 {
528 const std::string str = TextDb::getByIndex(idx);
529 const std::string enStr = reverseDictionary->getStr(str);
530 addInputText(enStr, false);
531 }
532 emoteWindow->clearText();
533 }
534 }
535 else if (eventId == ACTION_COLOR_PICKER)
536 {
537 if (mColorPicker != nullptr)
538 {
539 mChatColor = mColorPicker->getSelected();
540 config.setValue("chatColor", mChatColor);
541 }
542 }
543
544 if (mColorPicker != nullptr)
545 {
546 const Visible vis = fromBool(config.getBoolValue(
547 "showChatColorsList"), Visible);
548 if (mColorPicker->mVisible != vis)
549 mColorPicker->setVisible(vis);
550 }
551 }
552
requestChatFocus()553 bool ChatWindow::requestChatFocus()
554 {
555 // Make sure chatWindow is visible
556 if (!isWindowVisible())
557 {
558 setVisible(Visible_true);
559
560 /*
561 * This is used to hide chatWindow after sending the message. There is
562 * a trick here, because setVisible will set mTmpVisible to false, you
563 * have to put this sentence *after* setVisible, not before it
564 */
565 mTmpVisible = true;
566 }
567
568 // Don't do anything else if the input is already visible and has focus
569 if (mChatInput->isVisible() && mChatInput->isFocused())
570 return false;
571
572 // Give focus to the chat input
573 mChatInput->processVisible(Visible_true);
574 unHideWindow();
575 mChatInput->requestFocus();
576 return true;
577 }
578
isInputFocused() const579 bool ChatWindow::isInputFocused() const
580 {
581 return mChatInput->isFocused();
582 }
583
removeTab(ChatTab * const tab)584 void ChatWindow::removeTab(ChatTab *const tab)
585 {
586 mChatTabs->removeTab(tab);
587 }
588
addTab(ChatTab * const tab)589 void ChatWindow::addTab(ChatTab *const tab)
590 {
591 if (tab == nullptr)
592 return;
593
594 mChatTabs->addTab(tab, tab->mScrollArea);
595 logic();
596 }
597
removeWhisper(const std::string & nick)598 void ChatWindow::removeWhisper(const std::string &nick)
599 {
600 std::string tempNick = nick;
601 toLower(tempNick);
602 mWhispers.erase(tempNick);
603 }
604
removeChannel(const std::string & name)605 void ChatWindow::removeChannel(const std::string &name)
606 {
607 std::string tempName = name;
608 toLower(tempName);
609 mChannels.erase(tempName);
610 chatHandler->partChannel(name);
611 }
612
removeAllWhispers()613 void ChatWindow::removeAllWhispers()
614 {
615 std::list<ChatTab*> tabs;
616
617 FOR_EACH (TabMap::iterator, iter, mWhispers)
618 tabs.push_back(iter->second);
619
620 for (std::list<ChatTab*>::iterator it = tabs.begin();
621 it != tabs.end(); ++it)
622 {
623 delete *it;
624 }
625
626 mWhispers.clear();
627 }
628
removeAllChannels()629 void ChatWindow::removeAllChannels()
630 {
631 std::list<ChatTab*> tabs;
632
633 FOR_EACH (ChannelMap::iterator, iter, mChannels)
634 tabs.push_back(iter->second);
635
636 for (std::list<ChatTab*>::iterator it = tabs.begin();
637 it != tabs.end(); ++it)
638 {
639 delete *it;
640 }
641
642 mChannels.clear();
643 }
644
ignoreAllWhispers()645 void ChatWindow::ignoreAllWhispers()
646 {
647 for (TabMap::iterator iter = mWhispers.begin();
648 iter != mWhispers.end();
649 ++ iter)
650 {
651 WhisperTab *const tab = iter->second;
652 if (tab != nullptr)
653 {
654 if (playerRelations.getRelation(tab->getNick())
655 != Relation::IGNORED)
656 {
657 playerRelations.setRelation(tab->getNick(),
658 Relation::IGNORED);
659 }
660 tab->handleCommand("close", "");
661 }
662 }
663 }
664
chatInput(const std::string & message) const665 void ChatWindow::chatInput(const std::string &message) const
666 {
667 ChatTab *tab = nullptr;
668 std::string msg = message;
669 trim(msg);
670
671 if (config.getBoolValue("allowCommandsInChatTabs")
672 && msg.length() > 1
673 && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@')
674 && (localChatTab != nullptr))
675 {
676 tab = localChatTab;
677 }
678 else
679 {
680 tab = getFocused();
681 if (tab == nullptr)
682 tab = localChatTab;
683 }
684 if (tab != nullptr)
685 tab->chatInput(msg);
686 Game *const game = Game::instance();
687 if (game != nullptr)
688 game->setValidSpeed();
689 }
690
localChatInput(const std::string & msg) const691 void ChatWindow::localChatInput(const std::string &msg) const
692 {
693 if (localChatTab != nullptr)
694 localChatTab->chatInput(msg);
695 else
696 chatInput(msg);
697 }
698
doPresent() const699 void ChatWindow::doPresent() const
700 {
701 if (actorManager == nullptr)
702 return;
703
704 const ActorSprites &actors = actorManager->getAll();
705 std::string response;
706 int playercount = 0;
707
708 FOR_EACH (ActorSpritesIterator, it, actors)
709 {
710 if ((*it)->getType() == ActorType::Player)
711 {
712 if (!response.empty())
713 response.append(", ");
714 response.append(static_cast<Being*>(*it)->getName());
715 playercount ++;
716 }
717 }
718
719 ChatTab *const tab = getFocused();
720 if (tab != nullptr)
721 {
722 const std::string log = strprintf(
723 // TRANSLATORS: chat message
724 _("Present: %s; %d players are present."),
725 response.c_str(), playercount);
726 tab->chatLog(log,
727 ChatMsgType::BY_SERVER,
728 IgnoreRecord_false,
729 TryRemoveColors_true);
730 }
731 }
732
scroll(const int amount) const733 void ChatWindow::scroll(const int amount) const
734 {
735 if (!isWindowVisible())
736 return;
737
738 ChatTab *const tab = getFocused();
739 if (tab != nullptr)
740 tab->scroll(amount);
741 }
742
mousePressed(MouseEvent & event)743 void ChatWindow::mousePressed(MouseEvent &event)
744 {
745 if (event.isConsumed())
746 return;
747
748 if (event.getButton() == MouseButton::RIGHT)
749 {
750 if (popupMenu != nullptr)
751 {
752 ChatTab *const cTab = dynamic_cast<ChatTab*>(
753 mChatTabs->getSelectedTab());
754 if (cTab != nullptr)
755 {
756 event.consume();
757 if (inputManager.isActionActive(InputAction::CHAT_MOD))
758 {
759 inputManager.executeChatCommand(
760 InputAction::CLOSE_CHAT_TAB,
761 std::string(), cTab);
762 }
763 else
764 {
765 popupMenu->showChatPopup(viewport->mMouseX,
766 viewport->mMouseY,
767 cTab);
768 }
769 }
770 }
771 }
772
773 Window::mousePressed(event);
774
775 if (event.isConsumed())
776 return;
777
778 if (event.getButton() == MouseButton::LEFT)
779 {
780 const int clicks = event.getClickCount();
781 if (clicks == 2)
782 {
783 toggleChatFocus();
784 if (gui != nullptr)
785 gui->resetClickCount();
786 }
787 else if (clicks == 1)
788 {
789 const ChatTab *const tab = getFocused();
790 if (tab != nullptr)
791 mMoved = !isResizeAllowed(event);
792 }
793 }
794
795 mDragOffsetX = event.getX();
796 mDragOffsetY = event.getY();
797 }
798
mouseDragged(MouseEvent & event)799 void ChatWindow::mouseDragged(MouseEvent &event)
800 {
801 Window::mouseDragged(event);
802
803 if (event.isConsumed())
804 return;
805
806 if (canMove() && isMovable() && mMoved)
807 {
808 int newX = std::max(0, getX() + event.getX() - mDragOffsetX);
809 int newY = std::max(0, getY() + event.getY() - mDragOffsetY);
810 newX = std::min(mainGraphics->mWidth - getWidth(), newX);
811 newY = std::min(mainGraphics->mHeight - getHeight(), newY);
812 setPosition(newX, newY);
813 }
814 }
815
816 #define ifKey(key, str) \
817 else if (actionId == (key)) \
818 { \
819 temp = str; \
820 }
821
keyPressed(KeyEvent & event)822 void ChatWindow::keyPressed(KeyEvent &event)
823 {
824 const InputActionT actionId = event.getActionId();
825 std::string temp;
826 #ifdef __SWITCH__
827 if (event.getKey().getValue() == KeyValue::TEXTINPUT)
828 {
829 action(ActionEvent(this, "chatinput"));
830 return;
831 }
832 else
833 #endif
834 if (actionId == InputAction::GUI_DOWN)
835 {
836 if (mCurHist != mHistory.end())
837 {
838 // Move forward through the history
839 const HistoryIterator prevHist = mCurHist++;
840 addCurrentToHistory();
841
842 if (mCurHist != mHistory.end())
843 {
844 mChatInput->setText(*mCurHist);
845 mChatInput->setCaretPosition(CAST_U32(
846 mChatInput->getText().length()));
847 }
848 else
849 {
850 mChatInput->setText("");
851 mCurHist = prevHist;
852 }
853 }
854 else if (!mChatInput->getText().empty())
855 {
856 if (addCurrentToHistory())
857 mCurHist = mHistory.end();
858 mChatInput->setText("");
859 }
860 }
861 else if (actionId == InputAction::GUI_UP &&
862 mCurHist != mHistory.begin() &&
863 !mHistory.empty())
864 {
865 // Move backward through the history
866 --mCurHist;
867 addCurrentToHistory();
868 mChatInput->setText(*mCurHist);
869 mChatInput->setCaretPosition(CAST_U32(
870 mChatInput->getText().length()));
871 }
872 else if (actionId == InputAction::GUI_INSERT &&
873 !mChatInput->getText().empty())
874 {
875 const std::string str = mChatInput->getText();
876 // Add the current message to the history and clear the text
877 if (mHistory.empty() || str != mHistory.back())
878 mHistory.push_back(str);
879 mCurHist = mHistory.end();
880 mChatInput->setText(std::string());
881 }
882 else if (actionId == InputAction::GUI_TAB &&
883 !mChatInput->getText().empty())
884 {
885 autoComplete();
886 return;
887 }
888 else if (actionId == InputAction::GUI_CANCEL &&
889 mChatInput->mVisible == Visible_true)
890 {
891 mChatInput->processVisible(Visible_false);
892 }
893 else if (actionId == InputAction::CHAT_PREV_HISTORY &&
894 mChatInput->mVisible == Visible_true)
895 {
896 const ChatTab *const tab = getFocused();
897 if ((tab != nullptr) && tab->hasRows())
898 {
899 if (mChatHistoryIndex == 0U)
900 {
901 mChatHistoryIndex = CAST_U32(
902 tab->getRows().size());
903
904 mChatInput->setText("");
905 mChatInput->setCaretPosition(0);
906 return;
907 }
908
909 mChatHistoryIndex --;
910
911 unsigned int f = 0;
912 const std::list<std::string> &rows = tab->getRows();
913 for (std::list<std::string>::const_iterator it = rows.begin(),
914 it_end = rows.end(); it != it_end; ++ it, f ++)
915 {
916 if (f == mChatHistoryIndex)
917 mChatInput->setText(*it);
918 }
919 mChatInput->setCaretPosition(CAST_U32(
920 mChatInput->getText().length()));
921 }
922 }
923 else if (actionId == InputAction::CHAT_NEXT_HISTORY &&
924 mChatInput->mVisible == Visible_true)
925 {
926 const ChatTab *const tab = getFocused();
927 if ((tab != nullptr) && tab->hasRows())
928 {
929 const std::list<std::string> &rows = tab->getRows();
930 const size_t &tabSize = rows.size();
931 if (CAST_SIZE(mChatHistoryIndex) + 1 < tabSize)
932 {
933 mChatHistoryIndex ++;
934 }
935 else if (CAST_SIZE(mChatHistoryIndex) < tabSize)
936 {
937 mChatHistoryIndex ++;
938 mChatInput->setText("");
939 mChatInput->setCaretPosition(0);
940 return;
941 }
942 else
943 {
944 mChatHistoryIndex = 0;
945 }
946
947 unsigned int f = 0;
948 for (std::list<std::string>::const_iterator
949 it = rows.begin(), it_end = rows.end();
950 it != it_end; ++it, f++)
951 {
952 if (f == mChatHistoryIndex)
953 mChatInput->setText(*it);
954 }
955 mChatInput->setCaretPosition(CAST_U32(
956 mChatInput->getText().length()));
957 }
958 }
959 else if (actionId == InputAction::GUI_F1)
960 {
961 if (emoteWindow != nullptr)
962 {
963 if (emoteWindow->mVisible == Visible_true)
964 emoteWindow->hide();
965 else
966 emoteWindow->show();
967 }
968 }
969 ifKey(InputAction::GUI_F2, "\u2318")
970 ifKey(InputAction::GUI_F3, "\u263A")
971 ifKey(InputAction::GUI_F4, "\u2665")
972 ifKey(InputAction::GUI_F5, "\u266A")
973 ifKey(InputAction::GUI_F6, "\u266B")
974 ifKey(InputAction::GUI_F7, "\u26A0")
975 ifKey(InputAction::GUI_F8, "\u2622")
976 ifKey(InputAction::GUI_F9, "\u262E")
977 ifKey(InputAction::GUI_F10, "\u2605")
978 ifKey(InputAction::GUI_F11, "\u2618")
979 ifKey(InputAction::GUI_F12, "\u2592")
980
981 if (inputManager.isActionActive(InputAction::GUI_CTRL))
982 {
983 if (actionId == InputAction::GUI_B)
984 {
985 std::string inputText = mChatInput->getTextBeforeCaret();
986 toLower(inputText);
987 const size_t idx = inputText.rfind("##b");
988 if (idx == std::string::npos
989 || mChatInput->getTextBeforeCaret().substr(idx, 3) == "##b")
990 {
991 temp = "##B";
992 }
993 else
994 {
995 temp = "##b";
996 }
997 }
998 }
999
1000 if (!temp.empty())
1001 addInputText(temp, false);
1002 }
1003
1004 #undef ifKey
1005
addCurrentToHistory()1006 bool ChatWindow::addCurrentToHistory()
1007 {
1008 const std::string &str = mChatInput->getText();
1009 if (str.empty())
1010 return false;
1011 FOR_EACH (HistoryIterator, it, mHistory)
1012 {
1013 if (*it == str)
1014 return false;
1015 }
1016 mHistory.push_back(str);
1017 return true;
1018 }
1019
attributeChanged(const AttributesT id,const int64_t oldVal,const int64_t newVal)1020 void ChatWindow::attributeChanged(const AttributesT id,
1021 const int64_t oldVal,
1022 const int64_t newVal)
1023 {
1024 if (!mShowBattleEvents)
1025 return;
1026 PRAGMA45(GCC diagnostic push)
1027 PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
1028 switch (id)
1029 {
1030 case Attributes::PLAYER_EXP:
1031 {
1032 if (oldVal > newVal)
1033 break;
1034 const int64_t change = newVal - oldVal;
1035 if (change != 0)
1036 {
1037 battleChatLog(std::string("+").append(toString(
1038 CAST_U64(change))).append(" xp"),
1039 ChatMsgType::BY_SERVER,
1040 IgnoreRecord_false,
1041 TryRemoveColors_true);
1042 }
1043 break;
1044 }
1045 case Attributes::PLAYER_BASE_LEVEL:
1046 battleChatLog(std::string(
1047 "Level: ").append(toString(CAST_S32(
1048 newVal))),
1049 ChatMsgType::BY_SERVER,
1050 IgnoreRecord_false,
1051 TryRemoveColors_true);
1052 break;
1053 case Attributes::PLAYER_JOB_EXP:
1054 {
1055 if (!config.getBoolValue("showJobExp"))
1056 return;
1057 if (oldVal > newVal ||
1058 PlayerInfo::getAttribute(
1059 Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
1060 {
1061 return;
1062 }
1063 const int64_t change = newVal - oldVal;
1064 if (change != 0)
1065 {
1066 battleChatLog(std::string("+").append(toString(CAST_U64(
1067 change))).append(" job"),
1068 ChatMsgType::BY_SERVER,
1069 IgnoreRecord_false,
1070 TryRemoveColors_true);
1071 }
1072 break;
1073 }
1074 default:
1075 break;
1076 }
1077 PRAGMA45(GCC diagnostic pop)
1078 }
1079
addInputText(const std::string & text,const bool space)1080 void ChatWindow::addInputText(const std::string &text,
1081 const bool space)
1082 {
1083 const int caretPos = mChatInput->getCaretPosition();
1084 const std::string &inputText = mChatInput->getText();
1085
1086 std::ostringstream ss;
1087 ss << inputText.substr(0, caretPos) << text;
1088 if (space)
1089 ss << " ";
1090
1091 ss << inputText.substr(caretPos);
1092 mChatInput->setText(ss.str());
1093 mChatInput->setCaretPosition(caretPos + CAST_S32(
1094 text.length()) + CAST_S32(space));
1095 requestChatFocus();
1096 }
1097
addItemText(const std::string & item)1098 void ChatWindow::addItemText(const std::string &item)
1099 {
1100 std::ostringstream text;
1101 text << "[" << item << "]";
1102 addInputText(text.str(),
1103 true);
1104 }
1105
setVisible(Visible visible)1106 void ChatWindow::setVisible(Visible visible)
1107 {
1108 Window::setVisible(visible);
1109
1110 /*
1111 * For whatever reason, if setVisible is called, the mTmpVisible effect
1112 * should be disabled.
1113 */
1114 mTmpVisible = false;
1115 }
1116
addWhisper(const std::string & restrict nick,const std::string & restrict mes,const ChatMsgTypeT own)1117 void ChatWindow::addWhisper(const std::string &restrict nick,
1118 const std::string &restrict mes,
1119 const ChatMsgTypeT own)
1120 {
1121 if (mes.empty() || (localPlayer == nullptr))
1122 return;
1123
1124 std::string playerName = localPlayer->getName();
1125 std::string tempNick = nick;
1126
1127 toLower(playerName);
1128 toLower(tempNick);
1129
1130 if (tempNick == playerName)
1131 return;
1132
1133 WhisperTab *tab = nullptr;
1134 const TabMap::const_iterator i = mWhispers.find(tempNick);
1135
1136 if (i != mWhispers.end())
1137 {
1138 tab = i->second;
1139 }
1140 else if (config.getBoolValue("whispertab"))
1141 {
1142 tab = addWhisperTab(nick, nick, false);
1143 if (tab != nullptr)
1144 saveState();
1145 }
1146
1147 if (tab != nullptr)
1148 {
1149 if (own == ChatMsgType::BY_PLAYER)
1150 {
1151 tab->chatInput(mes);
1152 }
1153 else if (own == ChatMsgType::BY_SERVER)
1154 {
1155 tab->chatLog(mes,
1156 ChatMsgType::BY_SERVER,
1157 IgnoreRecord_false,
1158 TryRemoveColors_true);
1159 }
1160 else
1161 {
1162 if (tab->getRemoveNames())
1163 {
1164 std::string msg = mes;
1165 const size_t idx = mes.find(':');
1166 if (idx != std::string::npos && idx > 0)
1167 {
1168 std::string nick2 = msg.substr(0, idx);
1169 msg = msg.substr(idx + 1);
1170 nick2 = removeColors(nick2);
1171 nick2 = trim(nick2);
1172 if (config.getBoolValue("removeColors"))
1173 msg = removeColors(msg);
1174 msg = trim(msg);
1175 tab->chatLog(nick2, msg);
1176 }
1177 else
1178 {
1179 if (config.getBoolValue("removeColors"))
1180 msg = removeColors(msg);
1181 tab->chatLog(msg,
1182 ChatMsgType::BY_SERVER,
1183 IgnoreRecord_false,
1184 TryRemoveColors_true);
1185 }
1186 }
1187 else
1188 {
1189 tab->chatLog(nick, mes);
1190 }
1191 localPlayer->afkRespond(tab, nick);
1192 }
1193 }
1194 else if (localChatTab != nullptr)
1195 {
1196 if (own == ChatMsgType::BY_PLAYER)
1197 {
1198 chatHandler->privateMessage(nick, mes);
1199
1200 // TRANSLATORS: chat message
1201 localChatTab->chatLog(strprintf(_("Whispering to %s: %s"),
1202 nick.c_str(), mes.c_str()),
1203 ChatMsgType::BY_PLAYER,
1204 IgnoreRecord_false,
1205 TryRemoveColors_true);
1206 }
1207 else
1208 {
1209 localChatTab->chatLog(std::string(nick).append(
1210 " : ").append(mes),
1211 ChatMsgType::ACT_WHISPER,
1212 IgnoreRecord_false,
1213 TryRemoveColors_true);
1214 if (localPlayer != nullptr)
1215 localPlayer->afkRespond(nullptr, nick);
1216 }
1217 }
1218 }
1219
addWhisperTab(const std::string & caption,const std::string & nick,const bool switchTo)1220 WhisperTab *ChatWindow::addWhisperTab(const std::string &caption,
1221 const std::string &nick,
1222 const bool switchTo)
1223 {
1224 if (localPlayer == nullptr)
1225 return nullptr;
1226
1227 std::string playerName = localPlayer->getName();
1228 std::string tempNick = nick;
1229
1230 toLower(playerName);
1231 toLower(tempNick);
1232
1233 const TabMap::const_iterator i = mWhispers.find(tempNick);
1234 WhisperTab *ret = nullptr;
1235
1236 if (tempNick == playerName)
1237 return nullptr;
1238
1239 if (i != mWhispers.end())
1240 {
1241 ret = i->second;
1242 }
1243 else
1244 {
1245 ret = new WhisperTab(this, caption, nick);
1246 if ((gui != nullptr) && !playerRelations.isGoodName(nick))
1247 ret->setLabelFont(gui->getSecureFont());
1248 mWhispers[tempNick] = ret;
1249 if (config.getBoolValue("showChatHistory"))
1250 ret->loadFromLogFile(nick);
1251 }
1252
1253 if (switchTo)
1254 mChatTabs->setSelectedTab(ret);
1255
1256 return ret;
1257 }
1258
getWhisperTab(const std::string & nick) const1259 WhisperTab *ChatWindow::getWhisperTab(const std::string &nick) const
1260 {
1261 if (localPlayer == nullptr)
1262 return nullptr;
1263
1264 std::string playerName = localPlayer->getName();
1265 std::string tempNick = nick;
1266
1267 toLower(playerName);
1268 toLower(tempNick);
1269
1270 const TabMap::const_iterator i = mWhispers.find(tempNick);
1271 WhisperTab *ret = nullptr;
1272
1273 if (tempNick == playerName)
1274 return nullptr;
1275
1276 if (i != mWhispers.end())
1277 ret = i->second;
1278
1279 return ret;
1280 }
1281
addSpecialChannelTab(const std::string & name,const bool switchTo)1282 ChatTab *ChatWindow::addSpecialChannelTab(const std::string &name,
1283 const bool switchTo)
1284 {
1285 ChatTab *ret = nullptr;
1286 if (name == TRADE_CHANNEL)
1287 {
1288 if (tradeChatTab == nullptr)
1289 {
1290 tradeChatTab = new TradeTab(chatWindow);
1291 tradeChatTab->setAllowHighlight(false);
1292 chatHandler->joinChannel(tradeChatTab->getChannelName());
1293 }
1294 ret = tradeChatTab;
1295 }
1296 else if (name == GM_CHANNEL)
1297 {
1298 if (gmChatTab == nullptr)
1299 {
1300 gmChatTab = new GmTab(chatWindow);
1301 chatHandler->joinChannel(gmChatTab->getChannelName());
1302 }
1303 ret = gmChatTab;
1304 }
1305 if (switchTo)
1306 mChatTabs->setSelectedTab(ret);
1307
1308 return ret;
1309 }
1310
addChannelTab(const std::string & name,const bool switchTo)1311 ChatTab *ChatWindow::addChannelTab(const std::string &name,
1312 const bool switchTo)
1313 {
1314 std::string tempName = name;
1315 toLower(tempName);
1316
1317 ChatTab *const tab = addSpecialChannelTab(name, switchTo);
1318 if (tab != nullptr)
1319 return tab;
1320
1321 const ChannelMap::const_iterator i = mChannels.find(tempName);
1322 ChannelTab *ret = nullptr;
1323
1324 if (i != mChannels.end())
1325 {
1326 ret = i->second;
1327 }
1328 else
1329 {
1330 ret = new ChannelTab(this, name);
1331 mChannels[tempName] = ret;
1332 ret->setAllowHighlight(false);
1333 if (config.getBoolValue("showChatHistory"))
1334 ret->loadFromLogFile(name);
1335 }
1336
1337 if (switchTo)
1338 mChatTabs->setSelectedTab(ret);
1339
1340 return ret;
1341 }
1342
addChatTab(const std::string & name,const bool switchTo,const bool join)1343 ChatTab *ChatWindow::addChatTab(const std::string &name,
1344 const bool switchTo,
1345 const bool join)
1346 {
1347 if (Net::getNetworkType() != ServerType::TMWATHENA &&
1348 name.size() > 1 &&
1349 name[0] == '#')
1350 {
1351 ChatTab *const tab = addChannelTab(name, switchTo);
1352 if ((tab != nullptr) && join)
1353 chatHandler->joinChannel(name);
1354 return tab;
1355 }
1356 return addWhisperTab(name, name, switchTo);
1357 }
1358
postConnection()1359 void ChatWindow::postConnection()
1360 {
1361 FOR_EACH (ChannelMap::const_iterator, iter, mChannels)
1362 {
1363 ChatTab *const tab = iter->second;
1364 if (tab == nullptr)
1365 return;
1366 chatHandler->joinChannel(tab->getChannelName());
1367 }
1368 if (langChatTab != nullptr)
1369 chatHandler->joinChannel(langChatTab->getChannelName());
1370 }
1371
1372 #define changeColor(fun) \
1373 { \
1374 msg = removeColors(msg); \
1375 int skip = 0; \
1376 const size_t sz = msg.length(); \
1377 for (size_t f = 0; f < sz; f ++) \
1378 { \
1379 if (skip > 0) \
1380 { \
1381 newMsg += msg.at(f); \
1382 skip --; \
1383 continue; \
1384 } \
1385 const unsigned char ch = CAST_U8(msg.at(f)); \
1386 if (f + 2 < sz && msg.substr(f, 2) == "%%") \
1387 { \
1388 newMsg += msg.at(f); \
1389 skip = 2; \
1390 } \
1391 else if (ch > 0xc0 || ch < 0x80) \
1392 { \
1393 newMsg += "##" + toString(fun) + msg.at(f); \
1394 if (mRainbowColor > 9U) \
1395 mRainbowColor = 0U; \
1396 } \
1397 else \
1398 { \
1399 newMsg += msg.at(f); \
1400 } \
1401 } \
1402 }
1403
addColors(std::string & msg)1404 std::string ChatWindow::addColors(std::string &msg)
1405 {
1406 // default color or chat command
1407 if (mChatColor == 0 || msg.length() == 0 || msg.at(0) == '#'
1408 || msg.at(0) == '/' || msg.at(0) == '@' || msg.at(0) == '!')
1409 {
1410 return msg;
1411 }
1412
1413 std::string newMsg;
1414 const int cMap[] = {1, 4, 5, 2, 3, 6, 7, 9, 0, 8};
1415
1416 // rainbow
1417 switch (mChatColor)
1418 {
1419 case 11:
1420 changeColor(mRainbowColor++)
1421 return newMsg;
1422 case 12:
1423 changeColor(cMap[mRainbowColor++])
1424 return newMsg;
1425 case 13:
1426 changeColor(cMap[9-mRainbowColor++])
1427 return newMsg;
1428 default:
1429 break;
1430 }
1431
1432 // simple colors
1433 return std::string("##").append(toString(mChatColor - 1)).append(msg);
1434 }
1435
1436 #undef changeColor
1437
autoComplete()1438 void ChatWindow::autoComplete()
1439 {
1440 const int caretPos = mChatInput->getCaretPosition();
1441 int startName = 0;
1442 const std::string &inputText = mChatInput->getText();
1443 std::string name = inputText.substr(0, caretPos);
1444
1445 for (int f = caretPos - 1; f > -1; f --)
1446 {
1447 if (isWordSeparator(inputText[f]))
1448 {
1449 startName = f + 1;
1450 name = inputText.substr(f + 1, caretPos - f);
1451 break;
1452 }
1453 }
1454
1455 if (caretPos - 1 + 1 == startName)
1456 return;
1457
1458 const ChatTab *const cTab = static_cast<ChatTab*>(
1459 mChatTabs->getSelectedTab());
1460 StringVect nameList;
1461
1462 if (cTab != nullptr)
1463 cTab->getAutoCompleteList(nameList);
1464 std::string newName = autoComplete(nameList, name);
1465 if (!newName.empty() && (startName == 0))
1466 secureChatCommand(newName);
1467
1468 if ((cTab != nullptr) && newName.empty())
1469 {
1470 cTab->getAutoCompleteCommands(nameList);
1471 newName = autoComplete(nameList, name);
1472 }
1473
1474 if (newName.empty() && (actorManager != nullptr))
1475 {
1476 actorManager->getPlayerNames(nameList, NpcNames_true);
1477 newName = autoComplete(nameList, name);
1478 if (!newName.empty() && (startName == 0))
1479 secureChatCommand(newName);
1480 }
1481 if (newName.empty())
1482 newName = autoCompleteHistory(name);
1483 if (newName.empty() && (spellManager != nullptr))
1484 newName = spellManager->autoComplete(name);
1485 if (newName.empty())
1486 newName = autoComplete(name, &mCommands);
1487 if (newName.empty() && (actorManager != nullptr))
1488 {
1489 actorManager->getMobNames(nameList);
1490 newName = autoComplete(nameList, name);
1491 }
1492 if (newName.empty())
1493 newName = autoComplete(name, &mCustomWords);
1494 if (newName.empty())
1495 {
1496 whoIsOnline->getPlayerNames(nameList);
1497 newName = autoComplete(nameList, name);
1498 }
1499
1500 if (!newName.empty())
1501 {
1502 mChatInput->setText(inputText.substr(0, startName).append(newName)
1503 .append(inputText.substr(caretPos,
1504 inputText.length() - caretPos)));
1505
1506 const int len = caretPos - CAST_S32(name.length())
1507 + CAST_S32(newName.length());
1508
1509 if (startName > 0)
1510 mChatInput->setCaretPosition(len + 1);
1511 else
1512 mChatInput->setCaretPosition(len);
1513 }
1514 }
1515
autoComplete(const StringVect & names,std::string partName)1516 std::string ChatWindow::autoComplete(const StringVect &names,
1517 std::string partName)
1518 {
1519 StringVectCIter i = names.begin();
1520 const StringVectCIter i_end = names.end();
1521 toLower(partName);
1522 std::string newName;
1523
1524 while (i != i_end)
1525 {
1526 if (!i->empty())
1527 {
1528 std::string name = *i;
1529 toLower(name);
1530
1531 const size_t pos = name.find(partName, 0);
1532 if (pos == 0)
1533 {
1534 if (!newName.empty())
1535 newName = findSameSubstringI(*i, newName);
1536 else
1537 newName = *i;
1538 }
1539 }
1540 ++i;
1541 }
1542
1543 return newName;
1544 }
1545
autoComplete(const std::string & partName,const History * const words)1546 std::string ChatWindow::autoComplete(const std::string &partName,
1547 const History *const words)
1548 {
1549 if (words == nullptr)
1550 return "";
1551
1552 ChatCommands::const_iterator i = words->begin();
1553 const ChatCommands::const_iterator i_end = words->end();
1554 StringVect nameList;
1555
1556 while (i != i_end)
1557 {
1558 const std::string line = *i;
1559 if (line.find(partName, 0) == 0)
1560 nameList.push_back(line);
1561 ++i;
1562 }
1563 return autoComplete(nameList, partName);
1564 }
1565
1566 /*
1567 void ChatWindow::moveTabLeft(ChatTab *tab)
1568 {
1569 mChatTabs->moveLeft(tab);
1570 }
1571
1572 void ChatWindow::moveTabRight(ChatTab *tab)
1573 {
1574 mChatTabs->moveRight(tab);
1575 }
1576 */
1577
autoCompleteHistory(const std::string & partName) const1578 std::string ChatWindow::autoCompleteHistory(const std::string &partName) const
1579 {
1580 History::const_iterator i = mHistory.begin();
1581 const History::const_iterator i_end = mHistory.end();
1582 StringVect nameList;
1583
1584 while (i != i_end)
1585 {
1586 std::string line = *i;
1587 unsigned int f = 0;
1588 while (f < line.length() && !isWordSeparator(line.at(f)))
1589 f++;
1590
1591 line = line.substr(0, f);
1592 if (!line.empty())
1593 nameList.push_back(line);
1594
1595 ++i;
1596 }
1597 return autoComplete(nameList, partName);
1598 }
1599
resortChatLog(std::string line,ChatMsgTypeT own,const std::string & channel,const IgnoreRecord ignoreRecord,const TryRemoveColors tryRemoveColors)1600 bool ChatWindow::resortChatLog(std::string line,
1601 ChatMsgTypeT own,
1602 const std::string &channel,
1603 const IgnoreRecord ignoreRecord,
1604 const TryRemoveColors tryRemoveColors)
1605 {
1606 if (own == ChatMsgType::BY_UNKNOWN)
1607 {
1608 const size_t pos = line.find(" : ");
1609 if (pos != std::string::npos)
1610 {
1611 if (line.length() <= pos + 3)
1612 own = ChatMsgType::BY_SERVER;
1613 else if (line.substr(0, pos) == localPlayer->getName())
1614 own = ChatMsgType::BY_PLAYER;
1615 else
1616 own = ChatMsgType::BY_OTHER;
1617 }
1618 else
1619 {
1620 own = ChatMsgType::BY_SERVER;
1621 }
1622 }
1623
1624 std::string prefix;
1625 if (!channel.empty())
1626 {
1627 prefix = std::string("##3").append(channel).append("##0");
1628 }
1629 else if (mEnableTradeFilter &&
1630 (tradeChatTab != nullptr) &&
1631 findI(line, mTradeFilter) != std::string::npos)
1632 {
1633 // TRANSLATORS: prefix for moved message to trade tab.
1634 tradeChatTab->chatLog(std::string("##S") + _("Moved: ") + line,
1635 own,
1636 ignoreRecord,
1637 tryRemoveColors);
1638 if (own == ChatMsgType::BY_PLAYER)
1639 {
1640 own = ChatMsgType::BY_SERVER;
1641 // TRANSLATORS: moved message to trade tab warning.
1642 line = _("Your message was moved to trade tab");
1643 }
1644 else
1645 {
1646 return false;
1647 }
1648 }
1649
1650 size_t idx2 = line.find(": ");
1651 if (idx2 != std::string::npos)
1652 {
1653 std::string tmpNick = line.substr(0, idx2);
1654 if (tmpNick.find('#') != std::string::npos ||
1655 tmpNick.find(':') != std::string::npos ||
1656 tmpNick.find('%') != std::string::npos ||
1657 tmpNick.find('@') != std::string::npos ||
1658 tmpNick.size() < 5 ||
1659 tmpNick[0] == '@' ||
1660 tmpNick[0] == '/' ||
1661 tmpNick[0] == '!'
1662 )
1663 {
1664 replaceAll(tmpNick, "#", "_");
1665 replaceAll(tmpNick, "%", "_");
1666 // TRANSLATORS: error message
1667 line = _("Broken nick detected: ") + line;
1668 own = ChatMsgType::BY_SERVER;
1669 }
1670 const size_t idx = line.find(": \302\202");
1671 if (idx == idx2)
1672 {
1673 if (line.find(": \302\202\302") != std::string::npos)
1674 {
1675 if (line.find(": \302\202\302e") != std::string::npos)
1676 {
1677 // Do nothing. Before was local pet emote
1678 }
1679 else if (line.find(": \302\202\302m") != std::string::npos)
1680 {
1681 // Do nothing. Before was local pet move
1682 }
1683 else if (line.find(": \302\202\302d") != std::string::npos)
1684 {
1685 // Do nothing. Before was local pet direction
1686 }
1687 else if (line.find(": \302\202\302a") != std::string::npos)
1688 {
1689 // Do nothing. Before was local pet ai enable/disable
1690 }
1691 // ignore other special message formats.
1692 return false;
1693 }
1694
1695 // pet talk message detected
1696 if (line.find(": \302\202\303 ") != std::string::npos)
1697 {
1698 // Do nothing. Before was local pet talk
1699 return false;
1700 }
1701 if (line.find(": \302\202\304") != std::string::npos)
1702 {
1703 replaceAll(line, ": \302\202\304", ": ");
1704 }
1705
1706 if (tradeChatTab != nullptr)
1707 {
1708 line = line.erase(idx + 2, 2);
1709 tradeChatTab->chatLog(prefix + line,
1710 own,
1711 ignoreRecord,
1712 tryRemoveColors);
1713 }
1714 else if (localChatTab != nullptr)
1715 {
1716 line = line.erase(idx + 2, 2);
1717 localChatTab->chatLog(prefix + line,
1718 own,
1719 ignoreRecord,
1720 tryRemoveColors);
1721 }
1722 return false;
1723 }
1724 }
1725
1726 if (!channel.empty())
1727 {
1728 if (langChatTab != nullptr)
1729 {
1730 if (langChatTab->getChannelName() == channel)
1731 {
1732 langChatTab->chatLog(line,
1733 own,
1734 ignoreRecord,
1735 tryRemoveColors);
1736 }
1737 else if (mShowAllLang)
1738 {
1739 langChatTab->chatLog(prefix + line,
1740 own,
1741 ignoreRecord,
1742 tryRemoveColors);
1743 }
1744 }
1745 else if (Net::getNetworkType() != ServerType::TMWATHENA)
1746 {
1747 channelChatLog(channel, line, own, ignoreRecord, tryRemoveColors);
1748 return false;
1749 }
1750 else if (mShowAllLang)
1751 {
1752 localChatTab->chatLog(prefix + line,
1753 own,
1754 ignoreRecord,
1755 tryRemoveColors);
1756 }
1757 }
1758 else if (localChatTab != nullptr)
1759 {
1760 localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1761 }
1762 return true;
1763 }
1764
battleChatLog(const std::string & line,ChatMsgTypeT own,const IgnoreRecord ignoreRecord,const TryRemoveColors tryRemoveColors)1765 void ChatWindow::battleChatLog(const std::string &line, ChatMsgTypeT own,
1766 const IgnoreRecord ignoreRecord,
1767 const TryRemoveColors tryRemoveColors)
1768 {
1769 if (own == ChatMsgType::BY_UNKNOWN)
1770 own = ChatMsgType::BY_SERVER;
1771 if (battleChatTab != nullptr)
1772 battleChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1773 else if (debugChatTab != nullptr)
1774 debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1775 }
1776
channelChatLog(const std::string & channel,const std::string & line,ChatMsgTypeT own,const IgnoreRecord ignoreRecord,const TryRemoveColors tryRemoveColors)1777 void ChatWindow::channelChatLog(const std::string &channel,
1778 const std::string &line,
1779 ChatMsgTypeT own,
1780 const IgnoreRecord ignoreRecord,
1781 const TryRemoveColors tryRemoveColors)
1782 {
1783 std::string tempChannel = channel;
1784 toLower(tempChannel);
1785
1786 ChatTab *tab = nullptr;
1787 const ChannelMap::const_iterator i = mChannels.find(tempChannel);
1788
1789 if (i != mChannels.end())
1790 {
1791 tab = i->second;
1792 }
1793 else
1794 {
1795 tab = addChannelTab(channel, false);
1796 if (tab != nullptr)
1797 saveState();
1798 }
1799
1800 if (tab != nullptr)
1801 tab->chatLog(line, own, ignoreRecord, tryRemoveColors);
1802 }
1803
initTradeFilter()1804 void ChatWindow::initTradeFilter()
1805 {
1806 const std::string tradeListName = settings.serverConfigDir
1807 + "/tradefilter.txt";
1808
1809 std::ifstream tradeFile;
1810 struct stat statbuf;
1811
1812 if (stat(tradeListName.c_str(), &statbuf) == 0 &&
1813 S_ISREG(statbuf.st_mode))
1814 {
1815 tradeFile.open(tradeListName.c_str(), std::ios::in);
1816 if (tradeFile.is_open())
1817 {
1818 char line[100];
1819 while (tradeFile.getline(line, 100))
1820 {
1821 const std::string str = line;
1822 if (!str.empty())
1823 mTradeFilter.push_back(str);
1824 }
1825 }
1826 tradeFile.close();
1827 }
1828 }
1829
updateOnline(const std::set<std::string> & onlinePlayers) const1830 void ChatWindow::updateOnline(const std::set<std::string> &onlinePlayers) const
1831 {
1832 const Party *party = nullptr;
1833 const Guild *guild = nullptr;
1834 if (localPlayer != nullptr)
1835 {
1836 party = localPlayer->getParty();
1837 guild = localPlayer->getGuild();
1838 }
1839 FOR_EACH (TabMap::const_iterator, iter, mWhispers)
1840 {
1841 if (iter->second == nullptr)
1842 return;
1843
1844 WhisperTab *const tab = static_cast<WhisperTab*>(iter->second);
1845 if (tab == nullptr)
1846 continue;
1847
1848 if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end())
1849 {
1850 tab->setWhisperTabColors();
1851 }
1852 else
1853 {
1854 const std::string &nick = tab->getNick();
1855 if (actorManager != nullptr)
1856 {
1857 const Being *const being = actorManager->findBeingByName(
1858 nick, ActorType::Player);
1859 if (being != nullptr)
1860 {
1861 tab->setWhisperTabColors();
1862 continue;
1863 }
1864 }
1865 if (party != nullptr)
1866 {
1867 const PartyMember *const pm = party->getMember(nick);
1868 if ((pm != nullptr) && pm->getOnline())
1869 {
1870 tab->setWhisperTabColors();
1871 continue;
1872 }
1873 }
1874 if (guild != nullptr)
1875 {
1876 const GuildMember *const gm = guild->getMember(nick);
1877 if ((gm != nullptr) && gm->getOnline())
1878 {
1879 tab->setWhisperTabColors();
1880 continue;
1881 }
1882 }
1883 tab->setWhisperTabOfflineColors();
1884 }
1885 }
1886 }
1887
loadState()1888 void ChatWindow::loadState()
1889 {
1890 int num = 0;
1891 while (num < 50)
1892 {
1893 const std::string nick = serverConfig.getValue(
1894 "chatWhisper" + toString(num), "");
1895
1896 if (nick.empty())
1897 break;
1898
1899 ChatTab *const tab = addChatTab(nick, false, false);
1900 if (tab != nullptr)
1901 {
1902 const int flags = serverConfig.getValue(
1903 "chatWhisperFlags" + toString(num), 1);
1904
1905 tab->setAllowHighlight((flags & 1) != 0);
1906 tab->setRemoveNames(((flags & 2) / 2) != 0);
1907 tab->setNoAway(((flags & 4) / 4) != 0);
1908 }
1909 num ++;
1910 }
1911 }
1912
saveState() const1913 void ChatWindow::saveState() const
1914 {
1915 int num = 0;
1916 for (ChannelMap::const_iterator iter = mChannels.begin(),
1917 iter_end = mChannels.end(); iter != iter_end && num < 50; ++iter)
1918 {
1919 if (iter->second == nullptr)
1920 return;
1921 if (!saveTab(num, iter->second))
1922 continue;
1923 num ++;
1924 }
1925
1926 for (TabMap::const_iterator iter = mWhispers.begin(),
1927 iter_end = mWhispers.end(); iter != iter_end && num < 50; ++iter)
1928 {
1929 if (iter->second == nullptr)
1930 return;
1931 if (!saveTab(num, iter->second))
1932 continue;
1933 num ++;
1934 }
1935
1936 while (num < 50)
1937 {
1938 serverConfig.deleteKey("chatWhisper" + toString(num));
1939 serverConfig.deleteKey("chatWhisperFlags" + toString(num));
1940 num ++;
1941 }
1942 }
1943
saveTab(const int num,const ChatTab * const tab)1944 bool ChatWindow::saveTab(const int num,
1945 const ChatTab *const tab)
1946 {
1947 if (tab == nullptr)
1948 return false;
1949
1950 serverConfig.setValue("chatWhisper" + toString(num),
1951 tab->getChannelName());
1952
1953 serverConfig.setValue("chatWhisperFlags" + toString(num),
1954 CAST_S32(tab->getAllowHighlight())
1955 + (2 * CAST_S32(tab->getRemoveNames()))
1956 + (4 * CAST_S32(tab->getNoAway())));
1957
1958 return true;
1959 }
1960
doReplace(const std::string & msg)1961 std::string ChatWindow::doReplace(const std::string &msg)
1962 {
1963 std::string str = msg;
1964 replaceSpecialChars(str);
1965 return str;
1966 }
1967
loadCustomList()1968 void ChatWindow::loadCustomList()
1969 {
1970 std::ifstream listFile;
1971 struct stat statbuf;
1972
1973 std::string listName = settings.serverConfigDir
1974 + "/customwords.txt";
1975
1976 if ((stat(listName.c_str(), &statbuf) == 0) && S_ISREG(statbuf.st_mode))
1977 {
1978 listFile.open(listName.c_str(), std::ios::in);
1979 if (listFile.is_open())
1980 {
1981 char line[101];
1982 while (listFile.getline(line, 100))
1983 {
1984 std::string str = line;
1985 if (!str.empty())
1986 mCustomWords.push_back(str);
1987 }
1988 }
1989 listFile.close();
1990 }
1991 }
1992
addToAwayLog(const std::string & line)1993 void ChatWindow::addToAwayLog(const std::string &line)
1994 {
1995 if (!settings.awayMode)
1996 return;
1997
1998 if (mAwayLog.size() > 20)
1999 mAwayLog.pop_front();
2000
2001 if (findI(line, mHighlights) != std::string::npos)
2002 mAwayLog.push_back("##aaway:" + line);
2003 }
2004
displayAwayLog() const2005 void ChatWindow::displayAwayLog() const
2006 {
2007 if (localChatTab == nullptr)
2008 return;
2009
2010 std::list<std::string>::const_iterator i = mAwayLog.begin();
2011 const std::list<std::string>::const_iterator i_end = mAwayLog.end();
2012
2013 while (i != i_end)
2014 {
2015 std::string str = *i;
2016 localChatTab->addNewRow(str);
2017 ++i;
2018 }
2019 }
2020
parseHighlights()2021 void ChatWindow::parseHighlights()
2022 {
2023 mHighlights.clear();
2024 if (localPlayer == nullptr)
2025 return;
2026
2027 splitToStringVector(mHighlights, config.getStringValue(
2028 "highlightWords"), ',');
2029
2030 mHighlights.push_back(localPlayer->getName());
2031 }
2032
parseGlobalsFilter()2033 void ChatWindow::parseGlobalsFilter()
2034 {
2035 mGlobalsFilter.clear();
2036 if (localPlayer == nullptr)
2037 return;
2038
2039 splitToStringVector(mGlobalsFilter, config.getStringValue(
2040 "globalsFilter"), ',');
2041
2042 mHighlights.push_back(localPlayer->getName());
2043 }
2044
findHighlight(const std::string & str)2045 bool ChatWindow::findHighlight(const std::string &str)
2046 {
2047 return findI(str, mHighlights) != std::string::npos;
2048 }
2049
copyToClipboard(const int x,const int y) const2050 void ChatWindow::copyToClipboard(const int x, const int y) const
2051 {
2052 const ChatTab *const tab = getFocused();
2053 if (tab == nullptr)
2054 return;
2055
2056 const BrowserBox *const text = tab->mTextOutput;
2057 if (text == nullptr)
2058 return;
2059
2060 std::string str = text->getTextAtPos(x, y);
2061 sendBuffer(str);
2062 }
2063
optionChanged(const std::string & name)2064 void ChatWindow::optionChanged(const std::string &name)
2065 {
2066 if (name == "autohideChat")
2067 mAutoHide = config.getBoolValue("autohideChat");
2068 else if (name == "showBattleEvents")
2069 mShowBattleEvents = config.getBoolValue("showBattleEvents");
2070 else if (name == "globalsFilter")
2071 parseGlobalsFilter();
2072 else if (name == "enableTradeFilter")
2073 mEnableTradeFilter = config.getBoolValue("enableTradeFilter");
2074 }
2075
mouseMoved(MouseEvent & event)2076 void ChatWindow::mouseMoved(MouseEvent &event)
2077 {
2078 mHaveMouse = true;
2079 Window::mouseMoved(event);
2080 }
2081
mouseEntered(MouseEvent & event)2082 void ChatWindow::mouseEntered(MouseEvent& event)
2083 {
2084 mHaveMouse = true;
2085 Window::mouseEntered(event);
2086 }
2087
mouseExited(MouseEvent & event)2088 void ChatWindow::mouseExited(MouseEvent& event)
2089 {
2090 updateVisibility();
2091 Window::mouseExited(event);
2092 }
2093
draw(Graphics * const graphics)2094 void ChatWindow::draw(Graphics *const graphics)
2095 {
2096 BLOCK_START("ChatWindow::draw")
2097 if (!mAutoHide || mHaveMouse)
2098 {
2099 GLDEBUG_START("ChatWindow::draw");
2100 Window::draw(graphics);
2101 GLDEBUG_END();
2102 }
2103 BLOCK_END("ChatWindow::draw")
2104 }
2105
safeDraw(Graphics * const graphics)2106 void ChatWindow::safeDraw(Graphics *const graphics)
2107 {
2108 BLOCK_START("ChatWindow::draw")
2109 if (!mAutoHide || mHaveMouse)
2110 {
2111 GLDEBUG_START("ChatWindow::draw");
2112 Window::safeDraw(graphics);
2113 GLDEBUG_END();
2114 }
2115 BLOCK_END("ChatWindow::draw")
2116 }
2117
updateVisibility()2118 void ChatWindow::updateVisibility()
2119 {
2120 if (gui == nullptr)
2121 return;
2122
2123 int mouseX = 0;
2124 int mouseY = 0;
2125 int x = 0;
2126 int y = 0;
2127 Gui::getMouseState(mouseX, mouseY);
2128 getAbsolutePosition(x, y);
2129 if (mChatInput->isVisible())
2130 {
2131 mHaveMouse = true;
2132 }
2133 else
2134 {
2135 mHaveMouse = mouseX >= x && mouseX <= x + getWidth()
2136 && mouseY >= y && mouseY <= y + getHeight();
2137 }
2138 }
2139
unHideWindow()2140 void ChatWindow::unHideWindow()
2141 {
2142 mHaveMouse = true;
2143 }
2144
2145 #ifdef USE_PROFILER
logicChildren()2146 void ChatWindow::logicChildren()
2147 {
2148 BLOCK_START("ChatWindow::logicChildren")
2149 BasicContainer::logicChildren();
2150 BLOCK_END("ChatWindow::logicChildren")
2151 }
2152 #endif // USE_PROFILER
2153
addGlobalMessage(const std::string & line)2154 void ChatWindow::addGlobalMessage(const std::string &line)
2155 {
2156 if (debugChatTab != nullptr &&
2157 findI(line, mGlobalsFilter) != std::string::npos)
2158 {
2159 debugChatTab->chatLog(line,
2160 ChatMsgType::BY_OTHER,
2161 IgnoreRecord_false,
2162 TryRemoveColors_true);
2163 }
2164 else
2165 {
2166 localChatTab->chatLog(line,
2167 ChatMsgType::BY_GM,
2168 IgnoreRecord_false,
2169 TryRemoveColors_true);
2170 }
2171 }
2172
isTabPresent(const ChatTab * const tab) const2173 bool ChatWindow::isTabPresent(const ChatTab *const tab) const
2174 {
2175 return mChatTabs->isTabPresent(tab);
2176 }
2177
debugMessage(const std::string & msg)2178 void ChatWindow::debugMessage(const std::string &msg)
2179 {
2180 if (debugChatTab != nullptr)
2181 {
2182 debugChatTab->chatLog(msg,
2183 ChatMsgType::BY_SERVER,
2184 IgnoreRecord_false,
2185 TryRemoveColors_true);
2186 }
2187 }
2188
showGMTab()2189 void ChatWindow::showGMTab()
2190 {
2191 if ((gmChatTab == nullptr) &&
2192 config.getBoolValue("enableGmTab") &&
2193 localPlayer->getGroupId() >= paths.getIntValue("gmTabMinimalLevel"))
2194 {
2195 addSpecialChannelTab(GM_CHANNEL, false);
2196 }
2197 }
2198
toggleChatFocus()2199 void ChatWindow::toggleChatFocus()
2200 {
2201 if (mChatInput->isVisible() && mChatInput->isFocused())
2202 mChatInput->processVisible(Visible_false);
2203 else
2204 requestChatFocus();
2205 }
2206
joinRoom(const bool isJoin)2207 void ChatWindow::joinRoom(const bool isJoin)
2208 {
2209 Tab *const tab = mChatTabs->getTabByIndex(0);
2210 if (tab != nullptr)
2211 {
2212 std::string name;
2213 if (isJoin)
2214 {
2215 name = PlayerInfo::getRoomName();
2216 }
2217 else
2218 {
2219 // TRANSLATORS: chat tab name
2220 name = _("General");
2221 }
2222 tab->setCaption(name);
2223 }
2224 }
2225
scheduleDelete()2226 void ChatWindow::scheduleDelete()
2227 {
2228 DebugMessageListener::removeListener(this);
2229 Window::scheduleDelete();
2230 }
2231