Repository: spyder-ide/spyder
Pull request: PR: Add a Plots plugin to browse figures generated by the IPython console
Fixes #2550

![figureexplorer_demo](https://user-images.githubusercontent.com/10170372/36064905-59f3497a-0e60-11e8-8c4e-6e2382bb5db8.gif)

![fig_viewer_example](https://user-images.githubusercontent.com/10170372/36064906-5ec8b2c8-0e60-11e8-964e-21aa4a29fefa.gif)



Problem statement:
Dock matplotlib figures
I don't use inline plotting, because you lose the zoom functions, which are really useful for me. But it'd be nice to have matplotlib windows docked into the spyder UI, so that I don't need to switch back and forth between the two windows.

Hints from issue discussion:
Sorry, but I think this is not so easy to do :-)

This feature would be a great idea! Hopefully it is not as difficult as imagined...

I depends a lot on how matplotlib is used, which backend... I think it's not even in the same process as the main interface ?

What we could provide is a module specific for spyder, so that `import spyderplotlib as spl; spl.figure()` creates a dockable widget instead of a standalone figure. That would mean that the scripts olny works from inside spyder. But it may create more problems that it solves.

Personally, having the option of creating a dockable widget instead of a standalone (or inline) figure would be a benefit. Then whenever my script is ready (i.e. I'm done testing and playing around), I can always change it around to a standard `matplotlib`. But might it not be possible to enforce using <a href="http://matplotlib.org/1.4.3/examples/user_interfaces/embedding_in_qt4.html">`qt` backend for `matplotlib`</a>, which could then be used within Spyder? 

After a bit more thought, I don't know how any of this could be done: the script is completely independent from the main spyder interface so there is no way to get a reference of the QMainWindow.

It could work in the internal console, but we're not going to do that.

That does sound rather insurmountable indeed. Too bad!

But here's another thought: could you perhaps display use the variable explorer to display an image/plot that is stored? Then one could simply store the plot in a variable, and have it displayed in that way. If in addition that window could be made dockable (and auto-refreshable), it would be a great solution (cf. issue #2863).

The last news I had about this is that matplotlib figures don't work very well with pickle (which is what we use internally to exchange the variables), that's why there is no format to save matlab figures like .fig in matlab. Maybe it has changed, in that case it is possible that it can be a viable workaround.

Perhaps the pickling of `matplotlib` figures will improve over time. We'll wait and see!

Having a dockable plot window (or say panne) is a very good idea.
I don't know how, but the "Rodeo" ide managed to get this worked. 
It would be very wonderful that somebody can make it happen in spyder.

There are major technical differences when comparing to something like Rodeo...

## On Rodeo:

Rodeo is JS technology for the Desktop and they only have access to one console at a time, but we could look into the code and check for hints.

## On Spyder:

Spyder uses Qt, through PyQt, so there is one python console where the app lives (Internal Console) and Spyder allows you to open N number of python/ipython consoles as separate processes.

Like @Nodd says, it is not possible for us to hook to a Matplotlib Canvas (based on Qt) with the main python interpreter where Spyder runs.

### However, what we can do something with pickle as suggested:

Add extra support for matplotlib figures inside the variable explorer so that everytime a specific type of matplotlib object is stored in a variable (an Axes object), we would (try to) pickle it, and then (try to) reload inside a Spyder Plugin Dock that would now be running within the Spyder App. If we are running on the ipython console it would be something like:

```
import matplotlib.pyplot as plt
import numpy as np

ax = plt.subplot(111)
x = np.linspace(0, 10)
y = np.exp(x)
some_plot = plt.plot(x, y)
```

Now we can detect that **some_plot** was created (or updated if changed) and trigger a pickle/unpickle process where the unpickled result would be displayed in a separate dockwidget. So in theory is "doable". This communication could go over a socket? or... we could just write to disk...? or we just do everything in memory with BytesIO etc?

### Gotchas
- The user needs to save the plot inside a variable, which is ok, since is a minor style change, plus scripts will work inside and outside spyder
- Depending on the complexity of the figure it might be slow?
- We now need to monitor these special variables to know if its content has been updates etc...
- Dont know how this works for more complex stuff like, interactive widgets...
- We need to do some matplotlib patching on startup to make sure we can override some methods... like automatically displaying a plot, when doing `some_plot = plt.plot(x, y)` if we can unpickle it, otherwise we would need to be able to display... now this gets also tricky.
- We are still depending on pickle support to work properly...
- We need to be able to use the exact same version of MPL, so we would need to run this in the target environment.

From http://matplotlib.org/users/whats_new.html#figures-are-picklable 1.2 version.. I guess is more mature now?

> Figures are picklable
> Philip Elson added an experimental feature to make figures picklable for quick and easy short-term storage of plots. Pickle files are not designed for long term storage, are unsupported when restoring a pickle saved in another matplotlib version and are insecure when restoring a pickle from an untrusted source. Having said this, they are useful for short term storage for later modification inside matplotlib.

@Nodd, @ccordoba12 what do you think?

Commit message: fix pep8

Patch to review:
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
index cbe6e8d3775..1f81a5df519 100644
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -332,6 +332,7 @@ def signal_handler(signum, frame=None):
         self.historylog = None
         self.ipyconsole = None
         self.variableexplorer = None
+        self.plots = None
         self.findinfiles = None
         self.thirdparty_plugins = []
 
@@ -854,6 +855,12 @@ def create_edit_action(text, tr_text, icon):
         self.variableexplorer = VariableExplorer(self)
         self.variableexplorer.register_plugin()
 
+        # Figure browser
+        self.set_splash(_("Loading figure browser..."))
+        from spyder.plugins.plots import Plots
+        self.plots = Plots(self)
+        self.plots.register_plugin()
+
         # History log widget
         if CONF.get('historylog', 'enable'):
             self.set_splash(_("Loading history plugin..."))
@@ -1465,6 +1472,7 @@ def setup_default_layouts(self, index, settings):
         explorer_project = self.projects
         explorer_file = self.explorer
         explorer_variable = self.variableexplorer
+        plots = self.plots
         history = self.historylog
         finder = self.findinfiles
         help_plugin = self.help
@@ -1487,8 +1495,8 @@ def setup_default_layouts(self, index, settings):
                     # column 2
                     [[outline]],
                     # column 3
-                    [[help_plugin, explorer_variable, helper, explorer_file,
-                      finder] + plugins,
+                    [[help_plugin, explorer_variable, plots, helper,
+                      explorer_file, finder] + plugins,
                      [console_int, console_ipy, history]]
                     ],
                     'width fraction': [0.0,             # column 0 width
@@ -1507,7 +1515,8 @@ def setup_default_layouts(self, index, settings):
                     [[editor],
                      [console_ipy, console_int]],
                     # column 1
-                    [[explorer_variable, history, outline, finder] + plugins,
+                    [[explorer_variable, plots, history, outline,
+                      finder] + plugins,
                      [explorer_file, explorer_project, help_plugin, helper]]
                     ],
                     'width fraction': [0.55,            # column 0 width
@@ -1526,7 +1535,7 @@ def setup_default_layouts(self, index, settings):
                     [[editor],
                      [console_ipy, console_int]],
                     # column 2
-                    [[explorer_variable, finder] + plugins,
+                    [[explorer_variable, plots, finder] + plugins,
                      [history, help_plugin, helper]]
                     ],
                     'width fraction': [0.20,            # column 0 width
@@ -1543,7 +1552,7 @@ def setup_default_layouts(self, index, settings):
                     # column 0
                     [[editor],
                      [console_ipy, console_int, explorer_file,
-                      explorer_project, help_plugin, explorer_variable,
+                      explorer_project, help_plugin, explorer_variable, plots,
                       history, outline, finder, helper] + plugins]
                     ],
                     'width fraction': [1.0],            # column 0 width
@@ -1557,7 +1566,7 @@ def setup_default_layouts(self, index, settings):
                     [[editor]],
                     # column 1
                     [[console_ipy, console_int, explorer_file,
-                      explorer_project, help_plugin, explorer_variable,
+                      explorer_project, help_plugin, explorer_variable, plots,
                       history, outline, finder, helper] + plugins]
                     ],
                     'width fraction': [0.55,      # column 0 width
diff --git a/spyder/config/main.py b/spyder/config/main.py
index dba1946db9e..7259880660e 100755
--- a/spyder/config/main.py
+++ b/spyder/config/main.py
@@ -185,6 +185,11 @@
               'truncate': True,
               'minmax': False
              }),
+            ('plots',
+             {
+              'mute_inline_plotting': False,
+              'show_plot_outline': False,
+             }),
             ('editor',
              {
               'printer_header/font/family': SANS_SERIF,
diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py
index b8e1dd99fa6..a4ce19b1583 100644
--- a/spyder/plugins/ipythonconsole.py
+++ b/spyder/plugins/ipythonconsole.py
@@ -662,6 +662,7 @@ def __init__(self, parent, testing=False, test_dir=TEMPDIR,
         self.help = None               # Help plugin
         self.historylog = None         # History log plugin
         self.variableexplorer = None   # Variable explorer plugin
+        self.plots = None              # plots plugin
         self.editor = None             # Editor plugin
         self.projects = None           # Projects plugin
 
@@ -822,6 +823,7 @@ def refresh_plugin(self):
         if client and not self.testing:
             sw = client.shellwidget
             self.variableexplorer.set_shellwidget_from_id(id(sw))
+            self.plots.set_shellwidget_from_id(id(sw))
             self.help.set_shell(sw)
         self.update_tabs_text()
         self.sig_update_plugin_title.emit()
@@ -907,6 +909,7 @@ def register_plugin(self):
         self.help = self.main.help
         self.historylog = self.main.historylog
         self.variableexplorer = self.main.variableexplorer
+        self.plots = self.main.plots
         self.editor = self.main.editor
         self.explorer = self.main.explorer
         self.projects = self.main.projects
@@ -1776,10 +1779,14 @@ def process_started(self, client):
             self.help.set_shell(client.shellwidget)
         if self.variableexplorer is not None:
             self.variableexplorer.add_shellwidget(client.shellwidget)
+        if self.plots is not None:
+            self.plots.add_shellwidget(client.shellwidget)
 
     def process_finished(self, client):
         if self.variableexplorer is not None:
             self.variableexplorer.remove_shellwidget(id(client.shellwidget))
+        if self.plots is not None:
+            self.plots.remove_shellwidget(id(client.shellwidget))
 
     def connect_external_kernel(self, shellwidget):
         """
diff --git a/spyder/plugins/plots.py b/spyder/plugins/plots.py
new file mode 100644
index 00000000000..abcb60ca32e
--- /dev/null
+++ b/spyder/plugins/plots.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""Plots Plugin"""
+
+
+# Third party imports
+from qtpy.QtCore import Signal
+from qtpy.QtWidgets import QStackedWidget, QGridLayout
+
+# Local imports
+from spyder.config.base import _
+from spyder.api.plugins import SpyderPluginWidget
+from spyder.api.preferences import PluginConfigPage
+from spyder.utils import icon_manager as ima
+from spyder.widgets.figurebrowser import FigureBrowser
+
+
+class PlotsConfigPage(PluginConfigPage):
+
+    def setup_page(self):
+        pass
+
+
+class Plots(SpyderPluginWidget):
+    """Plots plugin."""
+
+    CONF_SECTION = 'plots'
+    CONFIGWIDGET_CLASS = PlotsConfigPage
+    DISABLE_ACTIONS_WHEN_HIDDEN = False
+    sig_option_changed = Signal(str, object)
+
+    def __init__(self, parent):
+        SpyderPluginWidget.__init__(self, parent)
+
+        # Widgets
+        self.stack = QStackedWidget(self)
+        self.shellwidgets = {}
+
+        # Layout
+        layout = QGridLayout(self)
+        layout.addWidget(self.stack)
+
+        # Initialize plugin
+        self.initialize_plugin()
+
+    def get_settings(self):
+        """Retrieve all Plots configuration settings."""
+        return {name: self.get_option(name) for name in
+                ['mute_inline_plotting',  'show_plot_outline']}
+
+    # ---- Stack accesors
+
+    def set_current_widget(self, fig_browser):
+        """
+        Set the currently visible fig_browser in the stack widget, refresh the
+        actions of the cog menu button and move it to the layout of the new
+        fig_browser.
+        """
+        self.stack.setCurrentWidget(fig_browser)
+        # We update the actions of the options button (cog menu) and
+        # we move it to the layout of the current widget.
+        self.refresh_actions()
+        fig_browser.setup_options_button()
+
+    def current_widget(self):
+        return self.stack.currentWidget()
+
+    def count(self):
+        return self.stack.count()
+
+    def remove_widget(self, fig_browser):
+        self.stack.removeWidget(fig_browser)
+
+    def add_widget(self, fig_browser):
+        self.stack.addWidget(fig_browser)
+
+    # ---- Public API
+
+    def add_shellwidget(self, shellwidget):
+        """
+        Register shell with figure explorer.
+
+        This function opens a new FigureBrowser for browsing the figures
+        in the shell.
+        """
+        shellwidget_id = id(shellwidget)
+        if shellwidget_id not in self.shellwidgets:
+            self.options_button.setVisible(True)
+            fig_browser = FigureBrowser(
+                self, options_button=self.options_button)
+            fig_browser.set_shellwidget(shellwidget)
+            fig_browser.setup(**self.get_settings())
+            fig_browser.sig_option_changed.connect(
+                self.sig_option_changed.emit)
+            fig_browser.thumnails_sb.redirect_stdio.connect(
+                self.main.redirect_internalshell_stdio)
+            self.add_widget(fig_browser)
+            self.shellwidgets[shellwidget_id] = fig_browser
+            self.set_shellwidget_from_id(shellwidget_id)
+            return fig_browser
+
+    def remove_shellwidget(self, shellwidget_id):
+        # If shellwidget_id is not in self.shellwidgets, it simply means
+        # that shell was not a Python-based console (it was a terminal)
+        if shellwidget_id in self.shellwidgets:
+            fig_browser = self.shellwidgets.pop(shellwidget_id)
+            self.remove_widget(fig_browser)
+            fig_browser.close()
+
+    def set_shellwidget_from_id(self, shellwidget_id):
+        if shellwidget_id in self.shellwidgets:
+            fig_browser = self.shellwidgets[shellwidget_id]
+            self.set_current_widget(fig_browser)
+
+    # ---- SpyderPluginWidget API
+
+    def get_plugin_title(self):
+        """Return widget title"""
+        return _('Plots')
+
+    def get_plugin_icon(self):
+        """Return plugin icon"""
+        return ima.icon('hist')
+
+    def get_focus_widget(self):
+        """
+        Return the widget to give focus to when
+        this plugin's dockwidget is raised on top-level
+        """
+        return self.current_widget()
+
+    def closing_plugin(self, cancelable=False):
+        """Perform actions before parent main window is closed"""
+        return True
+
+    def refresh_plugin(self):
+        """Refresh widget"""
+        pass
+
+    def get_plugin_actions(self):
+        """Return a list of actions related to plugin"""
+        return self.current_widget().actions if self.current_widget() else []
+
+    def register_plugin(self):
+        """Register plugin in Spyder's main window"""
+        self.main.add_dockwidget(self)
+
+    def apply_plugin_settings(self, options):
+        """Apply configuration file's plugin settings"""
+        for fig_browser in list(self.shellwidgets.values()):
+            fig_browser.setup(**self.get_settings())
diff --git a/spyder/widgets/figurebrowser.py b/spyder/widgets/figurebrowser.py
new file mode 100644
index 00000000000..0198d986e2d
--- /dev/null
+++ b/spyder/widgets/figurebrowser.py
@@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
+
+    # ---- Toolbar Handlers
+
+    def zoom_in(self):
+        """
+        Zoom the figure in by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_in()
+
+    def zoom_out(self):
+        """
+        Zoom the figure out by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_out()
+
+    def go_previous_thumbnail(self):
+        """
+        Select the thumbnail previous to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_previous_thumbnail()
+
+    def go_next_thumbnail(self):
+        """
+        Select the thumbnail next to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_next_thumbnail()
+
+    def close_figure(self):
+        """Close the currently selected figure in the thumbnail scrollbar."""
+        self.thumnails_sb.remove_current_thumbnail()
+
+    def close_all_figures(self):
+        """Close all the figures in the thumbnail scrollbar."""
+        self.thumnails_sb.remove_all_thumbnails()
+
+
+class FigureViewer(QScrollArea):
+    """
+    A scrollarea that displays a single FigureCanvas with zooming and panning
+    capability with CTRL + Mouse_wheel and Left-press mouse button event.
+    """
+
+    sig_zoom_changed = Signal(float)
+
+    def __init__(self, parent=None):
+        super(FigureViewer, self).__init__(parent)
+        self.setAlignment(Qt.AlignCenter)
+        self.viewport().setStyleSheet("background-color: white")
+        self.setFrameStyle(0)
+
+        self._scalefactor = 0
+        self._scalestep = 1.2
+        self._sfmax = 10
+        self._sfmin = -10
+
+        # An internal flag that tracks when the figure is being panned.
+        self._ispanning = False
+
+        self.setup_figcanvas()
+
+    def setup_figcanvas(self):
+        """Setup the FigureCanvas."""
+        self.figcanvas = FigureCanvas()
+        self.figcanvas.installEventFilter(self)
+        self.setWidget(self.figcanvas)
+
+    def load_figure(self, fig, fmt):
+        """Set a new figure in the figure canvas."""
+        self.figcanvas.load_figure(fig, fmt)
+        self.scale_image()
+        self.figcanvas.repaint()
+
+    def eventFilter(self, widget, event):
+        """A filter to control the zooming and panning of the figure canvas."""
+
+        # ---- Zooming
+
+        if event.type() == QEvent.Wheel:
+            modifiers = QApplication.keyboardModifiers()
+            if modifiers == Qt.ControlModifier:
+                if event.angleDelta().y() > 0:
+                    self.zoom_in()
+                else:
+                    self.zoom_out()
+                return True
+            else:
+                return False
+
+        # ---- Panning
+
+        # Set ClosedHandCursor:
+
+        elif event.type() == QEvent.MouseButtonPress:
+            if event.button() == Qt.LeftButton:
+                QApplication.setOverrideCursor(Qt.ClosedHandCursor)
+                self._ispanning = True
+                self.xclick = event.globalX()
+                self.yclick = event.globalY()
+
+        # Reset Cursor:
+
+        elif event.type() == QEvent.MouseButtonRelease:
+            QApplication.restoreOverrideCursor()
+            self._ispanning = False
+
+        # Move  ScrollBar:
+
+        elif event.type() == QEvent.MouseMove:
+            if self._ispanning:
+                dx = self.xclick - event.globalX()
+                self.xclick = event.globalX()
+
+                dy = self.yclick - event.globalY()
+                self.yclick = event.globalY()
+
+                scrollBarH = self.horizontalScrollBar()
+                scrollBarH.setValue(scrollBarH.value() + dx)
+
+                scrollBarV = self.verticalScrollBar()
+                scrollBarV.setValue(scrollBarV.value() + dy)
+
+        return QWidget.eventFilter(self, widget, event)
+
+    # ---- Figure Scaling Handlers
+
+    def zoom_in(self):
+        """Scale the image up by one scale step."""
+        if self._scalefactor <= self._sfmax:
+            self._scalefactor += 1
+            self.scale_image()
+            self._adjust_scrollbar(self._scalestep)
+            self.sig_zoom_changed.emit(self.get_scaling())
+
+    def zoom_out(self):
+        """Scale the image down by one scale step."""
+        if self._scalefactor >= self._sfmin:
+            self._scalefactor -= 1
+            self.scale_image()
+            self._adjust_scrollbar(1/self._scalestep)
+            self.sig_zoom_changed.emit(self.get_scaling())
+
+    def scale_image(self):
+        """Scale the image size."""
+        new_width = int(self.figcanvas.fwidth *
+                        self._scalestep ** self._scalefactor)
+        new_height = int(self.figcanvas.fheight *
+                         self._scalestep ** self._scalefactor)
+        self.figcanvas.setFixedSize(new_width, new_height)
+
+    def get_scaling(self):
+        """Get the current scaling of the figure in percent."""
+        return self._scalestep**self._scalefactor*100
+
+    def reset_original_image(self):
+        """Reset the image to its original size."""
+        self._scalefactor = 0
+        self.scale_image()
+
+    def _adjust_scrollbar(self, f):
+        """
+        Adjust the scrollbar position to take into account the zooming of
+        the figure.
+        """
+        # Adjust horizontal scrollbar :
+        hb = self.horizontalScrollBar()
+        hb.setValue(int(f * hb.value() + ((f - 1) * hb.pageStep()/2)))
+
+        # Adjust the vertical scrollbar :
+        vb = self.verticalScrollBar()
+        vb.setValue(int(f * vb.value() + ((f - 1) * vb.pageStep()/2)))
+
+
+class ThumbnailScrollBar(QFrame):
+    """
+    A widget that manages the display of the FigureThumbnails that are
+    created when a figure is sent to the IPython console by the kernel and
+    that controls what is displayed in the FigureViewer.
+    """
+    redirect_stdio = Signal(bool)
+
+    def __init__(self, figure_viewer, parent=None):
+        super(ThumbnailScrollBar, self).__init__(parent)
+        self._thumbnails = []
+        self.current_thumbnail = None
+        self.set_figureviewer(figure_viewer)
+        self.setup_gui()
+
+    def setup_gui(self):
+        """Setup the main layout of the widget."""
+        scrollarea = self.setup_scrollarea()
+        up_btn, down_btn = self.setup_arrow_buttons()
+
+        self.setFixedWidth(135)
+        layout = QVBoxLayout(self)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.setSpacing(0)
+        layout.addWidget(up_btn)
+        layout.addWidget(scrollarea)
+        layout.addWidget(down_btn)
+
+    def setup_scrollarea(self):
+        """Setup the scrollarea that will contain the FigureThumbnails."""
+        self.view = QWidget()
+
+        self.scene = QGridLayout(self.view)
+        self.scene.setColumnStretch(0, 100)
+        self.scene.setColumnStretch(2, 100)
+
+        self.scrollarea = QScrollArea()
+        self.scrollarea.setWidget(self.view)
+        self.scrollarea.setWidgetResizable(True)
+        self.scrollarea.setFrameStyle(0)
+        self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.scrollarea.setSizePolicy(QSizePolicy(QSizePolicy.Ignored,
+                                                  QSizePolicy.Preferred))
+
+        # Set the vertical scrollbar explicitely :
+
+        # This is required to avoid a "RuntimeError: no access to protected
+        # functions or signals for objects not created from Python" in Linux.
+        self.scrollarea.setVerticalScrollBar(QScrollBar())
+
+        return self.scrollarea
+
+    def setup_arrow_buttons(self):
+        """
+        Setup the up and down arrow buttons that are placed at the top and
+        bottom of the scrollarea.
+        """
+        # Get the height of the up/down arrow of the default vertical
+        # scrollbar :
+
+        vsb = self.scrollarea.verticalScrollBar()
+        style = vsb.style()
+        opt = QStyleOptionSlider()
+        vsb.initStyleOption(opt)
+        vsb_up_arrow = style.subControlRect(
+                QStyle.CC_ScrollBar, opt, QStyle.SC_ScrollBarAddLine, self)
+
+        # Setup the up and down arrow button :
+
+        up_btn = up_btn = QPushButton(icon=ima.icon('last_edit_location'))
+        up_btn.setFlat(True)
+        up_btn.setFixedHeight(vsb_up_arrow.size().height())
+        up_btn.clicked.connect(self.go_up)
+
+        down_btn = QPushButton(icon=ima.icon('folding.arrow_down_on'))
+        down_btn.setFlat(True)
+        down_btn.setFixedHeight(vsb_up_arrow.size().height())
+        down_btn.clicked.connect(self.go_down)
+
+        return up_btn, down_btn
+
+    def set_figureviewer(self, figure_viewer):
+        """Set the bamespace for the FigureViewer."""
+        self.figure_viewer = figure_viewer
+
+    # ---- Save Figure
+
+    def save_all_figures_as(self):
+        """Save all the figures to a file."""
+        self.redirect_stdio.emit(False)
+        dirname = getexistingdirectory(self, caption='Save all figures',
+                                       basedir=getcwd_or_home())
+        self.redirect_stdio.emit(True)
+        if dirname:
+            self.save_all_figures_todir(dirname)
+
+    def save_all_figures_todir(self, dirname):
+        """Save all figure in dirname."""
+        for thumbnail in self._thumbnails:
+            fig = thumbnail.canvas.fig
+            fmt = thumbnail.canvas.fmt
+            fext = {'image/png': '.png',
+                    'image/jpeg': '.jpg',
+                    'image/svg+xml': '.svg'}[fmt]
+
+            figname = get_unique_figname(dirname, 'Figure', fext)
+            save_figure_tofile(fig, fmt, figname)
+
+    def save_current_figure_as(self):
+        """Save the currently selected figure."""
+        if self.current_thumbnail is None:
+            return
+
+        self.save_figure_as(self.current_thumbnail.canvas.fig,
+                            self.current_thumbnail.canvas.fmt)
+
+    def save_figure_as(self, fig, fmt):
+        """Save the figure to a file."""
+        fext, ffilt = {
+            'image/png': ('.png', 'PNG (*.png)'),
+            'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'),
+            'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)')}[fmt]
+
+        figname = get_unique_figname(getcwd_or_home(), 'Figure', fext)
+
+        self.redirect_stdio.emit(False)
+        fname, fext = getsavefilename(
+            parent=self.parent(), caption='Save Figure',
+            basedir=figname, filters=ffilt,
+            selectedfilter='', options=None)
+        self.redirect_stdio.emit(True)
+
+        if fname:
+            save_figure_tofile(fig, fmt, fname)
+
+    # ---- Thumbails Handlers
+
+    def add_thumbnail(self, fig, fmt):
+        thumbnail = FigureThumbnail()
+        thumbnail.canvas.load_figure(fig, fmt)
+
+        # Scale the thumbnail size, while respecting the figure
+        # dimension ratio.
+        fwidth = thumbnail.canvas.fwidth
+        fheight = thumbnail.canvas.fheight
+
+        max_length = 100
+        if fwidth/fheight > 1:
+            canvas_width = max_length
+            canvas_height = canvas_width / fwidth * fheight
+        else:
+            canvas_height = max_length
+            canvas_width = canvas_height / fheight * fwidth
+        thumbnail.canvas.setFixedSize(canvas_width, canvas_height)
+
+        thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
+        thumbnail.sig_remove_figure.connect(self.remove_thumbnail)
+        thumbnail.sig_save_figure.connect(self.save_figure_as)
+        self._thumbnails.append(thumbnail)
+
+        self.scene.setRowStretch(self.scene.rowCount()-1, 0)
+        self.scene.addWidget(thumbnail, self.scene.rowCount()-1, 1)
+        self.scene.setRowStretch(self.scene.rowCount(), 100)
+        self.set_current_thumbnail(thumbnail)
+
+    def remove_current_thumbnail(self):
+        """Remove the currently selected thumbnail."""
+        if self.current_thumbnail is not None:
+            self.remove_thumbnail(self.current_thumbnail)
+
+    def remove_all_thumbnails(self):
+        """Remove all thumbnails."""
+        for thumbnail in self._thumbnails:
+            self.layout().removeWidget(thumbnail)
+            thumbnail.deleteLater()
+        self._thumbnails = []
+        self.current_thumbnail = None
+        self.figure_viewer.figcanvas.clear_canvas()
+
+    def remove_thumbnail(self, thumbnail):
+        """Remove thumbnail."""
+        if thumbnail in self._thumbnails:
+            index = self._thumbnails.index(thumbnail)
+            self._thumbnails.remove(thumbnail)
+        self.layout().removeWidget(thumbnail)
+        thumbnail.deleteLater()
+
+        # Select a new thumbnail if any :
+
+        if thumbnail == self.current_thumbnail:
+            if len(self._thumbnails) > 0:
+                self.set_current_index(min(index, len(self._thumbnails)-1))
+            else:
+                self.current_thumbnail = None
+                self.figure_viewer.figcanvas.clear_canvas()
+
+    def set_current_index(self, index):
+        """Set the currently selected thumbnail by its index."""
+        self.set_current_thumbnail(self._thumbnails[index])
+
+    def set_current_thumbnail(self, thumbnail):
+        """Set the currently selected thumbnail."""
+        self.current_thumbnail = thumbnail
+        self.figure_viewer.load_figure(
+                thumbnail.canvas.fig, thumbnail.canvas.fmt)
+        for thumbnail in self._thumbnails:
+            thumbnail.highlight_canvas(thumbnail == self.current_thumbnail)
+
+    def go_previous_thumbnail(self):
+        """Select the thumbnail previous to the currently selected one."""
+        if self.current_thumbnail is not None:
+            index = self._thumbnails.index(self.current_thumbnail) - 1
+            index = index if index >= 0 else len(self._thumbnails) - 1
+            self.set_current_index(index)
+
+    def go_next_thumbnail(self):
+        """Select thumbnail next to the currently selected one."""
+        if self.current_thumbnail is not None:
+            index = self._thumbnails.index(self.current_thumbnail) + 1
+            index = 0 if index >= len(self._thumbnails) else index
+            self.set_current_index(index)
+
+    # ---- ScrollBar Handlers
+
+    def go_up(self):
+        """Scroll the scrollbar of the scrollarea up by a single step."""
+        vsb = self.scrollarea.verticalScrollBar()
+        vsb.setValue(int(vsb.value() - vsb.singleStep()))
+
+    def go_down(self):
+        """Scroll the scrollbar of the scrollarea down by a single step."""
+        vsb = self.scrollarea.verticalScrollBar()
+        vsb.setValue(int(vsb.value() + vsb.singleStep()))
+
+
+class FigureThumbnail(QWidget):
+    """
+    A widget that consists of a FigureCanvas, a side toolbar, and a context
+    menu that is used to show preview of figures in the ThumbnailScrollBar.
+    """
+
+    sig_canvas_clicked = Signal(object)
+    sig_remove_figure = Signal(object)
+    sig_save_figure = Signal(object, str)
+
+    def __init__(self, parent=None):
+        super(FigureThumbnail, self).__init__(parent)
+        self.canvas = FigureCanvas(self)
+        self.canvas.installEventFilter(self)
+        self.setup_gui()
+
+    def setup_gui(self):
+        """Setup the main layout of the widget."""
+        layout = QGridLayout(self)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(self.canvas, 0, 1)
+        layout.addLayout(self.setup_toolbar(), 0, 3, 2, 1)
+
+        layout.setColumnStretch(0, 100)
+        layout.setColumnStretch(2, 100)
+        layout.setRowStretch(1, 100)
+
+    def setup_toolbar(self):
+        """Setup the toolbar."""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.emit_save_figure)
+        delfig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Delete image"),
+                triggered=self.emit_remove_figure)
+
+        toolbar = QVBoxLayout()
+        toolbar.setContentsMargins(0, 0, 0, 0)
+        toolbar.setSpacing(1)
+        toolbar.addWidget(savefig_btn)
+        toolbar.addWidget(delfig_btn)
+        toolbar.addStretch(2)
+
+        return toolbar
+
+    def highlight_canvas(self, highlight):
+        """
+        Set a colored frame around the FigureCanvas if highlight is True.
+        """
+        colorname = self.canvas.palette().highlight().color().name()
+        if highlight:
+            self.canvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid %s;}" % colorname)
+        else:
+            self.canvas.setStyleSheet("FigureCanvas{}")
+
+    def eventFilter(self, widget, event):
+        """
+        A filter that is used to send a signal when the figure canvas is
+        clicked.
+        """
+        if event.type() == QEvent.MouseButtonPress:
+            if event.button() == Qt.LeftButton:
+                self.sig_canvas_clicked.emit(self)
+        return super(FigureThumbnail, self).eventFilter(widget, event)
+
+    def emit_save_figure(self):
+        """
+        Emit a signal when the toolbutton to save the figure is clicked.
+        """
+        self.sig_save_figure.emit(self.canvas.fig, self.canvas.fmt)
+
+    def emit_remove_figure(self):
+        """
+        Emit a signal when the toolbutton to close the figure is clicked.
+        """
+        self.sig_remove_figure.emit(self)
+
+
+class FigureCanvas(QFrame):
+    """
+    A basic widget on which can be painted a custom png, jpg, or svg image.
+    """
+
+    def __init__(self, parent=None):
+        super(FigureCanvas, self).__init__(parent)
+        self.setLineWidth(2)
+        self.setMidLineWidth(1)
+        self.setStyleSheet("background-color: white")
+
+        self.fig = None
+        self.fmt = None
+        self.fwidth, self.fheight = 200, 200
+
+    def clear_canvas(self):
+        """Clear the figure that was painted on the widget."""
+        self.fig = None
+        self.fmt = None
+        self._qpix_buffer = []
+        self.repaint()
+
+    def load_figure(self, fig, fmt):
+        """
+        Load the figure from a png, jpg, or svg image, convert it in
+        a QPixmap, and force a repaint of the widget.
+        """
+        self.fig = fig
+        self.fmt = fmt
+
+        if fmt in ['image/png', 'image/jpeg']:
+            self._qpix_orig = QPixmap()
+            self._qpix_orig.loadFromData(fig, fmt.upper())
+        elif fmt == 'image/svg+xml':
+            self._qpix_orig = QPixmap(svg_to_image(fig))
+
+        self._qpix_buffer = [self._qpix_orig]
+        self.fwidth = self._qpix_orig.width()
+        self.fheight = self._qpix_orig.height()
+
+    def paintEvent(self, event):
+        """Qt method override to paint a custom image on the Widget."""
+        super(FigureCanvas, self).paintEvent(event)
+        # Prepare the rect on which the image is going to be painted :
+
+        fw = self.frameWidth()
+        rect = QRect(0 + fw, 0 + fw,
+                     self.size().width() - 2 * fw,
+                     self.size().height() - 2 * fw)
+
+        if self.fig is None:
+            return
+
+        # Check/update the qpixmap buffer :
+
+        qpix2paint = None
+        for qpix in self._qpix_buffer:
+            if qpix.size().width() == rect.width():
+                qpix2paint = qpix
+                break
+        else:
+            if self.fmt in ['image/png', 'image/jpeg']:
+                qpix2paint = self._qpix_orig.scaledToWidth(
+                    rect.width(), mode=Qt.SmoothTransformation)
+            elif self.fmt == 'image/svg+xml':
+                qpix2paint = QPixmap(svg_to_image(self.fig, rect.size()))
+            self._qpix_buffer.append(qpix2paint)
+
+        if qpix2paint is not None:
+            # Paint the image on the widget :
+            qp = QPainter()
+            qp.begin(self)
+            qp.drawPixmap(rect, qpix2paint)
+            qp.end()
diff --git a/spyder/widgets/ipythonconsole/__init__.py b/spyder/widgets/ipythonconsole/__init__.py
index 9c2c1d68620..0d800427125 100644
--- a/spyder/widgets/ipythonconsole/__init__.py
+++ b/spyder/widgets/ipythonconsole/__init__.py
@@ -12,6 +12,7 @@
 from .debugging import DebuggingWidget
 from .help import HelpWidget
 from .namespacebrowser import NamepaceBrowserWidget
