/*
 * eventdlg.cpp - dialog for sending / receiving messages and events
 * Copyright (C) 2001-2002  Justin Karneges
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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, see <https://www.gnu.org/licenses/>.
 *
 */

#include "eventdlg.h"

#include "accountlabel.h"
#include "accountscombobox.h"
#include "alerticon.h"
#include "common.h"
#include "desktoputil.h"
#include "fancylabel.h"
#include "httpauthmanager.h"
#include "iconlabel.h"
#include "iconselect.h"
#include "icontoolbutton.h"
#include "iris/xmpp_htmlelement.h"
#include "iris/xmpp_serverinfomanager.h"
#include "jidutil.h"
#include "psiaccount.h"
#include "psicon.h"
#include "psicontactlist.h"
#include "psievent.h"
#include "psiiconset.h"
#include "psioptions.h"
#include "psirichtext.h"
#include "psitextview.h"
#include "psitooltip.h"
#include "shortcutmanager.h"
#include "textutil.h"
#include "userlist.h"
#include "xdata_widget.h"

#include <QApplication>
#include <QCheckBox>
#include <QClipboard>
#include <QCloseEvent>
#include <QComboBox>
#include <QCursor>
#include <QDateTime>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFrame>
#include <QHBoxLayout>
#include <QIcon>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QList>
#include <QMessageBox>
#include <QMimeData>
#include <QPushButton>
#include <QResizeEvent>
#include <QShowEvent>
#include <QStringList>
#include <QTextDocumentFragment>
#include <QTimer>
#include <QVBoxLayout>

static const QString geometryOption = "options.ui.message.size";

static QString findJid(const QString &s, int x, int *p1, int *p2)
{
    // scan backward for the beginning of a Jid
    int n1 = x;
    if (n1 >= int(s.length()))
        n1 = s.length() - 1;
    for (; n1 >= 0; --n1) {
        if (s.at(n1) == ',') {
            ++n1;
            break;
        }
    }
    if (n1 < 0)
        n1 = 0;
    // go forward, skipping whitespace
    for (; n1 < int(s.length()); ++n1) {
        if (!s.at(n1).isSpace())
            break;
    }

    // now find the end of the Jid
    int n2 = n1;
    for (; n2 < int(s.length()); ++n2) {
        if (s.at(n2) == ',')
            break;
    }
    --n2;
    // scan backwards from the end, skipping whitespace
    for (; n2 > n1; --n2) {
        if (!s.at(n2).isSpace())
            break;
    }
    ++n2;

    *p1 = n1;
    *p2 = n2;

    return s.mid(n1, n2 - n1);
}

//----------------------------------------------------------------------------
// ELineEdit - a line edit that handles advanced Jid entry
//----------------------------------------------------------------------------
ELineEdit::ELineEdit(EventDlg *parent, const char *name) : QLineEdit(parent)
{
    setObjectName(name);
    setAcceptDrops(true);
}

void ELineEdit::dragEnterEvent(QDragEnterEvent *e)
{
    if (e->mimeData()->hasText())
        e->accept();
    else
        e->ignore();
}

void ELineEdit::dropEvent(QDropEvent *e)
{
    QString str = e->mimeData()->text();

    if (!str.isEmpty()) {
        Jid jid(str);
        if (!jid.isValid())
            setText(str);
        else {
            EventDlg *e    = static_cast<EventDlg *>(parent());
            QString   name = e->jidToString(jid);

            bool hasComma = false, hasText = false;
            int  len = text().length();
            while (--len >= 0) {
                QChar c = text().at(len);
                if (c == ',') {
                    hasComma = true;
                    break;
                } else if (!c.isSpace()) {
                    hasText = true;
                    break;
                }
            }

            if (hasComma || !hasText)
                setText(text() + ' ' + name);
            else
                setText(text() + ", " + name);
        }
        setCursorPosition(text().length());

        repaint();
    }
}

void ELineEdit::keyPressEvent(QKeyEvent *e)
{
    QLineEdit::keyPressEvent(e);
    if (e->text().length() == 1 && e->text().at(0).isLetterOrNumber())
        emit tryComplete();
}

// QMenu *ELineEdit::createPopupMenu()
//{
//    EventDlg *e = (EventDlg *)parent();
//    int xoff = mapFromGlobal(QCursor::pos()).x();
//    int x = characterAt(d->hscroll + xoff, 0);
//    QString str = text();
//    int p1, p2;
//    QString j = findJid(str, x, &p1, &p2);
//    if(j.isEmpty())
//        return QLineEdit::createPopupMenu();
//
//    UserResourceList list = e->getResources(j);
//
//    setCursorPosition(p1);
//    setSelection(p1, p2-p1);
//
//    url = list;
//    url.sort();
//
//    int n = 100;
//    QMenu *rm = new QMenu(this);
//    connect(rm, SIGNAL(triggered(QAction*)), SLOT(resourceMenuActivated(QAction*)));
//
//    rm->addAction(tr("Recipient Default"));
//
//    if(!list.isEmpty()) {
//        rm->addSeparator();
//
//        for(UserResourceList::ConstIterator it = url.begin(); it != url.end(); ++it) {
//            const UserResource &r = *it;
//            QString name;
//            QString data;
//            if(r.name().isEmpty())
//                name = QObject::tr("[blank]");
//                data = "";
//            else
//                name = r.name();
//                data = name;
//
//            QAction *a = rm->addAction(PsiIconset::instance()->status(r.status()), name);
//            a.setData(data);
//        }
//    }
//
//    //pm->insertItem("Change Resource", rm, -1, 0);
//    //pm->insertSeparator(1);
//
//    return rm;
//}

void ELineEdit::resourceMenuActivated(QAction *x)
{
    if (x->data().toString().isNull())
        return;

    url.clear();
    emit changeResource(x->data().toString());
}

//----------------------------------------------------------------------------
// AttachView
//----------------------------------------------------------------------------
class AttachViewItem : public QListWidgetItem {
public:
    AttachViewItem(const QString &_url, const QString &_desc, AttachView *par) : QListWidgetItem(par)
    {
        type = 0;
        url  = _url;
        desc = _desc;

        setIcon(IconsetFactory::icon("psi/www").icon());
        setText(url + " (" + desc + ')');
        // setMultiLinesEnabled(true);
    }

    AttachViewItem(const QString &_gc, const QString &from, const QString &reason, const QString &_password,
                   AttachView *par) : QListWidgetItem(par)
    {
        type     = 1;
        gc       = _gc;
        password = _password;

        setIcon(IconsetFactory::icon("psi/groupChat").icon());
        QString text;
        if (!from.isEmpty())
            text = QObject::tr("Invitation to %1 from %2").arg(gc, from);
        else
            text = QObject::tr("Invitation to %1").arg(gc);
        if (!reason.isEmpty()) {
            text += QString(" (%1)").arg(reason);
        }
        setText(text);
        // setMultiLinesEnabled(true);
    }

    int rtti() const { return 9100; }

    QString url, desc;
    QString gc, password;
    int     type;
};

AttachView::AttachView(QWidget *parent) : QListWidget(parent)
{
    v_readOnly = false;
    // addColumn(tr("Attachments"));
    // setResizeMode(QListWidget::AllColumns);

    connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem *)), SLOT(qlv_doubleClicked(QListWidgetItem *)));
};

AttachView::~AttachView() { }

void AttachView::setReadOnly(bool b) { v_readOnly = b; }

