Manitou-Mail logo title

Source file: src/message_view.cpp

/* Copyright (C) 2004-2013 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 "db.h"
#include "main.h"

#include "message_view.h"
#include "body_view.h"
#include "browser.h"
#include "mail_displayer.h"
#include "msg_list_window.h"

#include <QFrame>
#include <QWebFrame>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QMessageBox>
#include <QScrollBar>
#include <QPalette>
#include <QPrinter>
#include <QPrintDialog>
#include <QTextDocument>
#include <QVariant>
#include <QShortcut>
#include <QKeySequence>

#if QT_VERSION>=0x040600
#include <QWebElement>
#endif

message_view::message_view(QWidget* parent, msg_list_window* sub_parent) : QWidget(parent)
{
  m_pmsg=NULL;
  m_content_type_shown = 0;
  m_zoom_factor=1.0;
  m_parent = sub_parent;
  setAutoFillBackground(true);
  QPalette pal = palette();
  pal.setColor(QPalette::Active, QPalette::Background, Qt::white);
  pal.setColor(QPalette::Inactive, QPalette::Background, Qt::white);
  setPalette(pal);

  enable_page_nav(false, false);
  m_bodyv = new body_view();
  QVBoxLayout* top_layout=new QVBoxLayout(this);
  top_layout->setContentsMargins(0,0,0,0);
  top_layout->setSpacing(0);

  top_layout->addWidget(m_bodyv);

  connect(m_bodyv, SIGNAL(loadFinished(bool)), this, SLOT(load_finished(bool)));
  connect(m_bodyv, SIGNAL(external_contents_requested()), this,
	  SLOT(ask_for_external_contents()));
  connect(m_bodyv, SIGNAL(popup_body_context_menu()), SIGNAL(popup_body_context_menu()));
  if (m_parent) {
    connect(m_bodyv->page(),
	    SIGNAL(linkHovered(const QString&,const QString, const QString&)),
	    this, SLOT(display_link(const QString&)));
  }
  connect(m_bodyv, SIGNAL(linkClicked(const QUrl&)), this, SLOT(link_clicked(const QUrl&)));

  QShortcut* t = new QShortcut(QKeySequence(tr("Ctrl+A", "Select all")), this);
  t->setContext(Qt::WidgetWithChildrenShortcut);
  connect(t, SIGNAL(activated()), this, SLOT(select_all_text()));
}


void
message_view::select_all_text()
{
  m_bodyv->page()->triggerAction(QWebPage::SelectAll);
}


void
message_view::display_link(const QString& link)
{
  if (m_parent && link.indexOf("#manitou-")!=0) {
    m_parent->show_status_message(link);
  }
}

void
message_view::link_clicked(const QUrl& url)
{
  if (url.scheme()=="mailto") {
    // TODO: use more headers, particularly "body"
    mail_header header;
    if (!url.path().isEmpty())
      header.m_to=url.path();
    if (url.hasQueryItem("subject")) {
      header.m_subject=url.queryItemValue("subject");
    }
    gl_pApplication->start_new_mail(header);
  }
  else if (url.scheme().isEmpty()) {
    const QString cmd=url.toString();
    if (cmd=="#manitou-fetch") {
      allow_external_contents();
    }
    else if (cmd=="#manitou-to_text") {
      show_text_part();
    }
    else if (cmd=="#manitou-to_html") {
      show_html_part();
    }
    else if (cmd=="#manitou-show") {
      emit on_demand_show_request();
      display_commands();
    }
    else if (cmd=="#manitou-complete_load") {
      complete_body_load();
      enable_command("complete_load", false);
      display_commands();
    }    
  }
  else {
    browser::open_url(url);
  }
}

QSize
message_view::sizeHint() const
{
  return QSize(400,200);
}

message_view::~message_view()
{
}

static inline int max(int a, int b)
{
  return a>b?a:b;
}


void
message_view::keyPressEvent(QKeyEvent* event)
{
  if (event->modifiers()==0 && event->key()==Qt::Key_Space) {
    page_down();
  }
  QWidget::keyPressEvent(event);
}

void
message_view::load_finished(bool ok)
{
  if (ok) {
    DBG_PRINTF(5, "load_finished");
    m_bodyv->set_loaded(true);
    if (!m_highlight_words.empty()) {
      m_bodyv->highlight_terms(m_highlight_words);
    }
  }
  else {
    DBG_PRINTF(2, "load_finished not OK");
  }
}

void
message_view::reset_state()
{
  m_has_text_part=false;
  m_has_html_part=false;
  m_bodyv->set_loaded(false);
  m_ext_contents=false;
  m_zoom_factor=1.0;
  m_bodyv->setTextSizeMultiplier(m_zoom_factor);
  //  m_highlight_words.clear();
}

// content_type is 1 if contents come from text part, 2 if html part
void
message_view::set_html_contents(const QString& html_body, int content_type)
{
  m_ext_contents=false;
  m_bodyv->set_loaded(false);
  m_content_type_shown = content_type;
  m_bodyv->display(html_body);
}

/* Return values:
   0: not yet computed
   1: plain text
   2: html
*/
int
message_view::content_type_shown() const
{
  return m_content_type_shown;
}

