/*
 *   This file is part of Dianara
 *   Copyright 2012-2019  JanKusanagi JRR <jancoding@gmx.com>
 *
 *   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, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .
 */

#include "composer.h"


Composer::Composer(GlobalObject *globalObject, bool forPublisher,
                   QWidget *parent) : QTextEdit(parent)
{
    m_globalObject = globalObject;
    m_forPublisher = forPublisher;

    this->setAcceptRichText(true);
    this->setTabChangesFocus(true);

#ifdef HAVE_SONNET_SPELLCHECKER
    m_spellDecorator = new Sonnet::SpellCheckDecorator(this);
#endif

    m_clickToPostString = tr("Click here or press Control+N to post a note...");


    // A menu to insert some Unicode symbols
    m_symbolsMenu = new QMenu(tr("Symbols"), this);
    m_symbolsMenu->setIcon(QIcon::fromTheme("character-set"));
    m_symbolsMenu->addAction(QString::fromUtf8("\342\230\272")); // Smiling face
    m_symbolsMenu->addAction(QString::fromUtf8("\342\230\271")); // Sad face
    m_symbolsMenu->addAction(QString::fromUtf8("\342\231\245")); // Heart
    m_symbolsMenu->addAction(QString::fromUtf8("\342\231\253")); // Musical note
    m_symbolsMenu->addAction(QString::fromUtf8("\342\230\225")); // Coffee
    m_symbolsMenu->addAction(QString::fromUtf8("\342\234\224")); // Check mark
    m_symbolsMenu->addAction(QString::fromUtf8("\342\234\230")); // Ballot X
    m_symbolsMenu->addAction(QString::fromUtf8("\342\230\205")); // Black star
    m_symbolsMenu->addAction(QString::fromUtf8("\342\254\205")); // Arrow to the left
    m_symbolsMenu->addAction(QString::fromUtf8("\342\236\241")); // Arrow to the right
    m_symbolsMenu->addAction(QString::fromUtf8("\342\231\273")); // Recycling symbol
    m_symbolsMenu->addAction(QString::fromUtf8("\342\210\236")); // Infinity
    connect(m_symbolsMenu, &QMenu::triggered,
            this, &Composer::insertSymbol);


    m_toolsMenu = new QMenu(tr("Formatting"), this);
    m_toolsMenu->addAction(tr("Normal"),
                           this,
                           SLOT(makeNormal()));
    m_toolsMenu->addAction(QIcon::fromTheme("format-text-bold"),
                           tr("Bold"),
                           this,
                           SLOT(makeBold()),
                           QKeySequence("Ctrl+B"));
    m_toolsMenu->addAction(QIcon::fromTheme("format-text-italic"),
                           tr("Italic"),
                           this,
                           SLOT(makeItalic()),
                           QKeySequence("Ctrl+I"));
    m_toolsMenu->addAction(QIcon::fromTheme("format-text-underline"),
                           tr("Underline"),
                           this,
                           SLOT(makeUnderline()),
                           QKeySequence("Ctrl+U"));
    m_toolsMenu->addAction(QIcon::fromTheme("format-text-strikethrough"),
                           tr("Strikethrough"),
                           this,
                           SLOT(makeStrikethrough()));

    m_toolsMenu->addSeparator();

    m_toolsMenu->addAction(QIcon::fromTheme("format-font-size-more"),
                           tr("Header"),
                           this,
                           SLOT(makeHeader()),
                           QKeySequence("Ctrl+H"));
    m_toolsMenu->addAction(QIcon::fromTheme("format-list-unordered"),
                           tr("List"),
                           this,
                           SLOT(makeList()));
    m_toolsMenu->addAction(QIcon::fromTheme("insert-table"),
                           tr("Table"),
                           this,
                           SLOT(makeTable()));
    m_toolsMenu->addAction(QIcon::fromTheme("format-justify-fill"),
                           tr("Preformatted block"),
                           this,
                           SLOT(makePreformatted()));
    m_toolsMenu->addAction(QIcon::fromTheme("format-text-italic"),
                           tr("Quote block"),
                           this,
                           SLOT(makeQuote()),
                           QKeySequence("Ctrl+O"));

    m_toolsMenu->addSeparator();

#ifdef HAVE_SONNET_SPELLCHECKER
    m_toolsMenu->addAction(QIcon::fromTheme("tools-check-spelling"),
                           tr("Configure spell checking..."),
                           this,
                           SLOT(requestSpellConfigDialog()));

    m_toolsMenu->addSeparator();
#endif

    m_toolsMenu->addAction(QIcon::fromTheme("insert-link"),
                           tr("Make a link"),
                           this,
                           SLOT(makeLink()),
                           QKeySequence("Ctrl+L"));
    m_toolsMenu->addAction(QIcon::fromTheme("insert-image"),
                           tr("Insert an image from a web site"),
                           this,
                           SLOT(insertImage()),
                           QKeySequence("Ctrl+P"));
    m_toolsMenu->addAction(QIcon::fromTheme("insert-horizontal-rule"),
                           tr("Insert line"),
                           this,
                           SLOT(insertLine()),
                           QKeySequence("Ctrl+Shift+L"));

    m_toolsMenu->addSeparator();

    m_toolsMenu->addMenu(m_symbolsMenu);


    m_toolsButton = new QPushButton(QIcon::fromTheme("format-list-ordered",
                                                     QIcon(":/images/button-configure.png")),
                                    tr("&Format",
                                       "Button for text formatting and related options"),
                                    this);
    m_toolsButton->setMenu(m_toolsMenu);
    m_toolsButton->setToolTip("<b></b>"
                              + tr("Text Formatting Options"));


#ifdef HAVE_KCHARSELECT
    m_charPickerButton = new QToolButton(this);
    m_charPickerButton->setIcon(QIcon::fromTheme("face-smile",
                                                 QIcon(":/images/button-symbols.png")));
    m_charPickerButton->setToolTip(tr("Insert symbols"));
    connect(m_charPickerButton, &QToolButton::clicked,
            this, &Composer::showCharacterPicker);
#endif


    // Extra action for the context menu, paste as plaintext
    m_pastePlaintextAction = new QAction(QIcon::fromTheme("edit-paste"),
                                         tr("Paste Text Without Formatting"),
                                         this);
    m_pastePlaintextAction->setShortcut(QKeySequence("Ctrl+Shift+V"));
    connect(m_pastePlaintextAction, &QAction::triggered,
            this, &Composer::pasteAsPlaintext);

    // Add action to this object, in order for the shortcut to work
    // Otherwise, the action isn't available until the context menu is present
    this->addAction(m_pastePlaintextAction);


    // Nick completion stuff
    m_nickCompleter = new QCompleter(m_globalObject->getNickCompletionModel(),
                                     this);
    m_nickCompleter->setWidget(this);
    m_nickCompleter->setMaxVisibleItems(15);
    m_nickCompleter->setCompletionColumn(0);
    m_nickCompleter->setCaseSensitivity(Qt::CaseInsensitive);
    m_nickCompleter->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
    connect(m_nickCompleter, SIGNAL(activated(QModelIndex)), // Old-style connect()
            this, SLOT(insertCompletedNick(QModelIndex))); // due to signal overload

    m_popupTableView = new QTableView(this);
    m_popupTableView->horizontalHeader()->hide();
    m_popupTableView->verticalHeader()->hide();
    m_popupTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_popupTableView->setAlternatingRowColors(true);
    m_popupTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_popupTableView->setSelectionMode(QAbstractItemView::SingleSelection);
    m_popupTableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    m_nickCompleter->setPopup(m_popupTableView);


    if (m_forPublisher)  // Publisher mode
    {
        this->setToolTip("<b></b>"
                         + tr("Type a message here to post it"));

        this->setPlaceholderText(m_clickToPostString);
    }
    else                     // or Commenter mode
    {
        this->setPlaceholderText(tr("Type a comment here"));
    }

    qDebug() << "Composer box created";
}