+from .figurebrowser import FigureBrowserWidget
 
 # ShellWidget contains the other widgets and ClientWidget
 # contains it
diff --git a/spyder/widgets/ipythonconsole/figurebrowser.py b/spyder/widgets/ipythonconsole/figurebrowser.py
new file mode 100644
index 00000000000..c27eb58db63
--- /dev/null
+++ b/spyder/widgets/ipythonconsole/figurebrowser.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Widget that handles communications between the IPython Console and
+the Plots plugin
+"""
+
+# ---- Standard library imports
+
+from base64 import decodestring
+
+# ---- Third party library imports
+
+from qtconsole.rich_jupyter_widget import RichJupyterWidget
+# from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+
+
+class FigureBrowserWidget(RichJupyterWidget):
+    """
+    Widget with the necessary attributes and methods to intercept the figures
+    sent by the kernel to the Ipython Console and send it to the Figure
+    Explorer plugin. This widget can also block the plotting of inline
+    figure in the Ipython Console so that figures are only plotted in the
+    explorer plugin.
+    """
+
+    # Reference to the figurebrowser widget connected to this client
+    figurebrowser = None
+
+    # ---- Public API
+
+    def set_figurebrowser(self, figurebrowser):
+        """Set the namespace for the figurebrowser widget."""
+        self.figurebrowser = figurebrowser
+
+    # ---- Private API (overrode by us)
+
+    def _handle_display_data(self, msg):
+        """
+        Reimplemented to handle communications between the figure explorer
+        and the kernel.
+        """
+        img = None
+        data = msg['content']['data']
+        if 'image/svg+xml' in data:
+            fmt = 'image/svg+xml'
+            img = data['image/svg+xml']
+        elif 'image/png' in data:
+            # PNG data is base64 encoded as it passes over the network
+            # in a JSON structure so we decode it.
+            fmt = 'image/png'
+            img = decodestring(data['image/png'].encode('ascii'))
+        elif 'image/jpeg' in data and self._jpg_supported:
+            fmt = 'image/jpeg'
+            img = decodestring(data['image/jpeg'].encode('ascii'))
+        if img is not None:
+            self.sig_new_inline_figure.emit(img, fmt)
+            if (self.figurebrowser is not None and
+                    self.figurebrowser.mute_inline_plotting):
+                del msg['content']['data'][fmt]
+
+        return super(FigureBrowserWidget, self)._handle_display_data(msg)
diff --git a/spyder/widgets/ipythonconsole/shell.py b/spyder/widgets/ipythonconsole/shell.py
index 8c221535bb0..284093fab23 100644
--- a/spyder/widgets/ipythonconsole/shell.py
+++ b/spyder/widgets/ipythonconsole/shell.py
@@ -23,12 +23,13 @@
 from spyder.utils import syntaxhighlighters as sh
 from spyder.utils.ipython.style import create_qss_style, create_style_class
 from spyder.widgets.helperwidgets import MessageCheckBox
-from spyder.widgets.ipythonconsole import (ControlWidget, DebuggingWidget,
-                                           HelpWidget, NamepaceBrowserWidget,
-                                           PageControlWidget)
+from spyder.widgets.ipythonconsole import (
+        ControlWidget, DebuggingWidget, FigureBrowserWidget,
+        HelpWidget, NamepaceBrowserWidget, PageControlWidget)
 
 
-class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget):
+class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget,
+                  FigureBrowserWidget):
     """
     Shell widget for the IPython Console
 
