[SOLVED]How to make a QLabel follow mouse movements properly.



  • I have created a number of components as part of a double image viewer idea I had.
    I initialise those components on my main class

    m_workspaced1 = new DetailViewport;     
    
    m_interactivepointer1 = new InteractivePointer;
    m_interactivepointer2 = new InteractivePointer;
    
    m_workspace1 = new Workspace;     
    m_workspace2 = new Workspace;     
    

    and I add them to my mainLayout

    m_mainLayout->addWidget(m_workspace1);
    m_mainLayout->addWidget(m_workspace2);
    ...
    

    So they get displayed as I expect them to.

    The whole problem is with my interactive pointers. The idea behind them is that instead of having a mouse pointer while hovering above an image, you will see a small label displaying a zoomed version (or some other data) of that image. This means that InteractivePointer is a subclass of QLabel, that contains an update function and initialises its display

    m_imaged = new QImage(8, 8, QImage::Format_ARGB32);
    m_pixmap = QPixmap::fromImage(*m_imaged);
    

    The update function has also a command like this

    this->move(posX, posY);
    

    for moving the label in a new position.

    The thing is that if I do not add my interactive pointers to the main layout they appear on their own separate window, and if I do add them then they do not appear at all.

    Question:
    So basically what I want to know is a good way to make a label appear on my app and follow the mouse movement.



  • UPDATE

    I managed to get the labels in my scene by using Proxy Widgets.

    So in a few words what I did was have two QGraphicsProxyWidget items, namely m_labelProxy1, and m_labelProxy2 that I then used in order to display my interactive pointers (aka QLabels) on both workspaces.

    m_labelProxy1 = m_scene1->addWidget(m_interactivepointer1);
    m_labelProxy2 = m_scene2->addWidget(m_interactivepointer2);
    

    This caused a few problems though. Before I do this, when running, the app would display both images centred and fully visible.

    But now it just displays them transposed, with only parts of them visible. Moreover if I move the mouse (and the QLabel that moves with it) towards the edges of the GraphicsView the Image starts moving downwards as if I am scrolling. Hopefully this images gives a rough idea.

    I do want the interactive pointers visible, but I also don't want them to affect my scene. Is there something I can do to achieve that.



  • UPDATE 2:

    The above issue was solved by initialising the interactive pointers somewhere within the image, that is already displayed at this point. I then do not allow them to move anywhere outside the image area.

    Yet another problem came to my attention. If I try to drag around the image whilst the interactive pointer is visible, it will leave various marks behind

    Those marks will eventually disappear if I minimise the app, or scroll that area somewhere outside the visible viewport. It still looks bad though, and calling repaint() on my QGraphicsView will not help.

    Only calling update() to the scene held by that QGraphicsView solves the issue. And I know this thread has started spanning too far from its title, but I would be interested to know why that happens.


  • Lifetime Qt Champion

    Hi,

    Just to be sure I understand you correctly: is that label moving along with the mouse ?



  • Yes.
    If you take a close look at the image, you will notice a greyish rectangle. Just imagine that instead of a pointer you have that, and that it is able to display various data as you move around your mouse (e.g. a zoomed version of the pixels below it).


  • Lifetime Qt Champion

    Shouldn't you rather have a secondary QGraphicsView that would show the same scene but zoomed in ?

    On a side note: m_imaged = new QImage(8, 8, QImage::Format_ARGB32);
    In most of the cases, there's no need for a QImage allocated on the heap



  • I am not sure I understand you correctly. Because then my second QGraphicsView should be on top of the first (the one displaying the image) and transparent.

    Also on your side note, I don't think I can possibly do anything else. The InteractivePointer class is rather simple, but if I change the m_image initialisation on the constructor then the pointer will display nothing but the background.

    #include "interactivepointer.h"
    
    InteractivePointer::InteractivePointer()
    {
        this->setScaledContents(true);
        this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
        this->setFixedWidth(pointerSizeX());
        this->setFixedHeight(pointerSizeY());
        //this->setTextFormat(Qt::RichText);
        this->setMargin( 0 );
        this->setContentsMargins(0, 0, 0, 0 );
        this->setAlignment(Qt::AlignTop);
    
        // Initialise an image.
        m_imagep = new QImage(8, 8, QImage::Format_ARGB32);
        m_pixmap = QPixmap::fromImage(*m_imagep);
    
        // Set pixelmap to this label.
        this->setPixmap(m_pixmap);
    
        // Set font size.
        m_labelFont.setPointSize(fontSize());
    
        // Font metrics in order to adjust pointer size
        QFontMetrics fm(m_labelFont);
        m_pixelsWideT = fm.width("000");
        m_pixelsWideS = fm.width(" ");
        m_pixelsHighT = fm.height();
        m_pixelsHighS = fm.lineSpacing();
    }
    
    void InteractivePointer::update(int posX, int posY, QVector<int> val1, QVector<int> val2,
                                    int winSize, int mode)
    {
        // Also update current position.
        updatePosition(posX, posY);
    
        if (mode == 0)                      // Zoom view
        {
            setPointerSizeX(40);
            setPointerSizeY(40);
            this->setFixedWidth(pointerSizeX());
            this->setFixedHeight(pointerSizeY());
    
            zoomView(val1, winSize);
        }
        else if (mode == 1)                 // Difference view
        {
            setPointerSizeX(40);
            setPointerSizeY(40);
            this->setFixedWidth(pointerSizeX());
            this->setFixedHeight(pointerSizeY());
    
            diffView(val1, val2, winSize);
        }
        else                                // Text View
        {        
            setPointerSizeX(winSize * (m_pixelsWideT + m_pixelsWideS));     // Adjust pointerX size;
            setPointerSizeY(winSize * (m_pixelsHighS));                     // Adjust pointerY size;
            this->setFixedWidth(pointerSizeX());
            this->setFixedHeight(pointerSizeY());
    
            this->setLineWidth(pointerSizeY());
            textView(val1, winSize);
        }
    }
    
    void InteractivePointer::updatePosition(int posX, int posY)
    {
        // Move pointer to its new position.
        this->move((posX - this->width() / 2), (posY - this->height() / 2));
    }
    
    void InteractivePointer::setPointerSizeX(const float &pointerSizeX_)  // Set pointer width.
    {
        m_pointerSizeX = pointerSizeX_;
    }
    
    float InteractivePointer::pointerSizeX() const                        // Get pointer width.
    {
        return m_pointerSizeX;
    }
    
    void InteractivePointer::setPointerSizeY(const float &pointerSizeY_)  // Set pointer height.
    {
        m_pointerSizeY = pointerSizeY_;
    }
    
    float InteractivePointer::pointerSizeY() const                        // Get pointer height.
    {
        return m_pointerSizeY;
    }
    
    void InteractivePointer::setFontSize(const int &fontSize_)            // Set font size.
    {
        m_fontSize = fontSize_;
    }
    
    int InteractivePointer::fontSize() const                              // Get font size.
    {
        return m_fontSize;
    }
    
    void InteractivePointer::zoomView(QVector<int> val1, int winSize)
    {
        // Resize image.
        m_imagep = new QImage(winSize, winSize, QImage::Format_ARGB32);
    
        // Update current pixels.
        int counter = 0;
        for (int i = 0; i < winSize; i++)
        {
            for (int j = 0; j < winSize; j++)
            {
                m_imagep->setPixel(i, j, qRgb(val1[counter + 0], val1[counter + 1], val1[counter + 2]));
    
                counter = counter + 3;
            }
        }
    
        // Create pixel map.
        m_pixmap = QPixmap::fromImage(*m_imagep);
    
        // Set pixel map to this label.
        this->setPixmap(m_pixmap);
    
        delete m_imagep;
    }
    
    void InteractivePointer::diffView(QVector<int> val1, QVector<int> val2, int winSize)
    {
        // Resize image.
        m_imagep = new QImage(winSize, winSize, QImage::Format_ARGB32);
    
        int diff1;
        int diff2;
        int diff3;
    
        // Update current pixels.
        int counter = 0;
        for (int i = 0; i < winSize; i++)
        {
            for (int j = 0; j < winSize; j++)
            {
                diff1 = val1[counter + 0] - val2[counter + 0];
                if (diff1 < 0 || diff1 > 255)
                    diff1 = diff1 - 256;
                diff2 = val1[counter + 1] - val2[counter + 1];
                if (diff2 < 0 || diff2 > 255)
                    diff2 = diff2 - 256;
                diff3 = val1[counter + 2] - val1[counter + 2];
                if (diff3 < 0 || diff3 > 255)
                    diff3 = diff3 - 256;
    
                m_imagep->setPixel(i, j, qRgb(diff1, diff2, diff3));
    
                counter = counter + 3;
            }
        }
    
        // Create pixel map.
        m_pixmap = QPixmap::fromImage(*m_imagep);
    
        // Set pixel map to this label.
        this->setPixmap(m_pixmap);
    
        delete m_imagep;
    }
    
    void InteractivePointer::textView(QVector<int> val1, int winSize)
    {
        this->clear();
    
        int grayColor = 0;
    
        /*double stepX = this->width() / winSize;
        double stepY = this->height() / winSize;
    
        int mid = winSize / 2;
    
        double curX = 0;
        double curY = 0;*/
    
        int counter = 0;
        for (int i = 0; i < winSize; i++)
        {
            for (int j = 0; j < winSize; j++)
            {
                counter = (j * winSize + i) * 3;
                /*curX = curX + stepX*i;
                curY = curY + stepY*j;
                QRect rec(curX-stepX, curY-stepY, curX, curY);*/
    
                grayColor = 0.21 * val1[counter + 0] +
                            0.72 * val1[counter + 1] +
                            0.07 * val1[counter + 2];
    
                QString tempVal = "---";
                if (grayColor / 10 < 1)
                    tempVal = QString("00" + QString::number(grayColor));
                else if (grayColor / 100 < 1)
                    tempVal = QString("0" + QString::number(grayColor));
                else
                    tempVal = QString::number(grayColor);
    
                this->setFont(m_labelFont);
    
                this->setText(this->text() + " " + tempVal);
    
                //counter = counter + 3; // This will rotate the image by 90 deg.
            }
    
            if (i < winSize - 1)
                this->setText(this->text() + "\n");
        }
    }
    

  • Lifetime Qt Champion

    Why transparent since you want a zoomed view of your image ?

    You have a memory leak with m_imagep. You never never delete it. And your use of it is basically:

    1. m_imagep = new QImage(parameters);
    2. modify m_image content
    3. m_pixmap = QPixmap::fromImage(*m_imagep);

    This:

    1. m_image = QImage(parameters);
    2. modify m_image content
    3. m_pixmap = QPixmap::fromImage(m_image);;

    will do the same without memory leak.



  • @SGaist, I think he wants this mechanism to be able to display any arbitrary data, and just mentioned a smaller version of the image as an example of something that might be displayed.

    "you will see a small label displaying a zoomed version (or some other data)"



  • Just to help paint a better picture of the main concept please have a look at this.

    Close to the helmet you can see a blurry rectangle. That is what I have been calling an interactive pointer and is displayed instead of the normal mouse arrow when you hover above the image. I paint it inside my scene as a proxy widget. That is why I said that I did not understand the suggestion of using another QGraphicsView.

    Also SGais I am applying your suggestion, but I thought that by adding delete m_image I could get rid of the leak, if you notice its my very last command on zoom and diffView functions.


  • Lifetime Qt Champion

    I managed to miss the delete, the code scrolling isn't always working well…

    I just thought of something, why not use a QGraphicsPixmapItem ?



  • PixmapItem indeed seems to have been created for that exact purpose, whereas ProxyWidget is more useful (as the name implies) for embedding a widget inside the scene. One drawback though, I did not knew about its existence while implementing the interactive pointer. Plus it will take me a while to implement it because I cannot directly take textPointer and use it as is in a PixmapItem. I definitely want to implement it later on though, probably as soon as I finish with undo/redo functionality.

    I am glad you did not see the delete statement because that way I learned something new. Not to mention that the code is more simple this way.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.