void
message_view::set_mail_item (mail_msg* p)
{
  m_pmsg=p;
  m_bodyv->set_mail_item(p);
}

void
message_view::enable_page_nav(bool back, bool forward)
{
  m_can_move_back=back;
  m_can_move_forward=forward;
}

void
message_view::highlight_terms(const std::list<searched_text>& lst)
{
  if (m_bodyv->is_loaded()) {
    m_bodyv->highlight_terms(lst);
  }
  else {
    m_highlight_words=lst;
  }
}

void
message_view::page_down()
{
  QWebFrame* frame = m_bodyv->page()->mainFrame();
  if (!frame)
    return;
  QPoint pos = frame->scrollPosition();
  int y = m_bodyv->geometry().height() + pos.y();
  if (y > frame->contentsSize().height()-frame->geometry().height())
    y = frame->contentsSize().height()-frame->geometry().height();
  pos.setY(y);
  frame->setScrollPosition(pos);
}

void
message_view::set_wrap(bool b)
{
  Q_UNUSED(b);
}

/* Replace the whole display by the "fetch on demand" href link or
   toggle back to normal display */
void
message_view::set_show_on_demand(bool b)
{
  DBG_PRINTF(4, "set_show_on_demand(%d)", (int)b);
  if (b) {
    m_bodyv->clear();
    update();
  }
  else {
    if (m_parent)
      display_body(m_parent->get_display_prefs(), 0);    
  }
}

void
message_view::print()
{
  QPrinter printer;
  QPrintDialog dialog(&printer, this);
  dialog.setWindowTitle(tr("Print message"));
  if (dialog.exec() == QDialog::Accepted)
    m_bodyv->print(&printer);
}

void
message_view::setFont(const QFont& font)
{
  QWidget::setFont(font);
  m_bodyv->set_font(font);
}

void
message_view::clear()
{
  m_bodyv->clear();
}


void
message_view::copy()
{
  m_bodyv->page()->triggerAction(QWebPage::Copy);
}


void
message_view::allow_external_contents()
{
  m_bodyv->authorize_external_contents(true);
  m_ext_contents=true;
  enable_command("fetch", false);
  QString html_body = m_bodyv->page()->mainFrame()->toHtml();
  m_bodyv->page()->mainFrame()->setHtml(html_body, QUrl("/"));
  display_commands();
}

void
message_view::ask_for_external_contents()
{
  enable_command("fetch", true);
}

/*
  preferred_format: 0=from config, 1=text, 2=html
*/
void
message_view::display_body(const display_prefs& prefs, int preferred_format)
{
  if (!m_pmsg)
    return;

  if (preferred_format==0) {
    preferred_format = get_config().get_string("display/body/preferred_format").toLower()=="text" ? 1: 2;
  }

  reset_state();
  reset_commands();

  /* First we try to fetch html contents from body.bodyhtml
     If it's empty, we look for an HTML part (even if we won't load it later) */
  attachment* html_attachment=NULL;
  QString body_html = m_pmsg->get_body_html(); // from the body table
  if (body_html.isEmpty())
    html_attachment = m_pmsg->body_html_attached_part(); // from the attachments

  QString body_text = m_pmsg->get_body_text(true);

  attachments_list& attchs = m_pmsg->attachments();
  if (m_pmsg->has_attachments() > 0) {
    // Note: don't try to optimize here by removing the fetch: we need
    // it later for a lot of reasons, and the result is cached.
    attchs.fetch();
  }

  if (preferred_format==1) {
    if (body_text.isEmpty()) {
      if (attchs.size() > 0) {
	attachments_list::iterator iter;
	for (iter=attchs.begin(); iter!=attchs.end(); iter++) {
	  if (iter->filename().isEmpty() && iter->mime_type()=="text/plain") {
	    iter->append_decoded_contents(body_text);
	  }
	}
      }
    }
  }
  else {
    // preferred display format=html
    if (html_attachment && body_html.isEmpty()) {
      html_attachment->append_decoded_contents(body_html);
    }
  }

  // Format headers
  mail_displayer disp(this);
  QString h = QString("<div id=\"manitou-header\">%1%2%3</div><p>")
    .arg(disp.sprint_headers(prefs.m_show_headers_level, m_pmsg))
    .arg(disp.sprint_additional_headers(prefs, m_pmsg))
    .arg(QString("<div id=\"manitou-commands\"></div>"));

  set_wrap(prefs.m_wrap_lines);

  m_has_text_part = !body_text.isEmpty();
  m_has_html_part = (html_attachment!=NULL || !body_html.isEmpty());

  if (preferred_format==1 || body_html.isEmpty()) {
    QString b2 = "<html><body>";
    b2.append(h);
    b2.append("<div id=\"manitou-body\">");
    b2.append(disp.text_body_to_html(body_text, prefs));
    b2.append("</div>");
    b2.append("</body></html>");

    set_html_contents(b2, 1);
    // partial load?
    if (m_pmsg->body_length()>0 && m_pmsg->body_fetched_length() < m_pmsg->body_length()) {
      if (m_parent) {
	enable_command("complete_load", true);
	QString msg = QString(tr("Partial load (%1%)")).arg(m_pmsg->body_fetched_length()*100/ m_pmsg->body_length());
	m_parent->blip_status_message(msg);
      }
    }
    if (m_parent) {
      enable_command("to_html", m_has_html_part);
    }
  }
  else {
    body_html.prepend("<div id=\"manitou-body\">");
    body_html.append("</div>");
    set_html_contents(body_html, 2);
    prepend_body_fragment(h);
    if (m_parent)
      enable_command("to_text", m_has_text_part);
  }
  display_commands();
}