void AttachView::urlAdd(const QString &url, const QString &desc)
{
    new AttachViewItem(url, desc, this);
    emit childCountChanged();
}

void AttachView::gcAdd(const QString &gc, const QString &from, const QString &reason, const QString &password)
{
    new AttachViewItem(gc, from, reason, password, this);
    emit childCountChanged();
}

void AttachView::contextMenuEvent(QContextMenuEvent *e)
{
    AttachViewItem *i = !selectedItems().isEmpty() ? static_cast<AttachViewItem *>(selectedItems().first()) : nullptr;
    if (!i)
        return;

    QAction *goToUrlAction       = nullptr;
    QAction *copyLocationAction  = nullptr;
    QAction *joinGroupChatAction = nullptr;
    QAction *removeAction        = nullptr;

    QMenu pm(this);
    if (i->type == 0) {
        goToUrlAction      = pm.addAction(tr("Go to &URL..."));
        copyLocationAction = pm.addAction(tr("Copy location"));
    } else {
        joinGroupChatAction = pm.addAction(tr("Join &Groupchat..."));
    }
    pm.addSeparator();
    removeAction = pm.addAction(tr("Remove"));

    if (v_readOnly) {
        removeAction->setEnabled(false);
    }

    QAction *n = pm.exec(e->globalPos());
    if (!n)
        return;

    if (n == goToUrlAction) {
        goURL(i->url);
    } else if (n == joinGroupChatAction) {
        emit actionGCJoin(i->gc, i->password);
    } else if (n == copyLocationAction) {
        QApplication::clipboard()->setText(i->url, QClipboard::Clipboard);
        if (QApplication::clipboard()->supportsSelection())
            QApplication::clipboard()->setText(i->url, QClipboard::Selection);
    } else if (n == removeAction) {
        takeItem(row(i));
        delete i;
        emit childCountChanged();
    }
}

void AttachView::qlv_doubleClicked(QListWidgetItem *lvi)
{
    AttachViewItem *i = static_cast<AttachViewItem *>(lvi);
    if (!i)
        return;

    if (i->type == 0)
        goURL(i->url);
    else
        emit actionGCJoin(i->gc, i->password);
}

void AttachView::goURL(const QString &_url)
{
    if (_url.isEmpty())
        return;

    QString url = _url;
    if (url.indexOf("://") == -1)
        url.insert(0, "http://");

    DesktopUtil::openUrl(url);
}

UrlList AttachView::urlList() const
{
    UrlList list;

    for (int index = 0; index < count(); ++index) {
        AttachViewItem *i = static_cast<AttachViewItem *>(item(index));
        list += Url(i->url, i->desc);
    }

    return list;
}

void AttachView::addUrlList(const UrlList &list)
{
    for (const auto &u : list) {
        urlAdd(u.url(), u.desc());
    }
}

//----------------------------------------------------------------------------
// AddUrlDlg
//----------------------------------------------------------------------------
AddUrlDlg::AddUrlDlg(QWidget *parent) : QDialog(parent)
{
    setupUi(this);
#ifndef Q_OS_MAC
    setWindowIcon(IconsetFactory::icon("psi/www").icon());
#endif
    setModal(true);

    connect(pb_close, SIGNAL(clicked()), SLOT(reject()));
    connect(pb_ok, SIGNAL(clicked()), SLOT(accept()));
}

AddUrlDlg::~AddUrlDlg() { }

//----------------------------------------------------------------------------
// EventDlg - a window to read and write events
//----------------------------------------------------------------------------
class EventDlg::Private : public QObject {
    Q_OBJECT
public:
    Private(EventDlg *d) { dlg = d; }

    ~Private() { setNextAnim(nullptr); }

    void setNextAnim(PsiIcon *anim)
    {
        if (nextAnim_) {
            delete nextAnim_;
            nextAnim_ = nullptr;
        }

        if (anim) {
            nextAnim_ = new AlertIcon(anim);
        }
    }

    PsiIcon *nextAnim() const { return nextAnim_; }

    EventDlg   *dlg = nullptr;
    PsiCon     *psi = nullptr;
    PsiAccount *pa  = nullptr;

    QLabel           *lb_identity = nullptr;
    QLabel           *lb_time     = nullptr;
    QLabel           *lb_count    = nullptr;
    AccountsComboBox *cb_ident    = nullptr;
    QComboBox        *cb_type     = nullptr;
    AccountLabel     *lb_ident    = nullptr;
    IconLabel        *lb_status   = nullptr;
    ELineEdit        *le_to       = nullptr;
    QLineEdit        *le_from     = nullptr;
    QLineEdit        *le_subj     = nullptr;
    IconToolButton   *tb_url = nullptr, *tb_info = nullptr, *tb_history = nullptr, *tb_icon = nullptr;
    IconButton *pb_next = nullptr, *pb_close = nullptr, *pb_quote = nullptr, *pb_deny = nullptr, *pb_send = nullptr,
               *pb_reply = nullptr, *pb_chat = nullptr, *pb_auth = nullptr, *pb_http_confirm = nullptr,
               *pb_http_deny = nullptr, *pb_form_submit = nullptr, *pb_form_cancel = nullptr;
    QCheckBox   *ck_all_auth       = nullptr;
    PsiTextView *mle               = nullptr;
    AttachView  *attachView        = nullptr;
    QTimer      *whois             = nullptr;
    PsiIcon     *anim              = nullptr;
    QWidget     *w_http_id         = nullptr;
    QLineEdit   *le_http_id        = nullptr;
    QWidget     *xdata_form        = nullptr;
    XDataWidget *xdata             = nullptr;
    QLabel      *xdata_instruction = nullptr;

    PsiHttpAuthRequest  httpAuthRequest;
    RosterExchangeItems rosterExchangeItems;
    QString             lastWhois;
    Jid                 jid, realJid;
    Message             m;
    QString             thread;
    QStringList         completionList;
    QStringList         sendLeft;

    bool composing  = false;
    bool urlOnShow  = false;
    int  transid    = 0;
    int  nextAmount = 0;

private:
    PsiIcon *nextAnim_ = nullptr;

private slots:
    void ensureEditPosition()
    {
        QTextCursor cursor = mle->textCursor();
        cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
        cursor.clearSelection();
        mle->setTextCursor(cursor);
    }

public slots:
    void addEmoticon(const PsiIcon *icon) { addEmoticon(icon->defaultText()); }

    void addEmoticon(QString text)
    {
        if (!dlg->isActiveWindow()) {
            return;
        }

        PsiRichText::addEmoticon(mle, text);
    }

    void updateCounter() { lb_count->setNum(int(mle->getPlainText().length())); }
};

