1"""Collections of Popup menus associated to filenames."""
2import wx
3import wx.lib.dialogs as wxdg
4import abipy.gui.awx as awx
5import abipy.gui.electronswx as ewx
6
7from collections import OrderedDict
8from abipy.abilab import abifile_subclass_from_filename, abiopen
9from abipy.core.mixins import NcDumper, get_filestat
10from abipy.abio.outputs import AbinitLogFile, AbinitOutputFile
11from abipy.iotools.visualizer import Visualizer
12from abipy.waves import WfkFile
13from abipy.electrons import SigresFile, GsrFile
14from abipy.electrons.bse import MdfFile
15from abipy.gui.events import AbinitEventsFrame
16from abipy.gui.timer import AbinitTimerFrame
17from abipy.gui.editor import SimpleTextViewer, MyEditorFrame
18from abipy.gui.wxncview import NcViewerFrame
19
20
21__all__ = [
22    "popupmenu_for_filename",
23]
24
25
26def popupmenu_for_filename(parent, filename):
27    """
28    Factory function that returns the appropriate popup menu.
29
30    Args:
31        parent:
32            The parent wx window. Used to connect the children created
33            by the popupmenu to the calling window.
34    """
35    menu = PopupMenu.from_filename(filename)
36
37    if menu is not None:
38        menu.set_target(filename)
39        menu.set_parent(parent)
40
41    return menu
42
43#--------------------------------------------------------------------------------------------------
44# Callbacks
45
46
47def showNcdumpMessage(parent, filepath):
48    """Open a dialog with the output of ncdump."""
49    title = "ncdump output for file %s" % filepath
50    text = NcDumper().dump(filepath)
51    # TODO: Get a decent wxpython editor somewhere
52    #SimpleTextViewer(parent, text, title=title).Show()
53    MyEditorFrame.from_text(parent, text, title=title).Show()
54
55def openWxncview(parent, filepath):
56    """Open a dialog with the output of ncdump."""
57    title = "wxncview:  %s" % filepath
58    text = NcDumper().dump(filepath)
59    NcViewerFrame(parent, filepath, title=title).Show()
60
61
62def showFileStat(parent, filepath):
63    """Open a dialog reporting file stats."""
64    caption = "Info on file %s" % filepath
65    stat_dict = get_filestat(filepath)
66    msg = str(stat_dict)
67    style = wx.DEFAULT_FRAME_STYLE
68    wxdg.ScrolledMessageDialog(parent, msg, caption=caption, size=(600, 600), style=style).Show()
69
70
71def showAbinitEventsFrame(parent, filepath):
72    """Open a dialog reporting file stats."""
73    AbinitEventsFrame(parent, filepath).Show()
74
75
76def showAbinitTimerFrame(parent, filepath):
77    """Open a dialog reporting file stats."""
78    try:
79        frame = AbinitTimerFrame(parent, filepath)
80        frame.Show()
81    except awx.Error as exc:
82        awx.showErrorMessage(parent, str(exc))
83
84
85def showStructure(parent, filepath):
86    ncfile = abiopen(filepath)
87    visu_classes = Visualizer.get_available(ext="xsf")
88    if not visu_classes:
89        print("Not visualizer found for extension xsf")
90        return
91    vname = visu_classes[0].name
92
93    visu = ncfile.structure.visualize(vname)
94
95    thread = awx.WorkerThread(parent, target=visu)
96    thread.start()
97
98
99
100class PopupMenu(wx.Menu):
101    """
102    Base class for popup menus. `A PopupMenu` has a list of callback functions
103    indexed by the menu title. The signature of the callback function is func(parent, filename) where
104    filename is the name of the file selected in the Widget and parent is the wx
105    Window that will become the parent of the new frame created by the callback.
106    """
107    MENU_TITLES = OrderedDict([
108    ])
109
110    HANDLED_FILES = []
111
112    def __init__(self):
113        super(PopupMenu, self).__init__()
114        self._make_menu()
115
116    @staticmethod
117    def from_filename(filename):
118        """
119        Static factory function that instanciates the appropriate subclass of `NcFilePopupMenu`
120        Returns None if the extesion of filename is not supported.
121        """
122        # Find the AbinitNcFile subclass associated to files.
123        try:
124            file_class = abifile_subclass_from_filename(filename)
125        except KeyError:
126            if filename.endswith(".nc"): NcFilePopupMenu()
127            return None
128
129        # Check whether a subclass handles this file..
130        # Fallback to a simple PopupMenu if no match.
131        def allsubclasses(cls):
132            """Returns the set of subclasses of cls."""
133            children = [cls]
134            for sc in cls.__subclasses__():
135                if sc.__subclasses__():
136                    for k in sc.__subclasses__():
137                        children.extend(allsubclasses(k))
138                else:
139                    children.append(sc)
140            return set(children)
141
142        for cls in allsubclasses(PopupMenu):
143            if cls.handle_file_class(file_class):
144                return cls()
145        else:
146            if filename.endswith(".nc"): NcFilePopupMenu()
147            return PopupMenu()
148
149    @classmethod
150    def handle_file_class(cls, file_class):
151        """True if the popupmenu is associated to file_class."""
152        return file_class in cls.HANDLED_FILES
153
154    def _make_menu(self):
155        """Build the menu taking into account the options of the superclasses."""
156        base_classes = list(self.__class__.__bases__) + [self.__class__]
157        base_classes.reverse()
158
159        assert not hasattr(self, "menu_title_by_id")
160        assert not hasattr(self, "menu_titles")
161        self.menu_title_by_id, self.menu_titles = OrderedDict(), OrderedDict()
162
163        for cls in base_classes:
164            try:
165                menus = cls.MENU_TITLES
166            except AttributeError as exc:
167                awx.WARNING("exc ",exc," for cls", cls)
168                continue
169
170            self.menu_titles.update(menus)
171
172            for title in menus:
173                self.menu_title_by_id[wx.NewId()] = title
174
175            # Add sentinel for Menu separator.
176            self.menu_title_by_id["separator_" + str(len(self.menu_titles))] = None
177
178        for (id, title) in self.menu_title_by_id.items():
179            if title is None:
180                sepid = int(id.split("_")[-1])
181                if sepid != len(self.menu_titles):
182                    self.AppendSeparator()
183            else:
184                # Register menu handlers with EVT_MENU, on the menu.
185                self.Append(id, title)
186                wx.EVT_MENU(self, id, self.OnMenuSelection)
187
188    def set_parent(self, parent):
189        """Set the parent window."""
190        self._parent = parent
191
192    @property
193    def parent(self):
194        """Returns the parent window."""
195        try:
196            return self._parent
197        except AttributeError:
198            awx.WARNING("Popup menu doesn't have parent")
199            return None
200
201    def set_target(self, target):
202        """Set the target of the callback."""
203        self._target = target
204
205    @property
206    def target(self):
207        """The target of the callback."""
208        try:
209            return self._target
210        except AttributeError:
211            return None
212
213    def _get_callback(self, title):
214        return self.menu_titles[title]
215
216    def OnMenuSelection(self, event):
217        title = self.menu_title_by_id[event.GetId()]
218        callback = self._get_callback(title)
219        #print("Calling callback %s on target %s" % (callback, self.target))
220        try:
221            callback(parent=self.parent, filepath=self.target)
222        except:
223            awx.showErrorMessage(parent=self.parent)
224
225
226class AbinitTextFilePopupMenu(PopupMenu):
227    """
228    """
229    MENU_TITLES = OrderedDict([
230        ("events",     showAbinitEventsFrame),
231        ("properties", showFileStat),
232        ("timer",      showAbinitTimerFrame),
233    ])
234
235    HANDLED_FILES = [AbinitLogFile, AbinitOutputFile]
236
237
238class NcFilePopupMenu(PopupMenu):
239    """
240    Base class for popup menus. `A PopupMenu` has a list of callback functions
241    indexed by the menu title and a list of `AbinitNcFile` associated to it.
242    The signature of the callback function is func(filename, parent) where
243    filename is the name of the file selected in the Widget and parent is the wx
244    Window that will become the parent of the new frame created by the callback.
245
246    How to subclass PopupMenu:
247
248        1. Define a new class that inherits from NcFilePopupMenu.
249
250        2. Define the callbacks in the class variable MENU_TITLES.
251           Use OrderedDict to have a fixed ordering of the labels.
252
253        3. Define the class variable HANDLED_FILES with the list of
254           `AbinitNcFile` subclasses associated to the popupmenu.
255
256        4. Done (most of the work is indeed done in the base class and in
257           the factory function popupmenu_for_filename.
258    """
259    MENU_TITLES = OrderedDict([
260        ("structure",  showStructure),
261        ("ncdump",     showNcdumpMessage),
262        ("wxncview",   openWxncview),
263        ("properties", showFileStat),
264    ])
265
266    HANDLED_FILES = []
267
268
269class EbandsPopupMenu(NcFilePopupMenu):
270    """Popup menu for Netcdf files that contain the electron band structure."""
271    MENU_TITLES = OrderedDict([
272        ("ePlot", ewx.showElectronBandsPlot),
273        ("eDos",  ewx.showElectronDosFrame),
274        ("eJdos", ewx.showElectronJdosFrame),
275    ])
276
277    HANDLED_FILES = [WfkFile, GsrFile]
278
279
280def showQPData(parent, filepath):
281    sigres = abiopen(filepath)
282    qps_spin = sigres.qplist_spin
283    assert len(qps_spin) == 1
284    qps_spin[0].plot_qps_vs_e0()
285
286
287class SigResPopupMenu(NcFilePopupMenu):
288    """Popup menu for SIGRES files."""
289    MENU_TITLES = OrderedDict([
290        ("qpDataPlot", showQPData),
291    ])
292
293    HANDLED_FILES = [SigresFile]
294
295
296def showEXCMDF(parent, filepath):
297    mdf_file = MdfFile(filepath)
298    mdf_file.plot_mdfs()
299
300
301class MDFPopupMenu(NcFilePopupMenu):
302    """Popup menu for MDF files."""
303    MENU_TITLES = OrderedDict([
304        ("mdfPlot", showEXCMDF),
305    ])
306
307    HANDLED_FILES = [MdfFile]
308
309