Source file: src/edit_rules.cpp
/* Copyright (C) 2004-2014 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 "filter_rules.h"
#include "edit_rules.h"
#include "filter_expr_editor.h"
#include "tags.h"
#include "icons.h"
#include "helper.h"
#include "ui_controls.h"
#include "headers_groupview.h"
#include "tagsdialog.h"
#include "app_config.h"
#include "searchbox.h"
#include "filter_action_editor.h"
#include "filter_results_window.h"
#include "selectmail.h"
#include "filter_eval.h"
#include <QMessageBox>
#include <QFrame>
#include <QHeaderView>
#include <QTimer>
#include <QPushButton>
#include <QLayout>
#include <QLineEdit>
#include <QButtonGroup>
#include <QToolButton>
#include <QLabel>
#include <QCheckBox>
#include <QStringList>
#include <QEvent>
#include <QFont>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QGridLayout>
#include <QFocusEvent>
int expr_lvitem::m_max_id=0;
//
// expr_lvitem
//
expr_lvitem::expr_lvitem(QTreeWidget* parent): QTreeWidgetItem(parent)
{
m_id = ++m_max_id;
}
expr_lvitem::~expr_lvitem()
{
// don't delete m_expr, we don't own it
}
// Sort items
bool
expr_lvitem::operator<(const QTreeWidgetItem &other) const
{
int column = treeWidget()->sortColumn();
switch(column) {
case filter_edit::icol_number:
// sort by number
return text(column).toInt() < other.text(column).toInt();
case filter_edit::icol_name:
case filter_edit::icol_expr:
// sort case insensitively
return text(column).toLower() < other.text(column).toLower();
case filter_edit::icol_last_hit:
const expr_lvitem* lv1 = static_cast<const expr_lvitem*>(&other);
return m_expr->m_last_hit.FullOutput() < lv1->m_expr->m_last_hit.FullOutput();
break;
}
return false; // never reached
}
void
expr_lvitem::set_expression_text(const QString text)
{
QString t = text;
// replace newlines by spaces to prevent multi-line expressions, at least visually
t.replace(QChar('\n'), QChar(' '));
setText(filter_edit::icol_expr, t);
}
//
// filter_edit
//
filter_edit::filter_edit(QWidget* parent): QWidget(parent)
{
m_hd=NULL;
m_test_window_results = NULL;
m_confirm_close=true;
m_eval_timer = NULL;
setWindowTitle(tr("Filter rules"));
setWindowIcon(UI_ICON(FT_ICON16_FILTERS));
m_date_format = get_config().get_date_format_code();
QVBoxLayout* top_layout = new QVBoxLayout(this);
// Top of the window: title,blank space,edit box
QHBoxLayout* top_hboxl = new QHBoxLayout();
// top_layout->setMargin(5);
QLabel* lexpr = new QLabel(tr("Conditions:"));
QFont fnt1 = lexpr->font();
fnt1.setBold(true);
lexpr->setFont(fnt1);
top_hboxl->addWidget(lexpr);
top_hboxl->addStretch(8);
QLabel* filter_title = new QLabel(tr("Filter:"));
search_edit* filter_box = new search_edit();
connect(filter_box, SIGNAL(textChanged(const QString&)),
this, SLOT(filter_out_exprs(const QString&)));
top_hboxl->addWidget(filter_title);
top_hboxl->addWidget(filter_box);
top_layout->addLayout(top_hboxl);
QVBoxLayout* box_expr = new QVBoxLayout();
top_layout->addLayout(box_expr, 10);
QVBoxLayout* arrows_box = new QVBoxLayout();
m_btn_top = new QPushButton(UI_ICON(ICON16_GO_TOP), "");
m_btn_top->setToolTip(tr("Move to the top of the list"));
m_btn_up = new QPushButton(UI_ICON(ICON16_GO_UP), "");
m_btn_up->setToolTip(tr("Move one line up"));
m_btn_down = new QPushButton(UI_ICON(ICON16_GO_DOWN), "");
m_btn_down->setToolTip(tr("Move one line down"));
m_btn_bottom = new QPushButton(UI_ICON(ICON16_GO_BOTTOM), "");
m_btn_bottom->setToolTip(tr("Move to the bottom of the list"));
connect(m_btn_top, SIGNAL(clicked()), this, SLOT(move_filter_top()));
connect(m_btn_up, SIGNAL(clicked()), this, SLOT(move_filter_up()));
connect(m_btn_down, SIGNAL(clicked()), this, SLOT(move_filter_down()));
connect(m_btn_bottom, SIGNAL(clicked()), this, SLOT(move_filter_bottom()));
arrows_box->addWidget(m_btn_top);
arrows_box->addWidget(m_btn_up);
arrows_box->addWidget(m_btn_down);
arrows_box->addWidget(m_btn_bottom);
enable_up_down_buttons(false);
// expr_sv->setMaximumHeight(150);
QHBoxLayout* buttons_plus_view = new QHBoxLayout();
buttons_plus_view->addLayout(arrows_box);
lv_expr = new QTreeWidget();
buttons_plus_view->addWidget(lv_expr);
// box_expr->setFrameStyle(QFrame::Panel|QFrame::Raised);
// box_expr->setMargin(2);
box_expr->addLayout(buttons_plus_view);
// lv_expr->setMultiSelection(false);
QStringList labels;
labels << tr("Order") << tr("Name") << tr("Expression") << tr("Last hit");
lv_expr->setHeaderLabels(labels);
lv_expr->header()->resizeSection(icol_number, 40); // width for "number" column
lv_expr->header()->resizeSection(icol_name, 100); // width for "Name" column
lv_expr->header()->resizeSection(icol_expr, 400); // width for "Expression" column
lv_expr->header()->resizeSection(icol_last_hit, 50); // width for "Expression" column
connect(lv_expr->header(), SIGNAL(sortIndicatorChanged (int, Qt::SortOrder)),
this, SLOT(expr_sort_order_changed(int,Qt::SortOrder)));
lv_expr->setRootIsDecorated(false);
lv_expr->setAllColumnsShowFocus(true);
connect(lv_expr, SIGNAL(itemSelectionChanged()),
this, SLOT(selection_changed()));
QHBoxLayout* expr_btn_box = new QHBoxLayout();
box_expr->addLayout(expr_btn_box);
QPushButton* expr_new = new QPushButton(tr("New"));
expr_btn_box->addWidget(expr_new);
expr_btn_box->setStretchFactor(expr_new, 1);
connect(expr_new, SIGNAL(clicked()), this, SLOT(new_expr()));
QPushButton* expr_test = new QPushButton(tr("Try"));
expr_btn_box->addWidget(expr_test);
expr_btn_box->setStretchFactor(expr_test, 1);
connect(expr_test, SIGNAL(clicked()), this, SLOT(test_expr()));
expr_btn_delete = new QPushButton(tr("Delete"));
expr_btn_box->addWidget(expr_btn_delete);
expr_btn_box->setStretchFactor(expr_btn_delete, 1);
connect(expr_btn_delete, SIGNAL(clicked()), this, SLOT(delete_expr()));
expr_btn_delete->setEnabled(false);
#if 0
m_suggest_btn = new QPushButton(tr("Suggest"));
expr_btn_box->addWidget(m_suggest_btn);
m_suggest_btn->setEnabled(false);
expr_btn_box->setStretchFactor(m_suggest_btn, 1);
connect(m_suggest_btn, SIGNAL(clicked()), this, SLOT(suggest_filter()));
#endif
expr_btn_box->addStretch(8);
QFrame* expr_cont = new QFrame();
expr_cont->setContentsMargins(3,3,3,0);
m_expr_container = expr_cont;
expr_cont->setFrameStyle(QFrame::Box);
box_expr->addWidget(expr_cont);
QGridLayout* expr_grid = new QGridLayout(expr_cont);
expr_grid->setContentsMargins(4,4,4,1);
// expr_grid->setSpacing(1);
int row=0;
int col=0;
QLabel* lexpr_name = new QLabel(tr("Condition's name:"), expr_cont);
expr_grid->addWidget(lexpr_name, row, col++);
QLabel* ql_expr_label = new QLabel(tr("Expression:"), expr_cont);
expr_grid->addWidget(ql_expr_label, row, col++);
row++; col=0;
ql_expr_name = new focus_line_edit(expr_cont, "expr_name", this);
connect(ql_expr_name, SIGNAL(textEdited(const QString&)),
this, SLOT(current_name_edited(const QString&)));
expr_grid->addWidget(ql_expr_name, row, col++);
QHBoxLayout* expr_layout = new QHBoxLayout();
ql_expr_full = new expr_line_edit();
expr_layout->addWidget(ql_expr_full);
connect(ql_expr_full, SIGNAL(textEdited(const QString&)),
this, SLOT(current_expr_edited(const QString&)));
connect(ql_expr_full, SIGNAL(toolbutton_clicked()),
this, SLOT(show_eval_message()));
QIcon zoom_icon(UI_ICON(FT_ICON16_ZOOM_PAGE));
m_zoom_button = new QToolButton();
expr_layout->addWidget(m_zoom_button);
m_zoom_button->setIcon(zoom_icon);
m_zoom_button->setToolTip(tr("Zoom"));
expr_grid->addLayout(expr_layout, row, col++);
connect(m_zoom_button, SIGNAL(clicked()), this, SLOT(zoom_on_expr()));
expr_grid->setColumnStretch(0, 1); // 1/5 for name
expr_grid->setColumnStretch(1, 4); // 4/5 for expression
row++; col=0;
expr_grid->addWidget(new QLabel(tr("Filter's direction:")), row, col++);
QHBoxLayout* dirlayout = new QHBoxLayout;
dirlayout->setContentsMargins(0,0,0,0);
m_dir = new button_group(QBoxLayout::LeftToRight);
m_dir->setContentsMargins(0,0,0,0);
m_dir->setLineWidth(0);
m_dir->addButton(new QRadioButton(tr("Incoming mail")), 0);
m_dir->addButton(new QRadioButton(tr("Outgoing mail")), 1);
m_dir->addButton(new QRadioButton(tr("Both")), 2);
m_dir->addButton(new QRadioButton(tr("None (disabled)")), 3);
dirlayout->addWidget(m_dir, 0);
dirlayout->addStretch(10);
expr_grid->addLayout(dirlayout, row, col++);
connect(m_dir->group(), SIGNAL(buttonClicked(int)), this, SLOT(direction_changed(int)));
// -- Actions --
m_lrules = new QLabel(tr("Actions:"));
m_lrules->setFont(fnt1);
top_layout->addWidget(m_lrules);
QHBoxLayout* btn_row_actions = new QHBoxLayout();
btn_row_actions->setAlignment(Qt::AlignLeft);
m_btn_add_action = new QPushButton(tr("Add"));
btn_row_actions->addWidget(m_btn_add_action);
connect(m_btn_add_action, SIGNAL(clicked()), this, SLOT(add_action()));
QFont btn_font = m_btn_add_action->font();
btn_font.setPointSize((btn_font.pointSize()*8)/10);
m_btn_add_action->setFont(btn_font);
m_btn_edit_action = new QPushButton(tr("Modify"));
btn_row_actions->addWidget(m_btn_edit_action);
connect(m_btn_edit_action, SIGNAL(clicked()), this, SLOT(edit_action()));
m_btn_edit_action->setFont(btn_font);
m_btn_remove_action = new QPushButton(tr("Remove"));
btn_row_actions->addWidget(m_btn_remove_action);
connect(m_btn_remove_action, SIGNAL(clicked()), this, SLOT(remove_action()));
m_btn_remove_action->setFont(btn_font);
top_layout->addLayout(btn_row_actions);
QHBoxLayout* box_actions = new QHBoxLayout();
top_layout->addLayout(box_actions, 3);
QVBoxLayout* action_arrows_box = new QVBoxLayout();
m_btn_up_action = new QPushButton(UI_ICON(ICON16_GO_UP), "");
m_btn_up_action->setToolTip(tr("Move one line up"));
m_btn_down_action = new QPushButton(UI_ICON(ICON16_GO_DOWN), "");
m_btn_down_action->setToolTip(tr("Move one line down"));
connect(m_btn_up_action, SIGNAL(clicked()), this, SLOT(move_action_up()));
connect(m_btn_down_action, SIGNAL(clicked()), this, SLOT(move_action_down()));
action_arrows_box->addWidget(m_btn_up_action);
action_arrows_box->addWidget(m_btn_down_action);
box_actions->addLayout(action_arrows_box);
lv_actions = new action_listview();
box_actions->addWidget(lv_actions);
lv_actions->setHeaderLabels(QStringList(tr("Actions list")));
lv_actions->setSortingEnabled(false); // should be kept sorted by the order of actions
connect(lv_actions, SIGNAL(key_del()), this, SLOT(remove_action()));
connect(lv_actions, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
this, SLOT(modify_action(QTreeWidgetItem*,int)));
connect(lv_actions, SIGNAL(itemSelectionChanged()),
this, SLOT(actions_selection_changed()));
// Help OK Cancel
QHBoxLayout* hbox_valid = new QHBoxLayout();
hbox_valid->setMargin(5);
hbox_valid->setSpacing(5);
top_layout->addLayout(hbox_valid);
hbox_valid->addStretch(1); // space at left
QPushButton* whelp = new QPushButton(tr("Help"));
hbox_valid->addWidget(whelp, 0);
QPushButton* wok = new QPushButton(tr("OK"));
hbox_valid->addWidget(wok, 0);
wok->setDefault(true);
QPushButton* wcancel = new QPushButton(tr("Cancel"));
hbox_valid->addWidget(wcancel);
connect(wok, SIGNAL(clicked()), this, SLOT(ok()));
connect(wcancel, SIGNAL(clicked()), this, SLOT(cancel()));
connect(whelp, SIGNAL(clicked()), this, SLOT(help()));
clear_expr();
disable_all_expr();
untie_actions();
load();
lv_expr->header()->setSortIndicatorShown(true);
lv_expr->sortByColumn(icol_number, Qt::AscendingOrder);
lv_expr->setSortingEnabled(true);
actions_selection_changed();
}
filter_edit::~filter_edit()
{
if (m_hd)
delete m_hd;
}
void
filter_edit::expr_sort_order_changed(int col_index, Qt::SortOrder order)
{
enable_up_down_buttons(col_index==0 && order==Qt::AscendingOrder &&
!lv_expr->selectedItems().empty());
}
expr_lvitem*
filter_edit::selected_item() const
{
return lv_expr->selectedItems().isEmpty() ? NULL:
static_cast<expr_lvitem*>(lv_expr->selectedItems().at(0));
}
/* show the filter expressions that contain 'substring' and hide those that don't */
void
filter_edit::filter_out_exprs(const QString& substring)
{
QTreeWidgetItemIterator iter(lv_expr);
while (*iter) {
if (substring.isEmpty() ||
((*iter)->text(icol_name).contains(substring, Qt::CaseInsensitive) ||
(*iter)->text(icol_expr).contains(substring, Qt::CaseInsensitive)))
{
(*iter)->setHidden(false);
}
else {
(*iter)->setHidden(true);
}
++iter;
}
if (substring.isEmpty()) {
expr_lvitem* item = selected_item();
if (item)
lv_expr->scrollToItem(item);
}
}
/*
direction: 1=>up, 2=>top, 3=>down, 4=>bottom
*/
void
filter_edit::move_filter_item(int direction)
{
expr_lvitem* item = dynamic_cast<expr_lvitem*>(lv_expr->currentItem());
if (!item)
return;
int index = lv_expr->indexOfTopLevelItem(item);
int new_index;
DBG_PRINTF(5, "indexOfTopLevelItem=%d", index);
switch(direction) {
case 1:
new_index = index-1;
break;
case 2:
new_index=0;
break;
case 3:
new_index = index+1;
break;
case 4:
new_index = lv_expr->topLevelItemCount()-1;
break;
default:
DBG_PRINTF(1, "invalid direction");
return;
}
if (new_index<0 || index == new_index || new_index>=lv_expr->topLevelItemCount())
return;
/* We take out the item at 'index' and reinsert it above
'new_index'. The code below should work with any possible
relationship between 'index' and 'new_index', even though the
current UI limits 'new_index' to be near 'index' or at the top or
bottom of the list. */
DBG_PRINTF(5, "new_index=%d", new_index);
lv_expr->setCurrentItem(NULL);
item = dynamic_cast<expr_lvitem*>(lv_expr->takeTopLevelItem(index));
/* reinsert item between item0 and item1. If item0 is nul, the
insert is at the top of the list. If item1 is nul, it's at the
bottom of the list. */
expr_lvitem* item0 = (new_index-1>=0) ? dynamic_cast<expr_lvitem*>(lv_expr->topLevelItem(new_index-1)) : NULL;
expr_lvitem* item1 = (new_index < lv_expr->topLevelItemCount()) ? dynamic_cast<expr_lvitem*>(lv_expr->topLevelItem(new_index)) : NULL;
if (item1 && item0) {
// moved between two existing elements
item->m_expr->m_apply_order = item0->m_expr->m_apply_order + ( item1->m_expr->m_apply_order - item0->m_expr->m_apply_order) / 2;
}
else if (!item0 && item1) {
// moved at the top of the list
item->m_expr->m_apply_order = item1->m_expr->m_apply_order/2;
}
else if (item0 && !item1) {
// moved at the end of the list
item->m_expr->m_apply_order = item0->m_expr->m_apply_order+1;
}
item->m_expr->m_dirty = true;
lv_expr->setSortingEnabled(false);
DBG_PRINTF(5, "before insert at %d: lastindex=%d", new_index,
lv_expr->topLevelItemCount()-1);
lv_expr->insertTopLevelItem(new_index, item);
renumber_items();
lv_expr->setSortingEnabled(true);
lv_expr->setCurrentItem(item);
}
void
filter_edit::move_filter_top()
{
move_filter_item(2);
}
void
filter_edit::move_filter_up()
{
move_filter_item(1);
}
void
filter_edit::move_filter_bottom()
{
move_filter_item(4);
}
void
filter_edit::move_filter_down()
{
move_filter_item(3);
}
void
filter_edit::direction_changed(int id)
{
Q_UNUSED(id);
DBG_PRINTF(4, "direction_changed");
if (m_current_expr) {
m_current_expr->m_dirty=true;
dlg_fields_to_filter_expr(m_current_expr);
}
}
void
filter_edit::show_eval_message()
{
validate_expression();
}
void
filter_edit::zoom_on_expr()
{
if (!m_current_expr)
return;
filter_expr_text_editor * w = new filter_expr_text_editor(this);
QString initial_txt = ql_expr_full->text();
w->set_text(initial_txt);
int ret=w->exec();
QString ntxt = w->get_text();
if (ret && ntxt != initial_txt) {
ql_expr_full->setText(ntxt);
current_expr_edited(ntxt);
}
w->close();
}
#if 0
void
filter_edit::set_sel_list(const std::list<unsigned int>& l)
{
m_sel_list = l;
m_suggest_btn->setEnabled(true);
}
#endif
void
filter_edit::current_name_edited(const QString& new_name)
{
QList<QTreeWidgetItem*> sel = lv_expr->selectedItems();
if (sel.size()==1) {
expr_lvitem* item = static_cast<expr_lvitem*>(sel.at(0));
item->setText(icol_name, new_name);
}
if (m_current_expr) {
m_current_expr->m_expr_name = new_name;
m_current_expr->m_dirty = true;
}
}
void
filter_edit::current_expr_edited(const QString& new_expr)
{
QList<QTreeWidgetItem*> sel = lv_expr->selectedItems();
if (sel.size()==1) {
expr_lvitem* item = static_cast<expr_lvitem*>(sel.at(0));
item->set_expression_text(new_expr);
}
if (m_current_expr) {
m_current_expr->m_expr_text = new_expr;
m_current_expr->m_dirty=true;
ql_expr_full->show_button(0);
// Cancel the timer if it's already running.
if (m_eval_timer)
delete m_eval_timer;
m_eval_timer = new QTimer(this);
m_eval_timer->setSingleShot(true);
m_eval_timer->setInterval(500);
connect(m_eval_timer, SIGNAL(timeout()),
this, SLOT(display_expression_validity()));
m_eval_timer->start();
}
}
/*
Disable entirely the actions panel.
To be called when no expression is selected.
*/
void
filter_edit::untie_actions()
{
m_lrules->setEnabled(false);
m_lrules->setText(tr("Actions:"));
// uncheck all and disable each action widget
reset_actions();
// disable the actions list
lv_actions->setEnabled(false);
#if 0 // REMOVE
// also disable the radio buttons
for (int i=0; i<action_line::idx_max; i++) {
w_actions[i]->m_rb->setEnabled(false);
}
#endif
}
void
filter_edit::closeEvent(QCloseEvent* event)
{
if (m_confirm_close && m_expr_list.needs_save()) {
QString msg = tr("There are unsaved changes.\nSave now?");
int rep = QMessageBox::question(this, tr("Confirmation"), msg, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
if (rep==QMessageBox::Cancel) {
m_confirm_close=true;
event->ignore();
return;
}
if (rep==QMessageBox::Save) {
event->ignore();
QTimer::singleShot(0, this, SLOT(ok()));
}
else {
m_confirm_close=false;
event->accept();
}
}
else
event->accept();
}
void
filter_edit::ok()
{
ql_expr_full->validate();
ql_expr_name->validate();
// accept();
if (m_expr_list.update_db()) {
m_confirm_close=false;
close();
}
}
void
filter_edit::cancel()
{
// accept();
m_confirm_close=false;
close();
}
bool
filter_edit::load()
{
expr_list* l = &m_expr_list;
if (!l->fetch()) return false;
std::list<filter_expr>::iterator it = l->begin();
int number=1;
for (; it != l->end(); ++it) {
expr_lvitem* item = new expr_lvitem(lv_expr);
item->setText(icol_name, (*it).m_expr_name);
item->setText(icol_number, QString::number(number++));
item->set_expression_text((*it).m_expr_text);
item->setText(icol_last_hit, (*it).m_last_hit.OutputHM(m_date_format));
item->m_expr = &(*it);
item->m_db=true;
}
return true;
}
void
filter_edit::renumber_items()
{
QTreeWidgetItemIterator iter(lv_expr);
int num=1;
while (*iter) {
(*iter)->setText(icol_number, QString::number(num++));
++iter;
}
}
void
filter_edit::clear_expr()
{
ql_expr_name->setText(QString::null);
ql_expr_full->setText(QString::null);
m_current_expr=NULL;
lv_expr->clearSelection();
untie_actions();
}
#if 0
void
filter_edit::suggest_filter()
{
if (!m_hd) {
m_hd = new headers_groupview();
m_hd->set_threshold(100);
m_hd->init(m_sel_list);
connect(m_hd->m_trview, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
this, SLOT(expr_from_header(QTreeWidgetItem*)));
connect(m_hd, SIGNAL(close()), this, SLOT(close_headers_groupview()));
}
m_hd->show();
}
#endif
void
filter_edit::expr_from_header(QTreeWidgetItem* item)
{
if (!item) return;
const QString h = item->text(1);
int sep_pos = h.indexOf(":");
if (sep_pos>0) {
QString header_name = h.left(sep_pos);
QString header_value = h.mid(sep_pos+1);
header_value = header_value.trimmed();
new_expr();
QString expr = "header(\""+ header_name + "\") eq \"" + header_value + "\"";
ql_expr_full->setText(expr);
}
}
void
filter_edit::close_headers_groupview()
{
}
void
filter_edit::test_expr()
{
if (!m_current_expr)
return;
m_test_msgs_filter = new msgs_filter();
msgs_filter* f = m_test_msgs_filter;
f->m_max_results = 1000;
f->set_date_order(-1);
f->m_include_trash = true;
m_fthread = new fetch_thread();
m_nb_filter_test_match = 0;
int r = f->asynchronous_fetch(m_fthread);
DBG_PRINTF(3, "asynchronous_fetch return: %d", r);
if (r==1) {
if (m_test_window_results==NULL) {
m_test_window_results = new filter_results_window;
m_test_window_results->setAttribute(Qt::WA_DeleteOnClose);
connect(m_test_window_results, SIGNAL(destroyed()), this, SLOT(close_results_window()));
connect(m_test_window_results, SIGNAL(stop_run()), this, SLOT(end_test_requested()));
m_test_window_results->show();
}
else {
m_test_window_results->clear();
}
QString win_title;
if (m_current_expr->m_expr_name.isEmpty())
win_title = tr("Filter matches");
else
win_title = tr("Filter matches for '%1'").arg(m_current_expr->m_expr_name);
m_test_window_results->setWindowTitle(win_title);
m_test_window_results->show_filter_expression(m_current_expr->m_expr_text);
m_test_window_results->show_progressbar();
m_filter_run_stopped = false;
m_ftimer = new QTimer(this);
connect(m_ftimer, SIGNAL(timeout()), this, SLOT(timer_done()));
m_waiting_for_results = true;
m_ftimer->start(100); // check results every 1/10s
}
}
void
filter_edit::timer_done()
{
if (m_waiting_for_results && m_fthread->isFinished()) {
m_waiting_for_results=false;
m_test_msgs_filter->postprocess_fetch(*m_fthread);
bool finished=false;
if (m_test_window_results != NULL && !m_filter_run_stopped) {
std::list<mail_result>::iterator it;
for (it=m_fthread->m_results->begin(); it!=m_fthread->m_results->end(); ++it) {
filter_evaluator filter_eval;
filter_eval_result res = filter_eval.evaluate(*m_current_expr, m_expr_list, it->m_id);
if (res.result) {
m_nb_filter_test_match++;
if (m_test_window_results && !m_filter_run_stopped)
m_test_window_results->incorporate_message(*it);
else
break;
}
else {
if (!res.errstr.isEmpty()) {
// Stop the test on any evaluation error
m_ftimer->stop();
m_test_window_results->hide_progressbar();
QMessageBox::critical(this, tr("Filter error"), tr("Error near character %1:\n%2").arg(res.evp+1).arg(res.errstr));
if (m_test_window_results->nb_results()==0)
delete m_test_window_results;
finished = true;
break;
}
}
QApplication::processEvents();
}
}
if (!m_test_msgs_filter->has_more_results() || !m_test_window_results || m_filter_run_stopped || finished) {
finished=true;
if (m_test_window_results) {
m_test_window_results->hide_progressbar();
m_test_window_results->show_status_message(tr("%1 match(es) found. Filter test finished.").arg(m_nb_filter_test_match));
}
}
else {
m_test_window_results->show_status_message(tr("%1 match(es) found. Testing more...").arg(m_nb_filter_test_match));
m_waiting_for_results = true;
int r = m_test_msgs_filter->asynchronous_fetch(m_fthread, true);
if (r!=1) // error
finished=true;
}
if (finished) {
delete m_ftimer;
m_fthread->release();
delete m_fthread;
delete m_test_msgs_filter;
}
}
}
void
filter_edit::close_results_window()
{
if (m_test_window_results) {
m_test_window_results = NULL;
}
}
void
filter_edit::end_test_requested()
{
m_filter_run_stopped=true;
if (m_waiting_for_results) {
m_waiting_for_results = false;
m_fthread->cancel();
m_fthread->release();
}
}
void
filter_edit::delete_expr()
{
if (!m_current_expr) {
DBG_PRINTF(6, "no current expr");
return;
}
int r=QMessageBox::warning(this, tr("Please confirm"), tr("Delete filter?"), tr("OK"), tr("Cancel"), QString::null);
if (r==0) {
expr_lvitem* item=dynamic_cast<expr_lvitem*>(lv_expr->currentItem());
if (!item) {
DBG_PRINTF(1, "Couldn't find selected item");
return;
}
item->m_expr->m_delete=true;
delete item;
clear_expr();
enable_up_down_buttons(false);
renumber_items();
}
}
void
filter_edit::enable_up_down_buttons(bool b)
{
m_btn_up->setEnabled(b);
m_btn_down->setEnabled(b);
m_btn_bottom->setEnabled(b);
m_btn_top->setEnabled(b);
}
void
filter_edit::selection_changed()
{
if (lv_expr->selectedItems().empty()) {
// selection cleared
clear_expr();
enable_up_down_buttons(false);
expr_btn_delete->setEnabled(false);
enable_expr_edit(false);
}
else {
// single selection
expr_lvitem* item = static_cast<expr_lvitem*>(lv_expr->selectedItems().at(0));
enable_up_down_buttons(lv_expr->header()->sortIndicatorSection()==0 &&
lv_expr->header()->sortIndicatorOrder()==Qt::AscendingOrder);
filter_expr* e = item->m_expr;
m_current_expr = e;
expr_btn_delete->setEnabled(item->m_id!=0);
enable_expr_edit(true);
filter_expr_to_dlg(e);
reset_actions();
display_actions();
if (m_eval_timer) {
delete m_eval_timer;
m_eval_timer=NULL;
}
display_expression_validity();
}
}
void
filter_edit::display_expression_validity()
{
if (m_current_expr) {
filter_evaluator filter_eval;
filter_eval_result res = filter_eval.evaluate(*m_current_expr, m_expr_list, 0);
if (!res.errstr.isEmpty()) {
ql_expr_full->show_button(1);
}
else {
ql_expr_full->show_button(2);
}
}
}
/*
update the 'Actions' panel with the actions connected to the
currently selected expression
*/
void
filter_edit::display_actions()
{
m_lrules->setEnabled(true);
lv_actions->clear();
lv_actions->setEnabled(true);
m_current_action=NULL;
filter_expr* e = m_current_expr;
if (!e) {
DBG_PRINTF(1, "ERR: no current expression");
return;
}
if (e->m_expr_name.isEmpty())
m_lrules->setText(tr("Actions connected to current condition"));
else
m_lrules->setText(tr("Actions connected to condition '%1':").arg(e->m_expr_name));
QList<filter_action>::iterator iter;
action_lvitem* after=NULL;
for (iter=e->m_actions.begin(); iter!=e->m_actions.end(); ++iter) {
after = new action_lvitem(lv_actions, after);
CHECK_PTR(after);
// DBG_PRINTF(6, "add action %s:%s\n", iter->m_str_atype.latin1(),
// iter->m_action_string.latin1());
after->m_act_ptr = &(*iter);
after->setText(0, iter->ui_text());
}
create_null_action();
if (lv_actions->topLevelItem(0)) {
// pre-select the first element
DBG_PRINTF(6, "setSelected(0x%p)", lv_actions->topLevelItem(0));
lv_actions->setCurrentItem(lv_actions->topLevelItem(0));
m_current_action=static_cast<action_lvitem*>(lv_actions->topLevelItem(0));
}
}
// Add the (New action) entry
void
filter_edit::create_null_action()
{
#if 0
int count=lv_actions->topLevelItemCount();
action_lvitem* last=NULL;
if (count>0)
last = static_cast<action_lvitem*>(lv_actions->topLevelItem(count-1));
action_lvitem* item = new action_lvitem(lv_actions, last);
CHECK_PTR(item);
item->setText(0, tr("(New action)"));
item->m_act_ptr = NULL; // no real associated action
#endif
}
/*
Reset the 'Actions' panel
*/
void
filter_edit::reset_actions()
{
lv_actions->clear();
}
void
filter_edit::enable_expr_edit(bool enable)
{
m_expr_container->setEnabled(enable);
if (!enable) {
ql_expr_full->show_button(0);
}
}
void
filter_edit::validate_expression()
{
if (!m_current_expr)
return;
filter_evaluator filter_eval;
filter_eval_result res = filter_eval.evaluate(*m_current_expr, m_expr_list, 0);
if (!res.errstr.isEmpty()) {
QMessageBox::critical(this, tr("Filter error"), tr("Error at character %1:\n%2").arg(res.evp+1).arg(res.errstr));
}
else {
QMessageBox::information(this, tr("Filter validity"), tr("The expression syntax is correct."));
}
}
void
filter_edit::new_expr()
{
clear_expr();
enable_expr_edit(true);
expr_lvitem* item = new expr_lvitem();
int number = 1+lv_expr->topLevelItemCount();
item->setText(icol_number, QString::number(number));
lv_expr->addTopLevelItem(item);
filter_expr ne;
ne.m_new=true;
ne.m_dirty=true;
ne.m_expr_id=0;
ne.m_apply_order = 1 + m_expr_list.max_apply_order();
m_expr_list.push_back(ne);
item->m_expr = &(m_expr_list.back());
m_current_expr = item->m_expr;
filter_expr_to_dlg(item->m_expr);
lv_expr->setCurrentItem(item);
ql_expr_name->setFocus();
}
bool
filter_edit::expr_fields_filled() const
{
return !ql_expr_full->text().isEmpty();
}
void
filter_edit::dlg_fields_to_filter_expr(filter_expr *e)
{
e->m_expr_text = ql_expr_full->text();
e->m_expr_name = ql_expr_name->text();
const char dir[]={'I','O','B','N'};
int dir_idx=m_dir->selected_id();
if (dir_idx>=0 && dir_idx<4)
e->m_direction = dir[m_dir->selected_id()];
else
e->m_direction = 'N';
}
void
filter_edit::filter_expr_to_dlg(filter_expr* e)
{
ql_expr_name->setText(e->m_expr_name);
ql_expr_full->setText(e->m_expr_text);
ql_expr_full->setCursorPosition(0);
switch(e->m_direction) {
case 'I':
m_dir->setButton(0);
break;
case 'O':
m_dir->setButton(1);
break;
case 'B':
m_dir->setButton(2);
break;
case 'N':
m_dir->setButton(3);
break;
}
}
void
filter_edit::expr_update()
{
bool select_item=false;
// change the text in the expressions listview
QList<QTreeWidgetItem*> list_selected = lv_expr->selectedItems();
expr_lvitem* item = list_selected.count()==1 ?
static_cast<expr_lvitem*>(list_selected.first()) : NULL;
if (!item) {
item = new expr_lvitem(lv_expr);
CHECK_PTR(item);
int number = 1+lv_expr->topLevelItemCount();
item->setText(icol_number, QString::number(number));
item->setText(icol_name, ql_expr_name->text());
select_item=true;
filter_expr new_expr;
m_expr_list.push_back(new_expr);
item->m_expr = &(m_expr_list.back());
DBG_PRINTF(6, "creating new expr");
item->m_expr->m_new=true;
item->m_expr->m_expr_id=0;
m_current_expr = item->m_expr;
}
item->setText(icol_name, ql_expr_name->text());
item->set_expression_text(ql_expr_full->text());
filter_expr* e = item->m_expr; // should be m_current_expr
e->m_expr_name = ql_expr_name->text();
e->m_expr_text = ql_expr_full->text();
dlg_fields_to_filter_expr(e);
e->m_dirty=true;
DBG_PRINTF(6, "expr_id %d is marked dirty", e->m_expr_id);
if (select_item)
lv_expr->setCurrentItem(item);
}
void
filter_edit::disable_all_expr()
{
enable_expr_edit(false);
expr_btn_delete->setEnabled(false);
}
void
filter_edit::actions_selection_changed()
{
QList<QTreeWidgetItem*> sel = lv_actions->selectedItems();
bool has_sel = !sel.isEmpty();
m_btn_edit_action->setEnabled(has_sel);
m_btn_remove_action->setEnabled(has_sel);
m_btn_up_action->setEnabled(has_sel);
m_btn_down_action->setEnabled(has_sel);
}
/*
Delete the current action
*/
void
filter_edit::remove_action()
{
action_lvitem* a = lv_actions->selected_action();
if (!a || !a->m_act_ptr)
return;
filter_expr* e=m_current_expr;
QList<filter_action>::iterator iter;
for (iter=e->m_actions.begin(); iter!=e->m_actions.end(); ++iter) {
if (a->m_act_ptr==&(*iter)) {
e->m_actions.erase(iter);
e->m_dirty=true;
break;
}
}
delete a;
}
void
filter_edit::add_action()
{
if (!m_current_expr)
return;
filter_action_editor editor(this);
editor.setWindowTitle(tr("Add a filter action"));
int res=editor.exec();
if (res==QDialog::Accepted) {
filter_action a = editor.get_action();
action_lvitem* p = new action_lvitem;
lv_actions->addTopLevelItem(p);
m_current_expr->m_actions.push_back(a);
p->m_act_ptr=&(m_current_expr->m_actions.back());
p->setText(0, p->m_act_ptr->ui_text());
m_current_expr->m_dirty=true;
}
}
// called on double click on lv_actions
void
filter_edit::modify_action(QTreeWidgetItem* item, int column)
{
Q_UNUSED(item);
Q_UNUSED(column);
edit_action();
}
void
filter_edit::edit_action()
{
action_lvitem* lva = lv_actions->selected_action();
if (!lva)
return;
filter_action_editor editor(this);
editor.setWindowTitle(tr("Edit a filter action"));
editor.display_action(lva->m_act_ptr);
int res=editor.exec();
if (res==QDialog::Accepted) {
filter_action a = editor.get_action();
*(lva->m_act_ptr) = a;
lva->setText(0, a.ui_text());
m_current_expr->m_dirty=true;
}
}
void
filter_edit::help()
{
#if 0 // DEBUG
filter_expr* e=m_current_expr;
QList<filter_action>::iterator iter;
DBG_PRINTF(6, "current actions (m_current_action=0x%p", m_current_action);
for (iter=e->m_actions.begin(); iter!=e->m_actions.end(); ++iter) {
DBG_PRINTF(6,"\tm_str_atype=%s, m_action_string=%s",
iter->m_str_atype.latin1(), iter->m_action_string.latin1());
}
#endif
helper::show_help("filters");
}
void
filter_edit::move_action_up()
{
if (!m_current_expr)
return;
if (lv_expr->header()->sortIndicatorSection()!=0) {
QMessageBox::critical(this, tr("Error"), tr("Please sort by filter order (first column) to enable moving filters"));
return;
}
lv_actions->move_filter_action(-1, m_current_expr);
}
void
filter_edit::move_action_down()
{
if (!m_current_expr)
return;
lv_actions->move_filter_action(1, m_current_expr);
}
//
// focus_line_edit
//
focus_line_edit::focus_line_edit(QWidget* parent, const QString& name,
filter_edit* form) :
QLineEdit(parent)
{
m_form=form;
m_name=name;
connect(this, SIGNAL(returnPressed()), this, SLOT(validate()));
}
void
focus_line_edit::validate()
{
if (text()!=m_last_value && m_form->expr_fields_filled()) {
m_form->expr_update();
m_last_value=text();
}
}
void
focus_line_edit::setText(const QString& t)
{
m_last_value=t;
QLineEdit::setText(t);
setCursorPosition(0);
}
void
focus_line_edit::focusOutEvent(QFocusEvent * e)
{
DBG_PRINTF(6, "focusOutEvent()");
validate();
QLineEdit::focusOutEvent(e);
}
action_lvitem*
action_listview::selected_action()
{
return selectedItems().isEmpty() ? NULL:
static_cast<action_lvitem*>(selectedItems().at(0));
}
void
action_listview::keyPressEvent(QKeyEvent* e)
{
if (e->modifiers()!=0 || e->key()!=Qt::Key_Delete) {
QTreeWidget::keyPressEvent(e);
}
else
emit key_del();
}
void
action_listview::move_filter_action(int direction, filter_expr* filter)
{
action_lvitem* item = selected_action();
if (!item)
return;
int index = indexOfTopLevelItem(item);
int new_index;
DBG_PRINTF(5, "indexOfTopLevelItem=%d", index);
switch(direction) {
case -1:
new_index = index-1;
break;
case 1:
new_index = index+1;
break;
default:
return; // shouldn't happen
}
if (new_index<0 || index == new_index || new_index>=topLevelItemCount())
return;
/* We take out the item at 'index' and reinsert it above
'new_index'. The code below should work with any possible
relationship between 'index' and 'new_index', even though the
current UI limits 'new_index' to be near 'index' or at the top or
bottom of the list. */
DBG_PRINTF(5, "new_index=%d", new_index);
item = dynamic_cast<action_lvitem*>(takeTopLevelItem(index));
insertTopLevelItem(new_index, item);
setCurrentItem(item);
filter_action action = filter->m_actions.takeAt(index);
filter->m_actions.insert(new_index, action);
filter->m_dirty=true;
}
expr_line_edit::expr_line_edit(QWidget* parent) : QLineEdit(parent)
{
m_button = new QToolButton(this);
QPixmap pixmap = FT_MAKE_ICON(ICON16_IMPORTANT);
m_button->setIcon(QIcon(pixmap));
m_button->setIconSize(pixmap.size());
m_button->setCursor(Qt::ArrowCursor);
m_button->setStyleSheet("QToolButton { border: none; padding: 0px; }");
m_button->setEnabled(false);
connect(m_button, SIGNAL(clicked()), this, SLOT(button_clicked()));
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(update_button(const QString&)));
}
void
expr_line_edit::resizeEvent(QResizeEvent *)
{
QSize sz = m_button->sizeHint();
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
m_button->move(rect().right() - frameWidth - sz.width(),
(rect().bottom() + 2 - sz.height())/2);
}
void
expr_line_edit::update_button(const QString& text)
{
m_button->setEnabled(!text.isEmpty());
}
void
expr_line_edit::validate()
{
}
/*
show= 0:not visible, 1:warning icon, 2:ok icon
*/
void
expr_line_edit::show_button(int show)
{
if (show) {
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
setStyleSheet(QString("QLineEdit { padding-right: %1px; } ").arg(m_button->sizeHint().width() + frameWidth + 1));
QSize msz = minimumSizeHint();
setMinimumSize(qMax(msz.width(), m_button->sizeHint().width() + frameWidth * 2 + 2),
qMax(msz.height(), m_button->sizeHint().height() + frameWidth * 2 + 2));
QPixmap pixmap;
if (show==1) {
pixmap = FT_MAKE_ICON(ICON16_IMPORTANT);
m_button->setToolTip(tr("Error while evaluating expression. Click to read the error message."));
}
else {
pixmap = FT_MAKE_ICON(ICON16_DIALOG_OK);
m_button->setToolTip(tr("No error found in expression."));
}
m_button->setIcon(QIcon(pixmap));
m_button->setIconSize(pixmap.size());
m_button->show();
}
else {
m_button->setToolTip("");
m_button->hide();
}
}
void
expr_line_edit::button_clicked()
{
emit toolbutton_clicked();
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files