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>&nbsp;</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