Package gui :: Module pipe_editor
[hide private]
[frames] | no frames]

Source Code for Module gui.pipe_editor

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