Source file: src/mail_displayer.cpp
/* Copyright (C) 2004-2012 Daniel Verite
This file is part of Manitou-Mail (see http://www.manitou-mail.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "mail_displayer.h"
#include "main.h"
#include "app_config.h"
#include "db.h"
#include "body_view.h"
#include "message_view.h"
#include "tags.h"
#include "xface/xface.h"
#include <QRegExp>
#include "filter_log.h"
#include <list>
mail_displayer::mail_displayer(message_view* w)
{
msg_widget = w;
}
mail_displayer::~mail_displayer()
{
}
void
mail_displayer::find_urls(const QString& s, std::list<std::pair<int,int> >* matches)
{
int pos=0;
int len;
QRegExp url("(https?://|www\\.)[\\w\\-]+(\\.[\\w\\-]+)*([^>\"\\s\\[\\]\\)])*");
while ((pos=url.indexIn(s, pos))>=0) {
len=url.matchedLength();
matches->push_back(std::pair<int,int>(pos, len));
pos+=len;
}
}
/*
Takes a single line as input (\n included)
Translate tabs to spaces; translate the characters that are not
allowed in a QTextEdit; highlight searched text;
optionally put URLs inside href tags
*/
QString
mail_displayer::expand_body_line(const QString& line,
const display_prefs& prefs)
{
uint len=line.length();
const int tabsize=8;
QString exp_s;
static const QChar tab = QChar('\t');
static const QChar lbracket = QChar('<');
static const QChar rbracket = QChar('>');
static const QChar amp = QChar('&');
static const QChar ctrl_92 = QChar((ushort)0x92);
int col = 0; // current column
int last_space = 0; // offset in exp_s
int space_encode_chars;
int word_cols = 0; //
QChar last_src_char;
// positions of the occurrences of a searched text within the original string
std::list<uint> hilight_list;
std::pair<int,int> cur_url;
std::list<std::pair<int,int> > urls;
if (prefs.m_clickable_urls) {
find_urls(line, &urls);
}
if (!urls.empty()) {
cur_url = urls.front();
urls.pop_front();
}
else
cur_url = std::pair<int,int>(-1,-1);
for (uint i=0; i<len; i++) {
if (i==(uint)cur_url.first) {
exp_s.append(QString("<a href=\"%1\">").arg(line.mid(i, cur_url.second)));
}
// contents
const QChar c=line.at(i);
if (c == tab) {
int j;
for (j=0; j<(int)(tabsize-(col%tabsize)); j++)
exp_s.append(" ");
col += j;
space_encode_chars=6;
if (j)
last_space = exp_s.length()-space_encode_chars;
word_cols=0;
}
else if (c == lbracket) {
exp_s.append("<");
col++;
word_cols++;
}
else if (c == rbracket) {
exp_s.append(">");
col++;
word_cols++;
}
else if (c == amp) {
exp_s.append("&");
col++;
word_cols++;
}
else if (c == ctrl_92) {
// Unicode 0x92 is not displayed by QTextBrowser but is produced
// by Outlook so we replace it by a basic simple quote
exp_s.append(QChar(0x27));
col++;
word_cols++;
}
else if (c.unicode()>=(ushort)0x80 && c.unicode()<=(ushort)0x9F) {
/* Unicode characters from the "other, Control category" and
whose codes are higher than 0x80 are expressed as HTML codes
because QTextBrowser doesn't render them correctly */
exp_s.append(QString("&#x%1;").arg(c.unicode(), 0, 16));
}
else if (c == ' ') {
// repeated spaces are ignored as in html, so we use instead
// (but only in the case of repeated spaces to limit the overhead)
last_space=exp_s.length();
if (last_src_char==QChar(' ') || last_src_char==tab) {
exp_s.append(" ");
space_encode_chars=6;
}
else {
exp_s.append(c);
space_encode_chars=1;
}
word_cols = 0;
col++;
}
else if (c=='\n') {
exp_s.append("<br>");
col=0;
word_cols=0;
}
else {
exp_s.append(c);
col++;
word_cols++;
}
if (i==(uint)(cur_url.first+cur_url.second-1)) {
exp_s.append("</a>");
if (!urls.empty()) {
cur_url = urls.front(); // next URL
urls.pop_front();
}
else
cur_url = std::pair<int,int>(-1,-1);
}
last_src_char=c;
// wrap the line
#if 0 // now performed by QTextBrowser
const int max_column = 78;
if (m_wrap_lines && (col > max_column)) {
if (last_space) {
exp_s.replace(last_space, space_encode_chars, "<br>");
last_space=0;
col = word_cols;
}
}
#endif
}
return exp_s;
}
QString
mail_displayer::sprint_headers(int show_headers_level, mail_msg* msg)
{
static const char *hListe[] = {
"date:", "from:", "to:", "cc:", "reply-to:", "bcc:", "subject:"
};
if (show_headers_level==0)
return QString::null; // don't show any header
const QString& h=msg->get_headers();
uint nPos=0;
uint nEnd=h.length();
QString sOutput;
QString s_xface, s_face;
uint face_offset=0;
uint xface_offset=0;
QString hfontsz_o; // <big> or empty
QString hfontsz_c; // </big> or empty
if (show_headers_level==1) {
// Show only headers from hListe
const int nH=sizeof(hListe)/sizeof(hListe[0]);
QString sLines[nH];
while (nPos<nEnd) {
// position of end of current line
int nPosLf=h.indexOf('\n',nPos);
if (nPosLf==-1)
nPosLf=nEnd;
if (h.mid(nPos,7) == "X-Face:" && s_face.isEmpty()) {
xface_offset = nPos+7;
}
if (h.mid(nPos,5) == "Face:") {
s_face = h.mid(nPos+5, nPosLf-(nPos+5));
face_offset = nPos+5;
xface_offset = 0;
}
// see if we have to display this header
for (int i=0; i<nH; i++) {
uint nHlen=strlen(hListe[i]);
if (h.mid(nPos,nHlen).toLower() == hListe[i]) {
QString sContents=h.mid(nPos+nHlen, nPosLf-(nPos+nHlen));
sContents.replace("<", "<");
sContents.replace(">", ">");
sContents.replace("\t", " ");
sLines[i]=hfontsz_o+QString("<font color=\"blue\">") + h.mid(nPos,nHlen) +
"</font>" + sContents + hfontsz_c + "<br>";
break;
}
}
nPos=nPosLf+1;
}
// order the headers as in hListe and not as they appear in the
// original message
for (int j=0; j<nH; j++) {
if (sLines[j].length()>0)
sOutput+=sLines[j];
}
QString hface;
QString hface_url;
if (face_offset) {
hface = "Face:";
hface_url = QString("face?id=%1&o=%2").arg(msg->get_id()).arg(face_offset);
}
else if (xface_offset) {
hface = "<nobr>X-Face:</nobr>";
hface_url = QString("xface?id=%1&o=%2").arg(msg->get_id()).arg(xface_offset);
}
if (!hface.isEmpty()) {
QString st = "<table><tr><td width=50 valign=top><font color=\"blue\">"+hface+"</font><br>";
st += "<img src=\"manitou://"+hface_url+"\">";
st += "</td>";
sOutput = st + "<td>" + sOutput + "</td></tr></table>";
}
}
else if (show_headers_level==3) {
// show raw headers
if (msg->header().fetch_raw()) {
sOutput=msg->header().m_raw;
sOutput.replace("<", "<");
sOutput.replace(">", ">");
sOutput.replace("\n", "<br>");
sOutput.replace(" ", " ");
}
}
else if (show_headers_level==4) {
// show decoded headers from raw source
if (msg->header().fetch_raw()) {
QStringList list=msg->header().m_raw.split(QRegExp("\n\\s*"));
QStringList::Iterator it = list.begin();
while (it != list.end()) {
QString sline=*it;
mail_header::decode_line(sline);
sline.replace("<", "<");
sline.replace(">", ">");
sline.replace(" ", " ");
sOutput.append(sline);
sOutput.append("<br>");
++it;
}
}
}
else {
msg->header().format(/*show_headers_level,*/ sOutput, h);
}
// remove the last <br>
if (sOutput.endsWith("<br>"))
sOutput.truncate(sOutput.length()-4);
return sOutput;
}
QString
mail_displayer::text_body_to_html(const QString &b, const display_prefs& prefs)
{
int startline=0;
int endline;
QString b2;
int quoted_lines=0;
static const char* quoted_text_font="<font color=\"#5ca730\">";
static const char* quoted_ellipsis_font="<font color=\"#4d8b28\">";
// number of lines always shown at the beginning of a quoted part
const int quoted_text_lines_shown=3;
bool last_folded_line_was_newline=false;
do {
endline = b.indexOf ('\n', startline);
if (endline<0) {
endline = b.length();
}
if (prefs.m_hide_quoted>0) {
/* Reduce portions of quoted text.
Empty lines are assumed to be part of a quote block */
if (b.at(startline)=='>' || (quoted_lines>0 && b.at(startline)=='\n')) {
if (++quoted_lines <= quoted_text_lines_shown) {
QString q=quoted_text_font;
q.append(expand_body_line(b.mid(startline, endline-startline), prefs));
q.append("</font>");
q.append("<br>");
b2.append(q);
last_folded_line_was_newline=false; // we're not folding yet
}
else {
last_folded_line_was_newline = (b.at(startline)=='\n');
}
}
else if (quoted_lines) {
if (quoted_lines>quoted_text_lines_shown) {
if (last_folded_line_was_newline)
quoted_lines--; // don't count an ending empty line
if (quoted_lines-quoted_text_lines_shown>0) {
b2.append(quoted_ellipsis_font);
b2.append("<b>> ");
b2.append(QObject::tr("[Collapsed quoted text (%1 lines)]").arg(quoted_lines-quoted_text_lines_shown));
b2.append("</b></font><br>");
}
if (last_folded_line_was_newline)
b2.append("<br>");
}
quoted_lines=0;
}
// end of quoted text processing
}
if (!quoted_lines) {
b2.append(expand_body_line(b.mid(startline, endline-startline), prefs));
b2.append("<br>");
}
startline = endline+1;
} while (startline < b.length());
// FIXME: if a block of quoted text finishes the body, the [Hidden quoted]
// line is zapped. The right fix is to move the whole expansion of
// quoted text in a separate function:
// startline = expand_quoted_text(&body_text, startline, &html_quoted_text);
return b2;
}
// [static] Return an html-displayable form of the string
QString&
mail_displayer::htmlize(QString s)
{
return s.replace("<", "<").replace(">", ">");
}
/* Format the traces of the filter execution in an HTML string.
Filters that have been modified or deleted since the time when they've been
applied to the message are indicated as such.
*/
QString
mail_displayer::format_filters_trace(const filter_log_list& list)
{
if (list.isEmpty())
return QString();
QString r = "<font color=\"brown\">" + QObject::tr("Filtered by:") + " </font>";
bool show_numbers=(list.count()>1);
for (int i=0; i<list.count(); i++) {
const filter_log_entry e = list.at(i);
if (i>0)
r.append("<br> ");
if (show_numbers)
r.append(QString("%1. ").arg(i+1));
QString name = e.filter_name();
if (!name.isEmpty()) {
r.append(QString(" [%1] ").arg(htmlize(name)));
}
else {
r.append(QString(" [#%1] ").arg(e.filter_expr_id()));
if (e.m_deleted) {
r.append("<i>" + QObject::tr("(filter deleted)") + "</i>");
}
}
if (!e.m_deleted) {
r.append("<i>");
r.append(e.m_modified ? QObject::tr("(filter modified)") : htmlize(e.filter_expression()));
r.append("</i>");
}
}
return r;
}
QString
mail_displayer::sprint_additional_headers(const display_prefs& prefs,
mail_msg* msg)
{
QString h;
// Insert tag names
if (prefs.m_show_tags_in_headers) {
std::list<uint>& tags = msg->get_tags();
QStringList tag_names = tags_repository::names_list(tags);
if (!tag_names.isEmpty()) {
h.append(QString("<br><font color=\"brown\">" + QObject::tr("Tags:") + " </font>"));
for (int i=0; i<tag_names.size(); i++) {
tag_names[i] = "<b>"+ htmlize(tag_names[i]) + "</b>";
}
h.append(tag_names.join(" | "));
}
}
// Insert filters log if enabled
if (prefs.m_show_filters_trace) {
filter_log_list lli;
if (lli.fetch(msg->get_id())) {
QString fh = format_filters_trace(lli);
if (!fh.isEmpty()) {
h.append("<br>");
h.append(fh);
}
}
}
return h;
}
void
display_prefs::init()
{
m_show_headers_level = 1;
m_show_tags = get_config().get_bool("show_tags");
m_threaded = get_config().get_bool("display_threads");
m_wrap_lines = true;
m_hide_quoted = 0;
m_clickable_urls=get_config().get_bool("body_clickable_urls", true);
m_show_filters_trace = get_config().get_bool("display/filters_trace");
m_show_tags_in_headers = get_config().get_bool("display/tags_in_headers", true);
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files