Component hook - How to set a persistent custom mouse cursor


#1

Hi,

I was wondering what is the cleanest way set a persistent custom mouse cursor ?
Currently, I’ve a global cursor I set up upon loading the main window, and each sub component need to call “setMouseCursor” to use it.

I was wondering if there is a easier way.
Currently I’m trying to hook inside the component peer to call the setMouseCursor in it, but it’s not clean.
Does the new look and feel will support this ?

Else, how to do it without having to modify each component ?


#2

Yes, it’s not really designed to let you specify global cursors. Messing with the componentpeer is really not a good approach. I suppose a look and feel could let you provide a method to override a component’s cursor, but it’s not something I’d thought about before.


#3

I finally used a “CursorUpdater” singleton used by every component.
However, whatever I try, the Slider component doesn’t respect the “setMouseCursor” method. It just revert to the standard arrow.
I don’t see anywhere in code were it does this.

Do you have an idea ?


#4

Maybe Slider overrides the getMouseCursor method?


#5

It doesn’t.

I’ve no idea why then it doesn’t accept the mouse cursor I’m telling it to use.
Is there something with the drag and drop ?


#6

Well a slider actually contains other components for the text entry box, etc, so they’d also probably need their cursor changing. If you’re really determined to change it for everything, why not just write a recursive function that sets the cursor for every component?


#7

I’m not using any textbox in my code.
Applying setMouseCursor recursively doesn’t really solve it as, as soon as new components get created, I need to callback the “recursiveApply” function again.

I think a hook mechanism in component would be quite simple to do, and very useful:
In component.hpp:

struct CreationHook
{
    virtual void componentCreated(Component * ) = 0;
    virtual ~CreationHook() {}
};

// In Component class
void registerCreationHook(CreationHook * hook);
void unregisterCreationHook(CreationHook * hook);

static CreationHook * hook;

In component.cpp

CreationHook * Component::hook = 0;
// In Component::Component
[...]
if (hook) hook->componentCreated(this);
[...]

#8

Oh no, I don’t like the idea of a hook at all! Yeesh. No, that’s what the L+F is for. I’d be open the idea of some sort of LookAndFeel::getCursorFor(Component) which would normally just return the component’s cursor.


#9

Then a change like this:
In LookAndFeel.hpp, in LookAndFeel class

MouseCursor & getMouseCursorFor(Component & component) { return component.getMouseCursor(); }
template <class T>
MouseCursor & getMouseCursorFor(T & component) { return component.getMouseCursor(); }

Then we would only have to do something like:

// Specialization of Sliders 
template<> 
MouseCursor & LookAndFeel::getMouseCursorFor(Slider & component) { return globalSliderMouseCursor; }

It’s a kind of static, compile time specialization. However, I don’t get where you’d call this method (calling it at numerous places seems a huge effort?)


#10

Templates…? What have you been smoking?

All you’d need would be a virtual method, which the peer would call instead of directly calling Component::getMouseCursor. So you could create a custom look and feel, and it’d give you a chance to return a different cursor for any components you chose to.


#11

I don’t get it.
How do you select which component to accept with a virtual method ?
I mean, if I’ve understood you correctly, you meant :

// Pseudo code
ComponentPeer::changeMouseCursor()
{
     // With LookAndFeel::getMouseCursor being virtual
     Native::setMouseCursor(LookAndFeel::getInstance()->getMouseCursor(peer->component));
}

Then, how does LookAndFeel knows it’s dealing with a Slider or say a TextBox ? dynamic_cast ?
Dynamic cast is worst than templates, as it consume runtime cost, and is a pain to maintain (I would have to make my look and feel class aware of all the slider’s derived class I’ve written, and as soon as I add a new child, I must remember to update L&F code).

I want to have a global mouse cursor to the application (different than the windows’ arrow, it’s a “hand-like” cursor that is animated).
I’ve almost solved it with my singleton code, which stores component pointers in a list and call “setMouseCursor” at regular interval to animate the hand.