void
message_view::prepend_body_fragment(const QString& fragment)
{
  QWebPage* page = m_bodyv->page();
  page->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
  QString s = fragment;
  s.replace("'", "\\'");
  s.replace("\n", "\\n");
  QString js = QString("try {var b=document.getElementsByTagName('body')[0]; var p=document.createElement('div'); p.innerHTML='%1'; b.insertBefore(p, b.firstChild); 1;} catch(e) { e; }").arg(s);
  QVariant v = page->mainFrame()->evaluateJavaScript(js);
  page->settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
}

void
message_view::reset_commands()
{
  m_enabled_commands.clear();
}

void
message_view::display_commands()
{
  QWebPage* page = m_bodyv->page();
  page->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
  QString s = command_links();
  s.replace("'", "\\'");
  QString js = QString("try {var p=document.getElementById(\"manitou-commands\"); p.innerHTML='%1'; 1;} catch(e) { e; }").arg(s);
  QVariant v = page->mainFrame()->evaluateJavaScript(js);
  page->settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
}

void
message_view::enable_command(const QString command, bool enable)
{
  m_enabled_commands.insert(command, enable);
/*  display_commands();*/
}

QString
message_view::command_links()
{
  static const char* commands[] = {
    "fetch", QT_TR_NOOP("Fetch external contents"),
    "to_text", QT_TR_NOOP("Show text part"),
    "to_html", QT_TR_NOOP("Show HTML part"),
    "complete_load", QT_TR_NOOP("Complete load")
  };

  QString line;
  for (uint i=0; i<sizeof(commands)/sizeof(commands[0]); i+=2) {
    if (m_enabled_commands.contains(commands[i])) {
      const QString cmd=commands[i];
      if (m_enabled_commands.value(cmd))
	line.append(QString(" &nbsp;&nbsp;&nbsp;<a id=\"%1\" href=\"#manitou-%1\">%2</a>").arg(cmd).arg(tr(commands[i+1])));
    }
  }
  return line;
}

void
message_view::show_text_part()
{
  if (m_parent)
    display_body(m_parent->get_display_prefs(), 1);
  enable_command("to_html", m_has_html_part);
  enable_command("to_text", false);
  display_commands();
}

void
message_view::show_html_part()
{
  if (m_parent)
    display_body(m_parent->get_display_prefs(), 2);
  enable_command("to_text", m_has_text_part);
  enable_command("to_html", false);
  display_commands();
}

void
message_view::change_zoom(int direction)
{
  if (direction>0)
    m_zoom_factor = m_zoom_factor*1.3;
  else if (direction<0)
    m_zoom_factor = m_zoom_factor/1.3;
  else
    m_zoom_factor=1.0;

  m_bodyv->setTextSizeMultiplier(m_zoom_factor);
}

void
message_view::complete_body_load()
{
  m_pmsg->fetch_body_text(false);
  show_text_part();
}

QString
message_view::selected_text() const
{
  return m_bodyv->selectedText();
}

QString
message_view::selected_html_fragment()
{
  // TODO: check for the selection
#if QT_VERSION<0x040600
  QVariant res = m_bodyv->page()->mainFrame()->evaluateJavaScript("document.getElementsById('manitou-body').innerHTML");
  return res.toString();
#else
  QWebElement elt = m_bodyv->page()->mainFrame()->findFirstElement("div#manitou-body");
  if (!elt.isNull()) {
    return elt.toOuterXml();
  }
  else
    return "";
#endif
}

QString
message_view::body_as_text()
{
#if QT_VERSION<0x040600
  QVariant res = m_bodyv->page()->mainFrame()->evaluateJavaScript("document.getElementsById('manitou-body').innerHTML");
  return res.toString();
#else
  QWebElement elt = m_bodyv->page()->mainFrame()->findFirstElement("div#manitou-body");
  if (!elt.isNull()) {
    return elt.toPlainText();
  }
  else
    return "";
#endif
}

HTML source code generated by GNU Source-Highlight plus some custom post-processing

List of all available source files