EventDlg::EventDlg(const QString &to, PsiCon *psi, PsiAccount *pa) : AdvancedWidget<QWidget>(nullptr)
{
    setAttribute(Qt::WA_DeleteOnClose);
    d            = new Private(this);
    d->composing = true;
    d->psi       = psi;
    d->pa        = nullptr;
    d->psi->dialogRegister(this);

    d->anim       = nullptr;
    d->nextAmount = 0;
    d->urlOnShow  = false;

    setAccount(pa);

    d->whois = new QTimer;
    d->whois->setSingleShot(true);
    connect(d->whois, SIGNAL(timeout()), SLOT(doWhois()));

    init();

    d->cb_ident->setAccount(pa);

    d->pb_send->show();
    d->le_to->setText(expandAddresses(to, false));
    d->le_to->setCursorPosition(0);

    if (PsiOptions::instance()->getOption("options.ui.message.auto-grab-urls-from-clipboard").toBool()) {
        // url in clipboard?
        QClipboard *cb   = QApplication::clipboard();
        QString     text = cb->text(QClipboard::Clipboard);
        if (text.isEmpty() && cb->supportsSelection()) {
            text = cb->text(QClipboard::Selection);
        }
        if (!text.isEmpty()) {
            if (text.left(7) == "http://") {
                d->attachView->urlAdd(text, "");
                cb->clear(QClipboard::Selection);
                cb->clear(QClipboard::Clipboard);
            }
        }
    }

    updateIdentity(pa);

    X11WM_CLASS("event");

    if (d->le_to->text().isEmpty())
        d->le_to->setFocus();
    else
        d->mle->setFocus();
}

EventDlg::EventDlg(const Jid &j, PsiAccount *pa, bool unique) : AdvancedWidget<QWidget>(nullptr)
{
    setAttribute(Qt::WA_DeleteOnClose);
    d            = new Private(this);
    d->composing = false;
    d->psi       = pa->psi();
    d->pa        = pa;
    d->jid       = j;
    d->realJid   = j;

    if (unique)
        d->pa->dialogRegister(this, j);
    else
        d->pa->dialogRegister(this);

    d->anim       = nullptr;
    d->nextAmount = 0;
    d->urlOnShow  = false;

    init();

    d->le_from->setText(expandAddresses(d->jid.full(), false));
    d->le_from->setCursorPosition(0);

    doWhois();

    d->pb_next->show();

    d->pb_close->setFocus();

    X11WM_CLASS("event");

    setTime(QDateTime::currentDateTime(), true);
}

EventDlg::~EventDlg()
{
    if (d->composing) {
        delete d->whois;
        d->psi->dialogUnregister(this);
    } else {
        d->pa->dialogUnregister(this);
    }
    delete d;
}