Composer::~Composer()
{
    qDebug() << "Composer box destroyed";
}



void Composer::erase()
{
    this->clear();

    if (m_forPublisher)
    {
        this->setPlaceholderText(m_clickToPostString);
    }
}


void Composer::insertLink(QString url, QString title)
{
    bool prettyLink = true;
    if (title.isEmpty())
    {
        prettyLink = false;
        title = url;
    }

    this->insertHtml("<a href=\"" + url + "\">"
                     + title + "</a>");

    // Space after, when there's no text after the link,
    // or the link is inserted without selecting text before,
    // so what the user types next is regular text
    if (this->textCursor().atEnd() || !prettyLink)
    {
        this->insertHtml("&nbsp;");
        // Could use this->makeNormal(), but has some drawbacks
    }
}


void Composer::requestCompletion(QString partialNick)
{
    m_nickCompleter->setCompletionPrefix(partialNick);
    m_popupTableView->ensurePolished();

    m_popupTableView->resizeColumnsToContents();
    m_popupTableView->resizeRowsToContents();
    m_popupTableView->ensurePolished();

    int tableWidth = m_popupTableView->columnWidth(0)
                     + m_popupTableView->columnWidth(1) + 2;
    if (tableWidth > this->width() + 2)
    {
        tableWidth = this->width() + 2;
        m_popupTableView->setColumnWidth(0, (tableWidth / 4) * 3);
        m_popupTableView->setColumnWidth(1, tableWidth / 4);
    }

    m_popupTableView->setMinimumWidth(tableWidth);

    int rows = qMin(m_popupTableView->model()->rowCount(), 15);
    int rowHeight = m_popupTableView->rowHeight(0) + 4;
    m_popupTableView->setMinimumHeight(rows * rowHeight);

    // Popup might get disabled at some point when Composer is used in a Post
    m_popupTableView->setEnabled(true);

    m_nickCompleter->complete(QRect(this->cursorRect().x(),
                                    this->cursorRect().y() + 24,
                                    tableWidth,
                                    1));
}


