Skip to content

Explicit tool registration. #9941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Dec 6, 2017

@fariza This is more or less how I would bikeshed the thing...
The advantage is that if someone wants to override some other tool for their backend (say, Quit), they don't have to go fiddle with the global dictionary. Plus other arguments listed in #8712.

Perhaps an even better way to do this would be to store the tool classes as attribiutes (subclasses) of the respective FigureManager classes, as this will make inheritance automatic (i.e. gtk3agg will know to go find the tools on the gtk3 figuremanager).

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer mentioned this pull request Dec 6, 2017
@OceanWolf
Copy link
Member

I think I get what you mean, you see a problem with wanting the possibility of different default_tools for different backends.

I agree that playing around with the default_tools dictionary seems like a bad idea, but as far as I know, other options exist for going around this. For example the backend can remove a tool and then add a different one, and also in MEP27 I effectively redesigned the quit tool to fire an MPL closing event. The whole point of MEP22 and MEP27 were to standardise the backends making things simple. I think as long as we keep to that, we give the individual backends the maximum freedom to do what they want.

@@ -234,7 +234,7 @@ def remove_tool(self, name):

del self._tools[name]

def add_tool(self, name, tool, *args, **kwargs):
def add_tool(self, tool_name):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't remove the *args, and **kwargs, those are really useful when adding custom tools.
Sometimes you need to pass initialization arguments when manually adding tools

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, restored it


tool_obj = tool_cls(self, name, *args, **kwargs)
self._tools[name] = tool_obj
tool_obj = tool_cls(self, tool_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same comment for the *args and **kwargs

@anntzer
Copy link
Contributor Author

anntzer commented Dec 6, 2017

Also pushed removal of sender and data kwargs.

@fariza
Copy link
Member

fariza commented Dec 6, 2017

@OceanWolf how do you see this?
I tested it and seems to work fine.

Pros:

  • I like how it simplifies things.

Cons:

  • I don't like the removal of the rubberband as a tool, but I agree that it is not used anywhere else.
  • The end user now has to do one extra action and is to register the tool, even if he is adding it manually to the toolmanager.

@anntzer I know it is an anti-pattern but, can we do something to eliminate the need to register the tool when adding it by hand (like in the example)?

@anntzer
Copy link
Contributor Author

anntzer commented Dec 6, 2017

I agree always having to register is not super elegant; I'm totally fine with allowing passing a Tool instance to add_tool directly (and store the name of the tool as an attribute of the class, so this way thusly added tools also have a proper signal name, and there's no need to mention the name explicitly when registering the builtin tools).

I'm keeping the inheritance mechanism private for now because I hope to make it work together with the _Backend class inheritance mechanism.

I still think that if we're worried about rubberbands they should become freestanding functions, to me it's very clear they were previously shoehorned into the wrong API.

@anntzer
Copy link
Contributor Author

anntzer commented Dec 6, 2017

Pushed the changes suggested above.

@fariza
Copy link
Member

fariza commented Dec 6, 2017

why change to canvas instead of backend for tracking?

@fariza
Copy link
Member

fariza commented Dec 6, 2017

Now there is another problem, we can not add twice the same tool, this is sometimes useful when passing arguments at initialization (*args, **kwargs of add_tool)

@anntzer
Copy link
Contributor Author

anntzer commented Dec 7, 2017

Switching to registering on the canvas class allows one to rely on canvas inheritance rather than having to manually declare the inherited tools.

I think the issue with having the same tool twice was already present before (in the sense that the tool_trigger_$foo event is overloaded). I agree that this should be fixed somehow, but I don't think this PR changes anything?

@fariza
Copy link
Member

fariza commented Dec 7, 2017

The name was defined when calling add_tool so we could give different names to the same tool.

A combination of both could work fine.
What about add_tool(self, name, cls, *args, **args) where name is striclty string, and cls is strictly a class derived from ToolBase.
We could use the tools.register just to fill the dict of available tools for default initialization as you were doing (I like that approach)

And for the user in "manual" mode, we force him to use the name and the cls

@anntzer
Copy link
Contributor Author

anntzer commented Dec 7, 2017

I guess a possibility would be to change the signature of ToolBase to Tool(toolmanager, name=None, ...) (with name effectively defaulting to the class name), and use tool.name as the key.

@fariza
Copy link
Member

fariza commented Dec 7, 2017

What is the advantage of this vs forcing to pass the name?

@fariza
Copy link
Member

fariza commented Dec 7, 2017

After a little thought, maybe is not a bad idea.
As long as the default tools, we provide a name that is not the classname I'm fine with that solution

@OceanWolf
Copy link
Member

This seems rather ugly to me. I haven't had a look at the code yet, but "name" of a tool exists as a label for identification by the ToolManager. By passing a name to the tool, you then give the tool the job to check with the tool manager whether if it can take that name or not, and if not, what then? We now have an object that has failed to set up properly because the given attributes, name and toolmanager conflict with one another.

I would keep name out of Tool, as it does not belong to the Tool, but the relationship between the ToolManager and the Tool.

@anntzer
Copy link
Contributor Author

anntzer commented Dec 7, 2017

Haven't thought too much about this but I think it's fine to use @fariza's proposed signature too.

@fariza
Copy link
Member

fariza commented Dec 7, 2017

@OceanWolf right now the tool name is given at the add_tool call.
Before the tool is actually instantiated, it is the toolmanager that decides if the name is valid or not.
The tool keeps a reference to it's name

@fariza
Copy link
Member

fariza commented Dec 7, 2017

Just to say, that sometimes tools need to know their identifier