void EventDlg::init()
{
    QVBoxLayout *vb1 = new QVBoxLayout(this);
    vb1->setContentsMargins(4, 4, 4, 4);
    vb1->setSpacing(4);

    // first row
    QHBoxLayout *hb1 = new QHBoxLayout;
    hb1->setSpacing(4);
    vb1->addLayout(hb1);
    d->lb_identity = new QLabel(tr("Identity:"), this);
    hb1->addWidget(d->lb_identity);

    d->transid = -1;

    if (d->composing) {
        d->lb_ident = nullptr;
        d->cb_ident = d->psi->accountsComboBox(this);
        connect(d->cb_ident, SIGNAL(activated(PsiAccount *)), SLOT(updateIdentity(PsiAccount *)));
        hb1->addWidget(d->cb_ident);
    } else {
        d->cb_ident = nullptr;
        d->lb_ident = new AccountLabel(this);
        d->lb_ident->setAccount(d->pa);
        d->lb_ident->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
        hb1->addWidget(d->lb_ident);
    }
    connect(d->psi, SIGNAL(accountCountChanged()), this, SLOT(updateIdentityVisibility()));
    updateIdentityVisibility();

    // second row
    QHBoxLayout *hb2 = new QHBoxLayout;
    hb2->setSpacing(4);
    vb1->addLayout(hb2);

    d->lb_status = new IconLabel(this);
    PsiToolTip::install(d->lb_status);
    d->lb_status->setPsiIcon(IconsetFactory::iconPtr("status/noauth"));

    QLabel *l;
    if (d->composing) {
        l = new QLabel(tr("To:"), this);
        hb2->addWidget(l);
        hb2->addWidget(d->lb_status);
        d->le_to = new ELineEdit(this);
        connect(d->le_to, SIGNAL(textChanged(const QString &)), SLOT(to_textChanged(const QString &)));
        connect(d->le_to, SIGNAL(changeResource(const QString &)), SLOT(to_changeResource(const QString &)));
        connect(d->le_to, SIGNAL(tryComplete()), SLOT(to_tryComplete()));
        hb2->addWidget(d->le_to);
    } else {
        l = new QLabel(tr("From:"), this);
        hb2->addWidget(l);
        hb2->addWidget(d->lb_status);
        d->le_from = new QLineEdit(this);
        d->le_from->setReadOnly(true);
        hb2->addWidget(d->le_from);
    }

    if (d->composing) {
        l = new QLabel(tr("Type:"), this);
        hb2->addWidget(l);
        d->cb_type = new QComboBox(this);
        d->cb_type->addItem(tr("Normal"));
        d->cb_type->addItem(tr("Chat"));
        hb2->addWidget(d->cb_type);
    } else {
        l = new QLabel(tr("Time:"), this);
        hb2->addWidget(l);
        d->lb_time = new QLabel(this);
        d->lb_time->setFrameShadow(QFrame::Sunken);
        d->lb_time->setFrameShape(QFrame::Panel);
        hb2->addWidget(d->lb_time);
    }

    // icon select
    // connect(d->psi->iconSelectPopup(), SIGNAL(iconSelected(const PsiIcon *)), d, SLOT(addEmoticon(const PsiIcon *)));
    connect(d->psi->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString)));

    d->tb_icon = new IconToolButton(this);
    d->tb_icon->setPsiIcon(IconsetFactory::iconPtr("psi/smile"));
    d->tb_icon->setMenu(d->psi->iconSelectPopup());
    d->tb_icon->setPopupMode(QToolButton::InstantPopup);
    // d->tb_icon->setPopupDelay(1);
    d->tb_icon->setToolTip(tr("Select icon"));
    if (!d->composing)
        d->tb_icon->setEnabled(false);

    // message length counter
    d->le_subj  = new QLineEdit(this);
    d->lb_count = new QLabel(this);
    d->lb_count->setToolTip(tr("Message length"));
    d->lb_count->setFixedWidth(40);
    d->lb_count->setFrameShadow(QFrame::Sunken);
    d->lb_count->setFrameShape(QFrame::Panel);
    d->lb_count->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    d->lb_count->setNum(0);

    d->tb_url = new IconToolButton(this);
    connect(d->tb_url, SIGNAL(clicked()), SLOT(addUrl()));
    d->tb_url->setToolTip(tr("Add URL"));
    d->tb_info = new IconToolButton(this);
    connect(d->tb_info, SIGNAL(clicked()), SLOT(doInfo()));
    d->tb_info->setToolTip(tr("User info"));
    d->tb_history = new IconToolButton(this);
    connect(d->tb_history, SIGNAL(clicked()), SLOT(doHistory()));
    d->tb_history->setToolTip(tr("Message history"));

    QList<IconToolButton *> toolButtons;
    toolButtons << d->tb_url << d->tb_info << d->tb_history;
    toolButtons << d->tb_icon;
    for (IconToolButton *toolButton : std::as_const(toolButtons))
        if (toolButton)
            toolButton->setFocusPolicy(Qt::NoFocus);

    QHBoxLayout *hb3 = new QHBoxLayout;
    hb3->setSpacing(4);
    vb1->addLayout(hb3);

    //    if(d->composing /* && config->showsubject */) {
    if (PsiOptions::instance()->getOption("options.ui.message.show-subjects").toBool()) {
        // third row
        l = new QLabel(tr("Subject:"), this);
        hb3->addWidget(l);
        hb3->addWidget(d->le_subj);
        hb3->addWidget(d->lb_count);
        hb3->addWidget(d->tb_icon);
        hb3->addWidget(d->tb_url);
        hb3->addWidget(d->tb_info);
        hb3->addWidget(d->tb_history);

        if (!d->composing) {
            d->le_subj->setReadOnly(true);
            d->tb_url->setEnabled(false);
        }

    } else {
        d->le_subj->hide();
        hb2->addWidget(d->lb_count);
        hb2->addWidget(d->tb_icon);
        hb2->addWidget(d->tb_url);
        hb2->addWidget(d->tb_info);
        hb2->addWidget(d->tb_history);
    }

    // text area
    d->mle = new PsiTextView(this);
    // d->mle->setDialog(this);
    d->mle->setReadOnly(false);
    d->mle->setUndoRedoEnabled(true);
    d->mle->setMinimumHeight(50);
    vb1->addWidget(d->mle);

    connect(d->mle, SIGNAL(textChanged()), d, SLOT(updateCounter()));

    if (d->composing) {
        d->mle->setAcceptRichText(false);
    } else {
        d->mle->setReadOnly(true);
        d->mle->setUndoRedoEnabled(false);
    }

    // attachment view
    d->attachView = new AttachView(this);
    d->attachView->setFixedHeight(80);
    d->attachView->hide();
    connect(d->attachView, SIGNAL(childCountChanged()), SLOT(showHideAttachView()));
    connect(d->attachView, SIGNAL(actionGCJoin(const QString &, const QString &)),
            SLOT(actionGCJoin(const QString &, const QString &)));
    vb1->addWidget(d->attachView);

    if (!d->composing)
        d->attachView->setReadOnly(true);
    else
        QTimer::singleShot(0, d, SLOT(ensureEditPosition()));

    // http auth transaction id
    d->w_http_id            = new QWidget(this);
    QHBoxLayout *hb_http_id = new QHBoxLayout(d->w_http_id);
    hb_http_id->setContentsMargins(0, 0, 0, 0);
    hb_http_id->setSpacing(4);
    d->le_http_id = new QLineEdit(d->w_http_id);
    l             = new QLabel(tr("Transaction &identifier:"), d->w_http_id);
    l->setBuddy(d->le_http_id);
    hb_http_id->addWidget(l);
    hb_http_id->addWidget(d->le_http_id);

    vb1->addWidget(d->w_http_id);
    d->w_http_id->hide();

    // data form
    d->xdata      = new XDataWidget(d->psi, this, d->pa->client(), d->jid);
    d->xdata_form = new QWidget(this);
    d->xdata_form->setFocusProxy(d->xdata);
    QVBoxLayout *vb_xdata = new QVBoxLayout(d->xdata_form);
    d->xdata_instruction  = new QLabel(d->xdata_form);
    vb_xdata->addWidget(d->xdata_instruction);
    vb_xdata->addWidget(d->xdata);
    vb1->addStretch();
    vb1->addWidget(d->xdata_form);
    d->xdata_form->hide();

    // bottom row
    QHBoxLayout *hb4 = new QHBoxLayout;
    vb1->addLayout(hb4);

    d->pb_close = new IconButton(this);
    d->pb_close->setText(tr("&Close"));
    connect(d->pb_close, SIGNAL(clicked()), SLOT(close()));
    d->pb_close->setMinimumWidth(80);
    hb4->addWidget(d->pb_close);

    hb4->addStretch(1);

    d->pb_next = new IconButton(this);
    connect(d->pb_next, SIGNAL(clicked()), SLOT(doReadNext()));
    d->pb_next->setText(tr("&Next"));
    d->pb_next->hide();
    d->pb_next->setMinimumWidth(96);
    d->pb_next->setEnabled(false);
    hb4->addWidget(d->pb_next);

    d->pb_quote = new IconButton(this);
    d->pb_quote->setText(tr("&Quote"));
    connect(d->pb_quote, SIGNAL(clicked()), SLOT(doQuote()));
    d->pb_quote->hide();
    d->pb_quote->setMinimumWidth(96);
    hb4->addWidget(d->pb_quote);

    d->ck_all_auth = new QCheckBox(tr("For all"), this);
    d->ck_all_auth->hide();
    hb4->addWidget(d->ck_all_auth);

    d->pb_deny = new IconButton(this);
    d->pb_deny->setText(tr("&Deny"));
    connect(d->pb_deny, SIGNAL(clicked()), SLOT(doDeny()));
    d->pb_deny->hide();
    d->pb_deny->setMinimumWidth(96);
    hb4->addWidget(d->pb_deny);

    d->pb_auth = new IconButton(this);
    d->pb_auth->setText(tr("&Add/Auth"));
    connect(d->pb_auth, SIGNAL(clicked()), SLOT(doAuth()));
    d->pb_auth->setPsiIcon(IconsetFactory::iconPtr("psi/addContact"));
    d->pb_auth->hide();
    d->pb_auth->setMinimumWidth(96);
    hb4->addWidget(d->pb_auth);

    d->pb_send = new IconButton(this);
    d->pb_send->setText(tr("&Send"));
    connect(d->pb_send, SIGNAL(clicked()), SLOT(doSend()));
    d->pb_send->hide();
    d->pb_send->setMinimumWidth(96);
    hb4->addWidget(d->pb_send);

    d->pb_chat = new IconButton(this);
    d->pb_chat->setText(tr("&Chat"));
    connect(d->pb_chat, SIGNAL(clicked()), SLOT(doChat()));
    d->pb_chat->hide();
    d->pb_chat->setMinimumWidth(96);
    hb4->addWidget(d->pb_chat);

    d->pb_reply = new IconButton(this);
    d->pb_reply->setText(tr("&Reply"));
    connect(d->pb_reply, SIGNAL(clicked()), SLOT(doReply()));
    d->pb_reply->hide();
    d->pb_reply->setMinimumWidth(96);
    hb4->addWidget(d->pb_reply);

    d->pb_http_confirm = new IconButton(this);
    d->pb_http_confirm->setText(tr("C&onfirm"));
    connect(d->pb_http_confirm, SIGNAL(clicked()), SLOT(doHttpConfirm()));
    d->pb_http_confirm->hide();
    d->pb_http_confirm->setMinimumWidth(96);
    hb4->addWidget(d->pb_http_confirm);

    d->pb_http_deny = new IconButton(this);
    d->pb_http_deny->setText(tr("&Deny"));
    connect(d->pb_http_deny, SIGNAL(clicked()), SLOT(doHttpDeny()));
    d->pb_http_deny->hide();
    d->pb_http_deny->setMinimumWidth(96);
    hb4->addWidget(d->pb_http_deny);

    // data form submit button
    d->pb_form_submit = new IconButton(this);
    d->pb_form_submit->setText(tr("&Submit"));
    connect(d->pb_form_submit, SIGNAL(clicked()), SLOT(doFormSubmit()));
    d->pb_form_submit->hide();
    d->pb_form_submit->setMinimumWidth(96);
    hb4->addWidget(d->pb_form_submit);

    // data form cancel button
    d->pb_form_cancel = new IconButton(this);
    d->pb_form_cancel->setText(tr("&Cancel"));
    connect(d->pb_form_cancel, SIGNAL(clicked()), SLOT(doFormCancel()));
    d->pb_form_cancel->hide();
    d->pb_form_cancel->setMinimumWidth(96);
    hb4->addWidget(d->pb_form_cancel);

    if (d->composing)
        setTabOrder(d->le_to, d->le_subj);
    else
        setTabOrder(d->le_from, d->le_subj);
    setTabOrder(d->le_subj, d->mle);

    setGeometryOptionPath(geometryOption);

    optionsUpdate();

    // ShortcutManager::connect("common.close", this, SLOT(close()));
    ShortcutManager::connect("common.user-info", this, SLOT(doInfo()));
    ShortcutManager::connect("common.history", this, SLOT(doHistory()));
    // ShortcutManager::connect("message.send", this, SLOT(doSend()));
}