/*
 * Hide placeholder message
 *
 */
void Composer::hideInfoMessage()
{
    this->setPlaceholderText(QString());
}


QPushButton *Composer::getToolsButton()
{
    return m_toolsButton;
}

#ifdef HAVE_KCHARSELECT
QToolButton *Composer::getCharPickerButton()
{
    return m_charPickerButton;
}
#endif

/*
 * Enable or disable the Ctrl+Shift+V action to paste without format
 *
 * Needed to avoid this conflict between Publisher and Commenters:
 *
 *   QAction::eventFilter: Ambiguous shortcut overload: Ctrl+Shift+V
 *
 */
void Composer::setPlainPasteEnabled(bool state)
{
    m_pastePlaintextAction->setEnabled(state);
}


/*****************************************************************************/
/****************************** PROTECTED ************************************/
/*****************************************************************************/


/*
 * Send a signal when getting focus
 *
 */
void Composer::focusInEvent(QFocusEvent *event)
{
    emit focusReceived(); // inform Publisher() or Commenter() that we have focus
    QTextEdit::focusInEvent(event); // process standard event: allows context menu

    qDebug() << "Composer box got focus";
}

/*
 * Same signal when having something dropped into the widget
 *
 */
void Composer::dropEvent(QDropEvent *event)
{
    qDebug() << "Composer received a drag-and-drop:"
             << event->mimeData()->text();

    emit focusReceived();

    QList<QUrl> urls = event->mimeData()->urls();

    // Something with a URL was dropped, probably a local file
    if (!urls.isEmpty())
    {
        qDebug() << "** Dropped URLs:" << urls;

        QString fileUrl = urls.first().toLocalFile();
        if (urls.first().isLocalFile()) // Local, but might be a directory
        {
            if (QFileInfo(fileUrl).isFile())
            {
                qDebug() << "**** It's a local file:" << fileUrl;
                if (urls.count() == 1)
                {
                    // Clear possible prior error messages
                    emit errorHappened(QString());
                }
                else
                {
                    // Notify user that only one file can be attached
                    emit errorHappened(tr("You can attach only one file."));
                }

                emit fileDropped(fileUrl);
            }
            else
            {
                qDebug() << "**** Dropped a folder!";
                emit errorHappened(tr("You cannot drop folders here, "
                                      "only a single file."));
            }

            return;
        }
    }

    QTextEdit::dropEvent(event);
}



