How to make a Qt interactive text editing widget
Asked Answered
C

3

5

I want to develop an application with two main widgets one is a text editor and the other one is for graphic viewer.

enter image description here

The basic idea is to let the user hover over any block of code in the text area and the associated part of the drawing gets selected or highlighted.

For the graphic widget, after some research it seems QGraphicsScene fits the requirements the most, but I'm not sure what widget to use for the text editor such that it gives me a signal when hovering over any block of code (and also send a string parameter "id" of the block).

The reverse action is required too, so when the user choose to inspect element in the graphics view the text view is scrolled to view the associate block of code and highlight it (like in Google Chrome "Inspect Element" feature).

Cornelia answered 26/9, 2013 at 12:6 Comment(11)
did you try with redefining mouseMoveEvent in derived class of the widgets in which you want detect hover?Wolfgang
I would not personally use QGraphicsScene for this, but rather qml or at least opengl based solution with QtGui for the graphics viewer. You could get the position of the cursor somehow in the text widget, but it is kinda tricky unfortunately. However, this would indeed look a fancy feature IMO. :-)Gingras
@Wolfgang the problem as i understand is that op has not decided yet, from which class to deriveToxicant
@Mahmoud are you able to detect an entity in QGraphicsScene?(as I didn't ever worked with it). if yes then logic to find block in text editor is easy.Wolfgang
@Wolfgang the mouse position doesn't reflect which block of text is hovered, specially if there are scrollers, right?Cornelia
@LaszloPapp I think OpenGL will have all the featured but it add unwanted complexity, I think QGraphicsScene has all I need (so far :D).Cornelia
@Toxicant exactly, thanks. And I think I need more advanced widget because mouse position can't be mapped easily to which text block it is hovering.Cornelia
@MahmoudHassan: you are aware of that QGV is more or less unmaintained and might be obsoleted any soon?Gingras
@Wolfgang hmm, it seems this is another problem as I didn't found a way to get the index of the selected item, (I'm drawing all items programatically), any idea?Cornelia
@MahmoudHassan my logic is if you can find circle object in Graphic named circle1 then u just have to make a logic to search word circle1 in text edit and the braces pair that just next to word circle1 can be highlightedWolfgang
@MahmoudHassan though I didn't work with graphics but as this https://mcmap.net/q/2035221/-using-hover-events is saying u can make handle of hover event for graphic items in handle detect name of item just attach name in tooltip (or property i m not sure abt it) and find in text edit as above logicWolfgang
W
2

To highlight text when an object is hovered in scene you have to reimplement QGraphicsScene and QGraphicsItem (which one you going to use) to inform mainwindow to find and highlight text. Here is sample code to highlight text when an object hovered in scene I used QGraphicsPixmapItem:


Graphic scene

 class GraphicScene : public QGraphicsScene
{
    Q_OBJECT
public:
    GraphicScene();

    void EmitItemHoverd(QString name)
    {
      emit SignalItemHovered(name);
    }


signals:
    void SignalItemHovered(QString);
};

GraphicsItem :

#include "GraphicScene.h"

class GraphicItem : public QGraphicsPixmapItem
{
    QString itemName;
    GraphicScene * scene;
public:
    GraphicItem(GraphicScene *s, QString name);//name you can set from editor

    void hoverEnterEvent(QGraphicsSceneHoverEvent *event);

};
GraphicItem::GraphicItem(GraphicScene *s, QString name)
{
    scene = s;
    itemName = name;
    this->setAcceptHoverEvents(true);
}

void GraphicItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    scene->EmitItemHoverd(itemName);
}

in MainWindow constructor connect

connect(scene,SIGNAL(SignalItemHovered(QString)),this,SLOT(OnItemHovered(QString)));

Here is slot:

void MainWindow::OnItemHovered(QString name)
{
   ui->textEdit->find(name);
  QTextCursor tc = ui->textEdit->textCursor();
  tc.select(QTextCursor::WordUnderCursor);    
  ui->textEdit->find("}");
  QTextCursor tc1 = ui->textEdit->textCursor();
  tc1.select(QTextCursor::WordUnderCursor);
  int pos2 = tc1.selectionStart();

  tc.setPosition(pos2,QTextCursor::KeepAnchor);
  ui->textEdit->setTextCursor(tc);
}

and logic to draw:

    GraphicItem * item = new GraphicItem(scene,"Circle");

    QPixmap  map(50,50);
    QPainter * painter= new QPainter(&map);
    painter->setBrush(QBrush(Qt::red,Qt::SolidPattern));
    painter->drawEllipse(20,20,15,15);
    item->setPixmap(map);

    scene->addItem(item);
    ui->graphicsView->update();
    delete painter;

NOTE: Using public EmitItemHoverd may be an issue here I used just for sake of explaining the logic you can make it protected with required changes.

Yeah I knew its half of answer but opposite logic can be implimented based on above

Wolfgang answered 28/9, 2013 at 9:22 Comment(4)
Thanks Tab for this answer :) The idea of overriding the hover signal of QGraphicsScene is pretty good, I will absolutely consider this.Cornelia
Btw, "textEdit" doesn't support a lot of fancy text editing features (like: line numbering, line coloring "odd lines have different background color than even lines", automatic color keywords, ...) I'm still looking for workarounds to these issues. PS: your "OnItemHovered" function will work better if you add this line at the beginning: ui->textEdit->moveCursor(QTextCursor::Start); because now it can't find the block in code if the block lies before the current cursor position.Cornelia
text edit supports html script so you can manage style sheet by that. For the QTextCursor I doesn't tested that code so much I was just trying to see if my idea is implementable or not well thanks for suggestion.Wolfgang
Thanks Tab, I have manipulated you code a lot but the basic idea was the key, and for the record I used this topic qt-project.org/doc/qt-4.8/widgets-codeeditor.html for the code area and it is working great so far :)Cornelia
J
4

