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. Accept/reject focus coming by mouse click, based on coordinates
QtWS25 Last Chance

Accept/reject focus coming by mouse click, based on coordinates

Scheduled Pinned Locked Moved Solved General and Desktop
widgetfocusmouse
11 Posts 3 Posters 3.0k Views
  • 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.
  • S Offline
    S Offline
    sommerluk
    wrote on 30 Aug 2020, 11:34 last edited by
    #1

    Hello.

    How can be created a widget of circular shape, that accepts focus by mouse click only when clicking within the circle, but not when clicking outside the circle?

    Background: I’m developing a colour wheel widget. It inherits from QWidget. From the user perspective, the colour wheel is obviously circular. However, the underlying widget is of course rectangular for the layout management. So if QWidget::setFocusPolicy(Qt::FocusPolicy::ClickFocus)
    is set, the widget will accept focus when clicking within the circle, but also when clicking outside the circle anywhere in the rectangular space of the widget. But I don’t like this behaviour. I would prefer the focus is only accepted when clicking directly in the circle itself.

    I wonder if there is a clean solution for this problem. With QDial, there exists a circular-shaped widget within the Qt library itself; however QDial shows exactly the behaviour that I don’t want, so looking at QDial doesn’t help.

    Currently, I use a sort of workaround. I call QWidget::setFocusPolicy(Qt::FocusPolicy::TabFocus)
    so the build-in mechanism doesn’t accept any focus by mouse click. Then, I reimplement QWidget::mousePressEvent() and within this implementation, if the click is within the circle, I call setFocus(Qt::MouseFocusReason). That works. The disadvantage is, that focus by mouse click is accepted though the focusPolicy() property suggests it is not. This is confusing for programmers who might use the widget. And if they set the property to Qt::FocusPolicy::ClickFocus, again clicks outside the circle will be accepted. That feels like a dirty hack.

    Also I’ve tried to reimplement QWidget::event() and QWidget::focusInEvent() and simply ignore the event, but apparently this does not have any effect.

    Is there a solution that actually rejects the focus when Qt::FocusPolicy::ClickFocus isn’t set, but actually only accepts clicks within the circle if Qt::FocusPolicy::ClickFocus is set?

    Best regards

    1 Reply Last reply
    0
    • S Offline
      S Offline
      stryga42
      wrote on 30 Aug 2020, 13:21 last edited by
      #2

      Just a guess, not a proven solution: Did you try to install an event filter (see QObject::eventFilter) on your widget? You could look for QEvent::FocusIn, calculate if the click was inside your visual boundary and accept or reject accordingly. But honestly, I'm not sure if this works better than ignoring the QWidget::focusInEvent...

      1 Reply Last reply
      0
      • S Offline
        S Offline
        sommerluk
        wrote on 30 Aug 2020, 17:03 last edited by
        #3

        @stryga42 Thanks for the suggestion.

        I’ve tried that now, but the result is the same as with reimplementing event() or focusInEvent(): It doesn’t prevent that the focus is assigned. It seems the focus is assigned anyway, and the event just informs the widget about that, without a possibility to cancel it?

        1 Reply Last reply
        0
        • S Offline
          S Offline
          stryga42
          wrote on 30 Aug 2020, 17:21 last edited by
          #4

          Maybe you are right that the focusIn is "too late".
          What about combining the eventFilter approach (the filter is notified definitely before the target object gets the event) with your current mouse click approach? Use FocusPolicy::ClickFocus and suppress the MouseButtonPress event in the event filter if the mouse position is not truly "inside".

          1 Reply Last reply
          0
          • S Offline
            S Offline
            sommerluk
            wrote on 30 Aug 2020, 17:41 last edited by
            #5

            I’ve just tied that. Same result: Even when filtering all mouse and focus events, the focus itself gets assigned nevertheless.

            1 Reply Last reply
            0
            • J Offline
              J Offline
              jeremy_k
              wrote on 31 Aug 2020, 07:10 last edited by
              #6
              #include <QApplication>
              #include <QWidget>
              #include <QPainter>
              #include <QLayout>
              #include <QMouseEvent>
              #include <QPixmap>
              #include <QImage>
              
              class Widget : public QWidget
              {
                  Q_OBJECT
              
              protected:
                  void paintEvent([[maybe_unused]] QPaintEvent *event) override
                  {
                      QPainter painter(this);
                      painter.drawRect(this->rect());
                      painter.setBrush(this->hasFocus() ? Qt::red : Qt::blue);
                      painter.drawEllipse(this->rect());
                  }
              
                  void mousePressEvent(QMouseEvent *event) override
                  {
                      QPixmap pixmap = this->grab(QRect(event->x(), event->y(), 1, 1));
                      auto pixel = pixmap.toImage().pixel(0, 0);
                      if (pixel == 0xFF0000FF)
                          this->setFocus(Qt::MouseFocusReason);
                      QWidget::mousePressEvent(event);
                  }
              };
              
              int main(int argc, char *argv[])
              {
                  QApplication a(argc, argv);
                  QWidget topLevel;
                  topLevel.setGeometry(0, 0, 200, 100);
                  QHBoxLayout layout(&topLevel);
                  Widget w1, w2;
                  layout.addWidget(&w1);
                  layout.addWidget(&w2);
                  topLevel.show();
                  return a.exec();
              }
              
              #include "main.moc"
              

              Asking a question about code? http://eel.is/iso-c++/testcase/

              S 1 Reply Last reply 1 Sept 2020, 12:24
              0
              • J jeremy_k
                31 Aug 2020, 07:10
                #include <QApplication>
                #include <QWidget>
                #include <QPainter>
                #include <QLayout>
                #include <QMouseEvent>
                #include <QPixmap>
                #include <QImage>
                
                class Widget : public QWidget
                {
                    Q_OBJECT
                
                protected:
                    void paintEvent([[maybe_unused]] QPaintEvent *event) override
                    {
                        QPainter painter(this);
                        painter.drawRect(this->rect());
                        painter.setBrush(this->hasFocus() ? Qt::red : Qt::blue);
                        painter.drawEllipse(this->rect());
                    }
                
                    void mousePressEvent(QMouseEvent *event) override
                    {
                        QPixmap pixmap = this->grab(QRect(event->x(), event->y(), 1, 1));
                        auto pixel = pixmap.toImage().pixel(0, 0);
                        if (pixel == 0xFF0000FF)
                            this->setFocus(Qt::MouseFocusReason);
                        QWidget::mousePressEvent(event);
                    }
                };
                
                int main(int argc, char *argv[])
                {
                    QApplication a(argc, argv);
                    QWidget topLevel;
                    topLevel.setGeometry(0, 0, 200, 100);
                    QHBoxLayout layout(&topLevel);
                    Widget w1, w2;
                    layout.addWidget(&w1);
                    layout.addWidget(&w2);
                    topLevel.show();
                    return a.exec();
                }
                
                #include "main.moc"
                
                S Offline
                S Offline
                sommerluk
                wrote on 1 Sept 2020, 12:24 last edited by
                #7

                @jeremy_k

                Thanks for the proposition.

                However, this code would just allow to capture the mouse focus within the circle. This is what my code is doing yet. The question is how to prevent that the widget gets focus when clicked outside the circle, but inside the rectangle, when QWidget::setFocusPolicy(Qt::FocusPolicy::ClickFocus) had been called.

                1 Reply Last reply
                0
                • J Offline
                  J Offline
                  jeremy_k
                  wrote on 1 Sept 2020, 19:23 last edited by
                  #8

                  I'm not sure what is meant by focus, if not the return value of QWidget::hasFocus().

                  Asking a question about code? http://eel.is/iso-c++/testcase/

                  1 Reply Last reply
                  0
                  • S Offline
                    S Offline
                    sommerluk
                    wrote on 1 Sept 2020, 20:42 last edited by
                    #9

                    Indeed.

                    Now, my problem is not how to reimplement mousePressEvent(). I've done that yet, just as you propose, and it works. The actual implementation is here: https://sommerluk.github.io/perceptualcolor/chromahuediagram_8cpp_source.html#l00137

                    It does quite exactly the same thing.

                    My problem is that this feels a little bit like a hack. This implementation will get focus by mouse click even when the widget’s focusPolicy is Qt::FocusPolicy::TabFocus. On the other hand, when focusPolicy is Qt::FocusPolicy::ClickFocus, it will also gather the focus by clicks outside the circle, but within the widget rectangle. That feels inconsistent…

                    J 1 Reply Last reply 1 Sept 2020, 21:35
                    0
                    • S sommerluk
                      1 Sept 2020, 20:42

                      Indeed.

                      Now, my problem is not how to reimplement mousePressEvent(). I've done that yet, just as you propose, and it works. The actual implementation is here: https://sommerluk.github.io/perceptualcolor/chromahuediagram_8cpp_source.html#l00137

                      It does quite exactly the same thing.

                      My problem is that this feels a little bit like a hack. This implementation will get focus by mouse click even when the widget’s focusPolicy is Qt::FocusPolicy::TabFocus. On the other hand, when focusPolicy is Qt::FocusPolicy::ClickFocus, it will also gather the focus by clicks outside the circle, but within the widget rectangle. That feels inconsistent…

                      J Offline
                      J Offline
                      jeremy_k
                      wrote on 1 Sept 2020, 21:35 last edited by
                      #10

                      @sommerluk said in Accept/reject focus coming by mouse click, based on coordinates:

                      My problem is that this feels a little bit like a hack. This implementation will get focus by mouse click even when the widget’s focusPolicy is Qt::FocusPolicy::TabFocus.

                      So check the widget's focus policy and call or don't call setFocus() according to your needs. It looks like adding a new focus policy reason that fits in the enum won't be rejected as of 5.15.0, although there's no explicit allowance for it in the API. Adding a separate attribute of some sort is less likely to conflict with Qt API changes.

                      On the other hand, when focusPolicy is Qt::FocusPolicy::ClickFocus, it will also gather the focus by clicks outside the circle, but within the widget rectangle. That feels inconsistent…

                      The documentation says Qt::ClickFocus 0x2 the widget accepts focus by clicking.
                      QWidgets are rectangular. The unpainted or differently painted space within the rectangle still belongs to that widget as far as the framework is concerned.

                      Have a look at the implementation. Determining if the widget gets focus due to ClickFocus happens before the click is delivered to the widget, based on its geometry, focus policy, and focus proxy.
                      https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qapplication.cpp.html#3125

                      For what it's worth, other people have asked for the same functionality.
                      https://bugreports.qt.io/browse/QTBUG-598

                      Asking a question about code? http://eel.is/iso-c++/testcase/

                      1 Reply Last reply
                      0
                      • S Offline
                        S Offline
                        sommerluk
                        wrote on 1 Sept 2020, 21:53 last edited by
                        #11

                        Okay, got it: Relevant code resides in QApplication and cannot be changed by subclassing QWidget. There’s no way around Qt::FocusPolicy but there is the possibility to add other attributes.

                        Thanks a lot!

                        1 Reply Last reply
                        0

                        7/11

                        1 Sept 2020, 12:24

                        • Login

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