void Composer::keyPressEvent(QKeyEvent *event)
{
    // Allow cancelling/accepting the autocompletion popup
    if (m_nickCompleter->popup()->isVisible())
    {
        if (event->key() == Qt::Key_Enter
         || event->key() == Qt::Key_Return
         || event->key() == Qt::Key_Escape)
        {
            event->ignore();
            return;
        }
    }


    // Control+Enter = Send message (post)
    if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
      && event->modifiers() == Qt::ControlModifier)
    {
        qDebug() << "Control+Enter was pressed";
        emit editingFinished();
    }
    else if (event->key() == Qt::Key_Escape)
    {
        qDebug() << "Escape was pressed";
        if (this->toPlainText().isEmpty())
        {
            qDebug() << "There was no text, cancelling post";
            this->cancelPost();
        }
    }
    else if (event->key() == Qt::Key_Up
          && event->modifiers() != Qt::ShiftModifier)
    {
        QTextCursor currentCursor = this->textCursor();
        if (currentCursor.movePosition(QTextCursor::Up)) // true=cursor is able to go up
        {
            QTextEdit::keyPressEvent(event);
        }
        else
        {
            qDebug() << "KEYPRESS UP: Can't go up, will focus on TITLE field";
            emit focusTitleRequested();
        }
    }
    else
    {
        QTextEdit::keyPressEvent(event);

        QTextCursor currentCursor = this->textCursor();
        currentCursor.select(QTextCursor::WordUnderCursor);

        if (currentCursor.document()->characterAt(currentCursor.selectionStart() - 1)
            == QChar('@'))
        {
            // Show completer
            this->requestCompletion(currentCursor.selectedText());
        }
        else
        {
            // Hide it, if it was visible
            m_nickCompleter->popup()->hide();
        }
    }

    event->accept();
}


/*
 * For custom context menu
 *
 */
void Composer::contextMenuEvent(QContextMenuEvent *event)
{
    m_customContextMenu = this->createStandardContextMenu();

    // 'Formating' menu before default context menu
    m_customContextMenu->insertMenu(m_customContextMenu->actions().at(0),
                                    m_toolsMenu);
    m_customContextMenu->insertSeparator(m_customContextMenu->actions().at(1));

    // And options added after default context menu
    m_customContextMenu->addSeparator();


    m_customContextMenu->addAction(m_pastePlaintextAction);
    m_pastePlaintextAction->setEnabled(this->canPaste());


    m_customContextMenu->exec(event->globalPos());

    // FIXME: Possible leak... should delete m_customContextMenu?
    m_customContextMenu->deleteLater();

    event->accept();
}


/*
 * Intervene when pasting, so we can turn link-looking text into real
 * HTML links, and turn direct links to images into embedded images
 *
 */
