1 /*
2 * %kadu copyright begin%
3 * Copyright 2014 Bartosz Brachaczek (b.brachaczek@gmail.com)
4 * Copyright 2013, 2014, 2015 Rafał Przemysław Malinowski (rafal.przemyslaw.malinowski@gmail.com)
5 * %kadu copyright end%
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (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 #include "chat-widget-impl.h"
22
23 #include "chat/type/chat-type-manager.h"
24 #include "configuration/deprecated-configuration-api.h"
25 #include "contacts/contact-set.h"
26 #include "contacts/model/chat-adapter.h"
27 #include "contacts/model/contact-list-model.h"
28 #include "core/injected-factory.h"
29 #include "gui/actions/actions.h"
30 #include "gui/actions/action.h"
31 #include "gui/configuration/chat-configuration-holder.h"
32 #include "gui/hot-key.h"
33 #include "gui/web-view-highlighter.h"
34 #include "gui/widgets/chat-edit-box-size-manager.h"
35 #include "gui/widgets/chat-edit-box.h"
36 #include "gui/widgets/chat-top-bar-container-widget.h"
37 #include "gui/widgets/chat-widget/chat-widget-actions.h"
38 #include "gui/widgets/chat-widget/chat-widget-title.h"
39 #include "gui/widgets/custom-input.h"
40 #include "gui/widgets/filtered-tree-view.h"
41 #include "gui/widgets/talkable-tree-view.h"
42 #include "gui/widgets/webkit-messages-view/webkit-messages-view-factory.h"
43 #include "gui/widgets/webkit-messages-view/webkit-messages-view.h"
44 #include "gui/widgets/search-bar.h"
45 #include "gui/windows/message-dialog.h"
46 #include "gui/windows/kadu-window-service.h"
47 #include "gui/windows/kadu-window.h"
48 #include "html/html-conversion.h"
49 #include "html/html-string.h"
50 #include "icons/icons-manager.h"
51 #include "message/message-manager.h"
52 #include "message/message-storage.h"
53 #include "message/sorted-messages.h"
54 #include "misc/misc.h"
55 #include "model/model-chain.h"
56 #include "protocols/protocol.h"
57 #include "protocols/services/chat-state.h"
58 #include "talkable/filter/name-talkable-filter.h"
59 #include "talkable/model/talkable-proxy-model.h"
60
61 #include <QtCore/QFileInfo>
62 #include <QtCore/QMimeData>
63 #include <QtGui/QKeyEvent>
64 #include <QtWidgets/QApplication>
65 #include <QtWidgets/QMessageBox>
66 #include <QtWidgets/QShortcut>
67 #include <QtWidgets/QSplitter>
68 #include <QtWidgets/QVBoxLayout>
69
ChatWidgetImpl(Chat chat,QWidget * parent)70 ChatWidgetImpl::ChatWidgetImpl(Chat chat, QWidget *parent) :
71 ChatWidget{parent},
72 CurrentChat{chat},
73 BuddiesWidget{0},
74 ProxyModel{0},
75 InputBox{0},
76 HorizontalSplitter{0},
77 IsComposing{false},
78 CurrentContactActivity{ChatState::None},
79 SplittersInitialized{false}
80 {
81 }
82
~ChatWidgetImpl()83 ChatWidgetImpl::~ChatWidgetImpl()
84 {
85 ComposingTimer.stop();
86
87 kaduStoreGeometry();
88
89 emit widgetDestroyed(CurrentChat);
90 emit widgetDestroyed(this);
91
92 if (currentProtocol() && currentProtocol()->chatStateService() && chat().contacts().toContact())
93 currentProtocol()->chatStateService()->sendState(chat().contacts().toContact(), ChatState::Gone);
94
95 CurrentChat.setOpen(false);
96 }
97
setActions(Actions * actions)98 void ChatWidgetImpl::setActions(Actions *actions)
99 {
100 m_actions = actions;
101 }
102
setChatConfigurationHolder(ChatConfigurationHolder * chatConfigurationHolder)103 void ChatWidgetImpl::setChatConfigurationHolder(ChatConfigurationHolder *chatConfigurationHolder)
104 {
105 m_chatConfigurationHolder = chatConfigurationHolder;
106 }
107
setChatEditBoxSizeManager(ChatEditBoxSizeManager * chatEditBoxSizeManager)108 void ChatWidgetImpl::setChatEditBoxSizeManager(ChatEditBoxSizeManager *chatEditBoxSizeManager)
109 {
110 m_chatEditBoxSizeManager = chatEditBoxSizeManager;
111 }
112
setChatTypeManager(ChatTypeManager * chatTypeManager)113 void ChatWidgetImpl::setChatTypeManager(ChatTypeManager *chatTypeManager)
114 {
115 m_chatTypeManager = chatTypeManager;
116 }
117
setChatWidgetActions(ChatWidgetActions * chatWidgetActions)118 void ChatWidgetImpl::setChatWidgetActions(ChatWidgetActions *chatWidgetActions)
119 {
120 m_chatWidgetActions = chatWidgetActions;
121 }
122
setConfiguration(Configuration * configuration)123 void ChatWidgetImpl::setConfiguration(Configuration *configuration)
124 {
125 m_configuration = configuration;
126 }
127
setIconsManager(IconsManager * iconsManager)128 void ChatWidgetImpl::setIconsManager(IconsManager *iconsManager)
129 {
130 m_iconsManager = iconsManager;
131 }
132
setInjectedFactory(InjectedFactory * injectedFactory)133 void ChatWidgetImpl::setInjectedFactory(InjectedFactory *injectedFactory)
134 {
135 m_injectedFactory = injectedFactory;
136 }
137
setKaduWindowService(KaduWindowService * kaduWindowService)138 void ChatWidgetImpl::setKaduWindowService(KaduWindowService *kaduWindowService)
139 {
140 m_kaduWindowService = kaduWindowService;
141 }
142
setMessageManager(MessageManager * messageManager)143 void ChatWidgetImpl::setMessageManager(MessageManager *messageManager)
144 {
145 m_messageManager = messageManager;
146 }
147
setMessageStorage(MessageStorage * messageStorage)148 void ChatWidgetImpl::setMessageStorage(MessageStorage *messageStorage)
149 {
150 m_messageStorage = messageStorage;
151 }
152
setWebkitMessagesViewFactory(WebkitMessagesViewFactory * webkitMessagesViewFactory)153 void ChatWidgetImpl::setWebkitMessagesViewFactory(WebkitMessagesViewFactory *webkitMessagesViewFactory)
154 {
155 m_webkitMessagesViewFactory = webkitMessagesViewFactory;
156 }
157
init()158 void ChatWidgetImpl::init()
159 {
160 Title = m_injectedFactory->makeInjected<ChatWidgetTitle>(this);
161
162 setAcceptDrops(true);
163
164 createGui();
165 configurationUpdated();
166
167 ComposingTimer.setInterval(2 * 1000);
168 connect(&ComposingTimer, SIGNAL(timeout()), this, SLOT(checkComposing()));
169
170 connect(edit(), SIGNAL(textChanged()), this, SLOT(updateComposing()));
171
172 // icon for conference never changes
173 if (CurrentChat.contacts().count() == 1)
174 {
175 if (currentProtocol() && currentProtocol()->chatStateService())
176 connect(currentProtocol()->chatStateService(), SIGNAL(peerStateChanged(const Contact &, ChatState)),
177 this, SLOT(contactActivityChanged(const Contact &, ChatState)));
178 }
179
180 connect(CurrentChat, SIGNAL(updated()), this, SLOT(chatUpdated()));
181
182 CurrentChat.setOpen(true);
183 }
184
createGui()185 void ChatWidgetImpl::createGui()
186 {
187 QVBoxLayout *mainLayout = new QVBoxLayout(this);
188 mainLayout->setMargin(0);
189 mainLayout->setSpacing(0);
190
191 TopBarContainer = m_injectedFactory->makeInjected<ChatTopBarContainerWidget>(CurrentChat);
192 mainLayout->addWidget(TopBarContainer);
193
194 VerticalSplitter = new QSplitter(Qt::Vertical, this);
195
196 mainLayout->addWidget(VerticalSplitter);
197
198 HorizontalSplitter = new QSplitter(Qt::Horizontal, this);
199 HorizontalSplitter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
200 HorizontalSplitter->setMinimumHeight(10);
201
202 QFrame *frame = new QFrame(HorizontalSplitter);
203 frame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
204
205 QVBoxLayout *frameLayout = new QVBoxLayout(frame);
206 frameLayout->setMargin(0);
207 frameLayout->setSpacing(0);
208
209 MessagesView = m_webkitMessagesViewFactory->createWebkitMessagesView(CurrentChat, true, frame);
210
211 frameLayout->addWidget(MessagesView.get());
212
213 WebViewHighlighter *highligher = new WebViewHighlighter(MessagesView.get());
214
215 SearchBar *messagesSearchBar = new SearchBar(frame);
216 frameLayout->addWidget(messagesSearchBar);
217
218 connect(messagesSearchBar, SIGNAL(searchPrevious(QString)), highligher, SLOT(selectPrevious(QString)));
219 connect(messagesSearchBar, SIGNAL(searchNext(QString)), highligher, SLOT(selectNext(QString)));
220 connect(messagesSearchBar, SIGNAL(clearSearch()), highligher, SLOT(clearSelect()));
221 connect(highligher, SIGNAL(somethingFound(bool)), messagesSearchBar, SLOT(somethingFound(bool)));
222
223 QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_PageUp + Qt::SHIFT), this);
224 connect(shortcut, SIGNAL(activated()), MessagesView.get(), SLOT(pageUp()));
225
226 shortcut = new QShortcut(QKeySequence(Qt::Key_PageDown + Qt::SHIFT), this);
227 connect(shortcut, SIGNAL(activated()), MessagesView.get(), SLOT(pageDown()));
228
229 shortcut = new QShortcut(QKeySequence(Qt::Key_PageUp + Qt::ControlModifier), this);
230 connect(shortcut, SIGNAL(activated()), MessagesView.get(), SLOT(pageUp()));
231
232 shortcut = new QShortcut(QKeySequence(Qt::Key_PageDown + Qt::ControlModifier), this);
233 connect(shortcut, SIGNAL(activated()), MessagesView.get(), SLOT(pageDown()));
234 HorizontalSplitter->addWidget(frame);
235
236 InputBox = m_injectedFactory->makeInjected<ChatEditBox>(CurrentChat, this);
237 InputBox->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
238 InputBox->setMinimumHeight(10);
239
240 messagesSearchBar->setSearchWidget(InputBox->inputBox());
241
242 auto *chatType = m_chatTypeManager->chatType(CurrentChat.type());
243 if (chatType && chatType->name() != "Contact")
244 createContactsList();
245
246 VerticalSplitter->addWidget(HorizontalSplitter);
247 VerticalSplitter->setStretchFactor(0, 1);
248 VerticalSplitter->addWidget(InputBox);
249 VerticalSplitter->setStretchFactor(1, 0);
250
251 connect(VerticalSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(verticalSplitterMoved(int, int)));
252 connect(InputBox->inputBox(), SIGNAL(sendMessage()), this, SLOT(sendMessage()));
253 connect(InputBox->inputBox(), SIGNAL(keyPressed(QKeyEvent *, CustomInput *, bool &)),
254 this, SLOT(keyPressedSlot(QKeyEvent *, CustomInput *, bool &)));
255 }
256
createContactsList()257 void ChatWidgetImpl::createContactsList()
258 {
259 QWidget *contactsListContainer = new QWidget(HorizontalSplitter);
260
261 QVBoxLayout *layout = new QVBoxLayout(contactsListContainer);
262 layout->setContentsMargins(0, 0, 0, 0);
263 layout->setSpacing(0);
264
265 BuddiesWidget = m_injectedFactory->makeInjected<FilteredTreeView>(FilteredTreeView::FilterAtTop, this);
266 BuddiesWidget->setMinimumSize(QSize(30, 30));
267
268 TalkableTreeView *view = m_injectedFactory->makeInjected<TalkableTreeView>(BuddiesWidget);
269 view->setItemsExpandable(false);
270
271 auto chain = new ModelChain(this);
272 auto contactListModel = m_injectedFactory->makeInjected<ContactListModel>(chain);
273 new ChatAdapter(contactListModel, CurrentChat);
274 chain->setBaseModel(contactListModel);
275 ProxyModel = m_injectedFactory->makeInjected<TalkableProxyModel>(chain);
276
277 NameTalkableFilter *nameFilter = new NameTalkableFilter(NameTalkableFilter::UndecidedMatching, ProxyModel);
278 connect(BuddiesWidget, SIGNAL(filterChanged(QString)), nameFilter, SLOT(setName(QString)));
279
280 ProxyModel->addFilter(nameFilter);
281 chain->addProxyModel(ProxyModel);
282
283 view->setChain(chain);
284 view->setRootIsDecorated(false);
285 view->setShowIdentityNameIfMany(false);
286 view->setContextMenuEnabled(true);
287
288 connect(view, SIGNAL(talkableActivated(Talkable)),
289 m_kaduWindowService->kaduWindow(), SLOT(talkableActivatedSlot(Talkable)));
290
291 BuddiesWidget->setView(view);
292
293 QToolBar *toolBar = new QToolBar(contactsListContainer);
294 toolBar->addAction(m_actions->createAction("editUserAction", InputBox->actionContext(), InputBox));
295 toolBar->addAction(m_actions->createAction("leaveChatAction", InputBox->actionContext(), InputBox));
296
297 layout->addWidget(toolBar);
298 layout->addWidget(BuddiesWidget);
299
300 QList<int> sizes;
301 sizes.append(3);
302 sizes.append(1);
303 HorizontalSplitter->setSizes(sizes);
304 }
305
configurationUpdated()306 void ChatWidgetImpl::configurationUpdated()
307 {
308 InputBox->inputBox()->setFont(m_chatConfigurationHolder->chatFont());
309 QString style;
310 QColor color = qApp->palette().text().color();
311 if (m_chatConfigurationHolder->chatTextCustomColors())
312 {
313 style = QString("background-color:%1;").arg(m_chatConfigurationHolder->chatTextBgColor().name());
314 color = m_chatConfigurationHolder->chatTextFontColor();
315 }
316 InputBox->inputBox()->viewport()->setStyleSheet(style);
317 QPalette palette = InputBox->inputBox()->palette();
318 palette.setBrush(QPalette::Text, color);
319 InputBox->inputBox()->setPalette(palette);
320 }
321
chatUpdated()322 void ChatWidgetImpl::chatUpdated()
323 {
324 qApp->alert(window());
325 }
326
keyPressEventHandled(QKeyEvent * e)327 bool ChatWidgetImpl::keyPressEventHandled(QKeyEvent *e)
328 {
329 if (e->matches(QKeySequence::Copy) && !MessagesView->selectedText().isEmpty())
330 {
331 // Do not use triggerPageAction(), see bug #2345.
332 MessagesView->pageAction(QWebPage::Copy)->trigger();
333 return true;
334 }
335
336 if (HotKey::shortCut(m_configuration, e,"ShortCuts", "chat_clear"))
337 {
338 clearChatWindow();
339 return true;
340 }
341
342 if (HotKey::shortCut(m_configuration, e,"ShortCuts", "chat_close"))
343 {
344 emit closeRequested(this);
345 return true;
346 }
347
348 if (HotKey::shortCut(m_configuration, e,"ShortCuts", "kadu_searchuser"))
349 {
350 m_actions->createAction("lookupUserInfoAction", InputBox->actionContext(), InputBox)->activate(QAction::Trigger);
351 return true;
352 }
353
354 if (HotKey::shortCut(m_configuration, e,"ShortCuts", "kadu_openchatwith"))
355 {
356 m_actions->createAction("openChatWithAction", InputBox->actionContext(), InputBox)->activate(QAction::Trigger);
357 return true;
358 }
359
360 return false;
361 }
362
keyPressEvent(QKeyEvent * e)363 void ChatWidgetImpl::keyPressEvent(QKeyEvent *e)
364 {
365 if (keyPressEventHandled(e))
366 e->accept();
367 else
368 QWidget::keyPressEvent(e);
369 }
370
resizeEvent(QResizeEvent * e)371 void ChatWidgetImpl::resizeEvent(QResizeEvent *e)
372 {
373 QWidget::resizeEvent(e);
374
375 if (m_chatEditBoxSizeManager->initialized())
376 commonHeightChanged(m_chatEditBoxSizeManager->commonHeight());
377 }
378
title() const379 ChatWidgetTitle * ChatWidgetImpl::title() const
380 {
381 return Title;
382 }
383
addMessages(const SortedMessages & messages)384 void ChatWidgetImpl::addMessages(const SortedMessages &messages)
385 {
386 if (messages.empty())
387 return;
388
389 auto unread = std::any_of(begin(messages), end(messages), [](const Message &message){
390 return message.type() == MessageTypeReceived || message.type() == MessageTypeSystem;
391 });
392
393 MessagesView->setForcePruneDisabled(true);
394 MessagesView->add(messages);
395 if (unread)
396 LastReceivedMessageTime = QDateTime::currentDateTime();
397 }
398
addMessage(const Message & message)399 void ChatWidgetImpl::addMessage(const Message &message)
400 {
401 MessagesView->add(message);
402
403 if (message.type() != MessageTypeReceived && message.type() != MessageTypeSystem)
404 return;
405
406 LastReceivedMessageTime = QDateTime::currentDateTime();
407 emit messageReceived(this);
408 }
409
messages() const410 SortedMessages ChatWidgetImpl::messages() const
411 {
412 return MessagesView->messages();
413 }
414
appendSystemMessage(NormalizedHtmlString htmlContent)415 void ChatWidgetImpl::appendSystemMessage(NormalizedHtmlString htmlContent)
416 {
417 Message message = m_messageStorage->create();
418 message.setMessageChat(CurrentChat);
419 message.setType(MessageTypeSystem);
420 message.setContent(htmlContent);
421 message.setReceiveDate(QDateTime::currentDateTime());
422 message.setSendDate(QDateTime::currentDateTime());
423
424 MessagesView->add(message);
425 }
426
resetEditBox()427 void ChatWidgetImpl::resetEditBox()
428 {
429 InputBox->inputBox()->clear();
430
431 Action *action;
432 action = m_chatWidgetActions->bold()->action(InputBox->actionContext());
433 if (action)
434 InputBox->inputBox()->setFontWeight(action->isChecked() ? QFont::Bold : QFont::Normal);
435
436 action = m_chatWidgetActions->italic()->action(InputBox->actionContext());
437 if (action)
438 InputBox->inputBox()->setFontItalic(action->isChecked());
439
440 action = m_chatWidgetActions->underline()->action(InputBox->actionContext());
441 if (action)
442 InputBox->inputBox()->setFontUnderline(action->isChecked());
443 }
444
clearChatWindow()445 void ChatWidgetImpl::clearChatWindow()
446 {
447 MessageDialog *dialog = MessageDialog::create(m_iconsManager->iconByPath(KaduIcon("dialog-question")), tr("Kadu"), tr("Chat window will be cleared. Continue?"));
448 dialog->addButton(QMessageBox::Yes, tr("Clear chat window"));
449 dialog->addButton(QMessageBox::No, tr("Cancel"));
450
451 if (!m_configuration->deprecatedApi()->readBoolEntry("Chat", "ConfirmChatClear") || dialog->ask())
452 {
453 MessagesView->clearMessages();
454 MessagesView->setForcePruneDisabled(false);
455 activateWindow();
456 }
457 }
458
459 /* sends the message typed */
sendMessage()460 void ChatWidgetImpl::sendMessage()
461 {
462 if (InputBox->inputBox()->toPlainText().isEmpty())
463 return;
464
465 emit messageSendRequested(this);
466
467 if (!CurrentChat.isConnected())
468 {
469 MessageDialog::show(m_iconsManager->iconByPath(KaduIcon("dialog-error")), tr("Kadu"),
470 QString("%1\n%2%3")
471 .arg(tr("Cannot send message while being offline."))
472 .arg(tr("Account:"))
473 .arg(chat().chatAccount().id()),
474 QMessageBox::Ok, this);
475 return;
476 }
477
478 if (!m_messageManager->sendMessage(CurrentChat, InputBox->inputBox()->htmlMessage()))
479 return;
480
481 resetEditBox();
482
483 // We sent the message and reseted the edit box, so composing of that message is done.
484 // Note that if ComposingTimer is not active, it means that we already reported
485 // composing had stopped.
486 if (ComposingTimer.isActive())
487 composingStopped();
488
489 emit messageSent(this);
490 }
491
colorSelectorAboutToClose()492 void ChatWidgetImpl::colorSelectorAboutToClose()
493 {
494 }
495
edit() const496 CustomInput * ChatWidgetImpl::edit() const
497 {
498 return InputBox ? InputBox->inputBox() : 0;
499 }
500
talkableProxyModel() const501 TalkableProxyModel * ChatWidgetImpl::talkableProxyModel() const
502 {
503 return ProxyModel;
504 }
505
countMessages() const506 int ChatWidgetImpl::countMessages() const
507 {
508 return MessagesView ? MessagesView->countMessages() : 0;
509 }
510
decodeLocalFiles(QDropEvent * event,QStringList & files)511 bool ChatWidgetImpl::decodeLocalFiles(QDropEvent *event, QStringList &files)
512 {
513 if (!event->mimeData()->hasUrls() || event->source() == MessagesView.get())
514 return false;
515
516 QList<QUrl> urls = event->mimeData()->urls();
517
518 foreach (const QUrl &url, urls)
519 {
520 QString file = url.toLocalFile();
521 if (!file.isEmpty())
522 {
523 //is needed to check if file refer to local file?
524 QFileInfo fileInfo(file);
525 if (fileInfo.exists())
526 files.append(file);
527 }
528 }
529 return !files.isEmpty();
530
531 }
532
dragEnterEvent(QDragEnterEvent * e)533 void ChatWidgetImpl::dragEnterEvent(QDragEnterEvent *e)
534 {
535 QStringList files;
536
537 if (decodeLocalFiles(e, files))
538 e->acceptProposedAction();
539
540 }
541
dragMoveEvent(QDragMoveEvent * e)542 void ChatWidgetImpl::dragMoveEvent(QDragMoveEvent *e)
543 {
544 QStringList files;
545
546 if (decodeLocalFiles(e, files))
547 e->acceptProposedAction();
548 }
549
dropEvent(QDropEvent * e)550 void ChatWidgetImpl::dropEvent(QDropEvent *e)
551 {
552 QStringList files;
553
554 if (decodeLocalFiles(e, files))
555 {
556 e->acceptProposedAction();
557
558 QStringList::const_iterator i = files.constBegin();
559 QStringList::const_iterator end = files.constEnd();
560
561 for (; i != end; ++i)
562 emit fileDropped(CurrentChat, *i);
563 }
564 }
565
currentProtocol() const566 Protocol *ChatWidgetImpl::currentProtocol() const
567 {
568 return CurrentChat.chatAccount().protocolHandler();
569 }
570
requestClose()571 void ChatWidgetImpl::requestClose()
572 {
573 emit closeRequested(this);
574 }
575
verticalSplitterMoved(int pos,int index)576 void ChatWidgetImpl::verticalSplitterMoved(int pos, int index)
577 {
578 Q_UNUSED(pos)
579 Q_UNUSED(index)
580
581 if (SplittersInitialized)
582 m_chatEditBoxSizeManager->setCommonHeight(VerticalSplitter->sizes().at(1));
583 }
584
kaduRestoreGeometry()585 void ChatWidgetImpl::kaduRestoreGeometry()
586 {
587 if (!HorizontalSplitter)
588 return;
589
590 if (!chat())
591 return;
592
593 QList<int> horizSizes = stringToIntList(chat().property("chat-geometry:WidgetHorizontalSizes", QString()).toString());
594 if (!horizSizes.isEmpty())
595 HorizontalSplitter->setSizes(horizSizes);
596 }
597
kaduStoreGeometry()598 void ChatWidgetImpl::kaduStoreGeometry()
599 {
600 if (!HorizontalSplitter)
601 return;
602
603 if (!chat())
604 return;
605
606 chat().addProperty("chat-geometry:WidgetHorizontalSizes", intListToString(HorizontalSplitter->sizes()),
607 CustomProperties::Storable);
608 }
609
showEvent(QShowEvent * e)610 void ChatWidgetImpl::showEvent(QShowEvent *e)
611 {
612 QWidget::showEvent(e);
613 if (!SplittersInitialized)
614 QMetaObject::invokeMethod(this, "setUpVerticalSizes", Qt::QueuedConnection);
615 }
616
setUpVerticalSizes()617 void ChatWidgetImpl::setUpVerticalSizes()
618 {
619 // now we can accept this signal
620 connect(m_chatEditBoxSizeManager, SIGNAL(commonHeightChanged(int)), this, SLOT(commonHeightChanged(int)));
621
622 // already set up by other window, so we use this window setting
623 if (m_chatEditBoxSizeManager->initialized())
624 {
625 commonHeightChanged(m_chatEditBoxSizeManager->commonHeight());
626 SplittersInitialized = true;
627 return;
628 }
629
630 QList<int> vertSizes;
631 int h = height();
632 vertSizes.append(h / 3 * 2 + h % 3);
633 vertSizes.append(h / 3);
634
635 VerticalSplitter->setSizes(vertSizes);
636 SplittersInitialized = true;
637 m_chatEditBoxSizeManager->setCommonHeight(vertSizes.at(1));
638 }
639
commonHeightChanged(int commonHeight)640 void ChatWidgetImpl::commonHeightChanged(int commonHeight)
641 {
642 QList<int> sizes = VerticalSplitter->sizes();
643
644 int sum = 0;
645 if (2 == sizes.count())
646 {
647 if (sizes.at(1) == commonHeight)
648 return;
649 sum = sizes.at(0) + sizes.at(1);
650 }
651 else
652 sum = height();
653
654 if (sum < commonHeight)
655 commonHeight = sum / 3;
656
657 sizes.clear();
658 sizes.append(sum - commonHeight);
659 sizes.append(commonHeight);
660 VerticalSplitter->setSizes(sizes);
661 }
662
composingStopped()663 void ChatWidgetImpl::composingStopped()
664 {
665 ComposingTimer.stop();
666 IsComposing = false;
667
668 if (currentProtocol() && currentProtocol()->chatStateService() && chat().contacts().toContact())
669 currentProtocol()->chatStateService()->sendState(chat().contacts().toContact(), ChatState::Paused);
670 }
671
checkComposing()672 void ChatWidgetImpl::checkComposing()
673 {
674 if (!IsComposing)
675 composingStopped();
676 else
677 // Reset IsComposing to false, so if updateComposing() method doesn't set it to true
678 // before ComposingTimer hits this method again, we will call composingStopped().
679 IsComposing = false;
680 }
681
updateComposing()682 void ChatWidgetImpl::updateComposing()
683 {
684 if (!currentProtocol() || !currentProtocol()->chatStateService())
685 return;
686
687 if (!ComposingTimer.isActive())
688 {
689 // If the text was deleted either by sending a message or explicitly by the user,
690 // let's not report it as composing.
691 if (edit()->toPlainText().isEmpty())
692 return;
693
694 if (chat().contacts().toContact())
695 currentProtocol()->chatStateService()->sendState(chat().contacts().toContact(), ChatState::Composing);
696
697 ComposingTimer.start();
698 }
699 IsComposing = true;
700 }
701
chatState() const702 ChatState ChatWidgetImpl::chatState() const
703 {
704 return CurrentContactActivity;
705 }
706
contactActivityChanged(const Contact & contact,ChatState state)707 void ChatWidgetImpl::contactActivityChanged(const Contact &contact, ChatState state)
708 {
709 if (CurrentContactActivity == state)
710 return;
711
712 if (!CurrentChat.contacts().contains(contact))
713 return;
714
715 CurrentContactActivity = state;
716 emit chatStateChanged(CurrentContactActivity);
717
718 if (m_chatConfigurationHolder->contactStateChats())
719 MessagesView->contactActivityChanged(contact, state);
720
721 if (CurrentContactActivity == ChatState::Gone)
722 {
723 auto msg = QString("[ " + tr("%1 ended the conversation").arg(contact.ownerBuddy().display()) + " ]");
724 Message message = m_messageStorage->create();
725 message.setMessageChat(CurrentChat);
726 message.setType(MessageTypeSystem);
727 message.setMessageSender(contact);
728 message.setContent(normalizeHtml(plainToHtml(msg)));
729 message.setSendDate(QDateTime::currentDateTime());
730 message.setReceiveDate(QDateTime::currentDateTime());
731
732 MessagesView->add(message);
733 }
734 }
735
keyPressedSlot(QKeyEvent * e,CustomInput * input,bool & handled)736 void ChatWidgetImpl::keyPressedSlot(QKeyEvent *e, CustomInput *input, bool &handled)
737 {
738 Q_UNUSED(input)
739
740 if (e->key() == Qt::Key_Home && e->modifiers() == Qt::AltModifier)
741 MessagesView->scrollToTop();
742 else if (e->key() == Qt::Key_End && e->modifiers() == Qt::AltModifier)
743 MessagesView->forceScrollToBottom();
744
745 if (handled)
746 return;
747
748 handled = keyPressEventHandled(e);
749 }
750
751 #include "moc_chat-widget-impl.cpp"
752