@OceanWolf
Copy link
Member

Why? I don't see why a tool should ever need to know what we call it. It makes no difference to the function of a hammer whether it gets called a hammer, or a hitty thing, so long as we know what we call it. The purpose of a tool lies in doing something. That said, if we already have it that way, I don't mind changing it.

Also, I think I come around now to the idea of using the canvas class to store the tools used, but I think the PR here looks way too complicated for such a simple change. Why don't you just move default_tools from global scope, into the FigureCanvasBase and then have the init function of inherited classes operate on the dict? When creating an instance of the FigureCanvas the MLO will happen automatically without you having to explicitly search through the MLO, plus you get way more custom functionality because you essentially implicitly move your register_tool method out of global scope and into the __init__ method of canvas.

@fariza
Copy link
Member

fariza commented Dec 8, 2017

@OceanWolf right now the name it is only used by the cursor tool, it needs to know the name of the tools to connect to their trigger event, and this is done using their name. But I agree with you (the hammer convinced me)
One solution for this, and remove the name completely from the tool, would be to modify the add_tool_event and pass the name of the added tool (makes sense).
@anntzer do you want to include this change in this PR? if you prefer I can make a smaller one (easier to approve) with just that change

Regarding the filling of default_tools
I prefer the use of the backend for tool identification (seems more natural to me), but I don't see anything against using the canvas.
As I understand @OceanWolf goes towards a solution that implies filling the dict on canvas init method instead of a separated "Register" call near the tool declaration.
This seems simpler but less explicit.
If this is a viable solution, why not the figuremanager instead of the canvas? All the toolmanager/toolbar/statusbar initialization stuff happens in the figuremanager, so the filling of that dict could happen here aswell

@anntzer
Copy link
Contributor Author

anntzer commented Dec 8, 2017

May be easier to do all the refactoring in a single PR I think.

@OceanWolf
Copy link
Member

I would rather have smaller PRs, it becomes easier to see the logic, to see what gets changed and why. I have been toying with the idea of getting rid of breaking of chunks of the main MEP27 PR, but the pain of the rebasing has put me off so far given how bits of the PR have changed as time has progressed. Maybe I will just bite the bullet over the holidays or something.

And yes, I see why you do it, for tool admin purposes, but that shouldn't require too much fiddling to change. Also as a tool admin thing, I see that as more the responsibility of the ToolManager than the tool, so either make SetCursor as the exception and have it better interface with ToolManager with name getting passed as an event; and/or move this event connection logic completely to the ToolManager.

@OceanWolf
Copy link
Member

As for the default_tools location, yes, you understand me correctly. The problem with putting it in FigureManager lies with the FigureManager classes getting replaced quite soon (hopefully) with MEP27 with no inheritance.

I dislike having all of these methods and variables around to determine something. It spreads out the code too much to figure out what what has happening, keep code simple to read, if something happens with the Canvas class, I expect to find it in the canvas class in the code. Having tool registration in backend_tools.py seems wrong to me. This file exists as a compilation of tool classes, not managing them. Any logic outside of what an individual can do tool I think should go elsewhere.

I go with KISS, easier to debug and spot mistakes. Also going back to the logic before about a tool not knowing its name, this will then mess up the register tools function. Going back to a dict of {name: class} will correct this.

@fariza
Copy link
Member

fariza commented Dec 8, 2017

I am not convinced that default_tools dictionary has to be moved somewhere else. Inside backend_tools.py seems just logic to me. But if the implementation makes it easier to read and to debug I'm in!

Regarding the name argument in tool.
We just remove it and modify the tool_added_event, it just makes sense for this event to contain the name of the tool being added, actually for all events, it just makes sense to contain the name of the tool

@OceanWolf
Copy link
Member

I don't see any other way for this PR to accomplish want it wants to accomplish once we have moved name, but yes, lets do the name thing in a separate PR and then see what we have left with here.

@fariza
Copy link
Member

fariza commented Dec 10, 2017

@anntzer in case you didn't have time, here is a small PR removing the name #9966
If you feel, rebasing is going to give you more problems than reimplementing it over your PR, please feel free to reject it.

@fariza fariza mentioned this pull request Dec 10, 2017
6 tasks
@anntzer
Copy link
Contributor Author

anntzer commented Dec 10, 2017

No worries, please go ahead.

@anntzer
Copy link
Contributor Author

anntzer commented Dec 10, 2017

Although I guess the other question is, do tools really need to have names to start with?
As you mentioned, this is only used to that the cursor tool can register onto trigger events for other tools. But this can be implemented in at least two different ways:

  • the individual tools can call themselves canvas.set_cursor or toolbar.set_cursor (or wherever we want to store this method)
  • or, there could be a single tool_trigger_event that passes the Tool object as one of its arguments.

@fariza
Copy link
Member

fariza commented Dec 10, 2017 via email

@OceanWolf
Copy link
Member

We need name to identify the tool, we can have multiple versions of the same tool at the moment, that makes it easy to create several tools from the same class initialised with different parameters.

@fariza
Copy link
Member

fariza commented Jan 4, 2018

I am +1 with registering the tool inside the canvas, @OceanWolf can we move ahead with this? No matter what we do, it will never be perfect, put it is pretty good as it is.

@fariza fariza mentioned this pull request Jan 4, 2018
6 tasks
@anntzer
Copy link
Contributor Author

anntzer commented Apr 12, 2023

Superseded by #20990.

@anntzer anntzer closed this Apr 12, 2023
@anntzer anntzer deleted the toolregistry branch April 12, 2023 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants