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