bool EventDlg::messagingEnabled() { return PsiOptions::instance()->getOption("options.ui.message.enabled").toBool(); }

void EventDlg::setAccount(PsiAccount *pa)
{
    if (d->pa)
        disconnect(d->pa, SIGNAL(updatedActivity()), this, SLOT(accountUpdatedActivity()));

    d->pa = pa;
    connect(d->pa, SIGNAL(updatedActivity()), this, SLOT(accountUpdatedActivity()));
}

void EventDlg::updateIdentity(PsiAccount *pa)
{
    if (!pa) {
        close();
        return;
    }

    setAccount(pa);

    buildCompletionList();
    doWhois(true);
}

void EventDlg::updateIdentityVisibility()
{
    bool visible = d->psi->contactList()->enabledAccounts().count() > 1;
    if (d->cb_ident)
        d->cb_ident->setVisible(visible);
    if (d->lb_ident)
        d->lb_ident->setVisible(visible);
    d->lb_identity->setVisible(visible);
}

void EventDlg::accountUpdatedActivity()
{
    // TODO: act on account activity change
}

QString EventDlg::text() const { return d->mle->getPlainText(); }

bool EventDlg::isForAll() const { return d->ck_all_auth->isChecked(); }

void EventDlg::setHtml(const QString &s)
{
    d->mle->clear();
    d->mle->appendText(s);
}

void EventDlg::setSubject(const QString &s) { d->le_subj->setText(s); }

void EventDlg::setThread(const QString &t) { d->thread = t; }

void EventDlg::setUrlOnShow() { d->urlOnShow = true; }

PsiAccount *EventDlg::psiAccount() { return d->pa; }

QStringList EventDlg::stringToList(const QString &s, bool enc) const
{
    QStringList list;

    int x1, x2;
    x1 = 0;
    x2 = 0;
    while (1) {
        // scan along for a comma
        bool found = false;
        for (int n = x1; n < int(s.length()); ++n) {
            if (s.at(n) == ',') {
                found = true;
                x2    = n;
                break;
            }
        }
        if (!found)
            x2 = s.length();

        QString c = s.mid(x1, (x2 - x1));
        QString j;
        if (enc)
            j = findJidInString(c);
        else
            j = c;
        if (j.isEmpty())
            j = c;

        j = j.trimmed();
        // printf("j=[%s]\n", j.latin1());
        if (!j.isEmpty())
            list += j;

        if (!found)
            break;
        x1 = x2 + 1;
    }

    return list;
}

QString EventDlg::findJidInString(const QString &s) const
{
    int a = s.indexOf('<');
    if (a != -1) {
        ++a;
        int b = s.indexOf('>', a);
        if (b != -1)
            return s.mid(a, b - a); // disabled JIDUtil::decode822 since jids already stringpreped
    }
    return "";
}

QString EventDlg::expandAddresses(const QString &in, bool enc) const
{
    // printf("in: [%s]\n", in.latin1());
    QString     str;
    QStringList list  = stringToList(in, enc);
    bool        first = true;
    for (const auto &item : list) {
        if (!first)
            str += ", ";
        first = false;

        Jid                   j(item);
        QList<UserListItem *> ul = d->pa->findRelevant(j);
        if (ul.isEmpty()) {
            str += j.full();
            continue;
        }
        UserListItem *u = ul.first();

        Jid jid;
        if (j.resource().isEmpty())
            jid = u->jid().full();
        else
            jid = u->jid().withResource(j.resource());

        QString name;
        if (!u->name().isEmpty())
            name += u->name()
                + QString(" <%1>").arg(jid.full()); // disabled JIDUtil::encode822 since jid is aready stringpreped
        else
            name = jid.full(); // disabled JIDUtil::encode822 since jid is aready stringpreped
        str += name;
    }

    // printf("expanding: [%s]\n", str.latin1());
    return str;
}

void EventDlg::to_textChanged(const QString &) { d->whois->start(250); }

void EventDlg::to_changeResource(const QString &r)
{
    QString str   = d->le_to->text();
    int     start = d->le_to->selectionStart();
    // int len = d->le_to->selectedText().length();
    if (start == -1) {
        // printf("bad selection\n");
        return;
    }
    // printf("selection: [%d,%d]\n", start, len);

    int     p1, p2;
    QString s = findJid(str, start, &p1, &p2);
    QString j = findJidInString(s);
    if (j.isEmpty())
        j = s;
    Jid jid(j);
    if (!jid.isValid()) {
        // printf("invalid jid\n");
        return;
    }
    // printf("s=[%s], j=[%s], p: [%d,%d]\n", s.latin1(), j.latin1(), p1, p2);

    QString js = jidToString(jid, r);
    // printf("js=[%s]\n", js.latin1());
    /*str.remove(start, len);
    str.insert(start, js);
    d->le_to->deselect();
    d->le_to->setCursorPosition(0);
    d->le_to->setText(str);
    //d->le_to->setCursorPosition(start + js.length());*/
    d->le_to->insert(js);
}

void EventDlg::to_tryComplete()
{
    if (!PsiOptions::instance()->getOption("options.ui.message.use-jid-auto-completion").toBool())
        return;

    QString str = d->le_to->text();
    int     x   = d->le_to->cursorPosition();

    int     p1, p2;
    QString s = findJid(str, x, &p1, &p2);
    if (s.length() < 1 || x != p2)
        return;

    for (auto name : std::as_const(d->completionList)) {
        if (s.length() > name.length())
            continue;

        bool ok = true;
        int  n;
        for (n = 0; n < int(s.length()); ++n) {
            if (s.at(n).toLower() != name.at(n).toLower()) {
                ok = false;
                break;
            }
        }
        name = name.mid(n);
        if (ok) {
            d->le_to->insert(name);
            d->le_to->setCursorPosition(x);
            d->le_to->setSelection(x, name.length());
            break;
        }
    }
}

void EventDlg::buildCompletionList()
{
    d->completionList.clear();

    d->completionList += d->pa->jid().full();

    const auto &list = *d->pa->userList();
    for (UserListItem *u : list) {
        QString j = u->jid().full();
        if (!u->name().isEmpty())
            d->completionList += u->name() + " <" + j + '>';
        d->completionList += j;
    }
}

QString EventDlg::jidToString(const Jid &jid, const QString &r) const
{
    QString name;

    QList<UserListItem *> ul = d->pa->findRelevant(jid);
    if (!ul.isEmpty()) {
        UserListItem *u = ul.first();

        QString j;
        if (r.isEmpty())
            j = u->jid().full();
        else
            j = Jid(u->jid().bare()).withResource(r).full();

        if (!u->name().isEmpty())
            name = u->name() + QString(" <%1>").arg(JIDUtil::encode822(j));
        else
            name = JIDUtil::encode822(j);
    } else
        name = JIDUtil::encode822(jid.full());

    return name;
}

