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