# -*- coding: utf-8 -*-
"""
mplbasewidget.py

Base class for matplotlib widget for PyQt.

Copyright (C) 2018 Tong Zhang <zhangt@frib.msu.edu>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
"""

import time
import numpy as np
from collections import deque
from collections import OrderedDict
from functools import partial

from PyQt5.QtCore import QTimer
from PyQt5.QtCore import QVariant
from PyQt5.QtCore import Qt
from PyQt5.QtCore import pyqtProperty
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot

from PyQt5.QtGui import QColor
from PyQt5.QtGui import QFont
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QPalette
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QResizeEvent

from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QMenu
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget

import matplotlib as mpl
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.lines import Line2D
from matplotlib.ticker import AutoMinorLocator
from matplotlib.ticker import NullLocator

from mpl4qt.widgets.mpltoolbar import MToolbar
from mpl4qt.widgets.utils import ALL_COLORMAPS
from mpl4qt.widgets.utils import AUTOFORMATTER
from mpl4qt.widgets.utils import AUTOFORMATTER_MATHTEXT
from mpl4qt.widgets.utils import BOOTSTRAP_GREEN
from mpl4qt.widgets.utils import BOOTSTRAP_RED
from mpl4qt.widgets.utils import LINE_STY_VALS
from mpl4qt.widgets.utils import LINE_DS_VALS
from mpl4qt.widgets.utils import MatplotlibCurveWidgetSettings
from mpl4qt.widgets.utils import SCALE_STY_VALS
from mpl4qt.widgets.utils import cycle_list_next
from mpl4qt.widgets.utils import mfont_to_qfont
from mpl4qt.widgets.utils import mplcolor2hex
from mpl4qt.widgets.utils import set_font
from mpl4qt.widgets.utils import generate_formatter
from mpl4qt.widgets.utils import is_cmap_valid

MPL_VERSION = mpl.__version__
DTMSEC = 500  # msec
DTSEC = DTMSEC / 1000.0  # sec