void EventDlg::doWhois(bool force)
{
    QString str;
    if (d->composing) {
        str = d->le_to->text();
        if (str == d->lastWhois && !force)
            return;
    } else {
        str = d->le_from->text();
    }

    // printf("whois: [%s]\n", str.latin1());
    d->lastWhois      = str;
    QStringList list  = stringToList(str);
    bool        found = false;
    if (list.count() == 1) {
        Jid j(list[0]);
        d->jid                   = j;
        QList<UserListItem *> ul = d->pa->findRelevant(j);
        if (!ul.isEmpty()) {
            d->tb_info->setEnabled(true);
            d->tb_history->setEnabled(true);
            found = true;
        }
        updateContact(d->jid);
    }
    if (!found) {
        d->jid = "";

        d->lb_status->setPsiIcon(IconsetFactory::iconPtr("status/noauth"));
        d->tb_info->setEnabled(false);
        d->tb_history->setEnabled(false);
        setWindowTitle(tr("Send Message"));
        d->lb_status->setToolTip(QString());
    }
}

UserResourceList EventDlg::getResources(const QString &s) const
{
    UserResourceList list;

    QString j = findJidInString(s);
    if (j.isEmpty())
        j = s;
    Jid jid(j);
    if (!jid.isValid())
        return list;

    QList<UserListItem *> ul = d->pa->findRelevant(jid);
    if (!ul.isEmpty()) {
        UserListItem *u = ul.first();
        if (u->isAvailable())
            return u->userResourceList();
    }

    return list;
}

void EventDlg::optionsUpdate()
{
    // update the font
    QFont f;
    f.fromString(PsiOptions::instance()->getOption("options.ui.look.font.message").toString());
    d->mle->setFont(f);

    // update status icon
    doWhois(true);

    if (PsiOptions::instance()->getOption("options.ui.message.show-character-count").toBool() && d->composing)
        d->lb_count->show();
    else
        d->lb_count->hide();

    if (PsiOptions::instance()->getOption("options.ui.emoticons.use-emoticons").toBool())
        d->tb_icon->show();
    else
        d->tb_icon->hide();

    // tool buttons: not required
    d->tb_url->setPsiIcon(IconsetFactory::iconPtr("psi/www"));
    d->tb_info->setPsiIcon(IconsetFactory::iconPtr("psi/vCard"));
    d->tb_history->setPsiIcon(IconsetFactory::iconPtr("psi/history"));

    // update the readnext icon
    if (d->nextAmount > 0)
        d->pb_next->forceSetPsiIcon(d->nextAnim());

        // update the widget icon
#ifndef Q_OS_MAC
    if (d->composing) {
        setWindowIcon(IconsetFactory::icon("psi/sendMessage").icon());
    } else {
        if (d->anim)
            setWindowIcon(d->anim->icon());
    }
#endif
}

void EventDlg::showEvent(QShowEvent *e)
{
    QWidget::showEvent(e);

    if (d->urlOnShow) {
        d->urlOnShow = false;
        QTimer::singleShot(1, this, SLOT(addUrl()));
    }
}

void EventDlg::keyPressEvent(QKeyEvent *e)
{
    // FIXMEKEY
    QKeySequence key = int(e->key()) + int(e->modifiers() & ~Qt::KeypadModifier);
    if (ShortcutManager::instance()->shortcuts("common.close").contains(key))
        close();
    else if (ShortcutManager::instance()->shortcuts("message.send").contains(key))
        doSend();
    else if (ShortcutManager::instance()->shortcuts("common.hide").contains(key))
        hide();
    else
        e->ignore();
}

void EventDlg::doSend()
{
    if (!d->composing)
        return;

    if (!d->pb_send->isEnabled())
        return;

    if (!d->pa->checkConnected(this))
        return;

    if (d->mle->getPlainText().isEmpty() && d->attachView->count() == 0) {
        QMessageBox::information(this, tr("Warning"), tr("Please type in a message first."));
        return;
    }

    QStringList list = stringToList(d->le_to->text());
    if (list.isEmpty()) {
        QMessageBox::warning(this, tr("Warning"), tr("No recipients have been specified!"));
        return;
    }

    Message m;
    if (d->cb_type->currentIndex() == 0)
        m.setType(Message::Type::Normal);
    else
        m.setType(Message::Type::Chat);

    m.setBody(d->mle->getPlainText());
    m.setSubject(d->le_subj->text());
    m.setUrlList(d->attachView->urlList());
    m.setTimeStamp(QDateTime::currentDateTime());
    m.setThread(d->thread);
    d->m = m;

    if (list.count() > 1 && !d->pa->serverInfoManager()->multicastService().isEmpty()
        && PsiOptions::instance()->getOption("options.enable-multicast").toBool()) {
        m.setTo(d->pa->serverInfoManager()->multicastService());
        for (const QString &recipient : list) {
            m.addAddress(Address(XMPP::Address::To, Jid(recipient)));
        }
        d->pa->dj_sendMessage(m, true);
    } else {
        for (const auto &item : list) {
            m.setTo(Jid(item));
            d->pa->dj_sendMessage(m, true);
        }
    }
    doneSend();
}

void EventDlg::doneSend() { close(); }

void EventDlg::doReadNext() { emit aReadNext(d->realJid); }

void EventDlg::doChat()
{
    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;

    Jid  j(list[0]);
    emit aChat(j);
}

void EventDlg::doReply()
{
    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;
    Jid  j(list[0]);
    emit aReply(j, "", d->le_subj->text(), d->thread);
}

void EventDlg::doQuote()
{
    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;
    Jid j(list[0]);

    QString body = TextUtil::rich2plain(d->mle->getHtml());
    emit    aReply(j, body, d->le_subj->text(), d->thread);
}

void EventDlg::doDeny()
{
    if (!d->pa->checkConnected(this))
        return;

    if (d->rosterExchangeItems.isEmpty()) {
        QStringList list = stringToList(d->le_from->text());
        if (list.isEmpty())
            return;
        Jid  j(list[0]);
        emit aDeny(j);
    } else {
        d->rosterExchangeItems.clear();
    }
    close();
}

void EventDlg::doAuth()
{
    if (!d->pa->checkConnected(this))
        return;

    if (d->rosterExchangeItems.isEmpty()) {
        QStringList list = stringToList(d->le_from->text());
        if (list.isEmpty())
            return;
        Jid  j(list[0]);
        emit aAuth(j);
    } else {
        emit aRosterExchange(d->rosterExchangeItems);
        d->rosterExchangeItems.clear();
    }
    d->pb_auth->setEnabled(false);
    closeAfterReply();
}

/*!
    Executed when user wants to confirm a HTTP request.
*/
void EventDlg::doHttpConfirm()
{
    if (!d->pa->checkConnected(this))
        return;

    if (!d->httpAuthRequest.hasId()) {
        const QString id = d->le_http_id->text();
        if (id.isEmpty()) {
            QMessageBox::information(this, tr("Warning"), tr("Please type in a transaction identifier first."));
            d->le_http_id->setFocus();
            return;
        } else {
            d->httpAuthRequest.setId(id);
        }
    }

    emit aHttpConfirm(d->httpAuthRequest);

    d->le_http_id->setEnabled(false);
    d->pb_http_confirm->setEnabled(false);
    d->pb_http_deny->setEnabled(false);
    closeAfterReply();
}

