Search code examples
qtqwidgetclipqpainterqt4.8

QPainter ignores clipping inside paintEvent


I am trying to a draw text below a QPushButton (outside of the bounds of the button) whenever it gets focused. For this I have subclassed QPushButton and I am drawing text using QPainter drawText method inside overriden paintEvent method of QPushButton. Before drawing I am setting QPainter::setClipping(false) to enable text drawing out side button's bounds. But somehow QPainter::setClipping(false) is not working and the text is not getting drawn outside the button bounds.


Solution

  • The painter's paint engine itself implements clipping at a lower level, and the widget painter is not clipping by default. In other words: it's impossible to turn this clipping off. The following holds:

    void Class::paintEvent(QPaintEvent *ev) {
      Parent::paintEvent(ev);
      QPainter p(this);
      Q_ASSERT(!p.hasClipping());
      // and now you do:
      p.setClipping(false); // this is a no-op!
    }
    

    A simple solution is to add a widget that draws the text as a sibling of the button, and locate it below the button. That widget can be a QLabel.

    static const char kLabelSibling[] = "qq_labelSibling";
    static const char kTrackedSibling[] = "qq_trackedSibling";
    
    void setTextBelow(QWidget *widget, const QString &text) {
      Q_ASSERT(widget->parent);
      class Filter : QObject {
        static QLabel *getLabel(QObject *sibling) {
          return widget->property(kLabelSibling).value<QLabel*>();
        }
        static void updateLabel(QWidget *label) {
          auto *sibling = label->property(kTrackedSibling).value<QWidget*>();
          Q_ASSERT(sibling);
          label->setParent(sibling->parent());
          label->move(sibling->bottomLeft());
          label->setVisible(sibling->hasFocus());
        }
        bool eventFilter(QObject *obj, QEvent *ev) override {
          if (auto *label = getLabel(obj))
            if (ev->type() == QEvent::Resize || ev->type() == QEvent::Move 
                || ev->type() == QEvent::ParentChange || ev->type() == QEvent::FocusIn
                || ev->type() == QEvent::FocusOut)
              updateLabel(label);
          return false;
        }
       public:
        using QObject::QObject;
      };
      auto *label = Filter::getLabel(widget);
      if (!label) {
        label = new QLabel(widget->parent());
        label->setProperty(kTrackedSibling, QVariant::fromValue(widget));
        widget->setProperty(kLabelSibling, QVariant::fromValue(label));
        widget->installEventFilter(new Filter(widget));
        QObject::connect(widget, &QObject::destroyed, label, [widget, label]{
          widget->setProperty(kLabelSibling, {});
          label->setProperty(kTrackedSibling, {});
          label->deleteLater();
        });
      }
      label->setText(text);
      Filter::updateLabel(label);
    }
    

    (Sorry, the above is untested and written from memory)