class BasePlotWidget(QWidget):
    # combo keyshorts, keystring, timestamp
    keycombo_cached = pyqtSignal(str, float)

    # indices list of points selected by lasso tool,
    # ind: array, pts: array (selected)
    # for i,idx in enumerate(ind): idx, pts[i]
    selectedIndicesUpdated = pyqtSignal(QVariant, QVariant)

    # zoomed ROI changed
    zoom_roi_changed = pyqtSignal(tuple, tuple)

    # grid
    gridOnUpdated = pyqtSignal(bool)

    # legend
    legendOnUpdated = pyqtSignal(bool)

    # autoscale
    autoScaleOnUpdated = pyqtSignal(bool)

    # bg color
    bgColorChanged = pyqtSignal(QColor)

    # xy pos, x,y (default) or x,y,z
    xyposUpdated = pyqtSignal(list)

    # cross markers updated, is_new_marker?, x, y, mk_name
    markerUpdated = pyqtSignal(bool, float, float, 'QString')

    # selected point/line
    selectedPointChanged = pyqtSignal(float, float)
    selectedLineChanged = pyqtSignal(Line2D)

    # shaded area updated (mpltoolbar)
    shaded_area_updated = pyqtSignal(tuple, tuple)

    # xlimit is changed
    xlimitMinChanged = pyqtSignal(float)
    xlimitMaxChanged = pyqtSignal(float)
    # ylimit is changed
    ylimitMinChanged = pyqtSignal(float)
    ylimitMaxChanged = pyqtSignal(float)

    def __init__(self, parent=None, show_toolbar=True, **kws):
        super(BasePlotWidget, self).__init__(parent)
        self.widget_type = '__BasePlotWidget'
        self.figure = Figure()
        self.axes = self.figure.add_subplot(111)
        self.axes.set_picker(True)
        self.init_figure()
        self.canvas = FigureCanvas(self.figure)
        self.setParent(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.sys_bg_color = self.palette().color(QPalette.Background)
        self.sys_fg_color = self.palette().color(QPalette.Foreground)
        DEFAULT_FONTS = {
            'title': QFontDatabase.systemFont(QFontDatabase.TitleFont),
            'fixed': QFontDatabase.systemFont(QFontDatabase.FixedFont),
            'general': QFontDatabase.systemFont(QFontDatabase.GeneralFont),
        }
        self.sys_label_font = DEFAULT_FONTS['general']
        self.sys_title_font = DEFAULT_FONTS['title']
        self.post_style_figure()
        # set up layout
        self.set_up_layout()

        self.adjustSize()
        self.set_context_menu()

        # track (x,y)
        self.canvas.mpl_connect('motion_notify_event', self.on_motion)

        # key press
        self.canvas.mpl_connect('key_press_event', self.on_key_press)

        # key release
        self.canvas.mpl_connect('key_release_event', self.on_key_release)

        # pick
        self.canvas.mpl_connect('pick_event', self.on_pick)

        # button
        self.canvas.mpl_connect('button_press_event', self.on_press)
        self.canvas.mpl_connect('button_release_event', self.on_release)

        self.canvas.mpl_connect('scroll_event', self.on_scroll)

        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.canvas.setFocus()

        # patches container: mk_area,
        # see draw_shade_area()
        self._patches = {}

        # dnd
        self.setAcceptDrops(True)

        # window/widget/dialog handlers
        self._handlers = {}

        # cross markers
        self._markers = OrderedDict() # list of {mk_name: [hl,vl,cp,pt,(x,y)]}
        self._to_add_marker = False
        self._added_marker = False # if added or not
        self._marker_id = 1 # initial marker id, always increase, even for deletion
        self._marker_with_xy = False  # anote with (x,y)
        self._visible_hvlines = True  # default visibility
        self.markerUpdated.connect(self.on_cross_markers_update)

        # pan
        self._pan_on = False

        # keypress cache
        self.dq_keycombo = deque([], 2)
        self.keycombo_cached.connect(self.on_update_keycombo_cache)

        # tb_toggle
        self._fig_tb_toggle = show_toolbar
        if self._fig_tb_toggle:
            # show mpltool
            self.__show_mpl_tools()

        #
        self.as_ann = None
        self.autoScaleOnUpdated.connect(self.on_autoscale_toggled)

        # add marker mpltool
        self._mk_add_hint_ann = None

        # [(lbl, (o,lw,mw))]
        self._last_sel_lines = {}

    def on_cross_markers_update(self):
        # cross markers updated.
        if len(self._markers) == 2:
            w = self._handlers.get('w_mpl_tools', None)
            if w is None:
                return  # usually is not None
            w.on_show_mks()
            w.mk_view.close()

    def draw_shade_area(self, p1, p2, **kws):
        # see markers view
        from matplotlib.patches import Rectangle
        def f(p1, p2):
            x1, y1 = p1
            x2, y2 = p2
            pts = sorted([[x1, y1], [x2, y2], [x1, y2], [x2, y1]])
            p1, p4 = pts[0], pts[-1]
            return p1, p4[0] - p1[0], p4[1] - p1[1]
        p = Rectangle(*f(p1, p2), **kws)
        self._patches['mk_area'] = p
        self.axes.add_patch(p)
        self.update_figure()

    @pyqtSlot(bool)
    def on_autoscale_toggled(self, auto_scale_enabled):
        # if auto scale is enabled, put text label
        if auto_scale_enabled:
            if self.as_ann is None:
                self.as_ann = self.axes.annotate('AutoScale is Enabled',
                            xy=(1.0, 1.01),
                            ha='right', va='bottom',
                            xycoords=('axes fraction'),
                            color='w',
                            bbox=dict(
                                boxstyle='round,pad=0.3',
                                fc=BOOTSTRAP_GREEN, ec=BOOTSTRAP_GREEN,
                                lw=1.0, alpha=0.8))
            else:
                self.as_ann.set_visible(True)
        else:
            if self.as_ann is not None:
                self.as_ann.set_visible(False)
        self.update_figure()

    @pyqtSlot(bool, 'QString', bool)
    def on_marker_add_checked(self, is_checked, mk_name, update_flag):
        # Add marker tool is checked.
        if update_flag:
            text = "Updating Marker ({}) is Activated, Finish by CTRL+M\nStart New by CTRL+M".format(mk_name)
        else:
            text = "Adding Marker ({}) is Activated, Finish by CTRL+M\nStart New by CTRL+M".format(mk_name)
        if is_checked:
            if self._mk_add_hint_ann is None:
                self._mk_add_hint_ann = self.axes.annotate(
                            text,
                            xy=(0, 1.01),
                            ha='left', va='bottom',
                            xycoords=('axes fraction'),
                            color='w',
                            bbox=dict(
                                boxstyle='round,pad=0.3',
                                fc=BOOTSTRAP_RED, ec=BOOTSTRAP_RED,
                                lw=1.0, alpha=0.8))
            else:
                self._mk_add_hint_ann.set_text(text)
                self._mk_add_hint_ann.set_visible(True)
        else:
            if self._mk_add_hint_ann is not None:
                self._mk_add_hint_ann.set_visible(False)
        self.update_figure()

    def get_crossmk_config(self, name):
        # get cross marker (w/ lines, text) config by name
        hl, _, cp, pt, _, = self._markers[name]
        return {'ls': hl.get_ls(), 'lw': hl.get_lw(),
                'c': hl.get_c(),
                'line_visible': hl.get_visible(),
                'line_alpha': hl.get_alpha(),
                'ms': cp.get_ms(), 'mk': cp.get_marker(),
                'mew': cp.get_mew(), 'mec': cp.get_mec(),
                'mfc': cp.get_mfc(),
                'mk_visible': cp.get_visible(),
                'mk_alpha': cp.get_alpha(),
                'text_visible': pt.get_visible(),
                'text_color': pt.get_color(),
                'text_content': pt.get_text(),
                'text_alpha': pt.get_bbox_patch().get_alpha(),}

    def draw_hvlines(self, x0, y0, name, mc=None):
        if name in self._markers:
            is_new_marker = False
            hl, vl, cp, pt, _ = self._markers[name]
            if mc is None:
                mc = hl.get_color()
        else:
            is_new_marker = True
            hl, vl, cp, pt = None, None, None, None
            assert mc is not None # mc must be given

        if hl is None:
            hl = self.axes.axhline(y0,
                                   alpha=0.8, color=mc, ls='--')
            hl.set_label('_H-Line {}'.format(name))
        else:
            hl.set_ydata([y0, y0])

        if vl is None:
            vl = self.axes.axvline(x0,
                                   alpha=0.8, color=mc, ls='--')
            vl.set_label('_V-Line {}'.format(name))
        else:
            vl.set_xdata([x0, x0])

        if cp is None:
            cp, = self.axes.plot([x0], [y0], 'o',
                                 mec=mc, mfc='w', mew=2.0, alpha=0.9)
            cp.set_label('_Cross-Point {}'.format(name))
            if self._marker_with_xy:
                text = '{0:g},{1:g}'.format(x0, y0)
            else:
                text = name
            pt = self.axes.annotate(text,
                    color='#000000', xy=(x0, y0), xytext=(15, 15),
                    xycoords="data", textcoords="offset pixels",
                    bbox=dict(boxstyle="round", fc='w'))
            pt.get_bbox_patch().set_alpha(0.5)
        else:
            cp.set_data([x0], [y0])
            pt.xy = (x0, y0)
            if self._marker_with_xy:
                pt.set_text('{0:g},{1:g}'.format(x0, y0))
            else:
                pt.set_text(name)
            self._markers[name][-1] = (x0, y0)

        if is_new_marker:
            self._markers[name] = [hl, vl, cp, pt, (x0, y0)]

        self.markerUpdated.emit(is_new_marker, x0, y0, name)
        self.update_figure()

    def set_visible_hvlines(self, flag=True):
        """Set all markers visible (*flag* is True) or invisible (*flag* is False).
        """
        self._visible_hvlines = flag
        for name, (hl, vl, cp, pt, _,) in self._markers.items():
            for o in (hl, vl, cp, pt):
                o.set_visible(flag)
        self.update_figure()

    def __show_mpl_tools(self):
        if 'w_mpl_tools' in self._handlers:
            w = self._handlers['w_mpl_tools']
        else:
            w = MToolbar(self.figure.canvas, self)
            self._handlers['w_mpl_tools'] = w
            w.selectedIndicesUpdated.connect(self.on_selected_indices)
            w.zoom_roi_changed.connect(self.on_zoom_roi_changed)
            w.shaded_area_updated.connect(self.on_shaded_area_updated)
        w.show_toolbar()
        w.floatable_changed.emit(False)

    @pyqtSlot(QVariant, QVariant)
    def on_selected_indices(self, ind, pts):
        self.selectedIndicesUpdated.emit(ind, pts)

    @pyqtSlot(tuple, tuple)
    def on_shaded_area_updated(self, xlim, ylim):
        self.shaded_area_updated.emit(xlim, ylim)

    @pyqtSlot(tuple, tuple)
    def on_zoom_roi_changed(self, xlim, ylim):
        # print("Zoomed Rect ROI: ", xlim, ylim)
        self.zoom_roi_changed.emit(xlim, ylim)

    def set_up_layout(self):
        self.vbox = QVBoxLayout()
        self.vbox.setContentsMargins(0, 0, 0, 0)
        self.vbox.addWidget(self.canvas, 1)
        self.setLayout(self.vbox)

    def post_style_figure(self):
        self.init_prop_settings()
        self.set_figure_color()

    def init_prop_settings(self):
        """Initial settings for properties.
        """
        ## fonts:
        # xy labels
        lbl = self.axes.xaxis.label
        self._fig_xylabel_font = mfont_to_qfont(lbl.get_fontproperties())
        self._fig_xylabel_visible = lbl.get_visible()
        # xy ticklabels
        tklbl = self.axes.get_xticklabels()[0]
        self._fig_xyticks_font = mfont_to_qfont(tklbl.get_fontproperties())
        # title
        title = self.axes.title
        self._fig_title_font = mfont_to_qfont(title.get_fontproperties())
        self._fig_title_visible = title.get_visible()

        ## border, if auto scale is enabled, style could not be changed.
        o = list(self.axes.spines.values())[0]
        # c, lw, ls, vis,
        self._fig_border_color = QColor(mplcolor2hex(o.get_ec()))
        self._fig_border_lw = o.get_linewidth()
        self._fig_border_ls = o.get_linestyle()
        self._fig_border_visible = o.get_visible()

        # aspect
        self._fig_aspect = str(self.axes.get_aspect())

        # tight?
        self._fig_tight_layout = False

        # lbls,title
        self._fig_title = ''
        self._fig_xlabel = ''
        self._fig_ylabel = ''

        # figure, w,h,dpi
        self._fig_width, self._fig_height = self.figure.get_size_inches()
        self._fig_dpi = self.figure.get_dpi()

        # bg color
        self._fig_bgcolor = self.sys_bg_color

        # grid color
        self._fig_grid_color = QColor('gray')
        # grid toggle
        self._fig_grid_toggle = False

        # mticks toggle
        self._fig_mticks_toggle = False

        # legend toggle
        self._legend_toggle = False

        # legend location
        self._legend_location = 0

        # xyticks angle
        self._fig_xticks_angle = 0
        self._fig_yticks_angle = 0

        # xyticks color
        self._fig_ticks_color = self.sys_fg_color

        # tick format
        self._fig_xtick_formatter_type = 'Auto'
        self._fig_xtick_formatter = None  # placeholder only
        self._fig_xtick_cfmt = ''  # c string format for FuncFormatter
        self._fig_ytick_formatter_type = 'Auto'
        self._fig_ytick_formatter = None  # placeholder only
        self._fig_ytick_cfmt = ''  # c string format for FuncFormatter
        self._fig_ticks_enable_mathtext = False  # use math text or not

        # xy axis scale
        self._fig_xscale = 'linear'
        self._fig_yscale = 'linear'

        # xylimits
        self._xlim_min, self._xlim_max = self.axes.get_xlim()
        self._ylim_min, self._ylim_max = self.axes.get_ylim()

        # ticklabels visibility
        xtklbl = self.axes.get_xticklabels()[0]
        ytklbl = self.axes.get_yticklabels()[0]
        self._fig_xticks_visible = xtklbl.get_visible()
        self._fig_yticks_visible = ytklbl.get_visible()

        # auto scale
        self._fig_auto_scale = False  # default disable autoscale

    def on_scroll(self, e):
        if e.inaxes is None:
            return
        if e.step < 0:
            f = 1.05 ** (-e.step)
        else:
            f = 0.95 ** e.step
        self.zoom(e, f)

    def zoom(self, e, factor):
        x0, y0 = e.xdata, e.ydata
        x_left, x_right = self.axes.get_xlim()
        y_bottom, y_up = self.axes.get_ylim()

        self.axes.set_xlim((x0 - (x0 - x_left) * factor,
                            x0 + (x_right - x0) * factor))
        self.axes.set_ylim((y0 - (y0 - y_bottom) * factor,
                            y0 + (y_up - y0) * factor))
        self.update_figure()

    def on_motion(self, evt):
        if evt.inaxes is None:
            return
        x_pos, y_pos = evt.xdata, evt.ydata
        self.xyposUpdated.emit([x_pos, y_pos])

    def on_key_press(self, e):
        k, t = e.key, time.time()
        self.keycombo_cached.emit(k, t)
        QTimer.singleShot(DTMSEC, partial(self._on_delay_pop, k, t))

    def on_key_release(self, e):
        if len(self.dq_keycombo) != 2:
            return
        (k1, t1) = self.dq_keycombo.popleft()
        (k2, t2) = self.dq_keycombo.popleft()
        if t2 - t1 < DTSEC:
            self.process_keyshort_combo(k1, k2)

    def on_pick(self, evt):
        o = evt.artist
        if isinstance(o, Line2D):
            lw0, mw0 = o.get_lw(), o.get_mew()
            x, y = o.get_data()
            ind = evt.ind
            x0, y0 = x[ind][0], y[ind][0]
            o.set_lw(lw0 * 2)
            o.set_mew(mw0 * 2)
            self._last_sel_lines.setdefault(
                    o.get_label(),
                    (o, lw0, mw0))
            self.selectedPointChanged.emit(x0, y0)
            self.selectedLineChanged.emit(o)
            self.update_figure()
        elif isinstance(evt.artist, Axes):
            if self._last_sel_lines:
                for lbl, (o, lw0, mw0) in self._last_sel_lines.items():
                    o.set_lw(lw0)
                    o.set_mew(mw0)
                self.update_figure()
                self._last_sel_lines = {}

    def on_press(self, e):
        if e.inaxes is None:
            return
        if e.button == 1 and self._to_add_marker:
            self.draw_hvlines(e.xdata, e.ydata, self._mk_name, self._current_mc)
            self.set_visible_hvlines(self._visible_hvlines)
            self._added_marker = True
            QGuiApplication.restoreOverrideCursor()

    def on_release(self, e):
        pass

    def dragEnterEvent(self, e):
        pass

    def dropEvent(self, e):
        pass

    def init_figure(self):
        raise NotImplementedError

    def update_figure(self):
        if self._fig_auto_scale:
            try:
                self.axes.relim()
            except:
                pass
            else:
                self.axes.autoscale()
        self.canvas.draw_idle()

    def contextMenuEvent(self, evt):
        self._create_ctxtmenu().exec_(self.mapToGlobal(evt.pos()))

    def _create_ctxtmenu(self):
        menu = QMenu(self)
        config_action = QAction(QIcon(QPixmap(":/tools/config.png")),
                                "Config", menu)
        config_action.setShortcut("c,c")
        config_action.setObjectName('config_action')
        export_action = QAction(QIcon(QPixmap(":/tools/export.png")),
                                "Export", menu)
        import_action = QAction(QIcon(QPixmap(":/tools/import.png")),
                                "Import", menu)
        reset_action = QAction(QIcon(QPixmap(":/tools/reset.png")),
                               "Reset", menu)
        tb_action = QAction(QIcon(QPixmap(":/tools/tools.png")),
                            "Tools", menu)
        tb_action.setObjectName('tb_action')
        tb_action.setShortcut("t,t")
        fitting_action = QAction(QIcon(QPixmap(":/tools/fitting.png")),
                                 "Fitting", menu)
        export_data_action = QAction(QIcon(QPixmap(":/tools/export.png")),
                                "Export Data", menu)
        info_action = QAction(QIcon(QPixmap(":/tools/info.png")),
                                "About", menu)
        keyshort_action = QAction(QIcon(QPixmap(":/tools/keyshort.png")),
                                "Shortcuts", menu)

        menu.addAction(config_action)
        menu.addAction(export_action)
        menu.addAction(import_action)
        menu.addAction(reset_action)
        menu.addSeparator()
        menu.addAction(tb_action)
        menu.addAction(fitting_action)
        menu.addAction(export_data_action)
        menu.addSeparator()
        menu.addAction(keyshort_action)
        menu.addAction(info_action)

        menu.setStyleSheet('QMenu {margin: 2px;}')

        config_action.triggered.connect(self.on_config)
        export_action.triggered.connect(self.on_export_config)
        import_action.triggered.connect(self.on_import_config)
        reset_action.triggered.connect(self.on_reset_config)
        tb_action.triggered.connect(self.toggle_mpl_tools)
        fitting_action.triggered.connect(self.on_fitting_data)
        export_data_action.triggered.connect(self.on_export_data)
        info_action.triggered.connect(self.on_info)
        keyshort_action.triggered.connect(self.kbd_help)

        return menu

    @pyqtSlot()
    def on_fitting_data(self):
        """Fitting data.
        """
        raise NotImplementedError("Fitting data is to be implemented.")

    @pyqtSlot()
    def on_export_data(self):
        raise NotImplementedError("Export data is to be implemented.")

    @pyqtSlot()
    def on_info(self):
        from ._info import get_pkg_info
        QMessageBox.about(self, 'About mpl4qt', get_pkg_info())

    @pyqtSlot()
    def toggle_mpl_tools(self):
        self.setToolbarToggle(not self.getToolbarToggle())

    @pyqtSlot()
    def on_reset_config(self):
        # apply default settings
        raise NotImplementedError("Reset config is to be implemented.")

    @pyqtSlot()
    def on_config(self):
        raise NotImplementedError("Config panel is to be implemented.")

    @pyqtSlot()
    def on_export_config(self):
        filepath, _ = QFileDialog.getSaveFileName(self,
                                                  "Save Settings",
                                                  "./mpl_settings.json",
                                                  "JSON Files (*.json)")
        if not filepath:
            return
        try:
            s = self.get_mpl_settings()
            s.write(filepath, sort_keys=False)
        except:
            QMessageBox.warning(self, "Warning",
                                "Cannot export settings to {}".format(filepath),
                                QMessageBox.Ok)
        else:
            QMessageBox.information(self, "Information",
                                    "Successfully export settings to {}".format(filepath),
                                    QMessageBox.Ok)

    @pyqtSlot()
    def on_import_config(self):
        filepath, _ = QFileDialog.getOpenFileName(self,
                                                  "Open Settings",
                                                  "./mpl_settings.json",
                                                  "JSON Files (*.json)")
        if not filepath:
            return
        self._import_mpl_settings(filepath)

    def apply_mpl_settings(self, settings):
        pass

    def _import_mpl_settings(self, filepath):
        try:
            s = MatplotlibCurveWidgetSettings(filepath)
            self.apply_mpl_settings(s)
        except:
            QMessageBox.warning(self, "Warning",
                                "Cannot import&apply settings with {}".format(filepath),
                                QMessageBox.Ok)
        else:
            QMessageBox.information(self, "Information",
                                    "Successfully import&apply settings with {}".format(filepath),
                                    QMessageBox.Ok)

    def get_mpl_settings(self):
        """Return all the settings for the current figure.
        """
        pass

    def resize_figure(self):
        """Must be triggered for set fig size.
        """
        self.canvas.resizeEvent(QResizeEvent(self.canvas.size(), self.canvas.size()))

    def set_figure_color(self, color=None):
        if color is None:
            color = self.sys_bg_color.getRgbF()
        self.figure.set_facecolor(color)
        self.figure.set_edgecolor(color)
        if MPL_VERSION > "1.5.1":
            self.axes.set_facecolor(color)
        else:
            self.axes.set_axis_bgcolor(color)

    def set_ticks_color(self, color=None):
        if color is None:
            color = self.sys_bg_color.getRgbF()
        all_lbls = self.axes.get_xticklabels() + self.axes.get_yticklabels()
        [lbl.set_color(color) for lbl in all_lbls]

    def set_ticks_visible(self, visible, xoy="x"):
        if getattr(self, "_fig_{}ticks_visible".format(xoy)):
            tklbls = getattr(self.axes, 'get_{}ticklabels'.format(xoy))()
            # !hiding cannot be reversed!
            [i.set_visible(visible) for i in tklbls]
        else:
            getattr(self.axes, '{}axis'.format(xoy)).reset_ticks()
            self.rotate_ticks(self._fig_xticks_angle, 'x')
            self.rotate_ticks(self._fig_yticks_angle, 'y')

    def set_xticks(self, tks):
        self.axes.set_xticks(tks)
        [set_font(lbl, self._fig_xyticks_font) for lbl in self.axes.get_xticklabels()]
        self.update_figure()

    def set_yticks(self, tks):
        self.axes.set_yticks(tks)
        [set_font(lbl, self._fig_xyticks_font) for lbl in self.axes.get_yticklabels()]
        self.update_figure()

    def set_xticklabels(self, tklbls):
        self.axes.set_xticklabels(tklbls)
        self.update_figure()

    def set_yticklabels(self, tklbls):
        self.axes.set_yticklabels(tklbls)
        self.update_figure()

    def toggle_mticks(self, f):
        if f:
            self.axes.xaxis.set_minor_locator(AutoMinorLocator())
            self.axes.yaxis.set_minor_locator(AutoMinorLocator())
        else:
            self.axes.xaxis.set_minor_locator(NullLocator())
            self.axes.yaxis.set_minor_locator(NullLocator())

    def toggle_grid(self,
                    toggle_checked=False,
                    which='major',
                    b=None,
                    color=None,
                    **kws):
        if toggle_checked:
            which = 'both' if kws.get('mticks', True) else 'major'
            self.axes.grid(which=which, color=color, linestyle='--')
        else:
            self.axes.grid(b=False, which='minor')
            self.axes.grid(b=False)

    def set_xylabel_font(self, font=None):
        if font is None:
            font = self.sys_label_font
        set_font(self.axes.xaxis.label, font)
        set_font(self.axes.yaxis.label, font)

    def set_xyticks_font(self, font=None):
        if font is None:
            font = self.sys_label_font
        all_lbls = self.axes.get_xticklabels() + self.axes.get_yticklabels()
        [set_font(lbl, font) for lbl in all_lbls]

    def set_title_font(self, font=None):
        if font is None:
            font = self.sys_title_font
        set_font(self.axes.title, font)

    def set_context_menu(self, ):
        self.setContextMenuPolicy(Qt.DefaultContextMenu)

    def clear_figure(self):
        self.axes.clear()
        self.update_figure()

    def clear_data(self):
        """Set with empty canvas.
        """
        pass

    @pyqtSlot('QString', float)
    def on_update_keycombo_cache(self, key, ts):
        self.dq_keycombo.append((key, ts))

    def _on_delay_pop(self, k, t):
        if (k, t) not in self.dq_keycombo:
            return
        self.process_keyshort(k)
        self.dq_keycombo.remove((k, t))

    def set_border_color(self, c):
        for _, o in self.axes.spines.items():
            o.set_color(c.getRgbF())

    def set_border_lw(self, x):
        for _, o in self.axes.spines.items():
            o.set_linewidth(x)

    def set_border_ls(self, s):
        for _, o in self.axes.spines.items():
            o.set_linestyle(s)

    def set_border_visible(self, f):
        for _, o in self.axes.spines.items():
            o.set_visible(f)

    def getFigureAutoScale(self):
        return self._fig_auto_scale

    @pyqtSlot(bool)
    def setFigureAutoScale(self, f):
        """Set xy limits as autoscale or not.

        Parameters
        ----------
        f : bool
            Toggle for the autoscale.
        """
        self._fig_auto_scale = f
        if f:
            self.set_autoscale()
        #
        self.autoScaleOnUpdated.emit(f)

    figureAutoScale = pyqtProperty(bool, getFigureAutoScale,
                                   setFigureAutoScale)

    def getFigureBorderColor(self):
        return self._fig_border_color

    @pyqtSlot(QColor)
    def setFigureBorderColor(self, c, **kws):
        """Set color for the data boundaries.

        Parameters
        ----------
        c : QColor
            Color of the boundaries.
        """
        self._fig_border_color = c
        self.set_border_color(c)
        self.update_figure()

    figureBorderColor = pyqtProperty(QColor, getFigureBorderColor,
                                     setFigureBorderColor)

    def getFigureBorderLineWidth(self):
        return self._fig_border_lw

    @pyqtSlot(float)
    def setFigureBorderLineWidth(self, x):
        """Set line width for the border.

        Parameters
        ----------
        x : float
            Line width.
        """
        self._fig_border_lw = x
        self.set_border_lw(x)
        self.update_figure()

    figureBorderLineWidth = pyqtProperty(float, getFigureBorderLineWidth,
                                         setFigureBorderLineWidth)

    def getFigureBorderLineStyle(self):
        return self._fig_border_ls

    @pyqtSlot('QString')
    def setFigureBorderLineStyle(self, s):
        """Set line style for the border.

        Parameters
        ----------
        s : str
            String for the line style, see `line style <https://matplotlib.org/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D>`_.
        """
        if s not in LINE_STY_VALS:
            return
        self._fig_border_ls = s
        self.set_border_ls(s)
        self.update_figure()

    figureBorderLineStyle = pyqtProperty('QString',
                                         getFigureBorderLineStyle, setFigureBorderLineStyle)

    def getFigureBorderVisible(self):
        return self._fig_border_visible

    @pyqtSlot(bool)
    def setFigureBorderVisible(self, f):
        """Set borders visible or not.

        Parameters
        ----------
        f : bool
            Line visible (True) or not (False).
        """
        self._fig_border_visible = f
        self.set_border_visible(f)
        self.update_figure()

    figureBorderVisible = pyqtProperty(bool, getFigureBorderVisible,
                                       setFigureBorderVisible)

    def getFigureAspectRatio(self):
        return self._fig_aspect

    @pyqtSlot('QString')
    def setFigureAspectRatio(self, s):
        """Set aspect ratio of the axes.

        Parameters
        ----------
        s : str
            Aspect ratio, 'auto', 'equal' and any number.
        """
        try:
            float(s)
        except ValueError:
            if s in ('auto', 'equal'):
                self._fig_aspect = s
            else:
                return
        else:
            if float(s) <= 0:
                return
            self._fig_aspect = s
        finally:
            self.axes.set_aspect(self._fig_aspect)
            self.update_figure()

    figureAspectRatio = pyqtProperty('QString', getFigureAspectRatio,
                                     setFigureAspectRatio)

    def getTightLayoutToggle(self):
        return self._fig_tight_layout

    @pyqtSlot(bool)
    def setTightLayoutToggle(self, f):
        """Toggle for the tight layout.

        Parameters
        ----------
        f : bool
            Tight layout toggle.
        """
        self._fig_tight_layout = f
        if f:
            # self.figure.set_tight_layout({'pad': 0.1})
            self.figure.subplots_adjust(left=0.05, right=0.98, top=0.98, bottom=0.06)
        else:
            # self.figure.set_tight_layout({'pad': 1.2})
            self.figure.subplots_adjust(left=0.125, right=0.9, top=0.9, bottom=0.10)
        self.update_figure()

    figureTightLayout = pyqtProperty(bool, getTightLayoutToggle,
                                     setTightLayoutToggle)

    def getFigureXlabel(self):
        return self._fig_xlabel

    @pyqtSlot('QString')
    def setFigureXlabel(self, s):
        """Set xlabel string.

        Parameters
        ----------
        s : str
            String for xlabel.
        """
        self._fig_xlabel = s
        self.axes.set_xlabel(s)
        set_font(self.axes.xaxis.label, self._fig_xylabel_font)
        self.update_figure()

    figureXlabel = pyqtProperty('QString', getFigureXlabel, setFigureXlabel)

    def getFigureYlabel(self):
        return self._fig_ylabel

    @pyqtSlot('QString')
    def setFigureYlabel(self, s):
        """Set ylabel string.

        Parameters
        ----------
        s : str
            String for ylabel.
        """
        self._fig_ylabel = s
        self.axes.set_ylabel(s)
        set_font(self.axes.yaxis.label, self._fig_xylabel_font)
        self.update_figure()

    figureYlabel = pyqtProperty('QString', getFigureYlabel, setFigureYlabel)


    def getFigureXYlabelVisible(self):
        return self._fig_xylabel_visible

    @pyqtSlot(bool)
    def setFigureXYlabelVisible(self, f):
        """Set figure xylabels visible or not.

        Parameters
        ----------
        f : bool
            Figure xylabels visible or not.
        """
        self._fig_xylabel_visible = f
        self.axes.xaxis.label.set_visible(f)
        self.axes.yaxis.label.set_visible(f)
        self.update_figure()

    figureXYlabelVisible = pyqtProperty(bool, getFigureXYlabelVisible,
                                        setFigureXYlabelVisible)

    def getFigureTitleVisible(self):
        return self._fig_title_visible

    @pyqtSlot(bool)
    def setFigureTitleVisible(self, f):
        """Set figure title visible or not.

        Parameters
        ----------
        f : bool
            Figure title visible or not.
        """
        self._fig_title_visible = f
        self.axes.title.set_visible(f)
        self.update_figure()

    figureTitleVisible = pyqtProperty(bool, getFigureTitleVisible,
                                      setFigureTitleVisible)

    def getFigureTitle(self):
        return self._fig_title

    @pyqtSlot('QString')
    def setFigureTitle(self, s):
        """Set figure title.

        Parameters
        ----------
        s : str
            Title for the figure.
        """
        self._fig_title = s
        self.axes.set_title(s)
        set_font(self.axes.title, self._fig_title_font)
        self.update_figure()

    figureTitle = pyqtProperty('QString', getFigureTitle, setFigureTitle)

    def getFigureXYlabelFont(self):
        return self._fig_xylabel_font

    @pyqtSlot(QFont)
    def setFigureXYlabelFont(self, font):
        """Set font for x and y labels.

        Parameters
        ----------
        font : QFont
            Font to set.
        """
        self._fig_xylabel_font = font
        self.set_xylabel_font(font)
        self.update_figure()

    figureXYlabelFont = pyqtProperty(QFont, getFigureXYlabelFont,
                                     setFigureXYlabelFont)

    def getFigureTitleFont(self):
        return self._fig_title_font

    @pyqtSlot(QFont)
    def setFigureTitleFont(self, font):
        """Set font for figure title.

        Parameters
        ----------
        font : QFont
            Font to set.
        """
        self._fig_title_font = font
        self.set_title_font(font)
        self.update_figure()

    figureTitleFont = pyqtProperty(QFont, getFigureTitleFont,
                                   setFigureTitleFont)

    def getFigureWidth(self):
        return self._fig_width

    @pyqtSlot(float)
    def setFigureWidth(self, w):
        """Set figure width in inch.

        Parameters
        ----------
        w : float
            Figure width in inch (>= 2.0).
        """
        self._fig_width = max(w, 2.0)
        self.figure.set_size_inches([self._fig_width, self._fig_height])
        self.resize_figure()
        self.update_figure()

    figureWidth = pyqtProperty(float, getFigureWidth, setFigureWidth)

    def getFigureHeight(self):
        return self._fig_height

    @pyqtSlot(float)
    def setFigureHeight(self, h):
        """Set figure height in inch.

        Parameters
        ----------
        h : float
            Figure height in inch (>= 2.0).
        """
        self._fig_height = max(h, 2.0)
        self.figure.set_size_inches([self._fig_width, self._fig_height])
        self.resize_figure()
        self.update_figure()

    figureHeight = pyqtProperty(float, getFigureHeight, setFigureHeight)

    def getFigureDpi(self):
        return self._fig_dpi

    @pyqtSlot(float)
    def setFigureDpi(self, d):
        """Set figure dpi.

        Parameters
        ----------
        d : float
            Figure dpi in [50.0, 600.0].
        """
        self._fig_dpi = min(600.0, max(d, 50.0))
        self.figure.set_dpi(d)
        self.resize_figure()
        self.update_figure()

    figureDPI = pyqtProperty(float, getFigureDpi, setFigureDpi)

    def getXLimitMin(self):
        return self._xlim_min

    @pyqtSlot(float)
    def setXLimitMin(self, x=None):
        """Set minimum of xlimit.

        Parameters
        ----------
        x : float
            Minimum of xlimit.
        """
        if x is None:
            x, _ = self._get_default_xlim()
        self._xlim_min = x
        xmin, xmax = self.get_xlim()
        if x < xmax:
            self.axes.set_xlim([x, xmax])
            self.update_figure()
            self.xlimitMinChanged.emit(x)

    figureXLimitMin = pyqtProperty(float, getXLimitMin, setXLimitMin)

    def getXLimitMax(self):
        return self._xlim_max

    @pyqtSlot(float)
    def setXLimitMax(self, x=None):
        """Set maximum of xlimit.

        Parameters
        ----------
        x : float
            Maximum of xlimit.
        """
        if x is None:
            _, x = self._get_default_xlim()
        self._xlim_max = x
        xmin, xmax = self.get_xlim()
        if x > xmin:
            self.axes.set_xlim([xmin, x])
            self.update_figure()
            self.xlimitMaxChanged.emit(x)

    figureXLimitMax = pyqtProperty(float, getXLimitMax, setXLimitMax)

    def getYLimitMin(self):
        return self._ylim_min

    @pyqtSlot(float)
    def setYLimitMin(self, y=None):
        """Set minimum of ylimit.

        Parameters
        ----------
        y : float
            Minimum of ylimit.
        """
        if y is None:
            y, _ = self._get_default_ylim()
        self._ylim_min = y
        ymin, ymax = self.get_ylim()
        if y < ymax:
            self.axes.set_ylim([y, ymax])
            self.update_figure()
            self.ylimitMinChanged.emit(y)

    figureYLimitMin = pyqtProperty(float, getYLimitMin, setYLimitMin)

    def getYLimitMax(self):
        return self._ylim_max

    @pyqtSlot(float)
    def setYLimitMax(self, y=None):
        """Set maximum of ylimit.

        Parameters
        ----------
        y : float
            Maximum of ylimit.
        """
        if y is None:
            _, y = self._get_default_ylim()
        self._ylim_max = y
        ymin, ymax = self.get_ylim()
        if y > ymin:
            self.axes.set_ylim([ymin, y])
            self.update_figure()
            self.ylimitMaxChanged.emit(y)

    figureYLimitMax = pyqtProperty(float, getYLimitMax, setYLimitMax)

    def getFigureXTicksVisible(self):
        return self._fig_xticks_visible

    @pyqtSlot(bool)
    def setFigureXTicksVisible(self, f):
        """Set xticklabels visible or not.

        Parameters
        ----------
        f : bool
            Object visible (True) or not (False).
        """
        self.set_ticks_visible(f, "x")
        self.update_figure()
        self._fig_xticks_visible = f

    figureXTicksVisible = pyqtProperty(bool, getFigureXTicksVisible,
                                       setFigureXTicksVisible)

    def getFigureYTicksVisible(self):
        return self._fig_yticks_visible

    @pyqtSlot(bool)
    def setFigureYTicksVisible(self, f):
        """Set yticklabels visible or not.

        Parameters
        ----------
        f : bool
            Object visible (True) or not (False).
        """
        self.set_ticks_visible(f, "y")
        self.update_figure()
        self._fig_yticks_visible = f

    figureYTicksVisible = pyqtProperty(bool, getFigureYTicksVisible,
                                       setFigureYTicksVisible)

    def getFigureBgColor(self):
        return self._fig_bgcolor

    @pyqtSlot(QColor)
    def setFigureBgColor(self, color):
        """Set figure background color.

        Parameters
        ----------
        color : QColor
            Color to set.
        """
        self._fig_bgcolor = color
        self.set_figure_color(color.getRgbF())
        self.update_figure()
        self.bgColorChanged.emit(color)

    figureBackgroundColor = pyqtProperty(QColor, getFigureBgColor,
                                         setFigureBgColor)

    def getFigureGridColor(self):
        return self._fig_grid_color

    @pyqtSlot(QColor)
    def setFigureGridColor(self, c, **kws):
        """Set color for the grid line.

        Parameters
        ----------
        c : QColor
            Color of the grid line.
        """
        self._fig_grid_color = c
        self.toggle_grid(
            toggle_checked=self._fig_grid_toggle,
            color=c.getRgbF(),
            **{
                k: v
                for k, v in kws.items() if k not in ('toggle_checked', 'color')
            })
        self.update_figure()

    figureGridColor = pyqtProperty(QColor, getFigureGridColor,
                                   setFigureGridColor)

    def getFigureGridToggle(self):
        return self._fig_grid_toggle

    @pyqtSlot(bool)
    def setFigureGridToggle(self, f, **kws):
        """Toggle for the figure grid.

        Parameters
        ----------
        f : bool
            Figure grid toggle.
        """
        self._fig_grid_toggle = f
        self.toggle_grid(
            toggle_checked=f,
            color=self._fig_grid_color.getRgbF(),
            **{
                k: v
                for k, v in kws.items() if k not in ('toggle_checked', 'color')
            })
        self.update_figure()
        #
        self.gridOnUpdated.emit(f)

    figureGridToggle = pyqtProperty(bool, getFigureGridToggle,
                                    setFigureGridToggle)

    def getFigureMTicksToggle(self):
        return self._fig_mticks_toggle

    @pyqtSlot(bool)
    def setFigureMTicksToggle(self, f):
        """Toggle for the minor ticks.

        Note
        ----
        Before toggle on, be sure the axis scale is linear.

        Parameters
        ----------
        f : bool
            Minor ticks on/off toggle.
        """
        self._fig_mticks_toggle = f

        xscale = self.getFigureXScale()
        yscale = self.getFigureYScale()
        if xscale != 'linear':
            self.setFigureXScale('linear')
        if yscale != 'linear':
            self.setFigureYScale('linear')
        self.toggle_mticks(f)
        if xscale != 'linear':
            self.setFigureXScale(xscale)
        if yscale != 'linear':
            self.setFigureYScale(yscale)

        self.update_figure()

    figureMTicksToggle = pyqtProperty(bool, getFigureMTicksToggle,
                                      setFigureMTicksToggle)

    def getLegendToggle(self):
        return self._legend_toggle

    @pyqtSlot(bool)
    def setLegendToggle(self, f):
        """Toggle for figure legend.

        Parameters
        ----------
        f : bool
            Figure legend on/off toggle.
        """
        self._legend_toggle = f
        if f:
            self._legend_box = self.axes.legend(loc=self._legend_location)
        else:
            try:
                self._legend_box.set_visible(False)
            except AttributeError:
                pass
        self.update_figure()
        #
        self.legendOnUpdated.emit(f)

    figureLegendToggle = pyqtProperty(bool, getLegendToggle, setLegendToggle)

    def getLegendLocation(self):
        return self._legend_location

    @pyqtSlot(int)
    def setLegendLocation(self, i):
        """Set legend location.

        Parameters
        ----------
        i : int
            Index number of legend location,
            see `matplotlib.pyplot.legend <https://matplotlib.org/api/_as_gen/matplotlib.pyplot.legend.html>`_.
        """
        self._legend_location = i
        if self._legend_toggle:
            self._legend_box = self.axes.legend(loc=i)
            self.update_figure()

    figureLegendLocation = pyqtProperty(int, getLegendLocation,
                                        setLegendLocation)

    def getFigureXTicksAngle(self):
        return self._fig_xticks_angle

    @pyqtSlot(float)
    def setFigureXTicksAngle(self, angle):
        """Set rotation angle for the xtick labels.

        Parameters
        ----------
        angle : float
            Angle in degree to rotate.
        """
        self._fig_xticks_angle = angle
        self.rotate_ticks(angle, 'x')
        self.update_figure()

    figureXTicksAngle = pyqtProperty(float, getFigureXTicksAngle,
                                     setFigureXTicksAngle)

    def getFigureYTicksAngle(self):
        return self._fig_yticks_angle

    @pyqtSlot(float)
    def setFigureYTicksAngle(self, angle):
        """Set rotation angle for the ytick labels.

        Parameters
        ----------
        angle : float
            Angle in degree to rotate.
        """
        self._fig_yticks_angle = angle
        self.rotate_ticks(angle, 'y')
        self.update_figure()

    figureYTicksAngle = pyqtProperty(float, getFigureYTicksAngle,
                                     setFigureYTicksAngle)

    def getFigureXYticksFont(self):
        return self._fig_xyticks_font

    @pyqtSlot(QFont)
    def setFigureXYticksFont(self, font):
        """Set font for the tick labels.

        Parameters
        ----------
        font : QFont
            Font to set.
        """
        self._fig_xyticks_font = font
        self.set_xyticks_font(font)
        self.update_figure()

    figureXYticksFont = pyqtProperty(QFont, getFigureXYticksFont,
                                     setFigureXYticksFont)

    def getFigureXYticksColor(self):
        return self._fig_ticks_color

    @pyqtSlot(QColor)
    def setFigureXYticksColor(self, color):
        """Set color for the ticks.

        Parameters
        ----------
        color : QColor
            Color to set.
        """
        self._fig_ticks_color = color
        self.set_ticks_color(color.getRgbF())
        self.update_figure()

    figureXYticksColor = pyqtProperty(QColor, getFigureXYticksColor,
                                      setFigureXYticksColor)

    def getFigureXScale(self):
        return self._fig_xscale

    @pyqtSlot('QString')
    def setFigureXScale(self, s):
        """Set x-axis scale.

        Parameters
        ----------
        s : str
            Scale type, 'linear', 'log', 'symlog', 'logit', etc.
        """
        self._fig_xscale = s
        self.axes.set_xscale(s)
        self.update_figure()

    figureXScale = pyqtProperty('QString', getFigureXScale, setFigureXScale)

    def getFigureYScale(self):
        return self._fig_yscale

    @pyqtSlot('QString')
    def setFigureYScale(self, s):
        """Set y-axis scale.

        Parameters
        ----------
        s : str
            Scale type, 'linear', 'log', 'symlog', 'logit', etc.
        """
        self._fig_yscale = s
        self.axes.set_yscale(s)
        self.update_figure()

    figureYScale = pyqtProperty('QString', getFigureYScale, setFigureYScale)

    def getToolbarToggle(self):
        return self._fig_tb_toggle

    @pyqtSlot(bool)
    def setToolbarToggle(self, f):
        """Toggle for the mpl toolbar.

        Parameters
        ----------
        f : bool
            Turn on/off mpl toolbar.
        """
        self._fig_tb_toggle = f
        w = self._handlers.get('w_mpl_tools', None)
        if w is not None and not f:
            w.floatable_changed.emit(True)
            w.close()
        else:
            self.__show_mpl_tools()
        self.update_figure()

    figureToolbarToggle = pyqtProperty(bool, getToolbarToggle, setToolbarToggle)

    def _get_default_xlim(self):
        """limit range from data
        """
        try:
            xmin, xmax = self._x_data.min(), self._x_data.max()
        except:
            xmin, xmax = self.axes.get_xlim()
        x0, xhw = (xmin + xmax) * 0.5, (xmax - xmin) * 0.5
        return x0 - xhw * 1.1, x0 + xhw * 1.1

    def get_xlim(self):
        return self.axes.get_xlim()

    def _get_default_ylim(self):
        """limit range from data
        """
        try:
            ymin, ymax = self._y_data.min(), self._y_data.max()
        except:
            ymin, ymax = self.axes.get_ylim()
        y0, yhw = (ymin + ymax) * 0.5, (ymax - ymin) * 0.5
        return y0 - yhw * 1.1, y0 + yhw * 1.1

    def get_ylim(self):
        return self.axes.get_ylim()

    @pyqtSlot('QString', 'QString')
    def setXTickFormat(self, ftype, cfmt):
        if ftype == 'Auto':
            self._setXTickAutoFormat(ftype)
        elif ftype == 'Custom':
            self._setXTickCustomFormat(ftype, cfmt)

    def _setXTickAutoFormat(self, ftype):
        """Set x-axis ticks formatter with Auto style.

        Parameters
        ----------
        ftype : str
            Type of formatter, 'Auto'.
        """
        self._fig_xtick_formatter_type = ftype
        if self._fig_ticks_enable_mathtext:
            formatter = AUTOFORMATTER_MATHTEXT
        else:
            formatter = AUTOFORMATTER
        self._fig_xtick_formatter = formatter
        self.axes.xaxis.set_major_formatter(formatter)
        self.update_figure()

    def _setXTickCustomFormat(self, ftype, cfmt):
        """Set x-axis ticks formatter with custom style.

        Parameters
        ----------
        ftype : str
            Type of formatter, 'Custom'.
        cfmt : str
            C style string specifier.
        """
        self._fig_xtick_formatter_type = ftype
        self._fig_xtick_cfmt = cfmt
        formatter = generate_formatter(cfmt, math_text=self._fig_ticks_enable_mathtext)
        self._fig_xtick_formatter = formatter
        self.axes.xaxis.set_major_formatter(formatter)
        self.update_figure()

    @pyqtSlot('QString', 'QString')
    def setYTickFormat(self, ftype, cfmt):
        if ftype == 'Auto':
            self._setYTickAutoFormat(ftype)
        elif ftype == 'Custom':
            self._setYTickCustomFormat(ftype, cfmt)

    def _setYTickAutoFormat(self, ftype):
        """Set y-axis ticks formatter with Auto style.

        Parameters
        ----------
        ftype : str
            Type of formatter, 'Auto'.
        """
        self._fig_ytick_formatter_type = ftype
        if self._fig_ticks_enable_mathtext:
            formatter = AUTOFORMATTER_MATHTEXT
        else:
            formatter = AUTOFORMATTER
        self._fig_ytick_formatter = formatter
        self.axes.yaxis.set_major_formatter(formatter)
        self.update_figure()

    def _setYTickCustomFormat(self, ftype, cfmt):
        """Set y-axis ticks formatter with custom style.

        Parameters
        ----------
        ftype : str
            Type of formatter, 'Custom'.
        cfmt : str
            C style string specifier.
        """
        self._fig_ytick_formatter_type = ftype
        self._fig_ytick_cfmt = cfmt
        formatter = generate_formatter(cfmt, math_text=self._fig_ticks_enable_mathtext)
        self._fig_ytick_formatter = formatter
        self.axes.yaxis.set_major_formatter(formatter)
        self.update_figure()

    def rotate_ticks(self, angle, axis):
        """Rotate *axis* ticks by *angle* in degree.
        """
        lbls = getattr(self.axes, "get_{}ticklabels".format(axis))()
        for o in lbls:
            o.set_rotation(angle)

    def set_autoscale(self, axis='both'):
        self.axes.relim(visible_only=True)
        self.axes.autoscale(axis=axis)
        self.update_figure()

    def process_keyshort_combo(self, k1, k2):
        """Override this method to define combo keyshorts.
        """
        # print("Capture key combo: ", k1, k2)
        if k1 == 'a' and k2 == 'x':
            # auto xscale
            self.set_autoscale('x')
        elif k1 == 'a' and k2 == 'y':
            # auto yscale
            self.set_autoscale('y')
        elif k1 == 'a' and k2 == 'a':
            if self.widget_type == 'image':
                self.setAutoColorLimit(not self.getAutoColorLimit())
            # turn on/off autoscale
            self.setFigureAutoScale(not self.getFigureAutoScale())
        elif k1 == 'a' and k2 == 'c' and self.widget_type == 'image':
            # auto color range
            self.on_auto_clim()
        elif k1 == 'shift' and k2 == '?':
            # help msgbox
            self.kbd_help()
        elif k1 == 'c' and k2 == 'c':
            self._create_ctxtmenu().findChild(QAction, 'config_action').triggered.emit()
        elif k1 == 't' and k2 == 't':
            self._create_ctxtmenu().findChild(QAction, 'tb_action').triggered.emit()
        elif k1 == 'd' and k2 == 's' and self.widget_type in ('curve', 'errorbar'):
            # circulate curve drawstyle
            self.setLineDrawStyle(
                cycle_list_next(list(LINE_DS_VALS), self.getLineDrawStyle()))

    def process_keyshort(self, k):
        """Override this method to define keyshorts.
        """
        # print("Capture key: ", k)
        if k == 'g':
            # turn on/off grid
            self.setFigureGridToggle(not self.getFigureGridToggle())
        elif k == 'a': # and self.widget_type != 'image':
            # autoscale
            self.set_autoscale()
        elif k == 'm':
            # turn on/off mticks
            self.setFigureMTicksToggle(not self.getFigureMTicksToggle())
        elif k == 't':
            # turn on/off tightlayout
            self.setTightLayoutToggle(not self.getTightLayoutToggle())
        elif k == 'l':
            # turn on/off legend
            self.setLegendToggle(not self.getLegendToggle())
        elif k == 'r':
            # force refresh
            self.force_update()
        elif k == 's' and self.widget_type != 'image':
            # circulate y-axis scale type
            self.setFigureYScale(
                cycle_list_next(SCALE_STY_VALS, self.getFigureYScale()))
        elif k == 'c' and self.widget_type == 'image':
            # circulate image colormap
            self.setColorMap(
                cycle_list_next(ALL_COLORMAPS, self.getColorMap()))

    def kbd_help(self):
        """Help message box for keyboard shortcuts.
        """
        from .kbdhelpdialog import KbdHelpDialog
        w = KbdHelpDialog(self)
        w.setWindowTitle("Keyboard Shortcuts Help")
        w.exec_()

    def set_xlimit(self, *args):
        """Set xlimit with new limit, e.g. `set_xlimit(xmin, xmax)`.

        See Also
        --------
        setXLimitMin, setXLimitMax
        """
        self.axes.set_xlim(args)
        self.update_figure()

    def set_ylimit(self, *args):
        """Set ylimit with new limit.

        See Also
        --------
        setYLimitMin, setYLimitMax
        """
        self.axes.set_ylim(args)
        self.update_figure()


class MatplotlibBaseWidget(BasePlotWidget):
    """MatplotlibBaseWidget(BasePlotWidget)
    """

    def __init__(self, parent=None):
        super(MatplotlibBaseWidget, self).__init__(parent)
        self.widget_type = 'base'

    def init_figure(self):
        pass

    @pyqtSlot()
    def on_config(self):
        from .mplconfig import MatplotlibConfigPanel
        config_panel = MatplotlibConfigPanel(self)
        config_panel.exec_()

    def update_figure(self):
        if self._fig_auto_scale:
            try:
                self.axes.relim()
            except:
                pass
            else:
                self.axes.autoscale()
        self.canvas.draw_idle()


class MatplotlibCMapWidget(BasePlotWidget):
    def __init__(self, parent=None):
        super(MatplotlibCMapWidget, self).__init__(parent, False)
        self.figure.set_size_inches((self.getFigureWidth(), 0.2))
        self.figure.set_tight_layout(True)
        self.figure.subplots_adjust(
            top=0.9999, bottom=0.0001, left=0.0001, right=0.9999)
        self.axes.set_axis_off()

        # reverse cmap flag, '' or '_r'
        self._rcmap = ''

    def init_figure(self):
        gradient = np.linspace(0, 1, 256)
        gradient = np.vstack((gradient, gradient))
        self.im = self.axes.imshow(gradient, aspect='auto')

    def set_cmap(self, c):
        if not is_cmap_valid(c):
            return
        self._cmap = c
        self.im.set_cmap(self._cmap + self._rcmap)
        self.update_figure()

    def set_reverse_cmap(self, f):
        self._rcmap = '_r' if f else ''
        self.set_cmap(self._cmap)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication

    app = QApplication([])
    window = MatplotlibBaseWidget()
    window.show()

    app.exec_()

matplotlib.ticker.AutoMinorLocator matplotlib.figure.Figure matplotlib.ticker.NullLocator matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg