The Case of the Missing MouseEvent constructor


#1

MouseEvent has no default constructor (i.e. parameter-less) which means that a Component cannot save a MouseEvent in a member variable and use it later:

This won’t compile:

class MyComponent : public AsyncUpdater
{
public:
  void mouseEnter (MouseEvent& e)
  {
    m_mouseEvent = e; // Good luck with this one
    triggerAsyncUpdate ();
  }

private:
  MouseEvent m_mouseEvent;  // Class can't be instantiated because of this!
};

How did anyone not notice this ?!?!?!!?


#2

This is my use-case

class MouseEnterGroup

I implemented a work-around but it is hideous.


#3

That was deliberate - I didn’t want people hanging onto copies of mouseevents, which only have a valid meaning during the actual event itself. Putting one into a member variable is the sort of thing I didn’t want to happen to them!


#4

Makes sense. How would you resolve my use-case?


#5

I haven’t studied what you’re trying to do, but why do you need a member variable?


#6

It’s part of detecting a mouse-enter for a group of components. I save the MouseEvent, trigger an AsyncUpdater and then later on call the virtual function with the event. Here is the relevant function:

        void mouseEnter (MouseEvent const& e)
        {
          m_owner.m_mouseInsideNext = true;
          m_owner.setMouseEvent (e.getEventRelativeTo (m_owner.m_parentComponent));
          triggerAsyncUpdate ();
        }

Here’s the full code:

    // Derive your Component-derived class from this object in order to receive
    // mouse enter and mouse exit messages for your component and all of it's
    // children.
    //
    class MouseEnterGroup
    {
    public:
      explicit MouseEnterGroup (Component* parentComponent)
        : m_parentComponent (parentComponent)
        , m_helper (this)
        , m_mouseInside (false)
        , m_mouseInsideNext (false)
      {
      }

      ~MouseEnterGroup ()
      {
      }

      bool isMouseInsideGroup () const
      {
        return m_mouseInside;
      }

      // Called when the mouse enters or exits the group.
      // The event will be relative to the parent Component.
      //
      virtual void onMouseEnterGroup (MouseEvent const& e) { }
      virtual void onMouseExitGroup (MouseEvent const& e) { }

    private:
      void updateState ()
      {
        if (m_mouseInside != m_mouseInsideNext)
        {
          if (m_mouseInsideNext)
          {
            m_mouseInside = true;
            onMouseEnterGroup (getMouseEvent ());
          }
          else
          {
            m_mouseInside = false;
            onMouseExitGroup (getMouseEvent ());
          }
        }
      }

    private:
       // HACK because of Juce
      inline void setMouseEvent (MouseEvent const& mouseEvent)
        { memcpy (m_mouseEvent, &mouseEvent, sizeof (mouseEvent)); }
      inline MouseEvent const& getMouseEvent () const
        { return *reinterpret_cast <MouseEvent const*> (m_mouseEvent); }

      class Helper
        : private MouseListener
        , private AsyncUpdater
      {
      public:
        explicit Helper (MouseEnterGroup* const owner)
          : m_owner (*owner)
        {
          m_owner.m_parentComponent->addMouseListener (this, true);
        }

        ~Helper ()
        {
          m_owner.m_parentComponent->removeMouseListener (this);
        }

        void mouseEnter (MouseEvent const& e)
        {
          m_owner.m_mouseInsideNext = true;
          m_owner.setMouseEvent (e.getEventRelativeTo (m_owner.m_parentComponent));
          triggerAsyncUpdate ();
        }

        void mouseExit (MouseEvent const& e)
        {
          m_owner.m_mouseInsideNext = false;
          m_owner.setMouseEvent (e.getEventRelativeTo (m_owner.m_parentComponent));
          triggerAsyncUpdate ();
        }

        void handleAsyncUpdate ()
        {
          m_owner.updateState ();
        }

      private:
        MouseEnterGroup& m_owner;
      };

    private:
      Component* const m_parentComponent;
      Helper m_helper;
      bool m_mouseInside;
      bool m_mouseInsideNext;
      char m_mouseEvent [sizeof (MouseEvent)]; // HACK because of Juce!
    };

#7

But the event is no longer valid when the async updater fires… By that point, the components that are referenced by the event may have been deleted.

That’s why I tried to stop people copying these things around - it’s only safe to use them during the actual event callback. At a later point, calling its methods could cause crashes or return incorrect values.


#8

True in the general case, but impossible the way my class is designed. I see your point though…I can just stick with my reinterpret_cast and char array hack.


#9

Damn Jules, it seems you thought of everything. I see what you mean. If, during the processing of the mouse movements, a component referenced in my hierarchy changes its position it could make the coordinates become invalid.

Did you think of this as you were writing the MouseEvent class? Or did it later come up as a bug?


#10

Yes - all the code that surrounds component event callbacks has always had to be paranoid about components being deleted, because it's so common to do so during a callback. Hence all the WeakReference/ComponentDeletionChecker stuff that's scattered through the component classes.

Yes - all the code that surrounds component event callbacks has always had to be paranoid about components being deleted, because it’s so common to do so during a callback. Hence all the WeakReference/ComponentDeletionChecker stuff that’s scattered through the component classes.