/*!
    Executed when user wants to deny a HTTP request.
*/
void EventDlg::doHttpDeny()
{
    if (!d->pa->checkConnected(this))
        return;

    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;
    Jid j(list[0]);

    emit aHttpDeny(d->httpAuthRequest);

    d->le_http_id->setEnabled(false);
    d->pb_http_confirm->setEnabled(false);
    d->pb_http_deny->setEnabled(false);
    closeAfterReply();
}

/*!
    Executed when user wants to submit a completed form
*/
void EventDlg::doFormSubmit()
{
    // get original sender
    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;
    Jid j(list[0]);

    // populate the data fields
    XData data;
    data.setFields(d->xdata->fields());

    // ensure that the user completed all required fields
    if (!data.isValid()) {
        QMessageBox::information(this, tr("Warning"), tr("Please complete all required fields (marked with a '*')."));
        d->xdata_form->setFocus();
        return;
    }

    data.setType(XData::Data_Submit);
    emit aFormSubmit(data, d->thread, j);

    d->pb_form_submit->setEnabled(false);
    d->pb_form_cancel->setEnabled(false);

    closeAfterReply();
}

/*!
    Executed when user wants to cancel a form
*/
void EventDlg::doFormCancel()
{
    // get original sender
    QStringList list = stringToList(d->le_from->text());
    if (list.isEmpty())
        return;
    Jid j(list[0]);

    XData data;
    data.setType(XData::Data_Cancel);
    emit aFormCancel(data, d->thread, j);

    d->pb_form_submit->setEnabled(false);
    d->pb_form_cancel->setEnabled(false);

    closeAfterReply();
}

void EventDlg::doHistory() { d->pa->actionHistory(d->jid.withResource("")); }

void EventDlg::doInfo() { d->pa->actionInfo(d->jid); }

void EventDlg::closeAfterReply()
{
    if (d->nextAmount == 0)
        close();
}

void EventDlg::addUrl()
{
    AddUrlDlg *w = new AddUrlDlg(this);
    int        n = w->exec();
    if (n != QDialog::Accepted) {
        delete w;
        return;
    }

    QString url  = w->le_url->text();
    QString desc = w->le_desc->text();
    delete w;

    d->attachView->urlAdd(url, desc);
}

void EventDlg::showHideAttachView()
{
    if (d->attachView->count()) {
        if (d->attachView->isHidden())
            d->attachView->show();
    } else {
        if (!d->attachView->isHidden())
            d->attachView->hide();
    }
}

void EventDlg::updateContact(const Jid &jid)
{
    if (d->jid.compare(jid, false)) {
        QString               rname  = d->jid.resource();
        QList<UserListItem *> ul     = d->pa->findRelevant(d->jid);
        UserListItem         *u      = nullptr;
        int                   status = -1;
        if (!ul.isEmpty()) {
            u = ul.first();
            if (rname.isEmpty()) {
                // use priority
                if (!u->isAvailable())
                    status = STATUS_OFFLINE;
                else
                    status = makeSTATUS((*u->userResourceList().priority()).status());
            } else {
                // use specific
                UserResourceList::ConstIterator rit = u->userResourceList().find(rname);
                if (rit != u->userResourceList().constEnd())
                    status = makeSTATUS((*rit).status());
                else
                    status = STATUS_OFFLINE;
            }
        }

        if (status == -1 || !u)
            d->lb_status->setPsiIcon(IconsetFactory::iconPtr("status/noauth"));
        else
            d->lb_status->setPsiIcon(PsiIconset::instance()->statusPtr(jid, status));

        if (u)
            d->lb_status->setToolTip(u->makeTip(true, false));
        else
            d->lb_status->setToolTip(QString());

        if (u)
            setWindowTitle(JIDUtil::nickOrJid(u->name(), u->jid().full()));
    }
}

void EventDlg::setTime(const QDateTime &t, bool late)
{
    QString str;
    // str.sprintf("<nobr>%02d/%02d %02d:%02d:%02d</nobr>", t.date().month(), t.date().day(), t.time().hour(),
    // t.time().minute(), t.time().second());
    str = QString("<nobr>") + QLocale().toString(t, QLocale::ShortFormat) + "</nobr>";
    if (late)
        str = QString("<font color=\"red\">") + str + "</font>";

    d->lb_time->setText(str);
}

