0

I'm working here on a project and currently I'm stuck on the following problem. It is about a QTableView which has a column called "Description", the cells of this column contain a QPlainTextEditDelegate. I'm failing on setting the Height of the QPlainTextEdit everytime it is entered. Right now it behaves like a QLineEdit until I drag the row ( in which I'm active at that time ) of the QTableView larger.

What I want to do is to change the Height of the QPlainTextEdit once I entered it. What are your suggestions? How can I proceed to get this thing done?

Thank you all in advance!

BTW Sorry for my poor english :/

edit:

Ok I solved it, but without sizeHint, I used updateEditorGeometry :

void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const;

And inside this Method, you can set the width or height like you want

editor->setGeometry(option.rect.x(),option.rect.y(),<your_width>,<your_height>);

But thank you anyway!

Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148

2 Answers2

1

You should reimplement QAbstractItemDelegate::sizeHint method to return expected height when you create your editor. I don't think that it's necesary to emit QAbstractItemDelegate::sizeHintChanged signal after creating editor, but documentation doesn't say anything. If it doesn't work without it, you should emit sizeHintChanged after returning created editor widget to notify view of need to change row height.

j_kubik
  • 6,062
  • 1
  • 23
  • 42
  • Thanks for your answer I implemented the sizeHint method in my Delegate subclass but it gets never called. Meanwhile I was searching for the reason and found a message in the Qt-interest Archive. The person claims that it worked after he added : `tableView.verticalHeader( )->resizeSections( QHeaderView::Custom ); tableView.horizontalHeader( )->resizeSections( QHeaderView::Custom );` But that didn't solve the problem. Then I found another suggestion which says to disable stretchLastSection, but that didn't work, either. You mentioned to emit sizeHintChanged(),where should I place it exactly? –  Sep 20 '11 at 07:57
  • Size hint is called independent of editor existence - if your editor is bigger than for DisplayRole, you should emit sizeHintChanged afer creating your editior for given item (so in createEditor and setEditorData if size is data-dependent), so qt knows that it should now reserve more space. It is strange that your sizeHint doesn't get called - make sure that you are having exatly the same method declaration - sometimes missing const or something is causing method to be treated as different one and (obiously) not treated as virutal-override - therefore it doesn't get called. – j_kubik Sep 30 '11 at 16:36
0

I was facing the exact same issue, though with a custom delegate inside a QTableView which has a QPlainTextEdit as editor. Initailly, the editor would not grow beyond the size of its table cell.

What did not work for me:

  • updateEditorGeometry(): was only called on initial show()
  • custom sizeHint(): wasn't called ever, neither in subclass or baseclass, regardless any update(), updateGeometry() calls or signals

After seemingly endless hours of collecting small chunks of info about some undocumented QPlainTextEdit/QTextDocument 'features' and Qt quirks I got what I want by connecting to QPlainTextEdit->document()->documentLayout()'s documentSizeChanged(QSizeF) signal.

Below are the essentials to expand the editor as desired while limiting it to the parent QTableView's viewport (m_pTableView).

Hope this makes sense to anyone. Example code is not compilable as is but should give enough hints to get started.

h editor + delegate

#include <QPlainTextEdit>

// A custom QStyledItemDelegate
#include "library/tableitemdelegate.h"

/// A QPlainTextEdit to show all content lines in a scrollable view.
/// * finish editing with Return (like QLineEdit used for other metadata)
/// * add new line with Shift+Return.
/// Horizontal scrollbar is hidden as long as content is just one line.
/// Note: QTextEdit is no option here since it seems to transform content with
/// line breaks to html doc when committing data.
class MultiLineEditor : public QPlainTextEdit {
    Q_OBJECT
  public:
    MultiLineEditor(QWidget* pParent);

    bool eventFilter(QObject* obj, QEvent* event) override;

  signals:
    void editingFinished();
};

class MultiLineEditDelegate : public TableItemDelegate {
    Q_OBJECT
  public:
    explicit MultiLineEditDelegate(QTableView* pTrackTable);
    ~MultiLineEditDelegate() override = default;

    // called when the user starts editing an item
    QWidget* createEditor(QWidget* parent,
            const QStyleOptionViewItem& option,
            const QModelIndex& index) const override;

  private slots:
    void commitAndCloseEditor();

  private:
    void adjustEditor(MultiLineEditor* pEditor, const QSizeF size) const;
    mutable QRect m_editRect;
    mutable int m_lineCount;
};

cpp editor + delegate

#include "library/multilineeditdelegate.h"

#include <QAbstractTextDocumentLayout>
#include <cmath>

#include "moc_multilineeditdelegate.cpp"

