1 /*
2  * %kadu copyright begin%
3  * Copyright 2012 Wojciech Treter (juzefwt@gmail.com)
4  * Copyright 2013, 2014 Bartosz Brachaczek (b.brachaczek@gmail.com)
5  * Copyright 2012, 2013, 2014 Rafał Przemysław Malinowski (rafal.przemyslaw.malinowski@gmail.com)
6  * %kadu copyright end%
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of
11  * the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "timeline-chat-messages-view.h"
23 
24 #include "model/history-query-results-model.h"
25 #include "model/history-query-results-proxy-model.h"
26 #include "history-query-result.h"
27 
28 #include "chat-style/engine/chat-style-renderer-factory-provider.h"
29 #include "contacts/contact-set.h"
30 #include "formatted-string/formatted-string-factory.h"
31 #include "formatted-string/formatted-string-plain-text-visitor.h"
32 #include "formatted-string/formatted-string.h"
33 #include "gui/scoped-updates-disabler.h"
34 #include "gui/web-view-highlighter.h"
35 #include "gui/widgets/search-bar.h"
36 #include "gui/widgets/wait-overlay.h"
37 #include "gui/widgets/webkit-messages-view/webkit-messages-view-factory.h"
38 #include "gui/widgets/webkit-messages-view/webkit-messages-view.h"
39 #include "message/message-manager.h"
40 #include "message/sorted-messages.h"
41 #include "model/roles.h"
42 #include "plugin/plugin-injected-factory.h"
43 
44 #include <QtWidgets/QScrollBar>
45 #include <QtWidgets/QSplitter>
46 #include <QtWidgets/QTreeView>
47 #include <QtWidgets/QVBoxLayout>
48 
49 #define DATE_TITLE_LENGTH 120
50 
TimelineChatMessagesView(QWidget * parent)51 TimelineChatMessagesView::TimelineChatMessagesView(QWidget *parent) :
52 		QWidget(parent),
53 		TimelineWaitOverlay(0), MessagesViewWaitOverlay(0),
54 		ResultsFutureWatcher (0), MessagesFutureWatcher(0)
55 {
56 
57 }
58 
~TimelineChatMessagesView()59 TimelineChatMessagesView::~TimelineChatMessagesView()
60 {
61 }
62 
setFormattedStringFactory(FormattedStringFactory * formattedStringFactory)63 void TimelineChatMessagesView::setFormattedStringFactory(FormattedStringFactory *formattedStringFactory)
64 {
65 	m_formattedStringFactory = formattedStringFactory;
66 }
67 
setPluginInjectedFactory(PluginInjectedFactory * pluginInjectedFactory)68 void TimelineChatMessagesView::setPluginInjectedFactory(PluginInjectedFactory *pluginInjectedFactory)
69 {
70 	m_pluginInjectedFactory = pluginInjectedFactory;
71 }
72 
setMessageManager(MessageManager * messageManager)73 void TimelineChatMessagesView::setMessageManager(MessageManager *messageManager)
74 {
75 	connect(messageManager, SIGNAL(messageReceived(Message)), this, SLOT(newMessage(Message)));
76 	connect(messageManager, SIGNAL(messageSent(Message)), this, SLOT(newMessage(Message)));
77 }
78 
setWebkitMessagesViewFactory(WebkitMessagesViewFactory * webkitMessagesViewFactory)79 void TimelineChatMessagesView::setWebkitMessagesViewFactory(WebkitMessagesViewFactory *webkitMessagesViewFactory)
80 {
81 	m_webkitMessagesViewFactory = webkitMessagesViewFactory;
82 }
83 
init()84 void TimelineChatMessagesView::init()
85 {
86 	ResultsModel = m_pluginInjectedFactory->makeInjected<HistoryQueryResultsModel>(this);
87 	ResultsProxyModel = new HistoryQueryResultsProxyModel(ResultsModel);
88 	ResultsProxyModel->setSourceModel(ResultsModel);
89 
90 	setLayout(new QVBoxLayout(this));
91 	layout()->setMargin(0);
92 	layout()->setSpacing(0);
93 
94 	createGui();
95 }
96 
createGui()97 void TimelineChatMessagesView::createGui()
98 {
99 	Splitter = new QSplitter(Qt::Vertical, this);
100 
101 	Timeline = new QTreeView(Splitter);
102 
103 	Timeline->setAlternatingRowColors(true);
104 	Timeline->setModel(ResultsProxyModel);
105 	Timeline->setRootIsDecorated(false);
106 	Timeline->setUniformRowHeights(true);
107 
108 	connect(Timeline->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
109 	        this, SIGNAL(currentDateChanged()));
110 
111 	QFrame *frame = new QFrame(Splitter);
112 	frame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
113 
114 	QVBoxLayout *frameLayout = new QVBoxLayout(frame);
115 	frameLayout->setMargin(0);
116 	frameLayout->setSpacing(0);
117 
118 	MessagesView = m_webkitMessagesViewFactory->createWebkitMessagesView(Chat::null, false, frame);
119 	MessagesView->setFocusPolicy(Qt::StrongFocus);
120 	MessagesView->setForcePruneDisabled(true);
121 
122 	frameLayout->addWidget(MessagesView.get());
123 
124 	MessagesSearchBar = new SearchBar(this);
125 	MessagesSearchBar->setSearchWidget(MessagesView.get());
126 
127 	Highlighter = new WebViewHighlighter(MessagesView.get());
128 	Highlighter->setAutoUpdate(true);
129 
130 	connect(MessagesSearchBar, SIGNAL(searchPrevious(QString)), Highlighter, SLOT(selectPrevious(QString)));
131 	connect(MessagesSearchBar, SIGNAL(searchNext(QString)), Highlighter, SLOT(selectNext(QString)));
132 	connect(MessagesSearchBar, SIGNAL(clearSearch()), Highlighter, SLOT(clearSelect()));
133 	connect(Highlighter, SIGNAL(somethingFound(bool)), MessagesSearchBar, SLOT(somethingFound(bool)));
134 
135 	frameLayout->addWidget(MessagesSearchBar);
136 
137 	layout()->addWidget(Splitter);
138 }
139 
currentDate() const140 QDate TimelineChatMessagesView::currentDate() const
141 {
142 	return Timeline->currentIndex().data(DateRole).value<QDate>();
143 }
144 
setResults(const QVector<HistoryQueryResult> & results)145 void TimelineChatMessagesView::setResults(const QVector<HistoryQueryResult> &results)
146 {
147 	ResultsModel->setResults(results);
148 
149 	if (results.isEmpty())
150 	{
151 		emit currentDateChanged();
152 		return;
153 	}
154 
155 	const QModelIndex &selected = Timeline->model()->index(Timeline->model()->rowCount() - 1, 0);
156 	Timeline->setCurrentIndex(selected);
157 	Timeline->scrollTo(selected, QAbstractItemView::PositionAtBottom);
158 }
159 
futureResultsAvailable()160 void TimelineChatMessagesView::futureResultsAvailable()
161 {
162 	hideTimelineWaitOverlay();
163 
164 	if (!ResultsFutureWatcher)
165 		return;
166 
167 	setResults(ResultsFutureWatcher->result());
168 
169 	ResultsFutureWatcher->deleteLater();
170 	ResultsFutureWatcher = 0;
171 }
172 
futureResultsCanceled()173 void TimelineChatMessagesView::futureResultsCanceled()
174 {
175 	hideTimelineWaitOverlay();
176 
177 	if (!ResultsFutureWatcher)
178 		return;
179 
180 	ResultsFutureWatcher->deleteLater();
181 	ResultsFutureWatcher = 0;
182 }
183 
setFutureResults(const QFuture<QVector<HistoryQueryResult>> & futureResults)184 void TimelineChatMessagesView::setFutureResults(const QFuture<QVector<HistoryQueryResult>> &futureResults)
185 {
186 	if (ResultsFutureWatcher)
187 	{
188 		ResultsFutureWatcher->cancel();
189 		ResultsFutureWatcher->deleteLater();
190 	}
191 
192 	ResultsFutureWatcher = new QFutureWatcher<QVector<HistoryQueryResult> >(this);
193 	connect(ResultsFutureWatcher, SIGNAL(finished()), this, SLOT(futureResultsAvailable()));
194 	connect(ResultsFutureWatcher, SIGNAL(canceled()), this, SLOT(futureResultsCanceled()));
195 
196 	ResultsFutureWatcher->setFuture(futureResults);
197 
198 	showTimelineWaitOverlay();
199 }
200 
setMessages(const SortedMessages & messages)201 void TimelineChatMessagesView::setMessages(const SortedMessages &messages)
202 {
203 	ScopedUpdatesDisabler updatesDisabler{*MessagesView};
204 
205 	MessagesView->clearMessages();
206 	MessagesView->add(messages);
207 
208 	emit messagesDisplayed();
209 }
210 
newMessage(const Message & message)211 void TimelineChatMessagesView::newMessage(const Message &message)
212 {
213 	auto chatMatch = message.messageChat() == MessagesView->chat();
214 	if (!chatMatch)
215 	{
216 		if (message.messageChat().type() != "Contact" || MessagesView->chat().type() != "Buddy")
217 			return;
218 		if (!MessagesView->chat().contacts().toBuddySet().contains(message.messageChat().contacts().toContact().ownerBuddy()))
219 			return;
220 	}
221 
222 	auto formattedContent = m_formattedStringFactory->fromHtml(message.content());
223 	FormattedStringPlainTextVisitor plainTextVisitor;
224 	formattedContent->accept(&plainTextVisitor);
225 
226 	auto title = plainTextVisitor.result().replace('\n', ' ').replace('\r', ' ');
227 	if (title.length() > DATE_TITLE_LENGTH)
228 	{
229 		title.truncate(DATE_TITLE_LENGTH);
230 		title += " ...";
231 	}
232 
233 	auto messageDate = message.receiveDate().date();
234 	ResultsModel->addEntry(messageDate, message.messageChat(), title);
235 
236 	if (messageDate == currentDate())
237 		MessagesView->add(message);
238 }
239 
futureMessagesAvailable()240 void TimelineChatMessagesView::futureMessagesAvailable()
241 {
242 	if (!MessagesFutureWatcher)
243 	{
244 		hideMessagesViewWaitOverlay();
245 		return;
246 	}
247 
248 	setMessages(MessagesFutureWatcher->result());
249 	hideMessagesViewWaitOverlay(); // wait for messages to display before hiding
250 
251 	MessagesFutureWatcher->deleteLater();
252 	MessagesFutureWatcher = 0;
253 }
254 
futureMessagesCanceled()255 void TimelineChatMessagesView::futureMessagesCanceled()
256 {
257 	hideMessagesViewWaitOverlay();
258 
259 	if (!MessagesFutureWatcher)
260 		return;
261 
262 	MessagesFutureWatcher->deleteLater();
263 	MessagesFutureWatcher = 0;
264 }
265 
setFutureMessages(const QFuture<SortedMessages> & futureMessages)266 void TimelineChatMessagesView::setFutureMessages(const QFuture<SortedMessages> &futureMessages)
267 {
268 	if (MessagesFutureWatcher)
269 	{
270 		MessagesFutureWatcher->cancel();
271 		MessagesFutureWatcher->deleteLater();
272 	}
273 
274 	MessagesFutureWatcher = new QFutureWatcher<SortedMessages>(this);
275 	connect(MessagesFutureWatcher, SIGNAL(finished()), this, SLOT(futureMessagesAvailable()));
276 	connect(MessagesFutureWatcher, SIGNAL(canceled()), this, SLOT(futureMessagesCanceled()));
277 
278 	MessagesFutureWatcher->setFuture(futureMessages);
279 
280 	showMessagesViewWaitOverlay();
281 }
282 
showTimelineWaitOverlay()283 void TimelineChatMessagesView::showTimelineWaitOverlay()
284 {
285 	if (!TimelineWaitOverlay)
286 		TimelineWaitOverlay = new WaitOverlay(this);
287 	else
288 		TimelineWaitOverlay->show();
289 }
290 
hideTimelineWaitOverlay()291 void TimelineChatMessagesView::hideTimelineWaitOverlay()
292 {
293 	TimelineWaitOverlay->deleteLater();
294 	TimelineWaitOverlay = 0;
295 }
296 
showMessagesViewWaitOverlay()297 void TimelineChatMessagesView::showMessagesViewWaitOverlay()
298 {
299 	if (!MessagesViewWaitOverlay)
300 		MessagesViewWaitOverlay = m_pluginInjectedFactory->makeInjected<WaitOverlay>(MessagesView.get());
301 	else
302 		MessagesViewWaitOverlay->show();
303 }
304 
hideMessagesViewWaitOverlay()305 void TimelineChatMessagesView::hideMessagesViewWaitOverlay()
306 {
307 	MessagesViewWaitOverlay->deleteLater();
308 	MessagesViewWaitOverlay = 0;
309 }
310 
setTalkableVisible(const bool talkableVisible)311 void TimelineChatMessagesView::setTalkableVisible(const bool talkableVisible)
312 {
313 	ResultsProxyModel->setTalkableVisible(talkableVisible);
314 }
315 
setTitleVisible(const bool titleVisible)316 void TimelineChatMessagesView::setTitleVisible(const bool titleVisible)
317 {
318 	ResultsProxyModel->setTitleVisible(titleVisible);
319 }
320 
setTalkableHeader(const QString & talkableHeader)321 void TimelineChatMessagesView::setTalkableHeader(const QString &talkableHeader)
322 {
323 	ResultsModel->setTalkableHeader(talkableHeader);
324 }
325 
setLengthHeader(const QString & lengthHeader)326 void TimelineChatMessagesView::setLengthHeader(const QString &lengthHeader)
327 {
328 	ResultsModel->setLengthHeader(lengthHeader);
329 }
330 
sizes() const331 QList<int> TimelineChatMessagesView::sizes() const
332 {
333 	return Splitter->sizes();
334 }
335 
setSizes(const QList<int> & newSizes)336 void TimelineChatMessagesView::setSizes(const QList<int> &newSizes)
337 {
338 	Q_ASSERT(newSizes.size() == 2);
339 
340 	Splitter->setSizes(newSizes);
341 }
342 
343 #include "moc_timeline-chat-messages-view.cpp"
344