void Composer::insertFromMimeData(const QMimeData *source)
{
    /*
     * Prevent larjona's crash.
     *
     * Apparently the QMessageBox will mess with the 'source' pointer
     * if the contents of the clipboard came from another program.
     * This causes a segfault when selecting "insert as link".
     *
     * To avoid it, all data needed from 'source' will be stored before showing
     * the message box.
     *
     * https://gitlab.com/dianara/dianara-dev/issues/33
     *
     */

    const QString pastedHtml = source->html();
    const QString pastedText = source->text().trimmed();

    const bool sourceHasHtml = source->hasHtml();


    // First, check if it's just a link to an image, to offer embedding it
    if (pastedText.startsWith("http://")
     || pastedText.startsWith("https://"))
    {
        // If link looks like an image, ask the user how to insert it
        if (pastedText.endsWith(".png", Qt::CaseInsensitive)
         || pastedText.endsWith(".jpg", Qt::CaseInsensitive)
         || pastedText.endsWith(".jpeg", Qt::CaseInsensitive)
         || pastedText.endsWith(".gif", Qt::CaseInsensitive))
        {
            const int insertionType = QMessageBox::question(this,
                                            tr("Insert as image?"),
                                            tr("The link you are pasting "
                                               "seems to point to an image.")
                                            + "\n\n\n",
                                            tr("Insert as visible image"), // Default option (Enter)
                                            tr("Insert as link"),          // ESC option
                                            QString(), 0, 1);

            if (insertionType == 0) // Default button, insert as image
            {
                this->insertHtml("<img src=\"" + pastedText + "\" />"
                                 "<br/><br/>");
                return;
            }
        }
    }

    // If it wasn't just an image link, or offer to embed was rejected, continue

    // WARNING: Avoid accessing 'source' at this point, since the QMessageBox
    //          might have messed with it

    QTextDocument textDocument;
    if (sourceHasHtml)
    {
        textDocument.setHtml(pastedHtml);
        qDebug() << "Parsing pasted RICH (HTML) text for links...";
    }
    else
    {
        textDocument.setPlainText(pastedText);
        qDebug() << "Parsing pasted PLAIN text for links...";
    }


    QTextCursor cursor = QTextCursor(&textDocument);

    do
    {
        cursor.select(QTextCursor::WordUnderCursor);

        QString currentWord = cursor.selection().toPlainText()
                                                .trimmed(); // Strip possible zero-widths

        qDebug() << "WORD:" << currentWord;

        if (currentWord == QStringLiteral("http")
         || currentWord == QStringLiteral("https")
         || currentWord == QStringLiteral("ftp")
         || currentWord == QStringLiteral("ftps")
         || currentWord == QStringLiteral("sftp"))
        {
            qDebug() << "Text might be a link";

            qDebug() << "Cursor is anchor? " << cursor.charFormat().isAnchor();
            if (!cursor.charFormat().isAnchor())
            {
                qDebug() << "URL-looking text which isn't a link; "
                            "Replacing with proper link!";

                // Select text until end of pasted text, or a space/line break
                while (cursor.movePosition(QTextCursor::NextCharacter,
                                           QTextCursor::KeepAnchor))
                {
                    if (textDocument.characterAt(cursor.position()).isSpace())
                    {
                        break;
                    }
                }

                const QString linkFound = cursor.selection().toPlainText();

                if (linkFound.startsWith(QStringLiteral("http://"))
                 || linkFound.startsWith(QStringLiteral("https://"))
                 || linkFound.startsWith(QStringLiteral("ftp://"))
                 || linkFound.startsWith(QStringLiteral("ftps://"))
                 || linkFound.startsWith(QStringLiteral("sftp://")))
                {
                    // Turn current selection into a link to the same as the visible text
                    QTextCharFormat format = cursor.charFormat();
                    format.setAnchorHref(linkFound);
                    format.setAnchor(true);
                    cursor.setCharFormat(format);

                    qDebug() << "Linkified: " << linkFound;
                }
            }
        }
    } while (cursor.movePosition(QTextCursor::NextWord));


    this->insertHtml(textDocument.toHtml() + " ");
}



/*****************************************************************************/
/******************************** SLOTS **************************************/
/*****************************************************************************/



/*
 * Remove text formatting from selection, bold, italic, etc.
 *
 */
void Composer::makeNormal()
{
    QTextCharFormat charFormat;
    charFormat.clearForeground();
    charFormat.clearBackground();
    this->setCurrentCharFormat(charFormat);

    this->setFocus();
}



/*
 * Make selected text bold
 *
 */
void Composer::makeBold()
{
    QTextCharFormat charFormat;
    if (this->currentCharFormat().fontWeight() == QFont::Bold)
    {
        charFormat.setFontWeight(QFont::Normal);
    }
    else
    {
        charFormat.setFontWeight(QFont::Bold);
    }
    this->mergeCurrentCharFormat(charFormat);

    this->setFocus(); // Give focus back to text editor
}


/*
 * Make selected text italic
 *
 */
void Composer::makeItalic()
{
    QTextCharFormat charFormat;
    charFormat.setFontItalic(!this->currentCharFormat().fontItalic());
    this->mergeCurrentCharFormat(charFormat);

    this->setFocus();
}



/*
 * Underline selected text
 *
 */
void Composer::makeUnderline()
{
    QTextCharFormat charFormat;
    charFormat.setFontUnderline(!this->currentCharFormat().fontUnderline());
    this->mergeCurrentCharFormat(charFormat);

    this->setFocus();
}

/*
 * Strike out selected text
 *
 */
void Composer::makeStrikethrough()
{
    QTextCharFormat charFormat;
    charFormat.setFontStrikeOut(!this->currentCharFormat().fontStrikeOut());
    this->mergeCurrentCharFormat(charFormat);

    this->setFocus();
}

/*
 * Turn the selected text into an <h2> header
 *
 */
void Composer::makeHeader()
{
    const QString selectedText = this->textCursor().selectedText();

    if (!selectedText.isEmpty())
    {
        this->textCursor().removeSelectedText();
        this->insertHtml("<h2>" + selectedText + "</h2> ");
    }

    this->setFocus();
}


void Composer::makeList()
{
    QString selectedText = this->textCursor().selection().toHtml();
    selectedText = MiscHelpers::cleanupHtml(selectedText);

    if (selectedText.isEmpty())
    {
        this->textCursor().insertList(QTextListFormat::ListDisc);
    }
    else
    {
        // Capture only the HTML of each line by itself
        QRegExp pRE("<p .*>(.*)</p>");
        pRE.setMinimal(true);

        QString listHtml = "<ul>";

        int pos = 0;
        while ((pos = pRE.indexIn(selectedText, pos)) != -1)
        {
            listHtml.append("<li>" + pRE.cap(1) + "</li>");
            pos += pRE.matchedLength();
        }

        listHtml.append("</ul><br />");


        this->textCursor().removeSelectedText();
        this->insertHtml(listHtml);
        this->textCursor().deletePreviousChar(); // Delete undesired extra newline
    }

    this->setFocus();
}


void Composer::makeTable()
{
    const QString dialogTitle = tr("Table Size");

    bool inputOk = false;
    int rows = QInputDialog::getInt(this, dialogTitle,
                                    tr("How many rows (height)?")
                                    + "       " // Make the dialog a little wider than necessary
                                    + QString::fromUtf8("\342\207\225") // up-down arrow
                                    + "\n\n",
                                    5,
                                    1, 10,
                                    1, &inputOk);

    if (inputOk) // Rows dialog wasn't cancelled
    {
        int columns = QInputDialog::getInt(this, dialogTitle,
                                           tr("How many columns (width)?")
                                           + "       "
                                           + QString::fromUtf8("\342\207\224") // left-right arrow
                                           + "\n\n",
                                           4,
                                           1, 10,
                                           1, &inputOk);

        if (inputOk) // Columns dialog wasn't cancelled either
        {
            QTextTableFormat tableFormat;
            tableFormat.setCellPadding(2);
            tableFormat.setCellSpacing(4);

            this->textCursor().insertTable(rows, columns, tableFormat);
        }
    }

    this->setFocus();
}



/*
 * Put selected text into a <pre> block
 *
 */
void Composer::makePreformatted()
{
    QString selectedText = this->textCursor().selectedText();

    if (!selectedText.isEmpty())
    {
        this->textCursor().removeSelectedText();
        this->insertHtml("<pre>" + selectedText + "</pre> ");
    }

    this->setFocus();
}

/*
 * Mark selected as quoted, using <blockquote>
 *
 */
void Composer::makeQuote()
{
    QString selectedText = this->textCursor().selectedText();

    if (!selectedText.isEmpty())
    {
        this->textCursor().removeSelectedText();
        this->insertHtml("&nbsp; "
                         "<blockquote>&ldquo;" + selectedText
                         + "&rdquo;</blockquote>"
                           "<br />");
        this->textCursor().deletePreviousChar(); // Delete undesired extra newline
    }
    // FIXME: Qt's HTML changes <blockquote> into its own formatting
    // so other clients and the web UI might not display it as other people's blockquote's

    this->setFocus();
}


/*
 * Request Sonnet's spellchecking configuration dialog, via GlobalObject
 *
 */
void Composer::requestSpellConfigDialog()
{
    m_globalObject->showSpellConfigDialog();
}


/*
 * Convert selected text into a link
 *
 */
void Composer::makeLink()
{
    QString selectedText = this->textCursor().selectedText();
    QString link;

    bool validLink = false;
    bool dialogOk = true;

    while (!validLink)
    {
        if (selectedText.isEmpty())
        {
            link = QInputDialog::getText(this,
                                         tr("Insert a link"),
                                         tr("Type or paste a web address "
                                            "here.\n"
                                            "You could also select some "
                                            "text first, to turn it into "
                                            "a link.")
                                         + "\n\n",
                                         QLineEdit::Normal,
                                         "http://",
                                         &dialogOk);
        }
        else
        {
            QString shortenedText = MiscHelpers::elidedText(selectedText, 40);

            link = QInputDialog::getText(this,
                                         tr("Make a link from selected text"),
                                         tr("Type or paste a web address "
                                            "here.\n"
                                            "The selected text (%1) will be "
                                            "converted to a link.")
                                         .arg("'" + shortenedText + "'")
                                         + "\n\n",
                                         QLineEdit::Normal,
                                         "http://",
                                         &dialogOk);
        }

        if (!dialogOk)
        {
            this->setFocus();
            return;
        }

        link = link.trimmed(); // Remove possible spaces before or after

        if (link.startsWith("http://") || link.startsWith("https://")
         || link.startsWith("ftp://")  || link.startsWith("ftps://")
         || link.startsWith("sftp://") || link.startsWith("mailto:"))
        {
            validLink = true;
        }

        if (!validLink)
        {
            int choice = QMessageBox::warning(this,
                                              tr("Invalid link"),
                                              tr("The text you entered does "
                                                 "not look like a link.")
                                              + "<br/><br/>"
                                              + tr("It should start with "
                                                   "one of these types:",
                                                   "It = the link, from "
                                                   "previous sentence")
                                              + "<br/>"
                                                "<ul>"
                                                "<li>http://</li>"
                                                "<li>https://</li>"
                                                "<li>ftp://</li>"
                                                "<li>ftps://</li>"
                                                "<li>sftp://</li>"
                                                "<li>mailto:</li>"
                                                "</ul>"
                                                "<br/><br/><br/>",
                                              tr("&Use it anyway"),
                                              tr("&Enter it again"),
                                              tr("&Cancel link"),
                                              1, 2);
            if (choice == 0)
            {
                validLink = true;
            }
            else if (choice == 2)
            {
                this->setFocus();
                return;
            }
        }
    }

    this->textCursor().removeSelectedText();
    this->insertLink(link, selectedText);


    this->setFocus();
}



/*
 * Insert an image from a URL
 *
 */
