1 #include "tab_supervisor.h"
2 
3 #include "abstractclient.h"
4 #include "main.h"
5 #include "pb/event_game_joined.pb.h"
6 #include "pb/event_notify_user.pb.h"
7 #include "pb/event_user_message.pb.h"
8 #include "pb/game_event_container.pb.h"
9 #include "pb/moderator_commands.pb.h"
10 #include "pb/room_commands.pb.h"
11 #include "pb/room_event.pb.h"
12 #include "pb/serverinfo_room.pb.h"
13 #include "pb/serverinfo_user.pb.h"
14 #include "pixmapgenerator.h"
15 #include "settingscache.h"
16 #include "tab_account.h"
17 #include "tab_admin.h"
18 #include "tab_deck_editor.h"
19 #include "tab_deck_storage.h"
20 #include "tab_game.h"
21 #include "tab_logs.h"
22 #include "tab_message.h"
23 #include "tab_replays.h"
24 #include "tab_room.h"
25 #include "tab_server.h"
26 #include "userlist.h"
27 
28 #include <QApplication>
29 #include <QDebug>
30 #include <QMessageBox>
31 #include <QPainter>
32 #include <QSystemTrayIcon>
33 
subElementRect(SubElement element,const QStyleOption * option,const QWidget * widget) const34 QRect MacOSTabFixStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
35 {
36     if (element != SE_TabBarTabText) {
37         return QProxyStyle::subElementRect(element, option, widget);
38     }
39 
40     // Skip over QProxyStyle handling subElementRect,
41     // This fixes an issue with Qt 5.10 on OSX where the labels for tabs with a button and an icon
42     // get cut-off too early
43     return QCommonStyle::subElementRect(element, option, widget);
44 }
45 
CloseButton(QWidget * parent)46 CloseButton::CloseButton(QWidget *parent) : QAbstractButton(parent)
47 {
48     setFocusPolicy(Qt::NoFocus);
49     setCursor(Qt::ArrowCursor);
50     resize(sizeHint());
51 }
52 
sizeHint() const53 QSize CloseButton::sizeHint() const
54 {
55     ensurePolished();
56     int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this);
57     int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this);
58     return QSize(width, height);
59 }
60 
enterEvent(QEvent * event)61 void CloseButton::enterEvent(QEvent *event)
62 {
63     update();
64     QAbstractButton::enterEvent(event);
65 }
66 
leaveEvent(QEvent * event)67 void CloseButton::leaveEvent(QEvent *event)
68 {
69     update();
70     QAbstractButton::leaveEvent(event);
71 }
72 
paintEvent(QPaintEvent *)73 void CloseButton::paintEvent(QPaintEvent * /*event*/)
74 {
75     QPainter p(this);
76     QStyleOption opt;
77     opt.init(this);
78     opt.state |= QStyle::State_AutoRaise;
79     if (isEnabled() && underMouse() && !isChecked() && !isDown())
80         opt.state |= QStyle::State_Raised;
81     if (isChecked())
82         opt.state |= QStyle::State_On;
83     if (isDown())
84         opt.state |= QStyle::State_Sunken;
85 
86     if (const QTabBar *tb = qobject_cast<const QTabBar *>(parent())) {
87         int index = tb->currentIndex();
88         QTabBar::ButtonPosition position =
89             (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb);
90         if (tb->tabButton(index, position) == this)
91             opt.state |= QStyle::State_Selected;
92     }
93 
94     style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
95 }
96 
TabSupervisor(AbstractClient * _client,QWidget * parent)97 TabSupervisor::TabSupervisor(AbstractClient *_client, QWidget *parent)
98     : QTabWidget(parent), userInfo(0), client(_client), tabServer(0), tabUserLists(0), tabDeckStorage(0), tabReplays(0),
99       tabAdmin(0), tabLog(0)
100 {
101     setElideMode(Qt::ElideRight);
102     setMovable(true);
103     setIconSize(QSize(15, 15));
104 
105 #if defined(Q_OS_MAC)
106     // This is necessary to fix an issue on macOS with qt5.10,
107     // where tabs with icons and buttons get drawn incorrectly
108     tabBar()->setStyle(new MacOSTabFixStyle);
109 #endif
110 
111     connect(this, SIGNAL(currentChanged(int)), this, SLOT(updateCurrent(int)));
112 
113     connect(client, SIGNAL(roomEventReceived(const RoomEvent &)), this, SLOT(processRoomEvent(const RoomEvent &)));
114     connect(client, SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this,
115             SLOT(processGameEventContainer(const GameEventContainer &)));
116     connect(client, SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this,
117             SLOT(gameJoined(const Event_GameJoined &)));
118     connect(client, SIGNAL(userMessageEventReceived(const Event_UserMessage &)), this,
119             SLOT(processUserMessageEvent(const Event_UserMessage &)));
120     connect(client, SIGNAL(maxPingTime(int, int)), this, SLOT(updatePingTime(int, int)));
121     connect(client, SIGNAL(notifyUserEventReceived(const Event_NotifyUser &)), this,
122             SLOT(processNotifyUserEvent(const Event_NotifyUser &)));
123 
124     retranslateUi();
125 }
126 
~TabSupervisor()127 TabSupervisor::~TabSupervisor()
128 {
129     stop();
130 }
131 
retranslateUi()132 void TabSupervisor::retranslateUi()
133 {
134     QList<Tab *> tabs;
135     tabs.append(tabServer);
136     tabs.append(tabReplays);
137     tabs.append(tabDeckStorage);
138     tabs.append(tabAdmin);
139     tabs.append(tabUserLists);
140     tabs.append(tabLog);
141     QMapIterator<int, TabRoom *> roomIterator(roomTabs);
142     while (roomIterator.hasNext())
143         tabs.append(roomIterator.next().value());
144     QMapIterator<int, TabGame *> gameIterator(gameTabs);
145     while (gameIterator.hasNext())
146         tabs.append(gameIterator.next().value());
147     QListIterator<TabGame *> replayIterator(replayTabs);
148     while (replayIterator.hasNext())
149         tabs.append(replayIterator.next());
150     QListIterator<TabDeckEditor *> deckEditorIterator(deckEditorTabs);
151     while (deckEditorIterator.hasNext())
152         tabs.append(deckEditorIterator.next());
153     QMapIterator<QString, TabMessage *> messageIterator(messageTabs);
154     while (messageIterator.hasNext())
155         tabs.append(messageIterator.next().value());
156 
157     for (int i = 0; i < tabs.size(); ++i)
158         if (tabs[i]) {
159             int idx = indexOf(tabs[i]);
160             QString tabText = tabs[i]->getTabText();
161             setTabText(idx, sanitizeTabName(tabText));
162             setTabToolTip(idx, sanitizeHtml(tabText));
163             tabs[i]->retranslateUi();
164         }
165 }
166 
closeRequest()167 bool TabSupervisor::closeRequest()
168 {
169     if (getGameCount()) {
170         if (QMessageBox::question(this, tr("Are you sure?"),
171                                   tr("There are still open games. Are you sure you want to quit?"),
172                                   QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
173             return false;
174         }
175     }
176 
177     foreach (TabDeckEditor *tab, deckEditorTabs) {
178         if (!tab->confirmClose())
179             return false;
180     }
181 
182     return true;
183 }
184 
getClient() const185 AbstractClient *TabSupervisor::getClient() const
186 {
187     return localClients.isEmpty() ? client : localClients.first();
188 }
189 
sanitizeTabName(QString dirty) const190 QString TabSupervisor::sanitizeTabName(QString dirty) const
191 {
192     return dirty.replace("&", "&&");
193 }
194 
sanitizeHtml(QString dirty) const195 QString TabSupervisor::sanitizeHtml(QString dirty) const
196 {
197     return dirty.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
198 }
199 
myAddTab(Tab * tab)200 int TabSupervisor::myAddTab(Tab *tab)
201 {
202     connect(tab, SIGNAL(userEvent(bool)), this, SLOT(tabUserEvent(bool)));
203     connect(tab, SIGNAL(tabTextChanged(Tab *, QString)), this, SLOT(updateTabText(Tab *, QString)));
204 
205     QString tabText = tab->getTabText();
206     int idx = addTab(tab, sanitizeTabName(tabText));
207     setTabToolTip(idx, sanitizeHtml(tabText));
208 
209     return idx;
210 }
211 
start(const ServerInfo_User & _userInfo)212 void TabSupervisor::start(const ServerInfo_User &_userInfo)
213 {
214     isLocalGame = false;
215     userInfo = new ServerInfo_User(_userInfo);
216 
217     tabServer = new TabServer(this, client);
218     connect(tabServer, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this,
219             SLOT(addRoomTab(const ServerInfo_Room &, bool)));
220     myAddTab(tabServer);
221 
222     tabUserLists = new TabUserLists(this, client, *userInfo);
223     connect(tabUserLists, SIGNAL(openMessageDialog(const QString &, bool)), this,
224             SLOT(addMessageTab(const QString &, bool)));
225     connect(tabUserLists, SIGNAL(userJoined(ServerInfo_User)), this, SLOT(processUserJoined(ServerInfo_User)));
226     connect(tabUserLists, SIGNAL(userLeft(const QString &)), this, SLOT(processUserLeft(const QString &)));
227     myAddTab(tabUserLists);
228 
229     updatePingTime(0, -1);
230 
231     if (userInfo->user_level() & ServerInfo_User::IsRegistered) {
232         tabDeckStorage = new TabDeckStorage(this, client);
233         connect(tabDeckStorage, SIGNAL(openDeckEditor(const DeckLoader *)), this,
234                 SLOT(addDeckEditorTab(const DeckLoader *)));
235         myAddTab(tabDeckStorage);
236 
237         tabReplays = new TabReplays(this, client);
238         connect(tabReplays, SIGNAL(openReplay(GameReplay *)), this, SLOT(openReplay(GameReplay *)));
239         myAddTab(tabReplays);
240     } else {
241         tabDeckStorage = 0;
242         tabReplays = 0;
243     }
244 
245     if (userInfo->user_level() & ServerInfo_User::IsModerator) {
246         tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin));
247         connect(tabAdmin, SIGNAL(adminLockChanged(bool)), this, SIGNAL(adminLockChanged(bool)));
248         myAddTab(tabAdmin);
249 
250         tabLog = new TabLog(this, client);
251         myAddTab(tabLog);
252     } else {
253         tabAdmin = 0;
254         tabLog = 0;
255     }
256 
257     retranslateUi();
258 }
259 
startLocal(const QList<AbstractClient * > & _clients)260 void TabSupervisor::startLocal(const QList<AbstractClient *> &_clients)
261 {
262     tabUserLists = 0;
263     tabDeckStorage = 0;
264     tabReplays = 0;
265     tabAdmin = 0;
266     tabLog = 0;
267     isLocalGame = true;
268     userInfo = new ServerInfo_User;
269     localClients = _clients;
270     for (int i = 0; i < localClients.size(); ++i)
271         connect(localClients[i], SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this,
272                 SLOT(processGameEventContainer(const GameEventContainer &)));
273     connect(localClients.first(), SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this,
274             SLOT(localGameJoined(const Event_GameJoined &)));
275 }
276 
stop()277 void TabSupervisor::stop()
278 {
279     if ((!client) && localClients.isEmpty())
280         return;
281 
282     if (!localClients.isEmpty()) {
283         for (int i = 0; i < localClients.size(); ++i)
284             localClients[i]->deleteLater();
285         localClients.clear();
286 
287         emit localGameEnded();
288     } else {
289         if (tabUserLists)
290             tabUserLists->deleteLater();
291         if (tabServer)
292             tabServer->deleteLater();
293         if (tabDeckStorage)
294             tabDeckStorage->deleteLater();
295         if (tabReplays)
296             tabReplays->deleteLater();
297         if (tabAdmin)
298             tabAdmin->deleteLater();
299         if (tabLog)
300             tabLog->deleteLater();
301     }
302     tabUserLists = 0;
303     tabServer = 0;
304     tabDeckStorage = 0;
305     tabReplays = 0;
306     tabAdmin = 0;
307     tabLog = 0;
308 
309     QMapIterator<int, TabRoom *> roomIterator(roomTabs);
310     while (roomIterator.hasNext())
311         roomIterator.next().value()->deleteLater();
312     roomTabs.clear();
313 
314     QMapIterator<int, TabGame *> gameIterator(gameTabs);
315     while (gameIterator.hasNext())
316         gameIterator.next().value()->deleteLater();
317     gameTabs.clear();
318 
319     QListIterator<TabGame *> replayIterator(replayTabs);
320     while (replayIterator.hasNext())
321         replayIterator.next()->deleteLater();
322     replayTabs.clear();
323 
324     delete userInfo;
325     userInfo = 0;
326 }
327 
updatePingTime(int value,int max)328 void TabSupervisor::updatePingTime(int value, int max)
329 {
330     if (!tabServer)
331         return;
332     if (tabServer->getContentsChanged())
333         return;
334 
335     setTabIcon(indexOf(tabServer), QIcon(PingPixmapGenerator::generatePixmap(15, value, max)));
336 }
337 
closeButtonPressed()338 void TabSupervisor::closeButtonPressed()
339 {
340     Tab *tab = static_cast<Tab *>(static_cast<CloseButton *>(sender())->property("tab").value<QObject *>());
341     tab->closeRequest();
342 }
343 
addCloseButtonToTab(Tab * tab,int tabIndex)344 void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex)
345 {
346     QTabBar::ButtonPosition closeSide =
347         (QTabBar::ButtonPosition)tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tabBar());
348     CloseButton *closeButton = new CloseButton;
349     connect(closeButton, SIGNAL(clicked()), this, SLOT(closeButtonPressed()));
350     closeButton->setProperty("tab", QVariant::fromValue((QObject *)tab));
351     tabBar()->setTabButton(tabIndex, closeSide, closeButton);
352 }
353 
gameJoined(const Event_GameJoined & event)354 void TabSupervisor::gameJoined(const Event_GameJoined &event)
355 {
356     QMap<int, QString> roomGameTypes;
357     TabRoom *room = roomTabs.value(event.game_info().room_id());
358     if (room)
359         roomGameTypes = room->getGameTypes();
360     else
361         for (int i = 0; i < event.game_types_size(); ++i)
362             roomGameTypes.insert(event.game_types(i).game_type_id(),
363                                  QString::fromStdString(event.game_types(i).description()));
364 
365     TabGame *tab = new TabGame(this, QList<AbstractClient *>() << client, event, roomGameTypes);
366     connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *)));
367     connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool)));
368     connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
369     int tabIndex = myAddTab(tab);
370     addCloseButtonToTab(tab, tabIndex);
371     gameTabs.insert(event.game_info().game_id(), tab);
372     setCurrentWidget(tab);
373 }
374 
localGameJoined(const Event_GameJoined & event)375 void TabSupervisor::localGameJoined(const Event_GameJoined &event)
376 {
377     TabGame *tab = new TabGame(this, localClients, event, QMap<int, QString>());
378     connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *)));
379     connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
380     int tabIndex = myAddTab(tab);
381     addCloseButtonToTab(tab, tabIndex);
382     gameTabs.insert(event.game_info().game_id(), tab);
383     setCurrentWidget(tab);
384 
385     for (int i = 1; i < localClients.size(); ++i) {
386         Command_JoinGame cmd;
387         cmd.set_game_id(event.game_info().game_id());
388         localClients[i]->sendCommand(localClients[i]->prepareRoomCommand(cmd, 0));
389     }
390 }
391 
gameLeft(TabGame * tab)392 void TabSupervisor::gameLeft(TabGame *tab)
393 {
394     if (tab == currentWidget())
395         emit setMenu();
396 
397     gameTabs.remove(tab->getGameId());
398     removeTab(indexOf(tab));
399 
400     if (!localClients.isEmpty())
401         stop();
402 }
403 
addRoomTab(const ServerInfo_Room & info,bool setCurrent)404 void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent)
405 {
406     TabRoom *tab = new TabRoom(this, client, userInfo, info);
407     connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow()));
408     connect(tab, SIGNAL(roomClosing(TabRoom *)), this, SLOT(roomLeft(TabRoom *)));
409     connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool)));
410     int tabIndex = myAddTab(tab);
411     addCloseButtonToTab(tab, tabIndex);
412     roomTabs.insert(info.room_id(), tab);
413     if (setCurrent)
414         setCurrentWidget(tab);
415 }
416 
roomLeft(TabRoom * tab)417 void TabSupervisor::roomLeft(TabRoom *tab)
418 {
419     if (tab == currentWidget())
420         emit setMenu();
421 
422     roomTabs.remove(tab->getRoomId());
423     removeTab(indexOf(tab));
424 }
425 
openReplay(GameReplay * replay)426 void TabSupervisor::openReplay(GameReplay *replay)
427 {
428     TabGame *replayTab = new TabGame(this, replay);
429     connect(replayTab, SIGNAL(gameClosing(TabGame *)), this, SLOT(replayLeft(TabGame *)));
430     int tabIndex = myAddTab(replayTab);
431     addCloseButtonToTab(replayTab, tabIndex);
432     replayTabs.append(replayTab);
433     setCurrentWidget(replayTab);
434 }
435 
replayLeft(TabGame * tab)436 void TabSupervisor::replayLeft(TabGame *tab)
437 {
438     if (tab == currentWidget())
439         emit setMenu();
440 
441     replayTabs.removeAt(replayTabs.indexOf(tab));
442 }
443 
addMessageTab(const QString & receiverName,bool focus)444 TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus)
445 {
446     if (receiverName == QString::fromStdString(userInfo->name()))
447         return nullptr;
448 
449     ServerInfo_User otherUser;
450     UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(receiverName);
451     if (twi)
452         otherUser = twi->getUserInfo();
453     else
454         otherUser.set_name(receiverName.toStdString());
455 
456     TabMessage *tab;
457     tab = messageTabs.value(QString::fromStdString(otherUser.name()));
458     if (tab) {
459         if (focus)
460             setCurrentWidget(tab);
461         return tab;
462     }
463 
464     tab = new TabMessage(this, client, *userInfo, otherUser);
465     connect(tab, SIGNAL(talkClosing(TabMessage *)), this, SLOT(talkLeft(TabMessage *)));
466     connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow()));
467     int tabIndex = myAddTab(tab);
468     addCloseButtonToTab(tab, tabIndex);
469     messageTabs.insert(receiverName, tab);
470     if (focus)
471         setCurrentWidget(tab);
472     return tab;
473 }
474 
maximizeMainWindow()475 void TabSupervisor::maximizeMainWindow()
476 {
477     emit showWindowIfHidden();
478 }
479 
talkLeft(TabMessage * tab)480 void TabSupervisor::talkLeft(TabMessage *tab)
481 {
482     if (tab == currentWidget())
483         emit setMenu();
484 
485     messageTabs.remove(tab->getUserName());
486     removeTab(indexOf(tab));
487 }
488 
addDeckEditorTab(const DeckLoader * deckToOpen)489 TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
490 {
491     TabDeckEditor *tab = new TabDeckEditor(this);
492     if (deckToOpen)
493         tab->setDeck(new DeckLoader(*deckToOpen));
494     connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *)));
495     int tabIndex = myAddTab(tab);
496     addCloseButtonToTab(tab, tabIndex);
497     deckEditorTabs.append(tab);
498     setCurrentWidget(tab);
499     return tab;
500 }
501 
deckEditorClosed(TabDeckEditor * tab)502 void TabSupervisor::deckEditorClosed(TabDeckEditor *tab)
503 {
504     if (tab == currentWidget())
505         emit setMenu();
506 
507     deckEditorTabs.removeAt(deckEditorTabs.indexOf(tab));
508     removeTab(indexOf(tab));
509 }
510 
tabUserEvent(bool globalEvent)511 void TabSupervisor::tabUserEvent(bool globalEvent)
512 {
513     Tab *tab = static_cast<Tab *>(sender());
514     if (tab != currentWidget()) {
515         tab->setContentsChanged(true);
516         setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed"));
517     }
518     if (globalEvent && SettingsCache::instance().getNotificationsEnabled())
519         QApplication::alert(this);
520 }
521 
updateTabText(Tab * tab,const QString & newTabText)522 void TabSupervisor::updateTabText(Tab *tab, const QString &newTabText)
523 {
524     int idx = indexOf(tab);
525     setTabText(idx, sanitizeTabName(newTabText));
526     setTabToolTip(idx, sanitizeHtml(newTabText));
527 }
528 
processRoomEvent(const RoomEvent & event)529 void TabSupervisor::processRoomEvent(const RoomEvent &event)
530 {
531     TabRoom *tab = roomTabs.value(event.room_id(), 0);
532     if (tab)
533         tab->processRoomEvent(event);
534 }
535 
processGameEventContainer(const GameEventContainer & cont)536 void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
537 {
538     TabGame *tab = gameTabs.value(cont.game_id());
539     if (tab)
540         tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()));
541     else
542         qDebug() << "gameEvent: invalid gameId";
543 }
544 
processUserMessageEvent(const Event_UserMessage & event)545 void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event)
546 {
547     QString senderName = QString::fromStdString(event.sender_name());
548     TabMessage *tab = messageTabs.value(senderName);
549     if (!tab)
550         tab = messageTabs.value(QString::fromStdString(event.receiver_name()));
551     if (!tab) {
552         UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(senderName);
553         if (twi) {
554             UserLevelFlags userLevel = UserLevelFlags(twi->getUserInfo().user_level());
555             if (SettingsCache::instance().getIgnoreUnregisteredUserMessages() &&
556                 !userLevel.testFlag(ServerInfo_User::IsRegistered))
557                 // Flags are additive, so reg/mod/admin are all IsRegistered
558                 return;
559         }
560         tab = addMessageTab(QString::fromStdString(event.sender_name()), false);
561     }
562     if (!tab)
563         return;
564     tab->processUserMessageEvent(event);
565 }
566 
actShowPopup(const QString & message)567 void TabSupervisor::actShowPopup(const QString &message)
568 {
569     qDebug() << "ACT SHOW POPUP";
570     if (trayIcon && (QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) {
571         qDebug() << "LAUNCHING POPUP";
572         // disconnect(trayIcon, SIGNAL(messageClicked()), nullptr, nullptr);
573         trayIcon->showMessage(message, tr("Click to view"));
574         // connect(trayIcon, SIGNAL(messageClicked()), chatView, SLOT(actMessageClicked()));
575     }
576 }
577 
processUserLeft(const QString & userName)578 void TabSupervisor::processUserLeft(const QString &userName)
579 {
580     TabMessage *tab = messageTabs.value(userName);
581     if (tab)
582         tab->processUserLeft();
583 }
584 
processUserJoined(const ServerInfo_User & userInfoJoined)585 void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
586 {
587     QString userName = QString::fromStdString(userInfoJoined.name());
588     if (isUserBuddy(userName)) {
589         Tab *tab = static_cast<Tab *>(getUserListsTab());
590 
591         if (tab != currentWidget()) {
592             tab->setContentsChanged(true);
593             QPixmap avatarPixmap =
594                 UserLevelPixmapGenerator::generatePixmap(13, (UserLevelFlags)userInfoJoined.user_level(), true,
595                                                          QString::fromStdString(userInfoJoined.privlevel()));
596             setTabIcon(indexOf(tab), QPixmap(avatarPixmap));
597         }
598 
599         if (SettingsCache::instance().getBuddyConnectNotificationsEnabled()) {
600             QApplication::alert(this);
601             this->actShowPopup(tr("Your buddy %1 has signed on!").arg(userName));
602         }
603     }
604 
605     TabMessage *tab = messageTabs.value(userName);
606     if (tab)
607         tab->processUserJoined(userInfoJoined);
608 }
609 
updateCurrent(int index)610 void TabSupervisor::updateCurrent(int index)
611 {
612     if (index != -1) {
613         Tab *tab = static_cast<Tab *>(widget(index));
614         if (tab->getContentsChanged()) {
615             setTabIcon(index, QIcon());
616             tab->setContentsChanged(false);
617         }
618         emit setMenu(static_cast<Tab *>(widget(index))->getTabMenus());
619         tab->tabActivated();
620     } else
621         emit setMenu();
622 }
623 
624 /**
625  * Determine if a user is a moderator/administrator
626  * By seeing if they have the admin tab open & unlocked
627  * @return if the admin tab is open & unlocked
628  */
getAdminLocked() const629 bool TabSupervisor::getAdminLocked() const
630 {
631     if (!tabAdmin)
632         return true;
633     return tabAdmin->getLocked();
634 }
635 
processNotifyUserEvent(const Event_NotifyUser & event)636 void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event)
637 {
638 
639     switch ((Event_NotifyUser::NotificationType)event.type()) {
640         case Event_NotifyUser::UNKNOWN:
641             QMessageBox::information(
642                 this, tr("Unknown Event"),
643                 tr("The server has sent you a message that your client does not understand.\nThis message might mean "
644                    "there is a new version of Cockatrice available or this server is running a custom or pre-release "
645                    "version.\n\nTo update your client, go to Help -> Check for Updates."));
646             break;
647         case Event_NotifyUser::IDLEWARNING:
648             QMessageBox::information(this, tr("Idle Timeout"), tr("You are about to be logged out due to inactivity."));
649             break;
650         case Event_NotifyUser::PROMOTED:
651             QMessageBox::information(
652                 this, tr("Promotion"),
653                 tr("You have been promoted. Please log out and back in for changes to take effect."));
654             break;
655         case Event_NotifyUser::WARNING: {
656             if (!QString::fromStdString(event.warning_reason()).simplified().isEmpty())
657                 QMessageBox::warning(this, tr("Warned"),
658                                      tr("You have received a warning due to %1.\nPlease refrain from engaging in this "
659                                         "activity or further actions may be taken against you. If you have any "
660                                         "questions, please private message a moderator.")
661                                          .arg(QString::fromStdString(event.warning_reason()).simplified()));
662             break;
663         }
664         case Event_NotifyUser::CUSTOM: {
665             if (!QString::fromStdString(event.custom_title()).simplified().isEmpty() &&
666                 !QString::fromStdString(event.custom_content()).simplified().isEmpty()) {
667                 QMessageBox msgBox;
668                 msgBox.setParent(this);
669                 msgBox.setWindowFlags(Qt::Dialog);
670                 msgBox.setIcon(QMessageBox::Information);
671                 msgBox.setWindowTitle(QString::fromStdString(event.custom_title()).simplified());
672                 msgBox.setText(tr("You have received the following message from the server.\n(custom messages like "
673                                   "these could be untranslated)"));
674                 msgBox.setDetailedText(QString::fromStdString(event.custom_content()).simplified());
675                 msgBox.setMinimumWidth(200);
676                 msgBox.exec();
677             }
678             break;
679         }
680         default:;
681     }
682 }
683 
isOwnUserRegistered() const684 bool TabSupervisor::isOwnUserRegistered() const
685 {
686     return static_cast<bool>(getUserInfo()->user_level() & ServerInfo_User::IsRegistered);
687 }
688 
getOwnUsername() const689 QString TabSupervisor::getOwnUsername() const
690 {
691     return userInfo ? QString::fromStdString(userInfo->name()) : QString();
692 }
693 
isUserBuddy(const QString & userName) const694 bool TabSupervisor::isUserBuddy(const QString &userName) const
695 {
696     if (!getUserListsTab())
697         return false;
698     if (!getUserListsTab()->getBuddyList())
699         return false;
700     QMap<QString, UserListTWI *> buddyList = getUserListsTab()->getBuddyList()->getUsers();
701     bool senderIsBuddy = buddyList.contains(userName);
702     return senderIsBuddy;
703 }
704 
isUserIgnored(const QString & userName) const705 bool TabSupervisor::isUserIgnored(const QString &userName) const
706 {
707     if (!getUserListsTab())
708         return false;
709     if (!getUserListsTab()->getIgnoreList())
710         return false;
711     QMap<QString, UserListTWI *> buddyList = getUserListsTab()->getIgnoreList()->getUsers();
712     bool senderIsBuddy = buddyList.contains(userName);
713     return senderIsBuddy;
714 }
715 
getOnlineUser(const QString & userName) const716 const ServerInfo_User *TabSupervisor::getOnlineUser(const QString &userName) const
717 {
718     if (!getUserListsTab())
719         return nullptr;
720     if (!getUserListsTab()->getAllUsersList())
721         return nullptr;
722     QMap<QString, UserListTWI *> userList = getUserListsTab()->getAllUsersList()->getUsers();
723     const QString &userNameToMatchLower = userName.toLower();
724     QMap<QString, UserListTWI *>::iterator i;
725 
726     for (i = userList.begin(); i != userList.end(); ++i)
727         if (i.key().toLower() == userNameToMatchLower) {
728             const ServerInfo_User &userInfo = i.value()->getUserInfo();
729             return &userInfo;
730         }
731 
732     return nullptr;
733 };
734