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. Issue with QLabel and wordwrap
Forum Updated to NodeBB v4.3 + New Features

Issue with QLabel and wordwrap

Scheduled Pinned Locked Moved Solved General and Desktop
26 Posts 5 Posters 613 Views 3 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.
  • PLL3P PLL3

    Hi, closing this thread.

    To sum up, my issue stems from a Qt layout limitation, for posterity's sake here's the solution that I ended up using to get around my problem:

    I installed an eventFilter on my QLabel, filtering for QSinglePointEvent and checking wether the cursor was within the text, here's a small code example:

    bool SubWidget::eventFilter(QObject * iObject, QEvent* iEvent){
        if ((iObject == _label) && (iEvent->isSinglePointEvent())){ //if event is a mouse event on _label
            QFontMetrics fm(_label->font());
            int textWidth = fm.horizontalAdvance(_label->text()); // width of the text in pixels
            int textHeight = fm.height(); // height of the text in pixels
            int textX = 0;
            int textY = 0;
    
            // calculate the coordinates for the top-left pixel of the actual text, text position depends on alignment
            if ((_label->alignment() & Qt::AlignLeft) == Qt::AlignLeft){
                textX = _label->x();
            }
            else if ((_label->alignment() & Qt::AlignHCenter) == Qt::AlignHCenter){
                textX = _label->x() + (_label->width() - textWidth)/2;
            }
            else if ((_label->alignment() & Qt::AlignRight) == Qt::AlignRight){
                textX = _label->x() + _label->width() - textWidth;
            }
            if ((_label->alignment() & Qt::AlignTop) == Qt::AlignTop){
                textY = _label->y();
            }
            else if ((_label->alignment() & Qt::AlignVCenter) == Qt::AlignVCenter){
                textY = _label->y() + (_label->height() - textHeight)/2;
            }
            else if ((_label->alignment() & Qt::AlignBottom) == Qt::AlignBottom){
                textY = _label->y() + _label->height() - textHeight;
            }
            QSinglePointEvent* pointEvent = static_cast<QSinglePointEvent*>(iEvent);
            QPointF mousePos = pointEvent->position(); // Get mouse position relative to the label from the event
    
            if ((textX <= mousePos.x() && mousePos.x() <= (textX + textWidth)) // if mouse is located within the text area
                && (textY <= mousePos.y() && mousePos.y() <= (textY + textHeight)))
            {
                //Do stuff here to further refine the event
                //eg: hover event, mousepress event etc etc
                return true;
            }
            else
            {
                return false;
            }
        }
        return QWidget::eventFilter(iObject, iEvent);
    }
    
    B Offline
    B Offline
    Bonnie
    wrote last edited by Bonnie
    #21

    @PLL3 Emm, this doesn't seem right. Isn't it only working for single line text? Are you sure it works when the text is wrapped?
    The proper method I think here should be https://doc.qt.io/qt-6/qfontmetrics.html#boundingRect-3, which can calculate with text wrapping.

    PLL3P 1 Reply Last reply
    0
    • B Bonnie

      @PLL3 Emm, this doesn't seem right. Isn't it only working for single line text? Are you sure it works when the text is wrapped?
      The proper method I think here should be https://doc.qt.io/qt-6/qfontmetrics.html#boundingRect-3, which can calculate with text wrapping.

      PLL3P Offline
      PLL3P Offline
      PLL3
      wrote last edited by
      #22

      @Bonnie Ohh damn, you are correct, my textWidth value doesn't change when the text is wrapped or unwrapped.

      Thanks for pointing that out, I might have not detected this for a while.
      I'll look into what you proposed and update my previous solution if that works.

      1 Reply Last reply
      0
      • PLL3P PLL3

        Aren't you already handling the mouse events since QLabel has no "clicked" signal?

        Yes, but to handle the mouse position I simply use the enterEvent and leaveEvent toggling a flag to remember wether the mouse is hovering the widget or no.
        If I understand correctly, I'll have to replace/modify that to work with the text rect instead of the whole QLabel, sounds doable, if only a bit convoluted.

        Anyway, I think we've pretty much gone round the whole subject. Nothing much to do here, I think I'll close the thread soon.
        I'll keep watching @JonB 's thread, since it's asking for an evolution that would solve my current issue.

        JonBJ Offline
        JonBJ Offline
        JonB
        wrote last edited by
        #23

        @PLL3 said in Issue with QLabel and wordwrap:

        I'll keep watching @JonB 's thread, since it's asking for an evolution that would solve my current issue.

        FWIW, @Bonnie has now answered my thread correctly in https://forum.qt.io/post/832859. void QLayoutItem::setAlignment(Qt::Alignment alignment) is just what I was asking about/looking for but couldn't spot. One can forget "spacers" and "stretchers" and just do layout->setAlignment(Qt::AlignLeft) (or another desired alignment), it is a single setting on the layout and makes much simpler sense.

        However, I had a quick fiddle and didn't find it really helped with your word wrapping case. You might want to try for yourself in case you find some way, but I'm thinking it does not address your situation.

        1 Reply Last reply
        0
        • PLL3P Offline
          PLL3P Offline
          PLL3
          wrote last edited by PLL3
          #24

          @JonB One can forget "spacers" and "stretchers" and just do layout->setAlignment(Qt::AlignLeft)

          Out of curiosity I tried it, and it had the same result as adding a spacer item after my QLabel, ie it squishes it to the left and forces it to wrap as much as it can.

          @Bonnie The proper method I think here should be https://doc.qt.io/qt-6/qfontmetrics.html#boundingRect-3, which can calculate with text wrapping.

          Maybe I utilized it wrong but it did not work for me, I tried bot of these calls:

          QRect boundingRect1 = fm.boundingRect(_label->rect(), _label->alignment(), _label->text());
          
          QRect boundingRect2 = fm.boundingRect(_label->frameRect(), _label->alignment(), _label->text());
          

          As soon as the text start wrapping, it didn't work.

          I ended up using the original width of the unwrapped text and the actual size of the QLabel.
          The current width of the text is smaller value of the two.
          By comparing the original width of the unwrapped text and the QLabel width I estimate the number of lines and using the lineheight I can estimate the current height of the text.

          Here's the updated eventFilter:

          bool SubWidget::eventFilter(QObject * iObject, QEvent* iEvent){
              if ((iObject == _label) && (iEvent->isSinglePointEvent())){ //if event is a mouse event on _label
          
                  QFontMetrics fm(_label->font());
                  int lineHeight = fm.height(); // height of one line of text in pixels
                  int originalTextWidth = fm.horizontalAdvance(_label->text()); // original width of the text (unwrapped) in pixels
          
                  QRect labelRect = _label->frameRect();
                  int labelWidth = labelRect.width(); // Current width of the entire label
          
                  int nbOfLines = 1 + (originalTextWidth / labelWidth); // we try to estimate the number of lines (not always 100% accurate but good enough)
          
                  // We now have estimations of the actual text width and text height
                  int textHeightEstimate = lineHeight * nbOfLines;
                  int textWidthEstimate = originalTextWidth < labelWidth ? originalTextWidth : labelWidth;
          
                  int textX = 0;
                  int textY = 0;
                  // calculate the coordinates for the top-left pixel of the actual text, text position depends on alignment
                  if ((_label->alignment() & Qt::AlignLeft) == Qt::AlignLeft){
                      textX = 0;
                  }
                  else if ((_label->alignment() & Qt::AlignHCenter) == Qt::AlignHCenter){
                      textX = (_label->width() - textWidthEstimate)/2;
                  }
                  else if ((_label->alignment() & Qt::AlignRight) == Qt::AlignRight){
                      textX = _label->width() - textWidthEstimate;
                  }
                  if ((_label->alignment() & Qt::AlignTop) == Qt::AlignTop){
                      textY = 0;
                  }
                  else if ((_label->alignment() & Qt::AlignVCenter) == Qt::AlignVCenter){
                      textY = (_label->height() - textHeightEstimate)/2;
                  }
                  else if ((_label->alignment() & Qt::AlignBottom) == Qt::AlignBottom){
                      textY = _label->height() - textHeightEstimate;
                  }
          
                  QSinglePointEvent* pointEvent = static_cast<QSinglePointEvent*>(iEvent);
                  QPointF mousePos = pointEvent->position(); // Get mouse position relative to the label from the event
                  if ((textX <= mousePos.x() && mousePos.x() <= (textX + textWidthEstimate)) // if mouse is located within the text area
                      && (textY <= mousePos.y() && mousePos.y() <= (textY + textHeightEstimate)))
                  {
                      // Mouse is hovering text
                      // Do stuff here
                      std::cout << "MOUSE IS HOVERING TEXT !!!!!" << std::endl;
                      return true;
                  }
                  else
                  {
                      // Mouse is hovering label but not text
                      // Do stuff here
                      return false;
                  }
              }
              return QWidget::eventFilter(iObject, iEvent);
          }
          

          From my quick tests, this seems to work most of the time.
          One exception is, due to how wrapping works with words of varying sizes, when wrapping over several lines, I might calculate 5 lines of text when there are really 6. But this is frankly minor, and shouldn't happen very often.
          This works well enough and I already spent too much time on this minor issue.

          Thanks for your help @JonB and @Bonnie

          JonBJ B 2 Replies Last reply
          0
          • PLL3P PLL3

            @JonB One can forget "spacers" and "stretchers" and just do layout->setAlignment(Qt::AlignLeft)

            Out of curiosity I tried it, and it had the same result as adding a spacer item after my QLabel, ie it squishes it to the left and forces it to wrap as much as it can.

            @Bonnie The proper method I think here should be https://doc.qt.io/qt-6/qfontmetrics.html#boundingRect-3, which can calculate with text wrapping.

            Maybe I utilized it wrong but it did not work for me, I tried bot of these calls:

            QRect boundingRect1 = fm.boundingRect(_label->rect(), _label->alignment(), _label->text());
            
            QRect boundingRect2 = fm.boundingRect(_label->frameRect(), _label->alignment(), _label->text());
            

            As soon as the text start wrapping, it didn't work.

            I ended up using the original width of the unwrapped text and the actual size of the QLabel.
            The current width of the text is smaller value of the two.
            By comparing the original width of the unwrapped text and the QLabel width I estimate the number of lines and using the lineheight I can estimate the current height of the text.

            Here's the updated eventFilter:

            bool SubWidget::eventFilter(QObject * iObject, QEvent* iEvent){
                if ((iObject == _label) && (iEvent->isSinglePointEvent())){ //if event is a mouse event on _label
            
                    QFontMetrics fm(_label->font());
                    int lineHeight = fm.height(); // height of one line of text in pixels
                    int originalTextWidth = fm.horizontalAdvance(_label->text()); // original width of the text (unwrapped) in pixels
            
                    QRect labelRect = _label->frameRect();
                    int labelWidth = labelRect.width(); // Current width of the entire label
            
                    int nbOfLines = 1 + (originalTextWidth / labelWidth); // we try to estimate the number of lines (not always 100% accurate but good enough)
            
                    // We now have estimations of the actual text width and text height
                    int textHeightEstimate = lineHeight * nbOfLines;
                    int textWidthEstimate = originalTextWidth < labelWidth ? originalTextWidth : labelWidth;
            
                    int textX = 0;
                    int textY = 0;
                    // calculate the coordinates for the top-left pixel of the actual text, text position depends on alignment
                    if ((_label->alignment() & Qt::AlignLeft) == Qt::AlignLeft){
                        textX = 0;
                    }
                    else if ((_label->alignment() & Qt::AlignHCenter) == Qt::AlignHCenter){
                        textX = (_label->width() - textWidthEstimate)/2;
                    }
                    else if ((_label->alignment() & Qt::AlignRight) == Qt::AlignRight){
                        textX = _label->width() - textWidthEstimate;
                    }
                    if ((_label->alignment() & Qt::AlignTop) == Qt::AlignTop){
                        textY = 0;
                    }
                    else if ((_label->alignment() & Qt::AlignVCenter) == Qt::AlignVCenter){
                        textY = (_label->height() - textHeightEstimate)/2;
                    }
                    else if ((_label->alignment() & Qt::AlignBottom) == Qt::AlignBottom){
                        textY = _label->height() - textHeightEstimate;
                    }
            
                    QSinglePointEvent* pointEvent = static_cast<QSinglePointEvent*>(iEvent);
                    QPointF mousePos = pointEvent->position(); // Get mouse position relative to the label from the event
                    if ((textX <= mousePos.x() && mousePos.x() <= (textX + textWidthEstimate)) // if mouse is located within the text area
                        && (textY <= mousePos.y() && mousePos.y() <= (textY + textHeightEstimate)))
                    {
                        // Mouse is hovering text
                        // Do stuff here
                        std::cout << "MOUSE IS HOVERING TEXT !!!!!" << std::endl;
                        return true;
                    }
                    else
                    {
                        // Mouse is hovering label but not text
                        // Do stuff here
                        return false;
                    }
                }
                return QWidget::eventFilter(iObject, iEvent);
            }
            

            From my quick tests, this seems to work most of the time.
            One exception is, due to how wrapping works with words of varying sizes, when wrapping over several lines, I might calculate 5 lines of text when there are really 6. But this is frankly minor, and shouldn't happen very often.
            This works well enough and I already spent too much time on this minor issue.

            Thanks for your help @JonB and @Bonnie

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote last edited by
            #25

            @PLL3 said in Issue with QLabel and wordwrap:

            Out of curiosity I tried it, and it had the same result as adding a spacer item after my QLabel

            That's about what I expected, possibly in all cases. Which is why I didn't think it would help you. My beef (in the related thread I created) is that I feel this is a much "nicer" way of achieving left alignment without having a spacer at the end or a stretch on the last widget.

            1 Reply Last reply
            0
            • PLL3P PLL3

              @JonB One can forget "spacers" and "stretchers" and just do layout->setAlignment(Qt::AlignLeft)

              Out of curiosity I tried it, and it had the same result as adding a spacer item after my QLabel, ie it squishes it to the left and forces it to wrap as much as it can.

              @Bonnie The proper method I think here should be https://doc.qt.io/qt-6/qfontmetrics.html#boundingRect-3, which can calculate with text wrapping.

              Maybe I utilized it wrong but it did not work for me, I tried bot of these calls:

              QRect boundingRect1 = fm.boundingRect(_label->rect(), _label->alignment(), _label->text());
              
              QRect boundingRect2 = fm.boundingRect(_label->frameRect(), _label->alignment(), _label->text());
              

              As soon as the text start wrapping, it didn't work.

              I ended up using the original width of the unwrapped text and the actual size of the QLabel.
              The current width of the text is smaller value of the two.
              By comparing the original width of the unwrapped text and the QLabel width I estimate the number of lines and using the lineheight I can estimate the current height of the text.

              Here's the updated eventFilter:

              bool SubWidget::eventFilter(QObject * iObject, QEvent* iEvent){
                  if ((iObject == _label) && (iEvent->isSinglePointEvent())){ //if event is a mouse event on _label
              
                      QFontMetrics fm(_label->font());
                      int lineHeight = fm.height(); // height of one line of text in pixels
                      int originalTextWidth = fm.horizontalAdvance(_label->text()); // original width of the text (unwrapped) in pixels
              
                      QRect labelRect = _label->frameRect();
                      int labelWidth = labelRect.width(); // Current width of the entire label
              
                      int nbOfLines = 1 + (originalTextWidth / labelWidth); // we try to estimate the number of lines (not always 100% accurate but good enough)
              
                      // We now have estimations of the actual text width and text height
                      int textHeightEstimate = lineHeight * nbOfLines;
                      int textWidthEstimate = originalTextWidth < labelWidth ? originalTextWidth : labelWidth;
              
                      int textX = 0;
                      int textY = 0;
                      // calculate the coordinates for the top-left pixel of the actual text, text position depends on alignment
                      if ((_label->alignment() & Qt::AlignLeft) == Qt::AlignLeft){
                          textX = 0;
                      }
                      else if ((_label->alignment() & Qt::AlignHCenter) == Qt::AlignHCenter){
                          textX = (_label->width() - textWidthEstimate)/2;
                      }
                      else if ((_label->alignment() & Qt::AlignRight) == Qt::AlignRight){
                          textX = _label->width() - textWidthEstimate;
                      }
                      if ((_label->alignment() & Qt::AlignTop) == Qt::AlignTop){
                          textY = 0;
                      }
                      else if ((_label->alignment() & Qt::AlignVCenter) == Qt::AlignVCenter){
                          textY = (_label->height() - textHeightEstimate)/2;
                      }
                      else if ((_label->alignment() & Qt::AlignBottom) == Qt::AlignBottom){
                          textY = _label->height() - textHeightEstimate;
                      }
              
                      QSinglePointEvent* pointEvent = static_cast<QSinglePointEvent*>(iEvent);
                      QPointF mousePos = pointEvent->position(); // Get mouse position relative to the label from the event
                      if ((textX <= mousePos.x() && mousePos.x() <= (textX + textWidthEstimate)) // if mouse is located within the text area
                          && (textY <= mousePos.y() && mousePos.y() <= (textY + textHeightEstimate)))
                      {
                          // Mouse is hovering text
                          // Do stuff here
                          std::cout << "MOUSE IS HOVERING TEXT !!!!!" << std::endl;
                          return true;
                      }
                      else
                      {
                          // Mouse is hovering label but not text
                          // Do stuff here
                          return false;
                      }
                  }
                  return QWidget::eventFilter(iObject, iEvent);
              }
              

              From my quick tests, this seems to work most of the time.
              One exception is, due to how wrapping works with words of varying sizes, when wrapping over several lines, I might calculate 5 lines of text when there are really 6. But this is frankly minor, and shouldn't happen very often.
              This works well enough and I already spent too much time on this minor issue.

              Thanks for your help @JonB and @Bonnie

              B Offline
              B Offline
              Bonnie
              wrote last edited by Bonnie
              #26

              @PLL3 said in Issue with QLabel and wordwrap:

              Out of curiosity I tried it, and it had the same result as adding a spacer item after my QLabel, ie it squishes it to the left and forces it to wrap as much as it can.

              Yes, as the example code I posted above shows, I met this problem exactly when I'm using alignment in layout :)

              @PLL3 said in Issue with QLabel and wordwrap:

              QRect boundingRect1 = fm.boundingRect(_label->rect(), _label->alignment(), _label->text());

              Oh, flags is not the same as alignment, the way you call it still doesn't tell that you want text to wrap.
              So here it should be something like

              fm.boundingRect(_label->contentsRect(), _label->alignment() | Qt::TextWordWrap, _label->text())
              

              contentsRect() is not the exact rect value that Qt use to draw text, but is the closest one in my opinion. If you want to be more accurate you can check the Qt source code of QLabel::paintEvent, but maybe not very necessary.

              1 Reply Last reply
              0

              • Login

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