8000 Navigation indepenent of toolbar. by OceanWolf · Pull Request #11 · fariza/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Navigation indepenent of toolbar. #11

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

Conversation

OceanWolf
Copy link

This PR finishes the job of making NavigationBase independent from ToolbarBase as indicated by matplotlib#3652 (comment)

I.e. Navigation should not deal with Toolbar specific data, such as group and position, but only act as a manager of tools. This will only become a further nightmare when context menu's etc arise. Each Toolbar/Menu/other UI should encapsulate this data and allow for custom UI representations of these tools, especially when one wants to create menus on the fly depending on context.

@OceanWolf OceanWolf force-pushed the navigation-by-events-toolbar-independence branch from a78cfe5 to 2605f99 Compare February 16, 2015 17:19
@fariza
Copy link
Owner
fariza commented Feb 16, 2015

The one thing that really bothers me, is that we have to manually add tools to navigation and toolbar independently.
And to do it interactively becomes a problem

@OceanWolf
Copy link
Author

I know, I may have a solution for that, but I just wanted to get this off the ground for now.

The way it stands at the moment though feels too heavy handed and will get very messy when you want to do very little more.

I would much prefer the flexibility of this, then the simplicity of one less line of code... but perhaps, just perhaps we can have both.

@fariza
Copy link
Owner
fariza commented Feb 16, 2015

The simplicity is not only for the static code that goes into MPL, it is for the user that add tools on the fly.
The OO interface, has to be clean and simple, look at the example
fig.canvas.manager.navigation.add_tool('List', ListTools) is not too simple, but still pretty understandable.
Even if you want it in the toolbar you can do just (with variables to simplify)
navigation.add_tool('List', ListTools, group='io', position=-1)

What you are proposing is that the user has to do an additional (not intuitive)
toolbar.add_tool(navigation.get_tool('List'), 'io', -1)

And, if we go that way, why do we need a tool-added-event?

@OceanWolf
Copy link
Author

I know you were speaking about the user code, MPL code I don't think matters so long as it runs smoothly and one can debug and add features to it easily enough.

Speaking personally, it took me ages to figure out I had to specify a group. It makes sense to me that if I want to use a tool, I add it to, (hopefully) ToolManager (now that NavigationBase doesn't get extended, it doesn't require the Base suffix, and that it stores tool beyond the navigation group); and that if I want to a 8000 dd the tool to the Toolbar I add it to the toolbar.

It took me ages to trace where the decision gets made about whether it goes in the toolbar, and I eventually found it in the backend specific code, the last place I would have thought of looking for it.

Then comes the problem of why having more then one GUI tool interface (I count subtoolbars as one toolbar), what if I want tools A,B and C in the toolbar, but E,F,A and B in a right click menu, in that order, it becomes impossible. Also what if I want one tool for whatever reason to appear in the toolbar twice but under different groups, again impossible.

Here I can do:

toolbar_left.add_tools(toolbar1_tools)
toolbar_right.add_tools(toolbar2_tools)
...
context_menu.add_tools(toolbar1_tools + toolbar2_tools)

Where toolbar1_tools and toolbar2_tools may well have tools common to both. But I cannot do this under the current setup, primarily because it does not follow OOP design. It does an all or nothing approach, either a tool exists in all GUI containers or None at all.

@fariza
Copy link
Owner
fariza commented Feb 16, 2015

OK, I'm almost convinced.
Make the toolbar.add_tool to call navigation.get_tool, so we can pass a string, and the position argument to be relative to the group

@OceanWolf
Copy link
Author

Hehe and yay! I kind of just did that, and a bit more...

Confused about the position argument though, no changes in group code, just straining out the Toolbar options from the ToolManager options.

@OceanWolf
Copy link
Author

Did I add too much extra code just now?

Oh, and I think I have a couple of small fixes to make sure the full flexibility works.

for position, tool in enumerate(grouptools): 8000
self.add_tool(self.navigation.get_tool(tool), group, position)

def add_tool(self, tool, group, position):
Copy link
Owner

Choose a reason for hiding this comment

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

Here the tool can be a string, what if we just one to add one single tool on the fly?

@OceanWolf OceanWolf force-pushed the navigation-by-events-toolbar-independence branch from 2186529 to 7683b0b Compare February 16, 2015 19:18
for position, tool in enumerate(grouptools):
self.add_tool(tool, group, position)

def add_tool(self, tool, group, position):
Copy link
Owner

Choose a reason for hiding this comment

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

I like that it can be auto-added to navigation, but in that case, put the name as kwarg, when do you see it getting a tool object?

Copy link
Author

Choose a reason for hiding this comment

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

The only thing with using a kwarg means that we can't auto-add from ToolbarBase.add_tools(). I.e. sometimes pass a name, sometimes not... but I don't mind that if you don't, only a helper method.

Copy link
Owner

Choose a reason for hiding this comment

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

the test inside can make mandatory the kwarg name if the tool is a class

Copy link
Author

Choose a reason for hiding this comment

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

Hang on, now that gets weird:
add_tool(self, name, group, position=-1, tool_class=None)