You can use QTextEdit::cursorForPosition and QTextCursor::position to convert mouse coordinates to position in the text. You can determine the hovered code block using this position.

You can select arbitrary code block in the text edit as described in this answer.

QGraphicsScene seems to be a good choice, as it contains all the functionality you may need.

Joappa answered 26/9, 2013 at 13:0 Comment(0)
W
2

To highlight text when an object is hovered in scene you have to reimplement QGraphicsScene and QGraphicsItem (which one you going to use) to inform mainwindow to find and highlight text. Here is sample code to highlight text when an object hovered in scene I used QGraphicsPixmapItem:


Graphic scene

 class GraphicScene : public QGraphicsScene
{
    Q_OBJECT
public:
    GraphicScene();

    void EmitItemHoverd(QString name)
    {
      emit SignalItemHovered(name);
    }


signals:
    void SignalItemHovered(QString);
};

GraphicsItem :

#include "GraphicScene.h"

class GraphicItem : public QGraphicsPixmapItem
{
    QString itemName;
    GraphicScene * scene;
public:
    GraphicItem(GraphicScene *s, QString name);//name you can set from editor

    void hoverEnterEvent(QGraphicsSceneHoverEvent *event);

};
GraphicItem::GraphicItem(GraphicScene *s, QString name)
{
    scene = s;
    itemName = name;
    this->setAcceptHoverEvents(true);
}

void GraphicItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    scene->EmitItemHoverd(itemName);
}

in MainWindow constructor connect

connect(scene,SIGNAL(SignalItemHovered(QString)),this,SLOT(OnItemHovered(QString)));

Here is slot:

void MainWindow::OnItemHovered(QString name)
{
   ui->textEdit->find(name);
  QTextCursor tc = ui->textEdit->textCursor();
  tc.select(QTextCursor::WordUnderCursor);    
  ui->textEdit->find("}");
  QTextCursor tc1 = ui->textEdit->textCursor();
  tc1.select(QTextCursor::WordUnderCursor);
  int pos2 = tc1.selectionStart();

  tc.setPosition(pos2,QTextCursor::KeepAnchor);
  ui->textEdit->setTextCursor(tc);
}

and logic to draw:

    GraphicItem * item = new GraphicItem(scene,"Circle");

    QPixmap  map(50,50);
    QPainter * painter= new QPainter(&map);
    painter->setBrush(QBrush(Qt::red,Qt::SolidPattern));
    painter->drawEllipse(20,20,15,15);
    item->setPixmap(map);

    scene->addItem(item);
    ui->graphicsView->update();
    delete painter;

NOTE: Using public EmitItemHoverd may be an issue here I used just for sake of explaining the logic you can make it protected with required changes.

Yeah I knew its half of answer but opposite logic can be implimented based on above

Wolfgang answered 28/9, 2013 at 9:22 Comment(4)
Thanks Tab for this answer :) The idea of overriding the hover signal of QGraphicsScene is pretty good, I will absolutely consider this.Cornelia
Btw, "textEdit" doesn't support a lot of fancy text editing features (like: line numbering, line coloring "odd lines have different background color than even lines", automatic color keywords, ...) I'm still looking for workarounds to these issues. PS: your "OnItemHovered" function will work better if you add this line at the beginning: ui->textEdit->moveCursor(QTextCursor::Start); because now it can't find the block in code if the block lies before the current cursor position.Cornelia
text edit supports html script so you can manage style sheet by that. For the QTextCursor I doesn't tested that code so much I was just trying to see if my idea is implementable or not well thanks for suggestion.Wolfgang
Thanks Tab, I have manipulated you code a lot but the basic idea was the key, and for the record I used this topic qt-project.org/doc/qt-4.8/widgets-codeeditor.html for the code area and it is working great so far :)Cornelia
D
1

I would attempt this entirely in QML. I would make the text editor a list of text areas (possibly creating a new QML item for desired formatting). Each item in the list is effectively a newly created QML item itself (suitable to be dumped into a .qml file and referenced in a higher item). At this point, you can inject each of those items into the QML view on the left, only adding mouse handlers to fire when hovering or clicking in the area.

So, the left side is a QML scene that is managed and updated by a controller who takes raw text from an item list on the right and tries to parse it out as a new QML type. It should work, according to the theory in my head, and it would reduce a significant amount of complexity (especially in mapping a mouse point directly into a body of text).

Delp answered 26/9, 2013 at 15:12 Comment(2)
The idea of multiple text areas seems to be very good on mapping the mouse hover, I'm afraid it may not be the best solution because this is a text editor so the user can edit later, meaning imagine the scenario of the above screenshot, and the user later decided to add a new shape in between the two blocks, how can the program detect that and create a new text area? but I like this solution and will try to find a work around about this issue, thanks :)Cornelia
Could you condense the list to a single text file on save and expand it on load according to the top level scopes? That would be pretty cool.Delp

© 2022 - 2024 — McMap. All rights reserved.