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