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