1 /*
2 * This file is part of Licq, an instant messaging client for UNIX.
3 * Copyright (C) 2007-2013 Licq developers <licq-dev@googlegroups.com>
4 *
5 * Licq is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * Licq is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Licq; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "historyview.h"
21
22 #include <QDateTime>
23 #include <QRegExp>
24
25 #include <licq/contactlist/owner.h>
26 #include <licq/contactlist/user.h>
27 #include <licq/event.h>
28 #include <licq/userevents.h>
29
30 #include "config/chat.h"
31
32 using namespace LicqQtGui;
33 /* TRANSLATOR LicqQtGui::HistoryView */
34
getStyleNames(bool includeHistoryStyles)35 QStringList HistoryView::getStyleNames(bool includeHistoryStyles)
36 {
37 static const char* const styleNames[] = {
38 QT_TR_NOOP("Default"),
39 QT_TR_NOOP("Compact"),
40 QT_TR_NOOP("Tiny"),
41 QT_TR_NOOP("Table"),
42 QT_TR_NOOP("Long"),
43 QT_TR_NOOP("Wide")
44 };
45
46 int listLength = 6;
47
48 // Style 5 (Wide) is currently only supported in buffered mode which is not used for chat
49 if (!includeHistoryStyles)
50 listLength--;
51
52 QStringList styleList;
53 for (int i = 0; i < listLength; ++i)
54 styleList.append(tr(styleNames[i]));
55
56 return styleList;
57 }
58
HistoryView(bool historyMode,const Licq::UserId & userId,QWidget * parent)59 HistoryView::HistoryView(bool historyMode, const Licq::UserId& userId, QWidget* parent)
60 : MLView(parent),
61 myUserId(userId)
62 {
63 Config::Chat* chatConfig = Config::Chat::instance();
64 if (historyMode)
65 {
66 setHistoryConfig(chatConfig->histMsgStyle(), chatConfig->histDateFormat(),
67 chatConfig->histVertSpacing(), chatConfig->reverseHistory());
68 }
69 else
70 {
71 setChatConfig(chatConfig->chatMsgStyle(), chatConfig->chatDateFormat(),
72 chatConfig->chatVertSpacing(), chatConfig->chatAppendLineBreak(),
73 chatConfig->showNotices(), chatConfig->chatDateHeader());
74 }
75
76 setColors();
77 connect(chatConfig, SIGNAL(chatColorsChanged()), SLOT(setColors()));
78
79 clear();
80 }
81
~HistoryView()82 HistoryView::~HistoryView()
83 {
84 }
85
sizeHint() const86 QSize HistoryView::sizeHint() const
87 {
88 // Set a size hint wide enough for history to be readable
89 return QSize(400, 150);
90 }
91
setHistoryConfig(int msgStyle,const QString & dateFormat,bool extraSpacing,bool reverse)92 void HistoryView::setHistoryConfig(int msgStyle,
93 const QString& dateFormat, bool extraSpacing, bool reverse)
94 {
95 myUseBuffer = true;
96 myMsgStyle = msgStyle;
97 myDateFormat = dateFormat;
98 myExtraSpacing = extraSpacing;
99 myReverse = reverse;
100 myAppendLineBreak = false;
101 myShowNotices = false;
102 myAddDateHeader = false;
103 }
104
setChatConfig(int msgStyle,const QString & dateFormat,bool extraSpacing,bool appendLineBreak,bool showNotices,bool dateHeader)105 void HistoryView::setChatConfig(int msgStyle, const QString& dateFormat,
106 bool extraSpacing, bool appendLineBreak, bool showNotices, bool dateHeader)
107 {
108 myUseBuffer = false;
109 myMsgStyle = msgStyle;
110 myDateFormat = dateFormat;
111 myExtraSpacing = extraSpacing;
112 myReverse = false;
113 myAppendLineBreak = appendLineBreak;
114 myShowNotices = showNotices;
115 myAddDateHeader = dateHeader;
116 }
117
setColors(const QString & back,const QString & rcv,const QString & snt,const QString & rcvHist,const QString & sntHist,const QString & notice)118 void HistoryView::setColors(const QString& back, const QString& rcv, const QString& snt,
119 const QString& rcvHist, const QString& sntHist, const QString& notice)
120 {
121 myColorRcv = rcv;
122 myColorSnt = snt;
123 if (!rcvHist.isEmpty())
124 myColorRcvHistory = rcvHist;
125 if (!sntHist.isEmpty())
126 myColorSntHistory = sntHist;
127 if (!notice.isEmpty())
128 myColorNotice = notice;
129 if (!back.isEmpty())
130 setBackground(QColor(back));
131 }
132
setColors()133 void HistoryView::setColors()
134 {
135 Config::Chat* chatConfig = Config::Chat::instance();
136
137 setColors(
138 chatConfig->chatBackColor(),
139 chatConfig->recvColor(),
140 chatConfig->sentColor(),
141 chatConfig->recvHistoryColor(),
142 chatConfig->sentHistoryColor(),
143 chatConfig->noticeColor()
144 );
145 }
146
setReverse(bool reverse)147 void HistoryView::setReverse(bool reverse)
148 {
149 myReverse = reverse;
150 }
151
setOwner(const Licq::UserId & userId)152 void HistoryView::setOwner(const Licq::UserId& userId)
153 {
154 myUserId = userId;
155 }
156
clear()157 void HistoryView::clear()
158 {
159 MLView::clear();
160 myLastDate = QDate();
161
162 myBuffer = "";
163
164 switch (myMsgStyle)
165 {
166 case 5:
167 // table doesn't work when appending so must buffer when using this style
168 myUseBuffer = true;
169 break;
170 }
171 }
172
updateContent()173 void HistoryView::updateContent()
174 {
175 if (!myUseBuffer)
176 return;
177
178 switch (myMsgStyle)
179 {
180 case 5:
181 // When myReverse is set (so that we prepend() to the buffer),
182 // we cannot put the <table> tag in HistoryView::clear().
183 // That's why we are prepend()'ing it here, in updateContent().
184 // Then, if we combine incremenetal updateContent()'s with
185 // myMsgStyle == 5 (we don't so far), we'll obtain several
186 // <table>'s in the buffer -- this works, but stinks heavily.
187 myBuffer.prepend("<table border=\"0\">");
188 break;
189 }
190 // actually, we don't need it at all
191 // myBuffer.prepend("<html><body>");
192
193 setText(myBuffer);
194 }
195
internalAddMsg(QString s,const QDate & date)196 void HistoryView::internalAddMsg(QString s, const QDate& date)
197 {
198 if (myExtraSpacing)
199 {
200 if (myMsgStyle != 5)
201 {
202 if (myUseBuffer)
203 {
204 s.prepend("<p>");
205 s.append("</p>");
206 }
207 else
208 {
209 s.append("<br>");
210 }
211 }
212 else
213 {
214 s.append("<tr><td colspan=\"3\"></td></tr>");
215 }
216 }
217
218 if (myAddDateHeader && date != myLastDate)
219 {
220 s.prepend(QString("<hr><center><b>%1</b></center>")
221 #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0))
222 .arg(date.toString(Qt::DefaultLocaleLongDate)));
223 #else
224 .arg(date.toString(Qt::LocaleDate)));
225 #endif
226 }
227 else if (myAppendLineBreak)
228 {
229 s.prepend("<hr>");
230 }
231 myLastDate = date;
232
233 if (myUseBuffer)
234 {
235 if (!myExtraSpacing && myMsgStyle != 5)
236 s.append("<br>");
237
238 if (myReverse)
239 myBuffer.prepend(s);
240 else
241 myBuffer.append(s);
242 }
243 else
244 {
245 append(s);
246 }
247 }
248
addMsg(const Licq::Event * event)249 void HistoryView::addMsg(const Licq::Event* event)
250 {
251 if (event->userId() == myUserId && event->userEvent() != NULL)
252 addMsg(event->userEvent());
253 }
254
addMsg(bool isReceiver,bool fromHistory,const QString & eventDescription,const QDateTime & date,bool isDirect,bool isMultiRec,bool isUrgent,bool isEncrypted,const QString & contactName,QString messageText,QString anchor)255 void HistoryView::addMsg(bool isReceiver, bool fromHistory,
256 const QString& eventDescription, const QDateTime& date,
257 bool isDirect, bool isMultiRec, bool isUrgent, bool isEncrypted,
258 const QString& contactName, QString messageText, QString anchor)
259 {
260 QString s;
261 QString color;
262
263 if (fromHistory)
264 {
265 if (isReceiver)
266 color = myColorRcvHistory;
267 else
268 color = myColorSntHistory;
269 }
270 else
271 {
272 if (isReceiver)
273 color = myColorRcv;
274 else
275 color = myColorSnt;
276 }
277
278 // Remove trailing line breaks.
279 for (int i = messageText.length(); i > 0; --i)
280 {
281 if (messageText.at(i - 1) != '\n' && messageText.at(i - 1) != '\r')
282 {
283 messageText.truncate(i);
284 break;
285 }
286 }
287
288 // Extract everything inside <body>...</body>
289 // Leaving <html> and <body> messes with our message display
290 QRegExp body("<body[^>]*>(.*)</body>");
291 if (body.indexIn(messageText) != -1)
292 messageText = body.cap(1);
293
294 // Remove all font tags
295 messageText.replace(QRegExp("</?font[^>]*>"), "");
296
297 QString dateString = date.toString(myDateFormat);
298
299 if (!anchor.isEmpty())
300 anchor = "<a name=\"" + anchor + "\"/>";
301
302 QString flags = QString("%1%2%3%4")
303 .arg(isDirect ? 'D' : '-')
304 .arg(isMultiRec ? 'M' : '-')
305 .arg(isUrgent ? 'U' : '-')
306 .arg(isEncrypted ? 'E' : '-');
307
308 switch (myMsgStyle)
309 {
310 case 0:
311 s = QString("%1<font color=\"%2\"><b>%3[%4] %5:</b></font><br>")
312 .arg(anchor)
313 .arg(color)
314 .arg(dateString.isEmpty() && eventDescription.isEmpty() ? "" :
315 QString("%1%2").arg(eventDescription).arg(dateString))
316 .arg(flags)
317 .arg(contactName);
318 s.append(QString("<font color=\"%1\">%2</font>")
319 .arg(color)
320 .arg(messageText));
321 break;
322 case 1:
323 s = QString("%1<font color=\"%2\"><b>%3[%4] %5: </b></font>")
324 .arg(anchor)
325 .arg(color)
326 .arg(dateString.isEmpty() && eventDescription.isEmpty() ? "" :
327 QString("(%1%2) ").arg(eventDescription).arg(dateString))
328 .arg(flags)
329 .arg(contactName);
330 s.append(QString("<font color=\"%1\">%2</font>")
331 .arg(color)
332 .arg(messageText));
333 break;
334 case 2:
335 s = QString("%1<font color=\"%2\"><b>%3%4: </b></font>")
336 .arg(anchor)
337 .arg(color)
338 .arg(dateString.isEmpty() && eventDescription.isEmpty() ? "" :
339 QString("%1%2 - ").arg(eventDescription).arg(dateString))
340 .arg(contactName);
341 s.append(QString("<font color=\"%1\">%2</font>")
342 .arg(color)
343 .arg(messageText));
344 break;
345 case 3:
346 s = QString("%1<table border=\"1\"><tr>%3<td><b><font color=\"%2\">%4</font></b></font></td>")
347 .arg(anchor)
348 .arg(color)
349 .arg(dateString.isEmpty() && eventDescription.isEmpty() ? "" :
350 QString("<td><b><font color=\"%2\">%3%4</font></b></td>")
351 .arg(color).arg(eventDescription).arg(dateString))
352 .arg(contactName);
353 s.append(QString("<td><font color=\"%1\">%2</font></td></tr></table>")
354 .arg(color)
355 .arg(messageText));
356 break;
357 case 4:
358 s = QString("%1<font color=\"%2\"><b>%3 %4 %5<br>%6 [%7]</b></font><br><br>")
359 .arg(anchor)
360 .arg(color)
361 .arg(eventDescription)
362 .arg(isReceiver ? tr("from") : tr("to"))
363 .arg(contactName)
364 .arg(dateString)
365 .arg(flags);
366
367 // We break the paragraph here, since the history text
368 // could be in a different BiDi directionality than the
369 // header and timestamp text.
370 s.append(QString("<font color=\"%1\">%2</font><br><br>")
371 .arg(color)
372 .arg(messageText));
373 break;
374 case 5:
375 // Mode 5 is a table so it cannot be displayed in paragraphs
376 s = QString("<tr><td>%1<nobr><b><font color=\"%2\">%3</font><b> </nobr></td>")
377 .arg(anchor)
378 .arg(color)
379 .arg(dateString);
380 s.append(QString("<td><b><font color=\"%1\">%2</font></b></font> </td>")
381 .arg(color)
382 .arg(contactName));
383 s.append(QString("<td><font color=\"%1\">%2</font></td></tr>")
384 .arg(color)
385 .arg(messageText));
386 break;
387 }
388
389 internalAddMsg(s, date.date());
390 }
391
addMsg(const Licq::UserEvent * event,const Licq::UserId & uid)392 void HistoryView::addMsg(const Licq::UserEvent* event, const Licq::UserId& uid)
393 {
394 QDateTime date;
395 date.setTime_t(event->Time());
396 QString sd = date.time().toString(myDateFormat);
397 bool bUseHTML = false;
398
399 QString contactName;
400
401 Licq::UserId userId = uid.isValid() ? uid : myUserId;
402
403 unsigned long myPpid = 0;
404 QString myId;
405 {
406 Licq::UserReadGuard u(userId);
407 if (u.isLocked())
408 {
409 myId = u->accountId().c_str();
410 myPpid = u->protocolId();
411
412 if (event->isReceiver())
413 {
414 contactName = QString::fromUtf8(u->getAlias().c_str());
415 if (myPpid == ICQ_PPID)
416 for (int x = 0; x < myId.length(); ++x)
417 if (!myId.at(x).isDigit())
418 {
419 bUseHTML = true;
420 break;
421 }
422 }
423 }
424 }
425
426 if (!event->isReceiver())
427 {
428 Licq::OwnerReadGuard o(userId.ownerId());
429 if (o.isLocked())
430 contactName = QString::fromUtf8(o->getAlias().c_str());
431 }
432
433 QString messageText = QString::fromUtf8(event->text().c_str());
434
435 addMsg(event->isReceiver(), false,
436 (event->eventType() == Licq::UserEvent::TypeMessage ? "" : (event->description() + " ").c_str()),
437 date,
438 event->IsDirect(),
439 event->IsMultiRec(),
440 event->IsUrgent(),
441 event->IsEncrypted(),
442 contactName,
443 MLView::toRichText(messageText, true, bUseHTML));
444
445 if (event->isReceiver() &&
446 (event->eventType() == Licq::UserEvent::TypeMessage ||
447 event->eventType() == Licq::UserEvent::TypeUrl))
448 emit messageAdded();
449 }
450
addNotice(const QDateTime & dt,QString messageText)451 void HistoryView::addNotice(const QDateTime& dt, QString messageText)
452 {
453 if (!myShowNotices)
454 return;
455
456 QString color = myColorNotice;
457 QString s = "";
458 const QString dateTime = dt.toString(myDateFormat);
459
460 // Remove trailing line breaks.
461 for (int i = messageText.length(); i >= 0; --i)
462 {
463 if (messageText.at(i - 1) != '\n' && messageText.at(i - 1) != '\r')
464 {
465 messageText.truncate(i);
466 break;
467 }
468 }
469
470 switch (myMsgStyle)
471 {
472 case 1:
473 s = QString("<font color=\"%1\"><b>[%2] %3</b></font>")
474 .arg(color)
475 .arg(dateTime)
476 .arg(messageText);
477 break;
478 case 2:
479 s = QString("<font color=\"%1\"><b>[%2] %3</b></font>")
480 .arg(color)
481 .arg(dateTime)
482 .arg(messageText);
483 break;
484 case 3:
485 s = QString("<table border=\"1\"><tr><td><b><font color=\"%1\">%2</font><b><td><b><font color=\"%3\">%4</font></b></font></td></tr></table>")
486 .arg(color)
487 .arg(dateTime)
488 .arg(color)
489 .arg(messageText);
490 break;
491
492 case 5:
493 s = QString("<tr><td><b><font color=\"%1\">%2</font><b></td><td colspan=\"2\"><b><font color=\"%3\">%4</font></b></font></td></tr>")
494 .arg(color)
495 .arg(dateTime)
496 .arg(color)
497 .arg(messageText);
498 break;
499
500 case 0:
501 default:
502 s = QString("<font color=\"%1\"><b>[%2] %3</b></font><br>")
503 .arg(color)
504 .arg(dateTime)
505 .arg(messageText);
506 break;
507 }
508
509 internalAddMsg(s, dt.date());
510 }
511