"Fossies" - the Fresh Open Source Software Archive

Member "relax-5.0.0/gui/pipe_editor.py" (2 Dec 2019, 20730 Bytes) of package /linux/privat/relax-5.0.0.src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "pipe_editor.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.1.3_vs_5.0.0.

    1 ###############################################################################
    2 #                                                                             #
    3 # Copyright (C) 2011-2013,2015,2017 Edward d'Auvergne                         #
    4 #                                                                             #
    5 # This file is part of the program relax (http://www.nmr-relax.com).          #
    6 #                                                                             #
    7 # This program is free software: you can redistribute it and/or modify        #
    8 # it under the terms of the GNU General Public License as published by        #
    9 # the Free Software Foundation, either version 3 of the License, or           #
   10 # (at your option) any later version.                                         #
   11 #                                                                             #
   12 # This program is distributed in the hope that it will be useful,             #
   13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
   14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
   15 # GNU General Public License for more details.                                #
   16 #                                                                             #
   17 # You should have received a copy of the GNU General Public License           #
   18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
   19 #                                                                             #
   20 ###############################################################################
   21 
   22 # Module docstring.
   23 """The pipe editor GUI element."""
   24 
   25 # Python module imports.
   26 import wx
   27 import wx.grid
   28 
   29 # relax module imports.
   30 from data_store import Relax_data_store; ds = Relax_data_store()
   31 from graphics import WIZARD_IMAGE_PATH, fetch_icon
   32 from gui.components.menu import build_menu_item
   33 from gui.fonts import font
   34 from gui.icons import Relax_icons
   35 from gui.message import Question
   36 from gui.misc import add_border, bitmap_setup
   37 from gui.string_conv import gui_to_str, str_to_gui
   38 from gui.uf_objects import Uf_storage; uf_store = Uf_storage()
   39 from lib.errors import RelaxError
   40 from pipe_control.pipes import cdp_name, delete, get_bundle, get_type, pipe_names, switch
   41 from status import Status; status = Status()
   42 
   43 
   44 # Some IDs for the menu entries.
   45 MENU_BUNDLE = wx.NewId()
   46 MENU_DELETE = wx.NewId()
   47 MENU_SWITCH = wx.NewId()
   48 MENU_NEW_AUTO_ANALYSIS = wx.NewId()
   49 
   50 
   51 class Pipe_editor(wx.Frame):
   52     """The pipe editor window object."""
   53 
   54     def __init__(self, gui=None, size_x=1000, size_y=600, border=10):
   55         """Set up the relax controller frame.
   56         
   57         @keyword gui:       The main GUI object.
   58         @type gui:          wx.Frame instance
   59         @keyword size_x:    The initial and minimum width of the window.
   60         @type size_x:       int
   61         @keyword size_y:    The initial and minimum height of the window.
   62         @type size_y:       int
   63         @keyword border:    The size of the internal border of the window.
   64         @type border:       int
   65         """
   66 
   67         # Store the args.
   68         self.gui = gui
   69         self.border = border
   70 
   71         # Create GUI elements
   72         wx.Frame.__init__(self, None, id=-1, title="Data pipe editor")
   73 
   74         # Set up the window icon.
   75         self.SetIcons(Relax_icons())
   76 
   77         # Initialise some data.
   78         self.width_col_label = 40
   79 
   80         # Set the normal and minimum window sizes.
   81         self.SetMinSize((size_x, size_y))
   82         self.SetSize((size_x, size_y))
   83 
   84         # Place all elements within a panel (to remove the dark grey in MS Windows).
   85         self.main_panel = wx.Panel(self, -1)
   86 
   87         # Pack a sizer into the panel.
   88         main_sizer = wx.BoxSizer(wx.VERTICAL)
   89         self.main_panel.SetSizer(main_sizer)
   90 
   91         # Build the central sizer, with borders.
   92         sizer = add_border(main_sizer, border=border, packing=wx.VERTICAL)
   93 
   94         # Add the contents.
   95         sizer.AddSpacer(10)
   96         self.add_logo(sizer)
   97         sizer.AddSpacer(20)
   98         self.add_buttons(sizer)
   99         sizer.AddSpacer(10)
  100         self.add_table(sizer)
  101 
  102         # Bind some events.
  103         self.grid.Bind(wx.EVT_SIZE, self.resize)
  104         self.Bind(wx.EVT_CLOSE, self.handler_close)
  105         self.grid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.menu)
  106 
  107         # Initialise the observer name.
  108         self.name = 'pipe editor'
  109 
  110         # Update the grid.
  111         self.update_grid()
  112 
  113 
  114     def activate(self):
  115         """Activate or deactivate certain elements in response to the execution lock."""
  116 
  117         # Turn off all buttons.
  118         if status.exec_lock.locked():
  119             wx.CallAfter(self.button_bundle.Enable, False)
  120             wx.CallAfter(self.button_create.Enable, False)
  121             wx.CallAfter(self.button_copy.Enable, False)
  122             wx.CallAfter(self.button_delete.Enable, False)
  123             wx.CallAfter(self.button_hybrid.Enable, False)
  124             wx.CallAfter(self.button_switch.Enable, False)
  125 
  126         # Turn on all buttons.
  127         else:
  128             wx.CallAfter(self.button_bundle.Enable, True)
  129             wx.CallAfter(self.button_create.Enable, True)
  130             wx.CallAfter(self.button_copy.Enable, True)
  131             wx.CallAfter(self.button_delete.Enable, True)
  132             wx.CallAfter(self.button_hybrid.Enable, True)
  133             wx.CallAfter(self.button_switch.Enable, True)
  134 
  135 
  136     def menu(self, event):
  137         """The pop up menu.
  138 
  139         @param event:   The wx event.
  140         @type event:    wx event
  141         """
  142 
  143         # Get the row.
  144         row = event.GetRow()
  145 
  146         # Get the name of the data pipe.
  147         self.selected_pipe = gui_to_str(self.grid.GetCellValue(row, 0))
  148 
  149         # No data pipe.
  150         if not self.selected_pipe:
  151             return
  152 
  153         # The pipe type and bundle.
  154         pipe_type = get_type(self.selected_pipe)
  155         pipe_bundle = get_bundle(self.selected_pipe)
  156 
  157         # Initialise the menu.
  158         popup_menus = []
  159 
  160         # Menu entry:  add the data pipe to a bundle.
  161         if not pipe_bundle:
  162             popup_menus.append({
  163                 'id': MENU_BUNDLE,
  164                 'text': "&Add the pipe to a bundle",
  165                 'icon': fetch_icon("relax.pipe_bundle"),
  166                 'method': self.pipe_bundle
  167             })
  168 
  169         # Menu entry:  delete the data pipe.
  170         popup_menus.append({
  171             'id': MENU_DELETE,
  172             'text': "&Delete the pipe",
  173             'icon': fetch_icon('oxygen.actions.list-remove', "16x16"),
  174             'method': self.pipe_delete
  175         })
  176 
  177         # Menu entry:  switch to this data pipe.
  178         popup_menus.append({
  179             'id': MENU_SWITCH,
  180             'text': "&Switch to this pipe",
  181             'icon': fetch_icon('oxygen.actions.system-switch-user', "16x16"),
  182             'method': self.pipe_switch
  183         })
  184 
  185         # Menu entry:  new auto-analysis tab.
  186         if pipe_bundle and self.gui.analysis.page_index_from_bundle(pipe_bundle) == None and pipe_type in ['noe', 'r1', 'r2', 'mf', 'relax_disp']:
  187             popup_menus.append({
  188                 'id': MENU_NEW_AUTO_ANALYSIS,
  189                 'text': "&Associate with a new auto-analysis",
  190                 'icon': fetch_icon('oxygen.actions.document-new', "16x16"),
  191                 'method': self.associate_auto
  192             })
  193 
  194         # Execution lock, so do nothing.
  195         if status.exec_lock.locked():
  196             return
  197 
  198         # Initialise the menu.
  199         menu = wx.Menu()
  200 
  201         # Loop over the menu items.
  202         for i in range(len(popup_menus)):
  203             # Alias.
  204             info = popup_menus[i]
  205 
  206             # Add the menu item.
  207             build_menu_item(menu, id=info['id'], text=info['text'], icon=info['icon'])
  208 
  209             # Bind clicks.
  210             self.Bind(wx.EVT_MENU, info['method'], id=info['id'])
  211 
  212         # Pop up the menu.
  213         if status.show_gui:
  214             self.PopupMenu(menu)
  215 
  216         # Cleanup.
  217         menu.Destroy()
  218 
  219 
  220     def add_buttons(self, sizer):
  221         """Add the buttons to the sizer.
  222 
  223         @param sizer:   The sizer element to pack the buttons into.
  224         @type sizer:    wx.Sizer instance
  225         """
  226 
  227         # Create a horizontal layout for the buttons.
  228         button_sizer = wx.BoxSizer(wx.HORIZONTAL)
  229         sizer.Add(button_sizer, 0, wx.ALL|wx.EXPAND, 0)
  230 
  231         # The bundle button.
  232         self.button_bundle = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Bundle")
  233         self.button_bundle.SetBitmapLabel(wx.Bitmap(fetch_icon("relax.pipe_bundle", size="22x22"), wx.BITMAP_TYPE_ANY))
  234         self.button_bundle.SetFont(font.normal)
  235         self.button_bundle.SetToolTip(wx.ToolTip("Add a data pipe to a data pipe bundle."))
  236         button_sizer.Add(self.button_bundle, 1, wx.ALL|wx.EXPAND, 0)
  237         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_bundle)
  238 
  239         # The create button.
  240         self.button_create = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Create")
  241         self.button_create.SetBitmapLabel(wx.Bitmap(fetch_icon('oxygen.actions.list-add-relax-blue', "22x22"), wx.BITMAP_TYPE_ANY))
  242         self.button_create.SetFont(font.normal)
  243         self.button_create.SetToolTip(wx.ToolTip("Create a new data pipe."))
  244         button_sizer.Add(self.button_create, 1, wx.ALL|wx.EXPAND, 0)
  245         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_create)
  246 
  247         # The copy button.
  248         self.button_copy = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Copy")
  249         self.button_copy.SetBitmapLabel(wx.Bitmap(fetch_icon('oxygen.actions.list-add', "22x22"), wx.BITMAP_TYPE_ANY))
  250         self.button_copy.SetFont(font.normal)
  251         self.button_copy.SetToolTip(wx.ToolTip("Copy a data pipe."))
  252         button_sizer.Add(self.button_copy, 1, wx.ALL|wx.EXPAND, 0)
  253         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_copy)
  254 
  255         # The delete button.
  256         self.button_delete = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Delete")
  257         self.button_delete.SetBitmapLabel(wx.Bitmap(fetch_icon('oxygen.actions.list-remove', "22x22"), wx.BITMAP_TYPE_ANY))
  258         self.button_delete.SetFont(font.normal)
  259         self.button_delete.SetToolTip(wx.ToolTip("Delete a data pipe."))
  260         button_sizer.Add(self.button_delete, 1, wx.ALL|wx.EXPAND, 0)
  261         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_delete)
  262 
  263         # The hybridise button.
  264         self.button_hybrid = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Hybridise")
  265         self.button_hybrid.SetBitmapLabel(wx.Bitmap(fetch_icon('relax.pipe_hybrid', "22x22"), wx.BITMAP_TYPE_ANY))
  266         self.button_hybrid.SetFont(font.normal)
  267         self.button_hybrid.SetToolTip(wx.ToolTip("Hybridise data pipes."))
  268         button_sizer.Add(self.button_hybrid, 1, wx.ALL|wx.EXPAND, 0)
  269         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_hybrid)
  270 
  271         # The switch button.
  272         self.button_switch = wx.lib.buttons.ThemedGenBitmapTextButton(self.main_panel, -1, None, " Switch")
  273         self.button_switch.SetBitmapLabel(wx.Bitmap(fetch_icon('oxygen.actions.system-switch-user', "22x22"), wx.BITMAP_TYPE_ANY))
  274         self.button_switch.SetFont(font.normal)
  275         self.button_switch.SetToolTip(wx.ToolTip("Switch data pipes."))
  276         button_sizer.Add(self.button_switch, 1, wx.ALL|wx.EXPAND, 0)
  277         self.Bind(wx.EVT_BUTTON, self.uf_launch, self.button_switch)
  278 
  279 
  280     def uf_launch(self, event):
  281         """Launch the user function GUI wizards.
  282 
  283         @param event:   The wx event.
  284         @type event:    wx event
  285         """
  286 
  287         # Launch the respective user functions.
  288         if event.GetEventObject() == self.button_bundle:
  289             uf_store['pipe.bundle'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  290         elif event.GetEventObject() == self.button_create:
  291             uf_store['pipe.create'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  292         elif event.GetEventObject() == self.button_copy:
  293             uf_store['pipe.copy'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  294         elif event.GetEventObject() == self.button_delete:
  295             uf_store['pipe.delete'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  296         elif event.GetEventObject() == self.button_hybrid:
  297             uf_store['pipe.hybridise'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  298         elif event.GetEventObject() == self.button_switch:
  299             uf_store['pipe.switch'](event, wx_parent=self, wx_wizard_sync=True, wx_wizard_modal=True)
  300 
  301 
  302     def add_logo(self, box):
  303         """Add the logo to the sizer.
  304 
  305         @param box:     The sizer element to pack the logo into.
  306         @type box:      wx.Sizer instance
  307         """
  308 
  309         # The pipe logo.
  310         logo = wx.StaticBitmap(self.main_panel, -1, bitmap_setup(WIZARD_IMAGE_PATH+'pipe_200x90.png'))
  311 
  312         # Pack the logo.
  313         box.Add(logo, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
  314 
  315 
  316     def add_table(self, sizer):
  317         """Add the table to the sizer.
  318 
  319         @param sizer:   The sizer element to pack the table into.
  320         @type sizer:    wx.Sizer instance
  321         """
  322 
  323         # Grid of all data pipes.
  324         self.grid = wx.grid.Grid(self.main_panel, -1)
  325 
  326         # Initialise to a single row and 5 columns.
  327         self.grid.CreateGrid(1, 5)
  328 
  329         # Set the headers.
  330         self.grid.SetColLabelValue(0, "Data pipe")
  331         self.grid.SetColLabelValue(1, "Type")
  332         self.grid.SetColLabelValue(2, "Bundle")
  333         self.grid.SetColLabelValue(3, "Current")
  334         self.grid.SetColLabelValue(4, "Analysis tab")
  335 
  336         # Properties.
  337         self.grid.SetDefaultCellFont(font.normal)
  338         self.grid.SetLabelFont(font.normal_bold)
  339 
  340         # Set the row label widths.
  341         self.grid.SetRowLabelSize(self.width_col_label)
  342 
  343         # No cell resizing allowed.
  344         self.grid.EnableDragColSize(False)
  345         self.grid.EnableDragRowSize(False)
  346 
  347         # Add grid to sizer.
  348         sizer.Add(self.grid, 1, wx.ALL|wx.EXPAND, 0)
  349 
  350 
  351     def associate_auto(self, event):
  352         """Associate the selected data pipe with a new auto-analysis.
  353 
  354         @param event:   The wx event.
  355         @type event:    wx event
  356         """
  357 
  358         # Initialise the GUI data store object if needed.
  359         if not hasattr(ds, 'relax_gui'):
  360             self.gui.init_data()
  361 
  362         # The type and data pipe bundle.
  363         type = get_type(self.selected_pipe)
  364         bundle = get_bundle(self.selected_pipe)
  365 
  366         # Error checking.
  367         if self.selected_pipe == None:
  368             raise RelaxError("No data pipe has been selected - this is not possible.")
  369         if bundle == None:
  370             raise RelaxError("The selected data pipe is not associated with a data pipe bundle.")
  371 
  372         # The name.
  373         names = {
  374             'noe': 'Steady-state NOE',
  375             'r1': 'R1 relaxation',
  376             'r2': 'R2 relaxation',
  377             'mf': 'Model-free',
  378             'relax_disp': 'Relaxation dispersion'
  379         }
  380 
  381         # Create a new analysis with the selected data pipe.
  382         self.gui.analysis.new_analysis(analysis_type=type, analysis_name=names[type], pipe_name=self.selected_pipe, pipe_bundle=bundle)
  383 
  384 
  385     def handler_close(self, event):
  386         """Event handler for the close window action.
  387 
  388         @param event:   The wx event.
  389         @type event:    wx event
  390         """
  391 
  392         # Unregister the methods from the observers to avoid unnecessary updating.
  393         self.observer_setup(register=False)
  394 
  395         # Close the window.
  396         self.Hide()
  397 
  398 
  399     def observer_setup(self, register=True):
  400         """Register and unregister with the observer objects.
  401 
  402         @keyword register:  A flag which if True will register with the observers and if False will unregister all methods.
  403         @type register:     bool
  404         """
  405 
  406         # Register the methods with the observers.
  407         if register:
  408             status.observers.pipe_alteration.register(self.name, self.update_grid, method_name='update_grid')
  409             status.observers.gui_analysis.register(self.name, self.update_grid, method_name='update_grid')
  410             status.observers.exec_lock.register(self.name, self.activate, method_name='activate')
  411 
  412         # Unregister the methods.
  413         else:
  414             status.observers.pipe_alteration.unregister(self.name)
  415             status.observers.gui_analysis.unregister(self.name)
  416             status.observers.exec_lock.unregister(self.name)
  417 
  418 
  419     def pipe_bundle(self, event):
  420         """Bundle the date pipe.
  421 
  422         @param event:   The wx event.
  423         @type event:    wx event
  424         """
  425 
  426         # Bundle the data pipe.
  427         uf_store['pipe.bundle'](event, wx_parent=self, pipe=self.selected_pipe)
  428 
  429 
  430     def pipe_delete(self, event):
  431         """Delete the date pipe.
  432 
  433         @param event:   The wx event.
  434         @type event:    wx event
  435         """
  436 
  437         # Ask if this should be done.
  438         msg = "Are you sure you would like to delete the '%s' data pipe?  This operation cannot be undone." % self.selected_pipe
  439         if status.show_gui and Question(msg, parent=self, size=(350, 200), default=False).ShowModal() == wx.ID_NO:
  440             return
  441 
  442         # Delete the data pipe.
  443         delete(self.selected_pipe)
  444 
  445 
  446     def pipe_switch(self, event):
  447         """Switch to the selected date pipe.
  448 
  449         @param event:   The wx event.
  450         @type event:    wx event
  451         """
  452 
  453         # Switch to the selected data pipe.
  454         switch(self.selected_pipe)
  455 
  456         # Bug fix for MS Windows.
  457         wx.CallAfter(self.Raise)
  458 
  459 
  460     def resize(self, event):
  461         """Catch the resize to allow the grid to be resized.
  462 
  463         @param event:   The wx event.
  464         @type event:    wx event
  465         """
  466 
  467         # Set the column sizes.
  468         self.size_cols()
  469 
  470         # Continue with the normal resizing.
  471         event.Skip()
  472 
  473 
  474     def size_cols(self):
  475         """Set the column sizes."""
  476 
  477         # The grid size.
  478         x, y = self.grid.GetSize()
  479 
  480         # Number of columns.
  481         n = 5
  482 
  483         # The width of the current data pipe column.
  484         width_col_curr = 80
  485 
  486         # Set to equal sizes.
  487         width = int((x - self.width_col_label - width_col_curr) / (n - 1))
  488 
  489         # Set the column sizes.
  490         for i in range(n):
  491             # The narrower cdp column.
  492             if i == 3:
  493                 self.grid.SetColSize(i, width_col_curr)
  494 
  495             # All others.
  496             else:
  497                 self.grid.SetColSize(i, width)
  498 
  499 
  500     def update_grid(self):
  501         """Update the grid in a thread safe way using wx.CallAfter."""
  502 
  503         # Thread safe.
  504         wx.CallAfter(self.update_grid_safe)
  505 
  506         # Flush the events.
  507         wx.GetApp().Yield(True)
  508 
  509 
  510     def update_grid_safe(self):
  511         """Update the grid with the pipe data."""
  512 
  513         # First freeze the grid, so that the GUI element doesn't update until the end.
  514         self.grid.Freeze()
  515 
  516         # Acquire the pipe lock.
  517         status.pipe_lock.acquire('pipe editor window')
  518 
  519         # Delete the rows, leaving a single row.
  520         self.grid.DeleteRows(numRows=self.grid.GetNumberRows()-1)
  521 
  522         # Clear the contents of the first row.
  523         for i in range(self.grid.GetNumberCols()):
  524             self.grid.SetCellValue(0, i, str_to_gui(""))
  525 
  526         # The data pipes.
  527         pipe_list = pipe_names()
  528         n = len(pipe_list)
  529 
  530         # Append the appropriate number of rows.
  531         if n >= 1:
  532             self.grid.AppendRows(numRows=n-1)
  533 
  534         # Loop over the data pipes.
  535         for i in range(n):
  536             # Set the pipe name.
  537             self.grid.SetCellValue(i, 0, str_to_gui(pipe_list[i]))
  538 
  539             # Set the pipe type.
  540             self.grid.SetCellValue(i, 1, str_to_gui(get_type(pipe_list[i])))
  541 
  542             # Set the pipe bundle.
  543             self.grid.SetCellValue(i, 2, str_to_gui(get_bundle(pipe_list[i])))
  544 
  545             # Set the current pipe.
  546             if pipe_list[i] == cdp_name():
  547                 self.grid.SetCellValue(i, 3, str_to_gui("cdp"))
  548 
  549             # Set the tab the pipe belongs to.
  550             self.grid.SetCellValue(i, 4, str_to_gui(self.gui.analysis.page_name_from_bundle(get_bundle(pipe_list[i]))))
  551 
  552         # Set the grid properties once finalised.
  553         for i in range(self.grid.GetNumberRows()):
  554             # Row properties.
  555             self.grid.SetRowSize(i, 27)
  556 
  557             # Loop over the columns.
  558             for j in range(self.grid.GetNumberCols()):
  559                 # Cell properties.
  560                 self.grid.SetReadOnly(i, j)
  561 
  562         # Release the lock.
  563         status.pipe_lock.release('pipe editor window')
  564 
  565         # Unfreeze.
  566         self.grid.Thaw()