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