Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Interactive elements in delegate
Forum Updated to NodeBB v4.3 + New Features

Interactive elements in delegate

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 2 Posters 692 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ABDUA Offline
    ABDUA Offline
    ABDU
    wrote on last edited by
    #1

    Hi!
    I'm doing little messenger. And what I'm trying to do now is a history of correspondence.
    Initially, I wanted the user to be able to select and copy text in the delegate. Due to the fact that only one item can be interactive at a time, I inherited QTextEdit and made the pressed signal. When pressed, all editors except the current one are closed. Here's how I solved it (This is the minimum code to understand what I'm doing):

    class Model : public QAbstractListModel
    {
    public:
        enum Roles {
            MyText = Qt::UserRole + 1
        };
    
        Model(QObject *parent = nullptr)
            : QAbstractListModel{parent}
        {
            texts_.append("One One One\n"
                          "One One One");
            texts_.append("Two Two Two\n"
                          "Two Two Two");
        }
    
        int rowCount(const QModelIndex &parent) const override
        {
            return texts_.size();
        }
    
        QVariant data(const QModelIndex &index, int role) const override
        {
            if (role == Roles::MyText)
                return texts_[index.row()];
    
            return QVariant{};
        }
    
        Qt::ItemFlags flags(const QModelIndex &index) const override
        {
            return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
        }
    
    private:
        QStringList texts_;
    };
    
    class View : public QListView
    {
    public:
        View(QWidget *parent = nullptr)
            : QListView{parent}
        {
            setMouseTracking(true);
        }
    
    protected:
        void mouseMoveEvent(QMouseEvent *event) override
        {
            QModelIndex currentIndex_ = indexAt(event->pos());
            if (currentIndex_.isValid())
                openPersistentEditor(currentIndex_);
    
            QListView::mouseMoveEvent(event);
        }
    };
    
    class Delegate : public QStyledItemDelegate
    {
    public:
        Delegate(QObject *parent = nullptr)
            : QStyledItemDelegate{parent}
        {
    
        }
    
        void paint(QPainter *painter,
                   const QStyleOptionViewItem &option,
                   const QModelIndex &index) const override
        {
            painter->save();
    
            const auto text = index.data(Model::MyText).toString();
    
            painter->drawText(option.rect, text);
    
            painter->restore();
        }
    
        QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
        {
            return QSize{option.rect.width(), 40};
        }
    
        QWidget *createEditor(QWidget *parent,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override
        {
            QSharedPointer<TextEdit> editor{new TextEdit{parent}, &QObject::deleteLater};
            editor->document()->setDocumentMargin(0);
            editor->setReadOnly(true);
            editor->setFrameShape(QFrame::NoFrame);
    
            editors_.append(editor);
    
            connect(editor.get(), &TextEdit::pressed, this, &Delegate::onPressed);
    
            return editor.get();
        }
    
        void destroyEditor(QWidget *editor, const QModelIndex &index) const override
        {
            editors_.removeOne(editor);
        }
    
        void setEditorData(QWidget *editor, const QModelIndex &index) const override
        {
            const auto text = index.data(Model::MyText).toString();
            TextEdit *textEdit = static_cast<TextEdit *>(editor);
            textEdit->setText(text);
        }
    
    private slots:
        void onPressed()
        {
            TextEdit *editor = qobject_cast<TextEdit *>(sender());
    
            auto it = std::remove_if(editors_.begin(), editors_.end(), [&](QSharedPointer<TextEdit> e) {
                return e != editor;
            });
    
            editors_.erase(it, editors_.end());
        }
    
    private:
        mutable QVector<QSharedPointer<TextEdit>> editors_;
    };
    
    class TextEdit : public QTextEdit
    {
        Q_OBJECT
    public:
        explicit TextEdit(QWidget *parent = nullptr)
            : QTextEdit{parent}
        {
    
        }
    
    signals:
        void pressed();
    
    protected:
        void mousePressEvent(QMouseEvent *event) override
        {
            QTextEdit::mousePressEvent(event);
            emit pressed();
        }
    
    };
    

    I do not know if this is the right approach, but over time, I wanted to make a "like" button. And perhaps more interactive elements will be added in the future. Now it turns out one item will have to have two editors (a button and a text editor. Which seems impossible) or one complex element (the button and the text editor are layouted in the one widget).

    At the moment, I am changing the position of the editor using QAbstractItemDelegate::updateEditorGeometry. The problem is that it is difficult to keep both the editor and the delegate looking the same. And it made me doubt my decision. Is there another method to solve this problem?

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Why are you storing multiple editors in your delegate ?
      The idea of the delegate is that it handles one cell at a time. Once you finished editing a cell you go to the next and the editor is closed at that time.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      ABDUA 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        Why are you storing multiple editors in your delegate ?
        The idea of the delegate is that it handles one cell at a time. Once you finished editing a cell you go to the next and the editor is closed at that time.

        ABDUA Offline
        ABDUA Offline
        ABDU
        wrote on last edited by ABDU
        #3

        @SGaist I would like to do that. But is it possible to implement this in my situation? As you can see, static text is drawn in the delegate. And there is no way to interact with it. I would like the cursor to change when hovering and copy the text immediately, without double-clicking. Why I used several editors, because when using only one editor, when hovering the mouse over another element, my selection from the previous one disappeared. I would like the selection to remain until the user begins to select the text of another element. And in order to reduce the number of editors, I made the logic so that when clicked, all editors except the current one are closed.

        Apart from using several editors, I couldn't think of anything else. All that is currently with the text I want to implement is this: "I would like the cursor to change when hovering and copy the text immediately, without double-clicking. I would like the selection to remain until the user begins to select the text of another element. " If there are better ways, I would be happy to consider your options. Thanks!

        SGaistS 1 Reply Last reply
        0
        • ABDUA ABDU

          @SGaist I would like to do that. But is it possible to implement this in my situation? As you can see, static text is drawn in the delegate. And there is no way to interact with it. I would like the cursor to change when hovering and copy the text immediately, without double-clicking. Why I used several editors, because when using only one editor, when hovering the mouse over another element, my selection from the previous one disappeared. I would like the selection to remain until the user begins to select the text of another element. And in order to reduce the number of editors, I made the logic so that when clicked, all editors except the current one are closed.

          Apart from using several editors, I couldn't think of anything else. All that is currently with the text I want to implement is this: "I would like the cursor to change when hovering and copy the text immediately, without double-clicking. I would like the selection to remain until the user begins to select the text of another element. " If there are better ways, I would be happy to consider your options. Thanks!

          SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          This stack overflow thread might be a good starting point. Although it concentrate on link activation, the solutions suggested there might applicable to your current issue.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          ABDUA 2 Replies Last reply
          1
          • SGaistS SGaist

            This stack overflow thread might be a good starting point. Although it concentrate on link activation, the solutions suggested there might applicable to your current issue.

            ABDUA Offline
            ABDUA Offline
            ABDU
            wrote on last edited by
            #5

            @SGaist Thanks!
            I'll try and give an answer later.

            1 Reply Last reply
            0
            • SGaistS SGaist

              This stack overflow thread might be a good starting point. Although it concentrate on link activation, the solutions suggested there might applicable to your current issue.

              ABDUA Offline
              ABDUA Offline
              ABDU
              wrote on last edited by
              #6

              @SGaist Thanks! I've done the behavior I need without using editors.

              I'll leave the code here. In the future, it may be useful to someone:

              class Document : public QTextDocument
              {
                  Q_OBJECT
              public:
                  explicit Document(QObject *parent = nullptr)
                      : QTextDocument{parent}
                      , textCursor_{this}
                  {
                      setDocumentMargin(0);
                  }
              
                  int selectionStartPos() const
                  {
                      return textCursor_.selectionStart();
                  }
              
                  int selectionEndPos()   const
                  {
                      return textCursor_.selectionEnd();
                  }
              
                  void setCursorPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor)
                  {
                      clearSelection();
                      textCursor_.setPosition(pos, mode);
                      highlightSelection();
                  }
              
                  bool moveCursorPosition(QTextCursor::MoveOperation op,
                                          QTextCursor::MoveMode mm= QTextCursor::MoveAnchor,
                                          int n = 1)
                  {
                      clearSelection();
                      const bool result = textCursor_.movePosition(op, mm, n);
                      highlightSelection();
                      return result;
                  }
              
                  void select(QTextCursor::SelectionType selection)
                  {
                      clearSelection();
                      textCursor_.select(selection);
                      selectedWordOnDoubleClick_ = textCursor_;
                      highlightSelection();
                  }
              
                  void extendWordwiseSelection(int newPos)
                  {
                      QTextCursor selected   = textCursor_;
                      QTextCursor tempCursor = textCursor_;
                      tempCursor.setPosition(newPos, QTextCursor::KeepAnchor);
              
                      if (!tempCursor.movePosition(QTextCursor::StartOfWord))
                          return;
              
                      const int wordStartPos = tempCursor.position();
              
                      if (!tempCursor.movePosition(QTextCursor::EndOfWord))
                          return;
              
                      const int wordEndPos = tempCursor.position();
              
                      if (newPos < selectedWordOnDoubleClick_.position()) {
                          setCursorPosition(selected.selectionEnd());
                          setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
                      } else {
                          setCursorPosition(selected.selectionStart());
                          setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
                      }
                  }
              
                  QString selectedText() const
                  {
                      return textCursor_.selectedText();
                  }
              
              
              private:
                  void clearSelection()
                  {
                      QTextCharFormat defaultFormat;
                      textCursor_.setCharFormat(defaultFormat);
                  }
              
                  void highlightSelection()
                  {
                      QTextCharFormat selectFormat = textCursor_.charFormat();
                      selectFormat.setBackground(QApplication::palette().highlight().color());
                      selectFormat.setForeground(QApplication::palette().highlightedText().color());
              
                      textCursor_.setCharFormat(selectFormat);
                  }
              
              private:
                  QTextCursor textCursor_;
                  QTextCursor selectedWordOnDoubleClick_;
              };
              
              class Delegate : public QStyledItemDelegate
              {
              public:
                  Delegate(QObject *parent = nullptr)
                      : QStyledItemDelegate{parent}
                  {
              
                  }
              
                  void paint(QPainter *painter,
                             const QStyleOptionViewItem &option,
                             const QModelIndex &index) const override
                  {
                      painter->save();
              
                      painter->fillRect(option.rect, Qt::red);
              
                      if (index == interactiveIndex_) {
                          painter->translate(option.rect.topLeft());
                          doc_->documentLayout()->draw(painter, QAbstractTextDocumentLayout::PaintContext());
                      } else {
                          const auto text = index.data(Model::MyText).toString();
                          painter->drawText(option.rect, text);
                      }
              
                      painter->restore();
                  }
              
                  QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
                  {
                      return QSize{option.rect.width(), 40};
                  }
              
                  QModelIndex interactiveIndex() const
                  {
                      return interactiveIndex_;
                  }
              
                  void setInteractiveIndex(const QModelIndex &newInteractiveIndex)
                  {
                      interactiveIndex_ = newInteractiveIndex;
                  }
              
                  QSharedPointer<Document> doc() const
                  {
                      return doc_;
                  }
              
                  void setDoc(QSharedPointer<Document> newDoc)
                  {
                      doc_ = newDoc;
                  }
              
              private:
                  QModelIndex interactiveIndex_;
                  QSharedPointer<Document> doc_;
              };
              
              class Model : public QAbstractListModel
              {
              public:
                  enum Roles {
                      MyText = Qt::UserRole + 1
                  };
              
                  Model(QObject *parent = nullptr)
                      : QAbstractListModel{parent}
                  {
                      texts_.append("One One One\n"
                                    "One One One");
                      texts_.append("Two Two Two\n"
                                    "Two Two Two");
                      texts_.append("Three Three Three\n"
                                    "Three Three Three");
                      texts_.append("Four Four Four\n"
                                    "Four Four Four");
                  }
              
                  int rowCount(const QModelIndex &parent) const override
                  {
                      return texts_.size();
                  }
              
              
                  QVariant data(const QModelIndex &index, int role) const override
                  {
                      if (role == Roles::MyText)
                          return texts_[index.row()];
              
                      return QVariant{};
                  }
              
                  Qt::ItemFlags flags(const QModelIndex &index) const override
                  {
                      return QAbstractItemModel::flags(index);
                  }
              
              private:
                  QStringList texts_;
              };
              
              class View : public QListView
              {
              public:
                  View(QWidget *parent = nullptr)
                      : QListView{parent}
                      , model_{new Model{this}}
                      , delegate_{new Delegate{this}}
                  {
                      setModel(model_);
                      setItemDelegate(delegate_);
              
                      setMouseTracking(true);
                  }
              
              protected:
                  void mousePressEvent(QMouseEvent *event) override
                  {
                      pressedPos_ = event->pos();
              
                      previousPressedIndex_ = pressedIndex_;
                      pressedIndex_         = indexAt(pressedPos_);
              
                      if (pressedIndex_ != previousPressedIndex_) {
                          // Repaint previous pressed as static text. Means no selection draws
                          update(previousPressedIndex_);
                      }
              
                      const QString text   = pressedIndex_.data(Model::MyText).toString();
              
                      QSharedPointer<Document> doc{new Document};
                      doc->setPlainText(text);
              
                      const int startPos = getRelativeCursorPos(doc, pressedIndex_, pressedPos_);
                      doc->setCursorPosition(startPos);
              
                      delegate_->setInteractiveIndex(pressedIndex_);
                      delegate_->setDoc(doc);
              
                      wordSelection_ = false;
              
                      QListView::mousePressEvent(event);
                  }
              
                  void mouseMoveEvent(QMouseEvent *event) override
                  {
                      const QPoint currentMousePos = event->pos();
              
                      if (indexAt(currentMousePos).isValid()) {
                          if ((!QApplication::overrideCursor() || QApplication::overrideCursor()->shape() != Qt::IBeamCursor)) {
                              QApplication::setOverrideCursor(Qt::IBeamCursor);
                          }
                      } else {
                          QApplication::restoreOverrideCursor();
                      }
              
                      if (event->buttons() & Qt::LeftButton) {
                          const QPoint currentMousePos = event->pos();
              
                          QSharedPointer<Document> doc = delegate_->doc();
              
                          const int endPos = getRelativeCursorPos(doc, pressedIndex_, currentMousePos);
              
                          if (wordSelection_) {
                              doc->extendWordwiseSelection(endPos);
                          } else {
                              doc->setCursorPosition(endPos, QTextCursor::KeepAnchor);
                          }
              
                          update(pressedIndex_);
                      }
              
                      QListView::mouseMoveEvent(event);
                  }
              
                  void mouseDoubleClickEvent(QMouseEvent *event) override
                  {
                      QSharedPointer<Document> doc = delegate_->doc();
                      doc->select(QTextCursor::WordUnderCursor);
              
                      update(pressedIndex_);
              
                      wordSelection_ = true;
              
                      QListView::mouseDoubleClickEvent(event);
                  }
              
                  void mouseReleaseEvent(QMouseEvent *event) override
                  {
                      QListView::mouseReleaseEvent(event);
                      QSharedPointer<Document> doc = delegate_->doc();
              
                      QApplication::clipboard()->setText(doc->selectedText());
                  }
              
              private:
                  int getRelativeCursorPos(const QSharedPointer<Document> &doc,
                                           const QModelIndex &index,
                                           const QPoint &mousePos) const
                  {
                      const QRect itemRect = visualRect(index);
                      const QPoint relativePos = mousePos - itemRect.topLeft();
                      return doc->documentLayout()->hitTest(relativePos, Qt::FuzzyHit);
                  }
              
              private:
                  Model    *model_    = nullptr;
                  Delegate *delegate_ = nullptr;
              
                  QModelIndex previousPressedIndex_;
                  QModelIndex pressedIndex_;
                  QPoint pressedPos_;
              
                  bool wordSelection_ = false;
              };
              

              Perhaps the code is too big to ask if it can be improved somewhere. I did everything I could. But still I will leave the post unresolved for a short time. Maybe there will be people who will advise me to improve my code. I am open and grateful to anyone who can help improve the implementation in any way.

              1 Reply Last reply
              0
              • ABDUA ABDU has marked this topic as solved on

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved