Skip to content

Add "bring window to front" functionality #596

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
mdboom opened this issue Nov 17, 2011 · 60 comments
Closed

Add "bring window to front" functionality #596

mdboom opened this issue Nov 17, 2011 · 60 comments
Milestone

Comments

@mdboom
Copy link
Member

mdboom commented Nov 17, 2011

It would be nice to have a method on the figure object that would bring its GUI window to the top of stack -- if this is possible -- in a backend-neutral way.

(Suggested by Marshall Perrin on the PyInt mailing list)

@embray
Copy link
Contributor

embray commented Nov 17, 2011

Hmm.. I've been wanting to get into development on matplotlib's GUI features, and this seems like a simple enough issue to start with (depending, of course, on the backend).

Would it be a logical approach to add a couple methods to FigureManagerBase? I'm thinking maybe:

  • FigureManagerBase.bring_to_front()
  • FigureManagerBase.send_to_back()

and go from there?

@mdboom
Copy link
Member Author

mdboom commented Nov 17, 2011

Yes -- that makes sense.

Then there would need to be identical methods on the Figure object to delegate to these (since that's how most users will probably interact with it). A pair of pyplot methods to act on the current figure would also be useful. But that's just gravy once the various gui API hooks have been added.

@embray
Copy link
Contributor

embray commented Nov 21, 2011

I was playing around with this a bit and it turns out it's a bit trickier than I expected on Linux. This is because I can call the appropriate methods on each backend (GTK+, Tk, etc) to bring a window to the front. But what happens next depends entirely on what the window manager feels like doing, and in my case it doesn't usually want to actually bring them up.

I could fairly easily write some functions to talk directly to the window manager (at least as long as it follows the EWMH standard. But I'm not sure it's worth the effort.

@sparks
Copy link

sparks commented Mar 30, 2012

This bug is a big peeve for me. I'm gonna try my hand at implementing this the mac os x backend, however I don't know much about matplotlib structure for the moment so I'm open to suggestions about how to best approach this.

Also assuming this was implemented shouldn't the show() command always bring the window to the front right away?

@embray
Copy link
Contributor

embray commented Mar 30, 2012

@sparks See if you can stick to my suggestion of FigureManagerBase.bring_to_front(). send_to_back() I would consider optional. This should actually be quite doable on OSX--the only reason I didn't do that is that I don't normally work on an OSX machine.

I threw my hands up over the annoyance of different window managers in Linux doing different things, but that shouldn't be as much an issue on OSX. I do still plan on coming back to this for Linux sometime though, as it's an annoyance for me too.

And I agree that show() should bring the window to the front. Not sure how others feel about that.

@pelson
Copy link
Member

pelson commented Sep 2, 2012

I would love to see somebody tackle this issue - it would be fantastic to have a cross platform, cross toolkit interface which "just works".

@efiring
Copy link
Member

efiring commented Sep 2, 2012

@iguananaut, one does not always want show to bring windows to the front; it should be configurable.

@dmcdougall
Copy link
Member

I'm an OS X user and I'm fairly familiar with objective-c, and with the OS X window manager it's fairly easy to give a window focus (if the window manager is able to give it focus). Window managers in general, though, may choose to ignore any message passed to them, depending on what they're currently doing. They also certainly do not behave uniformly. X11 may behave differently from GNOME and KDE. I'd be happy to look into this a little but. A lot of toolkits (GTK, TK, QT) have a function that pings the window manager and says, "Hey, I'm important, give me focus!" But as I've said, this functionality will almost certainly not work uniformly across window managers. And I have absolutely no clue and how Windows deals with anything.

The question is, do we want to implement something that is potentially going to be temperamental in its behaviour? Honestly, I'd rather just rely on the user the click the window to give it focus.

@efiring
Copy link
Member

efiring commented Sep 3, 2012

I think that if each of the gui toolkits has a reasonably similar set of functions for (1) raising a window within the application--that is, relative to other windows in that application, and (2) requesting that the window manager raise the window, and (3) requesting that the window manager give it focus, then providing a gui-neutral api in mpl would be helpful. But I would not want to see this get complicated, or impose possibly unwanted new behavior.

@dmcdougall
Copy link
Member

Personally, I find it inconvenient when a window unexpectedly steals focus. Having said that, it seems that given the discussion in this issue, it would be the user's choice to initiate such behaviour. I'm happier with this than always stealing focus. Moreover, I did some Googling. It is the case that Windows never lets an application steal keyboard focus, but instead makes the taskbar flash. Not that there's really anything anybody can do about it, implementing the ideas discussed here would result in inconsistent behaviour across operating systems.

@embray
Copy link
Contributor

embray commented Sep 5, 2012

@efiring The point of this issue was not that windows should automatically be brought to the front--just that matplotlib should expose an API for doing so. That way if the GUI window pops up under my terminal I can just type a quick command to bring it forward. Or, more importantly, for applications that use matplotlib it would be nice to be able to do this.

I would say then that it's the responsibility of an application using matplotlib to provide users with an option to not allow windows to steal focus. All matplotlib needs to do is provide the appropriate API. I think this should still be done, but it turns out to be surprisingly tricky to get consistent across platforms. OSX should be fairly straightfoward, but X11 is tough. As @dmcdougall wrote all the relevant GUI toolkits have a way of doing this, but the actual resulting behavior is up to the whims of the window manager.

What we can do for X11 is bypass the window manager altogether and communicate with the X server via EWMH protocol--a standard protocol for window managers supported by most window managers still in common use. If we use EWMH it should be possible to do this without confusing the active window manager, so long as it's EWMH-compliant. Otherwise fall back on doing the best the GUI toolkit can provide and not guarantee the results. I could give that a try, but it would be up to others to provide the OSX and Windows implementations.

@embray
Copy link
Contributor

embray commented Sep 5, 2012

After a quick experiment, the EWMH approach works very well with metacity--the default WM for GNOME. Would have to experiment with more, but this is promising considering how widely used metacity is. Is matplotlib allowed to use ctypes?

@efiring
Copy link
Member

efiring commented Sep 5, 2012

We have never used ctypes, as far as I know. In what way would you propose to use it?

I would have expected that the way to implement this API in mpl would be via each toolkit, not by bypassing the toolkits and going straight to X or OSX or Windows.

@embray
Copy link
Contributor

embray commented Sep 5, 2012

I would prefer to do it through the GUI toolkits, as a matter of preference if not policy. If it is a matter of policy then so be it--it makes things pretty easy. When I experimented with this on GTK and Tk I found frustratingly inconsistent results, hence the desire to bypass the GUI toolkits in this case. For example, I simply could not get it to work with Tkinter, where the relevant method is "lift". The problem is that Tk will go ahead and configure the window stacking order, but then the window manager (in this case metacity, but ymmv) happily ignores it. GTK on the other hand uses the appropriate EWMH messages which the WM does obey, and so it gives better results. I guess the fault lies with the toolkit in this case.

Another open question would be: Do we want "bring_to_front()" to just raise the window in the stacking order, or should it also change the focus? I would tend to prefer, "No, don't change the focus."

@efiring
Copy link
Member

efiring commented Sep 5, 2012

We need to keep focus separate and independent from position.

@wigging
Copy link

wigging commented Feb 26, 2015

Please fix plt.show() on the Mac to bring figure windows to the front of the screen. On small screens the editor can take up the whole window which completely hides the plot figures. Another annoyance on the Mac is that multiple figure windows appear directly on top of each other - hiding the other figures below the top most figure.
screen

@embray
Copy link
Contributor

embray commented Feb 26, 2015

@wigging That's a related but separate issue from this one, IMO.

@krischer
Copy link
Contributor

krischer commented Apr 4, 2015

@embray and others as well: Is there an another issue about it? I cannot find it.

I strongly believe that plt.show() in non-interactive mode should always bring the window to the front. At least on OSX using the qt4agg backend it always hides behind other windows which is a big annoyance. Using the macosx backend one cannot event alt+tab to it but has to search the window in expose.

Is there any reason this is not done by default? I've been manually patching my matplotlib installations for a long time now...

@efiring
Copy link
Member

efiring commented Apr 4, 2015

@krischer I think the problem is simply that no one has come up with a PR to implement what we need:

  1. An API for bringing the window to the front; and separately for controlling whether it grabs focus.
  2. An implementation of this for each GUI backend, using the API of its native toolkit.
  3. Addition of default settings for bring-to-front and grab-focus to the rcParams configuration mechanism.

Such a PR would be universally welcomed, I believe. I suspect that even a PR that started with a single backend would be accepted, if there were reason to believe that most of the other backends could be supported in a similar way.

@OceanWolf
Copy link
Member

I thought about implementing something like this in MEP27, as it separates out the Window logic.

I agree that a call to window.show() should always raise a window to the top, by the definition of the word, if the window hides at the back where no-one can see it, then you haven't "shown" it.

At the moment show (at least on GTK3) only does something (showing the window and bringing it to the top) on its first invocation, successive calls to show do absolutely nothing. I see no reason why it shouldn't also bring the window to the top on successive calls (apart from BC).

In the end I decided against doing this in MEP27 as I thought it simplest to work as just a refactor, and leave it for afterwards, but if people feel otherwise I would gladly add it as part of MEP27, less work for me later (i.e. touching all the backends).

@wigging
Copy link

wigging commented Apr 4, 2015

@krischer How are you manually patching matplotlib to bring the figure windows to the front? I agree that this should be a default feature. It's very annoying when I can't find my plot window 😠.

@krischer
Copy link
Contributor

krischer commented Apr 4, 2015

@efiring, @OceanWolf I unfortunately don't know enough about matplotlib's backend system to make a qualified statement but I'll try to have a look in the next weeks if I find some time.

@wigging Very simple and probably stupid. It works for the qt4agg backend and I guess the qt5agg one as well. I also could not notice any side effects with some simple Qt based GUIs I have.

diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py
index 8e72a08..fcbce44 100644
--- a/lib/matplotlib/backends/backend_qt5.py
+++ b/lib/matplotlib/backends/backend_qt5.py
@@ -543,6 +543,7 @@ class FigureManagerQT(FigureManagerBase):

     def show(self):
         self.window.show()
+        self.window.raise_()

     def destroy(self, *args):
         # check for qApp first, as PySide deletes it in its atexit handler

@OceanWolf
Copy link
Member

@krischer yes, I wanted to do exactly that :). Also check out my MEP (https://github.com/matplotlib/matplotlib/blob/master/doc/devel/MEP/MEP27.rst), #4143 to see what I referred to above.

As one of the goals in MEP27 lies in standardising the API, I think this makes for the ideal time to do it 😄.

@gsonnenf
Copy link

Yes, please make this feature.
This works for Tk on Mac (but not on qt):
fig.canvas.manager.window.attributes('-topmost', 1)
fig.canvas.manager.window.attributes('-topmost', 0)
This works for Qt4 on Mac (but not on tk):
fig.canvas.manager.window.activateWindow()
fig.canvas.manager.window.raise_()

and I still cant figure out how to bring the window to the top if using the matplotlib.use('MacOSX').

@efiring
Copy link
Member

efiring commented Nov 17, 2015

From my standpoint as a user, I think this is now the single biggest barrier to smooth interactive work with matplotlib. Having to go hunt for the figure window every single time one makes a plot is a terrible distraction. If this problem has to be solved piecemeal, one backend and platform at a time, in a messy fashion, so be it. Better that than no solution at all. Qt might be a good first target, with Tk a close second priority.

@efiring
Copy link
Member

efiring commented Nov 17, 2015

My strong preference for default behavior would be for the window to be raised, so that it is fully visible, but not activated. I would rather let the user click it to activate it. The reason is that if it is raised and activated, it is too easy for it to intercept keystrokes that were intended for another window.

@efiring
Copy link
Member

efiring commented May 8, 2016

Yes, this issue is not going to be reopened; the reference to this one in #6378 is adequate for keeping history.

QuLogic pushed a commit to QuLogic/matplotlib that referenced this issue Oct 16, 2016
tkagg: raise each new window; partially addresses matplotlib#596
@Olsthoorn
Copy link

I found this answer for OSX

fig.canvas.manager.window.raise_()

@Olsthoorn
Copy link

The figure window(s) is/are probably just behind the spyder or editor window. The simplest solution when using a Mac is learning its trackpad gestures (see system preferences/trackpack). "App Exposé" is the one you need. This makes all windows visible, with one move of your fingers on the trackpad. Then click the figure window to raise it. In a python script/program, it works best when you use plt.show() only once at the very end. This ensures that the program is not blocked by plt.show() before finishing, as the program waits until you actually delete the figure. You may print a message just before plt.show() reminding the user and yourself where the plots are. Notice that all figure windows are raised to focus with a single click on the one that's visible. For me this is a convenient solution to the problem.

@wigging
Copy link

wigging commented Mar 9, 2017

@Olsthoorn I moved away from using plt.show() and instead use the following approach which makes the figure window on Macs appear in front of other items on the screen:

plt.ion()
plt.close('all')

plt.figure(1)
plt.plot(x, y)
plt.grid()

@mbrennwa
Copy link

I know that this is an old (and long) thread, but I seems to me like this went all wrong (I hope I am just not understanding things correctly). I actually DO NOT WANT new figure windows to pop to the front. I liked it the way it was before the change (see here). So what is really needed is to make this behavior more user-configurable. Is it possible to control the front / background stacking behavior of a matplotlib figure window?

@efiring
Copy link
Member

efiring commented May 31, 2017

It seems to be different for each backend, and also subject to the whims of the desktop environment. Which backend are you using, and in which environment?

@mbrennwa
Copy link

I have Ubuntu Linux with Gnome-3 desktop environment, and I use the TkAgg backaend. matplotlib 1.5 behaves as it should, matplotlib 2.0 does not.

Below is a copy of what I posted on stackexchange:


I have a python / matplotlib application that frequently updates a plot with new data coming in from a measurement instrument. The plot window should not change from background to foreground (or vice versa) with respect to other windows on my desktop when the plot is updated with new data.

This worked as desired with Python 3 on a machine running Ubuntu 16.10 with matplotlib 1.5.2rc. However, on a different machine with Ubuntu 17.04 and matplotlib 2.0.0, the figure window pops to the front every time the plot is updated with new data.

How can I control the window foreground/background behavior and keep the window focus when updating the plot with new data?

Here's a code example illustrating my plotting routine:

import matplotlib
import matplotlib.pyplot as plt
from time import time
from random import random

print ( matplotlib.__version__ )

# set up the figure
fig = plt.figure()
plt.xlabel('Time')
plt.ylabel('Value')
plt.ion()

# plot things while new data is generated:
t0 = time()
t = []
y = []
while True:
    t.append( time()-t0 )
    y.append( random() )
    fig.clear()
    plt.plot( t , y )
    plt.pause(1)

@efiring
Copy link
Member

efiring commented May 31, 2017

Apart from the change suggested in #8692, which won't help you any time soon, I think you can improve your situation by changing your plotting strategy to avoid using pause, which is calling show, which is raising the window.
Have you tried using the animation module? See http://matplotlib.org/devdocs/gallery/animation/random_data.html#sphx-glr-gallery-animation-random-data-py for example. Modifying it to match your example above would involve adding a call to line.set_xdata, and setting 'interval' to 1000 (milliseconds).
I think that if you use the animation module you will need to manually put the window behind others once at the start, but then it will stay there.

@mbrennwa
Copy link

mbrennwa commented Jun 1, 2017

That animation example does the trick of keeping the window in the background (or in the foreground, wherever I put it) even when the data is updated. I could adapt my code example above to use the animation module.

However, I believe the approach in the example will not work in my real-life application, because I cannot leave it to the animation module to ask for new data to update the plot. My application needs direct control to talk to the measurement instrument, and then update the plot as needed. Is there a way to do this?

@efiring
Copy link
Member

efiring commented Jun 1, 2017

Yes, I think it could be done with the animation module, but figuring out how--via subclassing Animation--might take a bit of time. There is probably a simpler way, but it is hard to specify it without seeing exactly what you are trying to do.
Suggestions:

  1. Put the ion() call at the top, before creating the figure, or run inside of ipython so that you can take advantage of its interactive magic (with no need for the ion call).
  2. General good practice: use the object-oriented API, reserving pyplot for a minimum of calls like plt.ion() and plt.subplots().
  3. Don't clear the figure unless you need to; if you are making an updating line plot, for example, just use the data updating strategy illustrated in the animation examples.

I think that is about all you will need; no clear, no pause; the redrawing should occur each time you update the data.

@WeatherGod
Copy link
Member

WeatherGod commented Jun 1, 2017 via email

@mbrennwa
Copy link

mbrennwa commented Jun 1, 2017

Ok, I had a look at this.

At first, I thought I'd try your suggestions on how to get this working without the animation module. I sort of have this working. However, it is not possible to resize or move the plot window on the screen. I guess there's some event loop that would need to be called, but I don't know how to fix this. Any suggestions?

Then I thought again about the animation module. I believe I could use this for my purposes if it would just update the plot with the current data from my measurement instrument stored in a buffer. However, I'd need to make the plot window to be "non blocking". In my existing code I use the pyplot.ion() function to get an interactive figure that does not block the code following the pyplot.show(). This is required to execute the code for instrument control and data acquisition. All examples I found for the animation module use a blocking window without the pyplot.ion() function, and I was not able to make the animation work in a non-blocking (interactive) way. Any suggestions?

@mbrennwa
Copy link

mbrennwa commented Jun 1, 2017

@WeatherGod: thanks for this suggestion. It would be cool if I could read up on this without buying a book. Any suggestions?

@mbrennwa
Copy link

mbrennwa commented Jun 1, 2017

Ok, I got it working without the animation module. Window resizing and similar actions are taken care of by calling fig.canvas.flush_events() .

Still, it would be nice if the animation module could be used with non-blocking plots. Is this possible? How?

@danielfaust
Copy link

danielfaust commented Aug 17, 2017

I'm facing the same issue as @mbrennwa

Stack Overflow: Make interactive matplotlib window not pop to front on each update (Windows 7)

Honestly, there is nothing in the semantics of the word pause which implies that something should be brought to the front. This may be different with the word show, but pause definitely should not be doing this. This is a buggy behavior.

@mbrennwa
Copy link

I fully agree with danielfaust.

Here's how I made things work in my application by avoiding the call to the buggy pause(...) command:
https://stackoverflow.com/questions/44278369/how-to-keep-matplotlib-python-window-in-background

@tacaswell
Copy link
Member

@mbrennwa FuncAnimation and friends work just fine with plt.ion() (I use it all the time). What examples are you referring to?

FuncAnimation can take a generator as input as well so you can write things like

def get_data():
    while True:
        yield get_some_hardware_data()
    
anim = FuncAnimation(fig, update_func, get_data(), interval=interval)

Could @mbrennwa and @danielfaust take a look at #4779 which is a start of thoroughly documenting how GUI event loop integration works in the context of Matplotlib?

@danielfaust
Copy link

@tacaswell I migrated my code over to to FuncAnimation when I faced the plt.pause issue. But my problem with FuncAnimation is that I don't want to use a fixed interval. I want a fetch-data-time + process-data-time + a fixed pause of let's say 5 seconds. I have some variants where the fetching and processing takes more than 5 seconds, others where both together take about 0.1 seconds, so in the case of the 0.1 seconds I'd choose an interval of 1 second, or a pause of 1 second, with the other one also a pause of one second, but I'd have to choose an interval of about 6-10 seconds with FuncAnimation. It's far better for me to just add a pause.

@tacaswell
Copy link
Member

If you have vary computation time on the data retrieval I would create a thread for that work and have it drop data into a queue, something like

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import threading
from queue import Queue, Empty
import time
import numpy as np

q = Queue()


def data_getting_worker():
    while True:
        time.sleep(np.random.rand() * 5)
        q.put(np.random.randn(15))
        print('put some data')


th_gettr = threading.Thread(target=data_getting_worker)

data = np.random.randn(15)


def data_reciver_gen():
    global data
    while True:
        try:
            # pull data off the queue until we get to the most recent one
            data = q.get(block=False)
            print('got some data')
        except Empty:
            yield data


fig, ax = plt.subplots()

ln, = ax.plot(data)


def updater(d_in):
    print('updating line')
    ln.set_ydata(d_in)
    return [ln]


th_gettr.start()
anim = FuncAnimation(fig, updater, frames=data_reciver_gen(), interval=1000)

Which so long as the processing in data_getting_worker is not a tight loop in c you will get a reasonably responsive figure. You can also use the same pattern with multiprocess to push that work off to a sub-process.

@mbrennwa
Copy link

@tacaswell: running the data acquisition (and a lot more) in FuncAnimation (or any other GUI for that matter) would be very awkward in my application. Plotting is just a small side job, and it may not even be possible on some machines that are running without a display system. Wrapping the whole code in a FuncAnimation is therefore certainly not the way to go in my case. I will therefore stick to my current solution, and I might improve the handling of the GUI/plotting event loop a bit following this example: https://stackoverflow.com/questions/45729092/make-interactive-matplotlib-window-not-pop-to-front-on-each-update-windows-7/45734500#45734500

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests