root/trunk/qt4-gui/src/userevents/usereventcommon.cpp

Revision 6463, 14.2 kB (checked in by flynd, 4 months ago)

Use a const pointer for user objects that are only fetched for read access. Fixed some places where we changed the user even though we just had a read lock.

Line 
1// -*- c-basic-offset: 2 -*-
2/*
3 * This file is part of Licq, an instant messaging client for UNIX.
4 * Copyright (C) 2007 Licq developers
5 *
6 * Licq is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Licq is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Licq; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20
21#include "usereventcommon.h"
22
23#include "config.h"
24
25#include <QApplication>
26#include <QDateTime>
27#include <QHBoxLayout>
28#include <QMenu>
29#include <QTextCodec>
30#include <QTimer>
31#include <QToolBar>
32#include <QToolButton>
33#include <QVBoxLayout>
34
35#include <licq_icqd.h>
36#include <licq_user.h>
37
38#include "config/chat.h"
39#include "config/iconmanager.h"
40
41#include "core/licqgui.h"
42#include "core/messagebox.h"
43#include "core/signalmanager.h"
44#include "core/usermenu.h"
45
46#include "dialogs/historydlg.h"
47#include "dialogs/keyrequestdlg.h"
48
49#include "helpers/support.h"
50#include "helpers/usercodec.h"
51
52#include "widgets/infofield.h"
53
54#include "usereventtabdlg.h"
55
56using namespace LicqQtGui;
57/* TRANSLATOR LicqQtGui::UserEventCommon */
58
59using std::list;
60using std::string;
61
62UserEventCommon::UserEventCommon(QString id, unsigned long ppid, QWidget* parent, const char* name)
63  : QWidget(parent),
64    myPpid(ppid),
65    myHighestEventId(-1)
66{
67  Support::setWidgetProps(this, name);
68  setAttribute(Qt::WA_DeleteOnClose, true);
69
70  if (!id.isEmpty())
71  {
72    char* realId = 0;
73    ICQUser::MakeRealId(id.toLatin1(), myPpid, realId);
74    myId = realId;
75    myUsers.push_back(realId);
76    delete [] realId;
77  }
78
79  // Find out what's supported for this protocol
80  mySendFuncs = 0xFFFFFFFF;
81  if (ppid != LICQ_PPID)
82  {
83    FOR_EACH_PROTO_PLUGIN_START(gLicqDaemon)
84    {
85      if ((*_ppit)->PPID() == ppid)
86      {
87        mySendFuncs = (*_ppit)->SendFunctions();
88        break;
89      }
90    }
91    FOR_EACH_PROTO_PLUGIN_END
92  }
93
94  myCodec = QTextCodec::codecForLocale();
95  myIsOwner = (gUserManager.FindOwner(myUsers.front().c_str(), myPpid) != NULL);
96  myDeleteUser = false;
97  myConvoId = 0;
98
99  myTophLayout = new QHBoxLayout(this);
100  myTopLayout = new QVBoxLayout();
101  myTophLayout->addLayout(myTopLayout);
102  myTophLayout->setStretchFactor(myTopLayout, 1);
103
104  QHBoxLayout* layt = new QHBoxLayout();
105  myTopLayout->addLayout(layt);
106
107  myToolBar = new QToolBar();
108  myToolBar->setIconSize(QSize(16, 16));
109  layt->addWidget(myToolBar);
110
111  layt->addStretch(1);
112
113  myTimezone = new InfoField(true);
114  myTimezone->setToolTip(tr("User's current local time"));
115  int timezoneWidth = 
116    qMax(myTimezone->fontMetrics().width("88:88:88"),
117         myTimezone->fontMetrics().width(tr("Unknown")))
118         + 10;
119  myTimezone->setFixedWidth(timezoneWidth);
120  myTimezone->setAlignment(Qt::AlignCenter);
121  myTimezone->setFocusPolicy(Qt::ClickFocus);
122  layt->addWidget(myTimezone);
123
124  myMenu = myToolBar->addAction(tr("Menu"), this, SLOT(showUserMenu()));
125  myMenu->setShortcut(Qt::ALT + Qt::Key_M);
126  pushToolTip(myMenu, tr("Open user menu"));
127  myMenu->setMenu(LicqGui::instance()->userMenu());
128  if (myIsOwner)
129    myMenu->setEnabled(false);
130
131  myHistory = myToolBar->addAction(tr("History..."), this, SLOT(showHistory()));
132  myHistory->setShortcut(Qt::ALT + Qt::Key_H);
133  pushToolTip(myHistory, tr("Show user history"));
134
135  myInfo = myToolBar->addAction(tr("User Info..."), this, SLOT(showUserInfo()));
136  myInfo->setShortcut(Qt::ALT + Qt::Key_I);
137  pushToolTip(myInfo, tr("Show user information"));
138
139  myEncodingsMenu = new QMenu(this);
140
141  myEncoding = myToolBar->addAction(tr("Encoding"), this, SLOT(showEncodingsMenu()));
142  myEncoding->setShortcut(Qt::ALT + Qt::Key_O);
143  pushToolTip(myEncoding, tr("Select the text encoding used for outgoing messages."));
144  myEncoding->setMenu(myEncodingsMenu);
145
146  myToolBar->addSeparator();
147
148  mySecure = myToolBar->addAction(tr("Secure Channel"), this, SLOT(switchSecurity()));
149  mySecure->setShortcut(Qt::ALT + Qt::Key_E);
150  pushToolTip(mySecure, tr("Open / Close secure channel"));
151  if (!(mySendFuncs & PP_SEND_SECURE))
152    mySecure->setEnabled(false);
153
154  myTimeTimer = NULL;
155  myTypingTimer = NULL;
156
157  const ICQUser* u = gUserManager.FetchUser(myUsers.front().c_str(), myPpid, LOCK_R);
158  if (u != NULL)
159  {
160    if (u->NewMessages() == 0)
161      setWindowIcon(IconManager::instance()->iconForStatus(u->StatusFull(), u->IdString(), u->PPID()));
162    else
163    {
164      setWindowIcon(IconManager::instance()->iconForEvent(ICQ_CMDxSUB_MSG));
165      flashTaskbar();
166    }
167
168    updateWidgetInfo(u);
169
170    // restore prefered encoding
171    myCodec = UserCodec::codecForICQUser(u);
172
173    setTyping(u->GetTyping());
174    gUserManager.DropUser(u);
175  }
176
177  myEncodingsGroup = new QActionGroup(this);
178  connect(myEncodingsGroup, SIGNAL(triggered(QAction*)), SLOT(setEncoding(QAction*)));
179
180  QString codec_name = QString::fromLatin1(myCodec->name()).toLower();
181
182  // populate the popup menu
183  for (UserCodec::encoding_t* it = &UserCodec::m_encodings[0]; it->encoding != NULL; ++it)
184  {
185    // Use check_codec since the QTextCodec name will be different from the
186    // user codec. But QTextCodec will recognize both, so let's make it standard
187    // for the purpose of checking for the same string.
188    QTextCodec* check_codec = QTextCodec::codecForName(it->encoding);
189
190    bool currentCodec = check_codec != NULL && QString::fromLatin1(check_codec->name()).toLower() == codec_name;
191
192    if (!currentCodec && !Config::Chat::instance()->showAllEncodings() && !it->isMinimal)
193      continue;
194
195    QAction* a = new QAction(UserCodec::nameForEncoding(it->encoding), myEncodingsGroup);
196    a->setCheckable(true);
197    a->setData(it->mib);
198
199    if (currentCodec)
200      a->setChecked(true);
201
202    if (currentCodec && !Config::Chat::instance()->showAllEncodings() && !it->isMinimal)
203    {
204      // if the current encoding does not appear in the minimal list
205      myEncodingsMenu->insertSeparator(myEncodingsMenu->actions()[0]);
206      myEncodingsMenu->insertAction(myEncodingsMenu->actions()[0], a);
207    }
208    else
209    {
210      myEncodingsMenu->addAction(a);
211    }
212  }
213
214  // We might be called from a slot so connect the signal only after all the
215  // existing signals are handled.
216  QTimer::singleShot(0, this, SLOT(connectSignal()));
217
218  myMainWidget = new QVBoxLayout();
219  myMainWidget->setContentsMargins(0, 0, 0, 0);
220  myTopLayout->addLayout(myMainWidget);
221
222  updateIcons();
223  connect(IconManager::instance(), SIGNAL(generalIconsChanged()), SLOT(updateIcons()));
224
225  // Check if we want the window sticky
226  if (!Config::Chat::instance()->tabbedChatting() &&
227      Config::Chat::instance()->msgWinSticky())
228    QTimer::singleShot(100, this, SLOT(setMsgWinSticky()));
229}
230
231UserEventCommon::~UserEventCommon()
232{
233  emit finished(myUsers.front().c_str(), myPpid);
234
235  if (myDeleteUser && !myIsOwner)
236    LicqGui::instance()->removeUserFromList(strdup(myUsers.front().c_str()), myPpid, this);
237
238  myUsers.clear();
239}
240
241void UserEventCommon::updateIcons()
242{
243  IconManager* iconman = IconManager::instance();
244
245  myMenu->setIcon(iconman->getIcon(IconManager::MenuIcon));
246  myHistory->setIcon(iconman->getIcon(IconManager::HistoryIcon));
247  myInfo->setIcon(iconman->getIcon(IconManager::InfoIcon));
248  myEncoding->setIcon(iconman->getIcon(IconManager::EncodingIcon));
249}
250
251bool UserEventCommon::isUserInConvo(QString id)
252{
253  char* realId;
254  ICQUser::MakeRealId(id.toLatin1(), myPpid, realId);
255  bool found = (std::find(myUsers.begin(), myUsers.end(), realId) != myUsers.end());
256  delete [] realId;
257  return found;
258}
259
260void UserEventCommon::setTyping(unsigned short type)
261{
262  if (type == ICQ_TYPING_ACTIVE)
263  {
264    if (myTypingTimer->isActive())
265      myTypingTimer->stop();
266    myTypingTimer->setSingleShot(true);
267    myTypingTimer->start(10000);
268
269    QPalette p = myTimezone->palette();
270    p.setColor(myTimezone->backgroundRole(), Config::Chat::instance()->tabTypingColor());
271    myTimezone->setPalette(p);
272  }
273  else
274  {
275    myTimezone->setPalette(QPalette());
276  }
277}
278
279void UserEventCommon::flashTaskbar()
280{
281  if (Config::Chat::instance()->flashTaskbar())
282    QApplication::alert(this);
283}
284
285void UserEventCommon::updateWidgetInfo(const ICQUser* u)
286{
287  QTextCodec* codec = UserCodec::codecForICQUser(u);
288
289  if (u->GetTimezone() == TIMEZONE_UNKNOWN)
290    myTimezone->setText(tr("Unknown"));
291  else
292  {
293    myRemoteTimeOffset = u->LocalTimeOffset();
294    updateTime();
295
296    if (myTimeTimer == NULL)
297    {
298      myTimeTimer = new QTimer(this);
299      connect(myTimeTimer, SIGNAL(timeout()), SLOT(updateTime()));
300      myTimeTimer->start(3000);
301    }
302  }
303
304  if (myTypingTimer == NULL)
305  {
306    myTypingTimer = new QTimer(this);
307    connect(myTypingTimer, SIGNAL(timeout()), SLOT(updateTyping()));
308  }
309
310  if (u->Secure())
311    mySecure->setIcon(IconManager::instance()->getIcon(IconManager::SecureOnIcon));
312  else
313    mySecure->setIcon(IconManager::instance()->getIcon(IconManager::SecureOffIcon));
314
315  QString tmp = codec->toUnicode(u->GetFirstName());
316  QString lastname = codec->toUnicode(u->GetLastName());
317  if (!tmp.isEmpty() && !lastname.isEmpty())
318    tmp += " ";
319  tmp += lastname;
320  if (!tmp.isEmpty())
321    tmp = " (" + tmp + ")";
322  myBaseTitle = QString::fromUtf8(u->GetAlias()) + tmp;
323
324  UserEventTabDlg* tabDlg = LicqGui::instance()->userEventTabDlg();
325  if (tabDlg != NULL && tabDlg->tabIsSelected(this))
326  {
327    tabDlg->setWindowTitle(myBaseTitle);
328    tabDlg->setWindowIconText(QString::fromUtf8(u->GetAlias()));
329  }
330  else
331  {
332    setWindowTitle(myBaseTitle);
333    setWindowIconText(QString::fromUtf8(u->GetAlias()));
334  }
335}
336
337void UserEventCommon::pushToolTip(QAction* action, QString tooltip)
338{
339  if (action == 0 || tooltip.isEmpty())
340    return;
341
342  QString newtip = tooltip;
343
344  if (!action->shortcut().isEmpty())
345    newtip += " (" + action->shortcut().toString(QKeySequence::NativeText) + ")";
346
347  action->setToolTip(newtip);
348}
349
350void UserEventCommon::connectSignal()
351{
352  connect(LicqGui::instance()->signalManager(),
353      SIGNAL(updatedUser(CICQSignal*)), SLOT(updatedUser(CICQSignal*)));
354}
355
356void UserEventCommon::setEncoding(QAction* action)
357{
358  int encodingMib = action->data().toUInt();
359
360  /* initialize a codec according to the encoding menu item id */
361  QString encoding(UserCodec::encodingForMib(encodingMib));
362
363  if (!encoding.isNull())
364  {
365    QTextCodec* codec = QTextCodec::codecForName(encoding.toLatin1());
366    if (codec == NULL)
367    {
368      WarnUser(this, tr("Unable to load encoding <b>%1</b>.<br>"
369            "Message contents may appear garbled.").arg(encoding));
370      return;
371    }
372    myCodec = codec;
373
374    /* save preferred character set */
375    ICQUser* u = gUserManager.FetchUser(myUsers.front().c_str(), myPpid, LOCK_W);
376    if (u != NULL)
377    {
378      u->SetEnableSave(false);
379      u->SetUserEncoding(encoding.toLatin1());
380      u->SetEnableSave(true);
381      u->SaveLicqInfo();
382      gUserManager.DropUser(u);
383    }
384
385    emit encodingChanged();
386  }
387}
388
389void UserEventCommon::setMsgWinSticky(bool sticky)
390{
391  Support::changeWinSticky(winId(), sticky);
392}
393
394void UserEventCommon::showHistory()
395{
396  new HistoryDlg(myUsers.front().c_str(), myPpid);
397}
398
399void UserEventCommon::showUserInfo()
400{
401  LicqGui::instance()->showInfoDialog(mnuUserGeneral, myUsers.front().c_str(), myPpid, true);
402}
403
404void UserEventCommon::switchSecurity()
405{
406  new KeyRequestDlg(QString::fromAscii(myUsers.front().c_str()), myPpid);
407}
408
409void UserEventCommon::updateTime()
410{
411  QDateTime t;
412  t.setTime_t(time(NULL) + myRemoteTimeOffset);
413  myTimezone->setText(t.time().toString());
414}
415
416void UserEventCommon::updateTyping()
417{
418  // MSN needs this, ICQ/AIM doesn't send additional packets
419  // This does need to be verified with the official AIM client, there is a
420  // packet for it, but ICQ isn't using it apparently.
421  if (myPpid == LICQ_PPID || myUsers.empty())
422    return;
423
424  //FIXME Which user?
425  ICQUser* u = gUserManager.FetchUser(myUsers.front().c_str(), myPpid, LOCK_W);
426  u->SetTyping(ICQ_TYPING_INACTIVEx0);
427  myTimezone->setPalette(QPalette());
428  UserEventTabDlg* tabDlg = LicqGui::instance()->userEventTabDlg();
429  if (Config::Chat::instance()->tabbedChatting() && tabDlg != NULL)
430    tabDlg->updateTabLabel(u);
431  gUserManager.DropUser(u);
432}
433
434void UserEventCommon::showUserMenu()
435{
436  // Tell menu which contact to use and show it immediately.
437  // Menu is normally delayed but if we use InstantPopup mode we won't get
438  //   this signal so we can't tell menu which contact to use.
439  LicqGui::instance()->userMenu()->setUser(myId, myPpid);
440  dynamic_cast<QToolButton*>(myToolBar->widgetForAction(myMenu))->showMenu();
441}
442
443void UserEventCommon::showEncodingsMenu()
444{
445  // Menu is normally delayed but if we use InstantPopup mode shortcut won't work
446  dynamic_cast<QToolButton*>(myToolBar->widgetForAction(myEncoding))->showMenu();
447}
448
449void UserEventCommon::updatedUser(CICQSignal* sig)
450{
451  if (myPpid != sig->PPID() || !isUserInConvo(sig->Id()))
452  {
453    if (myConvoId != 0 && sig->CID() == myConvoId)
454    {
455      char* realId;
456      ICQUser::MakeRealId(sig->Id(), sig->PPID(), realId);
457      myUsers.push_back(realId);
458      delete [] realId;
459
460      // Now update the tab label
461      UserEventTabDlg* tabDlg = LicqGui::instance()->userEventTabDlg();
462      if (tabDlg != NULL)
463        tabDlg->updateConvoLabel(this);
464    }
465    else
466      return;
467  }
468
469  const ICQUser* u = gUserManager.FetchUser(sig->Id(), myPpid, LOCK_R);
470  if (u == NULL)
471    return;
472
473  switch (sig->SubSignal())
474  {
475    case USER_STATUS:
476      if (u->NewMessages() == 0)
477        setWindowIcon(IconManager::instance()->iconForStatus(u->StatusFull(), u->IdString(), u->PPID()));
478      break;
479
480    case USER_GENERAL: // Fall through
481    case USER_SECURITY:
482    case USER_BASIC:
483      updateWidgetInfo(u);
484      break;
485
486    case USER_EVENTS:
487      if (u->NewMessages() == 0)
488        setWindowIcon(IconManager::instance()->iconForStatus(u->StatusFull(), u->IdString(), u->PPID()));
489      else
490      {
491        setWindowIcon(IconManager::instance()->iconForEvent(ICQ_CMDxSUB_MSG));
492        flashTaskbar();
493      }
494
495      break;
496  }
497
498  gUserManager.DropUser(u);
499
500  // Call the event specific function now
501  userUpdated(sig, sig->Id(), myPpid);
502}
Note: See TracBrowser for help on using the browser.