/***************************************************************************
 *   Copyright (C) 2007 by Pino Toscano <pino@kde.org>                     *
 *   Copyright (C) 2017    Klarälvdalens Datakonsult AB, a KDAB Group      *
 *                         company, info@kdab.com. Work sponsored by the   *
 *                         LiMux project of the city of Munich             *
 *   Copyright (C) 2018    Intevation GmbH <intevation@intevation.de>      *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "formwidgets.h"
#include "pageviewutils.h"
#include "revisionviewer.h"
#include "signaturepropertiesdialog.h"

#include <qbuttongroup.h>
#include <QKeyEvent>
#include <QMenu>
#include <QEvent>
#include <klineedit.h>
#include <KLocalizedString>
#include <kstandardaction.h>
#include <qaction.h>
#include <QUrl>
#include <QPainter>

// local includes
#include "core/action.h"
#include "core/form.h"
#include "core/document.h"
#include "debug_ui.h"

FormWidgetsController::FormWidgetsController( Okular::Document *doc )
    : QObject( doc ), m_doc( doc )
{
    // emit changed signal when a form has changed
    connect( this, &FormWidgetsController::formTextChangedByUndoRedo,
             this, &FormWidgetsController::changed );
    connect( this, &FormWidgetsController::formListChangedByUndoRedo,
             this, &FormWidgetsController::changed );
    connect( this, &FormWidgetsController::formComboChangedByUndoRedo,
             this, &FormWidgetsController::changed );

    // connect form modification signals to and from document
    connect( this, &FormWidgetsController::formTextChangedByWidget,
             doc, &Okular::Document::editFormText );
    connect( doc, &Okular::Document::formTextChangedByUndoRedo,
             this, &FormWidgetsController::formTextChangedByUndoRedo );

    connect( this, &FormWidgetsController::formListChangedByWidget,
             doc, &Okular::Document::editFormList );
    connect( doc, &Okular::Document::formListChangedByUndoRedo,
             this, &FormWidgetsController::formListChangedByUndoRedo );

    connect( this, &FormWidgetsController::formComboChangedByWidget,
             doc, &Okular::Document::editFormCombo );
    connect( doc, &Okular::Document::formComboChangedByUndoRedo,
             this, &FormWidgetsController::formComboChangedByUndoRedo );

    connect( this, &FormWidgetsController::formButtonsChangedByWidget,
             doc, &Okular::Document::editFormButtons );
    connect( doc, &Okular::Document::formButtonsChangedByUndoRedo,
             this, &FormWidgetsController::slotFormButtonsChangedByUndoRedo );

    // Connect undo/redo signals
    connect( this, &FormWidgetsController::requestUndo,
             doc, &Okular::Document::undo );
    connect( this, &FormWidgetsController::requestRedo,
             doc, &Okular::Document::redo );

    connect( doc, &Okular::Document::canUndoChanged,
             this, &FormWidgetsController::canUndoChanged );
    connect( doc, &Okular::Document::canRedoChanged,
             this, &FormWidgetsController::canRedoChanged );

    // Connect the generic formWidget refresh signal
    connect( doc, &Okular::Document::refreshFormWidget,
             this, &FormWidgetsController::refreshFormWidget );
}

FormWidgetsController::~FormWidgetsController()
{
}

void FormWidgetsController::signalAction( Okular::Action *a )
{
    emit action( a );
}

void FormWidgetsController::processScriptAction( Okular::Action *a, Okular::FormField * field, Okular::Annotation::AdditionalActionType type )
{
    // If it's not a Action Script or if the field is not a FormText, handle it normally
    if( a->actionType() != Okular::Action::Script || field->type() != Okular::FormField::FormText )
    {
        emit action( a );
        return;
    }
    switch( type )
    {
        // These cases are to be handled by the FormField text, so we let it happen.
        case Okular::Annotation::FocusIn:
        case Okular::Annotation::FocusOut:
            return;  
        case Okular::Annotation::PageOpening:
        case Okular::Annotation::PageClosing:
        case Okular::Annotation::CursorEntering:
        case Okular::Annotation::CursorLeaving:
        case Okular::Annotation::MousePressed:
        case Okular::Annotation::MouseReleased:
            emit action( a );
    }
}

void FormWidgetsController::registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton )
{
    if ( !fwButton )
        return;

    QAbstractButton *button = dynamic_cast<QAbstractButton *>(fwButton);
    if ( !button )
    {
        qWarning() << "fwButton is not a QAbstractButton" << fwButton;
        return;
    }

    QList< RadioData >::iterator it = m_radios.begin(), itEnd = m_radios.end();
    const int id = formButton->id();
    m_buttons.insert( id, button );
    for ( ; it != itEnd; ++it )
    {
        const RadioData &rd = *it;
        const QList< int >::const_iterator idsIt = std::find( rd.ids.begin(), rd.ids.end(), id );
        if ( idsIt != rd.ids.constEnd() )
        {
            qCDebug(OkularUiDebug) << "Adding id" << id << "To group including" << rd.ids;
            rd.group->addButton( button );
            rd.group->setId( button, id );
            return;
        }
    }

    const QList< int > siblings = formButton->siblings();

    RadioData newdata;
    newdata.ids = siblings;
    newdata.ids.append( id );
    newdata.group = new QButtonGroup();
    newdata.group->addButton( button );
    newdata.group->setId( button, id );

    // Groups of 1 (like checkboxes) can't be exclusive
    if (siblings.isEmpty())
        newdata.group->setExclusive( false );

    connect( newdata.group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
             this, &FormWidgetsController::slotButtonClicked );
    m_radios.append( newdata );
}

void FormWidgetsController::dropRadioButtons()
{
    QList< RadioData >::iterator it = m_radios.begin(), itEnd = m_radios.end();
    for ( ; it != itEnd; ++it )
    {
        delete (*it).group;
    }
    m_radios.clear();
    m_buttons.clear();
}

bool FormWidgetsController::canUndo()
{
    return m_doc->canUndo();
}

bool FormWidgetsController::canRedo()
{
    return m_doc->canRedo();
}

bool FormWidgetsController::shouldFormWidgetBeShown( Okular::FormField *form )
{
    return !form->isReadOnly() || form->type() == Okular::FormField::FormSignature;
}

void FormWidgetsController::slotButtonClicked( QAbstractButton *button )
{
    int pageNumber = -1;
    CheckBoxEdit *check = qobject_cast< CheckBoxEdit * >( button );
    if ( check )
    {
        // Checkboxes need to be uncheckable so if clicking a checked one
        // disable the exclusive status temporarily and uncheck it
        Okular::FormFieldButton *formButton = static_cast<Okular::FormFieldButton *>( check->formField() );
        if ( formButton->state() ) {
            const bool wasExclusive = button->group()->exclusive();
            button->group()->setExclusive(false);
            check->setChecked(false);
            button->group()->setExclusive(wasExclusive);
        }
        pageNumber = check->pageItem()->pageNumber();
    }
    else if ( RadioButtonEdit *radio = qobject_cast< RadioButtonEdit * >( button ) )
    {
        pageNumber = radio->pageItem()->pageNumber();
    }

    const QList< QAbstractButton* > buttons = button->group()->buttons();
    QList< bool > checked;
    QList< bool > prevChecked;
    QList< Okular::FormFieldButton*> formButtons;

    for ( QAbstractButton* button : buttons )
    {
        checked.append( button->isChecked() );
        Okular::FormFieldButton *formButton = static_cast<Okular::FormFieldButton *>( dynamic_cast<FormWidgetIface*>(button)->formField() );
        formButtons.append( formButton );
        prevChecked.append( formButton->state() );
    }
    if (checked != prevChecked)
        emit formButtonsChangedByWidget( pageNumber, formButtons, checked );
    if ( check )
    {
        // The formButtonsChangedByWidget signal changes the value of the underlying
        // Okular::FormField of the checkbox. We need to execute the activation
        // action after this.
        check->doActivateAction();
    }
}

void FormWidgetsController::slotFormButtonsChangedByUndoRedo( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons)
{
    for ( const Okular::FormFieldButton* formButton : formButtons )
    {
        int id = formButton->id();
        QAbstractButton* button = m_buttons[id];
        CheckBoxEdit *check = qobject_cast< CheckBoxEdit * >( button );
        if ( check )
        {
            emit refreshFormWidget( check->formField() );
        }
        // temporarily disable exclusiveness of the button group
        // since it breaks doing/redoing steps into which all the checkboxes
        // are unchecked
        const bool wasExclusive = button->group()->exclusive();
        button->group()->setExclusive(false);
        bool checked = formButton->state();
        button->setChecked( checked );
        button->group()->setExclusive(wasExclusive);
        button->setFocus();
    }
    emit changed( pageNumber );
}

FormWidgetIface * FormWidgetFactory::createWidget( Okular::FormField * ff, QWidget * parent )
{
    FormWidgetIface * widget = nullptr;

    switch ( ff->type() )
    {
        case Okular::FormField::FormButton:
        {
            Okular::FormFieldButton * ffb = static_cast< Okular::FormFieldButton * >( ff );
            switch ( ffb->buttonType() )
            {
                case Okular::FormFieldButton::Push:
                    widget = new PushButtonEdit( ffb, parent );
                    break;
                case Okular::FormFieldButton::CheckBox:
                    widget = new CheckBoxEdit( ffb, parent );
                    break;
                case Okular::FormFieldButton::Radio:
                    widget = new RadioButtonEdit( ffb, parent );
                    break;
                default: ;
            }
            break;
        }
        case Okular::FormField::FormText:
        {
            Okular::FormFieldText * fft = static_cast< Okular::FormFieldText * >( ff );
            switch ( fft->textType() )
            {
                case Okular::FormFieldText::Multiline:
                    widget = new TextAreaEdit( fft, parent );
                    break;
                case Okular::FormFieldText::Normal:
                    widget = new FormLineEdit( fft, parent );
                    break;
                case Okular::FormFieldText::FileSelect:
                    widget = new FileEdit( fft, parent );
                    break;
            }
            break;
        }
        case Okular::FormField::FormChoice:
        {
            Okular::FormFieldChoice * ffc = static_cast< Okular::FormFieldChoice * >( ff );
            switch ( ffc->choiceType() )
            {
                case Okular::FormFieldChoice::ListBox:
                    widget = new ListEdit( ffc, parent );
                    break;
                case Okular::FormFieldChoice::ComboBox:
                    widget = new ComboEdit( ffc, parent );
                    break;
            }
            break;
        }
        case Okular::FormField::FormSignature:
        {
            Okular::FormFieldSignature * ffs = static_cast< Okular::FormFieldSignature * >( ff );
            if ( ffs->isVisible() && ffs->signatureType() != Okular::FormFieldSignature::UnknownType )
                widget = new SignatureEdit( ffs, parent );
            break;
        }
        default: ;
    }

    if ( !FormWidgetsController::shouldFormWidgetBeShown( ff ) )
        widget->setVisibility( false );

    return widget;
}


FormWidgetIface::FormWidgetIface( QWidget * w, Okular::FormField * ff )
    : m_controller( nullptr ), m_ff( ff ), m_widget( w ), m_pageItem( nullptr )
{
}

FormWidgetIface::~FormWidgetIface()
{
}

Okular::NormalizedRect FormWidgetIface::rect() const
{
    return m_ff->rect();
}

void FormWidgetIface::setWidthHeight( int w, int h )
{
    m_widget->resize( w, h );
}

void FormWidgetIface::moveTo( int x, int y )
{
    m_widget->move( x, y );
}

bool FormWidgetIface::setVisibility( bool visible )
{
    bool hadfocus = m_widget->hasFocus();
    if ( hadfocus )
        m_widget->clearFocus();
    m_widget->setVisible( visible );
    return hadfocus;
}

void FormWidgetIface::setCanBeFilled( bool fill )
{
    m_widget->setEnabled( fill );
}

void FormWidgetIface::setPageItem( PageViewItem *pageItem )
{
    m_pageItem = pageItem;
}

void FormWidgetIface::setFormField( Okular::FormField *field )
{
    m_ff = field;
}

Okular::FormField* FormWidgetIface::formField() const
{
    return m_ff;
}

PageViewItem* FormWidgetIface::pageItem() const
{
    return m_pageItem;
}

void FormWidgetIface::setFormWidgetsController( FormWidgetsController *controller )
{
    m_controller = controller;
    QObject *obj = dynamic_cast< QObject * > ( this );
    QObject::connect( m_controller, &FormWidgetsController::refreshFormWidget, obj,
                      [this] ( Okular::FormField *form ) {
                          slotRefresh ( form );
                      });
}

void FormWidgetIface::slotRefresh( Okular::FormField * form )
{
    if ( m_ff != form )
    {
        return;
    }
    setVisibility( form->isVisible() && m_controller->shouldFormWidgetBeShown( form ) );

    m_widget->setEnabled( !form->isReadOnly() );
}


PushButtonEdit::PushButtonEdit( Okular::FormFieldButton * button, QWidget * parent )
    : QPushButton( parent ), FormWidgetIface( this, button )
{
    setText( button->caption() );
    
    if( button->caption().isEmpty() )
    {
        setFlat( true );
    }
    
    setVisible( button->isVisible() );
    setCursor( Qt::ArrowCursor );
}


CheckBoxEdit::CheckBoxEdit( Okular::FormFieldButton * button, QWidget * parent )
    : QCheckBox( parent ), FormWidgetIface( this, button )
{
    setText( button->caption() );

    setVisible( button->isVisible() );
    setCursor( Qt::ArrowCursor );
}

void CheckBoxEdit::setFormWidgetsController( FormWidgetsController *controller )
{
    Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
    FormWidgetIface::setFormWidgetsController( controller );
    m_controller->registerRadioButton( this, form );
    setChecked( form->state() );
}

void CheckBoxEdit::doActivateAction()
{
    Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
    if ( form->activationAction() )
        m_controller->signalAction( form->activationAction() );
}

void CheckBoxEdit::slotRefresh( Okular::FormField * form )
{
    if ( form != m_ff )
    {
        return;
    }
    FormWidgetIface::slotRefresh( form );

    Okular::FormFieldButton *button = static_cast<Okular::FormFieldButton *>(m_ff);
    bool oldState = isChecked();
    bool newState = button->state();
    if ( oldState != newState )
    {
        setChecked( button->state() );
        doActivateAction();
    }
}


RadioButtonEdit::RadioButtonEdit( Okular::FormFieldButton * button, QWidget * parent )
    : QRadioButton( parent ), FormWidgetIface( this, button )
{
    setText( button->caption() );

    setVisible( button->isVisible() );
    setCursor( Qt::ArrowCursor );
}

void RadioButtonEdit::setFormWidgetsController( FormWidgetsController *controller )
{
    Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
    FormWidgetIface::setFormWidgetsController( controller );
    m_controller->registerRadioButton( this, form );
    setChecked( form->state() );
}


FormLineEdit::FormLineEdit( Okular::FormFieldText * text, QWidget * parent )
    : QLineEdit( parent ), FormWidgetIface( this, text )
{
    int maxlen = text->maximumLength();
    if ( maxlen >= 0 )
        setMaxLength( maxlen );
    setAlignment( text->textAlignment() );
    setText( text->text() );
    if ( text->isPassword() )
        setEchoMode( QLineEdit::Password );

    m_prevCursorPos = cursorPosition();
    m_prevAnchorPos = cursorPosition();
    m_editing = false;

    connect( this, &QLineEdit::textEdited, this, &FormLineEdit::slotChanged );
    connect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged );

    setVisible( text->isVisible() );
}

void FormLineEdit::setFormWidgetsController(FormWidgetsController* controller)
{
    FormWidgetIface::setFormWidgetsController(controller);
    connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo,
             this, &FormLineEdit::slotHandleTextChangedByUndoRedo );
}

bool FormLineEdit::event( QEvent* e )
{
    if ( e->type() == QEvent::KeyPress )
    {
        QKeyEvent *keyEvent = static_cast< QKeyEvent* >( e );
        if ( keyEvent == QKeySequence::Undo )
        {
            emit m_controller->requestUndo();
            return true;
        }
        else if ( keyEvent == QKeySequence::Redo )
        {
            emit m_controller->requestRedo();
            return true;
        }
    }
    else if ( e->type() == QEvent::FocusIn )
    {
        const auto fft = static_cast< Okular::FormFieldText * > ( m_ff );
        setText( fft->text() );
        m_editing = true;
        if( const Okular::Action *action = m_ff->additionalAction( Okular::Annotation::FocusIn ) )
            emit m_controller->focusAction( action, fft );
        setFocus();
    }
    else if ( e->type() == QEvent::FocusOut )
    {
        // Don't worry about focus events from other sources than the user FocusEvent to edit the field
        QFocusEvent *focusEvent = static_cast< QFocusEvent* >( e );
        if( focusEvent->reason() == Qt::OtherFocusReason )
            return true;
        m_editing = false;
        
        if( const Okular::Action *action = m_ff->additionalAction( Okular::Annotation::FocusOut ) )
        {
            bool ok = false;
            emit m_controller->validateAction( action, static_cast< Okular::FormFieldText * > ( m_ff ), ok );
        }
        if ( const Okular::Action *action = m_ff->additionalAction( Okular::FormField::FormatField ) )
        {
            emit m_controller->formatAction( action, static_cast< Okular::FormFieldText * > ( m_ff ) );
        }
    }
    return QLineEdit::event( e );
}

void FormLineEdit::contextMenuEvent( QContextMenuEvent* event )
{
    QMenu *menu = createStandardContextMenu();

    QList<QAction *> actionList = menu->actions();
    enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct };

    QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu );
    QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu );
    connect( m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled );
    connect( m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled );
    kundo->setEnabled( m_controller->canUndo() );
    kredo->setEnabled( m_controller->canRedo() );

    QAction *oldUndo, *oldRedo;
    oldUndo = actionList[UndoAct];
    oldRedo = actionList[RedoAct];

    menu->insertAction( oldUndo, kundo );
    menu->insertAction( oldRedo, kredo );

    menu->removeAction( oldUndo );
    menu->removeAction( oldRedo );

    menu->exec( event->globalPos() );
    delete menu;
}

void FormLineEdit::slotChanged()
{
    Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);
    QString contents = text();
    int cursorPos = cursorPosition();

    if( form->additionalAction( Okular::FormField::FieldModified ) && m_editing && !form->isReadOnly() )
    {
        bool ok = false;
        QString oldInputText = form->text();
        form->setText( text() );
        emit m_controller->keystrokeAction( form->additionalAction( Okular::FormField::FieldModified ), form, ok );
        form->setText( oldInputText );
        if(!ok)
        {
            setText( oldInputText );
            return;
        }
    }

    if ( contents != form->text() )
    {
        emit m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
                                                   form,
                                                   contents,
                                                   cursorPos,
                                                   m_prevCursorPos,
                                                   m_prevAnchorPos );
    }

    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = cursorPos;
    if ( hasSelectedText() ) {
        if ( cursorPos == selectionStart() ) {
            m_prevAnchorPos = selectionStart() + selectedText().size();
        } else {
            m_prevAnchorPos = selectionStart();
        }
    }
}

void FormLineEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
                                                    Okular::FormFieldText* textForm,
                                                    const QString & contents,
                                                    int cursorPos,
                                                    int anchorPos )
{
    Q_UNUSED(pageNumber);
    if ( textForm != m_ff || contents == text() )
    {
        return;
    }
    disconnect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged );
    setText(contents);
    setCursorPosition(anchorPos);
    cursorForward( true, cursorPos - anchorPos );
    connect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged );
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = anchorPos;
    setFocus();
}

void FormLineEdit::slotRefresh( Okular::FormField *form )
{
    if (form != m_ff)
    {
        return;
    }
    FormWidgetIface::slotRefresh( form );

    Okular::FormFieldText *text = static_cast<Okular::FormFieldText *> ( form );
    setText( text->text() );
}

TextAreaEdit::TextAreaEdit( Okular::FormFieldText * text, QWidget * parent )
: KTextEdit( parent ), FormWidgetIface( this, text )
{
    setAcceptRichText( text->isRichText() );
    setCheckSpellingEnabled( text->canBeSpellChecked() );
    setAlignment( text->textAlignment() );
    setPlainText( text->text() );
    setUndoRedoEnabled( false );

    connect( this, &QTextEdit::textChanged, this, &TextAreaEdit::slotChanged );
    connect( this, &QTextEdit::cursorPositionChanged, this, &TextAreaEdit::slotChanged );
    connect( this, &KTextEdit::aboutToShowContextMenu,
             this, &TextAreaEdit::slotUpdateUndoAndRedoInContextMenu );
    m_prevCursorPos = textCursor().position();
    m_prevAnchorPos = textCursor().anchor();
    m_editing = false;
    setVisible( text->isVisible() );
}

TextAreaEdit::~TextAreaEdit()
{
    // We need this because otherwise on destruction we destruct the syntax highlighter
    // that ends up calling text changed but then we go to slotChanged and we're already
    // half destructed and all is bad
    disconnect( this, &QTextEdit::textChanged, this, &TextAreaEdit::slotChanged );
}

bool TextAreaEdit::event( QEvent* e )
{
    if ( e->type() == QEvent::KeyPress )
    {
        QKeyEvent *keyEvent = static_cast< QKeyEvent* >(e);
        if ( keyEvent == QKeySequence::Undo )
        {
            emit m_controller->requestUndo();
            return true;
        }
        else if ( keyEvent == QKeySequence::Redo )
        {
            emit m_controller->requestRedo();
            return true;
        }
    }
    else if ( e->type() == QEvent::FocusIn )
    {
        const auto fft = static_cast< Okular::FormFieldText * > ( m_ff );
        setText( fft->text() );
        m_editing = true;
    }
    else if ( e->type() == QEvent::FocusOut )
    {
        m_editing = false;
        if ( const Okular::Action *action = m_ff->additionalAction( Okular::FormField::FormatField ) )
        {
            emit m_controller->formatAction( action, static_cast< Okular::FormFieldText * > ( m_ff ) );
        }
    }
    return KTextEdit::event( e );
}

void TextAreaEdit::slotUpdateUndoAndRedoInContextMenu( QMenu* menu )
{
    if ( !menu ) return;

    QList<QAction *> actionList = menu->actions();
    enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };

    QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu );
    QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu );
    connect(m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled );
    connect(m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled );
    kundo->setEnabled( m_controller->canUndo() );
    kredo->setEnabled( m_controller->canRedo() );

    QAction *oldUndo, *oldRedo;
    oldUndo = actionList[UndoAct];
    oldRedo = actionList[RedoAct];

    menu->insertAction( oldUndo, kundo );
    menu->insertAction( oldRedo, kredo );

    menu->removeAction( oldUndo );
    menu->removeAction( oldRedo );
}

void TextAreaEdit::setFormWidgetsController( FormWidgetsController* controller )
{
    FormWidgetIface::setFormWidgetsController( controller );
    connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo,
             this, &TextAreaEdit::slotHandleTextChangedByUndoRedo );
}

void TextAreaEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
                                                    Okular::FormFieldText* textForm,
                                                    const QString & contents,
                                                    int cursorPos,
                                                    int anchorPos )
{
    Q_UNUSED(pageNumber);
    if ( textForm != m_ff )
    {
        return;
    }
    setPlainText( contents );
    QTextCursor c = textCursor();
    c.setPosition( anchorPos );
    c.setPosition( cursorPos,QTextCursor::KeepAnchor );
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = anchorPos;
    setTextCursor( c );
    setFocus();
}

void TextAreaEdit::slotChanged()
{
    Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);
    QString contents = toPlainText();
    int cursorPos = textCursor().position();

    if( form->additionalAction( Okular::FormField::FieldModified ) && m_editing && !form->isReadOnly() )
    {
        bool ok = false;
        QString oldInputText = form->text();
        form->setText( toPlainText() );
        emit m_controller->keystrokeAction( form->additionalAction( Okular::FormField::FieldModified ), form, ok );
        form->setText( oldInputText );
        if(!ok)
        {
            setText( oldInputText );
            return;
        }
    }

    if (contents != form->text())
    {
        emit m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
                                                   form,
                                                   contents,
                                                   cursorPos,
                                                   m_prevCursorPos,
                                                   m_prevAnchorPos );
    }
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = textCursor().anchor();
}

void TextAreaEdit::slotRefresh( Okular::FormField *form )
{
    if (form != m_ff)
    {
        return;
    }
    FormWidgetIface::slotRefresh( form );

    Okular::FormFieldText *text = static_cast<Okular::FormFieldText *> ( form );
    setPlainText( text->text() );
}

FileEdit::FileEdit( Okular::FormFieldText * text, QWidget * parent )
    : KUrlRequester( parent ), FormWidgetIface( this, text )
{
    setMode( KFile::File | KFile::ExistingOnly | KFile::LocalOnly );
    setFilter( i18n( "*|All Files" ) );
    setUrl( QUrl::fromUserInput( text->text() ) );
    lineEdit()->setAlignment( text->textAlignment() );

    m_prevCursorPos = lineEdit()->cursorPosition();
    m_prevAnchorPos = lineEdit()->cursorPosition();

    connect( this, &KUrlRequester::textChanged, this, &FileEdit::slotChanged );
    connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &FileEdit::slotChanged );
    setVisible( text->isVisible() );
}

void FileEdit::setFormWidgetsController( FormWidgetsController* controller )
{
    FormWidgetIface::setFormWidgetsController( controller );
    connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo,
             this, &FileEdit::slotHandleFileChangedByUndoRedo );
}

bool FileEdit::eventFilter( QObject* obj, QEvent* event )
{
    if ( obj == lineEdit() ) {
        if ( event->type() == QEvent::KeyPress )
        {
            QKeyEvent *keyEvent = static_cast< QKeyEvent* >( event );
            if ( keyEvent == QKeySequence::Undo )
            {
                emit m_controller->requestUndo();
                return true;
            }
            else if ( keyEvent == QKeySequence::Redo )
            {
                emit m_controller->requestRedo();
                return true;
            }
        }
        else if( event->type() == QEvent::ContextMenu )
        {
            QContextMenuEvent *contextMenuEvent = static_cast< QContextMenuEvent* >( event );

            QMenu *menu = ( (QLineEdit*) lineEdit() )->createStandardContextMenu();

            QList< QAction* > actionList = menu->actions();
            enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct };

            QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu );
            QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu );
            connect(m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled );
            connect(m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled );
            kundo->setEnabled( m_controller->canUndo() );
            kredo->setEnabled( m_controller->canRedo() );

            QAction *oldUndo, *oldRedo;
            oldUndo = actionList[UndoAct];
            oldRedo = actionList[RedoAct];

            menu->insertAction( oldUndo, kundo );
            menu->insertAction( oldRedo, kredo );

            menu->removeAction( oldUndo );
            menu->removeAction( oldRedo );

            menu->exec( contextMenuEvent->globalPos() );
            delete menu;
            return true;
        }
    }
    return KUrlRequester::eventFilter( obj, event );
}

void FileEdit::slotChanged()
{
    // Make sure line edit's text matches url expansion
    if ( text() != url().toLocalFile() )
        this->setText( url().toLocalFile() );

    Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);

    QString contents = text();
    int cursorPos = lineEdit()->cursorPosition();
    if (contents != form->text())
    {
        emit m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
                                                   form,
                                                   contents,
                                                   cursorPos,
                                                   m_prevCursorPos,
                                                   m_prevAnchorPos );
    }

    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = cursorPos;
    if ( lineEdit()->hasSelectedText() ) {
        if ( cursorPos == lineEdit()->selectionStart() ) {
            m_prevAnchorPos = lineEdit()->selectionStart() + lineEdit()->selectedText().size();
        } else {
            m_prevAnchorPos = lineEdit()->selectionStart();
        }
    }
}

void FileEdit::slotHandleFileChangedByUndoRedo( int pageNumber,
                                                Okular::FormFieldText* form,
                                                const QString & contents,
                                                int cursorPos,
                                                int anchorPos )
{
    Q_UNUSED(pageNumber);
    if ( form != m_ff || contents == text() )
    {
        return;
    }
    disconnect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &FileEdit::slotChanged );
    setText( contents );
    lineEdit()->setCursorPosition( anchorPos );
    lineEdit()->cursorForward( true, cursorPos - anchorPos );
    connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &FileEdit::slotChanged );
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = anchorPos;
    setFocus();
}

ListEdit::ListEdit( Okular::FormFieldChoice * choice, QWidget * parent )
    : QListWidget( parent ), FormWidgetIface( this, choice )
{
    addItems( choice->choices() );
    setSelectionMode( choice->multiSelect() ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection );
    setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
    const QList< int > selectedItems = choice->currentChoices();
    if ( choice->multiSelect() )
    {
        for ( const int index : selectedItems )
            if ( index >= 0 && index < count() )
                item( index )->setSelected( true );
    }
    else
    {
        if ( selectedItems.count() == 1 && selectedItems.at(0) >= 0 && selectedItems.at(0) < count() )
        {
            setCurrentRow( selectedItems.at(0) );
            scrollToItem( item( selectedItems.at(0) ) );
        }
    }

    connect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged );

    setVisible( choice->isVisible() );
    setCursor( Qt::ArrowCursor );
}

void ListEdit::setFormWidgetsController( FormWidgetsController* controller )
{
    FormWidgetIface::setFormWidgetsController( controller );
    connect( m_controller, &FormWidgetsController::formListChangedByUndoRedo,
             this, &ListEdit::slotHandleFormListChangedByUndoRedo );
}

void ListEdit::slotSelectionChanged()
{
    const QList< QListWidgetItem * > selection = selectedItems();
    QList< int > rows;
    for ( const QListWidgetItem * item : selection ) {
        rows.append( row( item ) );
    }
    Okular::FormFieldChoice *form = static_cast<Okular::FormFieldChoice *>(m_ff);
    if ( rows != form->currentChoices() ) {
        emit m_controller->formListChangedByWidget( pageItem()->pageNumber(),
                                                   form,
                                                   rows );
    }
}

void ListEdit::slotHandleFormListChangedByUndoRedo( int pageNumber,
                                                    Okular::FormFieldChoice* listForm,
                                                    const QList< int > & choices )
{
    Q_UNUSED(pageNumber);

    if ( m_ff != listForm ) {
        return;
    }

    disconnect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged );
    for(int i=0; i < count(); i++)
    {
        item( i )->setSelected( choices.contains(i) );
    }
    connect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged );

    setFocus();
}

ComboEdit::ComboEdit( Okular::FormFieldChoice * choice, QWidget * parent )
    : QComboBox( parent ), FormWidgetIface( this, choice )
{
    addItems( choice->choices() );
    setEditable( true );
    setInsertPolicy( NoInsert );
    lineEdit()->setReadOnly( !choice->isEditable() );
    QList< int > selectedItems = choice->currentChoices();
    if ( selectedItems.count() == 1 && selectedItems.at(0) >= 0 && selectedItems.at(0) < count() )
        setCurrentIndex( selectedItems.at(0) );

    if ( choice->isEditable() && !choice->editChoice().isEmpty() )
        lineEdit()->setText( choice->editChoice() );

    connect( this, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ComboEdit::slotValueChanged );
    connect( this, &QComboBox::editTextChanged, this, &ComboEdit::slotValueChanged );
    connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged );

    setVisible( choice->isVisible() );
    setCursor( Qt::ArrowCursor );
    m_prevCursorPos = lineEdit()->cursorPosition();
    m_prevAnchorPos = lineEdit()->cursorPosition();
}

void ComboEdit::setFormWidgetsController(FormWidgetsController* controller)
{
    FormWidgetIface::setFormWidgetsController(controller);
    connect( m_controller, &FormWidgetsController::formComboChangedByUndoRedo,
             this, &ComboEdit::slotHandleFormComboChangedByUndoRedo);

}

void ComboEdit::slotValueChanged()
{
    const QString text = lineEdit()->text();

    Okular::FormFieldChoice *form = static_cast<Okular::FormFieldChoice *>(m_ff);

    QString prevText;
    if ( form->currentChoices().isEmpty() )
    {
        prevText = form->editChoice();
    }
    else
    {
        prevText = form->choices().at(form->currentChoices().constFirst());
    }

    int cursorPos = lineEdit()->cursorPosition();
    if ( text != prevText )
    {
        emit m_controller->formComboChangedByWidget( pageItem()->pageNumber(),
                                                    form,
                                                    currentText(),
                                                    cursorPos,
                                                    m_prevCursorPos,
                                                    m_prevAnchorPos
                                                  );
    }
    prevText = text;
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = cursorPos;
    if ( lineEdit()->hasSelectedText() ) {
        if ( cursorPos == lineEdit()->selectionStart() ) {
            m_prevAnchorPos = lineEdit()->selectionStart() + lineEdit()->selectedText().size();
        } else {
            m_prevAnchorPos = lineEdit()->selectionStart();
        }
    }
}

void ComboEdit::slotHandleFormComboChangedByUndoRedo( int pageNumber,
                                                      Okular::FormFieldChoice* form,
                                                      const QString & text,
                                                      int cursorPos,
                                                      int anchorPos )
{
    Q_UNUSED(pageNumber);

    if ( m_ff != form ) {
        return;
    }

    // Determine if text corrisponds to an index choices
    int index = -1;
    for ( int i = 0; i < count(); i++ )
    {
        if ( itemText(i) == text )
        {
            index = i;
        }
    }

    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = anchorPos;

    disconnect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged );
    const bool isCustomValue = index == -1;
    if ( isCustomValue )
    {
        setEditText( text );
    }
    else
    {
        setCurrentIndex( index );
    }
    lineEdit()->setCursorPosition( anchorPos );
    lineEdit()->cursorForward( true, cursorPos - anchorPos );
    connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged );
    setFocus();
}

void ComboEdit::contextMenuEvent( QContextMenuEvent* event )
{
    QMenu *menu = lineEdit()->createStandardContextMenu();

    QList<QAction *> actionList = menu->actions();
    enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct };

    QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu );
    QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu );
    connect( m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled );
    connect( m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled );
    kundo->setEnabled( m_controller->canUndo() );
    kredo->setEnabled( m_controller->canRedo() );

    QAction *oldUndo, *oldRedo;
    oldUndo = actionList[UndoAct];
    oldRedo = actionList[RedoAct];

    menu->insertAction( oldUndo, kundo );
    menu->insertAction( oldRedo, kredo );

    menu->removeAction( oldUndo );
    menu->removeAction( oldRedo );

    menu->exec( event->globalPos() );
    delete menu;
}

bool ComboEdit::event( QEvent* e )
{
    if ( e->type() == QEvent::KeyPress )
    {
        QKeyEvent *keyEvent = static_cast< QKeyEvent* >(e);
        if ( keyEvent == QKeySequence::Undo )
        {
            emit m_controller->requestUndo();
            return true;
        }
        else if ( keyEvent == QKeySequence::Redo )
        {
            emit m_controller->requestRedo();
            return true;
        }
    }
    return QComboBox::event( e );
}

SignatureEdit::SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent )
    : QAbstractButton( parent ), FormWidgetIface( this, signature ),
      m_widgetPressed( false ), m_dummyMode( false ), m_wasVisible( false )
{
    setCursor( Qt::PointingHandCursor );
    connect( this, &SignatureEdit::clicked, this, &SignatureEdit::slotViewProperties );
}

void SignatureEdit::setDummyMode( bool set )
{
    m_dummyMode = set;
    if ( m_dummyMode )
    {
        m_wasVisible = isVisible();
        //if widget was hidden then show it.
        //even if it wasn't hidden calling this will still update the background.
        setVisibility( true );
    }
    else
    {
        //forms were not visible before this call so hide this widget.
        if ( !m_wasVisible )
            setVisibility( false );
        //forms were visible even before this call so only update the background color.
        else
            update();
    }
}

bool SignatureEdit::event( QEvent * e )
{
    if ( m_dummyMode && e->type() != QEvent::Paint )
    {
        e->accept();
        return true;
    }

    switch ( e->type() )
    {
        case QEvent::MouseButtonPress:
        {
            QMouseEvent *ev = static_cast< QMouseEvent * >( e );
            if ( ev->button() == Qt::LeftButton )
            {
                m_widgetPressed = true;
                update();
            }
            break;
        }
        case QEvent::MouseButtonRelease:
        {
            QMouseEvent *ev = static_cast< QMouseEvent * >( e );
            if ( ev->button() == Qt::LeftButton)
            {
                m_widgetPressed = false;
                update();
            }
            break;
        }
        case QEvent::Leave:
        {
            m_widgetPressed = false;
            update();
        }
        default:
            break;
    }

    return QAbstractButton::event( e );
}

void SignatureEdit::contextMenuEvent( QContextMenuEvent * event )
{
    QMenu *menu = new QMenu( this );
    QAction *signatureProperties = new QAction( i18n("Signature Properties"), menu );
    connect( signatureProperties, &QAction::triggered, this, &SignatureEdit::slotViewProperties );
    menu->addAction( signatureProperties );
    menu->exec( event->globalPos() );
    delete menu;
}

void SignatureEdit::paintEvent( QPaintEvent * )
{
    QPainter painter( this );
    //no borders when user hasn't allowed the forms to be shown
    if ( m_dummyMode && !m_wasVisible )
    {
        painter.setPen( Qt::transparent );
    }
    else
    {
        painter.setPen( Qt::black );
    }

    if ( m_widgetPressed || m_dummyMode )
    {
        QColor col = palette().color( QPalette::Active, QPalette::Highlight );
        col.setAlpha(50);
        painter.setBrush( col );
    }
    else
    {
        painter.setBrush( Qt::transparent );
    }
    painter.drawRect( 0, 0, width()-2, height()-2 );
}

void SignatureEdit::slotViewProperties()
{
    if ( m_dummyMode )
        return;

    Okular::FormFieldSignature *formSignature = static_cast< Okular::FormFieldSignature * >( formField() );
    SignaturePropertiesDialog propDlg( m_controller->m_doc, formSignature, this );
    propDlg.exec();
}

// Code for additional action handling.
// Challenge: Change preprocessor magic to C++ magic!
//
// The mouseRelease event is special because the PDF spec
// says that the activation action takes precedence over this.
// So the mouse release action is only signaled if no activation
// action exists.
//
// For checkboxes the activation action is not triggered as
// they are still triggered from the clicked signal and additionally
// when the checked state changes.

#define DEFINE_ADDITIONAL_ACTIONS(FormClass, BaseClass) \
    void FormClass::mousePressEvent( QMouseEvent *event ) \
    { \
        Okular::Action *act = m_ff->additionalAction( Okular::Annotation::MousePressed ); \
        if ( act ) \
        { \
            m_controller->signalAction( act ); \
        } \
        BaseClass::mousePressEvent( event ); \
    } \
    void FormClass::mouseReleaseEvent( QMouseEvent *event ) \
    { \
        if ( !QWidget::rect().contains( event->localPos().toPoint() ) ) \
        { \
            BaseClass::mouseReleaseEvent( event ); \
            return; \
        } \
        Okular::Action *act = m_ff->activationAction(); \
        if ( act && !qobject_cast< CheckBoxEdit* > ( this ) ) \
        { \
            m_controller->signalAction( act ); \
        } \
        else if ( ( act = m_ff->additionalAction( Okular::Annotation::MouseReleased ) ) ) \
        { \
            m_controller->signalAction( act ); \
        } \
        BaseClass::mouseReleaseEvent( event ); \
    } \
    void FormClass::focusInEvent( QFocusEvent *event ) \
    { \
        Okular::Action *act = m_ff->additionalAction( Okular::Annotation::FocusIn ); \
        if ( act ) \
        { \
            m_controller->processScriptAction( act, m_ff, Okular::Annotation::FocusIn ); \
        } \
        BaseClass::focusInEvent( event ); \
    } \
    void FormClass::focusOutEvent( QFocusEvent *event ) \
    { \
        Okular::Action *act = m_ff->additionalAction( Okular::Annotation::FocusOut ); \
        if ( act ) \
        { \
            m_controller->processScriptAction( act, m_ff, Okular::Annotation::FocusOut ); \
        } \
        BaseClass::focusOutEvent( event ); \
    } \
    void FormClass::leaveEvent( QEvent *event ) \
    { \
        Okular::Action *act = m_ff->additionalAction( Okular::Annotation::CursorLeaving ); \
        if ( act ) \
        { \
            m_controller->signalAction( act ); \
        } \
        BaseClass::leaveEvent( event ); \
    } \
    void FormClass::enterEvent( QEvent *event ) \
    { \
        Okular::Action *act = m_ff->additionalAction( Okular::Annotation::CursorEntering ); \
        if ( act ) \
        { \
            m_controller->signalAction( act ); \
        } \
        BaseClass::enterEvent( event ); \
    }

DEFINE_ADDITIONAL_ACTIONS( PushButtonEdit, QPushButton )
DEFINE_ADDITIONAL_ACTIONS( CheckBoxEdit, QCheckBox )
DEFINE_ADDITIONAL_ACTIONS( RadioButtonEdit, QRadioButton )
DEFINE_ADDITIONAL_ACTIONS( FormLineEdit, QLineEdit )
DEFINE_ADDITIONAL_ACTIONS( TextAreaEdit, KTextEdit )
DEFINE_ADDITIONAL_ACTIONS( FileEdit, KUrlRequester )
DEFINE_ADDITIONAL_ACTIONS( ListEdit, QListWidget )
DEFINE_ADDITIONAL_ACTIONS( ComboEdit, QComboBox )
DEFINE_ADDITIONAL_ACTIONS( SignatureEdit, QAbstractButton )

#undef DEFINE_ADDITIONAL_ACTIONS