Copy link
Owner

Choose a reason for hiding this comment

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

add_tool(self, tool, group, position=-1, name=None)

Copy link
Owner

Choose a reason for hiding this comment

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

tool can be either a string or a class

Copy link
Author

Choose a reason for hiding this comment

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

Because we pass the name to Navigation.get_tool, so we don't want to give that as the kwarg...

Sometimes, as I did here, I like to use tool, as an abstract word, it could mean name, or it could mean the object, or the class. So now we say no object, we definitely need a name, but we don't necessarily need a tool_class

Copy link
Owner

Choose a reason for hiding this comment

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

A couple of try/except blocks to let navigation give whatever it has with that reference, if not, then it means is a class, and we try again forcing the name kwarg

@fariza
8000
Copy link
Owner
fariza commented Feb 16, 2015

I prefer the signature something like toolbar.add_tool(tool, group, position=1, name=None), the idea of tool being a string and object is fine, but adding a tuple is a little bit too much.

@OceanWolf
Copy link
Author

Okay, I think I have sorted it all out, just one last pesky bug to nail down tomorrow that I have just found (I think in master) that I doubt has ever been exposed, until now...

@fariza fariza mentioned this pull request Feb 17, 2015
@OceanWolf
Copy link
Author

Phew, nasty bug found and fixed! I think this bug has existed since the creation of the mpl CallbackRegistry... (and I also improved the efficiency of the Garbage Collector at the same time). Now back to tools and this PR.

P.S. Do you get messages every time I push commits to this branch? Or only when I write a message?

@fariza
Copy link
Owner
fariza commented Feb 17, 2015

I get messages when you write a message and when you submit a PR but I don't remember getting anything with the commits

@fariza
Copy link
Owner
fariza commented Feb 17, 2015

Don't do the bugfix here, make another PR for mpl, and when merged there I will rebase.

@OceanWolf
Copy link
Author

Okay, but I think I have to enact some kind of magic to do this as I forked from your branch...

@OceanWolf OceanWolf force-pushed the navigation-by-events-toolbar-independence branch from 0040d25 to 1a4ac30 Compare February 18, 2015 16:38
@OceanWolf
Copy link
Author

Okay, all fixed up I think. and it even works even without the CallbackRegistry fix (though it doesn't hurt to have it, it just means slightly more work for the CPU and the memory in this case).

Now, shall I copy in the OrderedDict recipe into this branch/PR? I thought plain dict for backend_tools.tools and an OrderedDict for backend_tools.toolbar_tools.

Also do you want me to squash some/all of these commits?

@OceanWolf
Copy link
Author

P.S. Can't remove tool_added_event because it gets used by SetCursorBase

@OceanWolf OceanWolf force-pushed the navigation-by-events-toolbar-independence branch from 1a4ac30 to 5dd6099 Compare February 18, 2015 16:57
type given by tool, and use this as the name of the tool.
"""
t = self.navigation.get_tool(tool)
if t is None:
Copy link
Owner

Choose a reason for hiding this comment

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

Navigation will raise a warning, maybe use a try: block here to prevent that warning

Copy link
Author

Choose a reason for hiding this comment

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

that doesn't work, though just commited a fix that does work. Also I just learnt that warnings.warn only gives a specific warning once...

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

Almost there, just I want your confirmation that this seems right to you. Isn't it too many different configurations for the same method name (two different methods, but same name)

To correct my resume!!
in navigation

  • add_tool(name, str) adds a tool searching for the class
  • add_tool(name, cls) adds a tool passing a class

So the name comes first then the tool (string or cls)

in toolbar, when the tool object already exist in navigation

  • add_tool(obj, group) adds a tool object belonging to navigation to a given group, the name is already setted in the tool
  • add_tool(name, group) adds a tool searching for name in navigation

When the tool doesn't exist in navigation

  • add_tool(cls, group, name=str) adds a tool, passing the class to navigation for instantiation
  • add_tool(str, group, name=str) adds a tool, passing a string to navigation for class search

@OceanWolf
Copy link
Author

You forgot to add

  • add_tool(obj, group, name=str) adds a tool, passing the class obj.__class to navigation for instantiation.

But before we talk about too many configurations, I think we should look at a resume not built on types, because python doesn't work as a typed language... it uses duck-typing, therefore as a resume, I would write:

In Navigation:

  • add_tool(name, class_like) creates and registers a tool of class given by class_like with an id given by name.

In ToolCollection:

  • add_tool(tool_like, group) adds the tool_like registered in navigation
  • add_tool(class_like, group, name=str) uses class_like and name to register tool with navigation and adds the tool.

This raises the question whether class_like should include an object, with object.__class__ or not. If it does, then I think that should go in navigation.add_tool, otherwise I remove that part from ToolContainer.add_tool.

Also, if we try to register and add the tool and name already exists, should we just add that name? Using the same logic as the CallbackRegistry, I think if name already exists when one adds a tool to navigation.add_tool, it should just return the tool.

Finally we can now ask the question if it looks too complicated, i.e., whether we should remove the auto-register logic from ToolContainer.add_tool and create ToolContainer.register_and_add_tool which can give the warning tool already registered with navigation.

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

I am just afraid that it is prone to confusion.
I like your idea of removing the auto-register functionality, just to simplify things, it can be added later on. I don't think a method register_and_add_tool is convenient for now.
If this gets merged, we will have one full release cycle to add helper methods like this one.

In conclusion, what do you think of limiting the methods as follow
in navigation

  • add_tool(name, class_like)

in toolbar

  • add_tool(tool_like, group)

@OceanWolf
Copy link
Author

Fine with me.

For the other question, should a tool instance count as class_like? Or should we leave that up to the user to type navigation.add_tool(name, tool.__class__)? If it does then I guess I would add this to NavigationBase._get_cls_to_instantiate given that I would say that this method has the responsibility of returning a class from a class_like thing.

As I have already made Navigation.add_tool return the tool it adds in this PR, I will make it also return the tool if it already exists as I said above (or leave it for a separate another PR?).

Then comes the OrderedDict, as I said above, I think tools.tools only needs a plain dict, it stores no ordered information, tools.toolbar_tools on the other hand does store ordered information, and thus I will convert them. I should still do this in this PR right?

On an aside, does something exist for documenting stuff like x_like? Either point to the function that deals with it, i.e. get_tool in the case of tool_like and _get_cls_to_instantiate for class_like. Atm I can think two options, either a see method ..., or an %(x_like)s using @docstring.dedent_interpd.

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

I prefer if tool instance doesn't count as class_like, so no modification needed for navigation._get_cls_to_instantiate

in navigation.add_tool If the tool already exist, a warning must be issued, but I think it is ok to return the existing tool.

tools.tools a dict....
tools = {'zoom': ToolZoom, 'home': ToolHome', ... name: class_like...} seems perfect to me

For tools.toolbar_tools this is too little to justify the OrderedDict, remember that this code will remain there for a long time, until the minimum version will be 2.7, and even then, we have to clean it up, and make sure, nobody is using it directly etc....

For the docs, are you following https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt I am not sure the correct way to do it, so far, I just use a cls or int in the argument definition

@OceanWolf
Copy link
Author

Nice, that answers one of my questions, everywhere I see name : String, but we should write name : str according to this.

I had been partially working to that, I mainly have used http://matplotlib.org/devel/coding_guide.html, falling back to http://matplotlib.org/devel/documenting_mpl.html#documenting-matplotlib as it seems out of date...

@OceanWolf
Copy link
Author

There we go, I think that looks good now, I especially like the neatness of the dict :).

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

Did you check that the documentation builds correctly with those strings?

@OceanWolf
Copy link
Author

No, I think I followed numpy spec though. How should I check that?

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

Yep, travis is giving problems, difficult to track if documentation fails

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

Also, is always a good idea, to see how it does look.

@OceanWolf
Copy link
Author

Yes, but how do I do it? Does one command do it all?

@fariza
Copy link
Owner
fariza commented Feb 19, 2015

yes, in doc python make.py html

toolbar_tools = [['navigation', ['home', 'back', 'forward']],
['zoompan', ['zoom', 'pan']],
['layout', ['subplots']],
['io', ['save']]]
"""Default tools"""
Copy link
Owner

Choose a reason for hiding this comment

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

This docstring belongs to tools add one for this variable

@OceanWolf
Copy link
Author

Deleted those comments, realised what went wrong.

@OceanWolf
Copy link
Author

Okay, running build.py html I get 24 warnings, none from here, though, however looking through the generated docs I do see one or two mistakes.

How do I see if I get warnings/errors from here, i.e. once I have fixed them all?

@fariza
Copy link
Owner
fariza commented Feb 20, 2015

I don't have much experience with that appart from Travis fails on my last commit.
If you don't see errors, and the generated html looks fine to you, go ahead with your last changes.

I mean, errors related to your changes

@OceanWolf
Copy link
Author

But you just Travis gave problems on this...

@OceanWolf
Copy link
Author

The only ugly doc I see comes under backend.tools and backend.toolbar_tools, it prints the entire dictionary in the api as raw text...

fariza added a commit that referenced this pull request Feb 20, 2015
…dependence

Navigation indepenent of toolbar.
@fariza fariza merged commit d5df5df into fariza:navigation-by-events Feb 20, 2015
@fariza
Copy link
Owner
fariza commented Feb 20, 2015

I agree that the dosctring for variables like that is not pretty. I have been looking for a way to fix it, but so far nothing.

@OceanWolf
Copy link
Author

Yay, merged! 👯 But I thought you were going to let me squash those commits first ;).

@fariza
Copy link
Owner
fariza commented Feb 20, 2015

Ups... I forgot, anyway, I am pretty sure, the PR is going to be squashed before merged in master

@OceanWolf OceanWolf deleted the navigation-by-events-toolbar-independence branch February 24, 2015 23:47
fariza added a commit that referenced this pull request Sep 22, 2016
returning nan from u_I_mean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0