void ExtendedTableWidget::cellClicked(const QModelIndex& index)
{
    // If Ctrl-Shift is pressed try to jump to the row referenced by the foreign key of the clicked cell
    if(qApp->keyboardModifiers().testFlag(Qt::ControlModifier) && qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && model())
    {
        SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
        sqlb::ForeignKeyClause fk = m->getForeignKeyClause(index.column()-1);

        if(fk.isSet())
            emit foreignKeyClicked(sqlb::ObjectIdentifier(m->currentTableName().schema(), fk.table()),
                                   fk.columns().size() ? fk.columns().at(0) : "",
                                   m->data(index, Qt::EditRole).toByteArray());
    }
}
void ExtendedTableWidget::copy()
{
    QModelIndexList indices = selectionModel()->selectedIndexes();

    // Abort if there's nothing to copy
    if (indices.isEmpty())
        return;
    qSort(indices);

    SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());

    // If a single cell is selected, copy it to clipboard
    if (indices.size() == 1) {
        QImage img;
        QVariant data = m->data(indices.first(), Qt::EditRole);

        if (img.loadFromData(data.toByteArray())) { // If it's an image
            qApp->clipboard()->setImage(img);
            return;
        } else {
            qApp->clipboard()->setText(data.toString());
            return;
        }
    }

    m_buffer.clear();

    // If any of the cells contain binary data - we use inner buffer
    bool containsBinary = false;
    foreach (const QModelIndex& index, indices)
        if (m->isBinary(index)) {
            containsBinary = true;
            break;
        }

    if (containsBinary) {
        qApp->clipboard()->clear();
        // Copy selected data into inner buffer
        int columns = indices.last().column() - indices.first().column() + 1;
        while (!indices.isEmpty()) {
            QByteArrayList lst;
            for (int i = 0; i < columns; ++i) {
                lst << indices.first().data(Qt::EditRole).toByteArray();
                indices.pop_front();
            }
            m_buffer.push_back(lst);
        }

        return;
    }

    QModelIndex first = indices.first();
    QString result;
    int currentRow = 0;

    foreach(const QModelIndex& index, indices) {
        if (first == index) { /* first index */ }
        else if (index.row() != currentRow)
            result.append("\r\n");
        else
            result.append("\t");

        currentRow = index.row();
        QVariant data = index.data(Qt::EditRole);

        // non-NULL data is enquoted, whilst NULL isn't
        if (!data.isNull()) {
            QString text = data.toString();
            text.replace("\"", "\"\"");
            result.append(QString("\"%1\"").arg(text));
        }
    }

    qApp->clipboard()->setText(result);
}
void ExtendedTableWidget::copy(const bool withHeaders)
{
    QModelIndexList indices = selectionModel()->selectedIndexes();

    // Remove all indices from hidden columns, because if we don't we might copy data from hidden columns as well which is very
    // unintuitive; especially copying the rowid column when selecting all columns of a table is a problem because pasting the data
    // won't work as expected.
    QMutableListIterator<QModelIndex> i(indices);
    while (i.hasNext()) {
        if (isColumnHidden(i.next().column()))
            i.remove();
    }

    // Abort if there's nothing to copy
    if (indices.isEmpty())
        return;

    SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());

    // Clear internal copy-paste buffer
    m_buffer.clear();

    // If a single cell is selected, copy it to clipboard
    if (!withHeaders && indices.size() == 1) {
        QImage img;
        QVariant data = m->data(indices.first(), Qt::EditRole);

        if (img.loadFromData(data.toByteArray()))
        {
            // If it's an image, copy the image data to the clipboard
            qApp->clipboard()->setImage(img);
            return;
        } else {
            // It it's not an image, check if it's an empty field
            if (data.toByteArray().isEmpty())
            {
                // The field is either NULL or empty. Those are are handled via the internal copy-paste buffer
                qApp->clipboard()->setText(QString());      // Calling clear() alone doesn't seem to work on all systems
                qApp->clipboard()->clear();
                m_buffer.push_back(QByteArrayList{data.toByteArray()});
                return;
            }

            // The field isn't empty. Copy the text to the clipboard without quoting (for general plain text clipboard)
            qApp->clipboard()->setText(data.toByteArray());
            return;
        }
    }

    // If we got here, there are multiple selected cells, or copy with headers was requested.
    // In this case, we copy selected data into internal copy-paste buffer and then
    // we write a table both in HTML and text formats to the system clipboard.

    // Copy selected data into internal copy-paste buffer
    int last_row = indices.first().row();
    QByteArrayList lst;
    for(int i=0;i<indices.size();i++)
    {
        if(indices.at(i).row() != last_row)
        {
            m_buffer.push_back(lst);
            lst.clear();
        }
        lst << indices.at(i).data(Qt::EditRole).toByteArray();
        last_row = indices.at(i).row();
    }
    m_buffer.push_back(lst);

    QString result;
    QString htmlResult = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">";
    htmlResult.append("<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">");
    htmlResult.append("<title></title>");

    // The generator-stamp is later used to know whether the data in the system clipboard is still ours.
    // In that case we will give precedence to our internal copy buffer.
    QString now = QDateTime::currentDateTime().toString("YYYY-MM-DDTHH:mm:ss.zzz");
    m_generatorStamp = QString("<meta name=\"generator\" content=\"%1\"><meta name=\"date\" content=\"%2\">").arg(QApplication::applicationName().toHtmlEscaped(), now);
    htmlResult.append(m_generatorStamp);
    // TODO: is this really needed by Excel, since we use <pre> for multi-line cells?
    htmlResult.append("<style type=\"text/css\">br{mso-data-placement:same-cell;}</style></head><body><table>");

    int currentRow = indices.first().row();

    const QString fieldSepHtml = "</td><td>";
    const QString rowSepHtml = "</td></tr><tr><td>";
    const QString fieldSepText = "\t";
    const QString rowSepText = "\r\n";

    // Table headers
    if (withHeaders) {
        htmlResult.append("<tr><th>");
        int firstColumn = indices.front().column();
        for(int i = firstColumn; i <= indices.back().column(); i++) {
            QByteArray headerText = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toByteArray();
            if (i != firstColumn) {
                result.append(fieldSepText);
                htmlResult.append("</th><th>");
            }

            result.append(escapeCopiedData(headerText));
            htmlResult.append(headerText);
        }
        result.append(rowSepText);
        htmlResult.append("</th></tr>");
    }

    // Table data rows
    for(const QModelIndex& index : indices) {
        // Separators. For first cell, only opening table row tags must be added for the HTML and nothing for the text version.
        if (indices.first() == index)
            htmlResult.append("<tr><td>");
        else if (index.row() != currentRow) {
            result.append(rowSepText);
            htmlResult.append(rowSepHtml);
        } else {
            result.append(fieldSepText);
            htmlResult.append(fieldSepHtml);
        }
        currentRow = index.row();

        QImage img;
        QVariant data = index.data(Qt::EditRole);

        // Table cell data: image? Store it as an embedded image in HTML and as base 64 in text version
        if (img.loadFromData(data.toByteArray()))
        {
            QByteArray ba;
            QBuffer buffer(&ba);
            buffer.open(QIODevice::WriteOnly);
            img.save(&buffer, "PNG");
            buffer.close();

            QString imageBase64 = ba.toBase64();
            htmlResult.append("<img src=\"data:image/png;base64,");
            htmlResult.append(imageBase64);
            result.append(QString());
            htmlResult.append("\" alt=\"Image\">");
        } else {
            QByteArray text;
            if (!m->isBinary(index))
                text = data.toByteArray();

            // Table cell data: text
            if (text.contains('\n') || text.contains('\t'))
                htmlResult.append("<pre>" + QString(text).toHtmlEscaped() + "</pre>");
            else
                htmlResult.append(QString(text).toHtmlEscaped());

            result.append(escapeCopiedData(text));
        }
    }

    QMimeData *mimeData = new QMimeData;
    mimeData->setHtml(htmlResult + "</td></tr></table></body></html>");
    mimeData->setText(result);
    qApp->clipboard()->setMimeData(mimeData);
}