Source file: src/mailing_wizard.cpp
/* Copyright (C) 2004-2011 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 "main.h"
#include "mailing.h"
#include "mailing_wizard.h"
#include "mail_template.h"
#include "addresses.h"
#include "identities.h"
#include "text_merger.h"
#include "mailing_viewer.h"
#include <QDebug>
#include <QComboBox>
#include <QBoxLayout>
#include <QGridLayout>
#include <QRadioButton>
#include <QPushButton>
#include <QLineEdit>
#include <QListWidget>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QLabel>
#include <QMessageBox>
#include <QFileDialog>
#include <QFile>
#include <QFont>
#include <QCheckBox>
#include <QTextCodec>
#include <QProgressBar>
#include <QWebPage>
#include <QWebFrame>
#include <QGroupBox>
#include <QButtonGroup>
void
mailing_wizard_page::cleanupPage()
{
/* called on pressing the Back button.
The base function resets the fields to the values they had before initializePage()
We don't want that so we don't call it
*/
}
mailing_wizard_page_title::mailing_wizard_page_title()
{
setTitle(tr("Basic mailing information"));
setSubTitle(tr("Choose the title, subject and sender's identity"));
int row=0;
QGridLayout* layout = new QGridLayout(this);
m_title_widget = new QLineEdit;
registerField("mailing_title*", m_title_widget);
layout->addWidget(new QLabel(tr("Title:")), row, 0);
layout->addWidget(m_title_widget, row, 1);
QLabel* title_subtext = new QLabel(tr("The title is used internally to refer to the mailing. It does not appear in the messages sent."));
connect(m_title_widget, SIGNAL(textChanged(const QString&)), this, SIGNAL(completeChanged()));
QFont mini_font = title_subtext->font();
mini_font.setPointSize((mini_font.pointSize()*8)/10);
title_subtext->setFont(mini_font);
row++;
layout->addWidget(title_subtext, row, 1);
row++;
layout->addWidget(new QLabel(tr("Sender:")), row, 0);
m_cb_sender = new QComboBox;
layout->addWidget(m_cb_sender, row, 1);
QLabel* sender_subtext = new QLabel(tr("These addresses are defined in Preferences->Identities."));
sender_subtext->setFont(mini_font);
row++;
layout->addWidget(sender_subtext, row, 1);
if (m_identities.fetch()) {
identities::iterator iter;
for (iter = m_identities.begin(); iter != m_identities.end(); ++iter) {
mail_identity* p = &iter->second;
mail_address addr;
addr.set_name(p->m_name);
addr.set_email(p->m_email_addr);
m_cb_sender->addItem(addr.email_and_name(), QVariant(p->m_email_addr));
}
}
row++;
layout->addWidget(new QLabel(tr("Subject:")), row, 0);
QLineEdit* subject = new QLineEdit;
connect(subject, SIGNAL(textChanged(const QString&)), this, SIGNAL(completeChanged()));
layout->addWidget(subject, row, 1);
registerField("subject", subject);
QLabel* subject_subtext = new QLabel(tr("Placeholders written as {{variable-name}} may be used for variable text."));
subject_subtext->setFont(mini_font);
row++;
layout->addWidget(subject_subtext, row, 1);
}
void
mailing_wizard_page_title::initializePage()
{
// m_title_widget->setText(get_options()->title);
}
bool
mailing_wizard_page_title::isComplete() const
{
if (field("mailing_title").toString().isEmpty())
return false;
if (m_cb_sender->currentIndex() < 0)
return false;
if (field("subject").toString().isEmpty())
return false;
return true;
}
mail_address
mailing_wizard_page_title::sender_address()
{
mail_address addr;
identities::iterator iter;
int idx = m_cb_sender->currentIndex();
if (idx >= 0) {
QString email = m_cb_sender->itemData(idx).toString();
for (iter = m_identities.begin(); iter != m_identities.end(); ++iter) {
mail_identity* p = &iter->second;
if (email == p->m_email_addr) {
addr.set_name(p->m_name);
addr.set_email(p->m_email_addr);
return addr;
}
}
}
return addr;
}
mailing_wizard_page_import_data::mailing_wizard_page_import_data()
{
setTitle(tr("Recipient email addresses and merge data"));
setSubTitle(tr("Choose how you want to submit the data"));
m_rb[0] = new QRadioButton(tr("Import the list of recipients email addresses from a text file"));
m_rb[0]->setChecked(true);
m_rb[1] = new QRadioButton(tr("Import multi-column data in CSV format with column headers"));
m_rb[2] = new QRadioButton(tr("Insert the list of recipients email addresses into a text area in the next page"));
QVBoxLayout* layout = new QVBoxLayout(this);
for (int i=0; i<3; i++) {
layout->addWidget(m_rb[i]);
}
registerField("addresses_from_file", m_rb[0]);
registerField("mailmerge_from_file", m_rb[1]);
registerField("addresses_from_textarea", m_rb[2]);
// label for the list of template variables
m_label_variables = new QLabel();
m_label_variables->setWordWrap(true);
layout->addWidget(m_label_variables);
}
void
mailing_wizard_page_import_data::initializePage()
{
mailing_options* options = get_options();
QSet<QString> variables;
text_merger::extract_variables(options->text_part, variables);
text_merger::extract_variables(options->html_part, variables);
text_merger::extract_variables(field("subject").toString(), variables);
if (!variables.isEmpty()) {
QString txt = "<b>"+tr("Variables found in template(s):")+"</b>";
QStringList list = variables.toList();
qSort(list);
txt.append("<br>");
txt.append(list.join(", "));
/*
for (it=list.constBegin(); it!=list.constEnd(); ++it) {
txt.append(QString(" %1").arg(*it));
}
*/
m_label_variables->setText(txt);
m_rb[1]->setChecked(true);
}
else
m_label_variables->setText("");
}
int
mailing_wizard_page_import_data::nextId() const
{
if (m_rb[0]->isChecked() || m_rb[1]->isChecked()) {
return mailing_wizard::page_data_file;
}
else
return mailing_wizard::page_paste_address_list;
}
mailing_wizard_page_paste_address_list::mailing_wizard_page_paste_address_list()
{
setTitle(tr("Insert the recipients email addresses"));
setSubTitle(tr("Each line in the list will be taken as the To: field of a new independant message."));
QVBoxLayout* layout = new QVBoxLayout(this);
m_textarea = new QPlainTextEdit;
layout->addWidget(m_textarea);
}
QStringList
mailing_wizard_page_paste_address_list::get_addresses()
{
return m_textarea->toPlainText().split("\n", QString::KeepEmptyParts);
}
int
mailing_wizard_page_paste_address_list::nextId() const
{
return mailing_wizard::page_parse_data;
}
mailing_wizard_page_data_file::mailing_wizard_page_data_file()
{
setTitle(tr("Data file"));
QVBoxLayout* layout = new QVBoxLayout(this);
QHBoxLayout* ll = new QHBoxLayout;
QLabel* lfile = new QLabel(tr("Filename:"));
ll->addWidget(lfile);
m_filename = new QLineEdit;
ll->addWidget(m_filename);
registerField("addresses_filename*", m_filename);
QPushButton* btn = new QPushButton(tr("Browse"));
ll->addWidget(btn);
layout->addLayout(ll);
connect(btn, SIGNAL(clicked()), this, SLOT(browse()));
QString csv_txt = tr("<b>Rules:</b>");
csv_txt.append("<br>");
csv_txt.append(tr("The first record of the file must contain the column names."));
csv_txt.append("<br>");
csv_txt.append(tr("The recipient email address must be placed at the first column."));
csv_txt.append("<br>");
csv_txt.append(tr("The separator character between fields can be a tab, a comma or a semicolon."));
csv_txt.append("<br>");
csv_txt.append(tr("The file must be encoded in the current locale."));
m_label_csv_rules = new QLabel(csv_txt);
layout->addWidget(m_label_csv_rules);
m_label_csv_rules->hide();
}
void
mailing_wizard_page_data_file::initializePage()
{
if (field("mailmerge_from_file").toBool()) {
setSubTitle(tr("Please specify the location of the data file in CSV format"));
m_label_csv_rules->show();
}
else {
setSubTitle(tr("Please specify the location of the data file containing the recipient email addresses"));
m_label_csv_rules->hide();
}
}
void
mailing_wizard_page_data_file::browse()
{
QString s = QFileDialog::getOpenFileName(this);
if (!s.isEmpty())
m_filename->setText(s);
}
bool
mailing_wizard_page_data_file::validatePage()
{
QString path = m_filename->text();
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString errmsg = QIODevice::tr(f.errorString().toAscii().constData());
QMessageBox::critical(NULL, tr("Error"), tr("Can't open file '%2': %1").arg(errmsg).arg(path));
return false;
}
return true;
}
int
mailing_wizard_page_data_file::nextId() const
{
return mailing_wizard::page_parse_data;
}
mailing_wizard_page_parse_data::mailing_wizard_page_parse_data()
{
m_nb_lines=0;
m_nb_addresses=0;
m_nb_errors=0;
QVBoxLayout* layout = new QVBoxLayout(this);
m_result = new QLabel;
m_result->setTextFormat(Qt::RichText);
layout->addWidget(m_result);
layout->addStretch(1);
m_errors_label = new QLabel(tr("Parse errors:"));
layout->addWidget(m_errors_label);
m_errors_textarea = new QPlainTextEdit;
QFont font;
font.setStyleHint(QFont::TypeWriter);
font.setPointSize(10);
m_errors_textarea->setFont(font);
m_errors_textarea->setLineWrapMode(QPlainTextEdit::NoWrap);
m_errors_textarea->setReadOnly(true);
layout->addWidget(m_errors_textarea);
m_stop_text = new QLabel;
layout->addWidget(m_stop_text);
m_ignore_errors_checkbox = new QCheckBox();
connect(m_ignore_errors_checkbox, SIGNAL(stateChanged(int)), this, SLOT(ignore_error_state_changed(int)));
m_ignore_errors_text = new QLabel(tr("Discard the invalid lines and continue"));
QHBoxLayout* hb = new QHBoxLayout();
layout->addLayout(hb);
hb->setMargin(2);
hb->addWidget(m_ignore_errors_checkbox);
hb->addWidget(m_ignore_errors_text);
hb->addStretch(1);
m_ignore_errors_checkbox->setVisible(false);
m_ignore_errors_text->setVisible(false);
}
void
mailing_wizard_page_parse_data::ignore_error_state_changed(int state)
{
Q_UNUSED(state);
emit completeChanged();
}
void
mailing_wizard_page_parse_data::initializePage()
{
if (field("addresses_from_textarea").toBool()) {
setTitle(tr("Addresses syntax check"));
}
else {
setTitle(tr("Data file check"));
}
setSubTitle(tr("Result of the analysis of the data"));
m_nb_lines=0;
m_nb_addresses=0;
m_nb_errors=0;
m_error_lines.clear();
if (field("addresses_from_file").toBool() || field("mailmerge_from_file").toBool()) {
QString path = field("addresses_filename").toString();
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(NULL, tr("Error"), tr("Can't open file '%2': error %1").arg(f.error()).arg(path));
m_file_checked=false;
}
else {
if (field("addresses_from_file").toBool()) {
check_addresses_file(f);
}
else if (field("mailmerge_from_file").toBool()) {
check_csv_file(f);
}
m_file_checked=true;
f.close();
}
}
else if (field("addresses_from_textarea").toBool()) {
mailing_wizard* wz = static_cast<mailing_wizard*>(wizard());
QStringList alist = wz->get_pasted_addresses();
check_pasted_addresses(alist);
m_file_checked=true;
}
QString result = tr("Number of lines: %1").arg(m_nb_lines) + "<br>" + tr("Number of email addresses: %1").arg(m_nb_addresses) + "<br>";
if (m_recipient_list.isEmpty())
result.append(tr("<b>Error:</b> no valid address has been found"));
else {
if (m_nb_errors>0)
result.append(tr("<b>Number of errors:</b> <font color=red>%1</font>").arg(m_nb_errors));
else
result.append(tr("Number of errors: %1").arg(m_nb_errors));
}
if (field("mailmerge_from_file").toBool()) {
// for CSV file, append the names of columns
result.append("<p><b>" + tr("Column names:") + "</b><br>");
mailing_options* opt = get_options();
result.append(" ");
result.append(opt->field_names.join("<br> "));
}
m_result->setText("<p>"+result);
m_errors_textarea->clear();
if (m_error_lines.size()>0) {
for (int i=0; i<m_error_lines.size(); i++) {
m_errors_textarea->appendPlainText(m_error_lines.at(i));
}
m_errors_textarea->show();
m_errors_textarea->verticalScrollBar()->setValue(0);
m_errors_label->show();
}
else {
m_errors_textarea->hide();
m_errors_label->hide();
}
if (m_nb_errors>0) {
m_stop_text->setVisible(true);
if (field("addresses_from_textarea").toBool())
m_stop_text->setText(tr("The list of addresses is rejected. Please go back to the previous page and fix the errors."));
else
m_stop_text->setText(tr("The data file is rejected. You may go back to the previous page and submit a fixed data file."));
if (m_nb_addresses > 0) {
m_ignore_errors_checkbox->setVisible(true);
m_ignore_errors_text->setVisible(true);
}
}
else {
m_stop_text->setVisible(false);
m_ignore_errors_checkbox->setVisible(false);
m_ignore_errors_text->setVisible(false);
}
emit completeChanged();
}
void
mailing_wizard_page_parse_data::check_csv_file(QFile& f)
{
m_nb_lines=0;
m_nb_addresses=0;
m_nb_errors=0;
m_error_lines.clear();
m_recipient_list.clear();
mailing_options *opt = get_options();
opt->csv_data.clear();
text_merger tm;
try {
tm.init(&f);
}
catch(QString error) {
m_error_lines.append(error);
}
QString email_field_name = tm.column_name(0);
// TODO check if email_field_name is empty?
QString tmpl = QString("{%1}").arg(email_field_name);
QString email;
opt->field_names = tm.column_names();
while (!f.atEnd()) {
try {
QStringList fields_data = tm.collect_data(&f);
if (fields_data.isEmpty())
continue;
// discard empty lines (LF or CRLF alone). This is mostly useful
// if encountered at the end
if (fields_data.size()==1 && fields_data.at(0).isEmpty())
continue;
if (fields_data.size() != tm.nb_columns()) {
throw QObject::tr("line %3: %1 columns found where %2 are expected").arg(fields_data.size()).arg(tm.nb_columns()).arg(tm.nb_lines());
}
email = fields_data.at(0);
fields_data.removeFirst();
// The first element of contents is the email address
QStringList recipients; // may be filled with several addresses
bool res=parse_line(email, recipients);
if (!res || recipients.size()==0) {
m_nb_errors++;
m_error_lines << tr("line %1: %2").arg(tm.nb_lines()).arg(email);
}
else {
m_recipient_list.append(recipients.join(","));
m_nb_addresses += recipients.size();
opt->csv_data.append(fields_data);
}
}
catch(QString error) {
m_error_lines.append(error);
m_nb_errors++;
}
}
m_nb_lines = tm.nb_lines();
}
QStringList&
mailing_wizard_page_parse_data::recipient_list()
{
return m_recipient_list;
}
//static
bool
mailing_wizard_page_parse_data::parse_line(const QString ql, QStringList& recipients)
{
std::list<QString> addresses;
std::list<QString> comments;
/* extract the addresses (multiple addresses separated by commas are accepted)
without much syntax checking */
int r=mail_address::ExtractAddresses(ql, addresses, comments);
if (r==0) { // success
/* do basic syntax checking on each address */
for (std::list<QString>::iterator it = addresses.begin(); it!=addresses.end(); ++it) {
if (!mail_address::basic_syntax_check(*it)) {
r=1;
break;
}
else {
recipients.append(*it);
}
}
}
return r==0;
}
void
mailing_wizard_page_parse_data::check_addresses_file(QFile& f)
{
m_nb_lines=0;
m_nb_addresses=0;
m_nb_errors=0;
m_error_lines.clear();
m_recipient_list.clear();
QTextCodec* codec = QTextCodec::codecForLocale();
while (!f.atEnd()) {
m_nb_lines++;
QByteArray line = f.readLine();
QString ql = codec ? codec->toUnicode(line).trimmed() :
QString::fromLatin1(line).trimmed();
if (ql.isEmpty())
continue;
QStringList recipients; // may be filled with several addresses
bool res=parse_line(ql, recipients);
if (!res || recipients.size()==0) {
m_nb_errors++;
m_error_lines << tr("line %1: %2").arg(m_nb_lines).arg(ql);
}
else {
m_recipient_list.append(recipients.join(","));
m_nb_addresses += recipients.size();
}
}
}
void
mailing_wizard_page_parse_data::check_pasted_addresses(const QStringList& alist)
{
m_nb_lines=0;
m_nb_addresses=0;
m_nb_errors=0;
m_error_lines.clear();
m_recipient_list.clear();
for (QStringList::const_iterator iter = alist.constBegin(); iter!=alist.end(); ++iter) {
QString ql = (*iter).trimmed();
m_nb_lines++;
if (ql.isEmpty())
continue;
QStringList recipients; // may be filled with several addresses
bool res=parse_line(ql, recipients);
if (!res || recipients.size()==0) {
m_nb_errors++;
m_error_lines << tr("line %1: %2").arg(m_nb_lines).arg(ql);
}
else {
m_recipient_list.append(recipients.join(","));
m_nb_addresses += recipients.size();
}
}
}
bool
mailing_wizard_page_parse_data::validatePage()
{
return (m_nb_errors==0 || m_ignore_errors_checkbox->isChecked())
&& m_file_checked && m_recipient_list.size()>0;
}
bool
mailing_wizard_page_parse_data::isComplete() const
{
return mailing_wizard_page::isComplete() &&
(m_nb_errors==0 || m_ignore_errors_checkbox->isChecked()) &&
m_file_checked &&
m_recipient_list.size()>0;
}
int
mailing_wizard_page_parse_data::nextId() const
{
return mailing_wizard::page_final;
}
double_file_input::double_file_input(QWidget* parent) : QWidget(parent)
{
QGridLayout* grid = new QGridLayout(this);
m_label_html = new QLabel(tr("File for html part:"));
grid->addWidget(m_label_html, 0, 0); // row,column
m_filename_html = new QLineEdit;
grid->addWidget(m_filename_html, 0, 1);
m_btn_browse1 = new QPushButton(tr("Browse"));
grid->addWidget(m_btn_browse1, 0, 2);
connect(m_btn_browse1, SIGNAL(clicked()), this, SLOT(browse_html_file()));
m_label_text = new QLabel(tr("File for text part:"));
grid->addWidget(m_label_text, 1, 0);
m_filename_text = new QLineEdit;
grid->addWidget(m_filename_text, 1, 1);
m_btn_browse2 = new QPushButton(tr("Browse"));
grid->addWidget(m_btn_browse2, 1, 2);
m_label_filename_text = new QLabel(tr("Leave the above field blank to auto-generate the text part from the html part"));
QFont font = m_label_filename_text->font();
font.setPointSize((font.pointSize()*8)/10);
m_label_filename_text->setFont(font);
grid->addWidget(m_label_filename_text, 3, 1);
connect(m_btn_browse2, SIGNAL(clicked()), this, SLOT(browse_text_file()));
grid->setColumnStretch(0,0); // label
grid->setColumnStretch(1,1); // lineedit
grid->setColumnStretch(2,0); // button
}
/* adjust the visibility of html and/or text file lineedits according to
the mail format the is currently selected */
void
double_file_input::format_requested(mailing_db::template_format format)
{
switch(format) {
case mailing_db::format_text_plain:
m_filename_text->show();
m_label_text->show();
m_btn_browse2->show();
m_filename_html->hide();
m_label_filename_text->hide();
m_btn_browse1->hide();
m_label_html->hide();
break;
case mailing_db::format_html_and_text:
m_filename_text->show();
m_label_text->show();
m_btn_browse2->show();
m_filename_html->show();
m_label_filename_text->show();
m_btn_browse1->show();
m_label_html->show();
break;
case mailing_db::format_html_only:
m_filename_text->hide();
m_label_text->hide();
m_btn_browse2->hide();
m_filename_html->show();
m_label_filename_text->hide();
m_btn_browse1->show();
m_label_html->show();
break;
}
}
/* return values:
0= nothing submitted
1=only filename for html is submitted
2=only filename for text is submitted
3=html and text filenames are both submitted
*/
int
double_file_input::submitted_files()
{
if (m_filename_html->text().isEmpty())
return (m_filename_text->text().isEmpty() ? 0 : 2);
else
return (m_filename_text->text().isEmpty() ? 1 : 3);
}
/* display an error and return false if one specified file can't be opened
arg. 'which' is 1=html and text, 2=html only, 3=text only */
bool
double_file_input::check_files(int which)
{
if ((which==1 || which==2) &&
!check_file_existence(m_filename_html->text()))
return false;
return check_file_existence(m_filename_text->text());
}
bool
double_file_input::check_file_existence(const QString path)
{
if (!path.isEmpty()) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString errmsg = QIODevice::tr(f.errorString().toAscii().constData());
QMessageBox::critical(NULL, tr("Error"), tr("Can't open file '%2': %1").arg(errmsg).arg(path));
return false;
}
}
return true;
}
void
double_file_input::browse_html_file()
{
QString filter = tr("Html Files (*.htm *.html);;All Files (*)");
QString s = QFileDialog::getOpenFileName(this, tr("Locate HTML file"), QString(), filter);
if (!s.isEmpty())
m_filename_html->setText(s);
}
void
double_file_input::browse_text_file()
{
QString filter = tr("Text Files (*.txt);;All Files (*)");
QString s = QFileDialog::getOpenFileName(this, tr("Locate text file"), QString(), filter);
if (!s.isEmpty())
m_filename_text->setText(s);
}
mailing_wizard_page_template::mailing_wizard_page_template()
{
setTitle(tr("Choose a template"));
setSubTitle(tr("The template is the body of the message, with optional placeholders for variable text"));
QVBoxLayout* layout = new QVBoxLayout(this);
QGroupBox* grb = new QGroupBox(tr("Message format"));
QButtonGroup* grp = new QButtonGroup;
QVBoxLayout* blayout = new QVBoxLayout();
m_rb[0] = new QRadioButton(tr("Html (rich text) and alternate plain text part"));
m_rb[0]->setChecked(true);
m_format = mailing_db::format_html_and_text;
grp->addButton(m_rb[0], (int)mailing_db::format_html_and_text);
blayout->addWidget(m_rb[0]);
m_rb[1] = new QRadioButton(tr("Html (rich text) only"));
grp->addButton(m_rb[1], (int)mailing_db::format_html_only);
blayout->addWidget(m_rb[1]);
m_rb[2] = new QRadioButton(tr("Plain text only"));
grp->addButton(m_rb[2], (int)mailing_db::format_text_plain);
blayout->addWidget(m_rb[2]);
grb->setLayout(blayout);
grp->setExclusive(true);
connect(grp, SIGNAL(buttonClicked(int)), this, SLOT(format_chosen(int)));
layout->addWidget(grb);
layout->setStretchFactor(grb, 0);
QGroupBox* grb1 = new QGroupBox(tr("Message body (template)"));
QButtonGroup* grp1 = new QButtonGroup;
m_rb_file_template = new QRadioButton(tr("From file(s)"));
m_rb_database_template = new QRadioButton(tr("From a saved message template"));
m_rb_previous_mailing_template = new QRadioButton(tr("Copy from a previous mailing"));
grp1->addButton(m_rb_file_template, 1);
grp1->addButton(m_rb_database_template, 2);
grp1->addButton(m_rb_previous_mailing_template, 3);
QVBoxLayout* bl1 = new QVBoxLayout();
bl1->addWidget(m_rb_file_template);
bl1->addWidget(m_rb_database_template);
bl1->addWidget(m_rb_previous_mailing_template);
grb1->setLayout(bl1);
grp1->setExclusive(true);
m_rb_file_template->setChecked(true);
connect(grp1, SIGNAL(buttonClicked(int)), this, SLOT(template_source_chosen(int)));
layout->addWidget(grb1);
layout->setStretchFactor(grb1, 0);
QWidget* container = new QWidget;
QVBoxLayout* sub_layout = new QVBoxLayout(container);
layout->addWidget(container);
layout->setStretchFactor(container, 1);
m_wpart_files = new double_file_input;
sub_layout->addWidget(m_wpart_files);
// list of template titles with the template ID as user data
m_label_template = new QLabel(tr("Stored templates"));
sub_layout->addWidget(m_label_template);
m_wtmpl = new QListWidget;
sub_layout->addWidget(m_wtmpl);
mail_template_list mtl;
mtl.fetch_titles();
for (int i=0; i<mtl.size(); i++) {
m_wtmpl->addItem(mtl.at(i).m_title);
QListWidgetItem* item = m_wtmpl->item(i);
item->setData(Qt::UserRole, QVariant(mtl.at(i).m_template_id));
}
registerField("db_template", m_wtmpl);
// list of previous mailings
m_label_mailings = new QLabel(tr("Previous mailings"));
sub_layout->addWidget(m_label_mailings);
m_wmailings = new QListWidget;
sub_layout->addWidget(m_wmailings);
mailings mls;
if (mls.load()) {
for (int i=0; i<mls.size(); i++) {
m_wmailings->addItem(mls.at(i).title());
QListWidgetItem* item = m_wmailings->item(i);
item->setData(Qt::UserRole, QVariant(mls.at(i).id()));
}
}
registerField("previous_mailing", m_wmailings);
layout->addStretch(5);
template_source_chosen(1);
}
void
mailing_wizard_page_template::hide_all()
{
QWidget* widgets[] = {m_wpart_files, m_wtmpl, m_wmailings, m_label_template,
m_label_mailings };
for (uint i=0; i<sizeof(widgets)/sizeof(widgets[0]); i++) {
widgets[i]->setVisible(false);
}
}
void
mailing_wizard_page_template::format_chosen(int btn_id)
{
m_format = static_cast<mailing_db::template_format>(btn_id);
m_wpart_files->format_requested(m_format);
}
void
mailing_wizard_page_template::template_source_chosen(int btn_id)
{
hide_all();
switch(btn_id) {
case 1:
m_wpart_files->setVisible(true);
break;
case 2:
m_label_template->setVisible(true);
m_wtmpl->setVisible(true);
break;
case 3:
m_label_mailings->setVisible(true);
m_wmailings->setVisible(true);
break;
}
m_template_source=(tmpl_template_source)btn_id;
}
int
mailing_wizard_page_template::selected_template_id()
{
QListWidgetItem* item = m_wtmpl->currentItem();
return item ? item->data(Qt::UserRole).toInt() : 0;
}
int
mailing_wizard_page_template::selected_mailing_id()
{
QListWidgetItem* item = m_wmailings->currentItem();
return item ? item->data(Qt::UserRole).toInt() : 0;
}
mailing_wizard_page_template::tmpl_template_source
mailing_wizard_page_template::template_source()
{
return m_template_source;
}
QString
mailing_wizard_page_template::html_template_filename()
{
return m_wpart_files->m_filename_html->text();
}
QString
mailing_wizard_page_template::text_template_filename()
{
return m_wpart_files->m_filename_text->text();
}
mailing_db::template_format
mailing_wizard_page_template::body_format() const
{
return m_format;
}
// load the HTML template from a file. In case of error, may throw a
// QString with an error message
void
mailing_wizard_page_template::load_file_templates(const QString filename_text,
const QString filename_html,
QString* text, QString* html)
{
*text=QString();
*html=QString();
if (!filename_text.isEmpty()) {
QFile f(filename_text);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString errmsg = QIODevice::tr(f.errorString().toAscii().constData());
throw errmsg;
}
QByteArray contents = f.readAll();
*text = QTextCodec::codecForLocale()->toUnicode(contents);
f.close();
}
if (!filename_html.isEmpty()) {
QFile f(filename_html);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString errmsg = QIODevice::tr(f.errorString().toAscii().constData());
throw errmsg;
}
QByteArray contents = f.readAll();
*html = QTextCodec::codecForLocale()->toUnicode(contents);
f.close();
}
}
void
mailing_wizard_page_template::load_templates(mailing_options* opt)
{
mail_template tmpl;
mailing_wizard_page_template* wzpt = this;
mailing_db::template_format format = wzpt->body_format();
mailing_wizard_page_template::tmpl_template_source source = wzpt->template_source();
if (source==mailing_wizard_page_template::template_source_db_tmpl) {
// load template from database
int tmpl_id = wzpt->selected_template_id();
if (tmpl_id!=0) {
if (tmpl.load(tmpl_id)) {
if (format==mailing_db::format_text_plain || format==mailing_db::format_html_and_text) {
opt->text_part = tmpl.text_body();
}
else
opt->text_part.truncate(0);
if (format==mailing_db::format_html_and_text || format==mailing_db::format_html_only) {
opt->html_part = tmpl.html_body();
}
else
opt->html_part.truncate(0);
}
else
throw tr("Unable to load template #%1 from database").arg(tmpl_id);
}
else
throw tr("No template selected");
}
else if (source==mailing_wizard_page_template::template_source_files) {
// load template from file
load_file_templates(wzpt->text_template_filename(),
wzpt->html_template_filename(),
&opt->text_part,
&opt->html_part);
if (wzpt->body_format()==mailing_db::format_html_and_text && wzpt->text_template_filename().isEmpty()) {
// generate text part from html
opt->text_part=generate_text_part(opt->html_part);
}
}
else if (source==mailing_wizard_page_template::template_source_prev_mailing) {
// load template from previous mailing
int mlg_id = wzpt->selected_mailing_id();
if (mlg_id!=0) {
mailing_db mailing(mlg_id);
mailing_options mo;
if (mailing.load_definition(&mo)) {
switch(format) {
case mailing_db::format_text_plain:
opt->text_part = mo.text_part;
opt->html_part.truncate(0);
break;
case mailing_db::format_html_and_text:
opt->text_part = mo.text_part;
opt->html_part = mo.html_part;
if (opt->text_part.isEmpty())
opt->text_part=generate_text_part(opt->html_part);
break;
case mailing_db::format_html_only:
opt->html_part = mo.html_part;
opt->text_part.truncate(0);
break;
}
}
else
throw tr("Unable to load mailing #%1 from database").arg(mlg_id);
}
else
throw tr("No mailing selected");
}
}
bool
mailing_wizard_page_template::validatePage()
{
if (m_template_source == template_source_db_tmpl) {
int tmpl_id = selected_template_id();
if (tmpl_id==0)
return false;
// Check that the template has the format that the user requested
mail_template tmpl;
if (tmpl.load(tmpl_id)) {
if (m_format==mailing_db::format_text_plain && tmpl.text_body().isEmpty()) {
QMessageBox::critical(NULL, tr("Error"), tr("The selected template doesn't have a text part"));
return false;
}
if (m_format==mailing_db::format_html_only && tmpl.html_body().isEmpty()) {
QMessageBox::critical(NULL, tr("Error"), tr("The selected template doesn't have an html part"));
return false;
}
}
else {
QMessageBox::critical(NULL, tr("Error"), tr("Unable to load the selected template"));
return false;
}
}
else if (m_template_source == template_source_files) {
int subm = m_wpart_files->submitted_files();
QString errmsg;
int which;
switch(m_format) {
case mailing_db::format_html_only:
which=2;
if (subm!=1 && subm!=3)
errmsg=tr("Please indicate the location of a file containing the HTML part of the message");
break;
case mailing_db::format_html_and_text:
which=1;
if (subm!=1 && subm!=3)
errmsg=tr("Please indicate the location of both files, one containing the plain text part and the other the HTML part of the message");
// (actually the text part can be left blank but the errmsg is long enough already)
break;
case mailing_db::format_text_plain:
which=3;
if (subm!=2 && subm!=3)
errmsg=tr("Please indicate the location of a file containing the plain text part of the message");
break;
}
if (!errmsg.isEmpty()) {
QMessageBox::critical(NULL, tr("Missing filename"), errmsg);
return false;
}
if (!m_wpart_files->check_files(which))
return false;
}
else if (m_template_source == template_source_prev_mailing) {
int mlg_id = selected_mailing_id();
if (mlg_id==0)
return false;
// Check that the mailing has the format that the user requested
mailing_db mailing(mlg_id);
mailing_options mo;
if (mailing.load_definition(&mo)) {
if (m_format==mailing_db::format_text_plain && mo.text_part.isEmpty()) {
QMessageBox::critical(NULL, tr("Error"), tr("The selected previous mailing doesn't have a text part"));
return false;
}
if ((m_format==mailing_db::format_html_only || m_format==mailing_db::format_html_and_text) && mo.html_part.isEmpty()) {
QMessageBox::critical(NULL, tr("Error"), tr("The selected previous mailing doesn't have an html part"));
return false;
}
}
else {
QMessageBox::critical(NULL, tr("Error"), tr("Unable to retrieve the selected previous mailing"));
return false;
}
}
// Clear the filenames that are not needed, so that the rest of the process
// can use filename.isEmpty() as the test
switch(m_format) {
case mailing_db::format_html_only:
m_wpart_files->m_filename_text->clear();
break;
case mailing_db::format_text_plain:
m_wpart_files->m_filename_html->clear();
break;
case mailing_db::format_html_and_text:
break;
}
load_templates(get_options());
return mailing_wizard_page::validatePage();
}
mailing_wizard_page_final::mailing_wizard_page_final()
{
setTitle(tr("Finish the mailing setup"));
setSubTitle(tr("Preview the messages and choose to start the mailing immediately or later."));
int row=0;
QGridLayout* layout = new QGridLayout(this);
layout->setColumnStretch(0,0);
layout->setColumnStretch(1,1);
row++;
QPushButton* view_btn = new QPushButton(tr("Preview"));
layout->addWidget(view_btn, row, 0);
connect(view_btn, SIGNAL(clicked()), this, SLOT(preview_mailing()));
QLabel* preview_text = new QLabel(tr("Final check in a preview window on how each message will appear"));
QFont mini_font = preview_text->font();
mini_font.setPointSize((mini_font.pointSize()*8)/10);
preview_text->setFont(mini_font);
layout->addWidget(preview_text, row, 1);
row++;
QRadioButton* b1 = new QRadioButton(tr("Launch immediately"));
QRadioButton* b2 = new QRadioButton(tr("Launch later"));
b2->setChecked(true);
layout->addWidget(b1, row, 0);
row++;
layout->addWidget(b2, row, 0);
registerField("launch_now", b1);
row++;
m_progress_label = new QLabel(tr("Data import"));
layout->addWidget(m_progress_label, row, 0);
m_progress_label->setVisible(false);
m_progress_bar = new QProgressBar();
layout->addWidget(m_progress_bar, row, 0);
m_progress_bar->setVisible(false);
}
void
mailing_wizard_page_final::preview_mailing()
{
emit preview();
}
mailing_wizard_page_error_identities::mailing_wizard_page_error_identities()
{
setTitle(tr("No sender identity defined"));
QVBoxLayout* layout = new QVBoxLayout(this);
QLabel* label = new QLabel(tr("To be able to create a mailing, please go first in the Preferences/Identities and define at least one sender identity with an email address."));
layout->addWidget(label);
}
bool
mailing_wizard_page_error_identities::isComplete() const
{
return false;
}
mailing_wizard::mailing_wizard()
{
setWindowTitle(tr("Mailing setup"));
identities ids;
if (!ids.fetch() || ids.empty()) {
setPage(page_error_identities, new mailing_wizard_page_error_identities);
}
else {
setPage(page_title, new mailing_wizard_page_title);
setPage(page_template, new mailing_wizard_page_template);
setPage(page_import_data, new mailing_wizard_page_import_data);
setPage(page_data_file, new mailing_wizard_page_data_file);
setPage(page_parse_data, new mailing_wizard_page_parse_data);
setPage(page_paste_address_list, new mailing_wizard_page_paste_address_list);
mailing_wizard_page_final* p = new mailing_wizard_page_final;
setPage(page_final, p);
connect(p, SIGNAL(preview()), this, SLOT(preview_mailmerge()));
}
}
void
mailing_wizard::preview_mailmerge()
{
try {
gather_options();
}
catch(QString errmsg) {
QMessageBox::critical(NULL, tr("Error"), errmsg);
return;
}
mailing_viewer* w = new mailing_viewer(this);
w->setWindowModality(Qt::WindowModal);
w->set_data(m_options);
w->show_merge();
}
QString
mailing_wizard_page_template::generate_text_part(const QString html)
{
QWebPage page;
QWebFrame* frame = page.currentFrame();
frame->setHtml(html);
QString t = frame->toPlainText();
t.replace(QChar(0xa0), QChar(0x20)); // translate to space, not unbreakable space
return t;
}
// throws QString
void
mailing_wizard::gather_options()
{
m_options.title=field("mailing_title").toString();
mailing_wizard_page_title* wzpf = static_cast<mailing_wizard_page_title*>(page(page_title));
mail_address addr = wzpf->sender_address();
m_options.sender_email = addr.email();
QString mail_subject = mailing_db::filter_header_line(field("subject").toString());
m_options.header = QString("From: %1\nSubject: %2").
arg(addr.email_and_name()).
arg(mail_subject);
mailing_wizard_page_template* wzpt = static_cast<mailing_wizard_page_template*>(page(page_template));
wzpt->load_templates(&m_options);
mailing_wizard_page_parse_data* wzpp = static_cast<mailing_wizard_page_parse_data*>(page(page_parse_data));
m_options.mail_addresses = wzpp->recipient_list();
}
bool
mailing_wizard::launch_mailing()
{
db_cnx db;
try {
db.begin_transaction();
gather_options();
// instantiate the job
mailing_db mailing;
bool failed=true;
mailing_wizard_page_final* wzpf = static_cast<mailing_wizard_page_final*>(page(page_final));
mailing.set_status(field("launch_now").toBool() ?
mailing_db::status_running:
mailing_db::status_not_started);
if (mailing.create(m_options)) {
wzpf->m_progress_label->show();
wzpf->m_progress_bar->show();
if (mailing.store_data(m_options.mail_addresses, m_options.csv_data, wzpf->m_progress_bar)) {
if (field("launch_now").toBool())
failed = !mailing.instantiate_job();
else
failed=false;
}
}
if (failed)
throw tr("The launch of the mailing job has failed.");
db.commit_transaction();
}
catch(QString errmsg) {
db.rollback_transaction();
QMessageBox::critical(NULL, tr("Error"), errmsg);
return false;
}
return true;
}
bool
mailing_wizard::validateCurrentPage()
{
if (!currentPage())
return false;
if (currentPage()->validatePage()) {
if (currentId() == page_final) {
return launch_mailing();
}
return true;
}
else
return false;
}
QStringList
mailing_wizard::get_pasted_addresses()
{
mailing_wizard_page_paste_address_list* wzp = static_cast<mailing_wizard_page_paste_address_list*>(page(page_paste_address_list));
return wzp->get_addresses();
}
mailing_options*
mailing_wizard_page::get_options()
{
mailing_wizard* z = static_cast<mailing_wizard*>(wizard());
return z ? &z->m_options : NULL;
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files