@@ -43,6 +44,9 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget):
     sig_show_syspath = Signal(object)
     sig_show_env = Signal(object)
 
+    # For FigureBrowserWidget
+    sig_new_inline_figure = Signal(object, str)
+
     # For DebuggingWidget
     sig_pdb_step = Signal(str, int)
 


Code review comments:
diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
path: spyder/widgets/figurebrowser.py
text: @user1:
ctrl -> Ctrl

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
path: spyder/widgets/figurebrowser.py
text: @user1:
ipython -> IPython

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
+
+    # ---- Toolbar Handlers
+
+    def zoom_in(self):
+        """
+        Zoom the figure in by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_in()
+
+    def zoom_out(self):
+        """
+        Zoom the figure out by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_out()
+
+    def go_previous_thumbnail(self):
+        """
+        Select the thumbnail previous to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_previous_thumbnail()
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
+
+    # ---- Toolbar Handlers
+
+    def zoom_in(self):
+        """
+        Zoom the figure in by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_in()
+
+    def zoom_out(self):
+        """
+        Zoom the figure out by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_out()
+
+    def go_previous_thumbnail(self):
+        """
+        Select the thumbnail previous to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_previous_thumbnail()
+
+    def go_next_thumbnail(self):
+        """
+        Select the thumbnail next to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_next_thumbnail()
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
+
+    # ---- Toolbar Handlers
+
+    def zoom_in(self):
+        """
+        Zoom the figure in by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_in()
+
+    def zoom_out(self):
+        """
+        Zoom the figure out by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_out()
+
+    def go_previous_thumbnail(self):
+        """
+        Select the thumbnail previous to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_previous_thumbnail()
+
+    def go_next_thumbnail(self):
+        """
+        Select the thumbnail next to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_next_thumbnail()
+
+    def close_figure(self):
+        """Close the currently selected figure in the thumbnail scrollbar."""
+        self.thumnails_sb.remove_current_thumbnail()
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
+                triggered=self.thumnails_sb.save_all_figures_as)
+
+        closefig_btn = create_toolbutton(
+                self, icon=ima.icon('editclear'),
+                tip=_("Remove image"),
+                triggered=self.close_figure)
+
+        closeall_btn = create_toolbutton(
+                self, icon=ima.icon('filecloseall'),
+                tip=_("Remove all images from the explorer"),
+                triggered=self.close_all_figures)
+
+        vsep1 = QFrame()
+        vsep1.setFrameStyle(53)
+
+        goback_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowBack'),
+                tip=_("Previous Figure"),
+                triggered=self.go_previous_thumbnail)
+
+        gonext_btn = create_toolbutton(
+                self, icon=ima.icon('ArrowForward'),
+                tip=_("Next Figure"),
+                triggered=self.go_next_thumbnail)
+
+        vsep2 = QFrame()
+        vsep2.setFrameStyle(53)
+
+        zoom_out_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_out'),
+                tip=_("Zoom out (ctrl + mouse-wheel-down)"),
+                triggered=self.zoom_out)
+
+        zoom_in_btn = create_toolbutton(
+                self, icon=ima.icon('zoom_in'),
+                tip=_("Zoom in (ctrl + mouse-wheel-up)"),
+                triggered=self.zoom_in)
+
+        self.zoom_disp = QSpinBox()
+        self.zoom_disp.setAlignment(Qt.AlignCenter)
+        self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons)
+        self.zoom_disp.setReadOnly(True)
+        self.zoom_disp.setSuffix(' %')
+        self.zoom_disp.setRange(0, 9999)
+        self.zoom_disp.setValue(100)
+        self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue)
+
+        zoom_pan = QWidget()
+        layout = QHBoxLayout(zoom_pan)
+        layout.setSpacing(0)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(zoom_out_btn)
+        layout.addWidget(zoom_in_btn)
+        layout.addWidget(self.zoom_disp)
+
+        return [savefig_btn, saveall_btn, closefig_btn, closeall_btn, vsep1,
+                goback_btn, gonext_btn, vsep2, zoom_pan]
+
+    def setup_option_actions(self, mute_inline_plotting, show_plot_outline):
+        """Setup the actions to show in the cog menu."""
+        self.setup_in_progress = True
+        self.mute_inline_action = create_action(
+            self, _("Mute inline plotting"),
+            tip=_("Mute inline plotting in the ipython console."),
+            toggled=lambda state:
+            self.option_changed('mute_inline_plotting', state)
+            )
+        self.mute_inline_action.setChecked(mute_inline_plotting)
+
+        self.show_plot_outline_action = create_action(
+            self, _("Show plot outline"),
+            tip=_("Show the plot outline."),
+            toggled=self.show_fig_outline_in_viewer
+            )
+        self.show_plot_outline_action.setChecked(show_plot_outline)
+
+        self.actions = [self.mute_inline_action, self.show_plot_outline_action]
+        self.setup_in_progress = False
+
+    def setup_options_button(self):
+        """Add the cog menu button to the toolbar."""
+        if not self.options_button:
+            # When the FigureBowser widget is instatiated outside of the
+            # plugin (for testing purpose for instance), we need to create
+            # the options_button and set its menu.
+            self.options_button = create_toolbutton(
+                self, text=_('Options'), icon=ima.icon('tooloptions'))
+
+            actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions
+            self.options_menu = QMenu(self)
+            add_actions(self.options_menu, actions)
+            self.options_button.setMenu(self.options_menu)
+
+        if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None:
+            self.tools_layout.insertWidget(
+                self.tools_layout.count() - 1, self.options_button)
+        else:
+            self.tools_layout.addWidget(self.options_button)
+
+    def option_changed(self, option, value):
+        """Handle when the value of an option has changed"""
+        setattr(self, to_text_string(option), value)
+        self.shellwidget.set_namespace_view_settings()
+        if self.setup_in_progress is False:
+            self.sig_option_changed.emit(option, value)
+
+    def show_fig_outline_in_viewer(self, state):
+        """Draw a frame around the figure viewer if state is True."""
+        if state is True:
+            self.figviewer.figcanvas.setStyleSheet(
+                    "FigureCanvas{border: 1px solid lightgrey;}")
+        else:
+            self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}")
+        self.option_changed('show_plot_outline', state)
+
+    def set_shellwidget(self, shellwidget):
+        """Bind the shellwidget instance to the figure browser"""
+        self.shellwidget = shellwidget
+        shellwidget.set_figurebrowser(self)
+        shellwidget.sig_new_inline_figure.connect(self._handle_new_figure)
+
+    def get_actions(self):
+        """Get the actions of the widget."""
+        return self.actions
+
+    def _handle_new_figure(self, fig, fmt):
+        """
+        Handle when a new figure is sent to the ipython console by the
+        kernel.
+        """
+        self.thumnails_sb.add_thumbnail(fig, fmt)
+
+    # ---- Toolbar Handlers
+
+    def zoom_in(self):
+        """
+        Zoom the figure in by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_in()
+
+    def zoom_out(self):
+        """
+        Zoom the figure out by a single step in the figure viewer.
+        """
+        self.figviewer.zoom_out()
+
+    def go_previous_thumbnail(self):
+        """
+        Select the thumbnail previous to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_previous_thumbnail()
+
+    def go_next_thumbnail(self):
+        """
+        Select the thumbnail next to the currently selected one in the
+        thumbnail scrollbar.
+        """
+        self.thumnails_sb.go_next_thumbnail()
+
+    def close_figure(self):
+        """Close the currently selected figure in the thumbnail scrollbar."""
+        self.thumnails_sb.remove_current_thumbnail()
+
+    def close_all_figures(self):
+        """Close all the figures in the thumbnail scrollbar."""
+        self.thumnails_sb.remove_all_thumbnails()
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Widget that handles communications between the IPython Console and
+the Plots plugin
+"""
+
+# ---- Standard library imports
+
+from base64 import decodestring
+
+# ---- Third party library imports
+
+from qtconsole.rich_jupyter_widget import RichJupyterWidget
+# from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
path: spyder/widgets/ipythonconsole/figurebrowser.py
text: @user1:
Please remove this comment

