1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2007-2013 Licq developers <licq-dev@googlegroups.com>
4  *
5  * Licq is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * Licq is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Licq; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "usereventcommon.h"
21 
22 #include "config.h"
23 
24 #include <QApplication>
25 #include <QDateTime>
26 #include <QHBoxLayout>
27 #include <QMenu>
28 #include <QTimer>
29 #include <QToolBar>
30 #include <QToolButton>
31 #include <QVBoxLayout>
32 
33 #include <licq/contactlist/user.h>
34 #include <licq/contactlist/usermanager.h>
35 #include <licq/plugin/pluginmanager.h>
36 #include <licq/pluginsignal.h>
37 #include <licq/userevents.h>
38 
39 #include "config/chat.h"
40 #include "config/iconmanager.h"
41 #include "config/shortcuts.h"
42 
43 #include "core/licqgui.h"
44 #include "core/messagebox.h"
45 #include "core/signalmanager.h"
46 #include "core/usermenu.h"
47 
48 #include "dialogs/historydlg.h"
49 #include "dialogs/keyrequestdlg.h"
50 #include "userdlg/userdlg.h"
51 
52 #include "helpers/support.h"
53 #include "helpers/usercodec.h"
54 
55 #include "widgets/infofield.h"
56 
57 #include "usereventtabdlg.h"
58 
59 using namespace LicqQtGui;
60 /* TRANSLATOR LicqQtGui::UserEventCommon */
61 
62 using std::list;
63 using std::string;
64 
UserEventCommon(const Licq::UserId & userId,QWidget * parent,const char * name)65 UserEventCommon::UserEventCommon(const Licq::UserId& userId, QWidget* parent, const char* name)
66   : QWidget(parent),
67     myPpid(userId.protocolId()),
68     myHighestEventId(-1)
69 {
70   Support::setWidgetProps(this, name);
71   setAttribute(Qt::WA_DeleteOnClose, true);
72 
73   myUsers.push_back(userId);
74 
75   // Find out what's supported for the protocol
76   mySendFuncs = 0;
77   Licq::ProtocolPlugin::Ptr protocol = Licq::gPluginManager.getProtocolPlugin(myPpid);
78   if (protocol.get() != NULL)
79     mySendFuncs = protocol->capabilities();
80 
81   myIsOwner = myUsers.front().isOwner();
82   myDeleteUser = false;
83   myConvoId = 0;
84 
85   myTophLayout = new QHBoxLayout(this);
86   myTopLayout = new QVBoxLayout();
87   myTophLayout->addLayout(myTopLayout);
88   myTophLayout->setStretchFactor(myTopLayout, 1);
89 
90   QHBoxLayout* layt = new QHBoxLayout();
91   myTopLayout->addLayout(layt);
92 
93   myToolBar = new QToolBar();
94   myToolBar->setIconSize(QSize(16, 16));
95   layt->addWidget(myToolBar);
96 
97   layt->addStretch(1);
98 
99   myTimezone = new InfoField(true);
100   myTimezone->setToolTip(tr("User's current local time"));
101   int timezoneWidth =
102     qMax(myTimezone->fontMetrics().width("88:88:88"),
103          myTimezone->fontMetrics().width(tr("Unknown")))
104          + 10;
105   myTimezone->setFixedWidth(timezoneWidth);
106   myTimezone->setAlignment(Qt::AlignCenter);
107   myTimezone->setFocusPolicy(Qt::ClickFocus);
108   layt->addWidget(myTimezone);
109 
110   myMenu = myToolBar->addAction(tr("Menu"), this, SLOT(showUserMenu()));
111   myMenu->setMenu(gUserMenu);
112   if (myIsOwner)
113     myMenu->setEnabled(false);
114 
115   myHistory = myToolBar->addAction(tr("History..."), this, SLOT(showHistory()));
116   myInfo = myToolBar->addAction(tr("User Info..."), this, SLOT(showUserInfo()));
117 
118   myEncodingsMenu = new QMenu(this);
119   myEncoding = myToolBar->addAction(tr("Encoding"), this, SLOT(showEncodingsMenu()));
120   myEncoding->setMenu(myEncodingsMenu);
121   if (!(mySendFuncs & Licq::ProtocolPlugin::CanVaryEncoding))
122     myEncoding->setVisible(false);
123 
124   myToolBar->addSeparator();
125 
126   mySecure = myToolBar->addAction(tr("Secure Channel"), this, SLOT(switchSecurity()));
127   if (!(mySendFuncs & Licq::ProtocolPlugin::CanSendSecure))
128     mySecure->setEnabled(false);
129 
130   myTimeTimer = NULL;
131 
132   QString userEncoding;
133   {
134     Licq::UserReadGuard u(myUsers.front());
135     if (u.isLocked())
136     {
137       if (u->NewMessages() == 0)
138         setWindowIcon(IconManager::instance()->iconForUser(*u));
139       else
140       {
141         setWindowIcon(IconManager::instance()->iconForEvent(Licq::UserEvent::TypeMessage));
142         flashTaskbar();
143       }
144 
145       updateWidgetInfo(*u);
146 
147       // restore prefered encoding
148       userEncoding = u->userEncoding().c_str();
149 
150       setTyping(u->isTyping());
151     }
152     else
153     {
154       userEncoding = Licq::gUserManager.defaultUserEncoding().c_str();
155     }
156   }
157 
158   myEncodingsGroup = new QActionGroup(this);
159   connect(myEncodingsGroup, SIGNAL(triggered(QAction*)), SLOT(setEncoding(QAction*)));
160 
161   // populate the popup menu
162   for (int i = 0; UserCodec::m_encodings[i].encoding != NULL; ++i)
163   {
164     UserCodec::encoding_t* it = &UserCodec::m_encodings[i];
165     bool currentCodec = it->encoding == userEncoding;
166 
167     if (!currentCodec && !Config::Chat::instance()->showAllEncodings() && !it->isMinimal)
168       continue;
169 
170     QAction* a = new QAction(UserCodec::nameForEncoding(i), myEncodingsGroup);
171     a->setCheckable(true);
172     a->setData(i);
173 
174     if (currentCodec)
175       a->setChecked(true);
176 
177     if (currentCodec && !Config::Chat::instance()->showAllEncodings() && !it->isMinimal)
178     {
179       // if the current encoding does not appear in the minimal list
180       myEncodingsMenu->insertSeparator(myEncodingsMenu->actions()[0]);
181       myEncodingsMenu->insertAction(myEncodingsMenu->actions()[0], a);
182     }
183     else
184     {
185       myEncodingsMenu->addAction(a);
186     }
187   }
188 
189   myPopupNextMessage = new QAction("Popup Next Message", this);
190   addAction(myPopupNextMessage);
191   connect(myPopupNextMessage, SIGNAL(triggered()), gLicqGui, SLOT(showNextEvent()));
192 
193   // We might be called from a slot so connect the signal only after all the
194   // existing signals are handled.
195   QTimer::singleShot(0, this, SLOT(connectSignal()));
196 
197   myMainWidget = new QVBoxLayout();
198   myMainWidget->setContentsMargins(0, 0, 0, 0);
199   myTopLayout->addLayout(myMainWidget);
200 
201   updateIcons();
202   updateShortcuts();
203   connect(IconManager::instance(), SIGNAL(generalIconsChanged()), SLOT(updateIcons()));
204   connect(Config::Shortcuts::instance(), SIGNAL(shortcutsChanged()), SLOT(updateShortcuts()));
205 
206   // Check if we want the window sticky
207   if (!Config::Chat::instance()->tabbedChatting() &&
208       Config::Chat::instance()->msgWinSticky())
209     QTimer::singleShot(100, this, SLOT(setMsgWinSticky()));
210 }
211 
~UserEventCommon()212 UserEventCommon::~UserEventCommon()
213 {
214   emit finished(myUsers.front());
215 
216   if (myDeleteUser && !myIsOwner)
217     gLicqGui->removeUserFromList(myUsers.front(), this);
218 
219   myUsers.clear();
220 }
221 
updateIcons()222 void UserEventCommon::updateIcons()
223 {
224   IconManager* iconman = IconManager::instance();
225 
226   myMenu->setIcon(iconman->getIcon(IconManager::MenuIcon));
227   myHistory->setIcon(iconman->getIcon(IconManager::HistoryIcon));
228   myInfo->setIcon(iconman->getIcon(IconManager::InfoIcon));
229   myEncoding->setIcon(iconman->getIcon(IconManager::EncodingIcon));
230 }
231 
updateShortcuts()232 void UserEventCommon::updateShortcuts()
233 {
234   Config::Shortcuts* shortcuts = Config::Shortcuts::instance();
235 
236   myPopupNextMessage->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatPopupNextMessage));
237 
238   myMenu->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatUserMenu));
239   myHistory->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatHistory));
240   myInfo->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatUserInfo));
241   myEncoding->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatEncodingMenu));
242   mySecure->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatToggleSecure));
243 
244   // Tooltips include shortcut so update them here as well
245   pushToolTip(myMenu, tr("Open user menu"));
246   pushToolTip(myHistory, tr("Show user history"));
247   pushToolTip(myInfo, tr("Show user information"));
248   pushToolTip(myEncoding, tr("Select the text encoding used for outgoing messages."));
249   pushToolTip(mySecure, tr("Open / close secure channel"));
250 }
251 
isUserInConvo(const Licq::UserId & userId) const252 bool UserEventCommon::isUserInConvo(const Licq::UserId& userId) const
253 {
254   bool found = (std::find(myUsers.begin(), myUsers.end(), userId) != myUsers.end());
255   return found;
256 }
257 
setTyping(bool isTyping)258 void UserEventCommon::setTyping(bool isTyping)
259 {
260   if (isTyping)
261   {
262     QPalette p = myTimezone->palette();
263     p.setColor(myTimezone->backgroundRole(), Config::Chat::instance()->tabTypingColor());
264     myTimezone->setPalette(p);
265   }
266   else
267   {
268     myTimezone->setPalette(QPalette());
269   }
270 }
271 
flashTaskbar()272 void UserEventCommon::flashTaskbar()
273 {
274   if (Config::Chat::instance()->flashTaskbar())
275     QApplication::alert(this);
276 }
277 
updateWidgetInfo(const Licq::User * u)278 void UserEventCommon::updateWidgetInfo(const Licq::User* u)
279 {
280   if (u->timezone() == Licq::User::TimezoneUnknown)
281     myTimezone->setText(tr("Unknown"));
282   else
283   {
284     myRemoteTimeOffset = u->LocalTimeOffset();
285     updateTime();
286 
287     if (myTimeTimer == NULL)
288     {
289       myTimeTimer = new QTimer(this);
290       connect(myTimeTimer, SIGNAL(timeout()), SLOT(updateTime()));
291       myTimeTimer->start(3000);
292     }
293   }
294 
295   if (u->Secure())
296     mySecure->setIcon(IconManager::instance()->getIcon(IconManager::SecureOnIcon));
297   else
298     mySecure->setIcon(IconManager::instance()->getIcon(IconManager::SecureOffIcon));
299 
300   QString tmp = QString::fromUtf8(u->getFullName().c_str());
301   if (!tmp.isEmpty())
302     tmp = " (" + tmp + ")";
303   myBaseTitle = QString::fromUtf8(u->getAlias().c_str()) + tmp;
304 
305   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
306   if (tabDlg != NULL && tabDlg->tabIsSelected(this))
307   {
308     tabDlg->setWindowTitle(myBaseTitle);
309     tabDlg->setWindowIconText(QString::fromUtf8(u->getAlias().c_str()));
310   }
311   else
312   {
313     setWindowTitle(myBaseTitle);
314     setWindowIconText(QString::fromUtf8(u->getAlias().c_str()));
315   }
316 }
317 
pushToolTip(QAction * action,const QString & tooltip)318 void UserEventCommon::pushToolTip(QAction* action, const QString& tooltip)
319 {
320   if (action == 0 || tooltip.isEmpty())
321     return;
322 
323   QString newtip = tooltip;
324 
325   if (!action->shortcut().isEmpty())
326     newtip += " (" + action->shortcut().toString(QKeySequence::NativeText) + ")";
327 
328   action->setToolTip(newtip);
329 }
330 
connectSignal()331 void UserEventCommon::connectSignal()
332 {
333   connect(gGuiSignalManager,
334       SIGNAL(updatedUser(const Licq::UserId&, unsigned long, int, unsigned long)),
335       SLOT(updatedUser(const Licq::UserId&, unsigned long, int, unsigned long)));
336 }
337 
setEncoding(QAction * action)338 void UserEventCommon::setEncoding(QAction* action)
339 {
340   int index = action->data().toUInt();
341 
342   /* initialize a codec according to the encoding menu item id */
343   QString encoding = UserCodec::m_encodings[index].encoding;
344 
345   if (!encoding.isNull())
346   {
347     /* save preferred character set */
348     {
349       Licq::UserWriteGuard u(myUsers.front());
350       if (u.isLocked())
351       {
352         u->SetEnableSave(false);
353         u->setUserEncoding(encoding.toLocal8Bit().constData());
354         u->SetEnableSave(true);
355         u->save(Licq::User::SaveLicqInfo);
356       }
357     }
358 
359     emit encodingChanged();
360   }
361 }
362 
setMsgWinSticky(bool sticky)363 void UserEventCommon::setMsgWinSticky(bool sticky)
364 {
365   Support::changeWinSticky(winId(), sticky);
366 }
367 
showHistory()368 void UserEventCommon::showHistory()
369 {
370   new HistoryDlg(myUsers.front());
371 }
372 
showUserInfo()373 void UserEventCommon::showUserInfo()
374 {
375   UserDlg::showDialog(myUsers.front());
376 }
377 
switchSecurity()378 void UserEventCommon::switchSecurity()
379 {
380   new KeyRequestDlg(myUsers.front());
381 }
382 
updateTime()383 void UserEventCommon::updateTime()
384 {
385   QDateTime t;
386   t.setTime_t(time(NULL) + myRemoteTimeOffset);
387   myTimezone->setText(t.time().toString());
388 }
389 
showUserMenu()390 void UserEventCommon::showUserMenu()
391 {
392   // Tell menu which contact to use and show it immediately.
393   // Menu is normally delayed but if we use InstantPopup mode we won't get
394   //   this signal so we can't tell menu which contact to use.
395   gUserMenu->setUser(myUsers.front());
396   dynamic_cast<QToolButton*>(myToolBar->widgetForAction(myMenu))->showMenu();
397 }
398 
showEncodingsMenu()399 void UserEventCommon::showEncodingsMenu()
400 {
401   // Menu is normally delayed but if we use InstantPopup mode shortcut won't work
402   dynamic_cast<QToolButton*>(myToolBar->widgetForAction(myEncoding))->showMenu();
403 }
404 
updatedUser(const Licq::UserId & userId,unsigned long subSignal,int argument,unsigned long cid)405 void UserEventCommon::updatedUser(const Licq::UserId& userId, unsigned long subSignal, int argument, unsigned long cid)
406 {
407   if (!isUserInConvo(userId))
408   {
409     if (myConvoId != 0 && cid == myConvoId)
410     {
411       myUsers.push_back(userId);
412 
413       // Now update the tab label
414       UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
415       if (tabDlg != NULL)
416         tabDlg->updateConvoLabel(this);
417     }
418     else
419     {
420       return;
421     }
422   }
423 
424   Licq::UserReadGuard u(userId);
425   if (!u.isLocked())
426     return;
427 
428   switch (subSignal)
429   {
430     case Licq::PluginSignal::UserStatus:
431       if (u->NewMessages() == 0)
432         setWindowIcon(IconManager::instance()->iconForUser(*u));
433       break;
434 
435     case Licq::PluginSignal::UserBasic:
436     case Licq::PluginSignal::UserInfo: // For time zone
437     case Licq::PluginSignal::UserSecurity:
438       updateWidgetInfo(*u);
439       break;
440 
441     case Licq::PluginSignal::UserEvents:
442       if (u->NewMessages() == 0)
443         setWindowIcon(IconManager::instance()->iconForUser(*u));
444       else
445       {
446         setWindowIcon(IconManager::instance()->iconForEvent(Licq::UserEvent::TypeMessage));
447         flashTaskbar();
448       }
449 
450       break;
451   }
452 
453   u.unlock();
454 
455   // Call the event specific function now
456   userUpdated(userId, subSignal, argument, cid);
457 }
458 
focusChanged(bool gotFocus)459 void UserEventCommon::focusChanged(bool gotFocus)
460 {
461   // Check if we should block on events, but always unblock in case we might leave a user blocked
462   if (gotFocus && !Config::Chat::instance()->noSoundInActiveChat())
463     return;
464 
465   Licq::UserWriteGuard user(userId());
466   if (user.isLocked())
467     user->setOnEventsBlocked(gotFocus);
468 }
469 
event(QEvent * event)470 bool UserEventCommon::event(QEvent* event)
471 {
472   // Mark/unmark user as active user when we get/loose focus
473   if (event->type() == QEvent::WindowActivate || event->type() == QEvent::ShowToParent)
474     focusChanged(true);
475   if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::HideToParent)
476     focusChanged(false);
477 
478   return QWidget::event(event);
479 }
480