1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2000-2014 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 "usersendevent.h"
21 
22 #include "config.h"
23 
24 #include <cassert>
25 #include <ctime>
26 #include <boost/foreach.hpp>
27 
28 #include <QAction>
29 #include <QApplication>
30 #include <QColorDialog>
31 #include <QDateTime>
32 #include <QDesktopWidget>
33 #include <QDialogButtonBox>
34 #include <QDropEvent>
35 #include <QFileInfo>
36 #include <QFileDialog>
37 #include <QGroupBox>
38 #include <QHBoxLayout>
39 #include <QKeyEvent>
40 #include <QLabel>
41 #include <QMenu>
42 #include <QMimeData>
43 #include <QMovie>
44 #include <QPushButton>
45 #include <QShortcut>
46 #include <QSplitter>
47 #include <QToolBar>
48 #include <QToolButton>
49 #include <QVBoxLayout>
50 
51 #ifdef USE_KDE
52 #include <KDE/KColorDialog>
53 #include <KDE/KFileDialog>
54 #endif
55 
56 #include <licq/logging/log.h>
57 #include <licq/contactlist/owner.h>
58 #include <licq/contactlist/usermanager.h>
59 #include <licq/conversation.h>
60 #include <licq/daemon.h>
61 #include <licq/event.h>
62 #include <licq/icq/icq.h>
63 #include <licq/plugin/pluginmanager.h>
64 #include <licq/plugin/protocolplugin.h>
65 #include <licq/pluginsignal.h>
66 #include <licq/protocolmanager.h>
67 #include <licq/protocolsignal.h>
68 #include <licq/translator.h>
69 #include <licq/userevents.h>
70 
71 #include "config/chat.h"
72 #include "config/emoticons.h"
73 #include "config/iconmanager.h"
74 #include "config/shortcuts.h"
75 
76 #include "core/gui-defines.h"
77 #include "core/licqgui.h"
78 #include "core/mainwin.h"
79 #include "core/messagebox.h"
80 #include "core/signalmanager.h"
81 
82 #include "dialogs/chatdlg.h"
83 #include "dialogs/filedlg.h"
84 #include "dialogs/editfilelistdlg.h"
85 #include "dialogs/joinchatdlg.h"
86 #include "dialogs/keyrequestdlg.h"
87 #include "dialogs/showawaymsgdlg.h"
88 
89 #include "views/mmuserview.h"
90 #include "views/userview.h"
91 
92 #include "widgets/historyview.h"
93 #include "widgets/infofield.h"
94 #include "widgets/mledit.h"
95 #include "widgets/selectemoticon.h"
96 
97 #include "usereventtabdlg.h"
98 
99 using namespace LicqQtGui;
100 /* TRANSLATOR LicqQtGui::UserSendEvent */
101 using Licq::StringList;
102 using Licq::gProtocolManager;
103 using Licq::gConvoManager;
104 using std::list;
105 using std::make_pair;
106 using std::pair;
107 using std::string;
108 using std::vector;
109 
110 typedef pair<const Licq::UserEvent*, Licq::UserId> messagePair;
111 
orderMessagePairs(const messagePair & mp1,const messagePair & mp2)112 bool orderMessagePairs(const messagePair& mp1, const messagePair& mp2)
113 {
114   return (mp1.first->Time() < mp2.first->Time());
115 }
116 
UserSendEvent(int type,const Licq::UserId & userId,QWidget * parent)117 UserSendEvent::UserSendEvent(int type, const Licq::UserId& userId, QWidget* parent)
118   : UserEventCommon(userId, parent, "UserSendEvent"),
119     myType(type)
120 {
121   myPictureLabel = NULL;
122   clearDelay = 250;
123 
124   QShortcut* a = new QShortcut(Qt::Key_Escape, this);
125   connect(a, SIGNAL(activated()), SLOT(cancelSend()));
126 
127   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
128   if (tabDlg != NULL && parent == tabDlg)
129   {
130     a = new QShortcut(Qt::ALT + Qt::Key_Left, this);
131     connect(a, SIGNAL(activated()), tabDlg, SLOT(moveLeft()));
132 
133     a = new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this);
134     connect(a, SIGNAL(activated()), tabDlg, SLOT(moveLeft()));
135 
136     a = new QShortcut(Qt::ALT + Qt::Key_Right, this);
137     connect(a, SIGNAL(activated()), tabDlg, SLOT(moveRight()));
138 
139     a = new QShortcut(Qt::CTRL + Qt::Key_Tab, this);
140     connect(a, SIGNAL(activated()), tabDlg, SLOT(moveRight()));
141   }
142 
143   myEventTypeGroup = new QActionGroup(this);
144   connect(myEventTypeGroup, SIGNAL(triggered(QAction*)), SLOT(changeEventType(QAction*)));
145 
146   QAction* action;
147   int eventTypesCount = 0;
148 
149 #define ADD_SENDTYPE(eventFlag, eventType, caption) \
150     if (mySendFuncs & eventFlag) { \
151       action = new QAction(caption, myEventTypeGroup); \
152       action->setData(eventType); \
153       action->setCheckable(true); \
154       eventTypesCount++; \
155     }
156 
157   // Populated menu for switching event type
158   ADD_SENDTYPE(Licq::ProtocolPlugin::CanSendMsg, MessageEvent, tr("Message"));
159   ADD_SENDTYPE(Licq::ProtocolPlugin::CanSendUrl, UrlEvent, tr("URL"));
160   ADD_SENDTYPE(Licq::ProtocolPlugin::CanSendChat, ChatEvent, tr("Chat Request"));
161   ADD_SENDTYPE(Licq::ProtocolPlugin::CanSendFile, FileEvent, tr("File Transfer"));
162   ADD_SENDTYPE(Licq::ProtocolPlugin::CanSendContact, ContactEvent, tr("Contact List"));
163 
164 #undef ADD_SENDTYPE
165 
166   QMenu* mnuSendType = new QMenu(this);
167   mnuSendType->addActions(myEventTypeGroup->actions());
168 
169   myEventTypeMenu = myToolBar->addAction(tr("Message Type"), this, SLOT(showSendTypeMenu()));
170   myEventTypeMenu->setMenu(mnuSendType);
171   if (eventTypesCount <= 1)
172     myEventTypeMenu->setEnabled(false);
173 
174   mySendServerCheck = myToolBar->addAction(tr("Send Through Server"));
175   mySendServerCheck->setCheckable(true);
176 
177   bool canSendDirect = false;
178 
179   {
180     Licq::UserReadGuard u(myUsers.front());
181     if (u.isLocked())
182     {
183       mySendServerCheck->setChecked(u->SendServer());
184       canSendDirect = u->canSendDirect();
185     }
186   }
187   if (!canSendDirect)
188   {
189     mySendServerCheck->setChecked(true);
190     mySendServerCheck->setEnabled(false);
191   }
192 
193   myUrgentCheck = myToolBar->addAction(tr("Urgent"));
194   myUrgentCheck->setCheckable(true);
195 
196   myToolBar->addSeparator();
197 
198   myEmoticon = myToolBar->addAction(tr("Smileys"), this, SLOT(showEmoticonsMenu()));
199   myForeColor = myToolBar->addAction(tr("Text Color..."), this, SLOT(setForegroundICQColor()));
200   myBackColor = myToolBar->addAction(tr("Background Color..."), this, SLOT(setBackgroundICQColor()));
201 
202   QDialogButtonBox* buttons = new QDialogButtonBox();
203   myTopLayout->addWidget(buttons);
204 
205   mySendButton = buttons->addButton(tr("&Send"), QDialogButtonBox::ActionRole);
206   mySendButton->setDefault(true);
207   // add a wrapper around the send button that
208   // tries to establish a secure connection first.
209   connect(mySendButton, SIGNAL(clicked()), SLOT(sendTrySecure()));
210 
211   myCloseButton = buttons->addButton(QDialogButtonBox::Close);
212   myCloseButton->setAutoDefault(true);
213   connect(myCloseButton, SIGNAL(clicked()), SLOT(closeDialog()));
214 
215   buttons->setVisible(Config::Chat::instance()->showDlgButtons());
216 
217   myViewSplitter = new QSplitter(Qt::Vertical);
218   myMainWidget->addWidget(myViewSplitter);
219 
220   myHistoryView = 0;
221   if (Config::Chat::instance()->msgChatView())
222   {
223     myHistoryView = new HistoryView(false, myUsers.front(), myViewSplitter);
224     connect(myHistoryView, SIGNAL(messageAdded()), SLOT(messageAdded()));
225 
226     Licq::UserReadGuard u(myUsers.front());
227     int historyCount = Config::Chat::instance()->showHistoryCount();
228     int historyTime = Config::Chat::instance()->showHistoryTime();
229     if (u.isLocked() && (historyCount > 0 || historyTime > 0))
230     {
231       // Show recent messages in the history
232       Licq::HistoryList lHistoryList;
233       if (u->GetHistory(lHistoryList))
234       {
235         // Rewind to the starting point. This will be the first message shown in the dialog.
236         // Make sure we don't show the new messages waiting.
237         unsigned short nNewMessages = u->NewMessages();
238         Licq::HistoryList::iterator lHistoryIter = lHistoryList.end();
239         for (int i = 0; i < historyCount + nNewMessages && lHistoryIter != lHistoryList.begin(); i++)
240           lHistoryIter--;
241 
242         time_t timeLimit = time(NULL) - historyTime;
243         while (lHistoryIter != lHistoryList.begin())
244         {
245           lHistoryIter--;
246 
247           if ((*lHistoryIter)->Time() < timeLimit)
248           {
249             // Found a message that is older than we want, go back one step and stop looking
250             lHistoryIter++;
251             break;
252           }
253 
254           // One more message from history to show
255           historyCount++;
256         }
257 
258         bool bUseHTML = u->protocolId() == ICQ_PPID && !isdigit(u->accountId()[0]);
259         QString contactName = QString::fromUtf8(u->getAlias().c_str());
260         QString ownerName;
261         {
262           Licq::OwnerReadGuard o(u->id().ownerId());
263           if (o.isLocked())
264             ownerName = QString::fromUtf8(o->getAlias().c_str());
265           else
266             ownerName = QString(tr("Error! no owner set"));
267         }
268 
269         // Iterate through each message to add
270         // Only show old messages as recent ones. Don't show duplicates.
271         int nMaxNumToShow;
272         if (lHistoryList.size() <= static_cast<size_t>(historyCount))
273           nMaxNumToShow = lHistoryList.size() - nNewMessages;
274         else
275           nMaxNumToShow = historyCount;
276 
277         // Safety net
278         if (nMaxNumToShow < 0)
279           nMaxNumToShow = 0;
280 
281         QDateTime date;
282 
283         for (int i = 0; i < nMaxNumToShow && lHistoryIter != lHistoryList.end(); i++)
284         {
285           QString str;
286           date.setTime_t((*lHistoryIter)->Time());
287           QString messageText = QString::fromUtf8((*lHistoryIter)->text().c_str());
288 
289           myHistoryView->addMsg(
290               (*lHistoryIter)->isReceiver(),
291               true,
292               (*lHistoryIter)->eventType() == Licq::UserEvent::TypeMessage ? "" : ((*lHistoryIter)->description() + " ").c_str(),
293               date,
294               (*lHistoryIter)->IsDirect(),
295               (*lHistoryIter)->IsMultiRec(),
296               (*lHistoryIter)->IsUrgent(),
297               (*lHistoryIter)->IsEncrypted(),
298               (*lHistoryIter)->isReceiver() ? contactName : ownerName,
299               MLView::toRichText(messageText, true, bUseHTML));
300           lHistoryIter++;
301         }
302 
303         myHistoryView->GotoEnd();
304 
305         Licq::User::ClearHistory(lHistoryList);
306       }
307     }
308 
309     // Collect all messages to put them in the correct time order
310     vector<messagePair> messages;
311 
312     // add all unread messages.
313     if (u.isLocked() && u->NewMessages() > 0)
314     {
315       for (unsigned short i = 0; i < u->NewMessages(); i++)
316       {
317         const Licq::UserEvent* e = u->EventPeek(i);
318         // Get the convo id now
319         unsigned long convoId = e->ConvoId();
320         if (myConvoId == 0)
321           myConvoId = convoId;
322 
323         if (convoId == myConvoId)
324         {
325           if (e->Id() > myHighestEventId)
326             myHighestEventId = e->Id();
327 
328           messages.push_back(make_pair(e, u->id()));
329         }
330       }
331       u.unlock();
332 
333       // Now add messages that are a part of this convo
334       if (myConvoId != 0)
335       {
336         Licq::UserListGuard userList(myPpid);
337         BOOST_FOREACH(const Licq::User* user, **userList)
338         {
339           Licq::UserReadGuard pUser(user);
340           if (pUser->NewMessages() && myUsers.front() != pUser->id())
341           {
342             for (unsigned short i = 0; i < pUser->NewMessages(); i++)
343             {
344               const Licq::UserEvent* e = pUser->EventPeek(i);
345 
346               if (e->ConvoId() == myConvoId)
347               {
348                 if (e->Id() > myHighestEventId)
349                   myHighestEventId = e->Id();
350 
351                 // add to the convo list (but what if they left by the time we open this?)
352                 myUsers.push_back(pUser->id());
353                 messages.push_back(make_pair(e, pUser->id()));
354               }
355             }
356           }
357         }
358       }
359 
360       // Sort the messages by time
361       stable_sort(messages.begin(), messages.end(), orderMessagePairs);
362 
363       // Now, finally add them
364       vector<messagePair>::iterator messageIter;
365       for (messageIter = messages.begin(); messageIter != messages.end(); messageIter++)
366         myHistoryView->addMsg((*messageIter).first, (*messageIter).second);
367       messages.clear();
368 
369       // If the user closed the chat window, we have to make sure we aren't
370       // using the old nConvoId
371       if (gConvoManager.get(myConvoId) == NULL)
372         myConvoId = 0;
373     }
374 
375     // Do we already have an open socket?
376     if (myConvoId == 0)
377     {
378       Licq::Conversation* convo = gConvoManager.getFromUser(myUsers.front());
379       if (convo != NULL)
380         myConvoId = convo->id();
381     }
382 
383     connect(gLicqGui, SIGNAL(eventSent(const Licq::Event*)),
384         myHistoryView, SLOT(addMsg(const Licq::Event*)));
385     //myViewSplitter->setResizeMode(myHistoryView, QSplitter::FollowSizeHint);
386   }
387 
388   {
389     mySendTypingTimer = new QTimer(this);
390     connect(mySendTypingTimer, SIGNAL(timeout()), SLOT(textChangedTimeout()));
391   }
392 
393   myPictureSplitter = new QSplitter(myViewSplitter);
394 
395   myMessageEdit = new MLEdit(true, myPictureSplitter);
396   myMessageEdit->setSizeHintLines(3);
397   if (Config::Chat::instance()->checkSpelling())
398   {
399 #ifdef HAVE_HUNSPELL
400     myMessageEdit->setSpellingDictionary(Config::Chat::instance()->spellingDictionary());
401 #endif
402     myMessageEdit->setCheckSpellingEnabled(true);
403   }
404   myMessageEdit->installEventFilter(this); // Enables send with enter
405 
406   // Disable drops for edit box so our own handler gets them
407   myMessageEdit->setAcceptDrops(false);
408 
409 
410   // Extra controls for URL
411   myUrlControls = new QWidget();
412   QHBoxLayout* urlLayout = new QHBoxLayout(myUrlControls);
413   QLabel* urlLabel = new QLabel(tr("URL:"));
414   urlLayout->addWidget(urlLabel);
415   myUrlEdit = new InfoField(false);
416   urlLayout->addWidget(myUrlEdit);
417   urlLabel->setBuddy(myUrlEdit);
418   myUrlEdit->installEventFilter(this);
419   myUrlControls->setVisible(false);
420   myMainWidget->addWidget(myUrlControls);
421 
422   // Extra controls for Chat Request
423   myChatControls = new QWidget();
424   QHBoxLayout* chatLayout = new QHBoxLayout(myChatControls);
425   QLabel* chatLabel = new QLabel(tr("Multiparty:"));
426   chatLayout->addWidget(chatLabel);
427   myChatItemEdit = new InfoField(false);
428   chatLayout->addWidget(myChatItemEdit);
429   myChatInviteButton = new QPushButton(tr("Invite"));
430   chatLayout->addWidget(myChatInviteButton);
431   myChatControls->setVisible(false);
432   myMainWidget->addWidget(myChatControls);
433   myChatPort = 0;
434 
435   // Extra controls for File Transfer
436   myFileControls = new QWidget();
437   QHBoxLayout* fileLayout = new QHBoxLayout(myFileControls);
438   QLabel* fileLabel = new QLabel(tr("File(s):"));
439   fileLayout->addWidget(fileLabel);
440   myFileEdit = new InfoField(true);
441   fileLayout->addWidget(myFileEdit);
442   myFileBrowseButton = new QPushButton(tr("Browse"));
443   fileLayout->addWidget(myFileBrowseButton);
444   myFileEditButton = new QPushButton(tr("Edit"));
445   myFileEditButton->setEnabled(false);
446   fileLayout->addWidget(myFileEditButton);
447   myFileControls->setVisible(false);
448   myMainWidget->addWidget(myFileControls);
449 
450   // Extra controls for Contacts
451   myContactsControls = new QWidget();
452   QVBoxLayout* contactsLayout = new QVBoxLayout(myContactsControls);
453   contactsLayout->setContentsMargins(0, 0, 0, 0);
454   myContactsControls->setToolTip(tr("Drag Users Here - Right Click for Options"));
455   myContactsList = new MMUserView(myUsers.front(), gGuiContactList);
456   myContactsList->installEventFilter(this);
457   contactsLayout->addWidget(myContactsList);
458   myContactsControls->setVisible(false);
459   myMainWidget->addWidget(myContactsControls);
460 
461 
462   if (Config::Chat::instance()->msgChatView())
463   {
464     myViewSplitter->setStretchFactor(myViewSplitter->indexOf(myHistoryView), 1);
465     myViewSplitter->setStretchFactor(myViewSplitter->indexOf(myPictureSplitter), 0);
466 
467     connect(myHistoryView, SIGNAL(quote(const QString&)),
468         myMessageEdit, SLOT(insertPlainText(const QString&)));
469 
470     // Connect scroll up/down shortcuts from edit to scroll the history
471     connect(myMessageEdit, SIGNAL(scrollDownPressed()),
472         myHistoryView, SLOT(scrollPageDown()));
473     connect(myMessageEdit, SIGNAL(scrollUpPressed()),
474         myHistoryView, SLOT(scrollPageUp()));
475   }
476 
477   setFocusProxy(myMessageEdit);
478   if (Config::Chat::instance()->showDlgButtons())
479   {
480     setTabOrder(myMessageEdit, mySendButton);
481     setTabOrder(mySendButton, myCloseButton);
482   }
483 
484   myIcqColor.setToDefault();
485   myMessageEdit->setBackground(QColor(myIcqColor.backRed(), myIcqColor.backGreen(), myIcqColor.backBlue()));
486   myMessageEdit->setForeground(QColor(myIcqColor.foreRed(), myIcqColor.foreGreen(), myIcqColor.foreBlue()));
487 
488   updateIcons();
489   {
490     Licq::UserReadGuard u(myUsers.front());
491     updatePicture(*u);
492   }
493   updateShortcuts();
494 
495   updateEmoticons();
496   connect(Emoticons::self(), SIGNAL(themeChanged()), SLOT(updateEmoticons()));
497 
498   connect(myMessageEdit, SIGNAL(ctrlEnterPressed()), mySendButton, SIGNAL(clicked()));
499   connect(myMessageEdit, SIGNAL(textChanged()), SLOT(messageTextChanged()));
500   connect(mySendServerCheck, SIGNAL(triggered(bool)), SLOT(sendServerToggled(bool)));
501 
502   connect(myChatInviteButton, SIGNAL(clicked()), SLOT(chatInviteUser()));
503   connect(myFileBrowseButton, SIGNAL(clicked()), SLOT(fileBrowse()));
504   connect(myFileEditButton, SIGNAL(clicked()), SLOT(fileEditList()));
505 
506   QSize dialogSize = Config::Chat::instance()->sendDialogSize();
507   if (dialogSize.isValid())
508     resize(dialogSize);
509 
510   setAcceptDrops(true);
511 
512   setEventType();
513 }
514 
~UserSendEvent()515 UserSendEvent::~UserSendEvent()
516 {
517   // Empty
518 }
519 
closeEvent(QCloseEvent * event)520 void UserSendEvent::closeEvent(QCloseEvent* event)
521 {
522   UserEventCommon::closeEvent(event);
523 
524   if (event->isAccepted())
525   {
526     // This widget is about to be destroyed so remove us from the tab dialog
527     UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
528     if (tabDlg != NULL && tabDlg->tabExists(this))
529       tabDlg->removeTab(this);
530   }
531 }
532 
eventFilter(QObject * watched,QEvent * e)533 bool UserSendEvent::eventFilter(QObject* watched, QEvent* e)
534 {
535   if (watched == myMessageEdit)
536   {
537     // If we're in single line chat mode we send messages with Enter and
538     // insert new lines with Ctrl+Enter.
539     if (Config::Chat::instance()->singleLineChatMode() && e->type() == QEvent::KeyPress)
540     {
541       QKeyEvent* key = dynamic_cast<QKeyEvent*>(e);
542       const bool isEnter = (key->key() == Qt::Key_Enter || key->key() == Qt::Key_Return);
543       if (isEnter)
544       {
545         if (key->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier))
546         {
547           myMessageEdit->insertPlainText("\n");
548           myMessageEdit->ensureCursorVisible();
549         }
550         else
551           mySendButton->animateClick();
552         return true; // filter the event out
553       }
554     }
555     return false;
556   }
557   else if (watched == myUrlEdit || watched == myContactsList)
558   {
559     if (e->type() == QEvent::KeyPress)
560     {
561       QKeyEvent* key = dynamic_cast<QKeyEvent*>(e);
562       const bool isEnter = (key->key() == Qt::Key_Enter || key->key() == Qt::Key_Return);
563       if (isEnter && (Config::Chat::instance()->singleLineChatMode() || key->modifiers() & Qt::ControlModifier))
564       {
565         mySendButton->animateClick();
566         return true; // filter the event out
567       }
568     }
569     return false;
570   }
571   else
572     return UserEventCommon::eventFilter(watched, e);
573 }
574 
setEventType()575 void UserSendEvent::setEventType()
576 {
577   myForeColor->setEnabled(myType == MessageEvent || myType == UrlEvent);
578   myBackColor->setEnabled(myType == MessageEvent || myType == UrlEvent);
579   myEmoticon->setEnabled(myType != ContactEvent);
580 
581   myMessageEdit->setVisible(myType != ContactEvent);
582 
583   switch (myType)
584   {
585     case UrlEvent:
586       myTitle = myBaseTitle + tr(" - URL");
587       break;
588     case ChatEvent:
589       myTitle = myBaseTitle + tr(" - Chat Request");
590       break;
591     case FileEvent:
592       myTitle = myBaseTitle + tr(" - File Transfer");
593       break;
594     case ContactEvent:
595       myTitle = myBaseTitle + tr(" - Contact List");
596       break;
597     case MessageEvent:
598     default:
599       myTitle = myBaseTitle + tr(" - Message");
600       break;
601   }
602   setWindowTitle(myTitle);
603 
604   myUrlControls->setVisible(myType == UrlEvent);
605   myChatControls->setVisible(myType == ChatEvent);
606   myFileControls->setVisible(myType == FileEvent);
607   myContactsControls->setVisible(myType == ContactEvent);
608 
609   myEventTypeGroup->actions().at(myType)->setChecked(true);
610 
611   if (myType != ContactEvent)
612     myMessageEdit->setFocus();
613 }
614 
updateIcons()615 void UserSendEvent::updateIcons()
616 {
617   UserEventCommon::updateIcons();
618 
619   IconManager* iconman = IconManager::instance();
620 
621   // Toolbar buttons
622   myEventTypeMenu->setIcon(iconForType(myType));
623   mySendServerCheck->setIcon(iconman->getIcon(IconManager::ThroughServerIcon));
624   myUrgentCheck->setIcon(iconman->getIcon(IconManager::UrgentIcon));
625   myEmoticon->setIcon(iconman->getIcon(IconManager::SmileIcon));
626   myForeColor->setIcon(iconman->getIcon(IconManager::TextColorIcon));
627   myBackColor->setIcon(iconman->getIcon(IconManager::BackColorIcon));
628 
629   // Update message type icons in menu
630   foreach (QAction* a, myEventTypeGroup->actions())
631     a->setIcon(iconForType(a->data().toInt()));
632 }
633 
updateEmoticons()634 void UserSendEvent::updateEmoticons()
635 {
636   // Don't show tool button for emoticons if there are no emoticons to select
637   myEmoticon->setVisible(Emoticons::self()->emoticonsKeys().size() > 0);
638 }
639 
updateShortcuts()640 void UserSendEvent::updateShortcuts()
641 {
642   UserEventCommon::updateShortcuts();
643 
644   Config::Shortcuts* shortcuts = Config::Shortcuts::instance();
645 
646   myEventTypeMenu->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatEventMenu));
647   mySendServerCheck->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatToggleSendServer));
648   myUrgentCheck->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatToggleUrgent));
649   myEmoticon->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatEmoticonMenu));
650   myForeColor->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatColorFore));
651   myBackColor->setShortcut(shortcuts->getShortcut(Config::Shortcuts::ChatColorBack));
652 
653   // Tooltips include shortcut so update them here as well
654   pushToolTip(myEventTypeMenu, tr("Select type of message to send"));
655   pushToolTip(mySendServerCheck, tr("Send through server"));
656   pushToolTip(myUrgentCheck, tr("Urgent"));
657   pushToolTip(myEmoticon, tr("Insert smileys"));
658   pushToolTip(myForeColor, tr("Change text color"));
659   pushToolTip(myBackColor, tr("Change background color"));
660 }
661 
updatePicture(const Licq::User * u)662 void UserSendEvent::updatePicture(const Licq::User* u)
663 {
664   if (u == NULL)
665     return;
666 
667   if (myPictureLabel != NULL)
668   {
669     delete myPictureLabel;
670     myPictureLabel = NULL;
671   }
672 
673   if (Config::Chat::instance()->showUserPic() &&
674       u->GetPicturePresent())
675   {
676     QString picPath = QString::fromLocal8Bit(u->pictureFileName().c_str());
677     QMovie* picMovie = new QMovie(picPath, QByteArray(), this);
678     if (picMovie->isValid())
679     {
680       myPictureLabel = new QLabel();
681       myPictureSplitter->insertWidget(1, myPictureLabel);
682       myPictureLabel->setMovie(picMovie);
683       if (picMovie->frameCount() > 1)
684         picMovie->start();
685       else
686         picMovie->jumpToNextFrame();
687       myPictureLabel->setFixedWidth(myPictureLabel->sizeHint().width());
688       if (Config::Chat::instance()->showUserPicHidden())
689         myPictureSplitter->setSizes(QList<int>() << 1 << 0);
690     }
691     else
692       delete picMovie;
693   }
694 }
695 
iconForType(int type) const696 const QPixmap& UserSendEvent::iconForType(int type) const
697 {
698   switch (type)
699   {
700     case UrlEvent:
701       return IconManager::instance()->getIcon(IconManager::UrlMessageIcon);
702 
703     case ChatEvent:
704       return IconManager::instance()->getIcon(IconManager::ChatMessageIcon);
705 
706     case FileEvent:
707       return IconManager::instance()->getIcon(IconManager::FileMessageIcon);
708 
709     case ContactEvent:
710       return IconManager::instance()->getIcon(IconManager::ContactMessageIcon);
711 
712     case MessageEvent:
713     default:
714       return IconManager::instance()->getIcon(IconManager::StandardMessageIcon);
715   }
716 }
717 
setText(const QString & text)718 void UserSendEvent::setText(const QString& text)
719 {
720   myMessageEdit->setText(text);
721   myMessageEdit->GotoEnd();
722   myMessageEdit->document()->setModified(false);
723 }
724 
setUrl(const QString & url,const QString & description)725 void UserSendEvent::setUrl(const QString& url, const QString& description)
726 {
727   myUrlEdit->setText(url);
728   setText(description);
729 }
730 
setContact(const Licq::UserId & userId)731 void UserSendEvent::setContact(const Licq::UserId& userId)
732 {
733   Licq::UserReadGuard u(userId);
734   if (u.isLocked())
735     myContactsList->add(u->id());
736 }
737 
setFile(const QString & file,const QString & description)738 void UserSendEvent::setFile(const QString& file, const QString& description)
739 {
740   QFileInfo fileinfo(file);
741   if (fileinfo.exists() && fileinfo.isFile() && fileinfo.isReadable())
742   {
743     myFileEdit->setText(file);
744     setText(description);
745     myFileList.push_back(strdup(file.toLocal8Bit()));
746     myFileEditButton->setEnabled(true);
747   }
748 }
749 
addFile(const QString & file)750 void UserSendEvent::addFile(const QString& file)
751 {
752   if (myFileList.empty())
753     return;
754 
755   myFileList.push_back(strdup(file.toLocal8Bit()));
756 
757   myFileEditButton->setEnabled(true);
758   fileUpdateLabel(myFileList.size());
759 }
760 
convoJoin(const Licq::UserId & userId)761 void UserSendEvent::convoJoin(const Licq::UserId& userId)
762 {
763   if (!userId.isValid())
764     return;
765 
766   if (Config::Chat::instance()->msgChatView())
767   {
768     Licq::UserReadGuard u(userId);
769     QString userName;
770     if (u.isLocked())
771       userName = QString::fromUtf8(u->getAlias().c_str());
772     else
773       userName = "";
774 
775     myHistoryView->addNotice(QDateTime::currentDateTime(),
776         tr("%1 has joined the conversation.").arg(userName));
777   }
778 
779   if (!isUserInConvo(userId))
780     myUsers.push_back(userId);
781 
782   // Now update the tab label
783   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
784   if (tabDlg != NULL)
785     tabDlg->updateConvoLabel(this);
786 }
787 
convoLeave(const Licq::UserId & userId)788 void UserSendEvent::convoLeave(const Licq::UserId& userId)
789 {
790   if (!userId.isValid())
791     return;
792 
793   if (Config::Chat::instance()->msgChatView())
794   {
795     Licq::UserWriteGuard u(userId);
796     QString userName;
797     if (u.isLocked())
798       userName = QString::fromUtf8(u->getAlias().c_str());
799     else
800       userName = "";
801 
802     myHistoryView->addNotice(QDateTime::currentDateTime(),
803         tr("%1 has left the conversation.").arg(userName));
804 
805     // Remove the typing notification if active
806     if (u.isLocked())
807     {
808       if (u->isTyping())
809       {
810         u->setIsTyping(false);
811         myTimezone->setPalette(QPalette());
812         UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
813         if (Config::Chat::instance()->tabbedChatting() && tabDlg != NULL)
814           tabDlg->updateTabLabel(*u);
815       }
816     }
817   }
818 
819   if (myUsers.size() > 1)
820   {
821     list<Licq::UserId>::iterator it;
822     for (it = myUsers.begin(); it != myUsers.end(); it++)
823     {
824       if (*it == userId)
825       {
826         myUsers.remove(*it);
827         break;
828       }
829     }
830     myHistoryView->setOwner(myUsers.front());
831   }
832   else
833     myConvoId = 0;
834 
835   if (Config::Chat::instance()->msgChatView())
836   {
837     // Now update the tab label
838     UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
839     if (tabDlg != NULL)
840       tabDlg->updateConvoLabel(this);
841   }
842 }
843 
changeEvent(QEvent * event)844 void UserSendEvent::changeEvent(QEvent* event)
845 {
846   if (isActiveWindow())
847     QTimer::singleShot(clearDelay, this, SLOT(clearNewEvents()));
848   QWidget::changeEvent(event);
849 }
850 
changeEventType(int type)851 void UserSendEvent::changeEventType(int type)
852 {
853   if (myType == type)
854     return;
855 
856   switch (type)
857   {
858     case MessageEvent:
859       if ((mySendFuncs & Licq::ProtocolPlugin::CanSendMsg) == 0)
860         return;
861       break;
862     case UrlEvent:
863       if ((mySendFuncs & Licq::ProtocolPlugin::CanSendUrl) == 0)
864         return;
865       break;
866     case ChatEvent:
867       if ((mySendFuncs & Licq::ProtocolPlugin::CanSendChat) == 0)
868         return;
869       break;
870     case FileEvent:
871       if ((mySendFuncs & Licq::ProtocolPlugin::CanSendFile) == 0)
872         return;
873       break;
874     case ContactEvent:
875       if ((mySendFuncs & Licq::ProtocolPlugin::CanSendContact) == 0)
876         return;
877       break;
878     default:
879       assert(false);
880   }
881 
882   myType = type;
883   setEventType();
884 }
885 
retrySend(const Licq::Event * e,unsigned flags)886 void UserSendEvent::retrySend(const Licq::Event* e, unsigned flags)
887 {
888   const Licq::UserId& frontUserId(myUsers.front());
889 
890   unsigned long icqEventTag = 0;
891   mySendServerCheck->setChecked((flags & Licq::ProtocolSignal::SendDirect) == 0);
892   myUrgentCheck->setChecked(flags & Licq::ProtocolSignal::SendUrgent);
893 
894   Licq::IcqProtocol::Ptr icq;
895   if (frontUserId.protocolId() == ICQ_PPID)
896   {
897     icq = plugin_internal_cast<Licq::IcqProtocol>(
898         Licq::gPluginManager.getProtocolInstance(frontUserId.ownerId()));
899   }
900 
901   switch (e->userEvent()->eventType())
902   {
903     case Licq::UserEvent::TypeMessage:
904     {
905       bool userOffline = true;
906       {
907         Licq::UserReadGuard u(frontUserId);
908         if (u.isLocked())
909           userOffline = !u->isOnline();
910       }
911       const Licq::EventMsg* ue = dynamic_cast<const Licq::EventMsg*>(e->userEvent());
912       // create initial strings (implicit copying, no allocation impact :)
913       QByteArray wholeMessageRaw(Licq::gTranslator.returnToDos(ue->message()).c_str());
914       int wholeMessagePos = 0;
915 
916       bool needsSplitting = false;
917       // If we send through myServer (= have message limit), and we've crossed the limit
918       unsigned short maxSize = userOffline ? Licq::IcqProtocol::MaxOfflineMessageSize : Licq::IcqProtocol::MaxMessageSize;
919       if ((wholeMessageRaw.length() - wholeMessagePos) > maxSize)
920         needsSplitting = true;
921 
922       QString message;
923       QByteArray messageRaw;
924 
925       while (wholeMessageRaw.length() > wholeMessagePos)
926       {
927         if (needsSplitting)
928         {
929           // This is a bit ugly but adds safety. We don't simply search
930           // for a whitespace to cut at in the encoded text (since we don't
931           // really know how spaces are represented in its encoding), so
932           // we take the maximum length, then convert back to a Unicode string
933           // and then search for Unicode whitespaces.
934           messageRaw = Licq::gTranslator.returnToUnix(wholeMessageRaw.mid(wholeMessagePos, maxSize).data()).c_str();
935           message = QString::fromUtf8(messageRaw);
936 
937           if ((wholeMessageRaw.length() - wholeMessagePos) > maxSize)
938           {
939             // We try to find the optimal place to cut
940             // (according to our narrow-minded Latin1 idea of optimal :)
941             // prefer keeping sentences intact 1st
942             int foundIndex = message.lastIndexOf(QRegExp("[\\.\\n]"));
943             // slicing at 0 position would be useless
944             if (foundIndex <= 0)
945               foundIndex = message.lastIndexOf(QRegExp("\\s"));
946 
947             if (foundIndex > 0)
948             {
949               message.truncate(foundIndex + 1);
950               messageRaw = message.toUtf8();
951             }
952           }
953         }
954         else
955         {
956           messageRaw = ue->message().c_str();
957         }
958 
959         icqEventTag = gProtocolManager.sendMessage(frontUserId, messageRaw.data(),
960             flags, &myIcqColor);
961 
962         myEventTag.push_back(icqEventTag);
963 
964         wholeMessagePos += Licq::gTranslator.returnToDos(messageRaw.data()).size();
965       }
966 
967       icqEventTag = 0;
968 
969       break;
970     }
971 
972     case Licq::UserEvent::TypeUrl:
973     {
974       const Licq::EventUrl* ue = dynamic_cast<const Licq::EventUrl*>(e->userEvent());
975 
976       icqEventTag = gProtocolManager.sendUrl(frontUserId, ue->url(),
977           ue->description(), flags, &myIcqColor);
978 
979       break;
980     }
981 
982     case Licq::UserEvent::TypeContactList:
983     {
984       if (icq == NULL)
985         break;
986 
987       const Licq::EventContactList* ue = dynamic_cast<const Licq::EventContactList*>(e->userEvent());
988       const Licq::EventContactList::ContactList& clist = ue->Contacts();
989       StringList users;
990 
991       // ContactList is const but string list holds "char*" so we have to copy each string
992       for (Licq::EventContactList::ContactList::const_iterator i = clist.begin(); i != clist.end(); i++)
993         users.push_back((*i)->userId().accountId());
994 
995       if (users.empty())
996         break;
997 
998       icqEventTag = icq->icqSendContactList(frontUserId, users, flags, &myIcqColor);
999       break;
1000     }
1001 
1002     case Licq::UserEvent::TypeChat:
1003     {
1004       if (icq == NULL)
1005         break;
1006 
1007       const Licq::EventChat* ue = dynamic_cast<const Licq::EventChat*>(e->userEvent());
1008       icqEventTag = icq->icqChatRequest(frontUserId,
1009           ue->reason(), flags, ue->clients(), ue->Port());
1010       break;
1011     }
1012 
1013     case Licq::UserEvent::TypeFile:
1014     {
1015       const Licq::EventFile* ue = dynamic_cast<const Licq::EventFile*>(e->userEvent());
1016       list<string> filelist(ue->FileList());
1017 
1018       //TODO in the daemon
1019       icqEventTag = gProtocolManager.fileTransferPropose(frontUserId,
1020           ue->filename(), ue->fileDescription(), filelist, flags);
1021 
1022       break;
1023     }
1024 
1025     default:
1026       break;
1027   }
1028 
1029   if (icqEventTag)
1030     myEventTag.push_back(icqEventTag);
1031 
1032   sendBase();
1033 }
1034 
userUpdated(const Licq::UserId & userId,unsigned long subSignal,int argument,unsigned long cid)1035 void UserSendEvent::userUpdated(const Licq::UserId& userId, unsigned long subSignal, int argument, unsigned long cid)
1036 {
1037   Licq::UserWriteGuard u(userId);
1038 
1039   if (!u.isLocked())
1040     return;
1041 
1042   switch (subSignal)
1043   {
1044     case Licq::PluginSignal::UserStatus:
1045     {
1046       if (u->Port() == 0)
1047       {
1048         mySendServerCheck->setChecked(true);
1049         mySendServerCheck->setEnabled(false);
1050       }
1051       else
1052         mySendServerCheck->setEnabled(true);
1053 
1054       if (!u->isOnline())
1055         mySendServerCheck->setChecked(true);
1056 
1057       break;
1058     }
1059 
1060     case Licq::PluginSignal::UserEvents:
1061     {
1062       const Licq::UserEvent* e = u->EventPeekId(argument);
1063 
1064       if (e != NULL && myHighestEventId < argument &&
1065           myHistoryView && argument > 0)
1066       {
1067         myHighestEventId = argument;
1068         e = u->EventPeekId(argument);
1069 
1070         if (e != NULL)
1071           if ((u->protocolCapabilities() & Licq::ProtocolPlugin::CanConversationId) == 0 || cid == myConvoId)
1072           {
1073             u.unlock();
1074             myHistoryView->addMsg(e, userId);
1075             return;
1076           }
1077       }
1078       break;
1079     }
1080 
1081     case Licq::PluginSignal::UserSecurity:
1082       // Automatically unset 'send through server' upon
1083       // establishing secure channel
1084       if (u->Secure())
1085       {
1086         u->SetSendServer(false);
1087         mySendServerCheck->setChecked(false);
1088       }
1089       break;
1090 
1091     case Licq::PluginSignal::UserPicture:
1092       updatePicture(*u);
1093   }
1094 }
1095 
send()1096 void UserSendEvent::send()
1097 {
1098   const Licq::UserId& frontUserId(myUsers.front());
1099 
1100   if (myType == MessageEvent)
1101   {
1102     // don't let the user send empty messages
1103     if (myMessageEdit->toPlainText().trimmed().isEmpty())
1104       return;
1105 
1106     // do nothing if a command is already being processed
1107     if (myEventTag.size() > 0 && myEventTag.front() != 0)
1108       return;
1109 
1110     if (!myMessageEdit->document()->isModified() &&
1111         !QueryYesNo(this, tr("You didn't edit the message.\nDo you really want to send it?")))
1112       return;
1113   }
1114 
1115   if (myType == UrlEvent && myUrlEdit->text().trimmed().isEmpty())
1116   {
1117     InformUser(this, tr("No URL specified"));
1118     return;
1119   }
1120   if (myType == FileEvent && myFileEdit->text().trimmed().isEmpty())
1121   {
1122     WarnUser(this, tr("You must specify a file to transfer!"));
1123     return;
1124   }
1125   if (myType == ContactEvent && myContactsList->contacts().empty())
1126     return;
1127 
1128   bool secure = false;;
1129   bool offline = true;
1130   {
1131     Licq::UserReadGuard u(frontUserId);
1132     if (u.isLocked())
1133     {
1134       secure = u->Secure() || u->AutoSecure();
1135       offline = !u->isOnline();
1136     }
1137   }
1138 
1139   if ((myType == MessageEvent || myType == UrlEvent || myType == ContactEvent) &&
1140       secure && mySendServerCheck->isChecked())
1141   {
1142     if (!QueryYesNo(this, tr("Message can't be sent securely through the server!\n"
1143             "Send anyway?")))
1144       return;
1145 
1146     Licq::UserWriteGuard u(frontUserId);
1147     if (u.isLocked())
1148       u->SetAutoSecure(false);
1149   }
1150 
1151 
1152   // Take care of typing notification now`
1153   if (mySendTypingTimer->isActive())
1154     mySendTypingTimer->stop();
1155 
1156   if (myType != ContactEvent)
1157     connect(myMessageEdit, SIGNAL(textChanged()), SLOT(messageTextChanged()));
1158   gProtocolManager.sendTypingNotification(frontUserId, false, myConvoId);
1159 
1160   StringList contacts;
1161   Licq::UserId userId;
1162   foreach (userId, myContactsList->contacts())
1163   {
1164     contacts.push_back(userId.accountId());
1165   }
1166 
1167   unsigned flags = 0;
1168   if (!mySendServerCheck->isChecked())
1169     flags |= Licq::ProtocolSignal::SendDirect;
1170   if (myUrgentCheck->isChecked())
1171     flags |= Licq::ProtocolSignal::SendUrgent;
1172 
1173   if (myType == MessageEvent)
1174   {
1175     QByteArray wholeMessageRaw(Licq::gTranslator.returnToDos(myMessageEdit->toPlainText().toUtf8().data()).c_str());
1176     int wholeMessagePos = 0;
1177 
1178     bool needsSplitting = false;
1179     // If we send through server (= have message limit), and we've crossed the limit
1180     unsigned short maxSize = offline ? Licq::IcqProtocol::MaxOfflineMessageSize : Licq::IcqProtocol::MaxMessageSize;
1181     if (mySendServerCheck->isChecked() && ((wholeMessageRaw.length() - wholeMessagePos) > maxSize))
1182       needsSplitting = true;
1183 
1184     while (wholeMessageRaw.length() > wholeMessagePos)
1185     {
1186       QByteArray messageRaw;
1187 
1188       if (needsSplitting)
1189       {
1190         // This is a bit ugly but adds safety. We don't simply search
1191         // for a whitespace to cut at in the encoded text (since we don't
1192         // really know how spaces are represented in its encoding), so
1193         // we take the maximum length, then convert back to a Unicode string
1194         // and then search for Unicode whitespaces.
1195         messageRaw = Licq::gTranslator.returnToUnix(wholeMessageRaw.mid(wholeMessagePos, maxSize).data()).c_str();
1196         QString message = QString::fromUtf8(messageRaw);
1197 
1198         if (wholeMessageRaw.length() - wholeMessagePos > maxSize)
1199         {
1200           // We try to find the optimal place to cut
1201           // (according to our narrow-minded Latin1 idea of optimal :)
1202           // prefer keeping sentences intact 1st
1203           int foundIndex = message.lastIndexOf(QRegExp("[\\.\\n]"));
1204           // slicing at 0 position would be useless
1205           if (foundIndex <= 0)
1206             foundIndex = message.lastIndexOf(QRegExp("\\s"));
1207 
1208           if (foundIndex > 0)
1209           {
1210             message.truncate(foundIndex + 1);
1211             messageRaw = message.toUtf8();
1212           }
1213         }
1214       }
1215       else
1216       {
1217         messageRaw = myMessageEdit->toPlainText().toUtf8();
1218       }
1219 
1220       unsigned long icqEventTag = gProtocolManager.sendMessage(
1221           frontUserId,
1222           messageRaw.data(),
1223           flags,
1224           &myIcqColor,
1225           myConvoId);
1226       if (icqEventTag != 0)
1227         myEventTag.push_back(icqEventTag);
1228 
1229       wholeMessagePos += Licq::gTranslator.returnToDos(messageRaw.data()).size();
1230     }
1231   }
1232   else
1233   {
1234     unsigned long icqEventTag = 0;
1235 
1236     Licq::IcqProtocol::Ptr icq;
1237     if (frontUserId.protocolId() == ICQ_PPID)
1238     {
1239       icq = plugin_internal_cast<Licq::IcqProtocol>(
1240           Licq::gPluginManager.getProtocolInstance(frontUserId.ownerId()));
1241     }
1242 
1243     switch (myType)
1244     {
1245       case UrlEvent:
1246         icqEventTag = gProtocolManager.sendUrl(frontUserId,
1247             myUrlEdit->text().toUtf8().constData(),
1248             myMessageEdit->toPlainText().toUtf8().data(),
1249             flags,
1250             &myIcqColor);
1251         break;
1252 
1253       case ContactEvent:
1254         if (icq == NULL)
1255           return;
1256         icqEventTag = icq->icqSendContactList(frontUserId,
1257             contacts,
1258             flags,
1259             &myIcqColor);
1260         break;
1261 
1262       case ChatEvent:
1263         if (icq == NULL)
1264           return;
1265         icqEventTag = icq->icqChatRequest(frontUserId,
1266             myMessageEdit->toPlainText().toUtf8().data(), flags,
1267             myChatClients.toUtf8().data(), myChatPort);
1268         break;
1269 
1270       case FileEvent:
1271         //TODO in daemon
1272         icqEventTag = gProtocolManager.fileTransferPropose(
1273             frontUserId,
1274             QFile::encodeName(myFileEdit->text()).data(),
1275             myMessageEdit->toPlainText().toUtf8().data(),
1276             myFileList,
1277             flags);
1278         break;
1279     }
1280 
1281     myEventTag.push_back(icqEventTag);
1282   }
1283 
1284   sendBase();
1285 }
1286 
sendBase()1287 void UserSendEvent::sendBase()
1288 {
1289   if (!Config::Chat::instance()->manualNewUser())
1290   {
1291     bool newUser = false;
1292     {
1293       Licq::UserWriteGuard u(myUsers.front());
1294       if (u.isLocked() && u->NewUser())
1295       {
1296         u->SetNewUser(false);
1297         newUser = true;
1298       }
1299     }
1300     if (newUser)
1301       Licq::gUserManager.notifyUserUpdated(myUsers.front(), Licq::PluginSignal::UserSettings);
1302   }
1303 
1304   unsigned long icqEventTag = 0;
1305 
1306   if (myEventTag.size() != 0)
1307     icqEventTag = myEventTag.front();
1308 
1309   if (icqEventTag != 0 || myUsers.front().protocolId() != ICQ_PPID)
1310   {
1311     bool via_server = mySendServerCheck->isChecked();
1312     myProgressMsg = tr("Sending ");
1313     myProgressMsg += via_server ? tr("via server") : tr("direct");
1314     myProgressMsg += "...";
1315     QString title = myTitle + " [" + myProgressMsg + "]";
1316 
1317     UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1318     if (tabDlg != NULL && tabDlg->tabIsSelected(this))
1319       tabDlg->setWindowTitle(title);
1320 
1321     setWindowTitle(title);
1322     setCursor(Qt::WaitCursor);
1323     mySendButton->setText(tr("&Cancel"));
1324     myCloseButton->setEnabled(false);
1325     myMessageEdit->setEnabled(false);
1326 
1327     disconnect(mySendButton, SIGNAL(clicked()), this, SLOT(send()));
1328     connect(mySendButton, SIGNAL(clicked()), SLOT(cancelSend()));
1329 
1330     connect(gGuiSignalManager, SIGNAL(doneUserFcn(const Licq::Event*)),
1331         SLOT(eventDoneReceived(const Licq::Event*)));
1332   }
1333 }
1334 
eventDoneReceived(const Licq::Event * e)1335 void UserSendEvent::eventDoneReceived(const Licq::Event* e)
1336 {
1337   if (e == NULL)
1338   {
1339     QString title = myTitle + " [" + myProgressMsg + tr("error") + "]";
1340 
1341     UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1342     if (tabDlg != NULL && tabDlg->tabIsSelected(this))
1343       tabDlg->setWindowTitle(title);
1344 
1345     setWindowTitle(title);
1346 
1347     return;
1348   }
1349 
1350   unsigned long icqEventTag = 0;
1351   list<unsigned long>::iterator iter;
1352 
1353   for (iter = myEventTag.begin(); iter != myEventTag.end(); iter++)
1354   {
1355     if (e->Equals(*iter))
1356     {
1357       icqEventTag = *iter;
1358       myEventTag.erase(iter);
1359       break;
1360     }
1361   }
1362 
1363   if (icqEventTag == 0)
1364     return;
1365 
1366   QString title, result;
1367   switch (e->Result())
1368   {
1369     case Licq::Event::ResultAcked:
1370     case Licq::Event::ResultSuccess:
1371       result = tr("done");
1372       QTimer::singleShot(5000, this, SLOT(resetTitle()));
1373       break;
1374     case Licq::Event::ResultCancelled:
1375       result = tr("cancelled");
1376       break;
1377     case Licq::Event::ResultFailed:
1378     case Licq::Event::ResultUnsupported:
1379       result = tr("failed");
1380       break;
1381     case Licq::Event::ResultTimedout:
1382       result = tr("timed out");
1383       break;
1384     case Licq::Event::ResultError:
1385       result = tr("error");
1386       break;
1387     default:
1388       break;
1389   }
1390   title = myTitle + " [" + myProgressMsg + result + "]";
1391 
1392   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1393   if (tabDlg != NULL && tabDlg->tabIsSelected(this))
1394     tabDlg->setWindowTitle(title);
1395 
1396   setWindowTitle(title);
1397 
1398   setCursor(Qt::ArrowCursor);
1399   mySendButton->setText(tr("&Send"));
1400   myCloseButton->setEnabled(true);
1401   myMessageEdit->setEnabled(true);
1402 
1403   disconnect(mySendButton, SIGNAL(clicked()), this, SLOT(cancelSend()));
1404   connect(mySendButton, SIGNAL(clicked()), SLOT(send()));
1405 
1406   // If cancelled automatically, check "Send through Server"
1407   if (Config::Chat::instance()->autoSendThroughServer() && e->Result() == Licq::Event::ResultCancelled)
1408     mySendServerCheck->setChecked(true);
1409 
1410   if (myEventTag.empty())
1411   {
1412     disconnect(gGuiSignalManager, SIGNAL(doneUserFcn(const Licq::Event*)),
1413         this, SLOT(eventDoneReceived(const Licq::Event*)));
1414   }
1415 
1416   if (myType != ContactEvent)
1417     if(tabDlg == NULL || !tabDlg->tabExists(this) || tabDlg->tabIsSelected(this))
1418       myMessageEdit->setFocus();
1419 
1420   if (e->Result() != Licq::Event::ResultAcked)
1421   {
1422     if ((e->flags() & Licq::Event::FlagDirect) &&
1423         e->Result() != Licq::Event::ResultCancelled &&
1424        (Config::Chat::instance()->autoSendThroughServer() ||
1425          QueryYesNo(this, tr("Direct send failed,\nsend through server?"))) )
1426     {
1427       // Remember that we want to send through server
1428       mySendServerCheck->setChecked(true);
1429 
1430       retrySend(e, 0);
1431     }
1432     return;
1433   }
1434 
1435   QString msg;
1436 
1437   if (e->subResult() == Licq::Event::SubResultReturn)
1438   {
1439     {
1440       Licq::UserWriteGuard u(myUsers.front());
1441 
1442       msg = tr("%1 is in %2 mode:\n%3\nSend...")
1443           .arg(QString::fromUtf8(u->getAlias().c_str()))
1444           .arg(u->statusString().c_str())
1445           .arg(QString::fromUtf8(u->autoResponse().c_str()));
1446 
1447       u->SetShowAwayMsg(false);
1448     }
1449 
1450     // if the original message was through server, send this one through server
1451     unsigned flags = 0;
1452     if (e->userEvent()->IsDirect())
1453       flags |= Licq::ProtocolSignal::SendDirect;
1454 
1455     switch (QueryUser(this, msg, tr("Urgent"), tr(" to Contact List"), tr("Cancel")))
1456     {
1457       case 0:
1458         retrySend(e, flags | Licq::ProtocolSignal::SendUrgent);
1459         break;
1460       case 1:
1461         retrySend(e, flags | Licq::ProtocolSignal::SendToList);
1462         break;
1463       case 2:
1464         break;
1465     }
1466     return;
1467   }
1468 
1469   emit autoCloseNotify();
1470 
1471 
1472   switch (myType)
1473   {
1474     case UrlEvent:
1475     case ContactEvent:
1476     case MessageEvent:
1477     {
1478       if ((e->flags() & Licq::Event::FlagDirect) == 0)
1479         break;
1480 
1481       myMessageEdit->setText(QString::null);
1482 
1483       bool showAwayDlg = false;
1484       {
1485         Licq::UserReadGuard u(myUsers.front());
1486         if (u.isLocked())
1487           showAwayDlg = u->isAway() && u->ShowAwayMsg();
1488       }
1489 
1490       if (showAwayDlg && Config::Chat::instance()->popupAutoResponse())
1491         new ShowAwayMsgDlg(myUsers.front());
1492 
1493       break;
1494     }
1495 
1496     case FileEvent:
1497       if (!e->ExtendedAck() || !e->ExtendedAck()->accepted())
1498       {
1499         Licq::UserReadGuard u(myUsers.front());
1500         if (!u.isLocked())
1501           break;
1502         QString s = !e->ExtendedAck() ?
1503           tr("No reason provided") :
1504             QString::fromUtf8(e->ExtendedAck()->response().c_str());
1505         QString result = tr("File transfer with %1 refused:\n%2")
1506           .arg(QString::fromUtf8(u->getAlias().c_str()))
1507           .arg(s);
1508         u.unlock();
1509         InformUser(this, result);
1510       }
1511       else
1512       {
1513         const Licq::EventFile* f = dynamic_cast<const Licq::EventFile*>(e->userEvent());
1514         FileDlg* fileDlg = new FileDlg(myUsers.front());
1515         fileDlg->SendFiles(f->FileList(), e->ExtendedAck()->port());
1516       }
1517       break;
1518 
1519     case ChatEvent:
1520       if (!e->ExtendedAck() || !e->ExtendedAck()->accepted())
1521       {
1522         Licq::UserReadGuard u(myUsers.front());
1523         QString s = !e->ExtendedAck() ?
1524           tr("No reason provided") :
1525             QString::fromUtf8(e->ExtendedAck()->response().c_str());
1526         QString result = tr("Chat with %1 refused:\n%2")
1527           .arg(!u.isLocked() ? u->accountId().c_str() : QString::fromUtf8(u->getAlias().c_str()))
1528           .arg(s);
1529         u.unlock();
1530         InformUser(this, result);
1531       }
1532       else
1533       {
1534         const Licq::EventChat* c = dynamic_cast<const Licq::EventChat*>(e->userEvent());
1535         if (c->Port() == 0)  // If we requested a join, no need to do anything
1536         {
1537           ChatDlg* chatDlg = new ChatDlg(myUsers.front());
1538           chatDlg->StartAsClient(e->ExtendedAck()->port());
1539         }
1540       }
1541       break;
1542   }
1543 
1544   emit eventSent(e);
1545   if (Config::Chat::instance()->msgChatView() && myHistoryView != NULL)
1546   {
1547     myMessageEdit->clear();
1548     myMessageEdit->setFocus();
1549 
1550     // Makes the cursor blink so that the user sees that the text edit has focus.
1551     myMessageEdit->moveCursor(QTextCursor::Start);
1552 
1553     myUrlEdit->clear();
1554     myChatItemEdit->clear();
1555     myFileEdit->clear();
1556     myFileList.clear();
1557     myFileEditButton->setEnabled(false);
1558     myContactsList->clear();
1559 
1560     // After sending URI/File/Contact/ChatRequest switch back to text message
1561     if (myType != MessageEvent)
1562       changeEventType(MessageEvent);
1563   }
1564   else
1565     close();
1566 }
1567 
cancelSend()1568 void UserSendEvent::cancelSend()
1569 {
1570   unsigned long icqEventTag = 0;
1571 
1572   if (myEventTag.size())
1573     icqEventTag = myEventTag.front();
1574 
1575   if (icqEventTag == 0)
1576     return closeDialog(); // if we're not sending atm, let ESC close the window
1577 
1578   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1579   if (tabDlg != NULL && tabDlg->tabIsSelected(this))
1580     tabDlg->setWindowTitle(myTitle);
1581 
1582   gProtocolManager.cancelEvent(myUsers.front(), icqEventTag);
1583 }
1584 
changeEventType(QAction * action)1585 void UserSendEvent::changeEventType(QAction* action)
1586 {
1587   changeEventType(action->data().toInt());
1588 }
1589 
clearNewEvents()1590 void UserSendEvent::clearNewEvents()
1591 {
1592   // Iterate all users in the conversation
1593   for (list<Licq::UserId>::iterator it = myUsers.begin(); it != myUsers.end(); ++it)
1594   {
1595     Licq::UserWriteGuard u(*it);
1596     if (!u.isLocked())
1597       continue;
1598 
1599     UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1600     if (Config::Chat::instance()->msgChatView() &&
1601         isActiveWindow() &&
1602         (tabDlg == NULL || (!tabDlg->tabExists(this) || tabDlg->tabIsSelected(this))))
1603     {
1604       if (u->NewMessages() > 0)
1605       {
1606         std::vector<int> idList;
1607         for (unsigned short i = 0; i < u->NewMessages(); i++)
1608         {
1609           const Licq::UserEvent* e = u->EventPeek(i);
1610           if (e->Id() <= myHighestEventId && e->isReceiver() &&
1611               (e->eventType() == Licq::UserEvent::TypeMessage ||
1612               e->eventType() == Licq::UserEvent::TypeUrl))
1613             idList.push_back(e->Id());
1614         }
1615 
1616         for (std::vector<int>::size_type i = 0; i < idList.size(); i++)
1617           u->EventClearId(idList[i]);
1618       }
1619     }
1620   }
1621 }
1622 
closeDialog()1623 void UserSendEvent::closeDialog()
1624 {
1625   if (mySendTypingTimer->isActive())
1626     gProtocolManager.sendTypingNotification(myUsers.front(), false, myConvoId);
1627 
1628   if (Config::Chat::instance()->msgChatView())
1629   {
1630     // the window is at the front, if the timer has not expired and we close
1631     // the window, then the new events will stay there
1632     clearNewEvents();
1633   }
1634 
1635   Config::Chat::instance()->setCheckSpelling(myMessageEdit->checkSpellingEnabled());
1636   close();
1637 }
1638 
showEmoticonsMenu()1639 void UserSendEvent::showEmoticonsMenu()
1640 {
1641   // If no emoticons are available, don't display an empty window
1642   if (Emoticons::self()->emoticonsKeys().size() <= 0)
1643     return;
1644 
1645   SelectEmoticon* p = new SelectEmoticon(this);
1646 
1647   QWidget* desktop = qApp->desktop();
1648   QSize s = p->sizeHint();
1649   QWidget* button = myToolBar->widgetForAction(myEmoticon);
1650   QPoint pos = QPoint(0, button->height());
1651   pos = button->mapToGlobal(pos);
1652   if (pos.x() + s.width() > desktop->width())
1653   {
1654     pos.setX(desktop->width() - s.width());
1655     if (pos.x() < 0)
1656       pos.setX(0);
1657   }
1658   if (pos.y() + s.height() > desktop->height())
1659   {
1660     pos.setY(pos.y() - button->height() - s.height());
1661     if (pos.y() < 0)
1662       pos.setY(0);
1663   }
1664 
1665   connect(p, SIGNAL(selected(const QString&)), SLOT(insertEmoticon(const QString&)));
1666   p->move(pos);
1667   p->show();
1668 }
1669 
insertEmoticon(const QString & value)1670 void UserSendEvent::insertEmoticon(const QString& value)
1671 {
1672   myMessageEdit->insertPlainText(value);
1673 }
1674 
messageAdded()1675 void UserSendEvent::messageAdded()
1676 {
1677   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1678   if (isActiveWindow() &&
1679       (!Config::Chat::instance()->tabbedChatting() ||
1680        (tabDlg != NULL && tabDlg->tabIsSelected(this))))
1681     QTimer::singleShot(clearDelay, this, SLOT(clearNewEvents()));
1682 }
1683 
resetTitle()1684 void UserSendEvent::resetTitle()
1685 {
1686   UserEventTabDlg* tabDlg = gLicqGui->userEventTabDlg();
1687   if (tabDlg != NULL && tabDlg->tabIsSelected(this))
1688     tabDlg->setWindowTitle(myTitle);
1689 
1690   setWindowTitle(myTitle);
1691 }
1692 
sendServerToggled(bool sendServer)1693 void UserSendEvent::sendServerToggled(bool sendServer)
1694 {
1695   // When the "Send through server" checkbox is toggled by the user,
1696   // we save the setting to disk, so it is persistent.
1697 
1698   Licq::UserWriteGuard u(myUsers.front());
1699   if (u.isLocked())
1700     u->SetSendServer(sendServer);
1701 }
1702 
setBackgroundICQColor()1703 void UserSendEvent::setBackgroundICQColor()
1704 {
1705   QColor c = myMessageEdit->palette().color(QPalette::Base);
1706 #ifdef USE_KDE
1707   if (KColorDialog::getColor(c, this) != KColorDialog::Accepted)
1708     return;
1709 #else
1710   c = QColorDialog::getColor(c, this);
1711   if (!c.isValid())
1712     return;
1713 #endif
1714 
1715   myIcqColor.setBackground(c.red(), c.green(), c.blue());
1716   myMessageEdit->setBackground(c);
1717 }
1718 
setForegroundICQColor()1719 void UserSendEvent::setForegroundICQColor()
1720 {
1721   QColor c = myMessageEdit->palette().color(QPalette::Text);
1722 #ifdef USE_KDE
1723   if (KColorDialog::getColor(c, this) != KColorDialog::Accepted)
1724     return;
1725 #else
1726   c = QColorDialog::getColor(c, this);
1727   if (!c.isValid())
1728     return;
1729 #endif
1730 
1731   myIcqColor.setForeground(c.red(), c.green(), c.blue());
1732   myMessageEdit->setForeground(c);
1733 }
1734 
showSendTypeMenu()1735 void UserSendEvent::showSendTypeMenu()
1736 {
1737   // Menu is normally delayed but if we use InstantPopup mode shortcut won't work
1738   dynamic_cast<QToolButton*>(myToolBar->widgetForAction(myEventTypeMenu))->showMenu();
1739 }
1740 
messageTextChanged()1741 void UserSendEvent::messageTextChanged()
1742 {
1743   if (myMessageEdit->toPlainText().isEmpty())
1744     return;
1745 
1746   myTempMessage = myMessageEdit->toPlainText();
1747   gProtocolManager.sendTypingNotification(myUsers.front(), true, myConvoId);
1748   disconnect(myMessageEdit, SIGNAL(textChanged()), this, SLOT(messageTextChanged()));
1749   mySendTypingTimer->start(5000);
1750 }
1751 
textChangedTimeout()1752 void UserSendEvent::textChangedTimeout()
1753 {
1754   QString str = myMessageEdit->toPlainText();
1755 
1756   if (str != myTempMessage)
1757   {
1758     myTempMessage = str;
1759   }
1760   else
1761   {
1762     if (mySendTypingTimer->isActive())
1763       mySendTypingTimer->stop();
1764     connect(myMessageEdit, SIGNAL(textChanged()), SLOT(messageTextChanged()));
1765     gProtocolManager.sendTypingNotification(myUsers.front(), false, myConvoId);
1766   }
1767 }
1768 
sendTrySecure()1769 void UserSendEvent::sendTrySecure()
1770 {
1771   bool autoSecure = false;
1772   {
1773     Licq::UserReadGuard u(myUsers.front());
1774     if (u.isLocked())
1775     {
1776       autoSecure = (u->AutoSecure() && Licq::gDaemon.haveCryptoSupport() &&
1777           u->secureChannelSupport() == Licq::User::SecureChannelSupported &&
1778           !mySendServerCheck->isChecked() && !u->Secure());
1779     }
1780   }
1781 
1782   disconnect(mySendButton, SIGNAL(clicked()), this, SLOT(sendTrySecure()));
1783   connect(mySendButton, SIGNAL(clicked()), SLOT(send()));
1784 
1785   if (autoSecure)
1786   {
1787     QWidget* w = new KeyRequestDlg(myUsers.front());
1788     connect(w, SIGNAL(destroyed()), SLOT(send()));
1789   }
1790   else
1791     send();
1792 }
1793 
resizeEvent(QResizeEvent * event)1794 void UserSendEvent::resizeEvent(QResizeEvent* event)
1795 {
1796   Config::Chat::instance()->setSendDialogSize(size());
1797   UserEventCommon::resizeEvent(event);
1798 }
1799 
dragEnterEvent(QDragEnterEvent * event)1800 void UserSendEvent::dragEnterEvent(QDragEnterEvent* event)
1801 {
1802   if (event->mimeData()->hasText() ||
1803       event->mimeData()->hasUrls())
1804     event->acceptProposedAction();
1805 }
1806 
dropEvent(QDropEvent * event)1807 void UserSendEvent::dropEvent(QDropEvent* event)
1808 {
1809   event->ignore();
1810 
1811   if (gLicqGui->userDropEvent(myUsers.front(), *event->mimeData()))
1812     event->acceptProposedAction();
1813 }
1814 
chatInviteUser()1815 void UserSendEvent::chatInviteUser()
1816 {
1817   if (myChatPort == 0)
1818   {
1819     if (ChatDlg::chatDlgs.size() > 0)
1820     {
1821       ChatDlg* chatDlg = NULL;
1822       JoinChatDlg* j = new JoinChatDlg(true, this);
1823       if (j->exec() && (chatDlg = j->JoinedChat()) != NULL)
1824       {
1825         myChatItemEdit->setText(j->ChatClients());
1826         myChatPort = chatDlg->LocalPort();
1827         myChatClients = chatDlg->ChatName() + ", " + chatDlg->ChatClients();
1828       }
1829       delete j;
1830       myChatInviteButton->setText(tr("Clear"));
1831     }
1832   }
1833   else
1834   {
1835     myChatPort = 0;
1836     myChatClients = "";
1837     myChatItemEdit->setText("");
1838     myChatInviteButton->setText(tr("Invite"));
1839   }
1840 }
1841 
fileBrowse()1842 void UserSendEvent::fileBrowse()
1843 {
1844 #ifdef USE_KDE
1845   QStringList fl = KFileDialog::getOpenFileNames(KUrl(), QString(), this, tr("Select files to send"));
1846 #else
1847   QStringList fl = QFileDialog::getOpenFileNames(this, tr("Select files to send"));
1848 #endif
1849 
1850   if (fl.isEmpty())
1851     return;
1852 
1853   QStringList::ConstIterator it = fl.begin();
1854 
1855   for(; it != fl.end(); it++)
1856     myFileList.push_back(strdup((*it).toLocal8Bit()));
1857 
1858   fileUpdateLabel(myFileList.size());
1859 }
1860 
fileEditList()1861 void UserSendEvent::fileEditList()
1862 {
1863   EditFileListDlg* dlg = new EditFileListDlg(&myFileList);
1864   connect(dlg, SIGNAL(fileDeleted(unsigned)), SLOT(fileUpdateLabel(unsigned)));
1865 }
1866 
fileUpdateLabel(unsigned count)1867 void UserSendEvent::fileUpdateLabel(unsigned count)
1868 {
1869   myFileEditButton->setEnabled(count > 0);
1870 
1871   QString f;
1872 
1873   switch (count)
1874   {
1875     case 0:
1876       f = QString::null;
1877       break;
1878 
1879     case 1:
1880       f = myFileList.front().c_str();
1881       break;
1882 
1883     default:
1884       f = QString(tr("%1 Files")).arg(count);
1885       break;
1886   }
1887 
1888   myFileEdit->setText(f);
1889 }
1890