from __future__ import print_function
from petram.mfem_viewer import MFEM_menus
from functools import wraps
import threading

import os
import sys
import wx
import traceback
import numpy as np
import weakref
import subprocess as sp
import petram.helper.pickle_wrapper as pk
import binascii
from collections import defaultdict
from weakref import WeakKeyDictionary as WKD
from string import digits

import ifigure.widgets.dialog as dialog
import ifigure.events
from ifigure.utils.cbook import BuildMenu
from ifigure.widgets.dialog import progressbar
from ifigure.utils.edit_list import EditListPanel
from ifigure.utils.edit_list import EDITLIST_CHANGED
from ifigure.utils.edit_list import EDITLIST_CHANGING
from ifigure.utils.edit_list import EDITLIST_SETFOCUS
from ifigure.widgets.miniframe_with_windowlist import DialogWithWindowList, MiniFrameWithWindowList, WithWindowList_MixIn

import petram
from petram.pi.simple_frame_plus import SimpleFramePlus

import petram.debug as debug
dprint1, dprint2, dprint3 = debug.init_dprints('Dlg_plot_sol')

pane_colour1 = wx.Colour(245, 245, 245, 255)
pane_colour2 = wx.Colour(235, 235, 235, 255)

allow_single_mode = False


def setup_figure(fig, fig2):
    fig.nsec(1)
    fig.threed('on')
    fig.property(fig.get_axes(0), 'axis', False)
    fig.get_page(0).set_nomargin(True)
    fig.property(fig.get_page(0), 'bgcolor', 'white')

    aspect = fig2.property(fig2.get_axes(0), 'aspect')
    fig.property(fig.get_axes(0), 'aspect', aspect)

    xlim = fig2.xlim()
    ylim = fig2.ylim()
    zlim = fig2.zlim()
    fig.xlim(xlim)
    fig.ylim(ylim)
    fig.zlim(zlim)

    fig.view('noclip')


def sort_subdirs(choices):
    def extract_trailing_digits(txt):
        return txt[len(txt.rstrip(digits)):]

    sorted_subs = [x[1] for x in sorted([(int(extract_trailing_digits(x)), x)
                                         for x in choices if len(extract_trailing_digits(x)) != 0])]

    if '' in choices:
        sorted_subs = [''] + sorted_subs

    return sorted_subs


def read_solinfo_remote(user, server, path):
    txt = "$PetraM/bin/get_soldir_info.sh " + path
    command = ["ssh",  "-o",
               "PasswordAuthentication=no",
               "-o",
               "PreferredAuthentications=publickey",
               user + '@' + server, txt]

    print("Executing...", ' '.join(command))
    p = sp.Popen(command, stdout=sp.PIPE, stderr=sp.STDOUT)

    try:
        timeout = False
        outs, errs = p.communicate(timeout=30)
    except sp.TimeoutExpired:
        timeout = True
        p.kill()
        outs, errs = p.communicate()
    except BaseException:
        import traceback
        traceback.print_stack()
        assert False, "Communication Error"

    res = [x.strip() for x in outs.decode('utf-8').split('\n')]
    for x in res:
        if x.find("Permission denied") != -1:
            assert False, "Connection Failed\n" + "\n".join(res)

    if timeout:
        assert False, "Connection timeout"

    res = [x for x in res if len(x) > 0]
    res = res[-1].strip()

    try:
        res2 = pk.loads(binascii.a2b_hex(res))
    except binascii.Error:
        print("Failed to call: "+" ".join(command))
        print("res is :", res)
        raise

    if not res2[0]:
        assert False, res2[1]

    return res2[1]


ThreadEnd = wx.NewEventType()
EVT_THREADEND = wx.PyEventBinder(ThreadEnd, 1)


class _XY(tuple):
    def __call__(self, value):
        return (0, 0, 1., -value)


class _YZ(tuple):
    def __call__(self, value):
        return (1, 0, 0., -value)


class _ZX(tuple):
    def __call__(self, value):
        return (0, 1., 0., -value)


def get_mapper(mesh_in):
    from petram.mesh.mesh_utils import FaceOf, EdgeOf, PointOf

    def mapper1(*args):
        return FaceOf(args, mesh=mesh_in)

    def mapper2(*args):
        return EdgeOf(args, mesh=mesh_in)

    def mapper3(*args):
        return PointOf(args, mesh=mesh_in)
    return mapper1, mapper2, mapper3


def run_in_piScope_thread(func):
    @wraps(func)
    def func2(*args, **kwargs):
        self = args[0]

        title = self.GetTitle()
        app = wx.GetApp().TopWindow
        petram = app.proj.setting.parameters.eval('PetraM')

        if self._plot_thread is not None:
            if self._plot_thread.is_alive():
                wx.CallAfter(dialog.showtraceback,
                             parent=self,
                             txt='Previous Job is Running',
                             title='Error',
                             traceback='')
                return
        self.SetTitle(title + '(*** processing ***)')
        maxt = app.aconfig.setting['max_thread']
        if len(app.logw.threadlist) < maxt:

            dlg = progressbar(self, '', 'In progress', 5, modeless=True)
            dlg.Show()

            def caller(*in_args, **in_kwargs):
                try:
                    func(*in_args, **in_kwargs)
                except BaseException:
                    pass
                wx.CallAfter(dlg.Destroy)
                wx.CallAfter(self.set_title_no_status)

            t = threading.Thread(target=caller, args=args, kwargs=kwargs)
            self._plot_thread = t
            petram._status = 'evaluating sol...'
            ifigure.events.SendThreadStartEvent(petram,
                                                w=app,
                                                thread=t,
                                                useProcessEvent=True)
    return func2


# class DlgPlotSol(MiniFrameWithWindowList):
# class DlgPlotSol(DialogWithWindowList):


