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