1 /*
2 Copyright (C) 2010 David Edmundson <kde@davidedmundson.co.uk>
3 Copyright (C) 2011 Dominik Schmidt <dev@dominik-schmidt.de>
4 Copyright (C) 2011 Francesco Nwokeka <francesco.nwokeka@gmail.com>
5 Copyright (C) 2014 Marcin Ziemiński <zieminn@gmail.com>
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "chat-window.h"
23
24 #include "chat-search-bar.h"
25 #include "chat-tab.h"
26
27 #include <KTp/service-availability-checker.h>
28 #include <KTp/actions.h>
29 #include <KTp/contact.h>
30
31 #include <KActionMenu>
32 #include <KStandardAction>
33 #include <KLocalizedString>
34 #include <KActionCollection>
35 #include <ksettings/dialog.h>
36 #include <kcmodulecontainer.h>
37 #include <KNotification>
38 #include <KNotifyConfigWidget>
39 #include <KToolBar>
40 #include <KToolInvocation>
41 #include <KCModuleProxy>
42 #include <KIconLoader>
43
44 #include <QCoreApplication>
45 #include <QFileDialog>
46 #include <QUrl>
47 #include <QMenu>
48 #include <QMenuBar>
49 #include <QTabBar>
50 #include <QAction>
51 #include <QEvent>
52 #include <QWidgetAction>
53 #include <QLabel>
54 #include <QLineEdit>
55 #include <QMouseEvent>
56 #include <QScrollArea>
57
58 #include <TelepathyQt/Account>
59 #include <TelepathyQt/ContactCapabilities>
60 #include <TelepathyQt/PendingChannelRequest>
61 #include <TelepathyQt/TextChannel>
62
63 #include <Sonnet/DictionaryComboBox>
64
65 #include "emoticon-text-edit-action.h"
66 #include "invite-contact-dialog.h"
67 #include "telepathy-chat-ui.h"
68 #include "text-chat-config.h"
69
70 #ifdef HAVE_KPEOPLE
71 #include <kpeople/widgets/persondetailsdialog.h>
72 #include <kpeople/global.h>
73 #include <kpeople/persondata.h>
74 #endif
75
76 #include <KTp/Widgets/contact-info-dialog.h>
77 #include <KTp/OTR/constants.h>
78
79 #define PREFERRED_RFB_HANDLER "org.freedesktop.Telepathy.Client.krfb_rfb_handler"
80
81 // FIXME
82 // As of Qt 5.4 there's no way to get middle mouse click
83 // event other than reimplementing mouseReleaseEvent
84 // Remove this class once Qt has something for this
85 class MiddleMouseButtonHandler : public QTabWidget
86 {
87 Q_OBJECT
88 public:
89 MiddleMouseButtonHandler(QWidget *parent);
90
91 Q_SIGNALS:
92 void mouseMiddleClick(int index);
93
94 protected:
95 virtual void mouseReleaseEvent(QMouseEvent *event);
96 };
97
MiddleMouseButtonHandler(QWidget * parent)98 MiddleMouseButtonHandler::MiddleMouseButtonHandler(QWidget *parent)
99 : QTabWidget(parent)
100 {
101 }
102
mouseReleaseEvent(QMouseEvent * event)103 void MiddleMouseButtonHandler::mouseReleaseEvent(QMouseEvent *event)
104 {
105 if (event->button() == Qt::MiddleButton) {
106 Q_EMIT mouseMiddleClick(tabBar()->tabAt(event->pos()));
107 }
108
109 QTabWidget::mouseReleaseEvent(event);
110 }
111
112 //------------------------------------------------------------------------------
113
114 Q_GLOBAL_STATIC_WITH_ARGS(KTp::ServiceAvailabilityChecker, s_krfbAvailableChecker,
115 (QLatin1String(PREFERRED_RFB_HANDLER)));
116
ChatWindow()117 ChatWindow::ChatWindow()
118 : m_sendMessage(0),
119 m_tabWidget(0),
120 m_keyboardLayoutInterface(0),
121 m_otrActionMenu(0),
122 m_proxyService(
123 new ProxyService(QDBusConnection::sessionBus(),
124 KTP_PROXY_BUS_NAME,
125 KTP_PROXY_SERVICE_OBJECT_PATH,
126 this))
127 {
128 //This effectively constructs the s_krfbAvailableChecker object the first
129 //time that this code is executed. This is to start the d-bus query early, so
130 //that data are available when we need them later in desktopSharingCapability()
131 (void) s_krfbAvailableChecker.operator->();
132
133 KConfig config(QLatin1String("ktelepathyrc"));
134 KConfigGroup group = config.group("Appearance");
135 m_zoomFactor = group.readEntry("zoomFactor", (qreal) 1.0);
136
137 //setup actions
138 KStandardAction::close(this,SLOT(closeCurrentTab()),actionCollection());
139 KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
140 KStandardAction::preferences(this, SLOT(showSettingsDialog()), actionCollection());
141 KStandardAction::configureNotifications(this, SLOT(showNotificationsDialog()), actionCollection());
142 KStandardAction::showMenubar(this->menuBar(), SLOT(setVisible(bool)), actionCollection());
143
144 // keyboard shortcuts for the search bar
145 KStandardAction::find(this, SLOT(onSearchActionToggled()), actionCollection());
146
147 // start disabled
148 KStandardAction::findNext(this, SLOT(onFindNextText()), actionCollection())->setEnabled(false);
149 KStandardAction::findPrev(this, SLOT(onFindPreviousText()), actionCollection())->setEnabled(false);
150
151 KStandardAction::zoomIn(this, SLOT(onZoomIn()), actionCollection());
152 KStandardAction::zoomOut(this, SLOT(onZoomOut()), actionCollection());
153
154 m_keyboardLayoutInterface = new QDBusInterface(QLatin1String("org.kde.keyboard"), QLatin1String("/Layouts"),
155 QLatin1String("org.kde.KeyboardLayouts"), QDBusConnection::sessionBus(), this);
156
157 connect(m_keyboardLayoutInterface, SIGNAL(currentLayoutChanged(QString)), this, SLOT(onKeyboardLayoutChange(QString)));
158
159 // set up m_tabWidget
160 m_tabWidget = new MiddleMouseButtonHandler(this);
161 //clicking on the close button can result in the tab bar getting focus, this works round that
162 m_tabWidget->setFocusPolicy(Qt::TabFocus);
163 m_tabWidget->setMovable(true);
164 m_tabWidget->setDocumentMode(true);
165 m_tabWidget->setTabsClosable(true);
166 m_tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
167 m_tabWidget->tabBar()->hide();
168 m_tabWidget->setElideMode(Qt::ElideRight);
169
170 connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(destroyTab(int)));
171 connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(onCurrentIndexChanged(int)));
172
173 connect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), this, SLOT(onTabMiddleClicked(int)));
174 connect(qobject_cast<QTabBar*>(m_tabWidget->tabBar()), SIGNAL(customContextMenuRequested(QPoint)), SLOT(tabBarContextMenu(QPoint)));
175
176 setCentralWidget(m_tabWidget);
177
178 // create custom actions
179 // we must do it AFTER m_tabWidget is set up
180 setupCustomActions();
181
182 // start otr service and create otr actions for otr popup menu
183 setupOTR();
184
185 setupGUI(QSize(460, 440), static_cast<StandardWindowOptions>(Default^StatusBar), QLatin1String("chatwindow.rc"));
186
187 // Connects the toolbars iconSizeChanged to the custom toolbar item
188 // NOTE Must be called after setupGUI or the toolbars won't be created
189 Q_FOREACH(KToolBar *toolbar, toolBars()) {
190 connect(toolbar, SIGNAL(iconSizeChanged(const QSize&)), SLOT(updateAccountIcon()));
191 }
192 }
193
~ChatWindow()194 ChatWindow::~ChatWindow()
195 {
196 Q_EMIT aboutToClose(this);
197 }
198
tabBarContextMenu(const QPoint & globalPos)199 void ChatWindow::tabBarContextMenu(const QPoint &globalPos)
200 {
201 int index = m_tabWidget->tabBar()->tabAt(globalPos);
202 QAction close(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("Close"), this);
203 QAction dettach(QIcon::fromTheme(QStringLiteral("tab-detach")), i18n("Detach Tab"), this);
204 QAction moveLeft(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Move Tab Left"), this);
205 QAction moveRight(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("Move Tab Right"), this);
206
207 QMenu *menu = new QMenu(this);
208
209 menu->addAction(&moveLeft);
210 menu->addAction(&moveRight);
211 menu->addAction(&dettach);
212 menu->addAction(&close);
213
214 if (index == 0) {
215 moveLeft.setEnabled(false);
216 } else if (index == (m_tabWidget->count() - 1)) {
217 moveRight.setEnabled(false);
218 }
219
220 QAction *result = qobject_cast<QAction*>(menu->exec(m_tabWidget->tabBar()->mapToGlobal(globalPos)));
221
222 if(result == &close) {
223 destroyTab(index);
224 } else if (result == &dettach) {
225 Q_EMIT detachRequested(qobject_cast<ChatTab*>(m_tabWidget->widget(index)));
226 } else if (result == &moveLeft) {
227 m_tabWidget->tabBar()->moveTab(index, index - 1);
228 } else if (result == &moveRight) {
229 m_tabWidget->tabBar()->moveTab(index, index + 1);
230 }
231 }
232
focusChat(ChatTab * tab)233 void ChatWindow::focusChat(ChatTab *tab)
234 {
235 m_tabWidget->setCurrentWidget(tab);
236 }
237
getTab(const Tp::AccountPtr & account,const Tp::TextChannelPtr & incomingTextChannel)238 ChatTab* ChatWindow::getTab(const Tp::AccountPtr &account, const Tp::TextChannelPtr &incomingTextChannel)
239 {
240 ChatTab *match = 0;
241
242 // if targetHandle is None, targetId is also "", therefore we won't be able to find it.
243 if (incomingTextChannel->targetHandleType() != Tp::HandleTypeNone) {
244
245 //loop through all tabs checking for matches
246 for (int index = 0; index < m_tabWidget->count() && !match; ++index) {
247
248 // get chatWidget object
249 ChatTab *auxChatTab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
250
251 Q_ASSERT(auxChatTab);
252
253 // check for duplicate chat
254 if (auxChatTab->account() == account
255 && auxChatTab->textChannel()->targetId() == incomingTextChannel->targetId()
256 && auxChatTab->textChannel()->targetHandleType() == incomingTextChannel->targetHandleType()) {
257 match = auxChatTab;
258 }
259 }
260 }
261 return match;
262 }
263
getCurrentTab()264 ChatTab *ChatWindow::getCurrentTab()
265 {
266 return qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
267
268 }
269
tabs() const270 QList<ChatTab*> ChatWindow::tabs() const
271 {
272 QList<ChatTab*> tabs;
273 tabs.reserve(m_tabWidget->count());
274 for (int i = 0; i < m_tabWidget->count(); ++i) {
275 tabs << qobject_cast<ChatTab*>(m_tabWidget->widget(i));
276 }
277 return tabs;
278 }
279
removeTab(ChatTab * tab)280 void ChatWindow::removeTab(ChatTab *tab)
281 {
282 tab->stopOtrSession();
283 removeChatTabSignals(tab);
284
285 m_tabWidget->removeTab(m_tabWidget->indexOf(tab));
286
287 if (m_tabWidget->tabBar()->isVisible() && m_tabWidget->count() <= 1) {
288 m_tabWidget->tabBar()->hide();
289 }
290 }
291
onTabMiddleClicked(int index)292 void ChatWindow::onTabMiddleClicked(int index)
293 {
294 destroyTab(index);
295 }
296
addTab(ChatTab * tab)297 void ChatWindow::addTab(ChatTab *tab)
298 {
299 setupChatTabSignals(tab);
300 tab->setZoomFactor(m_zoomFactor);
301
302 QDBusPendingCall dbusPendingCall = m_keyboardLayoutInterface->asyncCall(QLatin1String("getCurrentLayout"));
303 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(dbusPendingCall, this);
304 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onGetCurrentKeyboardLayoutFinished(QDBusPendingCallWatcher*)));
305
306 m_tabWidget->addTab(tab, tab->icon(), tab->title());
307 m_tabWidget->setCurrentWidget(tab);
308 m_tabWidget->setTabToolTip(m_tabWidget->indexOf(tab), tab->title());
309
310 if (!m_tabWidget->tabBar()->isVisible() && m_tabWidget->count() > 1) {
311 m_tabWidget->tabBar()->show();
312 }
313
314 tab->setFocus();
315 tab->updateSendMessageShortcuts(m_sendMessage->shortcuts());
316 // block text input if key is being generated for this account
317 if(m_proxyService->isOngoingGeneration(QDBusObjectPath(tab->account()->objectPath()))) {
318 tab->blockTextInput(true);
319 }
320 }
321
destroyTab(int index)322 void ChatWindow::destroyTab(int index)
323 {
324 ChatTab *tab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
325 Q_ASSERT(tab);
326
327 tab->setChatWindow(0);
328 tab->deleteLater();
329 }
330
setTabText(int index,const QString & newTitle)331 void ChatWindow::setTabText(int index, const QString &newTitle)
332 {
333 m_tabWidget->setTabText(index, newTitle);
334
335 // this updates the window title and icon if the updated tab is the current one
336 if (index == m_tabWidget->currentIndex()) {
337 onCurrentIndexChanged(index);
338 }
339 }
340
setTabIcon(int index,const QIcon & newIcon)341 void ChatWindow::setTabIcon(int index, const QIcon & newIcon)
342 {
343 m_tabWidget->setTabIcon(index, newIcon);
344
345 // this updates the window title and icon if the updated tab is the current one
346 if (index == m_tabWidget->currentIndex()) {
347 onCurrentIndexChanged(index);
348 }
349 }
350
setTabTextColor(int index,const QColor & color)351 void ChatWindow::setTabTextColor(int index, const QColor& color)
352 {
353 m_tabWidget->tabBar()->setTabTextColor(index, color);
354 }
355
closeCurrentTab()356 void ChatWindow::closeCurrentTab()
357 {
358 destroyTab(m_tabWidget->currentIndex());
359 }
360
onAudioCallTriggered()361 void ChatWindow::onAudioCallTriggered()
362 {
363 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
364
365 // a check. Should never happen
366 if (!currChat) {
367 return;
368 }
369
370 startAudioCall(currChat->account(), currChat->textChannel()->targetContact());
371 }
372
onBlockContactTriggered()373 void ChatWindow::onBlockContactTriggered()
374 {
375 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
376
377 if (!currChat) {
378 return;
379 }
380
381 Tp::ContactPtr contact = currChat->textChannel()->targetContact();
382 if(!contact.isNull()) {
383 contact->block();
384 }
385 }
386
onCurrentIndexChanged(int index)387 void ChatWindow::onCurrentIndexChanged(int index)
388 {
389 if (index == -1) {
390 close();
391 return;
392 }
393
394 ChatTab *currentChatTab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
395 currentChatTab->acknowledgeMessages();
396 setWindowTitle(currentChatTab->title());
397 if (hasUnreadMessages()) {
398 setWindowIcon(QIcon::fromTheme(QStringLiteral("mail-mark-unread-new")));
399 } else {
400 setWindowIcon(currentChatTab->icon());
401 }
402
403 m_spellDictCombo->setCurrentByDictionary(currentChatTab->spellDictionary());
404
405 if (currentChatTab->isActiveWindow()) {
406 restoreKeyboardLayout(currentChatTab);
407 }
408
409 // when the tab changes I need to "refresh" the window's findNext and findPrev actions
410 if (currentChatTab->chatSearchBar()->searchBar()->text().isEmpty()) {
411 onEnableSearchActions(false);
412 } else {
413 onEnableSearchActions(true);
414 }
415
416 //enable/disable send file/start webcam buttons
417 //always disabled for group chats and offline accounts.
418 if (!currentChatTab->isGroupChat() && currentChatTab->account()->connection()) {
419 // check which capabilities the contact and user supports
420 KTp::ContactPtr targetContact = KTp::ContactPtr::qObjectCast(currentChatTab->textChannel()->targetContact());
421
422 setAudioCallEnabled(targetContact->audioCallCapability());
423 setFileTransferEnabled(targetContact->fileTransferCapability());
424 setVideoCallEnabled(targetContact->videoCallCapability());
425 setShareDesktopEnabled(targetContact->streamTubeServicesCapability().contains(QLatin1String("rfb")));
426 setInviteToChatEnabled(currentChatTab->account()->capabilities().textChatrooms());
427 setShowInfoEnabled(true);
428 toggleBlockButton(targetContact->isBlocked());
429 } else {
430 setAudioCallEnabled(false);
431 setFileTransferEnabled(false);
432 setVideoCallEnabled(false);
433 setShareDesktopEnabled(false);
434 setInviteToChatEnabled(currentChatTab->account()->capabilities().textChatrooms());
435 setBlockEnabled(false);
436 setShowInfoEnabled(false);
437 }
438
439 onOtrStatusChanged(currentChatTab->otrStatus());
440
441 // Allow "Leaving" rooms only in group chat, and when persistent rooms are enabled
442 actionCollection()->action(QLatin1String("leave-chat"))->setEnabled(currentChatTab->isGroupChat());
443 // No point having "Close" action with only one tab, it behaves exactly like "Quit"
444 actionCollection()->action(QLatin1String("file_close"))->setVisible(m_tabWidget->count() > 1);
445
446 if ( currentChatTab->account()->connection() ) {
447 const QString collab(QLatin1String("infinote"));
448 bool selfCanShare = currentChatTab->account()->connection()->selfContact()->capabilities().streamTubes(collab);
449 if (currentChatTab->isGroupChat()) {
450 // We can always share documents with a group chat if we support the service
451 setCollaborateDocumentEnabled(selfCanShare);
452 }
453 else {
454 bool otherCanShare = currentChatTab->textChannel()->targetContact()->capabilities().streamTubes(collab);
455 setCollaborateDocumentEnabled(selfCanShare && otherCanShare);
456 }
457 }
458
459 // only show enable the action if there are actually previous converstations
460 setPreviousConversationsEnabled(currentChatTab->previousConversationAvailable());
461
462 updateAccountIcon();
463 }
464
onEnableSearchActions(bool enable)465 void ChatWindow::onEnableSearchActions(bool enable)
466 {
467 actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::FindNext)))->setEnabled(enable);
468 actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::FindPrev)))->setEnabled(enable);
469 }
470
onFileTransferTriggered()471 void ChatWindow::onFileTransferTriggered()
472 {
473 // This feature should be used only in 1on1 chats!
474 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
475
476 // This should never happen
477 if (!currChat) {
478 return;
479 }
480
481 startFileTransfer(currChat->account(), currChat->textChannel()->targetContact());
482 }
483
onFindNextText()484 void ChatWindow::onFindNextText()
485 {
486 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
487
488 // This should never happen
489 if(!currChat) {
490 return;
491 }
492 currChat->chatSearchBar()->onNextButtonClicked();
493 }
494
onFindPreviousText()495 void ChatWindow::onFindPreviousText()
496 {
497 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
498
499 // This should never happen
500 if(!currChat) {
501 return;
502 }
503 currChat->chatSearchBar()->onPreviousButtonClicked();
504 }
505
onGenericOperationFinished(Tp::PendingOperation * op)506 void ChatWindow::onGenericOperationFinished(Tp::PendingOperation* op)
507 {
508 // send notification via plasma like the contactlist does
509 if (op->isError()) {
510 QString errorMsg(op->errorName() + QLatin1String(": ") + op->errorMessage());
511 sendNotificationToUser(SystemErrorMessage, errorMsg);
512 }
513 }
514
onGetCurrentKeyboardLayoutFinished(QDBusPendingCallWatcher * watcher)515 void ChatWindow::onGetCurrentKeyboardLayoutFinished(QDBusPendingCallWatcher* watcher)
516 {
517 if (!watcher->isError()) {
518 QDBusMessage replyMessage = watcher->reply();
519 ChatTab *chatTab = getCurrentTab();
520 if (chatTab) {
521 QString layout = replyMessage.arguments().first().toString();
522 chatTab->setCurrentKeyboardLayoutLanguage(layout);
523 }
524 }
525 watcher->deleteLater();
526 }
527
onInviteToChatTriggered()528 void ChatWindow::onInviteToChatTriggered()
529 {
530 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
531
532 InviteContactDialog *dialog = new InviteContactDialog(KTp::accountManager(), currChat->account(), currChat->textChannel(), this);
533 dialog->setAttribute(Qt::WA_DeleteOnClose);
534 dialog->show();
535 }
536
onKeyboardLayoutChange(const QString & keyboardLayout)537 void ChatWindow::onKeyboardLayoutChange(const QString& keyboardLayout)
538 {
539 ChatTab *currChat = getCurrentTab();
540 if (currChat) {
541 // To prevent keyboard layout change when the ChatWindow is minimized or not active
542 if (currChat->isActiveWindow()) {
543 currChat->setCurrentKeyboardLayoutLanguage(keyboardLayout);
544 }
545 }
546 }
547
onNextTabActionTriggered()548 void ChatWindow::onNextTabActionTriggered()
549 {
550 if (m_tabWidget->count() == 1) {
551 return;
552 }
553
554 int currIndex = m_tabWidget->currentIndex();
555
556 if (currIndex < m_tabWidget->count()-1) {
557 m_tabWidget->setCurrentIndex(++currIndex);
558 } else {
559 m_tabWidget->setCurrentIndex(0);
560 }
561 }
562
onPreviousTabActionTriggered()563 void ChatWindow::onPreviousTabActionTriggered()
564 {
565 if (m_tabWidget->count() == 1) {
566 return;
567 }
568
569 int currIndex = m_tabWidget->currentIndex();
570
571 if (currIndex > 0) {
572 m_tabWidget->setCurrentIndex(--currIndex);
573 } else {
574 m_tabWidget->setCurrentIndex(m_tabWidget->count()-1);
575 }
576 }
577
onSearchActionToggled()578 void ChatWindow::onSearchActionToggled()
579 {
580 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
581
582 // This should never happen
583 if(!currChat) {
584 return;
585 }
586 currChat->toggleSearchBar();
587 }
588
onCollaborateDocumentTriggered()589 void ChatWindow::onCollaborateDocumentTriggered()
590 {
591 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
592
593 if(!currChat) {
594 return;
595 }
596 if (currChat->isGroupChat()) {
597 offerDocumentToChatroom(currChat->account(), currChat->textChannel()->targetId());
598 }
599 else {
600 offerDocumentToContact(currChat->account(), currChat->textChannel()->targetContact());
601 }
602 }
603
onTabStateChanged()604 void ChatWindow::onTabStateChanged()
605 {
606 QIcon windowIcon;
607 ChatTab *sender = qobject_cast<ChatTab*>(QObject::sender());
608 if (sender) {
609 int tabIndex = m_tabWidget->indexOf(sender);
610 setTabTextColor(tabIndex, sender->titleColor());
611
612 if (TextChatConfig::instance()->showOthersTyping() && (sender->remoteChatState() == Tp::ChannelChatStateComposing)) {
613 setTabIcon(tabIndex, QIcon::fromTheme(QStringLiteral("document-edit")));
614 if (sender == m_tabWidget->currentWidget()) {
615 windowIcon = QIcon::fromTheme(QStringLiteral("document-edit"));
616 } else {
617 windowIcon = qobject_cast<ChatTab*>(m_tabWidget->currentWidget())->icon();
618 }
619 } else {
620 setTabIcon(tabIndex, sender->icon());
621 windowIcon = qobject_cast<ChatTab*>(m_tabWidget->currentWidget())->icon();
622 }
623
624 if (sender->unreadMessageCount() > 0) {
625 setTabIcon(tabIndex, QIcon::fromTheme(QStringLiteral("mail-mark-unread-new")));
626 }
627 }
628
629 if (hasUnreadMessages()) {
630 windowIcon = QIcon::fromTheme(QStringLiteral("mail-mark-unread-new"));
631 }
632
633 setWindowIcon(windowIcon);
634 }
635
onTabIconChanged(const QIcon & newIcon)636 void ChatWindow::onTabIconChanged(const QIcon & newIcon)
637 {
638 //find out which widget made the call, and update the correct tab.
639 QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
640 if (sender) {
641 int tabIndexToChange = m_tabWidget->indexOf(sender);
642 setTabIcon(tabIndexToChange, newIcon);
643 }
644 }
645
onTabTextChanged(const QString & newTitle)646 void ChatWindow::onTabTextChanged(const QString &newTitle)
647 {
648 //find out which widget made the call, and update the correct tab.
649 QWidget* sender = qobject_cast<QWidget*>(QObject::sender());
650 if (sender) {
651 int tabIndexToChange = m_tabWidget->indexOf(sender);
652 setTabText(tabIndexToChange, newTitle);
653 }
654 }
655
onVideoCallTriggered()656 void ChatWindow::onVideoCallTriggered()
657 {
658 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
659
660 // This should never happen
661 if (!currChat) {
662 return;
663 }
664
665 startVideoCall(currChat->account(), currChat->textChannel()->targetContact());
666 }
667
onUnblockContactTriggered()668 void ChatWindow::onUnblockContactTriggered()
669 {
670 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
671
672 if(!currChat) {
673 return;
674 }
675
676 Tp::ContactPtr contact = currChat->textChannel()->targetContact();
677 contact->unblock();
678 }
679
onShareDesktopTriggered()680 void ChatWindow::onShareDesktopTriggered()
681 {
682 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
683
684 // This should never happen
685 if (!currChat) {
686 return;
687 }
688
689 startShareDesktop(currChat->account(), currChat->textChannel()->targetContact());
690 }
691
onShowInfoTriggered()692 void ChatWindow::onShowInfoTriggered()
693 {
694 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
695 const Tp::ContactPtr contact = currChat->textChannel()->targetContact();
696
697 if(!currChat || !currChat->account() || !contact) {
698 return;
699 }
700
701 if (KTp::kpeopleEnabled()) {
702 #ifdef HAVE_KPEOPLE
703 QString personId(QLatin1String("ktp://"));
704 personId.append(currChat->account()->uniqueIdentifier());
705 personId.append(QLatin1String("?"));
706 personId.append(contact->id());
707
708 if (!personId.isEmpty()) {
709 KPeople::PersonDetailsDialog *view = new KPeople::PersonDetailsDialog(currChat);
710 KPeople::PersonData *person = new KPeople::PersonData(personId, view);
711 view->setPerson(person);
712 view->setAttribute(Qt::WA_DeleteOnClose);
713 view->show();
714 }
715 #endif
716 } else {
717 KTp::ContactInfoDialog* contactInfoDialog = new KTp::ContactInfoDialog(currChat->account(), contact, currChat);
718 contactInfoDialog->setAttribute(Qt::WA_DeleteOnClose);
719 contactInfoDialog->show();
720 }
721 }
722
onOpenContactListTriggered()723 void ChatWindow::onOpenContactListTriggered()
724 {
725 KToolInvocation::kdeinitExec(QLatin1String("ktp-contactlist"));
726 }
727
onOpenLogTriggered()728 void ChatWindow::onOpenLogTriggered()
729 {
730 int index = m_tabWidget->currentIndex();
731 ChatTab *currentChatTab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
732 Q_ASSERT(currentChatTab);
733
734 Tp::AccountPtr account = currentChatTab->account();
735 Tp::ContactPtr contact = currentChatTab->textChannel()->targetContact();
736
737 /* Add "--" before the UIDs so that KCmdLineArgs in ktp-log-viewer does not try to parse
738 * UIDs starting with "-" as arguments */
739 if (!contact.isNull()) {
740 KToolInvocation::kdeinitExec(QLatin1String("ktp-log-viewer"),
741 QStringList() << QLatin1String("--") << account->uniqueIdentifier() << contact->id());
742 } else {
743 KToolInvocation::kdeinitExec(QLatin1String("ktp-log-viewer"),
744 QStringList() << QLatin1String("--") << account->uniqueIdentifier() << currentChatTab->textChannel()->targetId());
745 }
746 }
747
onClearViewTriggered()748 void ChatWindow::onClearViewTriggered()
749 {
750 ChatWidget *chatWidget = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
751
752 if (!chatWidget) {
753 return;
754 }
755
756 chatWidget->clear();
757 }
758
showSettingsDialog()759 void ChatWindow::showSettingsDialog()
760 {
761 KSettings::Dialog *dialog = new KSettings::Dialog(this);
762
763 KPageWidgetItem *configPage = dialog->addModule(QLatin1String("kcm_ktp_chat_appearance"));
764 QScrollArea *moduleScroll = qobject_cast<QScrollArea*>(configPage->widget());
765 Q_ASSERT(moduleScroll);
766 KCModuleProxy *proxy = qobject_cast<KCModuleProxy*>(moduleScroll->widget());
767 Q_ASSERT(proxy);
768
769 connect(proxy->realModule(), SIGNAL(reloadTheme()),
770 this, SLOT(onReloadTheme()));
771
772 dialog->addModule(QLatin1String("kcm_ktp_chat_behavior"));
773 dialog->addModule(QLatin1String("kcm_ktp_chat_messages"));
774
775 KPageWidgetItem *otrConfigPage = dialog->addModule(QLatin1String("kcm_ktp_chat_otr"));
776 moduleScroll = qobject_cast<QScrollArea*>(otrConfigPage->widget());
777 Q_ASSERT(moduleScroll);
778 proxy = qobject_cast<KCModuleProxy*>(moduleScroll->widget());
779 Q_ASSERT(proxy);
780 QVariant value;
781 value.setValue(m_proxyService);
782 proxy->realModule()->setProperty("proxyService", value);
783
784 dialog->setAttribute(Qt::WA_DeleteOnClose);
785 dialog->show();
786 }
787
showNotificationsDialog()788 void ChatWindow::showNotificationsDialog()
789 {
790 KNotifyConfigWidget::configure(this, QLatin1String("ktelepathy"));
791 }
792
removeChatTabSignals(ChatTab * chatTab)793 void ChatWindow::removeChatTabSignals(ChatTab *chatTab)
794 {
795 disconnect(chatTab, SIGNAL(titleChanged(QString)), this, SLOT(onTabTextChanged(QString)));
796 disconnect(chatTab, SIGNAL(iconChanged(QIcon)), this, SLOT(onTabIconChanged(QIcon)));
797 disconnect(chatTab, SIGNAL(unreadMessagesChanged()), this, SLOT(onTabStateChanged()));
798 disconnect(chatTab, SIGNAL(contactPresenceChanged(KTp::Presence)), this, SLOT(onTabStateChanged()));
799 disconnect(chatTab->chatSearchBar(), SIGNAL(enableSearchButtonsSignal(bool)), this, SLOT(onEnableSearchActions(bool)));
800 disconnect(chatTab, SIGNAL(contactBlockStatusChanged(bool)), this, SLOT(toggleBlockButton(bool)));
801 if(chatTab->otrStatus())
802 disconnect(chatTab, SIGNAL(otrStatusChanged(OtrStatus)), this, SLOT(onOtrStatusChanged(OtrStatus)));
803 }
804
sendNotificationToUser(ChatWindow::NotificationType type,const QString & errorMsg)805 void ChatWindow::sendNotificationToUser(ChatWindow::NotificationType type, const QString& errorMsg)
806 {
807 //The pointer is automatically deleted when the event is closed
808 KNotification *notification;
809
810 if (type == SystemInfoMessage) {
811 notification = new KNotification(QLatin1String("telepathyInfo"));
812 } else {
813 notification = new KNotification(QLatin1String("telepathyError"));
814 }
815
816 notification->setText(errorMsg);
817 notification->sendEvent();
818 }
819
setupChatTabSignals(ChatTab * chatTab)820 void ChatWindow::setupChatTabSignals(ChatTab *chatTab)
821 {
822 connect(chatTab, SIGNAL(titleChanged(QString)), this, SLOT(onTabTextChanged(QString)));
823 connect(chatTab, SIGNAL(iconChanged(QIcon)), this, SLOT(onTabIconChanged(QIcon)));
824 connect(chatTab, SIGNAL(userTypingChanged(Tp::ChannelChatState)), this, SLOT(onTabStateChanged()));
825 connect(chatTab, SIGNAL(unreadMessagesChanged()), this, SLOT(onTabStateChanged()));
826 connect(chatTab, SIGNAL(contactPresenceChanged(KTp::Presence)), this, SLOT(onTabStateChanged()));
827 connect(chatTab->chatSearchBar(), SIGNAL(enableSearchButtonsSignal(bool)), this, SLOT(onEnableSearchActions(bool)));
828 connect(chatTab, SIGNAL(contactBlockStatusChanged(bool)), this, SLOT(toggleBlockButton(bool)));
829 connect(chatTab, SIGNAL(zoomFactorChanged(qreal)), this, SLOT(onZoomFactorChanged(qreal)));
830 if(chatTab->otrStatus())
831 connect(chatTab, SIGNAL(otrStatusChanged(OtrStatus)), this, SLOT(onOtrStatusChanged(OtrStatus)));
832 }
833
setupCustomActions()834 void ChatWindow::setupCustomActions()
835 {
836 KStandardAction::close(this, SLOT(closeCurrentTab()), this);
837 KStandardAction::quit(this, SLOT(close()), this);
838
839 QAction *nextTabAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next-view")), i18n("&Next Tab"), this);
840 nextTabAction->setShortcuts(KStandardShortcut::tabNext());
841 connect(nextTabAction, SIGNAL(triggered()), this, SLOT(onNextTabActionTriggered()));
842
843 QAction *previousTabAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous-view")), i18n("&Previous Tab"), this);
844 previousTabAction->setShortcuts(KStandardShortcut::tabPrev());
845 connect(previousTabAction, SIGNAL(triggered()), this, SLOT(onPreviousTabActionTriggered()));
846
847 QAction *audioCallAction = new QAction(QIcon::fromTheme(QStringLiteral("audio-headset")), i18n("&Audio Call"), this);
848 audioCallAction->setToolTip(i18nc("Toolbar icon tooltip", "Start an audio call with this contact"));
849 connect(audioCallAction, SIGNAL(triggered()), this, SLOT(onAudioCallTriggered()));
850
851 QAction *blockContactAction = new QAction(QIcon::fromTheme(QStringLiteral("im-ban-kick-user")), i18n("&Block Contact"), this);
852 blockContactAction->setToolTip(i18nc("Toolbar icon tooltip",
853 "Blocking means that this contact will not see you online and you will not receive any messages from this contact"));
854 connect(blockContactAction, SIGNAL(triggered()), this, SLOT(onBlockContactTriggered()));
855
856 QAction *fileTransferAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Send File"), this);
857 fileTransferAction->setToolTip(i18nc("Toolbar icon tooltip", "Send a file to this contact"));
858 connect(fileTransferAction, SIGNAL(triggered()), this, SLOT(onFileTransferTriggered()));
859
860 QAction *inviteToChat = new QAction(QIcon::fromTheme(QStringLiteral("user-group-new")), i18n("&Invite to Chat"), this);
861 inviteToChat->setToolTip(i18nc("Toolbar icon tooltip", "Invite another contact to join this chat"));
862 connect(inviteToChat, SIGNAL(triggered()), this, SLOT(onInviteToChatTriggered()));
863
864 QAction *videoCallAction = new QAction(QIcon::fromTheme(QStringLiteral("camera-web")), i18n("&Video Call"), this);
865 videoCallAction->setToolTip(i18nc("Toolbar icon tooltip", "Start a video call with this contact"));
866 connect(videoCallAction, SIGNAL(triggered()), this, SLOT(onVideoCallTriggered()));
867
868 QAction *shareDesktopAction = new QAction(QIcon::fromTheme(QStringLiteral("krfb")), i18n("Share My &Desktop"), this);
869 shareDesktopAction->setToolTip(i18nc("Toolbar icon tooltip", "Start an application that allows this contact to see your desktop"));
870 connect(shareDesktopAction, SIGNAL(triggered()), this, SLOT(onShareDesktopTriggered()));
871
872 QAction* collaborateDocumentAction = new QAction(QIcon::fromTheme(QStringLiteral("document-share")), i18n("&Collaboratively edit a document"), this);
873 connect(collaborateDocumentAction, SIGNAL(triggered()), this, SLOT(onCollaborateDocumentTriggered()));
874
875 QAction* showInfoAction = new QAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Contact info"), this);
876 connect(showInfoAction, SIGNAL(triggered()), this, SLOT(onShowInfoTriggered()));
877
878 QAction* leaveAction = new QAction(QIcon::fromTheme(QStringLiteral("irc-close-channel")), i18n("&Leave room"), this);
879 connect(leaveAction, SIGNAL(triggered()), this, SLOT(onLeaveChannelTriggered()));
880
881 m_spellDictCombo = new Sonnet::DictionaryComboBox();
882 connect(m_spellDictCombo, SIGNAL(dictionaryChanged(QString)),
883 this, SLOT(setTabSpellDictionary(QString)));
884
885 QWidgetAction *spellDictComboAction = new QWidgetAction(this);
886 spellDictComboAction->setDefaultWidget(m_spellDictCombo);
887 spellDictComboAction->defaultWidget()->setFocusPolicy(Qt::ClickFocus);
888 spellDictComboAction->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling")));
889 spellDictComboAction->setIconText(i18n("Choose Spelling Language"));
890
891 QAction *openLogAction = new QAction(QIcon::fromTheme(QStringLiteral("view-pim-journal")), i18nc("Action to open the log viewer with a specified contact","&Previous Conversations"), this);
892 connect(openLogAction, SIGNAL(triggered()), SLOT(onOpenLogTriggered()));
893
894 QAction *openContactListAction = new QAction(QIcon::fromTheme(QStringLiteral("telepathy-kde")), i18nc("Action to open the contact list","Contact &List"), this);
895 connect(openContactListAction, SIGNAL(triggered()), SLOT(onOpenContactListTriggered()));
896
897 QWidgetAction *accountIconAction = new QWidgetAction(this);
898 accountIconAction->setIcon(QIcon::fromTheme(QStringLiteral("telepathy-kde")));
899 accountIconAction->setText(i18n("Account Icon"));
900 m_accountIconLabel = new QLabel(this);
901 accountIconAction->setDefaultWidget(m_accountIconLabel);
902
903 QAction *clearViewAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("&Clear View"), this);
904 clearViewAction->setToolTip(i18nc("Toolbar icon tooltip", "Clear all messages from current chat tab"));
905 connect(clearViewAction, SIGNAL(triggered()), this, SLOT(onClearViewTriggered()));
906
907 EmoticonTextEditAction *addEmoticonAction = new EmoticonTextEditAction(this);
908 connect(addEmoticonAction, SIGNAL(emoticonActivated(QString)), this, SLOT(onAddEmoticon(QString)) );
909
910 m_sendMessage = new QAction(i18n("Send message"), this);
911
912 actionCollection()->setDefaultShortcuts(m_sendMessage,
913 // Setting default shortcuts. Return will be a primary one, and Enter (on keypad) - alternate.
914 QList<QKeySequence>() << QKeySequence(Qt::Key_Return) << QKeySequence(Qt::Key_Enter));
915 connect(m_sendMessage, SIGNAL(changed()), SLOT(updateSendMessageShortcuts()));
916
917 // add custom actions to the collection
918 actionCollection()->addAction(QLatin1String("next-tab"), nextTabAction);
919 actionCollection()->addAction(QLatin1String("previous-tab"), previousTabAction);
920 actionCollection()->addAction(QLatin1String("audio-call"), audioCallAction);
921 actionCollection()->addAction(QLatin1String("send-file"), fileTransferAction);
922 actionCollection()->addAction(QLatin1String("video-call"), videoCallAction);
923 actionCollection()->addAction(QLatin1String("invite-to-chat"), inviteToChat);
924 actionCollection()->addAction(QLatin1String("share-desktop"), shareDesktopAction);
925 actionCollection()->addAction(QLatin1String("language"), spellDictComboAction);
926 actionCollection()->addAction(QLatin1String("account-icon"), accountIconAction);
927 actionCollection()->addAction(QLatin1String("block-contact"), blockContactAction);
928 actionCollection()->addAction(QLatin1String("open-log"), openLogAction);
929 actionCollection()->addAction(QLatin1String("open-contact-list"), openContactListAction);
930 actionCollection()->addAction(QLatin1String("clear-chat-view"), clearViewAction);
931 actionCollection()->addAction(QLatin1String("emoticons"), addEmoticonAction);
932 actionCollection()->addAction(QLatin1String("send-message"), m_sendMessage);
933 actionCollection()->addAction(QLatin1String("collaborate-document"), collaborateDocumentAction);
934 actionCollection()->addAction(QLatin1String("contact-info"), showInfoAction);
935 actionCollection()->addAction(QLatin1String("leave-chat"), leaveAction);
936 }
937
938
setupOTR()939 void ChatWindow::setupOTR()
940 {
941 m_otrActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("&OTR"), this);
942 m_otrActionMenu->setDelayed(false);
943
944 QAction *startRestartOtrAction = new QAction(QIcon::fromTheme(QStringLiteral("object-locked")), i18n("&Start session"), this);
945 startRestartOtrAction->setEnabled(false);
946 connect(startRestartOtrAction, SIGNAL(triggered()), this, SLOT(onStartRestartOtrTriggered()));
947
948 QAction *stopOtrAction = new QAction(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("&Stop session"), this);
949 stopOtrAction->setEnabled(false);
950 connect(stopOtrAction, SIGNAL(triggered()), this, SLOT(onStopOtrTriggered()));
951
952 QAction *authenticateBuddyAction = new QAction(QIcon::fromTheme(QStringLiteral("application-pgp-signature")), i18n("&Authenticate contact"), this);
953 authenticateBuddyAction->setEnabled(false);
954 connect(authenticateBuddyAction, SIGNAL(triggered()), this, SLOT(onAuthenticateBuddyTriggered()));
955
956 m_otrActionMenu->addAction(startRestartOtrAction);
957 m_otrActionMenu->addAction(stopOtrAction);
958 m_otrActionMenu->addAction(authenticateBuddyAction);
959 m_otrActionMenu->setEnabled(false);
960
961 actionCollection()->addAction(QLatin1String("start-restart-otr"), startRestartOtrAction);
962 actionCollection()->addAction(QLatin1String("stop-otr"), stopOtrAction);
963 actionCollection()->addAction(QLatin1String("authenticate-otr"), authenticateBuddyAction);
964 actionCollection()->addAction(QLatin1String("otr-actions"), m_otrActionMenu);
965
966 // private key generation
967 connect(m_proxyService.data(), SIGNAL(keyGenerationStarted(Tp::AccountPtr)),
968 SLOT(onKeyGenerationStarted(Tp::AccountPtr)));
969 connect(m_proxyService.data(), SIGNAL(keyGenerationFinished(Tp::AccountPtr, bool)),
970 SLOT(onKeyGenerationFinished(Tp::AccountPtr, bool)));
971 }
972
onOtrStatusChanged(OtrStatus status)973 void ChatWindow::onOtrStatusChanged(OtrStatus status)
974 {
975
976 ChatWidget *chatTab = dynamic_cast<ChatWidget*>(QObject::sender());
977 // in case if this slot is called directly, not by the signal
978 if(!chatTab) {
979 chatTab = getCurrentTab();
980 }
981
982 if(chatTab != getCurrentTab()) {
983 return;
984 }
985
986 // OTR is disabled for this channel
987 if(!status) {
988 m_otrActionMenu->setEnabled(false);
989 m_otrActionMenu->menu()->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
990 return;
991 }
992
993 QAction* srAction = actionCollection()->action(QLatin1String("start-restart-otr"));
994 QAction* stopAction = actionCollection()->action(QLatin1String("stop-otr"));
995 QAction* authenticateBuddyAction = actionCollection()->action(QLatin1String("authenticate-otr"));
996
997 m_otrActionMenu->setEnabled(true);
998
999 switch(status.otrTrustLevel()) {
1000
1001 case KTp::OTRTrustLevelNotPrivate:
1002 m_otrActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
1003 m_otrActionMenu->setToolTip(i18n("Not private"));
1004 srAction->setEnabled(true);
1005 srAction->setText(i18n("&Start session"));
1006 stopAction->setEnabled(false);
1007 authenticateBuddyAction->setEnabled(false);
1008 return;
1009
1010 case KTp::OTRTrustLevelUnverified:
1011 m_otrActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-locked-unverified")));
1012 m_otrActionMenu->setToolTip(i18n("Unverified"));
1013 srAction->setEnabled(true);
1014 srAction->setText(i18n("&Restart session"));
1015 stopAction->setEnabled(true);
1016 authenticateBuddyAction->setEnabled(true);
1017 return;
1018
1019 case KTp::OTRTrustLevelPrivate:
1020 m_otrActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-locked-verified")));
1021 m_otrActionMenu->setToolTip(i18n("Private"));
1022 srAction->setEnabled(true);
1023 srAction->setText(i18n("&Restart session"));
1024 stopAction->setEnabled(true);
1025 authenticateBuddyAction->setEnabled(true);
1026 return;
1027
1028 case KTp::OTRTrustLevelFinished:
1029 m_otrActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-locked-finished")));
1030 m_otrActionMenu->setToolTip(i18n("Finished"));
1031 srAction->setEnabled(true);
1032 srAction->setText(i18n("&Restart session"));
1033 stopAction->setEnabled(true);
1034 authenticateBuddyAction->setEnabled(false);
1035 return;
1036
1037 default: return;
1038 }
1039 }
1040
onStartRestartOtrTriggered()1041 void ChatWindow::onStartRestartOtrTriggered()
1042 {
1043
1044 ChatTab* chat = getCurrentTab();
1045 chat->startOtrSession();
1046 }
1047
onStopOtrTriggered()1048 void ChatWindow::onStopOtrTriggered() {
1049
1050 ChatTab* chat = getCurrentTab();
1051 chat->stopOtrSession();
1052 }
1053
onAuthenticateBuddyTriggered()1054 void ChatWindow::onAuthenticateBuddyTriggered()
1055 {
1056
1057 ChatTab* chat = getCurrentTab();
1058 chat->authenticateBuddy();
1059 }
1060
onKeyGenerationStarted(Tp::AccountPtr account)1061 void ChatWindow::onKeyGenerationStarted(Tp::AccountPtr account)
1062 {
1063 // block user text input for these tabs
1064 QList<ChatTab*> allTabs = tabs();
1065 Q_FOREACH(ChatTab *ct, allTabs) {
1066 if(ct->account()->objectPath() == account->objectPath()) {
1067 ct->blockTextInput(true);
1068 }
1069 }
1070 }
1071
onKeyGenerationFinished(Tp::AccountPtr account,bool error)1072 void ChatWindow::onKeyGenerationFinished(Tp::AccountPtr account, bool error)
1073 {
1074 Q_UNUSED(error);
1075 // unblock user text input for these tabs
1076 QList<ChatTab*> allTabs = tabs();
1077 Q_FOREACH(ChatTab *ct, allTabs) {
1078 if(ct->account()->objectPath() == account->objectPath()) {
1079 ct->blockTextInput(false);
1080 }
1081 }
1082 }
1083
setCollaborateDocumentEnabled(bool enable)1084 void ChatWindow::setCollaborateDocumentEnabled(bool enable)
1085 {
1086 QAction* action = actionCollection()->action(QLatin1String("collaborate-document"));
1087
1088 if (action) {
1089 action->setEnabled(enable);
1090 if ( enable ) {
1091 action->setToolTip(i18nc("Toolbar icon tooltip", "Edit a plain-text document with this contact in real-time"));
1092 }
1093 else {
1094 action->setToolTip(i18nc("Toolbar icon tooltip for a disabled action", "<p>Both you and the target contact "
1095 "need to have the <i>kte-collaborative</i> package installed to share documents</p>"));
1096 }
1097 }
1098 }
1099
setAudioCallEnabled(bool enable)1100 void ChatWindow::setAudioCallEnabled(bool enable)
1101 {
1102 QAction *action = actionCollection()->action(QLatin1String("audio-call"));
1103
1104 // don't want to segfault. Should never happen
1105 if (action) {
1106 action->setEnabled(enable);
1107 }
1108 }
1109
setBlockEnabled(bool enable)1110 void ChatWindow::setBlockEnabled(bool enable)
1111 {
1112 QAction *action = actionCollection()->action(QLatin1String("block-contact"));
1113
1114 if (action) {
1115 action->setEnabled(enable);
1116 }
1117 }
1118
setFileTransferEnabled(bool enable)1119 void ChatWindow::setFileTransferEnabled(bool enable)
1120 {
1121 QAction *action = actionCollection()->action(QLatin1String("send-file"));
1122
1123 if (action) {
1124 action->setEnabled(enable);
1125 }
1126 }
1127
setInviteToChatEnabled(bool enable)1128 void ChatWindow::setInviteToChatEnabled(bool enable)
1129 {
1130 QAction *action = actionCollection()->action(QLatin1String("invite-to-chat"));
1131
1132 if (action) {
1133 action->setEnabled(enable);
1134 }
1135 }
1136
setVideoCallEnabled(bool enable)1137 void ChatWindow::setVideoCallEnabled(bool enable)
1138 {
1139 QAction *action = actionCollection()->action(QLatin1String("video-call"));
1140
1141 if (action) {
1142 action->setEnabled(enable);
1143 }
1144 }
1145
setShareDesktopEnabled(bool enable)1146 void ChatWindow::setShareDesktopEnabled(bool enable)
1147 {
1148 QAction *action = actionCollection()->action(QLatin1String("share-desktop"));
1149
1150 if (action) {
1151 action->setEnabled(enable);
1152 }
1153 }
1154
setPreviousConversationsEnabled(bool enable)1155 void ChatWindow::setPreviousConversationsEnabled ( bool enable )
1156 {
1157 QAction *action = actionCollection()->action(QLatin1String("open-log"));
1158
1159 if (action) {
1160 action->setEnabled(enable);
1161 }
1162 }
1163
setShowInfoEnabled(bool enable)1164 void ChatWindow::setShowInfoEnabled ( bool enable )
1165 {
1166 QAction *action = actionCollection()->action(QLatin1String("contact-info"));
1167
1168 if (action) {
1169 action->setEnabled(enable);
1170 }
1171 }
1172
updateAccountIcon()1173 void ChatWindow::updateAccountIcon()
1174 {
1175 int index = m_tabWidget->currentIndex();
1176 ChatTab *currentChatTab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
1177 m_accountIconLabel->setPixmap(currentChatTab->accountIcon().pixmap(toolBar()->iconSize()));
1178 }
1179
startAudioCall(const Tp::AccountPtr & account,const Tp::ContactPtr & contact)1180 void ChatWindow::startAudioCall(const Tp::AccountPtr& account, const Tp::ContactPtr& contact)
1181 {
1182 Tp::PendingChannelRequest *channelRequest = KTp::Actions::startAudioCall(account, contact);
1183 connect(channelRequest, SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onGenericOperationFinished(Tp::PendingOperation*)));
1184 }
1185
startFileTransfer(const Tp::AccountPtr & account,const Tp::ContactPtr & contact)1186 void ChatWindow::startFileTransfer(const Tp::AccountPtr& account, const Tp::ContactPtr& contact)
1187 {
1188 // check for existence of ContactPtr
1189 Q_ASSERT(contact);
1190
1191 // use the keyword "FileTransferLastDirectory" for setting last used dir for file transfer
1192 QStringList fileNames = QFileDialog::getOpenFileNames(this,
1193 i18n("Choose files to send to %1", contact->alias()),
1194 QStringLiteral("kfiledialog:///FileTransferLastDirectory"),
1195 QString()
1196 );
1197
1198 // User hit cancel button
1199 if (fileNames.isEmpty()) {
1200 return;
1201 }
1202
1203 QDateTime now = QDateTime::currentDateTime();
1204 Q_FOREACH(const QString& fileName, fileNames) {
1205 Tp::PendingChannelRequest* channelRequest = KTp::Actions::startFileTransfer(account, contact, fileName);
1206 connect(channelRequest, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onGenericOperationFinished(Tp::PendingOperation*)));
1207 }
1208 }
1209
offerDocumentToContact(const Tp::AccountPtr & account,const Tp::ContactPtr & targetContact)1210 void ChatWindow::offerDocumentToContact(const Tp::AccountPtr& account, const Tp::ContactPtr& targetContact)
1211 {
1212 const QUrl url = QUrl::fromLocalFile(QFileDialog::getOpenFileName());
1213 if ( ! url.isEmpty() ) {
1214 KTp::Actions::startCollaborativeEditing(account, targetContact, QList<QUrl>() << url, true);
1215 }
1216 }
1217
offerDocumentToChatroom(const Tp::AccountPtr & account,const QString & roomName)1218 void ChatWindow::offerDocumentToChatroom(const Tp::AccountPtr& account, const QString& roomName)
1219 {
1220 const QUrl url = QUrl::fromLocalFile(QFileDialog::getOpenFileName());
1221 if ( ! url.isEmpty() ) {
1222 KTp::Actions::startCollaborativeEditing(account, roomName, QList<QUrl>() << url, true);
1223 }
1224 }
1225
restoreKeyboardLayout(ChatTab * chatTab)1226 void ChatWindow::restoreKeyboardLayout(ChatTab *chatTab)
1227 {
1228 if (!chatTab || !TextChatConfig::instance()->rememberTabKeyboardLayout()) {
1229 return;
1230 }
1231
1232 QString currentKeyboardLayout = chatTab->currentKeyboardLayoutLanguage();
1233 if (!currentKeyboardLayout.isEmpty()) {
1234 m_keyboardLayoutInterface->asyncCall(QLatin1String("setLayout"), currentKeyboardLayout);
1235 }
1236 }
1237
startVideoCall(const Tp::AccountPtr & account,const Tp::ContactPtr & contact)1238 void ChatWindow::startVideoCall(const Tp::AccountPtr& account, const Tp::ContactPtr& contact)
1239 {
1240 Tp::PendingChannelRequest* channelRequest = KTp::Actions::startAudioVideoCall(account, contact);
1241 connect(channelRequest, SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onGenericOperationFinished(Tp::PendingOperation*)));
1242 }
1243
startShareDesktop(const Tp::AccountPtr & account,const Tp::ContactPtr & contact)1244 void ChatWindow::startShareDesktop(const Tp::AccountPtr& account, const Tp::ContactPtr& contact)
1245 {
1246 Tp::PendingChannelRequest* channelRequest = KTp::Actions::startDesktopSharing(account, contact);
1247 connect(channelRequest, SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onGenericOperationFinished(Tp::PendingOperation*)));
1248 }
1249
event(QEvent * e)1250 bool ChatWindow::event(QEvent *e)
1251 {
1252 if (e->type() == QEvent::WindowActivate) {
1253 //when the window is activated reset the message count on the active tab.
1254 ChatTab *currChat = qobject_cast<ChatTab*>(m_tabWidget->currentWidget());
1255 //it is (apparently) possible to get a window activation event whilst we're closing down and have no tabs
1256 //see https://bugs.kde.org/show_bug.cgi?id=322135
1257 if (currChat) {
1258 currChat->acknowledgeMessages();
1259 restoreKeyboardLayout(currChat);
1260 }
1261 }
1262
1263 return KXmlGuiWindow::event(e);
1264 }
1265
setTabSpellDictionary(const QString & dict)1266 void ChatWindow::setTabSpellDictionary(const QString &dict)
1267 {
1268 int index = m_tabWidget->currentIndex();
1269 ChatTab *currentChatTab=qobject_cast<ChatTab*>(m_tabWidget->widget(index));
1270 currentChatTab->setSpellDictionary(dict);
1271 }
1272
toggleBlockButton(bool contactIsBlocked)1273 void ChatWindow::toggleBlockButton(bool contactIsBlocked)
1274 {
1275 QAction *action = actionCollection()->action(QLatin1String("block-contact"));
1276 if(contactIsBlocked) {
1277 //Change the name of the action to "Unblock Contact"
1278 //and disconnect it with the block slot and reconnect it with unblock slot
1279 disconnect(action, SIGNAL(triggered()), this, SLOT(onBlockContactTriggered()));
1280 action->setText(i18n("&Unblock Contact"));
1281
1282 connect(action, SIGNAL(triggered()), this, SLOT(onUnblockContactTriggered()));
1283 } else {
1284 //Change the name of the action to "Block Contact"
1285 //and disconnect it with the unblock slot and reconnect it with block slot
1286 disconnect(action, SIGNAL(triggered()), this, SLOT(onUnblockContactTriggered()));
1287 action->setText(i18n("&Block Contact"));
1288
1289 connect(action, SIGNAL(triggered()), this, SLOT(onBlockContactTriggered()));
1290 }
1291 //Reset the WindowTitle
1292 ChatWidget *currChat = qobject_cast<ChatWidget*>(m_tabWidget->currentWidget());
1293 Q_ASSERT(currChat);
1294 setWindowTitle(currChat->title());
1295
1296 setBlockEnabled(true);
1297 }
1298
onAddEmoticon(const QString & emoticon)1299 void ChatWindow::onAddEmoticon(const QString& emoticon)
1300 {
1301 int index = m_tabWidget->currentIndex();
1302 ChatTab *currentChatTab = qobject_cast<ChatTab*>(m_tabWidget->widget(index));
1303 currentChatTab->addEmoticonToChat(emoticon);
1304 }
1305
hasUnreadMessages() const1306 bool ChatWindow::hasUnreadMessages() const
1307 {
1308 for (int i = 0; i < m_tabWidget->count(); i++) {
1309 ChatTab *tab = qobject_cast<ChatTab*>(m_tabWidget->widget(i));
1310 if (tab && tab->unreadMessageCount() > 0) {
1311 return true;
1312 }
1313 }
1314
1315 return false;
1316 }
1317
onZoomIn()1318 void ChatWindow::onZoomIn()
1319 {
1320 onZoomFactorChanged(m_zoomFactor + 0.1);
1321 }
1322
onZoomOut()1323 void ChatWindow::onZoomOut()
1324 {
1325 onZoomFactorChanged(m_zoomFactor - 0.1);
1326 }
1327
onZoomFactorChanged(qreal zoom)1328 void ChatWindow::onZoomFactorChanged(qreal zoom)
1329 {
1330 m_zoomFactor = zoom;
1331
1332 for (int i = 0; i < m_tabWidget->count(); i++) {
1333 ChatWidget *widget = qobject_cast<ChatWidget*>(m_tabWidget->widget(i));
1334 if (!widget) {
1335 continue;
1336 }
1337
1338 widget->setZoomFactor(zoom);
1339 }
1340
1341 KConfig config(QLatin1String("ktelepathyrc"));
1342 KConfigGroup group = config.group("Appearance");
1343 group.writeEntry("zoomFactor", m_zoomFactor);
1344 }
1345
updateSendMessageShortcuts()1346 void ChatWindow::updateSendMessageShortcuts()
1347 {
1348 QList<QKeySequence> newSendMessageShortcuts = m_sendMessage->shortcuts();
1349 for (int i = 0; i < m_tabWidget->count(); i++) {
1350 ChatTab* tab = qobject_cast<ChatTab*>(m_tabWidget->widget(i));
1351 tab->updateSendMessageShortcuts(newSendMessageShortcuts);
1352 }
1353 }
1354
onReloadTheme()1355 void ChatWindow::onReloadTheme()
1356 {
1357 for (int i = 0; i < m_tabWidget->count(); i++) {
1358 ChatTab *tab = qobject_cast<ChatTab*>(m_tabWidget->widget(i));
1359 tab->reloadTheme();
1360 }
1361 }
1362
onLeaveChannelTriggered()1363 void ChatWindow::onLeaveChannelTriggered()
1364 {
1365 ChatTab *tab = getCurrentTab();
1366 tab->stopOtrSession();
1367 tab->textChannel()->requestLeave();
1368 closeCurrentTab();
1369 }
1370
1371 #include "chat-window.moc"
1372