For slider, for example, the hand should updates to “half closed hand” (meaning, “there is an action to perform”).
Currently, I’m unable to do this (slider doesn’t do anything with setMouseCursor, but I still have to try the recursive method like you said, but it’s still not perfect).
With the L&F approach, I could only do a poor man “if (dynamic cast(type1) else if dynamic cast(type2)” and so on, which is slow as hell as soon as the number of component to handle is big!


#12

Dynamic casts are slow?? They take a few machine instructions… do you have the slightest idea how much other code runs every time the mouse moves!? A few dynamic casts, or even a few hundred of them, wouldn’t make any significant difference at all!

Your original request was for a global mouse cursor, and this is a perfect solution for that. But now you’re saying it’d be too hard to select all the cursors for the different components, so that doesn’t sound like a global cursor to me…


#13

I’ve “solved” the global mouse cursor issue by the singleton method I’ve described in post #3.
Then I was pissed that some component don’t respect setMouseCursor call.

Finally I’ve used the application with the new “global” cursor, and to find out that, after all, they don’t respect cursor in the “usual” widget interaction.
That is, when you move your mouse over a text box, you expect the cursor to change to indicate it’s an input field.
I’m back to case 1, if I want to set a global cursor, I must still allow specialized cursor for some component, or the user’ll never think it’s active.

For a coherent user interface, I can’t have the a custom cursor everywhere (with full color matching the application look and feel) and then that pop up to the white arrow or the text “|”'s cursor when hovering the component.
A global look and feel approach won’t be enough for this case.
I’m trying to figure out, with you, what would be the best method to still allow a customization of the mouse cursor, but while keeping the “basic” functionality of adaptive mouse cursor on some selected component.

Dynamic_cast aren’t slow when done coarsly. Whatever time they take, it’s slower than a compile time, type-based selection which is done in 0 instructions.
Where it is clearly worse is that a dynamic_cast algorithm is a pain to maintain (as you MUST put base class after child class in your if/else chain, you must remember to maintain this part of the code and so on…).

Sure, template are more difficult to write and understand, as, as soon as you’re upcasting to the base class the template system will fail.
But don’t put me wrong here, if every component implements:

private:
MouseCursor & getMouseCursorImpl() { return LookAndFeel::getMouseCursorFor(*this); } 

with the template system I’ve written in post #9, it works and resolve to 0 instructions when compiled in.
I would then only have to write:

template<> MouseCursor & LookAndFeel::getMouseCursorFor(Slider &) { return globalCursorForSlider; }
template<> MouseCursor & LookAndFeel::getMouseCursorFor(TextEditor &) { return globalCursorForTextBox; }
MouseCursor & LookAndFeel::getMouseCursorFor(Component &) { return globalCursorForComponent; }

which is, IMHO, both more efficient (no tests generated) and simpler than:

MouseCursor & LookAndFeel::getMouseCursorFor(Component & comp) 
{
      if (dynamic_cast<Slider *>(&comp))
      {
            // This won't work for a "class ImageSlider: public Slider" BTW, while the template-based approach would.
            return globalCursorForSlider;
      }
      // If I had done this:
      // else if (dynamic_cast<ImageSlider*>(&comp))  Then it won't work
      else if (dynamic_cast<TextEditor *>(&comp)) return globalCursorForTextBox;
      else return globalCursor;
}

Anyway, even with a LookAndFeel approach, I would be more than happy. It just that I find this sub-optimal and hazardous…


#14

Why wouldn’t you just do something like

[code]MouseCursor LookAndFeel::getMouseCursorFor(Component & comp)
{
MouseCursor m = comp.getMouseCursor();

if (m == normalCursor)
    m = newGlobalCursor;

return m;

}[/code]


#15

I should have edited my first post.
My initial problem was that it was not possible to set a global cursor globally easily without hacking the internal/peer code.

I’ve found a “solution” for the global cursor (your approach above would work BTW).
Then I’m facing another issue that appeared because the first one is “solved” that is a global cursor is not perfect as it fucks up the user experience (mouse cursor doesn’t change where we are used to see it change, like over a TextEditor or while dragging a slider).

In that case, the solution above doesn’t solve it. The only way for the solution to work would be to use a chain of dynamic_casts which, as I said in my previous post, is still a pain to maintain.
I’ve written in the previous post a method to “specialize” the mouse cursor selection based on component type not using dynamic casts, and easier to maintain.
I haven’t invented the idea, it’s called “double dispatch”.
I want the native code to select a cursor for a given component based on the component type, without having to modify each component code specifically.

A basic method would be to provide function overloading in LookAndFeel for each possible type, and the template code does just this, but you could do without, it would just requires writing a bunch of method:

MouseCursor & LookAndFeel::getMouseCursor(Slider & slider); 
MouseCursor & LookAndFeel::getMouseCursor(Component & slider) {  /* Default code */ return globalMouseCursor; }
MouseCursor & LookAndFeel::getMouseCursor(TextEditor & slider); 
MouseCursor & LookAndFeel::getMouseCursor(ImageSlider & slider);  // ...etc...

This still mean that every component need to have its own code calling this so the type isn’t lost:
In component.hpp

virtual MouseCursor & getMouseCursorImpl() { return getMouseCursor(); }

In any other “component”.hpp

MouseCursor & getMouseCursorImpl() { return LookAndFeel::getMouseCursor(*this); } // As *this is of the component type, the right method in LookAndFeel is called. Default LookAndFeel only provides the general case.

In componentPeer:

updateCursor() { return component.getMouseCursorImpl(); } 

#16

The template idea is appalling. Templated virtual methods are exactly the sort of semi-legal-c++ that should be avoided like the plague.

I don’t understand why you’re moaning about my idea being hard to maintain - if it seems that way, you must be missing the point. There are all sorts of tricks you can use to efficiently identify a component as being one that you’re interested in. E.g. make them all inherit from your own abstract ‘marker’ class and try casting the components to that. Or use a component property to mark them. Or scan up their parent hierarchy to see whether they’re inside one of your components, etc etc. Only a hacky implementation would be hard to maintain.


#17

While I disagree with you about the template solution, it offers the advantage of writing less, and writing optimal code (tests that are known statically at compile time will be optimized out by the compiler, that’s one of the initial benefit of template).
Yes templates bite, and yes template errors are subtil Proust’s prose. Here there is no boost-like template trick, BTW, it’s quite simple to understand.

I don’t think scanning the hierarchy at each mouse move isn’t lightweight either.
Using an empty /useless base class to simplify the dynamic_cast chain is what I call “hacky”, IMHO.

Anyway, to sum up, could you set up a working general getMouseCursor method in LookAndFeel, with a ref to a component in ?
Or do I implement something and send a patch to you for review ?


#18

I don’t understand it, and I’m pretty good at C++.

Seems like you’re talking about templating a virtual method, which would be impossible.

Your ideas about performance seem incredibly pessimistic… If you want to scare yourself, step through all the code that runs every mouse-move, and you’ll be horrified by how much is already happening! A few extra dynamic casts wouldn’t even be detectable!

I’ll add a sensible l+f method shortly.


#19

No the idea is that Component base class has a virtual method called “getMouseCursorImpl()” which, by default call “LookAndFeel::getMouseCursor(*this);”, so it’s likely a no-op.
Then as every component are derived from Component base class, they can either re-implement this method or not (which defaults to the simple function call).

If they do however, re-implement this method, the “this” inside the scope of the function is of the derived type, not the Component base type.
So the Slider::getMouseCursorImpl() { “this” here is of type “Slider*” not “Component *

Then they can do “return LookAndFeel::getMouseCursorFor(*this);” and this will call “static LookAndFeel::getMouseCursor(Slider&)” overload, and not “static LookAndFeel::getMouseCursor(Component&)” overload.
Please notice that here template aren’t used for anything here because I’m using static function overload instead.

Template comes into play when you want to use the “LookAndFeel” as an interface (so it’s not possible to write virtual method for every types anyway), in that case, templates provide the SFINAE (substitution failure is not an error) principle, that is what I’ve written before. In that case, instead of calling static function overload, you’ll call compiler generated templates.
What is good with them, is that you can specialize them without touching a single line of code in the component class.

So the code would become (in LookAndFeel):

template<class T>
MouseCursor & getMouseCursorFor(T & t) { return t.getMouseCursor(); } // Default is to call whatever cursor the component decided to use

Then, if you don’t do anything, LookAndFeel has a method that returns "component.getMouseCursor()"
So the

Slider::getMouseCursorImpl() { return LookAndFeel::getInstance()->getMouseCursorFor(*this); } will make the compiler generate a method "MouseCursor & LookAndFeel::getMouseCursorFor(Slider & t)"
Then if you write:

template<>
MouseCursor & getMouseCursorFor(Slider & t) { return globalMouseCursorForSlider; } 

Then instead of the compiler generated previous method, the specialized version will be called instead, meaning that all class below and including “Slider” base class will use “globalMouseCursorForSlider”.
And this, happens with NO runtime cost.


#20

The words “sledgehammer” and “nut” spring to mind…

I’ve added the l+f method for you now. Hopefully the extra CPU required to do all those dynamic casts won’t increase your electricity bill too much!