diff_hunk: @@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Widget that handles communications between the IPython Console and
+the Plots plugin
+"""
+
+# ---- Standard library imports
+
+from base64 import decodestring
+
+# ---- Third party library imports
+
+from qtconsole.rich_jupyter_widget import RichJupyterWidget
+# from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+
+
+class FigureBrowserWidget(RichJupyterWidget):
+    """
+    Widget with the necessary attributes and methods to intercept the figures
+    sent by the kernel to the Ipython Console and send it to the Figure
path: spyder/widgets/ipythonconsole/figurebrowser.py
text: @user1:
Ipython -> IPython

diff_hunk: @@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Widget that handles communications between the IPython Console and
+the Plots plugin
+"""
+
+# ---- Standard library imports
+
+from base64 import decodestring
+
+# ---- Third party library imports
+
+from qtconsole.rich_jupyter_widget import RichJupyterWidget
+# from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+
+
+class FigureBrowserWidget(RichJupyterWidget):
+    """
+    Widget with the necessary attributes and methods to intercept the figures
+    sent by the kernel to the Ipython Console and send it to the Figure
+    Explorer plugin. This widget can also block the plotting of inline
+    figure in the Ipython Console so that figures are only plotted in the
path: spyder/widgets/ipythonconsole/figurebrowser.py
text: @user1:
* figure -> figures
* Ipython -> IPython

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
path: spyder/widgets/figurebrowser.py
text: @user1:
`thumnails_sb` -> `thumbnails_sb`

