1from __future__ import (absolute_import, division, print_function,
2                        unicode_literals)
3
4import six
5from six.moves import tkinter as Tk
6
7import logging
8import os.path
9import sys
10
11# Paint image to Tk photo blitter extension
12import matplotlib.backends.tkagg as tkagg
13
14from matplotlib.backends.backend_agg import FigureCanvasAgg
15import matplotlib.backends.windowing as windowing
16
17import matplotlib
18from matplotlib import backend_tools, cbook, rcParams
19from matplotlib.backend_bases import (
20    _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
21    StatusbarBase, TimerBase, ToolContainerBase, cursors)
22from matplotlib.backend_managers import ToolManager
23from matplotlib._pylab_helpers import Gcf
24from matplotlib.figure import Figure
25from matplotlib.widgets import SubplotTool
26
27
28_log = logging.getLogger(__name__)
29
30backend_version = Tk.TkVersion
31
32# the true dots per inch on the screen; should be display dependent
33# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
34PIXELS_PER_INCH = 75
35
36cursord = {
37    cursors.MOVE: "fleur",
38    cursors.HAND: "hand2",
39    cursors.POINTER: "arrow",
40    cursors.SELECT_REGION: "tcross",
41    cursors.WAIT: "watch",
42    }
43
44
45def raise_msg_to_str(msg):
46    """msg is a return arg from a raise.  Join with new lines"""
47    if not isinstance(msg, six.string_types):
48        msg = '\n'.join(map(str, msg))
49    return msg
50
51def error_msg_tkpaint(msg, parent=None):
52    from six.moves import tkinter_messagebox as tkMessageBox
53    tkMessageBox.showerror("matplotlib", msg)
54
55
56class TimerTk(TimerBase):
57    '''
58    Subclass of :class:`backend_bases.TimerBase` that uses Tk's timer events.
59
60    Attributes
61    ----------
62    interval : int
63        The time between timer events in milliseconds. Default is 1000 ms.
64    single_shot : bool
65        Boolean flag indicating whether this timer should operate as single
66        shot (run once and then stop). Defaults to False.
67    callbacks : list
68        Stores list of (func, args) tuples that will be called upon timer
69        events. This list can be manipulated directly, or the functions
70        `add_callback` and `remove_callback` can be used.
71
72    '''
73    def __init__(self, parent, *args, **kwargs):
74        TimerBase.__init__(self, *args, **kwargs)
75        self.parent = parent
76        self._timer = None
77
78    def _timer_start(self):
79        self._timer_stop()
80        self._timer = self.parent.after(self._interval, self._on_timer)
81
82    def _timer_stop(self):
83        if self._timer is not None:
84            self.parent.after_cancel(self._timer)
85        self._timer = None
86
87    def _on_timer(self):
88        TimerBase._on_timer(self)
89
90        # Tk after() is only a single shot, so we need to add code here to
91        # reset the timer if we're not operating in single shot mode.  However,
92        # if _timer is None, this means that _timer_stop has been called; so
93        # don't recreate the timer in that case.
94        if not self._single and self._timer:
95            self._timer = self.parent.after(self._interval, self._on_timer)
96        else:
97            self._timer = None
98
99
100class FigureCanvasTk(FigureCanvasBase):
101    keyvald = {65507 : 'control',
102               65505 : 'shift',
103               65513 : 'alt',
104               65515 : 'super',
105               65508 : 'control',
106               65506 : 'shift',
107               65514 : 'alt',
108               65361 : 'left',
109               65362 : 'up',
110               65363 : 'right',
111               65364 : 'down',
112               65307 : 'escape',
113               65470 : 'f1',
114               65471 : 'f2',
115               65472 : 'f3',
116               65473 : 'f4',
117               65474 : 'f5',
118               65475 : 'f6',
119               65476 : 'f7',
120               65477 : 'f8',
121               65478 : 'f9',
122               65479 : 'f10',
123               65480 : 'f11',
124               65481 : 'f12',
125               65300 : 'scroll_lock',
126               65299 : 'break',
127               65288 : 'backspace',
128               65293 : 'enter',
129               65379 : 'insert',
130               65535 : 'delete',
131               65360 : 'home',
132               65367 : 'end',
133               65365 : 'pageup',
134               65366 : 'pagedown',
135               65438 : '0',
136               65436 : '1',
137               65433 : '2',
138               65435 : '3',
139               65430 : '4',
140               65437 : '5',
141               65432 : '6',
142               65429 : '7',
143               65431 : '8',
144               65434 : '9',
145               65451 : '+',
146               65453 : '-',
147               65450 : '*',
148               65455 : '/',
149               65439 : 'dec',
150               65421 : 'enter',
151               }
152
153    _keycode_lookup = {
154                       262145: 'control',
155                       524320: 'alt',
156                       524352: 'alt',
157                       1048584: 'super',
158                       1048592: 'super',
159                       131074: 'shift',
160                       131076: 'shift',
161                       }
162    """_keycode_lookup is used for badly mapped (i.e. no event.key_sym set)
163       keys on apple keyboards."""
164
165    def __init__(self, figure, master=None, resize_callback=None):
166        super(FigureCanvasTk, self).__init__(figure)
167        self._idle = True
168        self._idle_callback = None
169        t1,t2,w,h = self.figure.bbox.bounds
170        w, h = int(w), int(h)
171        self._tkcanvas = Tk.Canvas(
172            master=master, background="white",
173            width=w, height=h, borderwidth=0, highlightthickness=0)
174        self._tkphoto = Tk.PhotoImage(
175            master=self._tkcanvas, width=w, height=h)
176        self._tkcanvas.create_image(w//2, h//2, image=self._tkphoto)
177        self._resize_callback = resize_callback
178        self._tkcanvas.bind("<Configure>", self.resize)
179        self._tkcanvas.bind("<Key>", self.key_press)
180        self._tkcanvas.bind("<Motion>", self.motion_notify_event)
181        self._tkcanvas.bind("<KeyRelease>", self.key_release)
182        for name in "<Button-1>", "<Button-2>", "<Button-3>":
183            self._tkcanvas.bind(name, self.button_press_event)
184        for name in "<Double-Button-1>", "<Double-Button-2>", "<Double-Button-3>":
185            self._tkcanvas.bind(name, self.button_dblclick_event)
186        for name in "<ButtonRelease-1>", "<ButtonRelease-2>", "<ButtonRelease-3>":
187            self._tkcanvas.bind(name, self.button_release_event)
188
189        # Mouse wheel on Linux generates button 4/5 events
190        for name in "<Button-4>", "<Button-5>":
191            self._tkcanvas.bind(name, self.scroll_event)
192        # Mouse wheel for windows goes to the window with the focus.
193        # Since the canvas won't usually have the focus, bind the
194        # event to the window containing the canvas instead.
195        # See http://wiki.tcl.tk/3893 (mousewheel) for details
196        root = self._tkcanvas.winfo_toplevel()
197        root.bind("<MouseWheel>", self.scroll_event_windows, "+")
198
199        # Can't get destroy events by binding to _tkcanvas. Therefore, bind
200        # to the window and filter.
201        def filter_destroy(evt):
202            if evt.widget is self._tkcanvas:
203                self._master.update_idletasks()
204                self.close_event()
205        root.bind("<Destroy>", filter_destroy, "+")
206
207        self._master = master
208        self._tkcanvas.focus_set()
209
210    def resize(self, event):
211        width, height = event.width, event.height
212        if self._resize_callback is not None:
213            self._resize_callback(event)
214
215        # compute desired figure size in inches
216        dpival = self.figure.dpi
217        winch = width/dpival
218        hinch = height/dpival
219        self.figure.set_size_inches(winch, hinch, forward=False)
220
221
222        self._tkcanvas.delete(self._tkphoto)
223        self._tkphoto = Tk.PhotoImage(
224            master=self._tkcanvas, width=int(width), height=int(height))
225        self._tkcanvas.create_image(int(width/2),int(height/2),image=self._tkphoto)
226        self.resize_event()
227        self.draw()
228
229        # a resizing will in general move the pointer position
230        # relative to the canvas, so process it as a motion notify
231        # event.  An intended side effect of this call is to allow
232        # window raises (which trigger a resize) to get the cursor
233        # position to the mpl event framework so key presses which are
234        # over the axes will work w/o clicks or explicit motion
235        self._update_pointer_position(event)
236
237    def _update_pointer_position(self, guiEvent=None):
238        """
239        Figure out if we are inside the canvas or not and update the
240        canvas enter/leave events
241        """
242        # if the pointer if over the canvas, set the lastx and lasty
243        # attrs of the canvas so it can process event w/o mouse click
244        # or move
245
246        # the window's upper, left coords in screen coords
247        xw = self._tkcanvas.winfo_rootx()
248        yw = self._tkcanvas.winfo_rooty()
249        # the pointer's location in screen coords
250        xp, yp = self._tkcanvas.winfo_pointerxy()
251
252        # not figure out the canvas coordinates of the pointer
253        xc = xp - xw
254        yc = yp - yw
255
256        # flip top/bottom
257        yc = self.figure.bbox.height - yc
258
259        # JDH: this method was written originally to get the pointer
260        # location to the backend lastx and lasty attrs so that events
261        # like KeyEvent can be handled without mouse events.  e.g., if
262        # the cursor is already above the axes, then key presses like
263        # 'g' should toggle the grid.  In order for this to work in
264        # backend_bases, the canvas needs to know _lastx and _lasty.
265        # There are three ways to get this info the canvas:
266        #
267        # 1) set it explicitly
268        #
269        # 2) call enter/leave events explicitly.  The downside of this
270        #    in the impl below is that enter could be repeatedly
271        #    triggered if the mouse is over the axes and one is
272        #    resizing with the keyboard.  This is not entirely bad,
273        #    because the mouse position relative to the canvas is
274        #    changing, but it may be surprising to get repeated entries
275        #    without leaves
276        #
277        # 3) process it as a motion notify event.  This also has pros
278        #    and cons.  The mouse is moving relative to the window, but
279        #    this may surpise an event handler writer who is getting
280        #   motion_notify_events even if the mouse has not moved
281
282        # here are the three scenarios
283        if 1:
284            # just manually set it
285            self._lastx, self._lasty = xc, yc
286        elif 0:
287            # alternate implementation: process it as a motion
288            FigureCanvasBase.motion_notify_event(self, xc, yc, guiEvent)
289        elif 0:
290            # alternate implementation -- process enter/leave events
291            # instead of motion/notify
292            if self.figure.bbox.contains(xc, yc):
293                self.enter_notify_event(guiEvent, xy=(xc,yc))
294            else:
295                self.leave_notify_event(guiEvent)
296
297    show = cbook.deprecated("2.2", name="FigureCanvasTk.show",
298                            alternative="FigureCanvasTk.draw")(
299                                lambda self: self.draw())
300
301    def draw_idle(self):
302        'update drawing area only if idle'
303        if self._idle is False:
304            return
305
306        self._idle = False
307
308        def idle_draw(*args):
309            try:
310                self.draw()
311            finally:
312                self._idle = True
313
314        self._idle_callback = self._tkcanvas.after_idle(idle_draw)
315
316    def get_tk_widget(self):
317        """returns the Tk widget used to implement FigureCanvasTkAgg.
318        Although the initial implementation uses a Tk canvas,  this routine
319        is intended to hide that fact.
320        """
321        return self._tkcanvas
322
323    def motion_notify_event(self, event):
324        x = event.x
325        # flipy so y=0 is bottom of canvas
326        y = self.figure.bbox.height - event.y
327        FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
328
329
330    def button_press_event(self, event, dblclick=False):
331        x = event.x
332        # flipy so y=0 is bottom of canvas
333        y = self.figure.bbox.height - event.y
334        num = getattr(event, 'num', None)
335
336        if sys.platform=='darwin':
337            # 2 and 3 were reversed on the OSX platform I
338            # tested under tkagg
339            if   num==2: num=3
340            elif num==3: num=2
341
342        FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick, guiEvent=event)
343
344    def button_dblclick_event(self,event):
345        self.button_press_event(event,dblclick=True)
346
347    def button_release_event(self, event):
348        x = event.x
349        # flipy so y=0 is bottom of canvas
350        y = self.figure.bbox.height - event.y
351
352        num = getattr(event, 'num', None)
353
354        if sys.platform=='darwin':
355            # 2 and 3 were reversed on the OSX platform I
356            # tested under tkagg
357            if   num==2: num=3
358            elif num==3: num=2
359
360        FigureCanvasBase.button_release_event(self, x, y, num, guiEvent=event)
361
362    def scroll_event(self, event):
363        x = event.x
364        y = self.figure.bbox.height - event.y
365        num = getattr(event, 'num', None)
366        if   num==4: step = +1
367        elif num==5: step = -1
368        else:        step =  0
369
370        FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
371
372    def scroll_event_windows(self, event):
373        """MouseWheel event processor"""
374        # need to find the window that contains the mouse
375        w = event.widget.winfo_containing(event.x_root, event.y_root)
376        if w == self._tkcanvas:
377            x = event.x_root - w.winfo_rootx()
378            y = event.y_root - w.winfo_rooty()
379            y = self.figure.bbox.height - y
380            step = event.delta/120.
381            FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
382
383    def _get_key(self, event):
384        val = event.keysym_num
385        if val in self.keyvald:
386            key = self.keyvald[val]
387        elif val == 0 and sys.platform == 'darwin' and \
388                                        event.keycode in self._keycode_lookup:
389            key = self._keycode_lookup[event.keycode]
390        elif val < 256:
391            key = chr(val)
392        else:
393            key = None
394
395        # add modifier keys to the key string. Bit details originate from
396        # http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
397        # BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
398        # BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
399        # BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
400        # In general, the modifier key is excluded from the modifier flag,
401        # however this is not the case on "darwin", so double check that
402        # we aren't adding repeat modifier flags to a modifier key.
403        if sys.platform == 'win32':
404            modifiers = [(17, 'alt', 'alt'),
405                         (2, 'ctrl', 'control'),
406                         ]
407        elif sys.platform == 'darwin':
408            modifiers = [(3, 'super', 'super'),
409                         (4, 'alt', 'alt'),
410                         (2, 'ctrl', 'control'),
411                         ]
412        else:
413            modifiers = [(6, 'super', 'super'),
414                         (3, 'alt', 'alt'),
415                         (2, 'ctrl', 'control'),
416                         ]
417
418        if key is not None:
419            # note, shift is not added to the keys as this is already accounted for
420            for bitmask, prefix, key_name in modifiers:
421                if event.state & (1 << bitmask) and key_name not in key:
422                    key = '{0}+{1}'.format(prefix, key)
423
424        return key
425
426    def key_press(self, event):
427        key = self._get_key(event)
428        FigureCanvasBase.key_press_event(self, key, guiEvent=event)
429
430    def key_release(self, event):
431        key = self._get_key(event)
432        FigureCanvasBase.key_release_event(self, key, guiEvent=event)
433
434    def new_timer(self, *args, **kwargs):
435        """
436        Creates a new backend-specific subclass of :class:`backend_bases.Timer`.
437        This is useful for getting periodic events through the backend's native
438        event loop. Implemented only for backends with GUIs.
439
440        Other Parameters
441        ----------------
442        interval : scalar
443            Timer interval in milliseconds
444        callbacks : list
445            Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
446            will be executed by the timer every *interval*.
447
448        """
449        return TimerTk(self._tkcanvas, *args, **kwargs)
450
451    def flush_events(self):
452        self._master.update()
453
454
455class FigureManagerTk(FigureManagerBase):
456    """
457    Attributes
458    ----------
459    canvas : `FigureCanvas`
460        The FigureCanvas instance
461    num : int or str
462        The Figure number
463    toolbar : tk.Toolbar
464        The tk.Toolbar
465    window : tk.Window
466        The tk.Window
467
468    """
469    def __init__(self, canvas, num, window):
470        FigureManagerBase.__init__(self, canvas, num)
471        self.window = window
472        self.window.withdraw()
473        self.set_window_title("Figure %d" % num)
474        self.canvas = canvas
475        # If using toolmanager it has to be present when initializing the toolbar
476        self.toolmanager = self._get_toolmanager()
477        # packing toolbar first, because if space is getting low, last packed widget is getting shrunk first (-> the canvas)
478        self.toolbar = self._get_toolbar()
479        self.canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
480        self._num = num
481
482        self.statusbar = None
483
484        if self.toolmanager:
485            backend_tools.add_tools_to_manager(self.toolmanager)
486            if self.toolbar:
487                backend_tools.add_tools_to_container(self.toolbar)
488                self.statusbar = StatusbarTk(self.window, self.toolmanager)
489
490        self._shown = False
491
492        def notify_axes_change(fig):
493            'this will be called whenever the current axes is changed'
494            if self.toolmanager is not None:
495                pass
496            elif self.toolbar is not None:
497                self.toolbar.update()
498        self.canvas.figure.add_axobserver(notify_axes_change)
499
500    def _get_toolbar(self):
501        if matplotlib.rcParams['toolbar'] == 'toolbar2':
502            toolbar = NavigationToolbar2Tk(self.canvas, self.window)
503        elif matplotlib.rcParams['toolbar'] == 'toolmanager':
504            toolbar = ToolbarTk(self.toolmanager, self.window)
505        else:
506            toolbar = None
507        return toolbar
508
509    def _get_toolmanager(self):
510        if rcParams['toolbar'] == 'toolmanager':
511            toolmanager = ToolManager(self.canvas.figure)
512        else:
513            toolmanager = None
514        return toolmanager
515
516    def resize(self, width, height=None):
517        # before 09-12-22, the resize method takes a single *event*
518        # parameter. On the other hand, the resize method of other
519        # FigureManager class takes *width* and *height* parameter,
520        # which is used to change the size of the window. For the
521        # Figure.set_size_inches with forward=True work with Tk
522        # backend, I changed the function signature but tried to keep
523        # it backward compatible. -JJL
524
525        # when a single parameter is given, consider it as a event
526        if height is None:
527            cbook.warn_deprecated("2.2", "FigureManagerTkAgg.resize now takes "
528                                  "width and height as separate arguments")
529            width = width.width
530        else:
531            self.canvas._tkcanvas.master.geometry("%dx%d" % (width, height))
532
533        if self.toolbar is not None:
534            self.toolbar.configure(width=width)
535
536    def show(self):
537        """
538        this function doesn't segfault but causes the
539        PyEval_RestoreThread: NULL state bug on win32
540        """
541        _focus = windowing.FocusManager()
542        if not self._shown:
543            def destroy(*args):
544                self.window = None
545                Gcf.destroy(self._num)
546            self.canvas._tkcanvas.bind("<Destroy>", destroy)
547            self.window.deiconify()
548        else:
549            self.canvas.draw_idle()
550        # Raise the new window.
551        self.canvas.manager.window.attributes('-topmost', 1)
552        self.canvas.manager.window.attributes('-topmost', 0)
553        self._shown = True
554
555    def destroy(self, *args):
556        if self.window is not None:
557            #self.toolbar.destroy()
558            if self.canvas._idle_callback:
559                self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback)
560            self.window.destroy()
561        if Gcf.get_num_fig_managers()==0:
562            if self.window is not None:
563                self.window.quit()
564        self.window = None
565
566    def get_window_title(self):
567        return self.window.wm_title()
568
569    def set_window_title(self, title):
570        self.window.wm_title(title)
571
572    def full_screen_toggle(self):
573        is_fullscreen = bool(self.window.attributes('-fullscreen'))
574        self.window.attributes('-fullscreen', not is_fullscreen)
575
576
577@cbook.deprecated("2.2")
578class AxisMenu(object):
579    def __init__(self, master, naxes):
580        self._master = master
581        self._naxes = naxes
582        self._mbar = Tk.Frame(master=master, relief=Tk.RAISED, borderwidth=2)
583        self._mbar.pack(side=Tk.LEFT)
584        self._mbutton = Tk.Menubutton(
585            master=self._mbar, text="Axes", underline=0)
586        self._mbutton.pack(side=Tk.LEFT, padx="2m")
587        self._mbutton.menu = Tk.Menu(self._mbutton)
588        self._mbutton.menu.add_command(
589            label="Select All", command=self.select_all)
590        self._mbutton.menu.add_command(
591            label="Invert All", command=self.invert_all)
592        self._axis_var = []
593        self._checkbutton = []
594        for i in range(naxes):
595            self._axis_var.append(Tk.IntVar())
596            self._axis_var[i].set(1)
597            self._checkbutton.append(self._mbutton.menu.add_checkbutton(
598                label = "Axis %d" % (i+1),
599                variable=self._axis_var[i],
600                command=self.set_active))
601            self._mbutton.menu.invoke(self._mbutton.menu.index("Select All"))
602        self._mbutton['menu'] = self._mbutton.menu
603        self._mbar.tk_menuBar(self._mbutton)
604        self.set_active()
605
606    def adjust(self, naxes):
607        if self._naxes < naxes:
608            for i in range(self._naxes, naxes):
609                self._axis_var.append(Tk.IntVar())
610                self._axis_var[i].set(1)
611                self._checkbutton.append( self._mbutton.menu.add_checkbutton(
612                    label = "Axis %d" % (i+1),
613                    variable=self._axis_var[i],
614                    command=self.set_active))
615        elif self._naxes > naxes:
616            for i in range(self._naxes-1, naxes-1, -1):
617                del self._axis_var[i]
618                self._mbutton.menu.forget(self._checkbutton[i])
619                del self._checkbutton[i]
620        self._naxes = naxes
621        self.set_active()
622
623    def get_indices(self):
624        a = [i for i in range(len(self._axis_var)) if self._axis_var[i].get()]
625        return a
626
627    def set_active(self):
628        self._master.set_active(self.get_indices())
629
630    def invert_all(self):
631        for a in self._axis_var:
632            a.set(not a.get())
633        self.set_active()
634
635    def select_all(self):
636        for a in self._axis_var:
637            a.set(1)
638        self.set_active()
639
640
641class NavigationToolbar2Tk(NavigationToolbar2, Tk.Frame):
642    """
643    Attributes
644    ----------
645    canvas : `FigureCanvas`
646        the figure canvas on which to operate
647    win : tk.Window
648        the tk.Window which owns this toolbar
649
650    """
651    def __init__(self, canvas, window):
652        self.canvas = canvas
653        self.window = window
654        NavigationToolbar2.__init__(self, canvas)
655
656    def destroy(self, *args):
657        del self.message
658        Tk.Frame.destroy(self, *args)
659
660    def set_message(self, s):
661        self.message.set(s)
662
663    def draw_rubberband(self, event, x0, y0, x1, y1):
664        height = self.canvas.figure.bbox.height
665        y0 = height - y0
666        y1 = height - y1
667        if hasattr(self, "lastrect"):
668            self.canvas._tkcanvas.delete(self.lastrect)
669        self.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1)
670
671        #self.canvas.draw()
672
673    def release(self, event):
674        try: self.lastrect
675        except AttributeError: pass
676        else:
677            self.canvas._tkcanvas.delete(self.lastrect)
678            del self.lastrect
679
680    def set_cursor(self, cursor):
681        self.window.configure(cursor=cursord[cursor])
682        self.window.update_idletasks()
683
684    def _Button(self, text, file, command, extension='.gif'):
685        img_file = os.path.join(
686            rcParams['datapath'], 'images', file + extension)
687        im = Tk.PhotoImage(master=self, file=img_file)
688        b = Tk.Button(
689            master=self, text=text, padx=2, pady=2, image=im, command=command)
690        b._ntimage = im
691        b.pack(side=Tk.LEFT)
692        return b
693
694    def _Spacer(self):
695        # Buttons are 30px high, so make this 26px tall with padding to center it
696        s = Tk.Frame(
697            master=self, height=26, relief=Tk.RIDGE, pady=2, bg="DarkGray")
698        s.pack(side=Tk.LEFT, padx=5)
699        return s
700
701    def _init_toolbar(self):
702        xmin, xmax = self.canvas.figure.bbox.intervalx
703        height, width = 50, xmax-xmin
704        Tk.Frame.__init__(self, master=self.window,
705                          width=int(width), height=int(height),
706                          borderwidth=2)
707
708        self.update()  # Make axes menu
709
710        for text, tooltip_text, image_file, callback in self.toolitems:
711            if text is None:
712                # Add a spacer; return value is unused.
713                self._Spacer()
714            else:
715                button = self._Button(text=text, file=image_file,
716                                      command=getattr(self, callback))
717                if tooltip_text is not None:
718                    ToolTip.createToolTip(button, tooltip_text)
719
720        self.message = Tk.StringVar(master=self)
721        self._message_label = Tk.Label(master=self, textvariable=self.message)
722        self._message_label.pack(side=Tk.RIGHT)
723        self.pack(side=Tk.BOTTOM, fill=Tk.X)
724
725    def configure_subplots(self):
726        toolfig = Figure(figsize=(6,3))
727        window = Tk.Toplevel()
728        canvas = type(self.canvas)(toolfig, master=window)
729        toolfig.subplots_adjust(top=0.9)
730        canvas.tool = SubplotTool(self.canvas.figure, toolfig)
731        canvas.draw()
732        canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
733        window.grab_set()
734
735    def save_figure(self, *args):
736        from six.moves import tkinter_tkfiledialog, tkinter_messagebox
737        filetypes = self.canvas.get_supported_filetypes().copy()
738        default_filetype = self.canvas.get_default_filetype()
739
740        # Tk doesn't provide a way to choose a default filetype,
741        # so we just have to put it first
742        default_filetype_name = filetypes.pop(default_filetype)
743        sorted_filetypes = ([(default_filetype, default_filetype_name)]
744                            + sorted(six.iteritems(filetypes)))
745        tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes]
746
747        # adding a default extension seems to break the
748        # asksaveasfilename dialog when you choose various save types
749        # from the dropdown.  Passing in the empty string seems to
750        # work - JDH!
751        #defaultextension = self.canvas.get_default_filetype()
752        defaultextension = ''
753        initialdir = os.path.expanduser(rcParams['savefig.directory'])
754        initialfile = self.canvas.get_default_filename()
755        fname = tkinter_tkfiledialog.asksaveasfilename(
756            master=self.window,
757            title='Save the figure',
758            filetypes=tk_filetypes,
759            defaultextension=defaultextension,
760            initialdir=initialdir,
761            initialfile=initialfile,
762            )
763
764        if fname in ["", ()]:
765            return
766        # Save dir for next time, unless empty str (i.e., use cwd).
767        if initialdir != "":
768            rcParams['savefig.directory'] = (
769                os.path.dirname(six.text_type(fname)))
770        try:
771            # This method will handle the delegation to the correct type
772            self.canvas.figure.savefig(fname)
773        except Exception as e:
774            tkinter_messagebox.showerror("Error saving file", str(e))
775
776    def set_active(self, ind):
777        self._ind = ind
778        self._active = [self._axes[i] for i in self._ind]
779
780    def update(self):
781        _focus = windowing.FocusManager()
782        self._axes = self.canvas.figure.axes
783        NavigationToolbar2.update(self)
784
785
786class ToolTip(object):
787    """
788    Tooltip recipe from
789    http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387
790    """
791    @staticmethod
792    def createToolTip(widget, text):
793        toolTip = ToolTip(widget)
794        def enter(event):
795            toolTip.showtip(text)
796        def leave(event):
797            toolTip.hidetip()
798        widget.bind('<Enter>', enter)
799        widget.bind('<Leave>', leave)
800
801    def __init__(self, widget):
802        self.widget = widget
803        self.tipwindow = None
804        self.id = None
805        self.x = self.y = 0
806
807    def showtip(self, text):
808        "Display text in tooltip window"
809        self.text = text
810        if self.tipwindow or not self.text:
811            return
812        x, y, _, _ = self.widget.bbox("insert")
813        x = x + self.widget.winfo_rootx() + 27
814        y = y + self.widget.winfo_rooty()
815        self.tipwindow = tw = Tk.Toplevel(self.widget)
816        tw.wm_overrideredirect(1)
817        tw.wm_geometry("+%d+%d" % (x, y))
818        try:
819            # For Mac OS
820            tw.tk.call("::tk::unsupported::MacWindowStyle",
821                       "style", tw._w,
822                       "help", "noActivates")
823        except Tk.TclError:
824            pass
825        label = Tk.Label(tw, text=self.text, justify=Tk.LEFT,
826                         background="#ffffe0", relief=Tk.SOLID, borderwidth=1)
827        label.pack(ipadx=1)
828
829    def hidetip(self):
830        tw = self.tipwindow
831        self.tipwindow = None
832        if tw:
833            tw.destroy()
834
835
836class RubberbandTk(backend_tools.RubberbandBase):
837    def __init__(self, *args, **kwargs):
838        backend_tools.RubberbandBase.__init__(self, *args, **kwargs)
839
840    def draw_rubberband(self, x0, y0, x1, y1):
841        height = self.figure.canvas.figure.bbox.height
842        y0 = height - y0
843        y1 = height - y1
844        if hasattr(self, "lastrect"):
845            self.figure.canvas._tkcanvas.delete(self.lastrect)
846        self.lastrect = self.figure.canvas._tkcanvas.create_rectangle(
847            x0, y0, x1, y1)
848
849    def remove_rubberband(self):
850        if hasattr(self, "lastrect"):
851            self.figure.canvas._tkcanvas.delete(self.lastrect)
852            del self.lastrect
853
854
855class SetCursorTk(backend_tools.SetCursorBase):
856    def set_cursor(self, cursor):
857        self.figure.canvas.manager.window.configure(cursor=cursord[cursor])
858
859
860class ToolbarTk(ToolContainerBase, Tk.Frame):
861    _icon_extension = '.gif'
862    def __init__(self, toolmanager, window):
863        ToolContainerBase.__init__(self, toolmanager)
864        xmin, xmax = self.toolmanager.canvas.figure.bbox.intervalx
865        height, width = 50, xmax - xmin
866        Tk.Frame.__init__(self, master=window,
867                          width=int(width), height=int(height),
868                          borderwidth=2)
869        self._toolitems = {}
870        self.pack(side=Tk.TOP, fill=Tk.X)
871        self._groups = {}
872
873    def add_toolitem(
874            self, name, group, position, image_file, description, toggle):
875        frame = self._get_groupframe(group)
876        button = self._Button(name, image_file, toggle, frame)
877        if description is not None:
878            ToolTip.createToolTip(button, description)
879        self._toolitems.setdefault(name, [])
880        self._toolitems[name].append(button)
881
882    def _get_groupframe(self, group):
883        if group not in self._groups:
884            if self._groups:
885                self._add_separator()
886            frame = Tk.Frame(master=self, borderwidth=0)
887            frame.pack(side=Tk.LEFT, fill=Tk.Y)
888            self._groups[group] = frame
889        return self._groups[group]
890
891    def _add_separator(self):
892        separator = Tk.Frame(master=self, bd=5, width=1, bg='black')
893        separator.pack(side=Tk.LEFT, fill=Tk.Y, padx=2)
894
895    def _Button(self, text, image_file, toggle, frame):
896        if image_file is not None:
897            im = Tk.PhotoImage(master=self, file=image_file)
898        else:
899            im = None
900
901        if not toggle:
902            b = Tk.Button(master=frame, text=text, padx=2, pady=2, image=im,
903                          command=lambda: self._button_click(text))
904        else:
905            # There is a bug in tkinter included in some python 3.6 versions
906            # that without this variable, produces a "visual" toggling of
907            # other near checkbuttons
908            # https://bugs.python.org/issue29402
909            # https://bugs.python.org/issue25684
910            var = Tk.IntVar()
911            b = Tk.Checkbutton(master=frame, text=text, padx=2, pady=2,
912                               image=im, indicatoron=False,
913                               command=lambda: self._button_click(text),
914                               variable=var)
915        b._ntimage = im
916        b.pack(side=Tk.LEFT)
917        return b
918
919    def _button_click(self, name):
920        self.trigger_tool(name)
921
922    def toggle_toolitem(self, name, toggled):
923        if name not in self._toolitems:
924            return
925        for toolitem in self._toolitems[name]:
926            if toggled:
927                toolitem.select()
928            else:
929                toolitem.deselect()
930
931    def remove_toolitem(self, name):
932        for toolitem in self._toolitems[name]:
933            toolitem.pack_forget()
934        del self._toolitems[name]
935
936
937class StatusbarTk(StatusbarBase, Tk.Frame):
938    def __init__(self, window, *args, **kwargs):
939        StatusbarBase.__init__(self, *args, **kwargs)
940        xmin, xmax = self.toolmanager.canvas.figure.bbox.intervalx
941        height, width = 50, xmax - xmin
942        Tk.Frame.__init__(self, master=window,
943                          width=int(width), height=int(height),
944                          borderwidth=2)
945        self._message = Tk.StringVar(master=self)
946        self._message_label = Tk.Label(master=self, textvariable=self._message)
947        self._message_label.pack(side=Tk.RIGHT)
948        self.pack(side=Tk.TOP, fill=Tk.X)
949
950    def set_message(self, s):
951        self._message.set(s)
952
953
954class SaveFigureTk(backend_tools.SaveFigureBase):
955    def trigger(self, *args):
956        from six.moves import tkinter_tkfiledialog, tkinter_messagebox
957        filetypes = self.figure.canvas.get_supported_filetypes().copy()
958        default_filetype = self.figure.canvas.get_default_filetype()
959
960        # Tk doesn't provide a way to choose a default filetype,
961        # so we just have to put it first
962        default_filetype_name = filetypes.pop(default_filetype)
963        sorted_filetypes = ([(default_filetype, default_filetype_name)]
964                            + sorted(six.iteritems(filetypes)))
965        tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes]
966
967        # adding a default extension seems to break the
968        # asksaveasfilename dialog when you choose various save types
969        # from the dropdown.  Passing in the empty string seems to
970        # work - JDH!
971        # defaultextension = self.figure.canvas.get_default_filetype()
972        defaultextension = ''
973        initialdir = os.path.expanduser(rcParams['savefig.directory'])
974        initialfile = self.figure.canvas.get_default_filename()
975        fname = tkinter_tkfiledialog.asksaveasfilename(
976            master=self.figure.canvas.manager.window,
977            title='Save the figure',
978            filetypes=tk_filetypes,
979            defaultextension=defaultextension,
980            initialdir=initialdir,
981            initialfile=initialfile,
982            )
983
984        if fname == "" or fname == ():
985            return
986        else:
987            if initialdir == '':
988                # explicitly missing key or empty str signals to use cwd
989                rcParams['savefig.directory'] = initialdir
990            else:
991                # save dir for next time
992                rcParams['savefig.directory'] = os.path.dirname(
993                    six.text_type(fname))
994            try:
995                # This method will handle the delegation to the correct type
996                self.figure.savefig(fname)
997            except Exception as e:
998                tkinter_messagebox.showerror("Error saving file", str(e))
999
1000
1001class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase):
1002    def __init__(self, *args, **kwargs):
1003        backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs)
1004        self.window = None
1005
1006    def trigger(self, *args):
1007        self.init_window()
1008        self.window.lift()
1009
1010    def init_window(self):
1011        if self.window:
1012            return
1013
1014        toolfig = Figure(figsize=(6, 3))
1015        self.window = Tk.Tk()
1016
1017        canvas = type(self.canvas)(toolfig, master=self.window)
1018        toolfig.subplots_adjust(top=0.9)
1019        _tool = SubplotTool(self.figure, toolfig)
1020        canvas.draw()
1021        canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
1022        self.window.protocol("WM_DELETE_WINDOW", self.destroy)
1023
1024    def destroy(self, *args, **kwargs):
1025        self.window.destroy()
1026        self.window = None
1027
1028
1029backend_tools.ToolSaveFigure = SaveFigureTk
1030backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk
1031backend_tools.ToolSetCursor = SetCursorTk
1032backend_tools.ToolRubberband = RubberbandTk
1033Toolbar = ToolbarTk
1034
1035
1036@_Backend.export
1037class _BackendTk(_Backend):
1038    FigureManager = FigureManagerTk
1039
1040    @classmethod
1041    def new_figure_manager_given_figure(cls, num, figure):
1042        """
1043        Create a new figure manager instance for the given figure.
1044        """
1045        _focus = windowing.FocusManager()
1046        window = Tk.Tk(className="matplotlib")
1047        window.withdraw()
1048
1049        # Put a mpl icon on the window rather than the default tk icon.
1050        # Tkinter doesn't allow colour icons on linux systems, but tk>=8.5 has
1051        # a iconphoto command which we call directly. Source:
1052        # http://mail.python.org/pipermail/tkinter-discuss/2006-November/000954.html
1053        icon_fname = os.path.join(
1054            rcParams['datapath'], 'images', 'matplotlib.ppm')
1055        icon_img = Tk.PhotoImage(file=icon_fname)
1056        try:
1057            window.tk.call('wm', 'iconphoto', window._w, icon_img)
1058        except Exception as exc:
1059            # log the failure (due e.g. to Tk version), but carry on
1060            _log.info('Could not load matplotlib icon: %s', exc)
1061
1062        canvas = cls.FigureCanvas(figure, master=window)
1063        manager = cls.FigureManager(canvas, num, window)
1064        if matplotlib.is_interactive():
1065            manager.show()
1066            canvas.draw_idle()
1067        return manager
1068
1069    @staticmethod
1070    def trigger_manager_draw(manager):
1071        manager.show()
1072
1073    @staticmethod
1074    def mainloop():
1075        Tk.mainloop()
1076