MultiLineEditor::MultiLineEditor(QWidget* pParent)
        : QPlainTextEdit(pParent) {
    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    // Disable line wrap for a predictable view (like QLineEdit).
    // Horizontal scrollbars show up automatically.
    setLineWrapMode(QPlainTextEdit::NoWrap);
    // Remove ugly content offset, most notable with one-liners
    setContentsMargins(0, 0, 0, 0);
    document()->setDocumentMargin(0);
    // Add event filter to catch right-clicks and key presses
    installEventFilter(this);
};

bool MultiLineEditor::eventFilter(QObject* obj, QEvent* event) {
    if (event->type() == QEvent::MouseButtonPress) {
        // Work around a strange quirk: right-clicks outside the rectangle of
        // the underlying table index are not triggering the context menu.
        // Simply returning true fixes it.
        QMouseEvent* me = static_cast<QMouseEvent*>(event);
        if (me->button() == Qt::RightButton &&
                 rect().contains(me->pos(), false)) {
            return true;
        }
    } else if (event->type() == QEvent::KeyPress) {
        // Finish editing with Return key like in QLineEdit
        QKeyEvent* ke = static_cast<QKeyEvent*>(event);
        if ((ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) &&
                ke->modifiers().testFlag(Qt::NoModifier)) {
            emit editingFinished();
            return false;
        }
    }
    return QPlainTextEdit::eventFilter(obj, event);
}

MultiLineEditDelegate::MultiLineEditDelegate(QTableView* pTableView)
        : TableItemDelegate(pTableView),
          m_lineCount(0) {
}

QWidget* MultiLineEditDelegate::createEditor(QWidget* pParent,
        const QStyleOptionViewItem& option,
        const QModelIndex& index) const {
    Q_UNUSED(index);
    auto* pEditor = new MultiLineEditor(pParent);
    auto* pDocLayout = pEditor->document()->documentLayout();
    // Adjust height to fit content and maybe shift vertically to fit into the
    // library view. documentSizeChanged() is emitted when text changed, incl.
    // initial fill.
    // We could also connect to QPlainTextEdit::blockCountChanged(), though
    // for some reason, that only works for documents with 2+ lines
    // appending line breaks to single-line docs won't fire that signal :|
    connect(pDocLayout,
            &QAbstractTextDocumentLayout::documentSizeChanged,
            this,
            [this, pEditor](const QSizeF size) {
                adjustEditor(pEditor, size);
            });
    // emitted when pressing Return key to act like QLineEdit
    connect(pEditor,
            &MultiLineEditor::editingFinished,
            this,
            &MultiLineEditDelegate::commitAndCloseEditor);
    // Store the initial rectangle so we can read the x/y origin and in adjustEditor()
    m_editRect = option.rect;
    return pEditor;
}

void MultiLineEditDelegate::adjustEditor(MultiLineEditor* pEditor, const QSizeF size) const {
    // Compared to QTextEdit, size.height() is the line count (Qt speak: blocks)
    int newLineCount = static_cast<int>(round(size.height()));
    // Only act if line count changed
    if (newLineCount == m_lineCount) {
        return;
    } else {
        m_lineCount = newLineCount;
    }

    // Remove the scrollbars if content is just one line to emulate QLineEdit
    // appearace, else enable auto mode.
    Qt::ScrollBarPolicy pol(m_lineCount > 1 ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
    pEditor->setVerticalScrollBarPolicy(pol);
    pEditor->setHorizontalScrollBarPolicy(pol);

    // Calculate the content height
    int lines = m_lineCount;
    // Add extra margin so the horizontal scrollbar doesn't obstruct the last
    // line (which also avoids the vertical scrollbar as long as possible)
    lines += lines > 1 ? 1 : 0;

    QFontMetrics fm(pEditor->document()->defaultFont());
    int newH = fm.lineSpacing() * lines;

    // Limit editor to visible table height
    int tableH = m_pTableView->viewport()->rect().height();
    newH = std::min(newH, tableH);
    // If the editor overflows the table view, move it up so it's not clipped.
    // No need to care about y < 0 or y > (table height - line height) since the
    // table already ensures visibility when the index is selected.
    int newY = m_editRect.y();
    if ((newY + newH) > tableH) {
        newY = tableH - newH;
    }
    // Also limit width so scrollbars are visible and table is not scrolled if
    // cursor is moved horizontally.
    int newW = std::min(pEditor->width(), m_pTableView->viewport()->rect().width());

    pEditor->setGeometry(QRect(m_editRect.x(), newY, newW, newH));
}

void MultiLineEditDelegate::commitAndCloseEditor() {
    MultiLineEditor* pEditor = qobject_cast<MultiLineEditor*>(sender());
    emit commitData(pEditor);
    emit closeEditor(pEditor);
}
ronso0
  • 1
  • 1