void EventDlg::updateEvent(const PsiEvent::Ptr &e)
{
    // Default buttons setup
    d->pb_next->show();
    d->pb_chat->show();
    d->pb_reply->show();
    d->pb_quote->show();
    d->pb_close->show();
    d->mle->show();
    d->ck_all_auth->hide();
    d->pb_auth->hide();
    d->pb_deny->hide();
    d->pb_form_submit->hide();
    d->pb_form_cancel->hide();
    d->pb_http_confirm->hide();
    d->pb_http_deny->hide();
    d->xdata_form->hide();

    PsiIcon *oldanim = d->anim;
    d->anim          = PsiIconset::instance()->event2icon(e);

    if (d->anim != oldanim)
        setWindowIcon(d->anim->icon());

    d->le_from->setText(expandAddresses(e->from().full(), false));
    d->le_from->setCursorPosition(0);
    d->le_from->setToolTip(e->from().full());
    setTime(e->timeStamp(), e->late());

    bool showHttpId = false;

    if (e->type() == PsiEvent::HttpAuth) {

        HttpAuthEvent::Ptr     hae     = e.staticCast<HttpAuthEvent>();
        const HttpAuthRequest &confirm = hae->request();

        QString body(tr("Someone (maybe you) has requested access to the following resource:\n"
                        "URL: %1\n"
                        "Method: %2\n")
                         .arg(confirm.url(), confirm.method()));

        if (!confirm.hasId()) {
            body += tr("\n"
                       "If you wish to confirm this request, please provide transaction identifier and press Confirm "
                       "button. Otherwise press Deny button.");

            showHttpId = true;
        } else {
            body += tr("Transaction identifier: %1\n"
                       "\n"
                       "If you wish to confirm this request, please press Confirm button. "
                       "Otherwise press Deny button.")
                        .arg(confirm.id());
        }
        Message m(hae->message());
        m.setBody(body);
        hae->setMessage(m);

        d->httpAuthRequest = hae->request();
    }

    if (showHttpId) {
        d->le_http_id->clear();
        d->le_http_id->setEnabled(true);
        d->w_http_id->show();
        d->le_http_id->setFocus();
    } else {
        d->w_http_id->hide();
    }

    if (e->type() == PsiEvent::Message || e->type() == PsiEvent::HttpAuth) {
        MessageEvent::Ptr me = e.staticCast<MessageEvent>();
        const Message    &dm = me->message().displayMessage();

        // HTTP auth request buttons
        if (e->type() == PsiEvent::HttpAuth) {
            d->pb_chat->hide();
            d->pb_reply->hide();
            d->pb_quote->hide();

            d->pb_http_confirm->setEnabled(true);
            d->pb_http_confirm->show();
            d->pb_http_deny->setEnabled(true);
            d->pb_http_deny->show();
        }

        bool xhtml = dm.containsHTML() && PsiOptions::instance()->getOption("options.html.chat.render").toBool()
            && !dm.html().text().isEmpty();
        QString txt = xhtml ? dm.html().toString("div") : TextUtil::plain2rich(dm.body());

        // show subject line if the incoming message has one
        if (!dm.subject().isEmpty() && !PsiOptions::instance()->getOption("options.ui.message.show-subjects").toBool())
            txt = "<p><font color=\"red\"><b>" + tr("Subject:") + " " + TextUtil::plain2rich(dm.subject())
                + "</b></font></p>" + (xhtml ? "" : "<br>") + txt;

        if (!xhtml) {
            txt = TextUtil::linkify(txt);
            txt = TextUtil::emoticonify(txt);
            txt = TextUtil::legacyFormat(txt);
        }

        if (e->type() == PsiEvent::HttpAuth)
            txt = "<big>[HTTP Request Confirmation]</big><br>" + txt;

        setHtml("<qt>" + txt + "</qt>");

        d->le_subj->setText(dm.subject());
        d->le_subj->setCursorPosition(0);

        d->thread = dm.thread();

        // Form buttons
        if (!dm.getForm().fields().empty()) {
            d->pb_chat->hide();
            d->pb_reply->hide();
            d->pb_quote->hide();
            d->pb_close->hide();
            d->mle->hide();

            // show/enable controls we want
            d->pb_form_submit->show();
            d->pb_form_cancel->show();
            d->pb_form_submit->setEnabled(true);
            d->pb_form_cancel->setEnabled(true);
            // set title if specified
            const XData &form = dm.getForm();
            if (!form.title().isEmpty())
                setWindowTitle(form.title());

            // show data form
            d->xdata->setForm(form, false);
            auto xdfLFW = d->xdata->lastFocusabelWidget();
            if (xdfLFW) {
                setTabOrder(d->le_subj, d->xdata_form);
                setTabOrder(xdfLFW, d->pb_form_submit); // FIXME doesn't work? why?
            }
            d->xdata_form->setFocus();
            d->xdata_form->show();

            // set instructions
            QString str = TextUtil::plain2rich(form.instructions());
            d->xdata_instruction->setText(str);
        }

        d->attachView->clear();
        d->attachView->addUrlList(dm.urlList());

        if (!dm.mucInvites().isEmpty()) {
            MUCInvite i = dm.mucInvites().constFirst();
            d->attachView->gcAdd(dm.from().full(), i.from().bare(), i.reason(), dm.mucPassword());
        } else if (!dm.invite().isEmpty())
            d->attachView->gcAdd(dm.invite());
        showHideAttachView();
    } else if (e->type() == PsiEvent::Auth) {
        AuthEvent::Ptr ae   = e.staticCast<AuthEvent>();
        QString        type = ae->authType();

        d->le_subj->setText("");
        if (type == "subscribe") {
            QString body(tr("<big>[System Message]</big><br>This user wants to subscribe to your presence.  Click the "
                            "button labelled \"Add/Auth\" to authorize the subscription.  This will also add the "
                            "person to your contact list if it is not already there."));
            setHtml("<qt>" + body + "</qt>");

            d->pb_chat->show();
            d->pb_reply->hide();
            d->pb_quote->hide();

            d->ck_all_auth->show();
            d->pb_auth->setEnabled(true);
            d->pb_auth->show();
            d->pb_deny->show();

            d->pb_http_confirm->hide();
            d->pb_http_deny->hide();

        } else if (type == "subscribed") {
            QString body(tr("<big>[System Message]</big><br>You are now authorized."));
            setHtml("<qt>" + body + "</qt>");

            d->ck_all_auth->hide();
            d->pb_auth->hide();
            d->pb_deny->hide();
            d->pb_chat->show();
            d->pb_reply->show();
            d->pb_quote->show();
            d->pb_http_confirm->hide();
            d->pb_http_deny->hide();
        } else if (type == "unsubscribed") {
            QString body(tr("<big>[System Message]</big><br>Your authorization has been removed!"));
            setHtml("<qt>" + body + "</qt>");

            d->ck_all_auth->hide();
            d->pb_auth->hide();
            d->pb_deny->hide();
            d->pb_chat->show();
            d->pb_reply->show();
            d->pb_quote->show();
            d->pb_http_confirm->hide();
            d->pb_http_deny->hide();
        }
    } else if (e->type() == PsiEvent::RosterExchange) {
        RosterExchangeEvent::Ptr re        = e.staticCast<RosterExchangeEvent>();
        int                      additions = 0, deletions = 0, modifications = 0;
        for (const RosterExchangeItem &item : re->rosterExchangeItems()) {
            switch (item.action()) {
            case RosterExchangeItem::Add:
                additions++;
                break;
            case RosterExchangeItem::Delete:
                deletions++;
                break;
            case RosterExchangeItem::Modify:
                modifications++;
                break;
            }
        }
        QString action;
        if (additions > 0) {
            if (additions > 1)
                action += QString(tr("%1 additions")).arg(additions);
            else
                action += QString(tr("1 addition"));
            if (deletions > 0 || modifications > 0)
                action += ", ";
        }
        if (deletions > 0) {
            if (deletions > 1)
                action += QString(tr("%1 deletions")).arg(deletions);
            else
                action += QString(tr("1 deletion"));
            if (modifications > 0)
                action += ", ";
        }
        if (modifications > 0) {
            if (modifications > 1)
                action += QString(tr("%1 modifications")).arg(modifications);
            else
                action += QString(tr("1 modification"));
        }

        d->le_subj->setText("");
        QString body = QString(tr("<big>[System Message]</big><br>This user wants to modify your roster (%1). Click "
                                  "the button labelled \"Add/Auth\" to authorize the modification."))
                           .arg(action);
        setHtml("<qt>" + body + "</qt>");
        d->rosterExchangeItems = re->rosterExchangeItems();

        d->pb_chat->show();
        d->pb_reply->hide();
        d->pb_quote->hide();

        d->ck_all_auth->show();
        d->pb_auth->setEnabled(true);
        d->pb_auth->show();
        d->pb_deny->show();
    }

    d->mle->scrollToTop();

    if (!d->le_subj->text().isEmpty())
        d->le_subj->setToolTip(d->le_subj->text());
    else
        d->le_subj->setToolTip(QString());

    doWhois();
}

void EventDlg::updateReadNext(PsiIcon *nextAnim, int nextAmount)
{
    int oldAmount = d->nextAmount;

    d->setNextAnim(nextAnim);
    d->nextAmount = nextAmount;

    if (nextAmount == 0) {
        d->setNextAnim(nullptr);
        d->pb_next->forceSetPsiIcon(nullptr);
        d->pb_next->setEnabled(false);
        d->pb_next->setText(tr("&Next"));

        if (d->pb_reply->isVisibleTo(this) && d->pb_reply->isEnabled())
            d->pb_reply->setFocus();
        else if (d->pb_auth->isVisibleTo(this))
            d->pb_auth->setFocus();
        else if (d->w_http_id->isVisibleTo(this))
            d->le_http_id->setFocus();
        else if (d->pb_http_deny->isVisibleTo(this))
            d->pb_http_deny->setFocus();
    } else {
        d->pb_next->setEnabled(true);
        QString str(tr("&Next"));
        str += QString(" - %1").arg(nextAmount);
        d->pb_next->setText(str);

        d->pb_next->forceSetPsiIcon(d->nextAnim());

        if (d->nextAmount > oldAmount)
            d->pb_next->setFocus();
    }
}

void EventDlg::actionGCJoin(const QString &gc, const QString &passw)
{
    Jid j(gc);
    d->pa->actionJoin(j.withResource(""), passw);
}

#include "eventdlg.moc"