void Composer::insertImage()
{
    // FIXME: should make sure there is no text selected

    QString imageUrl;
    imageUrl = QInputDialog::getText(this,
                             tr("Insert an image from a URL"),
                             tr("Type or paste the image address here.\n"
                                "The link must point to the image file directly.")
                             + "\n\n",
                             QLineEdit::Normal,
                             "http://");


    if (!imageUrl.isEmpty())
    {
        if (imageUrl.startsWith("http://")
         || imageUrl.startsWith("https://"))
        {
            this->insertHtml("<img src=\"" + imageUrl + "\" />"
                             "<br/><br/>");
        }
        else
        {
            QMessageBox::warning(this, tr("Error: Invalid URL"),
                                 tr("The address you entered (%1) "
                                    "is not valid.\n"
                                    "Image addresses should begin "
                                    "with http:// or https://").arg(imageUrl));
        }
    }
    else
    {
        qDebug() << "insertImage(): Image URL is empty";
    }

    this->setFocus();
}

/*
 * Insert a horizontal line, <hr>
 *
 */
void Composer::insertLine()
{
    this->insertHtml("<hr><br>");

    this->setFocus();
}



void Composer::insertSymbol(QAction *action)
{
    this->insertPlainText(action->text());
    this->insertPlainText(" ");

    this->setFocus();
}


#ifdef HAVE_KCHARSELECT
void Composer::showCharacterPicker()
{
    // Make focus default to the composer when the character picker is closed
    this->setFocus();

    m_characterPicker = new CharacterPicker(this);
    connect(m_characterPicker, &CharacterPicker::characterSelected,
            this, &Composer::insertHtml);
    m_characterPicker->show();
}
#endif


void Composer::pasteAsPlaintext()
{
    QString subtype("plain");
    QString clipboardContents = QApplication::clipboard()->text(subtype,
                                                                QClipboard::Clipboard);

    this->makeNormal(); // Ensure normal text before inserting
    this->insertPlainText(clipboardContents);
}



/*
 * Insert the selected nick chosen from the autocomplete list.
 * Notify the parent object about it so the user can be added
 * to To/Cc.
 *
 */
void Composer::insertCompletedNick(QModelIndex nickData)
{
    QString nickId = nickData.data(Qt::UserRole + 1).toString();
    QString nickName = nickData.data().toString();
    QString nickUrl = nickData.data(Qt::UserRole + 2).toString();

    // Abort if there's no ID; this can happen if the ID column is clicked
    if (nickId.isEmpty())
    {
        qDebug() << "*** AUTOCOMPLETER: ID IS EMPTY! (clicked 2nd column?)";

        return;
    }

    QTextCursor textCursor = this->textCursor();
    textCursor.select(QTextCursor::WordUnderCursor);
    textCursor.removeSelectedText();

    this->insertHtml("<a href=\"" + nickUrl + "\">"
                     + nickName + "</a>");
    this->makeNormal();

    // Send signal for Publisher(), to add to the "To" field
    emit nickInserted(nickId, nickName, nickUrl, "to");
}




/*
 * Cancel editing of the post, clear it, return to minimum mode
 *
 */
void Composer::cancelPost()
{
    int cancelConfirmed = 2;
    int defaultButton = 1; // Initially planning for 2 buttons

    if (this->document()->isEmpty())
    {
        cancelConfirmed = 0; // Cancelling doesn't need confirmation if it's empty
    }
    else
    {
        QString saveDraftString;
        if (m_forPublisher)
        {
            saveDraftString = tr("Yes, but saving a &draft");
            defaultButton = 2; // Since in this case there are 3 buttons
        }

        cancelConfirmed = QMessageBox::question(this,
                                                tr("Cancel message?"),
                                                tr("Are you sure you want to "
                                                   "cancel this message?"),
                                                tr("&Yes, cancel it"),
                                                saveDraftString,
                                                tr("&No"),
                                                defaultButton, defaultButton);
    }


    if (cancelConfirmed == 0)
    {
        this->erase();

        // Emit signal to make Publisher go back to minimum mode
        emit editingCancelled();

        qDebug() << "Post cancelled";
    }
    else if (cancelConfirmed == 1) // Save draft
    {
        emit cancelSavingDraftRequested();
    }
}