class DlgPlotSol(SimpleFramePlus):
    def __init__(self, parent, id=wx.ID_ANY, title='Plot Solution', **kwargs):
        '''
        (use this style if miniframe is used)
        style=(wx.CAPTION|
                       wx.CLOSE_BOX|
                       wx.MINIMIZE_BOX|
                       wx.RESIZE_BORDER|
                       wx.FRAME_FLOAT_ON_PARENT)
        '''
        # style =  wx.CAPTION|wx.CLOSE_BOX#|wx.RESIZE_BORDER
        style = (wx.CAPTION |
                 wx.CLOSE_BOX |
                 wx.MINIMIZE_BOX |
                 wx.RESIZE_BORDER |
                 wx.FRAME_FLOAT_ON_PARENT)
        #         wx.FRAME_TOOL_WINDOW : this styles may not work on Windows/Mac

        from petram.sol.evaluators import def_config
        self.config = def_config

        from petram.debug import debug_evaluator_mp
        self.config["mp_debug"] = debug_evaluator_mp

        remote = parent.model.param.eval('remote')
        if remote is not None:
            host = parent.model.param.eval('host')
            self.config['cs_soldir'] = remote['rwdir']
            self.config['cs_server'] = host.getvar('server')
            self.config['cs_user'] = host.getvar('user')
            self.config['cs_ssh_opts'] = host.get_multiplex_opts()

        super(
            DlgPlotSol,
            self).__init__(
            parent,
            id,
            title,
            style=style,
            **kwargs)

        use_auinb = True
        if use_auinb:
            from wx.aui import AuiNotebook
            style = wx.aui.AUI_NB_TOP | wx.aui.AUI_NB_TAB_SPLIT | wx.aui.AUI_NB_TAB_MOVE | wx.aui.AUI_NB_SCROLL_BUTTONS
            self.nb = AuiNotebook(self, style=style)
        else:
            # Using standard Notebook. This one does not support tooltip
            self.nb = wx.Notebook(self, style=style)

        box = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(box)
        box.Add(self.nb, 1, wx.EXPAND | wx.ALL, 1)

        tabs = ['GeomBdr', 'Points', 'Edge', 'Bdr', 'Bdr(arrow)', 'Slice',
                'Probe', 'Integral', 'Config']
        tips = ["Geometry boundary plot:\n - Plot geometry/mesh boundaries",
                "Point cloud:\n - Scatter plot (provide coordinates)",
                "Edge plot:\n - Plot data on the selected geometry edge(s) or 1D domain(s)\n - Define abscissa in Expression(x) for data(x) 2D plot",
                "Boundary plot:\n - Plot data on the selected geometry boundary surface(s) or 2D domain(s)",
                "Boundary arrow plot:\n - Arrow plot on the selected geometry boundary surface(s) or 2D domain(s)",
                "Slice plot:\n - Plot data in a slice of the selected 3D domain(s) or 2D domain(s) on a grid data",
                "Probe plot:\n - Plot probe signals (recommended for time/iterative problems)",
                "Integral:\n - Compute integrated data",
                "Configuration", ]

        self.pages = {}
        self.elps = {}
        for ipage, t in enumerate(tabs):
            p = wx.Panel(self.nb)
            p.SetBackgroundColour(pane_colour1)
            self.nb.AddPage(p, t)
            if use_auinb:
                self.nb.SetPageToolTip(ipage, tips[ipage])

            self.pages[t] = p

        self.local_soldir = None
        self.local_solsubdir = None

        # these are sol info
        self.local_sols = None
        self.remote_sols = None

        self._plot_thread = None
        self._plot_data = None
        self.use_profiler = False  # debug

        self.apply_buttons = {}

        text = 'all'
        mfem_model = parent.model.param.getvar('mfem_model')

        if 'GeomBdr' in tabs:
            p = self.pages['GeomBdr']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            if len(choices) == 0:
                choices = ['no physics in model']
            ll = [['x', 'x', 500, {}],
                  ['y', 'y', 500, {}],
                  ['z', 'z', 500, {}],
                  ['Boundary Index', text, 0, {}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}],
                  ['Color', ['blue', 'none'], 506, {}],
                  [None, True, 3, {"text": 'merge solutions'}],
                  [None, True, 3, {"text": 'keep surface separated'}],
                  [None, True, 3, {"text": 'show edge only'}], ]

            elp = EditListPanel(p, ll)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['GeomBdr'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

        if 'Points' in tabs:
            p = self.pages['Points']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            if len(choices) == 0:
                choices = ['no physics in model']

            elp1 = [['x:', '0', 500, {}],
                    ['y:', '0', 500, {}],
                    ['z:', '0', 500, {}], ]

            tip1 = ('x coordinates of points',
                    'y coordinates of points',
                    'z coordinates of points',)

            elp2 = [['start point:', '0., 0., 0.', 0, {}],
                    ['end point:', '1., 0., 0.', 0, {}],
                    ['resolution:', '30', 0, {}], ]

            tip2 = ('initial point of line',
                    'final point of the line',
                    'number of points on the line')

            ss = [None, None, 34, ({'text': '',
                                    'choices': ['XYZ', 'Line  '],
                                    'cb_tip': "Point could generation mode",
                                    'call_fit': False},
                                   {'elp': elp1, 'tip': tip1},
                                   {'elp': elp2, 'tip': tip2},)]

            ll = [
                [
                    'Expression', '', 500, {}], ss, [
                    'Domains', 'all', 4, {
                        'style': wx.CB_DROPDOWN, 'choices': [
                            'all', 'visible', 'hidden']}], [
                        'NameSpace', choices[0], 4, {
                            'style': wx.CB_READONLY, 'choices': choices}], [
                                None, False, 3, {
                                    "text": 'animate'}], ]

            tip = ("Expression to evaluate on the points",
                   None,
                   "Select the domain containing the points",
                   "Select the namespace to use to evaluate the variables",
                   "Generate animation using phasing")

            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Points'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            # ebutton.Bind(wx.EVT_RIGHT_UP, self.onExportR)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Points"] = button

        if 'Edge' in tabs:
            p = self.pages['Edge']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            if len(choices) == 0:
                choices = ['no physics in model']

            s4 = {"style": wx.TE_PROCESS_ENTER,
                  "choices": [str(x + 1) for x in range(10)]}

            ll = [['Expression', '', 500, {}],
                  ["-> more...", None, None,
                      {"tlb_resize_samewidth": True, "colour": pane_colour2}, ],
                  ['Expression(x)', '', 500, {}],
                  ["<-"],
                  ['Edges', 'all', 4, {'style': wx.CB_DROPDOWN,
                                       'choices': ['all', 'visible', 'hidden']}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}],
                  [None, False, 3, {"text": 'animate'}],
                  [None, True, 3, {"text": 'merge solutions'}],
                  [None,  (True, ['1'], ), 127,
                   ({"text": 'Averaging'},
                    {"elp": [['Refine', 1, 104, s4], ]}, )], ]

            tip = ("Expression to evaluate on the edge",
                   None,
                   "Expression for x (2D plot)",
                   None,
                   "Select the edges where to evaluate and plot the expression",
                   "Select the namespace to use to evaluate the expression",
                   "Generate animation using phasing",
                   None,
                   "Data point refinement",
                   None)

            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Edge'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            # ebutton.Bind(wx.EVT_RIGHT_UP, self.onExportR)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            # hbox.Add(ibutton, 0, wx.ALL,1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)
            # button.Enable(False)
            self.apply_buttons["Edge"] = button

        if 'Bdr' in tabs:
            p = self.pages['Bdr']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            if len(choices) == 0:
                choices = ['no physics in model']

            s4 = {"style": wx.TE_PROCESS_ENTER,
                  "choices": [str(x + 1) for x in range(10)]}
            ll = [['Expression', '', 500, {}],
                  ["-> more...", None, None,
                      {"tlb_resize_samewidth": True, "colour": pane_colour2}, ],
                  ['Offset X', '0', 500, {}],
                  ['Offset Y', '0', 500, {}],
                  ['Offset Z', '0', 500, {}],
                  ['Scale', '1.0', 500, {}],
                  ["<-"],
                  ['Boundaries', 'all', 4, {'style': wx.CB_DROPDOWN,
                                            'choices': ['all', 'visible', 'hidden']}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}],
                  [None, False, 3, {"text": 'animate'}],
                  [None, True, 3, {"text": 'merge solutions'}],
                  [None,  (True, ['1'], ), 127,
                   ({"text": 'Averaging'},
                    {"elp": [['Refine', 1, 104, s4], ]}, )],
                  ['Decimate elements', '1', 0, {}, ], ]

            tip = ("Expression to evaluate on the boundary",
                   None,
                   "Offset X (evaluated using solution namespace)",
                   "Offset Y (evaluated using solution namespace)",
                   "Offset Z (evaluated using solution namespace)",
                   "Scaling coordinates (evaluated using local namespace)",
                   None,
                   "Select the boundary where to evaluate and plot the expression",
                   "Select the namespace to use to evaluate the expression",
                   "Generate animation using phasing",
                   None,
                   "Data point refinement", None, None,)

            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Bdr'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            # ibutton = wx.Button(p, wx.ID_ANY, "Integral")
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            # ibutton.Bind(wx.EVT_BUTTON, self.onInteg)
            ebutton.Bind(wx.EVT_LEFT_UP, self.onExport)
            # ebutton.Bind(wx.EVT_RIGHT_UP, self.onExportR)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            # hbox.Add(ibutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Bdr"] = button

        if 'Bdr(arrow)' in tabs:
            p = self.pages['Bdr(arrow)']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            if len(choices) == 0:
                choices = ['no physics in model']
            ll = [['Expression(u)', '', 500, {}],
                  ['Expression(v)', '', 500, {}],
                  ['Expression(w)', '', 500, {}],
                  ['Boundaries', 'all', 4, {'style': wx.CB_DROPDOWN,
                                            'choices': ['all', 'visible', 'hidden']}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}],
                  [None, False, 3, {
                      "text": 'animate (does not work)'}],
                  [None, True, 3, {"text": 'merge solutions'}],
                  ['Arrow count', 300, 400, None], ]

            tip = ("Expression for arrow coordinate 1 (u)",
                   "Expression for arrow coordinate 2 (v)",
                   "Expression for arrow coordinate 3 (w)",
                   "Select the boundary where to evaluate and plot the expression",
                   "Select the namespace to use to evaluate the expression",
                   None,
                   None,
                   "Number of arrows")
            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Bdr(arrow)'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Bdr(arrow)"] = button

        if 'Slice' in tabs:
            p = self.pages['Slice']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]

            elp1 = [['plane (a,b,c,d):', '0, 0, 1, 0', 500, {}], ]

            elp2 = [['plane (a,b,c,d):', '0, 0, 1, 0', 500, {}],
                    ['first axis:', '1., 0., 0.', 500, {}],
                    ['resolution', '0.01', 500, {}], ]

            txt1 = "\n".join(("ax + by + cz + d = 0, The parameters (a, b, c and/or d) can be 1D array",
                              "with the same length to define multiple plane."))
            ss = [None, None, 34, ({'text': 'Rendering',
                                    'choices': ['Triangles', 'Grid'],
                                    'call_fit': False},
                                   {'elp': elp1, 'tip': [txt1]},
                                   {'elp': elp2, 'tip': [txt1,
                                                         "1st axis on the plane, 2nd one is normal to the first axis and (a, b, c)",
                                                         "resolution of image"]},)]

            if len(choices) == 0:
                choices = ['no physics in model']
            ll = [['Expression', '', 500, {}],
                  ss,
                  ['Domains', 'all', 4, {'style': wx.CB_DROPDOWN,
                                         'choices': ['all', 'visible', 'hidden']}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}],
                  [None, False, 3, {"text": 'animate'}],
                  [None, True, 3, {"text": 'merge solutions'}], ]
            tip = ("Expression to evaluate on the slice",
                   None,
                   "Selection of domains to use for the slice plot",
                   "Namespace used to evaluate variables",
                   "Generate animation using phasing",
                   None)
            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Slice'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            # ebutton.Bind(wx.EVT_RIGHT_UP, self.onExportR)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Slice"] = button

        if 'Integral' in tabs:
            p = self.pages['Integral']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]
            if len(choices) == 0:
                choices = ['no physics in model']

            dom_bdr = ['Domain', 'Boundary']
            ll = [['Expression', '', 500, {}],
                  ['Kind', dom_bdr[0], 4, {'style': wx.CB_READONLY,
                                           'choices': dom_bdr}],
                  ['Index', text, 500, {}],
                  ['Order', '2',  0, {}],
                  ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
                                                'choices': choices}], ]

            elp = EditListPanel(p, ll)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Integral'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            button = wx.Button(p, wx.ID_ANY, "Apply")
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Integral"] = button

        if 'Probe' in tabs:
            p = self.pages['Probe']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            choices = list(mfem_model['Phys'])
            choices = [mfem_model['Phys'][c].fullpath() for c in choices]
            if len(choices) == 0:
                choices = ['no physics in model']

            ll = [['Expression', '', 500, {}],
                  ['Expression(x)', '', 500, {}],]
            # ['NameSpace', choices[0], 4, {'style': wx.CB_READONLY,
            #                              'choices': choices}], ]
            tip = ("Expression to evaluate",
                   "Expression for x (2D plot)",)
            # "Namespace used to evaluate variables")

            elp = EditListPanel(p, ll, tip=tip)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Probe'] = elp

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            ebutton = wx.Button(p, wx.ID_ANY, "Export")
            button = wx.Button(p, wx.ID_ANY, "Apply")
            ebutton.Bind(wx.EVT_BUTTON, self.onExport)
            button.Bind(wx.EVT_BUTTON, self.onApply)
            hbox.Add(ebutton, 0, wx.ALL, 1)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

            self.apply_buttons["Probe"] = button

        if 'Config' in tabs:
            p = self.pages['Config']
            vbox = wx.BoxSizer(wx.VERTICAL)
            p.SetSizer(vbox)

            elp1 = [["Sol", "sol", 504, {"choices": ["sol", ],
                                         "choices_cb": self.local_sollist}],
                    ["Sub dir.", "None", 4, {"style": wx.CB_READONLY,
                                             "choices": ["", ]}, ],
                    ["Sub dir.(2)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],
                    ["Sub dir.(3)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],]
#                    [None, None, 141, {"alignright": True,
#                                       "func": self.OnLoadLocalSol,
#                                       "noexpand": True,
#                                       "label": "Reload choices"}], ]
            tip1 = ("Solution folder",
                    "Subdirectory (for parametric scan/time-dependent sims.)",
                    None)
            elp2 = [["Number of workers", self.config['mp_worker'], 400, ],
                    ["Sol", "sol", 504, {"choices_cb": self.local_sollist,
                                         "choices": ["sol", ], }],
                    ["Sub dir.", "None", 4, {"style": wx.CB_READONLY,
                                             "choices": ["", ]}, ],
                    ["Sub dir.(2)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],
                    ["Sub dir.(3)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],]
#                    [None, None, 141, {"alignright": True,
#                                       "func": self.OnLoadLocalSol,
#                                       "noexpand": True,
#                                       "label": "Reload choices"}], ]
            tip2 = ("Numboer of worker processes",
                    "Solution folder",
                    "Subdirectory (for parametric scan/time-dependent sims.)",
                    None,)

            elp3 = [["Server", self.config['cs_server'], 0, ],
                    ["Number of workers", self.config['cs_worker'], 400, ],
                    ["Sol dir.", self.config['cs_soldir'], 504,
                     {"choices": [self.config['cs_soldir'], ],
                      "choices_cb": self.remote_sollist, }],
                    ["Sub dir.", "None", 4, {"style": wx.CB_READONLY,
                                             "choices": ["", ]}, ],
                    ["Sub dir.(2)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],
                    ["Sub dir.(3)", "None", 4, {"style": wx.CB_READONLY,
                                                "choices": ["", ]}, ],]
#                    [None, None, 141, {"alignright": True,
#                                       "func": self.OnLoadRemoteSol,
#                                       "noexpand": True,
#                                       "label": "Reload choices"}], ]

            tip3 = ("Remote server name",
                    "Numboer of worker processes on remoter server",
                    "Solution directory",
                    "Subdirectory",
                    None,)

            if allow_single_mode:
                if petram.mfem_model.has_cluster_access:
                    choices = ['Single', 'MP', 'C/S']
                    tip = '\n'.join(("- Single: plot local solution using single-processor",
                                     "- MP: plot local solution with multiprocessing",
                                     "- C/S: plot solution on a remote server"))
                    ll = [[None, None, 34, ({'text': "Worker Mode",
                                             'choices': choices,
                                             'cb_tip': tip,
                                             'call_fit': False},
                                            {'elp': elp1, 'tip': tip1},
                                            {'elp': elp2, 'tip': tip2},
                                            {'elp': elp3, 'tip': tip3},), ], ]
                else:
                    choices = ['Single', 'MP',]
                    tip = '\n'.join(("- Single: plot local solution using single-processor",
                                     "- MP: plot local solution with multiprocessing",))
                    ll = [[None, None, 34, ({'text': "Worker Mode",
                                             'choices': choices,
                                             'cb_tip': tip,
                                             'call_fit': False},
                                            {'elp': elp1, 'tip': tip1},
                                            {'elp': elp2, 'tip': tip2},), ], ]
            else:
                if petram.mfem_model.has_cluster_access:
                    choices = ['MP', 'C/S']
                    tip = '\n'.join(("- MP: plot local solution with multiprocessing",
                                     "- C/S: plot solution on a remote server"))
                    ll = [[None, None, 34, ({'text': "Worker Mode",
                                             'choices': choices,
                                             'cb_tip': tip,
                                             'call_fit': False},
                                            {'elp': elp2, 'tip': tip2},
                                            {'elp': elp3, 'tip': tip3},), ], ]
                else:
                    choices = ['MP',]
                    tip = '\n'.join(
                        ("- MP: plot local solution with multiprocessing",))
                    ll = [[None, None, 34, ({'text': "Worker Mode",
                                             'choices': choices,
                                             'cb_tip': tip,
                                             'call_fit': False},
                                            {'elp': elp2, 'tip': tip2},), ], ]

            elp = EditListPanel(p, ll)
            vbox.Add(elp, 1, wx.EXPAND | wx.ALL, 1)
            self.elps['Config'] = elp

            if allow_single_mode:
                if self.config['use_cs']:
                    c = choices[2]
                elif self.config['use_mp']:
                    c = choices[1]
                else:
                    c = choices[0]
                value = [[c,
                          ['', 'sol', "", "", ""],
                          [2, 'sol', "", "", ""],
                          [self.config['cs_server'], self.config['cs_worker'],
                              self.config['cs_soldir'], '', '', ''],
                          ]]
            else:
                if self.config['use_cs']:
                    c = choices[1]
                else:
                    self.config['use_mp'] = True
                    c = choices[0]
                value = [[c,
                          [2, 'sol', "", "", "", ],
                          [self.config['cs_server'], self.config['cs_worker'],
                              self.config['cs_soldir'], '', '', ''],
                          ]]

            elp.SetValue(value)
            parent.model.variables.setvar('remote_soldir',
                                          self.config['cs_soldir'])

            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5)
            button = wx.Button(p, wx.ID_ANY, "Reload Choices")
            button.Bind(wx.EVT_BUTTON, self.OnReloadSol)
            hbox.AddStretchSpacer()
            hbox.Add(button, 0, wx.ALL, 1)

        self.nb.SetSelection(self.nb.GetPageCount() - 1)
        self.Show()
        self.Layout()
        self.SetSize((850, 500))
        self.Bind(EDITLIST_CHANGED, self.onEL_Changed)
        self.Bind(EDITLIST_CHANGING, self.onEL_Changing)
        self.Bind(EDITLIST_SETFOCUS, self.onEL_SetFocus)
        self.Bind(EVT_THREADEND, self.onThreadEnd)

        wx.CallAfter(self.CentreOnParent)
        wx.CallAfter(self.update_sollist_local1)
        wx.CallAfter(self.update_sollist_local2)
        if self.config['use_cs']:
            wx.CallAfter(self.update_subdir_remote)

        self.solvars = WKD()
        self.evaluators = {}
        self.solfiles = {}
        self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocus)

    def name(self):
        return 'dlg_plot_sol'

    def onClose(self, evt):
        super(DlgPlotSol, self).onClose(evt)
        self.clean_evaluators()

    def clean_evaluators(self):
        for k in self.evaluators:
            self.evaluators[k].terminate_allnow()
        wx.Sleep(1)
        from petram.sol.evaluator_cs import EvaluatorClient
        for k in self.evaluators:
            if isinstance(self.evaluators[k],
                          EvaluatorClient):
                if (self.evaluators[k].p is not None and
                        self.evaluators[k].p.poll() is None):
                    self.evaluators[k].p.terminate()
        self.evaluators = {}

    def _get_subdir_cbs(self, base, idx):
        return (base.widgets[idx][0], base.widgets[idx+1][0],  base.widgets[idx+2][0])

    def get_remote_subdir_cb(self):
        if allow_single_mode:
            return self._get_subdir_cbs(self.elps['Config'].widgets[0][0].elps[2], 3)
            # return self.elps['Config'].widgets[0][0].elps[2].widgets[3][0]
        else:
            return self._get_subdir_cbs(self.elps['Config'].widgets[0][0].elps[1], 3)
            # return self.elps['Config'].widgets[0][0].elps[1].widgets[3][0]

    def get_local_single_subdir_cb(self):
        return self._get_subdir_cbs(self.elps['Config'].widgets[0][0].elps[0], 1)
        # return self.elps['Config'].widgets[0][0].elps[0].widgets[1][0]

    def get_local_multi_subdir_cb(self):
        if allow_single_mode:
            return self._get_subdir_cbs(self.elps['Config'].widgets[0][0].elps[1], 2)
            # return self.elps['Config'].widgets[0][0].elps[1].widgets[2][0]
        else:
            return self._get_subdir_cbs(self.elps['Config'].widgets[0][0].elps[0], 2)
            # return self.elps['Config'].widgets[0][0].elps[0].widgets[2][0]

    def update_subdir1(self, caseinfo, choices, dirnames, ss1,
                       cbs=None, cbm=None, cbc=None):
        def set_cb1(cb):
            c2 = choices[:]
            d2 = dirnames[:]

            cases = caseinfo.caselist
            for k, x in enumerate(caseinfo):
                cname = cases[k]
                c2.append(cname + " ("+x.info+")")
                d2.append(cname)

            cb[0].SetChoices(c2)
            if ss1 in c2:
                idx = c2.index(ss1)
                cb[0].SetSelection(idx)
                ss = d2[idx]
            elif ss1 in d2:
                idx = d2.index(ss1)
                cb[0].SetSelection(idx)
                ss = ss1
            else:
                cb[0].SetSelection(0)
                ss = d2[0]
            return ss

        ret = ""
        if cbs is not None:
            ret = set_cb1(cbs)
        if cbm is not None:
            ret = set_cb1(cbm)
        if cbc is not None:
            ret = set_cb1(cbc)

        return ret

    def update_subdir2(self, caseinfo, ss2,
                       cbs=None, cbm=None, cbc=None):
        def set_cb2(cb):
            idx = cb[0].GetSelection()

            choices = [""]
            dirnames = [""]
            ss = ""

            if idx != 0:
                caseinfo2 = [x for x in caseinfo][idx-1]
                cases = caseinfo2.caselist
                for k, x in enumerate(caseinfo2):
                    cname = cases[k]
                    choices.append(cname + " ("+x.info+")")
                    dirnames.append(cname)

            cb[1].SetChoices(choices)
            if ss2 in choices:
                idx = choices.index(ss2)
                cb[1].SetSelection(idx)
                ss = dirnames[idx]
            elif ss2 in dirnames:
                idx = dirnames.index(ss2)
                cb[1].SetSelection(idx)
                ss = ss2
            else:
                cb[1].SetSelection(0)
                ss = dirnames[0]

            if len(choices) <= 1:
                cb[1].Disable()
            else:
                cb[1].Enable()
            return ss

        ret = ""
        if cbs is not None:
            ret = set_cb2(cbs)
        if cbm is not None:
            ret = set_cb2(cbm)
        if cbc is not None:
            ret = set_cb2(cbc)

        return ret

    def update_subdir3(self, caseinfo, ss3,
                       cbs=None, cbm=None, cbc=None):
        def set_cb3(cb):
            choices = [""]
            dirnames = [""]

            idx = cb[0].GetSelection()
            ss = ""
            if idx > 0:
                caseinfo2 = [x for x in caseinfo][idx-1]
                idx = cb[1].GetSelection()
                if idx > 0:
                    caseinfo3 = [x for x in caseinfo2][idx-1]

                    cases = caseinfo3.caselist
                    for k, x in enumerate(caseinfo3):
                        cname = cases[k]
                        choices.append(cname + " ("+x.info+")")
                        dirnames.append(cname)

            cb[2].SetChoices(choices)
            if ss3 in choices:
                idx = choices.index(ss3)
                cb[2].SetSelection(idx)
                ss = dirnames[idx]
            elif ss3 in dirnames:
                idx = dirnames.index(ss3)
                cb[2].SetSelection(idx)
                ss = ss3
            else:
                cb[2].SetSelection(0)
                ss = dirnames[0]

            if len(choices) <= 1:
                cb[2].Disable()
            else:
                cb[2].Enable()
            return ss

        ret = ""
        if cbs is not None:
            ret = set_cb3(cbs)
        if cbm is not None:
            ret = set_cb3(cbm)
        if cbc is not None:
            ret = set_cb3(cbc)

        return ret

    def update_subdir_local(self, path, ss1, ss2, ss3):
        if allow_single_mode:
            single_cb2 = self.get_local_single_subdir_cb()
            multi_cb2 = self.get_local_multi_subdir_cb()
        else:
            single_cb2 = None
            multi_cb2 = self.get_local_multi_subdir_cb()

        from petram.sol.listsoldir import gather_soldirinfo
        info = gather_soldirinfo(path)

        dirnames = [""]
        choices = [""]
        '''
        solvers = list(info["checkpoint"])
        for solver in solvers:
            kk = sorted(list(info["checkpoint"][solver]))
            for k in kk:
                dirnames.append(info["checkpoint"][solver][k])
                choices.append(solver + " (" + str(k[1]) + ")")
        '''
        ss1 = self.update_subdir1(info["cases"], choices, dirnames, ss1,
                                  cbs=single_cb2,
                                  cbm=multi_cb2, cbc=None,)
        ss2 = self.update_subdir2(info["cases"], ss2, cbs=single_cb2,
                                  cbm=multi_cb2, cbc=None,)
        ss3 = self.update_subdir3(info["cases"], ss3, cbs=single_cb2,
                                  cbm=multi_cb2, cbc=None,)

        # probes = info["probes"]  # mapping from probe name to file
        # 2nd element is used anymore
        self.local_sols = (path, None, dict(zip(choices, dirnames)), info)
        return ss1, ss2, ss3

    def update_sollist_local_common(self, idx):
        model = self.GetParent().model

        sol_names = [name for name, child in model.solutions.get_children()]
        sols = [child for name, child in model.solutions.get_children()]
        owndirs = [x.owndir() for x in sols]

        if allow_single_mode:
            single_cb1 = self.elps['Config'].widgets[0][0].elps[0].widgets[0][0]
            multi_cb1 = self.elps['Config'].widgets[0][0].elps[1].widgets[1][0]
            choices = ([single_cb1.GetString(n)
                        for n in range(single_cb1.GetCount())] +
                       [multi_cb1.GetString(n)
                        for n in range(multi_cb1.GetCount())])
            s1 = str(single_cb1.GetValue())
            s2 = str(multi_cb1.GetValue())
            if s1 in sol_names:
                owndir1 = owndirs[sol_names.index(s1)]
            else:
                owndir1 = s1
            if s2 in sol_names:
                owndir2 = owndirs[sol_names.index(s2)]
            else:
                owndir2 = s2

            if not s1 in sol_names:
                sol_names.append(s1)
            if not s2 in sol_names:
                sol_names.append(s2)

        else:
            multi_cb1 = self.elps['Config'].widgets[0][0].elps[0].widgets[1][0]
            choices = [multi_cb1.GetString(n)
                       for n in range(multi_cb1.GetCount())]
            choices = list(set(choices))
            s2 = str(multi_cb1.GetValue())
            if s2 in sol_names:
                owndir2 = owndirs[sol_names.index(s2)]
            else:
                owndir2 = s2
            if not s2 in sol_names:
                sol_names.append(s2)

        for x in choices:
            if not x in sol_names:
                sol_names.append(x)
        sol_names = [x for x in sol_names if len(x) > 0]

        if allow_single_mode:
            single_cb1.SetChoices(sol_names)
        multi_cb1.SetChoices(sol_names)

        if self.local_soldir is not None:
            ss1, ss2, ss3 = self.local_solsubdir
        else:
            if model.param.eval('sol') is not None:
                ss1 = ""
                ss2 = ""
                ss3 = ""
            else:
                ss1 = None
                ss2 = None
                ss3 = None

        if idx == 1:
            if os.path.exists(owndir1):
                self.update_subdir_local(owndir1, ss1, ss2, ss3)
        else:
            if os.path.exists(owndir2):
                self.update_subdir_local(owndir2, ss1, ss2, ss3)

    def update_sollist_local1(self):
        if allow_single_mode:
            self.update_sollist_local_common(1)

    def update_sollist_local2(self):
        self.update_sollist_local_common(2)

    def update_subdir_remote(self):
        self._update_subdir_remote()

    @run_in_piScope_thread
    def _update_subdir_remote(self):
        try:
            info = read_solinfo_remote(self.config['cs_user'],
                                       self.config['cs_server'],
                                       self.config['cs_soldir'])
            info["cases"].restore()

        except AssertionError as err:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Faled to read remote directory info',
                         title='Error',
                         traceback=err.args[0])
            return
        except:
            # _, _, tb = sys.exc_info()
            # traceback.print_tb(tb) # Fixed format
            # tb_info = traceback.extract_tb(tb)
            # filename, line, func, text = tb_info[-1]
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Faled to read remote directory info',
                         title='Error',
                         traceback=traceback.format_exc(limit=-1))
            return

        self.post_threadend(self.update_subdir_remote_step2, info)

    def update_subdir_remote_step2(self, info):
        dirnames = [""]
        choices = [""]
        '''
        solvers = list(info["checkpoint"])
        for solver in solvers:
            kk = sorted(list(info["checkpoint"][solver]))
            for k in kk:
                dirnames.append(info["checkpoint"][solver][k])
                choices.append(solver + "(" + str(k[1]) + ")")
        '''
        try:
            ss1, ss2, ss3 = self.config['cs_solsubdir']
        except:
            ss1, ss2, ss3 = "", "", ""

        cb = self.get_remote_subdir_cb()
        ss1 = self.update_subdir1(
            info["cases"], choices, dirnames, ss1, cbc=cb)
        ss2 = self.update_subdir2(info["cases"], ss2, cbc=cb)
        ss3 = self.update_subdir3(info["cases"], ss3, cbc=cb)

        # probes = info["probes"]  # mapping from probe name to file
        # 2nd element is used anymore
        self.remote_sols = (self.config['cs_soldir'],
                            None, dict(zip(choices, dirnames)), info)

        self.config['cs_solsubdir'] = (ss1, ss2, ss3)

    def get_current_choices(self):
        if self.config['use_cs']:
            base = self.remote_sols[0]
            ccurrent = self.config['cs_solsubdir']
            info = self.local_sols[3]["cases"]

            remote = True
        else:
            base = self.local_sols[0]
            info = self.local_sols[3]["cases"]
            ccurrent = self.local_solsubdir
            remote = False

        if len(ccurrent[0]) == 0:
            subdirs = [(x, "", "") for x in info.caselist]
        elif len(ccurrent[1]) == 0:
            info = info.__getattr__(ccurrent[0])
            subdirs = [(ccurrent[0], x, "") for x in info.caselist]
        elif len(ccurrent[2]) == 0:
            info = info.__getattr__(ccurrent[0])
            info = info.__getattr__(ccurrent[1])
            subdirs = [(ccurrent[0], ccurrent[1], x)
                       for x in info.caselist]

        return remote, base, subdirs

    def OnLoadLocalSol(self, evt):
        self.update_sollist_local1()
        self.update_sollist_local2()
        self.load_sol_if_needed()

    def OnLoadRemoteSol(self, evt):
        self.update_subdir_remote()

    def OnReloadSol(self, evt):
        if self.config['use_cs']:
            self.OnLoadRemoteSol(evt)
        else:
            self.OnLoadLocalSol(evt)

    def OnUpdateUI_local(self, evt):
        pass

    def OnUpdateUI_remote(self, evt):
        pass

    def local_sollist(self):
        model = self.GetParent().model
        sol_names = [name for name, child in model.solutions.get_children()]

        if allow_single_mode:
            single_cb1 = self.elps['Config'].widgets[0][0].elps[0].widgets[0][0]
            multi_cb1 = self.elps['Config'].widgets[0][0].elps[1].widgets[1][0]
            choices = ([single_cb1.GetString(n)
                        for n in range(single_cb1.GetCount())] +
                       [multi_cb1.GetString(n)
                        for n in range(multi_cb1.GetCount())])
            s1 = str(single_cb1.GetValue())
            s2 = str(multi_cb1.GetValue())
            if not s1 in sol_names:
                sol_names.append(s1)
            if not s2 in sol_names:
                sol_names.append(s2)

        else:
            multi_cb1 = self.elps['Config'].widgets[0][0].elps[0].widgets[1][0]
            choices = [multi_cb1.GetString(n)
                       for n in range(multi_cb1.GetCount())]
            choices = list(set(choices))
            s2 = str(multi_cb1.GetValue())
            if not s2 in sol_names:
                sol_names.append(s2)

        for x in choices:
            if not x in sol_names:
                sol_names.append(x)
        sol_names = [x for x in sol_names if len(x) > 0]

        return sol_names

    def remote_sollist(self):
        if allow_single_mode:
            remote_cb1 = self.elps['Config'].widgets[0][0].elps[2].widgets[2][0]
        else:
            remote_cb1 = self.elps['Config'].widgets[0][0].elps[1].widgets[2][0]
        choices = [remote_cb1.GetString(n)
                   for n in range(remote_cb1.GetCount())]
        s1 = str(remote_cb1.GetValue())
        if not s1 in choices:
            choices.append(s1)
        choices = [x for x in choices if len(x) > 0]
        return choices

    def OnChildFocus(self, evt):
        self.GetParent()._palette_focus = 'plot'
        evt.Skip()

    def post_threadend(self, func, *args, **kwargs):
        evt = wx.PyCommandEvent(ThreadEnd, wx.ID_ANY)
        evt.pp_method = (func, args, kwargs)
        wx.PostEvent(self, evt)

    def set_title_no_status(self):
        title = self.GetTitle()
        self.SetTitle(title.split('(')[0])

    def onThreadEnd(self, evt):
        m = evt.pp_method[0]
        args = evt.pp_method[1]
        kargs = evt.pp_method[2]
        m(*args, **kargs)
        evt.Skip()

    def load_sol_if_needed(self):
        from petram.sol.solsets import read_sol, find_solfiles
        model = self.GetParent().model
        solfiles = model.variables.getvar('solfiles')

        doit = False
        if solfiles is not None:
            cpath = os.path.dirname(solfiles.set[0][0][0])
            if self.local_soldir is None:
                sol = model.param.eval('sol')
                if sol is None:
                    if model.variables.hasvar('solfiles'):
                        model.variables.delvar('solfiles')
                    return
                npath = sol.owndir()
                self.local_soldir = npath
                self.local_solsubdir = ["", "", ""]
            else:
                subs = [x for x in self.local_solsubdir if x != ""]
                npath = os.path.join(self.local_soldir, *subs)
                sol = model.param.eval('sol')
                if not os.path.exists(npath):  # fall back
                    sol = model.param.eval('sol')
                    npath = sol.owndir()
                    self.local_soldir = npath
                    self.local_solsubdir = ["", "", ""]

            if os.path.normpath(npath) != os.path.normpath(cpath):
                doit = True
            else:
                mfem_model = model.param.getvar('mfem_model')
                mfem_model.local_sol_path = npath
        else:
            doit = True
            if self.local_soldir is not None:
                subs = [x for x in self.local_solsubdir if x != ""]
                npath = os.path.join(self.local_soldir, *subs)
                if not os.path.exists(npath):  # fall back
                    sol = model.param.eval('sol')
                    npath = sol.owndir()
                    self.local_soldir = npath
                    self.local_solsubdir = ["", "", ""]
            else:
                sol = model.param.eval('sol')
                if sol is None:
                    if model.variables.hasvar('solfiles'):
                        model.variables.delvar('solfiles')
                    return
                npath = sol.owndir()
                self.local_soldir = npath
                self.local_solsubdir = ["", "", ""]
        if doit:
            try:
                print("reading sol from ", npath)
                solfiles = find_solfiles(path=npath)
                if solfiles is None:
                    if model.variables.hasvar('solfiles'):
                        model.variables.delvar('solfiles')
                else:
                    model.variables.setvar('solfiles', solfiles)
                mfem_model = model.param.getvar('mfem_model')
                mfem_model.local_sol_path = npath
            except BaseException:
                traceback.print_exc()
                if model.variables.hasvar('solfiles'):
                    model.variables.delvar('solfiles')

    def onEL_Changed(self, evt):
        sel = self.nb.GetSelection()
        if sel != self.nb.GetPageCount() - 1:
            evt.Skip()
            return

        model = self.GetParent().model
        v = self.elps['Config'].GetValue()

        ofs = 0 if allow_single_mode else -1

        if str(v[0][0]) == 'Single':
            if (self.config['use_mp'] or
                    self.config['use_cs']):
                self.clean_evaluators()

            self.config['use_mp'] = False
            self.config['use_cs'] = False
            model.variables.setvar('remote_soldir', None)

            sol_base = model.solutions.owndir()
            tmp = os.path.join(sol_base, str(v[0][1][0]))
            if os.path.exists(tmp):
                owndir = tmp
            else:
                tmp = os.path.expanduser(str(v[0][1][0]))
                if os.path.exists(tmp):
                    owndir = tmp
                else:
                    wx.CallAfter(dialog.showtraceback,
                                 parent=self,
                                 txt='Solution directory does not exist: ' +
                                     str(v[0][1][0]),
                                 title='Error',
                                 traceback='',)

            if self.local_sols is None:
                self.update_sollist_local1()

            ss1 = str(v[0][1][1])
            ss2 = str(v[0][1][2])
            ss3 = str(v[0][1][3])
            ss1, ss2, ss3 = self.update_subdir_local(owndir, ss1, ss2, ss3)
            self.local_soldir = owndir
            self.local_solsubdir = [ss1, ss2, ss3]

            cb = self.get_remote_subdir_cb()
            ss2 = self.update_subdir2(info["cases"], ss2, cbc=cb)
            ss3 = self.update_subdir3(info["cases"], ss3, cbc=cb)

            self.load_sol_if_needed()

        elif str(v[0][0]) == 'MP':
            if not self.config['use_mp']:
                self.clean_evaluators()

            if self.config['mp_worker'] != v[0][2+ofs][0]:
                self.clean_evaluators()

            self.config['mp_worker'] = v[0][2+ofs][0]
            self.config['use_mp'] = True
            self.config['use_cs'] = False

            model.variables.setvar('remote_soldir', None)

            sol_base = model.solutions.owndir()
            tmp = os.path.join(sol_base, str(v[0][2+ofs][1]))
            if os.path.exists(tmp):
                owndir = tmp
            else:
                tmp = os.path.expanduser(str(v[0][2+ofs][1]))
                if os.path.exists(tmp):
                    owndir = tmp
                else:
                    wx.CallAfter(dialog.showtraceback,
                                 parent=self,
                                 txt='Solution directory does not exist: ' +
                                     str(v[0][2+ofs][1]),
                                 title='Error',
                                 traceback='',)

            if self.local_sols is None:
                self.update_sollist_local2()

            ss1 = str(v[0][2+ofs][2])
            ss2 = str(v[0][2+ofs][3])
            ss3 = str(v[0][2+ofs][4])
            ss1, ss2, ss3 = self.update_subdir_local(owndir, ss1, ss2, ss3)
            self.local_soldir = owndir
            self.local_solsubdir = [ss1, ss2, ss3]
            self.load_sol_if_needed()

        elif str(v[0][0]) == 'C/S':
            if not self.config['use_cs']:
                self.clean_evaluators()

            if self.config['cs_worker'] != v[0][3+ofs][1]:
                self.clean_evaluators()

            self.config['cs_worker'] = str(v[0][3+ofs][1])

            reload_remote = False
            if (not self.config['use_cs'] or
                self.config['cs_server'] != str(v[0][3+ofs][0]) or
                    self.config['cs_soldir'] != str(v[0][3+ofs][2])):
                reload_remote = True

            self.config['cs_server'] = str(v[0][3+ofs][0])
            self.config['cs_soldir'] = str(v[0][3+ofs][2])
            self.config['use_mp'] = False
            self.config['use_cs'] = True

            model.variables.setvar('remote_soldir', self.config['cs_soldir'])

            if reload_remote:
                self.update_subdir_remote()
            else:
                ss1 = str(v[0][3+ofs][3])
                ss2 = str(v[0][3+ofs][4])
                ss3 = str(v[0][3+ofs][5])

                if self.remote_sols is not None:
                    self.config['cs_solsubdir'] = (ss1, ss2, ss3)

                #if self._remote_dir_info is not None:
                info = self.remote_sols[3]
                self.update_subdir_remote_step2(info)

        evt.Skip()

    def onEL_Changing(self, evt):
        evt.Skip()

    def onEL_SetFocus(self, evt):
        evt.Skip()

    def onApply(self, evt):
        elp = self.get_selected_elp()
        elp.AddCurrentToHistory()

        t = self.get_selected_plotmode()
        m = getattr(self, 'onApply' + t)

        m(evt)

    def onInteg(self, evt):
        elp = self.get_selected_elp()
        elp.AddCurrentToHistory()

        t = self.get_selected_plotmode()
        m = getattr(self, 'onInteg' + t)
        m(evt)

    def onExport(self, evt):
        elp = self.get_selected_elp()
        elp.AddCurrentToHistory()

        t = self.get_selected_plotmode()
        m = getattr(self, 'onExport' + t)

        m(evt)

    def get_selected_elp(self):
        t = self.nb.GetPageText(self.nb.GetSelection())
        elp = self.elps[t]
        return elp

    def get_selected_plotmode(self, kind=False):
        t = self.nb.GetPageText(self.nb.GetSelection())
        t = t.replace('(', '').replace(')', '')
        if kind:
            if t in ("GeomBdr", "Config"):
                return t, None
            kinds = {'Bdr': 'bdry',
                     'BdrArrorw': 'bdry',
                     'Edge': 'edge',
                     'Slice': 'domain',
                     'Points': 'domain',
                     'Integral': 'domain/boundary',
                     'Domain': 'domain'}
            i = getattr(self, 'get_attrs_field_' + t)
            value = self.elps[t].GetValue()
            attrs = str(value[i()])
            if attrs.strip().lower() != 'all':
                attrs = [int(x) for x in attrs.split(',') if x.strip() != '']
            if t == 'Integral':
                value = self.elps['Integral'].GetValue()
                kind = str(value[1]).strip()
                return kinds[t].lower(), attrs
            else:
                return kinds[t], attrs
        else:
            return t

    def add_selection(self, sel):
        t = self.get_selected_plotmode()
        i = getattr(self, 'get_attrs_field_' + t)
        attrs = self.elps[t].GetValue()[i()]
        if attrs.strip().lower() == 'all':
            return
        attrs = sorted(set([int(x) for x in attrs.split(',')] + sel))
        self.set_selection(attrs)

    def rm_selection(self, sel):
        t = self.get_selected_plotmode()
        i = getattr(self, 'get_attrs_field_' + t)
        attrs = self.elps[t].GetValue()[i()]
        if attrs.strip().lower() == 'all':
            return
        # print(attrs, sel)
        attrs = sorted([int(x) for x in attrs.split(',') if not int(x) in sel])
        self.set_selection(attrs)

    def set_selection(self, sel):
        t = self.get_selected_plotmode()
        i = getattr(self, 'get_attrs_field_' + t)
        txt = ', '.join([str(s) for s in sel])
        v = self.elps[t].GetValue()
        v[i()] = txt
        self.elps[t].SetValue(v)

    #
    #   Edge value ('Edge' tab)
    #

    def onApplyEdge(self, evt):
        value = self.elps['Edge'].GetValue()
        self._onApplyEdge(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onApplyEdge(self, value):
        expr = str(value[0]).strip()
        expr_x = str(value[1]).strip()

        if value[4]:
            from ifigure.widgets.wave_viewer import WaveViewer
            cls = WaveViewer
        else:
            cls = None

        refine = int(value[6][1][0])

        data, data_x, battrs = self.eval_edge(
            value, mode='plot', refine=refine)
        if data is None:
            return

        self.post_threadend(self.make_plot_edge, data, battrs,
                            data_x=data_x,
                            cls=cls, expr=expr, expr_x=expr_x,
                            force_float=(not value[4]))

    def make_plot_edge(self, data, battrs,
                       data_x=None, cls=None,
                       expr='', expr_x='', force_float=False):
        from ifigure.interactive import figure

        if data_x is None:
            # if verts is 1D, treat it 2D plot even if data_x is None
            if data[0][0].shape[1] == 1:
                data_x = [(None, verts[:, 0]) for verts, cdata, adata in data]
                data = [(None, cdata) for verts, cdata, adata in data]

        if data_x is None:
            v = None
            for verts, cdata, adata in data:
                if v is None:
                    v = figure(viewer=cls)
                    v.title(expr + ':' + str(battrs))
                    setup_figure(v, self.GetParent())
                    v.update(False)

                if cls is None:
                    v.solid(verts, adata, cz=True, cdata=cdata.astype(float),
                            shade='linear')
                else:
                    v.solid(verts, adata, cz=True, cdata=cdata,
                            shade='linear')

            if v is None:
                return

            v.update(True)
            v.update(False)
            ax = self.GetParent().get_axes()
            param = ax.get_axes3d_viewparam(ax._artists[0])
            ax2 = v.get_axes()
            ax2.set_axes3d_viewparam(param, ax2._artists[0])
            v.lighting(light=0.5)
            v.update(True)
        else:  # make 2D plot
            v = None
            for yy, xx in zip(data, data_x):
                y = yy[1].flatten()
                x = xx[1].flatten()
                xidx = np.argsort(x)

                if v is None:
                    v = figure(viewer=cls)
                    v.title(expr)
                    v.xtitle(expr_x)

                if force_float:
                    yy = y[xidx]
                    if np.iscomplexobj(yy):
                        v.plot(x[xidx], yy.real)
                    else:
                        v.plot(x[xidx], yy)
                else:
                    if cls is None:
                        v.plot(x[xidx], y[xidx])
                    else:
                        data = y[xidx].astype(complex, copy=False)
                        v.plot(x[xidx], data)

    def make_export_data_edge(self, do_integ):
        value = self.elps['Edge'] .GetValue()
        refine = int(value[6][1][0])

        data, data_x, battrs = self.eval_edge(
            value, mode='integ', refine=refine)
        if data is None:
            return

        ndim = data[0][0].shape[1]
        verts = np.hstack([v.flatten() for v, c, a in data]).flatten()
        cdata = np.hstack([c.flatten() for v, c, a in data]).flatten()
        verts = verts.reshape(-1, ndim)
        data = {'vertices': verts, 'data': cdata}

        if data_x is not None:
            cxdata = np.hstack([c.flatten() for v, c, a in data_x]).flatten()
            xverts = np.hstack([v.flatten() for v, c, a in data_x]).flatten()
            data['xvertices'] = xverts
            data['xdata'] = cxdata

        if do_integ:
            if data_x is not None:
                x = data['xdata']
            else:
                x = verts
            xx, idx = np.unique(x, return_index=True)

            from scipy.integrate import simpson
            data = {"integ": simpson(xx, cdata[idx])}
        return data

    def onExportEdge(self, evt):
        value = self.elps['Edge'] .GetValue()

        average = value[6][0]

        from petram.pi.dlg_export_opts import ask_export_opts
        opts = ask_export_opts(self, support_integ=average)

        if opts is None:
            return

        do_integ = opts[0]
        if average and do_integ:
            assert False, "averaging and integration can not be used together"
        do_loop = opts[-1]

        if do_loop:
            remote, base, subs = self.get_current_choices()
            all_data = []

            bk = (self.local_soldir,
                  self.local_solsubdir,
                  self.config['cs_soldir'],
                  self.config['cs_solsubdir'],)

            for s in subs:
                if remote:
                    self.config['cs_soldir'] = base
                    self.config['cs_solsubdir'] = s
                else:
                    self.local_soldir = base
                    self.local_solsubdir = s
                    self.load_sol_if_needed()

                data = self.make_export_data_edge(do_integ)
                data["subdirs"] = s
                all_data.append(data)

            self.local_soldir = bk[0]
            self.local_solsubdir = bk[1]
            self.config['cs_soldir'] = bk[2]
            self.config['cs_solsubdir'] = bk[3]
            # if not remote:
            #    self.load_sol_if_needed()

        else:
            all_data = self.make_export_data_edge(do_integ)

        if all_data is None or len(all_data) == 0:
            return  # nothine to export
        self.export_to_piScope_shell(all_data, 'edge_data')

    def get_attrs_field_Edge(self):
        return 2

    def eval_edge(self, value, mode='plot', refine=1):
        from petram.sol.evaluators import area_tri

        expr = str(value[0]).strip()
        expr_x = str(value[1]).strip()
        battrs = str(value[2])
        phys_path = value[3]
        if mode == 'plot':
            do_merge1 = value[5]
        else:
            do_merge1 = True
        average = value[6][0]
        if average:
            refine = 1

        exprs = [expr, expr_x] if expr_x != '' else [expr]
        data, void = self.evaluate_sol_edge(expr, battrs, phys_path,
                                            do_merge1, True,
                                            average=average,
                                            refine=refine,
                                            exprs=exprs)
        if data is None:
            return None, None, None

        if expr_x != '':
            data_x, void = self.evaluate_sol_edge(expr_x, battrs, phys_path,
                                                  do_merge1, True,
                                                  average=average,
                                                  refine=refine,
                                                  exprs=exprs)

            if data_x is None:
                return None, None, None
        else:
            data_x = None
        return data, data_x, battrs

    #
    #   Boundary value ('Bdr' tab)
    #
    def onApplyBdr(self, evt):
        value = self.elps['Bdr'] .GetValue()
        self._onApplyBdr(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onApplyBdr(self, value):
        expr = str(value[0]).strip()
        if len(expr) == 0:
            return

        if value[7]:
            from ifigure.widgets.wave_viewer import WaveViewer
            cls = WaveViewer
        else:
            cls = None

        refine = int(value[9][1][0])

        use_pointfill = int(value[10]) > 1
        data, battrs = self.eval_bdr(value, mode='plot', refine=refine)
        if data is None:
            return

        scale = str(value[4]).strip()
        if scale.strip() != "":
            model = self.GetParent().model
            mfem_model = model.param.getvar('mfem_model')
            phys_path = value[6]
            phys_ns = mfem_model[str(phys_path)]._global_ns.copy()
            scale = eval(scale, {}, phys_ns)
        else:
            scale = 1.0

        self.post_threadend(self.make_plot_bdr, data, battrs, scale,
                            cls=cls, expr=expr,
                            use_pointfill=use_pointfill)

    def make_plot_bdr(
            self,
            data,
            battrs,
            scale,
            cls=None,
            expr='',
            use_pointfill=False):
        from ifigure.interactive import figure

        dd = defaultdict(list)
        # regroup to sepparte triangles and quads.
        for k, datasets in enumerate(data):
            v, c, i = datasets  # verts, cdata, idata
            idx = i.shape[-1]
            dd[idx].append((k + 1, v, c, i))

        viewer = None

        for key in dd.keys():
            kk, verts, cdata, idata = zip(*(dd[key]))
            offsets = np.hstack(
                (0, np.cumsum([len(c) for c in cdata], dtype=int)))[:-1]
            offsets_idx = np.hstack([np.zeros(len(a), dtype=int) + o
                                     for o, a in zip(offsets, idata)])
            array_idx = np.hstack([np.zeros(len(c), dtype=int) + k
                                   for k, c in zip(kk, cdata)])
            array_idx = array_idx + 1

            verts = np.vstack(verts)*scale
            cdata = np.hstack(cdata)
            idata = np.vstack(idata)
            idata = idata + np.atleast_2d(offsets_idx).transpose()

            if viewer is None:
                viewer = figure(viewer=cls)
                setup_figure(viewer, self.GetParent())

                viewer.update(False)
                viewer.suptitle(expr + ':' + str(battrs))

            if cls is None:
                obj = viewer.solid(verts, idata, array_idx=array_idx,
                                   cz=True, cdata=cdata.astype(float),
                                   shade='linear',
                                   use_pointfill=use_pointfill)
                obj.set_gl_hl_use_array_idx(True)
            else:
                obj = viewer.solid(verts, idata, array_idx=array_idx,
                                   cz=True, cdata=cdata, shade='linear',
                                   use_pointfill=use_pointfill)
                obj.set_gl_hl_use_array_idx(True)

        if viewer is None:
            return

        viewer.update(True)
        viewer.update(False)
        ax = self.GetParent().get_axes()
        param = ax.get_axes3d_viewparam(ax._artists[0])
        ax2 = viewer.get_axes()
        ax2.set_axes3d_viewparam(param, ax2._artists[0])
        viewer.lighting(light=0.5)
        viewer.update(True)

    def make_export_data_bdr(self, do_integ, verbose=False):
        from petram.sol.evaluators import area_tri

        value = self.elps['Bdr'] .GetValue()
        refine = int(value[9][1][0])

        data, battrs = self.eval_bdr(value, mode='integ', refine=refine)
        if data is None:
            return

        verts, cdata, adata = data[0]
        if not do_integ:
            data = {'vertices': verts, 'data': cdata, 'index': adata}
        else:
            integ = 0.0
            for verts, cdata, adata in data:
                v = verts[adata]
                c = cdata[adata, ...]
                area = area_tri(v)
                integ += np.sum(area * np.mean(c, 1))
            data = {"integ": integ}
            if verbose:
                print("Area Ingegration")
                print("Expression : " + expr)
                print("Boundary Index :" + str(battrs))
                print("Value : " + str(integ))

        return data

    def onExportBdr(self, evt):
        value = self.elps['Bdr'] .GetValue()
        print(value)

        from petram.pi.dlg_export_opts import ask_export_opts
        opts = ask_export_opts(self)

        if opts is None:
            return

        do_integ = opts[0]
        do_loop = opts[-1]

        if do_loop:
            remote, base, subs = self.get_current_choices()
            all_data = []

            bk = (self.local_soldir,
                  self.local_solsubdir,
                  self.config['cs_soldir'],
                  self.config['cs_solsubdir'],)

            for s in subs:
                if remote:
                    self.config['cs_soldir'] = base
                    self.config['cs_solsubdir'] = s
                else:
                    self.local_soldir = base
                    self.local_solsubdir = s
                    self.load_sol_if_needed()

                data = self.make_export_data_bdr(do_integ)
                data["subdirs"] = s
                all_data.append(data)

            self.local_soldir = bk[0]
            self.local_solsubdir = bk[1]
            self.config['cs_soldir'] = bk[2]
            self.config['cs_solsubdir'] = bk[3]

        else:
            all_data = self.make_export_data_bdr(do_integ)

        if all_data is None or len(all_data) == 0:
            return  # nothine to export
        self.export_to_piScope_shell(all_data, 'bdr_data')

    def get_attrs_field_Bdr(self):
        return 1

    def eval_bdr(self, value,  mode='plot', export_type=1, refine=1):
        from petram.sol.evaluators import area_tri

        expr = str(value[0]).strip()
        battrs = str(value[5])
        phys_path = value[6]
        if mode == 'plot':
            do_merge1 = value[8]
            do_merge2 = True
        elif mode == 'integ':
            do_merge1 = True
            do_merge2 = False
        else:
            do_merge1 = False
            do_merge2 = False

        average = value[9][0]
        decimate = int(value[10])

        if average:
            refine = 1

        data, battrs2 = self.evaluate_sol_bdr(expr, battrs, phys_path,
                                              do_merge1, do_merge2,
                                              export_type=export_type,
                                              refine=refine,
                                              average=average,
                                              decimate=decimate)
        if data is None:
            return None, None

        uvw = (str(value[1]), str(value[2]), str(value[3]),)
        if len(uvw) == 3:
            for kk, expr in enumerate(uvw):
                if expr.strip() == '':
                    continue
                try:
                    u = float(expr.strip())
                    isfloat = True
                except BaseException:
                    isfloat = False
                    u, battrs2 = self.evaluate_sol_bdr(expr.strip(),
                                                       battrs, phys_path,
                                                       do_merge1, do_merge2,
                                                       export_type=export_type,
                                                       refine=refine,
                                                       average=average,
                                                       decimate=decimate)
                data = [list(x) for x in data]
                for k, datasets in enumerate(data):
                    if datasets[0].shape[1] == 2:
                        datasets[0] = np.hstack(
                            (datasets[0], np.zeros((datasets[0].shape[0], 1))))
                    elif datasets[0].shape[1] == 1:
                        datasets[0] = np.hstack(
                            (datasets[0], np.zeros((datasets[0].shape[0], 2))))

                    if isfloat:
                        datasets[0][:, kk] += u
                    else:
                        datasets[0][:, kk] += u[k][1]

        return data, battrs

    #
    #   PointCloud ('Points' tab)
    #
    def onApplyPoints(self, evt):
        value = self.elps['Points'] .GetValue()
        self._onApplyPoints(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onApplyPoints(self, value):
        expr = str(value[0]).strip()
        if value[4]:
            from ifigure.widgets.wave_viewer import WaveViewer
            cls = WaveViewer
        else:
            cls = None

        flag, dataset = self.call_eval_pointcloud(value)

        if flag > 0:
            ptx, data, attrs_out, attrs, pc_param = dataset
        else:
            evt.Skip()
            return

        pc_mode = value[1][0]
        if pc_mode == 'XYZ':
            data = {'vertices': ptx, 'data': data, 'attrs': attrs_out}
            self.post_threadend(self.export_to_piScope_shell,
                                data,
                                'point_data')
        else:
            self.post_threadend(
                self.make_plot_point,
                data,
                attrs_out,
                attrs,
                pc_param,
                cls=cls,
                expr=expr)

    def onExportPoints(self, evt):
        value = self.elps['Points'] .GetValue()

        flag, dataset = self.call_eval_pointcloud(value)

        if flag > 0:
            ptx, data, attrs_out, attrs, pc_param = dataset
        else:
            return

        data = {'vertices': ptx, 'data': data, 'attrs': attrs_out}
        self.export_to_piScope_shell(data, 'point_data')

    def onExportR1Points(self, evt):
        value = self.elps['Points'] .GetValue()
        self._onExportR1Points(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onExportR1Points(self, value):
        remote, base, subs = self.get_current_choices()
        ret = []
        for s in subs:
            if remote:
                self.config['cs_soldir'] = base
                self.config['cs_solsubdir'] = s
            else:
                self.local_soldir = base
                self.local_solsubdir = s
                self.load_sol_if_needed()

            flag, dataset = self.call_eval_pointcloud(value)
            if flag > 0:
                ptx, data, attrs_out, attrs, pc_param = dataset
                ret.append({'subdir': s, 'vertices': ptx,
                            'data': data, 'attrs': attrs_out})
            else:
                assert False, "pointcloud evaluation failed"

        self.post_threadend(self.export_to_piScope_shell,
                            ret, 'point_data')

    def onExportR2Points(self, evt):
        wx.CallAfter(
            dialog.showtraceback,
            parent=self,
            txt='Not Yet Implemented',
            title='Error',
            traceback='Exporing all time point for frequency \ndomain analysis is not available')
        wx.CallAfter(self.set_title_no_status)

    def call_eval_pointcloud(self, value):
        # def eval_pointcloud(self
        expr = str(value[0]).strip()
        attrs = str(value[2])
        phys_path = value[3]
        pc_mode = value[1][0]
        if pc_mode == 'XYZ':
            pc_value = value[1][1]
        elif pc_mode == 'Line':
            pc_value = value[1][2]
        else:
            return -1, None
        ptx, data, attrs_out, attrs, pc_param = self.eval_pointcloud(
            expr, attrs, phys_path, pc_mode, pc_value, mode='plot')
        if data is None:
            return -1, None
        return 1, (ptx, data, attrs_out, attrs, pc_param)

    def make_plot_point(
            self,
            data,
            attrs_out,
            attrs,
            param,
            cls=None,
            expr=False):
        value = self.elps['Points'] .GetValue()

        from ifigure.interactive import figure
        viewer = figure(viewer=cls)
        setup_figure(viewer, self.GetParent())

        viewer.update(False)
        viewer.suptitle(expr + ':' + str(attrs))

        if param['pc_type'] == 'line':
            pc_param = param['pc_param']
            sp = np.array(pc_param[0])
            ep = np.array(pc_param[1])
            num = pc_param[2]
            ii = np.linspace(0, 1., num)
            ptx = np.vstack([sp * (1 - i) + ep * i for i in ii])
            if ptx.shape(-1) == 3:
                # setup_figure(viewer, self.GetParent())
                viewer.plot(ptx[:, 0], ptx[:, 1], ptx[:, 2],
                            c=data.real, cz=True)
            else:
                viewer.plot(data.real)

        ax = self.GetParent().get_axes()
        param = ax.get_axes3d_viewparam(ax._artists[0])
        ax2 = viewer.get_axes()
        ax2.set_axes3d_viewparam(param, ax2._artists[0])
        viewer.lighting(light=0.5)
        viewer.update(True)

    def process_abcd(self, abcd_txt, phys_ns):
        ll = {"YZ": _YZ((1, 0, 0., 0)),
              "XY": _XY((0., 0, 1., 0)),
              "ZX": _ZX((0., 1, 0., 0)),
              "yz": _YZ((1, 0, 0., 0)),
              "xy": _XY((0., 0, 1., 0)),
              "zx": _ZX((0., 1, 0., 0)), }
        # add all combinations
        ll["ZY"] = ll["YZ"]
        ll["zy"] = ll["YZ"]
        ll["YX"] = ll["XY"]
        ll["yx"] = ll["XY"]
        ll["XZ"] = ll["ZX"]
        ll["xz"] = ll["ZX"]

        abcd_value = eval(abcd_txt, ll, phys_ns)

        lens = [1, 1, 1, 1]
        for i in range(4):
            try:
                lens[i] = len(abcd_value[i])
            except TypeError:
                pass
        num_planes = max(lens)
        planes = [None]*4

        for i in range(4):
            if lens[i] == 1:
                planes[i] = [abcd_value[i]]*num_planes
            elif lens[i] == num_planes:
                planes[i] = abcd_value[i]
            else:
                if param is None:
                    wx.CallAfter(
                        dialog.showtraceback,
                        parent=self,
                        txt='Can not determin planes',
                        title='Error',
                        traceback='a, b, c, d has to have the same lenght when multiple planes are defined',)
                return None
        return planes

#    to time this routine, we turn on this decorator
#    from petram.debug import use_profiler
#    @use_profiler
    def eval_pointcloud(
            self,
            expr,
            attrs,
            phys_path,
            pc_mode,
            pc_value,
            mode='plot'):
        model = self.GetParent().model
        solfiles = self.get_model_soldfiles()

        mesh = model.variables.getvar('mesh')
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()

        ec = mesh.extended_connectivity
        v2s = ec['vol2surf']
        FaceOf, EdgeOf, PointOf = get_mapper(mesh)
        ll = {'FaceOf': FaceOf, 'EdgeOf': EdgeOf, 'PointOf': PointOf}

        if attrs == 'all':
            attrs = list(np.unique(mesh.GetAttributeArray()))

        elif attrs == 'visible':
            m = self.GetParent()
            battrs = []
            for name, child in m.get_axes(0).get_children():
                if name.startswith('face'):
                    battrs.extend(child.shown_component)
            battrs = list(set(battrs))
            if mesh.Dimenstion() == 3:
                attrs = [
                    k for k in v2s if set(
                        v2s[k]).intersection(battrs) == set(
                        v2s[k])]
            else:
                attrs = battrs

        elif attrs == 'hidden':
            m = self.GetParent()
            battrs = []
            sbattrs = []
            for name, child in m.get_axes(0).get_children():
                if name.startswith('face'):
                    battrs.extend(child.hidden_component)
                    sbattrs.extend(child.shown_component)
            battrs = list(set(battrs))
            if mesh.Dimenstion() == 3:
                attrs = [
                    k for k in v2s if set(
                        v2s[k]).intersection(sbattrs) != set(
                        v2s[k])]
            else:
                attrs = battrs

        else:
            try:
                attrs = list(np.atleast_1d(eval(attrs, ll, phys_ns)))
            except BaseException:
                traceback.print_exc()
                assert False, "invalid selection: " + attrs

        ll = {}
        if pc_mode == 'XYZ':
            xx = np.atleast_1d(eval(pc_value[0], ll, phys_ns))
            yy = np.atleast_1d(eval(pc_value[1], ll, phys_ns))
            zz = np.atleast_1d(eval(pc_value[2], ll, phys_ns))
            pc_param = {'pc_type': 'xyz',
                        'pc_param': np.stack([xx, yy, zz], -1)}
            ptx, data, attrs_out = self.evaluate_pointcloud(
                expr, attrs, phys_path, **pc_param)

            return ptx, data, attrs_out, attrs, pc_param

        elif pc_mode == 'Line':
            sp = tuple(np.atleast_1d(eval(pc_value[0], ll, phys_ns)))
            ep = tuple(np.atleast_1d(eval(pc_value[1], ll, phys_ns)))
            num = int(eval(pc_value[2], ll, phys_ns))
            pc_param = {'pc_type': 'line', 'pc_param': (sp, ep, num)}
            ptx, data, attrs_out = self.evaluate_pointcloud(
                expr, attrs, phys_path, **pc_param)

            return ptx, data, attrs_out, attrs, pc_param

        elif pc_mode == 'CutPlane':
            planes = self.process_abcd(pc_value[0], phys_ns)
            if planes is None:
                return None, None, None, None, None

            params = []
            e1 = list(np.atleast_1d(eval(pc_value[1], ll, phys_ns)))
            res = float(eval(pc_value[2], ll, phys_ns))
            from petram.helper.geom import find_cp_pc_parameter
            for abcd in zip(*planes):
                param = find_cp_pc_parameter(
                    mesh, abcd, e1, gsize=res, attrs=attrs)
                if param is not None:
                    param = (tuple(param["origin"]),
                             tuple(param["e1"]),
                             tuple(param["e2"]),
                             tuple(param["x"]),
                             tuple(param["y"]),)

                params.append(param)

            if len(params) == 0:
                wx.CallAfter(
                    dialog.showtraceback,
                    parent=self,
                    txt='No point is found',
                    title='Error',
                    traceback='Some cut plane does not intersect with geometry')
                return None, None, None, None, None

            ptx_list = []
            data_list = []
            attrs_out_list = []
            attrs_list = []
            pc_param_list = []

            for param in params:
                pc_param = {'pc_type': 'cutplane', 'pc_param': param}
                ptx, data, attrs_out = self.evaluate_pointcloud(
                    expr, attrs, phys_path, **pc_param)

                ptx_list.append(ptx)
                data_list.append(data)
                attrs_out_list.append(attrs_out)
                attrs_list.append(attrs)
                pc_param_list.append(pc_param)

            return ptx_list, data_list, attrs_out_list, attrs_list, pc_param_list

        else:
            return None, None, None, None, None

    def get_attrs_field_Points(self):
        return 2

    #
    #   Geometry Boundary ('GeomBdr' tab)
    #
    def onApplyGeomBdr(self, evt):
        x, y, z = self.eval_geombdr(mode='plot')

        value = self.elps['GeomBdr'] .GetValue()
        battrs = str(value[3])
        edge_only = bool(value[8])

        c1 = value[5][0]
        c2 = value[5][1]
        kwargs = {'facecolor': c1,
                  'edgecolor': c2, }
        if c2 == (0, 0, 0, 0):
            kwargs['linewidth'] = 0.

        from ifigure.interactive import figure
        v = figure()
        setup_figure(v, self.GetParent())

        v.update(False)
        v.suptitle('Boundary ' + str(battrs))
        for xdata, ydata, zdata in zip(x, y, z):
            verts = np.vstack((xdata[1], ydata[1], zdata[1])).transpose()
            adata = xdata[2]
            v.solid(verts, adata, **kwargs)

        v.update(True)
        v.update(False)
        ax = self.GetParent().get_axes()
        param = ax.get_axes3d_viewparam(ax._artists[0])
        ax2 = v.get_axes()
        ax2.set_axes3d_viewparam(param, ax2._artists[0])
        v.lighting(light=0.5)
        v.update(True)

    def onExportGeomBdr(self, evt):
        from petram.sol.evaluators import area_tri
        x, y, z = self.eval_geombdr(mode='integ')
        # if data is None: return

        verts = np.dstack((x[0][1], y[0][1], z[0][1]))
        data = {'vertices': verts}
        self.export_to_piScope_shell(data, 'geom_data')

    def get_attrs_field_GeomBdr(self):
        return 3

    def eval_geombdr(self, mode='plot'):
        value = self.elps['GeomBdr'] .GetValue()
        cls = None
        expr_x = str(value[0]).strip()
        expr_y = str(value[1]).strip()
        expr_z = str(value[2]).strip()

        battrs = str(value[3])
        phys_path = value[4]
        edge_only = bool(value[8])
        if mode == 'plot':
            do_merge1 = value[6]
            do_merge2 = value[7]
        else:
            do_merge1 = True
            do_merge2 = False
        if edge_only:
            do_merge1 = False
            do_merge2 = False

        def call_eval_sol_bdr(expr, battrs=battrs, phys_path=phys_path,
                              do_merge1=do_merge1, do_merge2=do_merge2,
                              edge_only=edge_only):
            if str(expr).strip() != '':
                v, battrs = self.evaluate_sol_bdr(expr, battrs, phys_path,
                                                  do_merge1, do_merge2,
                                                  edge_only=edge_only)
            else:
                v = None
            return v
        x = call_eval_sol_bdr(expr_x)
        y = call_eval_sol_bdr(expr_y)
        z = call_eval_sol_bdr(expr_z)
        if x is None and y is None and z is None:
            return
        basedata = x
        if basedata is None:
            basedata = y
        if basedata is None:
            basedata = z

        zerodata = [(None, cdata * 0, adata) for verts, cdata, adata
                    in basedata]
        if x is None:
            x = zerodata
        if y is None:
            y = zerodata
        if z is None:
            z = zerodata
        return x, y, z

    #
    #   Arrow on Boundary ('Bdr(arrow)' tab)
    #
    def onApplyBdrarrow(self, evt):
        value = self.elps['Bdr(arrow)'].GetValue()
        self._onApplyBdrarrow(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onApplyBdrarrow(self, value):
        u, v, w, battrs = self.eval_bdrarrow(value, mode='plot')

        expr_u = str(value[0]).strip()
        expr_v = str(value[1]).strip()
        expr_w = str(value[2]).strip()
        if value[5]:
            from ifigure.widgets.wave_viewer import WaveViewer
            cls = WaveViewer
        else:
            cls = None

        self.post_threadend(self.make_plot_bdrarrow, u, v, w, battrs, value,
                            expr_u=expr_u,
                            expr_v=expr_v,
                            expr_w=expr_w,
                            cls=cls)

    def make_plot_bdrarrow(self, u, v, w, battrs, value,
                           expr_u='', expr_v='', expr_w='',
                           cls=None):

        from ifigure.interactive import figure

        viewer = figure(viewer=cls)
        setup_figure(viewer, self.GetParent())

        viewer.update(False)
        viewer.suptitle(
            '[' + ','.join((expr_u, expr_v, expr_w)) + '] : ' + str(battrs))

        allxyz = np.vstack([udata[0] for udata in u])
        dx = np.max(allxyz[:, 0]) - np.min(allxyz[:, 0])
        if allxyz.shape[1] > 1:
            dy = np.max(allxyz[:, 1]) - np.min(allxyz[:, 1])
        else:
            dy = dx * 0.
        if allxyz.shape[1] > 2:
            dz = np.max(allxyz[:, 2]) - np.min(allxyz[:, 2])
        else:
            dz = dy * 0.
        length = np.max((dx, dy, dz)) / 20.

        for udata, vdata, wdata in zip(u, v, w):
            xyz = udata[0]

            u = udata[1]
            v = vdata[1]
            w = wdata[1]

            ll = np.min([xyz.shape[0] - 1, int(value[7])])
            idx = np.linspace(0, xyz.shape[0] - 1, ll).astype(int)

            x = xyz[idx, 0]
            if xyz.shape[1] > 1:
                y = xyz[idx, 1]
            else:
                y = x * 0.
            if xyz.shape[1] > 2:
                z = xyz[idx, 2]
            else:
                z = x * 0.

            viewer.quiver3d(x, y, z, u[idx], v[idx], w[idx],
                            length=length)

        viewer.update(True)

        viewer.update(False)
        ax = self.GetParent().get_axes()
        param = ax.get_axes3d_viewparam(ax._artists[0])
        ax2 = viewer.get_axes()
        ax2.set_axes3d_viewparam(param, ax2._artists[0])
        viewer.lighting(light=0.5)
        viewer.update(True)

    def onExportBdrarrow(self, evt):
        value = self.elps['Bdr(arrow)'].GetValue()

        u, v, w, battrs = self.eval_bdrarrow(value, mode='export')
        udata = u[0][1]
        vdata = v[0][1]
        wdata = w[0][1]
        verts = v[0][0]
        xyz = np.mean(verts, 1)
        u = np.mean(udata, 1)
        v = np.mean(vdata, 1)
        w = np.mean(wdata, 1)
        data = {'x': xyz[:, 0],
                'y': xyz[:, 1],
                'z': xyz[:, 2],
                'u': u,
                'v': v,
                'w': w}
        self.export_to_piScope_shell(data, 'arrow_data')
        evt.Skip()

    def get_attrs_field_Bdrarrow(self):
        return 3

    def eval_bdrarrow(self, value, mode='plot'):
        expr_u = str(value[0]).strip()
        expr_v = str(value[1]).strip()
        expr_w = str(value[2]).strip()

        battrs = str(value[3])
        phys_path = value[4]
        if mode == 'plot':
            do_merge1 = value[6]
            do_merge2 = False
        else:
            do_merge1 = True
            do_merge2 = False

        def call_eval_sol_bdr(expr, battrs=battrs, phys_path=phys_path,
                              do_merge1=do_merge1, do_merge2=do_merge2):
            if str(expr).strip() != '':
                v, battrs = self.evaluate_sol_bdr(expr, battrs, phys_path,
                                                  do_merge1, do_merge2)
            else:
                v = None
                battrs = None
            return v, battrs

        u, ubattrs = call_eval_sol_bdr(expr_u)
        v, vbattrs = call_eval_sol_bdr(expr_v)
        w, wbattrs = call_eval_sol_bdr(expr_w)
        if u is None and v is None and w is None:
            return

        basedata = u
        battrs = ubattrs
        if basedata is None:
            basedata = v
            battrs = vbattrs
        if basedata is None:
            basedata = w
            battrs = wbattrs

        zerodata = [(verts, cdata * 0, adata)
                    for verts, cdata, adata in basedata]
        if u is None:
            u = zerodata
        if v is None:
            v = zerodata
        if w is None:
            w = zerodata
        return u, v, w, battrs
    #
    #   Slice plane ('Slice' tab)
    #

    def onApplySlice(self, evt):
        value = self.elps['Slice'] .GetValue()
        self._onSliceCommon(value)
        evt.Skip()

    def onExportSlice(self, evt):
        value = self.elps['Slice'] .GetValue()
        self._onSliceCommon(value, mode='export')
        evt.Skip()

    def onxportR1Slice(self, evt):
        value = self.elps['Slice'] .GetValue()

        remote, base, subs = self.get_current_choices()

        dataset = []
        for s in subs:
            if remote:
                self.config['cs_soldir'] = base
                self.config['cs_solsubdir'] = s
            else:
                self.local_soldir = base
                self.local_solsubdir = s
                self.load_sol_if_needed()

            data = self._onSliceCommon(value, mode='export_return')
            if data is None:
                assert False, "returned value is None ???"

            dataset.append({"subdir", s, "data", data})

        self.post_threadend(self.export_to_piScope_shell,
                            dataset, 'slice_data')

    def onExportR2Slice(self, evt):
        wx.CallAfter(
            dialog.showtraceback,
            parent=self,
            txt='Not Yet Implemented',
            title='Error',
            traceback='Exporing all time slice for frequency \ndomain analysis is not available')
        wx.CallAfter(self.set_title_no_status)

    @run_in_piScope_thread
    def _onSliceCommon(self, value, mode='plot'):
        expr = str(value[0]).strip()

        if value[4]:
            from ifigure.widgets.wave_viewer import WaveViewer
            cls = WaveViewer
        else:
            cls = None

        if value[1][0] == 'Triangles':
            phys_path = value[3]
            data, battrs = self.eval_slice(phys_path, mode='plot')
            if data is None:
                wx.CallAfter(self.set_title_no_status)
                return
            if mode == 'export':
                data = {'data': data}
                self.export_to_piScope_shell(data, 'slice_data')
            elif mode == 'export_return':
                data = {'data': data}
                return data
            else:
                self.post_threadend(self.make_plot_slice,
                                    data, battrs, cls=cls, expr=expr)
                return

        elif value[1][0] == 'Grid':
            attrs = str(value[2])
            phys_path = value[3]
            pc_mode = 'CutPlane'
            pc_value = value[1][2]
            ptx_list, data_list, attrs_out_list, attrs_list, pc_param_list = self.eval_pointcloud(
                expr, attrs, phys_path, pc_mode, pc_value, mode='plot')

            num = 1
            dataset = []

            for ptx, data, attrs_out, attrs, pc_param in zip(ptx_list, data_list, attrs_out_list,
                                                             attrs_list, pc_param_list):

                if data is None:
                    continue

                pc_param = pc_param['pc_param']
                im_center = pc_param[0]
                im_axes = (pc_param[1], pc_param[2])
                midx = (pc_param[3][0] + pc_param[3][1]) / 2.0
                midy = (pc_param[4][0] + pc_param[4][1]) / 2.0
                xmin, xmax, xsize = pc_param[3]
                ymin, ymax, ysize = pc_param[4]

                x = np.linspace(xmin, xmax, int((xmax - xmin) / xsize))
                y = np.linspace(ymin, ymax, int((ymax - ymin) / ysize))

                suffix = "_plane"+str(num)
                if mode == 'export':
                    data = {'data'+suffix: data, 'x'+suffix: x, 'y'+suffix: y,
                            'im_axes'+suffix: im_axes, 'im_center'+suffix: im_center}
                    self.export_to_piScope_shell(data, 'slice_data')
                elif mode == 'export_return':
                    data = {'data'+suffix: data, 'x'+suffix: x, 'y'+suffix: y,
                            'im_axes'+suffix: im_axes, 'im_center'+suffix: im_center}
                    dataset.append(data)
                else:
                    data = {'data': data, 'x': x, 'y': y,
                            'im_axes': im_axes, 'im_center': im_center,
                            'attrs_out': attrs_out,
                            'attrs': attrs}
                    dataset.append(data)
                num = num+1

            if mode == 'export_return':
                return dataset
            elif mode == 'plot':
                self.post_threadend(
                    self.make_plot_pc_slice,
                    dataset,
                    cls=cls,
                    expr=expr)

    def make_plot_slice(self, dataset, battrsset, cls=None, expr=''):
        from ifigure.interactive import figure
        v = figure(viewer=cls)
        setup_figure(v, self.GetParent())

        v.update(False)

        first = True
        for data,  battrs in zip(dataset, battrsset):
            if first:
                v.suptitle(expr + ':' + str(battrs))
                first = False
            for verts, cdata, adata in data:
                if cls is None:
                    v.solid(verts, adata, cz=True, cdata=cdata.astype(float),
                            shade='linear')
                else:
                    v.solid(verts, adata, cz=True, cdata=cdata, shade='linear')

        v.update(True)

        v.update(False)
        ax = self.GetParent().get_axes()
        param = ax.get_axes3d_viewparam(ax._artists[0])
        ax2 = v.get_axes()
        ax2.set_axes3d_viewparam(param, ax2._artists[0])
        v.lighting(light=0.5)
        v.update(True)

    def make_plot_pc_slice(self, dataset, cls=None, expr=False):

        from ifigure.interactive import figure

        first = True
        viewer = None

        for d in dataset:
            data = d['data']
            attrs_out = d['attrs_out']
            attrs = d['attrs']
            x = d['x']
            y = d['y']
            im_axes = d['im_axes']
            im_center = d['im_center']

            if first:
                viewer = figure(viewer=cls)
                setup_figure(viewer, self.GetParent())
                viewer.update(False)
                viewer.threed('on')

                viewer.suptitle(
                    expr + ':' + ",".join(([str(int(x)) for x in attrs])))
                first = False
            if cls is None:
                data = np.ma.masked_array(
                    data.real, mask=np.in1d(
                        attrs_out, attrs, invert=True))
            else:
                data = np.ma.masked_array(
                    data, mask=np.in1d(
                        attrs_out, attrs, invert=True))
            viewer.image(x, y, data, im_axes=im_axes, im_center=im_center)

            ax = self.GetParent().get_axes()
            param = ax.get_axes3d_viewparam(ax._artists[0])
            ax2 = viewer.get_axes()
            ax2.set_axes3d_viewparam(param, ax2._artists[0])
            viewer.lighting(light=0.5)

        if viewer is not None:
            viewer.update(True)

    def get_attrs_field_Slice(self):
        return 2

    def eval_slice(self, phys_path, mode='plot'):
        model = self.GetParent().model
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()

        value = self.elps['Slice'] .GetValue()

        expr = str(value[0]).strip()

        planes = self.process_abcd(str(value[1][1][0]), phys_ns)

        attrs = str(value[2])
        phys_path = value[3]
        if mode == 'plot':
            do_merge1 = value[5]
            do_merge2 = False
        else:
            do_merge1 = True
            do_merge2 = False

        dataset = []
        attrsset = []
        for plane in zip(*planes):
            data, battrs = self.evaluate_sol_slice(expr, attrs, plane, phys_path,
                                                   do_merge1, do_merge2)
            if data is not None:
                dataset.append(data)
                attrsset.append(battrs)

        if len(dataset) == 0:
            return None, None
        return dataset, attrsset

    '''
    integral
    '''

    def onApplyIntegral(self, evt):
        gui_value = self.elps['Integral'] .GetValue()
        self._onApplyIntegral(gui_value)
        evt.Skip()

    def _onApplyIntegral(self, gui_value):
        expr, value, kind, idx, order = self.eval_integral(gui_value)
        if value is not None:
            data = {'value': value,
                    'kind': kind,
                    'order': order,
                    'idx': idx,
                    'expr': expr, }
            self.post_threadend(self.export_to_piScope_shell,
                                data, 'integral_data')
            self.post_threadend(print,
                                "Integrated value", data['value'])

    def get_attrs_field_Integral(self):
        return 2

    def eval_integral(self, gui_value):
        expr = str(gui_value[0]).strip()
        kind = str(gui_value[1]).strip()
        attrs = str(gui_value[2])
        order = int(gui_value[3])
        phys_path = str(gui_value[4]).strip()

        value = self.evaluate_sol_integral(expr, kind, attrs, order, phys_path)
        return expr, value, kind, attrs, order
    '''
    probe
    '''

    def onApplyProbe(self, evt):
        value = self.elps['Probe'] .GetValue()
        self._onApplyProbe(value)
        evt.Skip()

    @run_in_piScope_thread
    def _onApplyProbe(self, value):
        expr = str(value[0]).strip()
        xexpr = str(value[1]).strip()

        xdata, data = self.eval_probe(value, mode='plot')
        all_data = {'xdata': xdata, 'data': data}

        if data is None and xdata is None:
            wx.CallAfter(self.set_title_no_status)
            return

        if len(data.shape) == 0:
            wx.CallAfter(self.set_title_no_status)
            wx.CallAfter(self.export_to_piScope_shell, all_data, 'probe_data')
            return

        self.post_threadend(
            self.make_plot_probe, (xdata, data), expr=expr, xexpr=xexpr)

    def onExportProbe(self, evt):
        value = self.elps['Probe'] .GetValue()

        remote, base, subs = self.get_current_choices()
        if len(subs) > 1:
            from petram.pi.dlg_export_opts import ask_export_opts
            opts = ask_export_opts(self, support_integ=False)

            if opts is None:
                return
            do_loop = opts[-1]
        else:
            do_loop = False

        self._onExportProbe(value, do_loop)
        evt.Skip()

    @run_in_piScope_thread
    def _onExportProbe(self, value, do_loop):
        remote, base, subs = self.get_current_choices()
        if do_loop:
            all_data = []

            bk = (self.local_soldir,
                  self.local_solsubdir,
                  self.config['cs_soldir'],
                  self.config['cs_solsubdir'],)

            for s in subs:
                if remote:
                    self.config['cs_soldir'] = base
                    self.config['cs_solsubdir'] = s
                else:
                    self.local_soldir = base
                    self.local_solsubdir = s
                    self.load_sol_if_needed()

                data = self.make_export_probe_data(value)
                print("!!!!!", data)
                if data is None:
                    continue

                data["subdirs"] = s
                all_data.append(data)

            self.local_soldir = bk[0]
            self.local_solsubdir = bk[1]
            self.config['cs_soldir'] = bk[2]
            self.config['cs_solsubdir'] = bk[3]

        else:
            all_data = self.make_export_probe_data(value)

        if all_data is None or len(all_data) == 0:
            return  # nothine to export
        wx.CallAfter(self.export_to_piScope_shell, all_data, 'probe_data')

    def make_export_probe_data(self, value):
        xdata, data = self.eval_probe(value, mode='plot')

        if data is None:
            return
        # if xdata is None:
        #    return

        data = {'xdata': xdata, 'data': data}
        return data

    def make_plot_probe(self, data, expr='', xexpr='', cls=None):
        from ifigure.interactive import figure
        v = figure(viewer=cls)
        v.update(False)
        v.suptitle(expr)
        if len(xexpr) != 0:
            v.xlabel(xexpr)
            v.plot(data[0], data[1])
        else:
            v.plot(data[1])
        v.update(True)

    def eval_probe(self, value, mode='plot'):
        expr = str(value[0]).strip()
        xexpr = str(value[1]).strip()
        # phys_path = value[2]
        xdata, data = self.evaluate_sol_probe(expr, xexpr)
        return xdata, data

    #
    #   common routines
    #
    def evaluate_sol_edge(self, expr, battrs, phys_path, do_merge1, do_merge2,
                          **kwargs):
        '''
        evaluate sol using boundary evaluator
        '''
        model = self.GetParent().model
        solfiles = self.get_model_soldfiles()
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()

        if solfiles is None:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Solution does not exist',
                         title='Error',
                         traceback='')
            wx.CallAfter(self.set_title_no_status)
            return None, None
        mesh = model.variables.getvar('mesh')
        if mesh is None:
            return

        FaceOf, EdgeOf, PointOf = get_mapper(mesh)
        ll = {'FaceOf': FaceOf, 'EdgeOf': EdgeOf, 'PointOf': PointOf}

        battrs = str(battrs).strip()
        if battrs.lower() == 'all':
            battrs = list(mesh.extended_connectivity['line2vert'])
        else:
            try:
                battrs = list(np.atleast_1d(eval(battrs, ll, phys_ns)))
            except BaseException:
                traceback.print_exc()
                assert False, "invalid selection: " + battrs

        from petram.sol.evaluators import build_evaluator

        average = kwargs.pop('average', True)
        if average:
            key, name = 'Edge', 'EdgeNodal'
        else:
            key, name = 'NCEdge', 'NCEdge'

        if key in self.evaluators:
            try:
                self.evaluators[key].validate_evaluator(name,
                                                        battrs,
                                                        solfiles)
            except IOError:
                dprint1("IOError detected setting failed=True")
                self.evaluators[key].failed = True

        if (key not in self.evaluators or
                self.evaluators[key].failed):
            if key in self.evaluators:
                self.evaluators[key].terminate_all()
            try:
                self.evaluators[key] = build_evaluator(battrs,
                                                       mfem_model,
                                                       solfiles,
                                                       name=name,
                                                       config=self.config)
            except:
                wx.CallAfter(dialog.showtraceback, parent=self,
                             txt='Failed to build evaluator',
                             title='Error',
                             traceback=''.join(traceback.format_exception_only(
                                 sys.exc_info()[0], sys.exc_info()[1])))
                wx.CallAfter(self.set_title_no_status)
                return None, None

            self.evaluators[key].validate_evaluator(name,
                                                    battrs,
                                                    solfiles, isFirst=True)

        try:
            self.evaluators[key].set_phys_path(phys_path)
            return self.evaluators[key].eval(expr, do_merge1, do_merge2,
                                             **kwargs)
        except BaseException:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Failed to evauate expression',
                         title='Error',
                         traceback=''.join(traceback.format_exception_only(
                             sys.exc_info()[0], sys.exc_info()[1])))
            wx.CallAfter(self.set_title_no_status)
        return None, None

    def evaluate_sol_bdr(self, expr, battrs, phys_path, do_merge1, do_merge2,
                         **kwargs):
        '''
        evaluate sol using boundary evaluator
        '''
        model = self.GetParent().model

        solfiles = self.get_model_soldfiles()
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()

        if solfiles is None:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Solution does not exist',
                         title='Error',
                         traceback='')
            wx.CallAfter(self.set_title_no_status)
            return None, None
        mesh = model.variables.getvar('mesh')
        if mesh is None:
            return
        FaceOf, EdgeOf, PointOf = get_mapper(mesh)
        ll = {'FaceOf': FaceOf, 'EdgeOf': EdgeOf, 'PointOf': PointOf}

        if battrs == 'all':
            battrs = list(mesh.extended_connectivity['surf2line'])
        elif battrs == 'visible':
            m = self.GetParent()
            battrs = []
            for name, child in m.get_axes(0).get_children():
                if name.startswith('face'):
                    battrs.extend(child.shown_component)
            battrs = list(set(battrs))
        elif battrs == 'hidden':
            m = self.GetParent()
            battrs = []
            for name, child in m.get_axes(0).get_children():
                if name.startswith('face'):
                    battrs.extend(child.hidden_component)
            battrs = list(set(battrs))
        else:
            try:
                battrs = list(np.atleast_1d(eval(battrs, ll, phys_ns)))
            except BaseException:
                traceback.print_exc()
                assert False, "invalid selection: " + battrs

            # battrs = [x+1 for x in range(mesh.bdr_attributes.Size())]

        average = kwargs.pop('average', True)
        decimate = kwargs.get('decimate', 1)

        from petram.sol.evaluators import build_evaluator
        if average:
            key, name = 'Bdr', 'BdrNodal'
        else:
            key, name = 'NCFace', 'NCFace'

        if key in self.evaluators:
            try:
                self.evaluators[key].validate_evaluator(name, battrs, solfiles,
                                                        decimate=decimate)
            except IOError:
                dprint1("IOError detected setting failed=True")
                self.evaluators[key].failed = True

        if (key not in self.evaluators or
                self.evaluators[key].failed):
            if key in self.evaluators:
                self.evaluators[key].terminate_all()
            try:
                self.evaluators[key] = build_evaluator(battrs,
                                                       mfem_model,
                                                       solfiles,
                                                       name=name,
                                                       config=self.config,
                                                       decimate=decimate)
            except:
                wx.CallAfter(dialog.showtraceback, parent=self,
                             txt='Failed to build evaluator',
                             title='Error',
                             traceback=''.join(traceback.format_exception_only(
                                 sys.exc_info()[0], sys.exc_info()[1])))
                wx.CallAfter(self.set_title_no_status)
                return None, None

            self.evaluators[key].validate_evaluator(name,
                                                    battrs,
                                                    solfiles, isFirst=True,
                                                    decimate=decimate)

        try:
            self.evaluators[key].set_phys_path(phys_path)
            return self.evaluators[key].eval(expr, do_merge1, do_merge2,
                                             **kwargs)
        except BaseException:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Failed to evauate expression',
                         title='Error',
                         traceback=traceback.format_exc())
            wx.CallAfter(self.set_title_no_status)
        return None, None

    def evaluate_pointcloud(self, expr, attrs, phys_path, **kwargs):
        '''
        evaluate sol using boundary evaluator
        '''
        model = self.GetParent().model
        solfiles = self.get_model_soldfiles()
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()

        if solfiles is None:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Solution does not exist',
                         title='Error',
                         traceback='')
            wx.CallAfter(self.set_title_no_status)
            return None, None, None
        mesh = model.variables.getvar('mesh')
        if mesh is None:
            return None, None, None

        from petram.sol.evaluators import build_evaluator
        key, name = 'Points', 'Pointcloud'

        if key in self.evaluators:
            try:
                self.evaluators[key].validate_evaluator(
                    name, attrs, solfiles, **kwargs)

            except IOError:
                dprint1("IOError detected setting failed=True")
                self.evaluators[key].failed = True

        if (key not in self.evaluators or
                self.evaluators[key].failed):
            if key in self.evaluators:
                self.evaluators[key].terminate_all()

            try:
                self.evaluators[key] = build_evaluator(attrs,
                                                       mfem_model,
                                                       solfiles,
                                                       name=name,
                                                       config=self.config,
                                                       **kwargs)
            except:
                wx.CallAfter(dialog.showtraceback, parent=self,
                             txt='Failed to build evaluator',
                             title='Error',
                             traceback=''.join(traceback.format_exception_only(
                                 sys.exc_info()[0], sys.exc_info()[1])))
                wx.CallAfter(self.set_title_no_status)
                return None, None

            self.evaluators[key].validate_evaluator(name,
                                                    attrs,
                                                    solfiles, isFirst=True,
                                                    **kwargs)

        try:
            self.evaluators[key].set_phys_path(phys_path)
            return self.evaluators[key].eval_pointcloud(expr)
        except BaseException:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Failed to evauate expression',
                         title='Error',
                         traceback=traceback.format_exc())
            wx.CallAfter(self.set_title_no_status)
        return None, None, None

    def evaluate_sol_slice(self, expr, attrs, plane, phys_path, do_merge1,
                           do_merge2):
        '''
        evaluate sol using slice evaluator
        '''
        model = self.GetParent().model
        solfiles = self.get_model_soldfiles()
        mfem_model = model.param.getvar('mfem_model')
        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()
        ll = {}

        if solfiles is None:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Solution does not exist',
                         title='Error',
                         traceback='')
            wx.CallAfter(self.set_title_no_status)
            return None, None
        mesh = model.variables.getvar('mesh')
        if mesh is None:
            return None, None

        if mesh.SpaceDimension() != 3:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Triangle Slice plot works only for 3D Tet mesh',
                         title='Error',
                         traceback='')
            wx.CallAfter(self.set_title_no_status)
            return None, None
        if attrs != 'all':
            try:
                attrs = list(np.atleast_1d(eval(attrs, ll, phys_ns)))
            except BaseException:
                traceback.print_exc()
                assert False, "Failed to evaluate attrs " + attrs
        else:
            attrs = list(mesh.extended_connectivity['vol2surf'])
            # attrs = [x+1 for x in range(mesh.attributes.Size())]

        if 'Slice' in self.evaluators:
            try:
                self.evaluators['Slice'].validate_evaluator(
                    'Slice', attrs, solfiles, plane=plane)
            except IOError:
                dprint1("IOError detected setting failed=True")
                self.evaluators['Slice'].failed = True

        from petram.sol.evaluators import build_evaluator
        if ('Slice' not in self.evaluators or
                self.evaluators['Slice'].failed):

            if 'Slice' in self.evaluators:
                self.evaluators['Slice'].terminate_all()

            try:
                self.evaluators['Slice'] = build_evaluator(attrs,
                                                           mfem_model,
                                                           solfiles,
                                                           name='Slice',
                                                           config=self.config,
                                                           plane=plane)
            except:
                wx.CallAfter(dialog.showtraceback, parent=self,
                             txt='Failed to build evaluator',
                             title='Error',
                             traceback=''.join(traceback.format_exception_only(
                                 sys.exc_info()[0], sys.exc_info()[1])))
                wx.CallAfter(self.set_title_no_status)
                return None, None

            self.evaluators['Slice'].validate_evaluator('Slice', attrs,
                                                        solfiles, isFirst=True,
                                                        plane=plane)

        try:
            self.evaluators['Slice'].set_phys_path(phys_path)
            # we do this in order to catch a communication error, which return
            # None
            ret1, ret2 = self.evaluators['Slice'].eval(
                expr, do_merge1, do_merge2)
            return ret1, ret2
        except BaseException:
            wx.CallAfter(dialog.showtraceback,
                         parent=self,
                         txt='Failed to evauate expression',
                         title='Error',
                         traceback=traceback.format_exc())

            wx.CallAfter(self.set_title_no_status)
        return None, None

    def evaluate_sol_integral(self, expr, kind, attrs, order, phys_path):
        model = self.GetParent().model
        solfiles = self.get_model_soldfiles()
        mfem_model = model.param.getvar('mfem_model')

        phys_ns = mfem_model[str(phys_path)]._global_ns.copy()
        mesh = model.variables.getvar('mesh')

        if attrs != 'all':
            try:
                attrs = list(np.atleast_1d(eval(attrs, {}, phys_ns)))
            except BaseException:
                traceback.print_exc()
                assert False, "Failed to evaluate attrs " + attrs
        else:
            if mesh.Dimension() == 3:
                if kind == 'Domain':
                    attrs = list(mesh.extended_connectivity['vol2surf'])
                else:
                    attrs = list(mesh.extended_connectivity['surf2line'])
            elif mesh.Dimension() == 2:
                if kind == 'Domain':
                    attrs = list(mesh.extended_connectivity['surf2line'])
                else:
                    attrs = list(mesh.extended_connectivity['line2vert'])
            elif mesh.Dimension() == 1:
                attrs = list(mesh.extended_connectivity['line2vert'])
            else:
                assert False, "unsupported mesh dimension"

        from petram.sol.evaluators import build_evaluator

        if 'Integral' in self.evaluators:
            self.evaluators['Integral'].terminate_all()

        try:
            self.evaluators['Integral'] = build_evaluator(attrs,
                                                          mfem_model,
                                                          solfiles,
                                                          name='Integral',
                                                          config=self.config)
        except:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Failed to build evaluator',
                         title='Error',
                         traceback=''.join(traceback.format_exception_only(
                             sys.exc_info()[0], sys.exc_info()[1])))
            wx.CallAfter(self.set_title_no_status)
            return None

        try:
            self.evaluators['Integral'].set_phys_path(phys_path)
            return self.evaluators['Integral'].eval_integral(expr,
                                                             kind=kind,
                                                             attrs=attrs,
                                                             order=order)
        except BaseException:
            wx.CallAfter(dialog.showtraceback,
                         parent=self,
                         txt='Failed to evauate expression (integral)',
                         title='Error',
                         traceback=traceback.format_exc())

            wx.CallAfter(self.set_title_no_status)
        return None

    def evaluate_sol_probe(self, expr, xexpr):
        model = self.GetParent().model
        solfiles = None   # probe does not load solfile (GridFunction)
        mfem_model = model.param.getvar('mfem_model')

        attrs = [1]

        from petram.sol.evaluators import build_evaluator

        if 'Probe' in self.evaluators:
            self.evaluators['Probe'].terminate_all()

        try:
            self.evaluators['Probe'] = build_evaluator(attrs,
                                                       mfem_model,
                                                       solfiles,
                                                       name='Probe',
                                                       config=self.config)
        except:
            wx.CallAfter(dialog.showtraceback, parent=self,
                         txt='Failed to build evaluator',
                         title='Error',
                         traceback=''.join(traceback.format_exception_only(
                             sys.exc_info()[0], sys.exc_info()[1])))
            wx.CallAfter(self.set_title_no_status)
            return None, None

        try:
            if not self.config['use_cs']:
                # probes = self.local_sols[0:2]
                probes = (self.local_sols[0], self.local_solsubdir)
            else:
                # probes = self.remote_sols[0:2]
                probes = (self.remote_sols[0], self.config['cs_solsubdir'])

            # self.evaluators['Probe'].set_phys_path(phys_path)
            data = self.evaluators['Probe'].eval_probe(expr, xexpr, probes)
            return data[1], np.transpose(data[2])
        except BaseException:
            wx.CallAfter(dialog.showtraceback,
                         parent=self,
                         txt='Failed to evauate expression (probe)',
                         title='Error',
                         traceback=traceback.format_exc())

            wx.CallAfter(self.set_title_no_status)
        return None, None

    #
    #   utilites
    #
    def export_to_piScope_shell(self, data, dataname):
        import wx
        import ifigure.widgets.dialog as dialog

        app = wx.GetApp().TopWindow
        app.shell.lvar[dataname] = data
        app.shell.SendShellEnterEvent()
        ret = dialog.message(app, dataname + ' is exported', 'Export', 0)

    def get_model_soldfiles(self):
        model = self.GetParent().model
        soldir = model.variables.getvar('remote_soldir')

        if not self.config['use_cs']:
            self.load_sol_if_needed()
            solfiles = model.variables.getvar('solfiles')
            return solfiles
        else:
            soldir = os.path.join(soldir, *self.config["cs_solsubdir"])
            return soldir