diff_hunk: @@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""Plots Plugin"""
+
+
+# Third party imports
+from qtpy.QtCore import Signal
+from qtpy.QtWidgets import QStackedWidget, QGridLayout
+
+# Local imports
+from spyder.config.base import _
+from spyder.api.plugins import SpyderPluginWidget
+from spyder.api.preferences import PluginConfigPage
+from spyder.utils import icon_manager as ima
+from spyder.widgets.figurebrowser import FigureBrowser
+
+
+class PlotsConfigPage(PluginConfigPage):
+
+    def setup_page(self):
+        pass
+
+
+class Plots(SpyderPluginWidget):
+    """Plots plugin."""
+
+    CONF_SECTION = 'plots'
+    CONFIGWIDGET_CLASS = PlotsConfigPage
+    DISABLE_ACTIONS_WHEN_HIDDEN = False
+    sig_option_changed = Signal(str, object)
+
+    def __init__(self, parent):
+        SpyderPluginWidget.__init__(self, parent)
+
+        # Widgets
+        self.stack = QStackedWidget(self)
+        self.shellwidgets = {}
+
+        # Layout
+        layout = QGridLayout(self)
+        layout.addWidget(self.stack)
+
+        # Initialize plugin
+        self.initialize_plugin()
+
+    def get_settings(self):
+        """Retrieve all Plots configuration settings."""
+        return {name: self.get_option(name) for name in
+                ['mute_inline_plotting',  'show_plot_outline']}
+
+    # ---- Stack accesors
+
+    def set_current_widget(self, fig_browser):
+        """
+        Set the currently visible fig_browser in the stack widget, refresh the
+        actions of the cog menu button and move it to the layout of the new
+        fig_browser.
+        """
+        self.stack.setCurrentWidget(fig_browser)
+        # We update the actions of the options button (cog menu) and
+        # we move it to the layout of the current widget.
+        self.refresh_actions()
+        fig_browser.setup_options_button()
+
+    def current_widget(self):
+        return self.stack.currentWidget()
+
+    def count(self):
+        return self.stack.count()
+
+    def remove_widget(self, fig_browser):
+        self.stack.removeWidget(fig_browser)
+
+    def add_widget(self, fig_browser):
+        self.stack.addWidget(fig_browser)
+
+    # ---- Public API
+
+    def add_shellwidget(self, shellwidget):
+        """
+        Register shell with figure explorer.
+
+        This function opens a new FigureBrowser for browsing the figures
+        in the shell.
+        """
+        shellwidget_id = id(shellwidget)
+        if shellwidget_id not in self.shellwidgets:
+            self.options_button.setVisible(True)
+            fig_browser = FigureBrowser(
+                self, options_button=self.options_button)
+            fig_browser.set_shellwidget(shellwidget)
+            fig_browser.setup(**self.get_settings())
+            fig_browser.sig_option_changed.connect(
+                self.sig_option_changed.emit)
+            fig_browser.thumnails_sb.redirect_stdio.connect(
path: spyder/plugins/plots.py
text: @user1:
This seems to be a grammatical error: `thumnails` should be `thumbnails`, right?

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
+    console to be plotted inline.
+    """
+    sig_option_changed = Signal(str, object)
+    sig_collapse = Signal()
+
+    def __init__(self, parent, options_button=None, plugin_actions=[]):
+        super(FigureBrowser, self).__init__(parent)
+
+        self.shellwidget = None
+        self.is_visible = True
+        self.figviewer = None
+        self.setup_in_progress = False
+
+        # Options :
+
+        self.mute_inline_plotting = None
+        self.show_plot_outline = None
+
+        # Option actions :
+
+        self.mute_inline_action = None
+        self.show_plot_outline_action = None
+
+        self.options_button = options_button
+        self.plugin_actions = plugin_actions
+
+    def setup(self, mute_inline_plotting=None, show_plot_outline=None):
+        """Setup the figure browser with provided settings."""
+        assert self.shellwidget is not None
+
+        self.mute_inline_plotting = mute_inline_plotting
+        self.show_plot_outline = show_plot_outline
+
+        if self.figviewer is not None:
+            self.mute_inline_action.setChecked(mute_inline_plotting)
+            self.show_plot_outline_action.setChecked(show_plot_outline)
+            return
+
+        self.figviewer = FigureViewer()
+        self.figviewer.setStyleSheet("FigureViewer{"
+                                     "border: 1px solid lightgrey;"
+                                     "border-top-width: 0px;"
+                                     "border-bottom-width: 0px;"
+                                     "border-left-width: 0px;"
+                                     "}")
+        self.thumnails_sb = ThumbnailScrollBar(self.figviewer)
+
+        # Option actions :
+
+        self.setup_option_actions(mute_inline_plotting, show_plot_outline)
+
+        # Create the layout :
+
+        main_widget = QSplitter()
+        main_widget.addWidget(self.figviewer)
+        main_widget.addWidget(self.thumnails_sb)
+        main_widget.setFrameStyle(QScrollArea().frameStyle())
+
+        self.tools_layout = QHBoxLayout()
+        toolbar = self.setup_toolbar()
+        for widget in toolbar:
+            self.tools_layout.addWidget(widget)
+        self.tools_layout.addStretch()
+        self.setup_options_button()
+
+        layout = create_plugin_layout(self.tools_layout, main_widget)
+        self.setLayout(layout)
+
+    def setup_toolbar(self):
+        """Setup the toolbar"""
+        savefig_btn = create_toolbutton(
+                self, icon=ima.icon('filesave'),
+                tip=_("Save Image As..."),
+                triggered=self.thumnails_sb.save_current_figure_as)
+
+        saveall_btn = create_toolbutton(
+                self, icon=ima.icon('save_all'),
+                tip=_("Save All Image..."),
path: spyder/widgets/figurebrowser.py
text: @user1:
Image -> Images

diff_hunk: @@ -0,0 +1,843 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Figure browser widget
+
+This is the main widget used in the Plots plugin
+"""
+
+
+# ---- Standard library imports
+
+import os.path as osp
+
+
+# ---- Third library imports
+
+from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
+from qtpy.compat import getsavefilename, getopenfilenames, getexistingdirectory
+from qtpy.QtCore import Qt, Signal, Slot, QRect, QEvent, QObject
+from qtpy.QtGui import QImage, QPixmap, QPainter
+from qtpy.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QMenu,
+                            QVBoxLayout, QWidget, QGridLayout, QFrame,
+                            QScrollArea, QPushButton, QScrollBar, QSizePolicy,
+                            QSpinBox, QSplitter, QStyleOptionSlider, QStyle)
+
+
+# ---- Local library imports
+
+from spyder.config.base import _
+from spyder.py3compat import is_unicode, to_text_string
+from spyder.utils import icon_manager as ima
+from spyder.utils.qthelpers import (add_actions, create_action,
+                                    create_toolbutton, create_plugin_layout,
+                                    MENU_SEPARATOR)
+from spyder.utils.misc import getcwd_or_home
+
+
+def save_figure_tofile(fig, fmt, fname):
+    """Save fig to fname in the format specified by fmt."""
+    root, ext = osp.splitext(fname)
+    if ext == '.png' and fmt == 'image/svg+xml':
+        qimg = svg_to_image(fig)
+        qimg.save(fname)
+    else:
+        if fmt == 'image/svg+xml' and is_unicode(fig):
+            fig = fig.encode('utf-8')
+
+        with open(fname, 'wb') as f:
+            f.write(fig)
+
+
+def get_unique_figname(dirname, root, ext):
+    i = 1
+    figname = root + '_%d' % i + ext
+    while True:
+        if osp.exists(osp.join(dirname, figname)):
+            i += 1
+            figname = root + '_%d' % i + ext
+        else:
+            return osp.join(dirname, figname)
+
+
+class FigureBrowser(QWidget):
+    """
+    Widget to browse the figures that were sent by the kernel to the ipython
path: spyder/widgets/figurebrowser.py
text: @user1:
ipython -> IPython